diff options
Diffstat (limited to 'editeng/source')
109 files changed, 77773 insertions, 0 deletions
diff --git a/editeng/source/accessibility/AccessibleComponentBase.cxx b/editeng/source/accessibility/AccessibleComponentBase.cxx new file mode 100644 index 0000000000..5e95afbd2f --- /dev/null +++ b/editeng/source/accessibility/AccessibleComponentBase.cxx @@ -0,0 +1,149 @@ +/* -*- 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 <editeng/AccessibleComponentBase.hxx> + +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> + +#include <tools/color.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility { + +// internal + +AccessibleComponentBase::AccessibleComponentBase() +{ +} + + +AccessibleComponentBase::~AccessibleComponentBase() +{ +} + +// XAccessibleComponent + +sal_Bool SAL_CALL AccessibleComponentBase::containsPoint ( + const css::awt::Point& aPoint) +{ + awt::Size aSize (getSize()); + return (aPoint.X >= 0) + && (aPoint.X < aSize.Width) + && (aPoint.Y >= 0) + && (aPoint.Y < aSize.Height); +} + + +uno::Reference<XAccessible > SAL_CALL + AccessibleComponentBase::getAccessibleAtPoint ( + const awt::Point& /*aPoint*/) +{ + return uno::Reference<XAccessible>(); +} + + +awt::Rectangle SAL_CALL AccessibleComponentBase::getBounds() +{ + return awt::Rectangle(); +} + + +awt::Point SAL_CALL AccessibleComponentBase::getLocation() +{ + awt::Rectangle aBBox (getBounds()); + return awt::Point (aBBox.X, aBBox.Y); +} + + +awt::Point SAL_CALL AccessibleComponentBase::getLocationOnScreen() +{ + return awt::Point(); +} + + +css::awt::Size SAL_CALL AccessibleComponentBase::getSize() +{ + awt::Rectangle aBBox (getBounds()); + return awt::Size (aBBox.Width, aBBox.Height); +} + + +void SAL_CALL AccessibleComponentBase::grabFocus() +{ + uno::Reference<XAccessibleContext> xContext (this, uno::UNO_QUERY); + uno::Reference<XAccessibleSelection> xSelection ( + xContext->getAccessibleParent(), uno::UNO_QUERY); + if (xSelection.is()) + { + // Do a single selection on this object. + xSelection->clearAccessibleSelection(); + xSelection->selectAccessibleChild (xContext->getAccessibleIndexInParent()); + } +} + + +sal_Int32 SAL_CALL AccessibleComponentBase::getForeground() +{ + return sal_Int32(COL_BLACK); +} + + +sal_Int32 SAL_CALL AccessibleComponentBase::getBackground() +{ + return sal_Int32(COL_WHITE); +} + + +// XAccessibleExtendedComponent + +css::uno::Reference< css::awt::XFont > SAL_CALL + AccessibleComponentBase::getFont() +{ + return uno::Reference<awt::XFont>(); +} + + +OUString SAL_CALL AccessibleComponentBase::getTitledBorderText() +{ + return OUString(); +} + + +OUString SAL_CALL AccessibleComponentBase::getToolTipText() +{ + return OUString(); +} + +// XTypeProvider + +uno::Sequence<uno::Type> + AccessibleComponentBase::getTypes() +{ + static const uno::Sequence aTypeList { + cppu::UnoType<XAccessibleComponent>::get(), + cppu::UnoType<XAccessibleExtendedComponent>::get() }; + return aTypeList; +} + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleContextBase.cxx b/editeng/source/accessibility/AccessibleContextBase.cxx new file mode 100644 index 0000000000..df52b70e78 --- /dev/null +++ b/editeng/source/accessibility/AccessibleContextBase.cxx @@ -0,0 +1,513 @@ +/* -*- 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 <editeng/AccessibleContextBase.hxx> + +#include <com/sun/star/accessibility/XAccessibleEventListener.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> + +#include <unotools/accessiblerelationsethelper.hxx> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <rtl/ref.hxx> + +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility { + +// internal + +AccessibleContextBase::AccessibleContextBase ( + uno::Reference<XAccessible> xParent, + const sal_Int16 aRole) + : WeakComponentImplHelper(m_aMutex), + mxParent(std::move(xParent)), + meDescriptionOrigin(NotSet), + meNameOrigin(NotSet), + mnClientId(0), + maRole(aRole) +{ + // Create the state set. + mnStateSet = 0; + + // Set some states. Don't use the SetState method because no events + // shall be broadcasted (that is not yet initialized anyway). + mnStateSet |= AccessibleStateType::ENABLED; + mnStateSet |= AccessibleStateType::SENSITIVE; + mnStateSet |= AccessibleStateType::SHOWING; + mnStateSet |= AccessibleStateType::VISIBLE; + mnStateSet |= AccessibleStateType::FOCUSABLE; + mnStateSet |= AccessibleStateType::SELECTABLE; + + // Create the relation set. + mxRelationSet = new ::utl::AccessibleRelationSetHelper (); +} + +AccessibleContextBase::~AccessibleContextBase() +{ +} + +bool AccessibleContextBase::SetState (sal_Int64 aState) +{ + ::osl::ClearableMutexGuard aGuard (m_aMutex); + if (!(mnStateSet & aState)) + { + mnStateSet |= aState; + // Clear the mutex guard so that it is not locked during calls to + // listeners. + aGuard.clear(); + + // Send event for all states except the DEFUNC state. + if (aState != AccessibleStateType::DEFUNC) + { + uno::Any aNewValue; + aNewValue <<= aState; + CommitChange( + AccessibleEventId::STATE_CHANGED, + aNewValue, + uno::Any(), -1); + } + return true; + } + else + return false; +} + + +bool AccessibleContextBase::ResetState (sal_Int64 aState) +{ + ::osl::ClearableMutexGuard aGuard (m_aMutex); + if (mnStateSet & aState) + { + mnStateSet &= ~aState; + // Clear the mutex guard so that it is not locked during calls to listeners. + aGuard.clear(); + + uno::Any aOldValue; + aOldValue <<= aState; + CommitChange( + AccessibleEventId::STATE_CHANGED, + uno::Any(), + aOldValue, -1); + return true; + } + else + return false; +} + + +bool AccessibleContextBase::GetState (sal_Int64 aState) +{ + ::osl::MutexGuard aGuard (m_aMutex); + return mnStateSet & aState; +} + + +void AccessibleContextBase::SetRelationSet ( + const rtl::Reference<utl::AccessibleRelationSetHelper>& rxNewRelationSet) +{ + // Try to emit some meaningful events indicating differing relations in + // both sets. + typedef std::pair<short int,short int> RD; + const RD aRelationDescriptors[] = { + RD(AccessibleRelationType::CONTROLLED_BY, AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED), + RD(AccessibleRelationType::CONTROLLER_FOR, AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED), + RD(AccessibleRelationType::LABELED_BY, AccessibleEventId::LABELED_BY_RELATION_CHANGED), + RD(AccessibleRelationType::LABEL_FOR, AccessibleEventId::LABEL_FOR_RELATION_CHANGED), + RD(AccessibleRelationType::MEMBER_OF, AccessibleEventId::MEMBER_OF_RELATION_CHANGED), + RD(AccessibleRelationType::INVALID, -1), + }; + for (int i=0; aRelationDescriptors[i].first!=AccessibleRelationType::INVALID; i++) + if (mxRelationSet->containsRelation(aRelationDescriptors[i].first) + != rxNewRelationSet->containsRelation(aRelationDescriptors[i].first)) + CommitChange (aRelationDescriptors[i].second, uno::Any(), uno::Any(), -1); + + mxRelationSet = rxNewRelationSet; +} + + +// XAccessible + +uno::Reference< XAccessibleContext> SAL_CALL + AccessibleContextBase::getAccessibleContext() +{ + return this; +} + + +// XAccessibleContext + +/** No children. +*/ +sal_Int64 SAL_CALL + AccessibleContextBase::getAccessibleChildCount() +{ + return 0; +} + + +/** Forward the request to the shape. Return the requested shape or throw + an exception for a wrong index. +*/ +uno::Reference<XAccessible> SAL_CALL + AccessibleContextBase::getAccessibleChild (sal_Int64 nIndex) +{ + ThrowIfDisposed (); + throw lang::IndexOutOfBoundsException ( + "no child with index " + OUString::number(nIndex), + nullptr); +} + + +uno::Reference<XAccessible> SAL_CALL + AccessibleContextBase::getAccessibleParent() +{ + ThrowIfDisposed (); + return mxParent; +} + + +sal_Int64 SAL_CALL + AccessibleContextBase::getAccessibleIndexInParent() +{ + ThrowIfDisposed (); + // Use a simple but slow solution for now. Optimize later. + + // Iterate over all the parent's children and search for this object. + if (!mxParent.is()) + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; + + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + { + sal_Int64 nChildCount = xParentContext->getAccessibleChildCount(); + for (sal_Int64 i=0; i<nChildCount; i++) + { + uno::Reference<XAccessible> xChild (xParentContext->getAccessibleChild (i)); + if (xChild.is()) + { + uno::Reference<XAccessibleContext> xChildContext = xChild->getAccessibleContext(); + if (xChildContext == static_cast<XAccessibleContext*>(this)) + return i; + } + } + } + + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; +} + + +sal_Int16 SAL_CALL + AccessibleContextBase::getAccessibleRole() +{ + ThrowIfDisposed (); + return maRole; +} + + +OUString SAL_CALL + AccessibleContextBase::getAccessibleDescription() +{ + ThrowIfDisposed (); + + return msDescription; +} + + +OUString SAL_CALL + AccessibleContextBase::getAccessibleName() +{ + ThrowIfDisposed (); + + if (meNameOrigin == NotSet) + { + // Do not send an event because this is the first time it has been + // requested. + msName = CreateAccessibleName(); + meNameOrigin = AutomaticallyCreated; + } + + return msName; +} + + +/** Return a copy of the relation set. +*/ +uno::Reference<XAccessibleRelationSet> SAL_CALL + AccessibleContextBase::getAccessibleRelationSet() +{ + ThrowIfDisposed (); + + // Create a copy of the relation set and return it. + if (mxRelationSet) + { + return new ::utl::AccessibleRelationSetHelper(*mxRelationSet); + } + else + return uno::Reference<XAccessibleRelationSet>(nullptr); +} + + +/** Return a copy of the state set. + Possible states are: + ENABLED + SHOWING + VISIBLE +*/ +sal_Int64 SAL_CALL + AccessibleContextBase::getAccessibleStateSet() +{ + if (rBHelper.bDisposed) + { + // We are already disposed! + // Create a new state set that has only set the DEFUNC state. + return AccessibleStateType::DEFUNC; + } + else + { + return mnStateSet; + } +} + + +lang::Locale SAL_CALL + AccessibleContextBase::getLocale() +{ + ThrowIfDisposed (); + // Delegate request to parent. + if (mxParent.is()) + { + uno::Reference<XAccessibleContext> xParentContext ( + mxParent->getAccessibleContext()); + if (xParentContext.is()) + return xParentContext->getLocale (); + } + + // No locale and no parent. Therefore throw exception to indicate this + // cluelessness. + throw IllegalAccessibleComponentStateException (); +} + + +// XAccessibleEventListener + +void SAL_CALL AccessibleContextBase::addAccessibleEventListener ( + const uno::Reference<XAccessibleEventListener >& rxListener) +{ + if (!rxListener.is()) + return; + + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + uno::Reference<uno::XInterface> x (static_cast<lang::XComponent *>(this), uno::UNO_QUERY); + rxListener->disposing (lang::EventObject (x)); + } + else + { + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, rxListener ); + } +} + + +void SAL_CALL AccessibleContextBase::removeAccessibleEventListener ( + const uno::Reference<XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed (); + if (!(rxListener.is() && mnClientId)) + return; + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, rxListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( mnClientId ); + mnClientId = 0; + } +} + +// XServiceInfo +OUString SAL_CALL AccessibleContextBase::getImplementationName() +{ + return "AccessibleContextBase"; +} + +sal_Bool SAL_CALL AccessibleContextBase::supportsService (const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +uno::Sequence< OUString > SAL_CALL + AccessibleContextBase::getSupportedServiceNames() +{ + return { + "com.sun.star.accessibility.Accessible", + "com.sun.star.accessibility.AccessibleContext"}; +} + + +// XTypeProvider + +uno::Sequence<sal_Int8> SAL_CALL + AccessibleContextBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// internal + +void SAL_CALL AccessibleContextBase::disposing() +{ + SetState (AccessibleStateType::DEFUNC); + + ::osl::MutexGuard aGuard (m_aMutex); + + // Send a disposing to all listeners. + if ( mnClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( mnClientId, *this ); + mnClientId = 0; + } + mxParent.clear(); + mxRelationSet.clear(); +} + + +void AccessibleContextBase::SetAccessibleDescription ( + const OUString& rDescription, + StringOrigin eDescriptionOrigin) +{ + if (!(eDescriptionOrigin < meDescriptionOrigin + || (eDescriptionOrigin == meDescriptionOrigin && msDescription != rDescription))) + return; + + uno::Any aOldValue, aNewValue; + aOldValue <<= msDescription; + aNewValue <<= rDescription; + + msDescription = rDescription; + meDescriptionOrigin = eDescriptionOrigin; + + CommitChange( + AccessibleEventId::DESCRIPTION_CHANGED, + aNewValue, + aOldValue, -1); +} + + +void AccessibleContextBase::SetAccessibleName ( + const OUString& rName, + StringOrigin eNameOrigin) +{ + if (!(eNameOrigin < meNameOrigin + || (eNameOrigin == meNameOrigin && msName != rName))) + return; + + uno::Any aOldValue, aNewValue; + aOldValue <<= msName; + aNewValue <<= rName; + + msName = rName; + meNameOrigin = eNameOrigin; + + CommitChange( + AccessibleEventId::NAME_CHANGED, + aNewValue, + aOldValue, -1); +} + + +OUString AccessibleContextBase::CreateAccessibleName() +{ + return "Empty Name"; +} + + +void AccessibleContextBase::CommitChange ( + sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue, + sal_Int32 nValueIndex) +{ + // Do not call FireEvent and do not even create the event object when no + // listener has been registered yet. Creating the event object can + // otherwise lead to a crash. See issue 93419 for details. + if (mnClientId != 0) + { + AccessibleEventObject aEvent ( + static_cast<XAccessibleContext*>(this), + nEventId, + rNewValue, + rOldValue, + nValueIndex); + + FireEvent (aEvent); + } +} + + +void AccessibleContextBase::FireEvent (const AccessibleEventObject& aEvent) +{ + if (mnClientId) + comphelper::AccessibleEventNotifier::addEvent( mnClientId, aEvent ); +} + + +void AccessibleContextBase::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ("object has been already disposed", + getXWeak()); + } +} + + +bool AccessibleContextBase::IsDisposed() const +{ + return (rBHelper.bDisposed || rBHelper.bInDispose); +} + + +void AccessibleContextBase::SetAccessibleRole( sal_Int16 _nRole ) +{ + maRole = _nRole; +} + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleEditableTextPara.cxx b/editeng/source/accessibility/AccessibleEditableTextPara.cxx new file mode 100644 index 0000000000..a4d0ca950b --- /dev/null +++ b/editeng/source/accessibility/AccessibleEditableTextPara.cxx @@ -0,0 +1,2675 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <algorithm> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> +#include <editeng/flditem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/i18n/Boundary.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <vcl/unohelp.hxx> +#include <vcl/settings.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <editeng/editeng.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoedprx.hxx> +#include <editeng/unoedsrc.hxx> +#include <svl/eitem.hxx> + + +// Project-local header + + +#include <com/sun/star/beans/PropertyState.hpp> + +#include <unopracc.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> +#include "AccessibleHyperlink.hxx" +#include "AccessibleImageBullet.hxx" + +#include <svtools/colorcfg.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <editeng/numitem.hxx> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::accessibility; + + +// AccessibleEditableTextPara implementation + + +namespace accessibility +{ + static const SvxItemPropertySet* ImplGetSvxCharAndParaPropertiesSet() + { + // PropertyMap for character and paragraph properties + static const SfxItemPropertyMapEntry aPropMap[] = + { + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + SVX_UNOEDIT_NUMBERING_PROPERTY, + { u"TextUserDefinedAttributes"_ustr, EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes"_ustr, EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + }; + static SvxItemPropertySet aPropSet( aPropMap, EditEngine::GetGlobalItemPool() ); + return &aPropSet; + } + + // #i27138# - add parameter <_pParaManager> + AccessibleEditableTextPara::AccessibleEditableTextPara( + uno::Reference< XAccessible > xParent, + const AccessibleParaManager* _pParaManager ) + : mnParagraphIndex( 0 ), + mnIndexInParent( 0 ), + mpEditSource( nullptr ), + maEEOffset( 0, 0 ), + mxParent(std::move( xParent )), + // well, that's strictly (UNO) exception safe, though not + // really robust. We rely on the fact that this member is + // constructed last, and that the constructor body catches + // exceptions, thus no chance for exceptions once the Id is + // fetched. Nevertheless, normally should employ RAII here... + mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()), + // #i27138# + mpParaManager( _pParaManager ) + { + + // Create the state set. + mnStateSet = 0; + + // these are always on + mnStateSet |= AccessibleStateType::MULTI_LINE; + mnStateSet |= AccessibleStateType::FOCUSABLE; + mnStateSet |= AccessibleStateType::VISIBLE; + mnStateSet |= AccessibleStateType::SHOWING; + mnStateSet |= AccessibleStateType::ENABLED; + mnStateSet |= AccessibleStateType::SENSITIVE; + } + + AccessibleEditableTextPara::~AccessibleEditableTextPara() + { + // sign off from event notifier + if( getNotifierClientId() != -1 ) + { + try + { + ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); + } + catch (const uno::Exception&) + { + } + } + } + + OUString AccessibleEditableTextPara::implGetText() + { + return GetTextRange( 0, GetTextLen() ); + } + + css::lang::Locale AccessibleEditableTextPara::implGetLocale() + { + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getLocale: paragraph index value overflow"); + + // return locale of first character in the paragraph + return LanguageTag(GetTextForwarder().GetLanguage( GetParagraphIndex(), 0 )).getLocale(); + } + + void AccessibleEditableTextPara::implGetSelection( sal_Int32& nStartIndex, sal_Int32& nEndIndex ) + { + sal_Int32 nStart, nEnd; + + if( GetSelection( nStart, nEnd ) ) + { + nStartIndex = nStart; + nEndIndex = nEnd; + } + else + { + // #102234# No exception, just set to 'invalid' + nStartIndex = -1; + nEndIndex = -1; + } + } + + void AccessibleEditableTextPara::implGetParagraphBoundary( const OUString& rText, css::i18n::Boundary& rBoundary, sal_Int32 /*nIndex*/ ) + { + SAL_INFO( "editeng", "AccessibleEditableTextPara::implGetParagraphBoundary: only a base implementation, ignoring the index" ); + + rBoundary.startPos = 0; + rBoundary.endPos = rText.getLength(); + } + + void AccessibleEditableTextPara::implGetLineBoundary( const OUString&, css::i18n::Boundary& rBoundary, sal_Int32 nIndex ) + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + const sal_Int32 nParaIndex = GetParagraphIndex(); + + DBG_ASSERT(nParaIndex >= 0, + "AccessibleEditableTextPara::implGetLineBoundary: paragraph index value overflow"); + + const sal_Int32 nTextLen = rCacheTF.GetTextLen( nParaIndex ); + + CheckPosition(nIndex); + + rBoundary.startPos = rBoundary.endPos = -1; + + const sal_Int32 nLineCount=rCacheTF.GetLineCount( nParaIndex ); + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + if( nLineCount <= 1 ) + rBoundary.startPos = 0; + else + rBoundary.startPos = nTextLen - rCacheTF.GetLineLen( nParaIndex, + nLineCount-1 ); + + rBoundary.endPos = nTextLen; + } + else + { + // normal line search + sal_Int32 nLine; + sal_Int32 nCurIndex; + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + nCurIndex += rCacheTF.GetLineLen( nParaIndex, nLine); + + if( nCurIndex > nIndex ) + { + rBoundary.startPos = nCurIndex - rCacheTF.GetLineLen( nParaIndex, nLine); + rBoundary.endPos = nCurIndex; + break; + } + } + } + } + + + void AccessibleEditableTextPara::SetIndexInParent( sal_Int32 nIndex ) + { + mnIndexInParent = nIndex; + } + + + void AccessibleEditableTextPara::SetParagraphIndex( sal_Int32 nIndex ) + { + sal_Int32 nOldIndex = mnParagraphIndex; + + mnParagraphIndex = nIndex; + + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetParagraphIndex(mnParagraphIndex); + + try + { + if( nOldIndex != nIndex ) + { + uno::Any aOldDesc; + uno::Any aOldName; + + try + { + aOldDesc <<= getAccessibleDescription(); + aOldName <<= getAccessibleName(); + } + catch (const uno::Exception&) // optional behaviour + { + } + // index and therefore description changed + FireEvent( AccessibleEventId::DESCRIPTION_CHANGED, uno::Any( getAccessibleDescription() ), aOldDesc ); + FireEvent( AccessibleEventId::NAME_CHANGED, uno::Any( getAccessibleName() ), aOldName ); + } + } + catch (const uno::Exception&) // optional behaviour + { + } + } + + + void AccessibleEditableTextPara::Dispose() + { + int nClientId( getNotifierClientId() ); + + // #108212# drop all references before notifying dispose + mxParent = nullptr; + mnNotifierClientId = -1; + mpEditSource = nullptr; + + // notify listeners + if( nClientId == -1 ) + return; + + try + { + uno::Reference < XAccessibleContext > xThis = getAccessibleContext(); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, xThis ); + } + catch (const uno::Exception&) + { + } + } + + void AccessibleEditableTextPara::SetEditSource( SvxEditSourceAdapter* pEditSource ) + { + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetEditSource(pEditSource); + + if( !pEditSource ) + { + // going defunc + UnSetState( AccessibleStateType::SHOWING ); + UnSetState( AccessibleStateType::VISIBLE ); + SetState( AccessibleStateType::INVALID ); + SetState( AccessibleStateType::DEFUNC ); + + Dispose(); + } + mpEditSource = pEditSource; + // #108900# Init last text content + try + { + TextChanged(); + } + catch (const uno::RuntimeException&) + { + } + } + + ESelection AccessibleEditableTextPara::MakeSelection( sal_Int32 nStartEEIndex, sal_Int32 nEndEEIndex ) + { + // check overflow + DBG_ASSERT(nStartEEIndex >= 0 && + nEndEEIndex >= 0 && + GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::MakeSelection: index value overflow"); + + sal_Int32 nParaIndex = GetParagraphIndex(); + return ESelection(nParaIndex, nStartEEIndex, nParaIndex, nEndEEIndex); + } + + ESelection AccessibleEditableTextPara::MakeSelection( sal_Int32 nEEIndex ) + { + return MakeSelection( nEEIndex, nEEIndex+1 ); + } + + ESelection AccessibleEditableTextPara::MakeCursor( sal_Int32 nEEIndex ) + { + return MakeSelection( nEEIndex, nEEIndex ); + } + + void AccessibleEditableTextPara::CheckIndex( sal_Int32 nIndex ) + { + if( nIndex < 0 || nIndex >= getCharacterCount() ) + throw lang::IndexOutOfBoundsException("AccessibleEditableTextPara: character index out of bounds", + getXWeak() ); + } + + void AccessibleEditableTextPara::CheckPosition( sal_Int32 nIndex ) + { + if( nIndex < 0 || nIndex > getCharacterCount() ) + throw lang::IndexOutOfBoundsException("AccessibleEditableTextPara: character position out of bounds", + getXWeak() ); + } + + void AccessibleEditableTextPara::CheckRange( sal_Int32 nStart, sal_Int32 nEnd ) + { + CheckPosition( nStart ); + CheckPosition( nEnd ); + } + + bool AccessibleEditableTextPara::GetSelection(sal_Int32 &nStartPos, sal_Int32 &nEndPos) + { + ESelection aSelection; + sal_Int32 nPara = GetParagraphIndex(); + if( !GetEditViewForwarder().GetSelection( aSelection ) ) + return false; + + if( aSelection.nStartPara < aSelection.nEndPara ) + { + if( aSelection.nStartPara > nPara || + aSelection.nEndPara < nPara ) + return false; + + if( nPara == aSelection.nStartPara ) + nStartPos = aSelection.nStartPos; + else + nStartPos = 0; + + if( nPara == aSelection.nEndPara ) + nEndPos = aSelection.nEndPos; + else + nEndPos = GetTextLen(); + } + else + { + if( aSelection.nStartPara < nPara || + aSelection.nEndPara > nPara ) + return false; + + if( nPara == aSelection.nStartPara ) + nStartPos = aSelection.nStartPos; + else + nStartPos = GetTextLen(); + + if( nPara == aSelection.nEndPara ) + nEndPos = aSelection.nEndPos; + else + nEndPos = 0; + } + + return true; + } + + OUString AccessibleEditableTextPara::GetTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + return GetTextForwarder().GetText( MakeSelection(nStartIndex, nEndIndex) ); + } + + sal_Int32 AccessibleEditableTextPara::GetTextLen() const + { + return GetTextForwarder().GetTextLen(GetParagraphIndex()); + } + + SvxEditSourceAdapter& AccessibleEditableTextPara::GetEditSource() const + { + if( !mpEditSource ) + throw uno::RuntimeException("No edit source, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + return *mpEditSource; + } + + SvxAccessibleTextAdapter& AccessibleEditableTextPara::GetTextForwarder() const + { + SvxEditSourceAdapter& rEditSource = GetEditSource(); + SvxAccessibleTextAdapter* pTextForwarder = rEditSource.GetTextForwarderAdapter(); + + if( !pTextForwarder ) + throw uno::RuntimeException("Unable to fetch text forwarder, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + + if( !pTextForwarder->IsValid() ) + throw uno::RuntimeException("Text forwarder is invalid, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + return *pTextForwarder; + } + + SvxViewForwarder& AccessibleEditableTextPara::GetViewForwarder() const + { + SvxEditSource& rEditSource = GetEditSource(); + SvxViewForwarder* pViewForwarder = rEditSource.GetViewForwarder(); + + if( !pViewForwarder ) + { + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + } + + if( !pViewForwarder->IsValid() ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + return *pViewForwarder; + } + + SvxAccessibleTextEditViewAdapter& AccessibleEditableTextPara::GetEditViewForwarder( bool bCreate ) const + { + SvxEditSourceAdapter& rEditSource = GetEditSource(); + SvxAccessibleTextEditViewAdapter* pTextEditViewForwarder = rEditSource.GetEditViewForwarderAdapter( bCreate ); + + if( !pTextEditViewForwarder ) + { + if( bCreate ) + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + else + throw uno::RuntimeException("No view forwarder, object not in edit mode", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + } + + if( pTextEditViewForwarder->IsValid() ) + return *pTextEditViewForwarder; + else + { + if( bCreate ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + else + throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", + const_cast< AccessibleEditableTextPara* > (this)->getXWeak() ); + } + } + + bool AccessibleEditableTextPara::HaveEditView() const + { + SvxEditSource& rEditSource = GetEditSource(); + SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder(); + + if( !pViewForwarder ) + return false; + + if( !pViewForwarder->IsValid() ) + return false; + + return true; + } + + bool AccessibleEditableTextPara::HaveChildren() + { + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::HaveChildren: paragraph index value overflow"); + + return GetTextForwarder().HaveImageBullet( GetParagraphIndex() ); + } + + tools::Rectangle AccessibleEditableTextPara::LogicToPixel( const tools::Rectangle& rRect, const MapMode& rMapMode, SvxViewForwarder const & rForwarder ) + { + // convert to screen coordinates + return tools::Rectangle( rForwarder.LogicToPixel( rRect.TopLeft(), rMapMode ), + rForwarder.LogicToPixel( rRect.BottomRight(), rMapMode ) ); + } + + + void AccessibleEditableTextPara::SetEEOffset( const Point& rOffset ) + { + auto aChild( maImageBullet.get() ); + if( aChild.is() ) + aChild->SetEEOffset(rOffset); + + maEEOffset = rOffset; + } + + void AccessibleEditableTextPara::FireEvent(const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue) const + { + uno::Reference < XAccessibleContext > xThis( const_cast< AccessibleEditableTextPara* > (this)->getAccessibleContext() ); + + AccessibleEventObject aEvent(xThis, nEventId, rNewValue, rOldValue, -1); + + // #106234# Delegate to EventNotifier + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), + aEvent ); + } + + void AccessibleEditableTextPara::SetState( const sal_Int64 nStateId ) + { + if( !(mnStateSet & nStateId) ) + { + mnStateSet |= nStateId; + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any( nStateId ) ); + } + } + + void AccessibleEditableTextPara::UnSetState( const sal_Int64 nStateId ) + { + if( mnStateSet & nStateId ) + { + mnStateSet &= ~nStateId; + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any( nStateId ) ); + } + } + + void AccessibleEditableTextPara::TextChanged() + { + OUString aCurrentString( implGetText() ); + uno::Any aDeleted; + uno::Any aInserted; + if( OCommonAccessibleText::implInitTextChangedEvent( maLastTextString, aCurrentString, + aDeleted, aInserted) ) + { + FireEvent( AccessibleEventId::TEXT_CHANGED, aInserted, aDeleted ); + maLastTextString = aCurrentString; + } + } + + bool AccessibleEditableTextPara::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nIndex ) + { + DBG_ASSERT(nIndex >= 0, + "AccessibleEditableTextPara::GetAttributeRun: index value overflow"); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getLocale: paragraph index value overflow"); + + return GetTextForwarder().GetAttributeRun( nStartIndex, + nEndIndex, + GetParagraphIndex(), + nIndex ); + } + + uno::Any SAL_CALL AccessibleEditableTextPara::queryInterface (const uno::Type & rType) + { + uno::Any aRet; + + // must provide XAccessibleText by hand, since it comes publicly inherited by XAccessibleEditableText + if ( rType == cppu::UnoType<XAccessibleText>::get()) + { + uno::Reference< XAccessibleText > aAccText = static_cast< XAccessibleEditableText * >(this); + aRet <<= aAccText; + } + else if ( rType == cppu::UnoType<XAccessibleEditableText>::get()) + { + uno::Reference< XAccessibleEditableText > aAccEditText = this; + aRet <<= aAccEditText; + } + else if ( rType == cppu::UnoType<XAccessibleHypertext>::get()) + { + uno::Reference< XAccessibleHypertext > aAccHyperText = this; + aRet <<= aAccHyperText; + } + else + { + aRet = AccessibleTextParaInterfaceBase::queryInterface(rType); + } + + return aRet; + } + + // XAccessible + uno::Reference< XAccessibleContext > SAL_CALL AccessibleEditableTextPara::getAccessibleContext() + { + // We implement the XAccessibleContext interface in the same object + return uno::Reference< XAccessibleContext > ( this ); + } + + // XAccessibleContext + sal_Int64 SAL_CALL AccessibleEditableTextPara::getAccessibleChildCount() + { + SolarMutexGuard aGuard; + + return HaveChildren() ? 1 : 0; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleChild( sal_Int64 i ) + { + SolarMutexGuard aGuard; + + if( !HaveChildren() ) + throw lang::IndexOutOfBoundsException("No children available", + getXWeak() ); + + if( i != 0 ) + throw lang::IndexOutOfBoundsException("Invalid child index", + getXWeak() ); + + auto aChild( maImageBullet.get() ); + + if( !aChild.is() ) + { + // there is no hard reference available, create object then + aChild = new AccessibleImageBullet(this); + + aChild->SetEditSource( &GetEditSource() ); + aChild->SetParagraphIndex( GetParagraphIndex() ); + aChild->SetIndexInParent( i ); + + maImageBullet = aChild; + } + + return aChild; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleParent() + { + SAL_WARN_IF(!mxParent.is(), "editeng", "AccessibleEditableTextPara::getAccessibleParent: no frontend set, did somebody forgot to call AccessibleTextHelper::SetEventSource()?"); + + return mxParent; + } + + sal_Int64 SAL_CALL AccessibleEditableTextPara::getAccessibleIndexInParent() + { + return mnIndexInParent; + } + + sal_Int16 SAL_CALL AccessibleEditableTextPara::getAccessibleRole() + { + return AccessibleRole::PARAGRAPH; + } + + OUString SAL_CALL AccessibleEditableTextPara::getAccessibleDescription() + { + SolarMutexGuard aGuard; + + // append first 40 characters from text, or first line, if shorter + // (writer takes first sentence here, but that's not supported + // from EditEngine) + // throws if defunc + OUString aLine; + + if( getCharacterCount() ) + aLine = getTextAtIndex(0, AccessibleTextType::LINE).SegmentText; + + // Get the string from the resource for the specified id. + OUString sStr(EditResId(RID_SVXSTR_A11Y_PARAGRAPH_DESCRIPTION)); + OUString sParaIndex = OUString::number(GetParagraphIndex()); + sStr = sStr.replaceFirst("$(ARG)", sParaIndex); + + if( aLine.getLength() > MaxDescriptionLen ) + { + OUString aCurrWord; + sal_Int32 i; + + // search backward from MaxDescriptionLen for previous word start + for( aCurrWord=getTextAtIndex(MaxDescriptionLen, AccessibleTextType::WORD).SegmentText, + i=MaxDescriptionLen, + aLine=OUString(); + i>=0; + --i ) + { + if( getTextAtIndex(i, AccessibleTextType::WORD).SegmentText != aCurrWord ) + { + if( i == 0 ) + // prevent completely empty string + aLine = getTextAtIndex(0, AccessibleTextType::WORD).SegmentText; + else + aLine = getTextRange(0, i); + } + } + } + + return sStr + aLine; + } + + OUString SAL_CALL AccessibleEditableTextPara::getAccessibleName() + { + //See tdf#101003 before implementing a body + return OUString(); + } + + uno::Reference< XAccessibleRelationSet > SAL_CALL AccessibleEditableTextPara::getAccessibleRelationSet() + { + // #i27138# - provide relations CONTENT_FLOWS_FROM + // and CONTENT_FLOWS_TO + if ( mpParaManager ) + { + rtl::Reference<utl::AccessibleRelationSetHelper> pAccRelSetHelper = + new utl::AccessibleRelationSetHelper(); + sal_Int32 nMyParaIndex( GetParagraphIndex() ); + // relation CONTENT_FLOWS_FROM + if ( nMyParaIndex > 0 && + mpParaManager->IsReferencable( nMyParaIndex - 1 ) ) + { + uno::Sequence<uno::Reference<XInterface> > aSequence + { cppu::getXWeak(mpParaManager->GetChild( nMyParaIndex - 1 ).first.get().get()) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_FROM, + aSequence ); + pAccRelSetHelper->AddRelation( aAccRel ); + } + + // relation CONTENT_FLOWS_TO + if ( (nMyParaIndex + 1) < mpParaManager->GetNum() && + mpParaManager->IsReferencable( nMyParaIndex + 1 ) ) + { + uno::Sequence<uno::Reference<XInterface> > aSequence + { cppu::getXWeak(mpParaManager->GetChild( nMyParaIndex + 1 ).first.get().get()) }; + AccessibleRelation aAccRel( AccessibleRelationType::CONTENT_FLOWS_TO, + aSequence ); + pAccRelSetHelper->AddRelation( aAccRel ); + } + + return pAccRelSetHelper; + } + else + { + // no relations, therefore empty + return uno::Reference< XAccessibleRelationSet >(); + } + } + + static uno::Sequence< OUString > const & getAttributeNames() + { + static const uno::Sequence<OUString> aNames{ + "CharColor", + "CharContoured", + "CharEmphasis", + "CharEscapement", + "CharFontName", + "CharHeight", + "CharPosture", + "CharShadowed", + "CharStrikeout", + "CharCaseMap", + "CharUnderline", + "CharUnderlineColor", + "CharWeight", + "NumberingLevel", + "NumberingRules", + "ParaAdjust", + "ParaBottomMargin", + "ParaFirstLineIndent", + "ParaLeftMargin", + "ParaLineSpacing", + "ParaRightMargin", + "ParaTabStops"}; + + return aNames; + } + + namespace { + + struct IndexCompare + { + const PropertyValue* pValues; + explicit IndexCompare( const PropertyValue* pVals ) : pValues(pVals) {} + bool operator() ( sal_Int32 a, sal_Int32 b ) const + { + return pValues[a].Name < pValues[b].Name; + } + }; + + } +} + +namespace +{ + OUString GetFieldTypeNameFromField(EFieldInfo const &ree) + { + OUString strFldType; + sal_Int32 nFieldType = -1; + if (ree.pFieldItem) + { + // So we get a field, check its type now. + nFieldType = ree.pFieldItem->GetField()->GetClassId() ; + } + switch (nFieldType) + { + case text::textfield::Type::DATE: + { + const SvxDateField* pDateField = static_cast< const SvxDateField* >(ree.pFieldItem->GetField()); + if (pDateField) + { + if (pDateField->GetType() == SvxDateType::Fix) + strFldType = "date (fixed)"; + else if (pDateField->GetType() == SvxDateType::Var) + strFldType = "date (variable)"; + } + break; + } + case text::textfield::Type::PAGE: + strFldType = "page-number"; + break; + //support the sheet name & pages fields + case text::textfield::Type::PAGES: + strFldType = "page-count"; + break; + case text::textfield::Type::TABLE: + strFldType = "sheet-name"; + break; + //End + case text::textfield::Type::TIME: + strFldType = "time"; + break; + case text::textfield::Type::EXTENDED_TIME: + { + const SvxExtTimeField* pTimeField = static_cast< const SvxExtTimeField* >(ree.pFieldItem->GetField()); + if (pTimeField) + { + if (pTimeField->GetType() == SvxTimeType::Fix) + strFldType = "time (fixed)"; + else if (pTimeField->GetType() == SvxTimeType::Var) + strFldType = "time (variable)"; + } + break; + } + case text::textfield::Type::AUTHOR: + strFldType = "author"; + break; + case text::textfield::Type::EXTENDED_FILE: + case text::textfield::Type::DOCINFO_TITLE: + strFldType = "file name"; + break; + case text::textfield::Type::DOCINFO_CUSTOM: + strFldType = "custom document property"; + break; + default: + break; + } + return strFldType; + } +} + +namespace accessibility +{ + OUString AccessibleEditableTextPara::GetFieldTypeNameAtIndex(sal_Int32 nIndex) + { + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + //For field object info + sal_Int32 nParaIndex = GetParagraphIndex(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex); + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + sal_Int32 reeBegin = ree.aPosition.nIndex + nAllFieldLen; + sal_Int32 reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if (nIndex < reeBegin) + break; + if (nIndex < reeEnd) + return GetFieldTypeNameFromField(ree); + } + return OUString(); + } + + sal_Int64 SAL_CALL AccessibleEditableTextPara::getAccessibleStateSet() + { + SolarMutexGuard aGuard; + + // Create a copy of the state set and return it. + + sal_Int64 nParentStates = 0; + if (getAccessibleParent().is()) + { + uno::Reference<XAccessibleContext> xParentContext = getAccessibleParent()->getAccessibleContext(); + nParentStates = xParentContext->getAccessibleStateSet(); + } + if (nParentStates & AccessibleStateType::EDITABLE) + { + mnStateSet |= AccessibleStateType::EDITABLE; + } + return mnStateSet; + } + + lang::Locale SAL_CALL AccessibleEditableTextPara::getLocale() + { + SolarMutexGuard aGuard; + + return implGetLocale(); + } + + void SAL_CALL AccessibleEditableTextPara::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); + } + + void SAL_CALL AccessibleEditableTextPara::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + if( getNotifierClientId() == -1 ) + return; + + const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() ); + mnNotifierClientId = -1; + ::comphelper::AccessibleEventNotifier::revokeClient( nId ); + } + } + + // XAccessibleComponent + sal_Bool SAL_CALL AccessibleEditableTextPara::containsPoint( const awt::Point& aTmpPoint ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::contains: index value overflow"); + + awt::Rectangle aTmpRect = getBounds(); + tools::Rectangle aRect( Point(aTmpRect.X, aTmpRect.Y), Size(aTmpRect.Width, aTmpRect.Height) ); + Point aPoint( aTmpPoint.X, aTmpPoint.Y ); + + return aRect.Contains( aPoint ); + } + + uno::Reference< XAccessible > SAL_CALL AccessibleEditableTextPara::getAccessibleAtPoint( const awt::Point& _aPoint ) + { + SolarMutexGuard aGuard; + + if( HaveChildren() ) + { + // #103862# No longer need to make given position relative + Point aPoint( _aPoint.X, _aPoint.Y ); + + // respect EditEngine offset to surrounding shape/cell + aPoint -= GetEEOffset(); + + // convert to EditEngine coordinate system + SvxTextForwarder& rCacheTF = GetTextForwarder(); + Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); + + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ) + { + tools::Rectangle aRect = aBulletInfo.aBounds; + + if( aRect.Contains( aLogPoint ) ) + return getAccessibleChild(0); + } + } + + // no children at all, or none at given position + return uno::Reference< XAccessible >(); + } + + awt::Rectangle SAL_CALL AccessibleEditableTextPara::getBounds() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getBounds: index value overflow"); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + tools::Rectangle aRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + + // convert to screen coordinates + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + + // offset from shape/cell + Point aOffset = GetEEOffset(); + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + awt::Point SAL_CALL AccessibleEditableTextPara::getLocation( ) + { + SolarMutexGuard aGuard; + + awt::Rectangle aRect = getBounds(); + + return awt::Point( aRect.X, aRect.Y ); + } + + awt::Point SAL_CALL AccessibleEditableTextPara::getLocationOnScreen( ) + { + SolarMutexGuard aGuard; + + // relate us to parent + uno::Reference< XAccessible > xParent = getAccessibleParent(); + if( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent, uno::UNO_QUERY ); + if( xParentComponent.is() ) + { + awt::Point aRefPoint = xParentComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + // #i88070# + // fallback to parent's <XAccessibleContext> instance + else + { + uno::Reference< XAccessibleContext > xParentContext = xParent->getAccessibleContext(); + if ( xParentContext.is() ) + { + uno::Reference< XAccessibleComponent > xParentContextComponent( xParentContext, uno::UNO_QUERY ); + if( xParentContextComponent.is() ) + { + awt::Point aRefPoint = xParentContextComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + } + } + } + + throw uno::RuntimeException("Cannot access parent", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + awt::Size SAL_CALL AccessibleEditableTextPara::getSize( ) + { + SolarMutexGuard aGuard; + + awt::Rectangle aRect = getBounds(); + + return awt::Size( aRect.Width, aRect.Height ); + } + + void SAL_CALL AccessibleEditableTextPara::grabFocus( ) + { + // set cursor to this paragraph + setSelection(0,0); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getForeground( ) + { + // #104444# Added to XAccessibleComponent interface + svtools::ColorConfig aColorConfig; + Color nColor = aColorConfig.GetColorValue( svtools::FONTCOLOR ).nColor; + return static_cast<sal_Int32>(nColor); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getBackground( ) + { + // #104444# Added to XAccessibleComponent interface + Color aColor( Application::GetSettings().GetStyleSettings().GetWindowColor() ); + + // the background is transparent + aColor.SetAlpha(0); + + return static_cast<sal_Int32>( aColor ); + } + + // XAccessibleText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getCaretPosition() + { + SolarMutexGuard aGuard; + + if( !HaveEditView() ) + return -1; + + ESelection aSelection; + if( GetEditViewForwarder().GetSelection( aSelection ) && + GetParagraphIndex() == aSelection.nEndPara ) + { + // caret is always nEndPara,nEndPos + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + if( aSelection.nEndPos - nBulletLen >= 0 ) + return aSelection.nEndPos - nBulletLen; + } + return aSelection.nEndPos; + } + + // not within this paragraph + return -1; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setCaretPosition( sal_Int32 nIndex ) + { + return setSelection(nIndex, nIndex); + } + + sal_Unicode SAL_CALL AccessibleEditableTextPara::getCharacter( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacter: index value overflow"); + + return OCommonAccessibleText::implGetCharacter( implGetText(), nIndex ); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& rRequestedAttributes ) + { + SolarMutexGuard aGuard; + + //Skip the bullet range to ignore the bullet text + SvxTextForwarder& rCacheTF = GetTextForwarder(); + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + if (aBulletInfo.bVisible) + nIndex += aBulletInfo.aText.getLength(); + CheckIndex(nIndex); // may throw IndexOutOfBoundsException + + bool bSupplementalMode = false; + uno::Sequence< OUString > aPropertyNames = rRequestedAttributes; + if (!aPropertyNames.hasElements()) + { + bSupplementalMode = true; + aPropertyNames = getAttributeNames(); + } + + // get default attributes... + ::comphelper::SequenceAsHashMap aPropHashMap( getDefaultAttributes( aPropertyNames ) ); + + // ... and override them with the direct attributes from the specific position + const uno::Sequence< beans::PropertyValue > aRunAttribs( getRunAttributes( nIndex, aPropertyNames ) ); + for (auto const& rRunAttrib : aRunAttribs) + { + aPropHashMap[ rRunAttrib.Name ] = rRunAttrib.Value; //!! should not only be the value !! + } + + // get resulting sequence + uno::Sequence< beans::PropertyValue > aRes; + aPropHashMap >> aRes; + + // since SequenceAsHashMap ignores property handles and property state + // we have to restore the property state here (property handles are + // of no use to the accessibility API). + for (beans::PropertyValue & rRes : asNonConstRange(aRes)) + { + bool bIsDirectVal = false; + for (auto const& rRunAttrib : aRunAttribs) + { + bIsDirectVal = rRes.Name == rRunAttrib.Name; + if (bIsDirectVal) + break; + } + rRes.Handle = -1; + rRes.State = bIsDirectVal ? PropertyState_DIRECT_VALUE : PropertyState_DEFAULT_VALUE; + } + if( bSupplementalMode ) + { + _correctValues( aRes ); + // NumberingPrefix + sal_Int32 nRes = aRes.getLength(); + aRes.realloc( nRes + 1 ); + beans::PropertyValue &rRes = aRes.getArray()[nRes]; + rRes.Name = "NumberingPrefix"; + OUString numStr; + if (aBulletInfo.nType != SVX_NUM_CHAR_SPECIAL && aBulletInfo.nType != SVX_NUM_BITMAP) + numStr = aBulletInfo.aText; + rRes.Value <<= numStr; + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + //For field object. + OUString strFieldType = GetFieldTypeNameAtIndex(nIndex); + if (!strFieldType.isEmpty()) + { + nRes = aRes.getLength(); + aRes.realloc( nRes + 1 ); + beans::PropertyValue &rResField = aRes.getArray()[nRes]; + rResField.Name = "FieldType"; + rResField.Value <<= strFieldType.toAsciiLowerCase(); + rResField.Handle = -1; + rResField.State = PropertyState_DIRECT_VALUE; + } + //sort property values + // build sorted index array + sal_Int32 nLength = aRes.getLength(); + const beans::PropertyValue* pPairs = aRes.getConstArray(); + std::unique_ptr<sal_Int32[]> pIndices(new sal_Int32[nLength]); + sal_Int32 i = 0; + for( i = 0; i < nLength; i++ ) + pIndices[i] = i; + std::sort( &pIndices[0], &pIndices[nLength], IndexCompare(pPairs) ); + // create sorted sequences according to index array + uno::Sequence<beans::PropertyValue> aNewValues( nLength ); + beans::PropertyValue* pNewValues = aNewValues.getArray(); + for( i = 0; i < nLength; i++ ) + { + pNewValues[i] = pPairs[pIndices[i]]; + } + + return aNewValues; + } + return aRes; + } + + awt::Rectangle SAL_CALL AccessibleEditableTextPara::getCharacterBounds( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterBounds: index value overflow"); + + // #108900# Have position semantics now for nIndex, as + // one-past-the-end values are legal, too. + CheckPosition( nIndex ); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + tools::Rectangle aRect = rCacheTF.GetCharBounds(GetParagraphIndex(), nIndex); + + // convert to screen + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + // #109864# offset from parent (paragraph), but in screen + // coordinates. This makes sure the internal text offset in + // the outline view forwarder gets cancelled out here + awt::Rectangle aParaRect( getBounds() ); + aScreenRect.Move( -aParaRect.X, -aParaRect.Y ); + + // offset from shape/cell + Point aOffset = GetEEOffset(); + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getCharacterCount() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterCount: index value overflow"); + + return implGetText().getLength(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getIndexAtPoint( const awt::Point& rPoint ) + { + SolarMutexGuard aGuard; + + sal_Int32 nPara; + sal_Int32 nIndex; + + // offset from surrounding cell/shape + Point aOffset( GetEEOffset() ); + Point aPoint( rPoint.X - aOffset.X(), rPoint.Y - aOffset.Y() ); + + // convert to logical coordinates + SvxTextForwarder& rCacheTF = GetTextForwarder(); + Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); + + // re-offset to parent (paragraph) + tools::Rectangle aParaRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + aLogPoint.Move( aParaRect.Left(), aParaRect.Top() ); + + if( rCacheTF.GetIndexAtPoint( aLogPoint, nPara, nIndex ) && + GetParagraphIndex() == nPara ) + { + // #102259# Double-check if we're _really_ on the given character + try + { + awt::Rectangle aRect1( getCharacterBounds(nIndex) ); + tools::Rectangle aRect2( aRect1.X, aRect1.Y, + aRect1.Width + aRect1.X, aRect1.Height + aRect1.Y ); + if( aRect2.Contains( Point( rPoint.X, rPoint.Y ) ) ) + return nIndex; + else + return -1; + } + catch (const lang::IndexOutOfBoundsException&) + { + // #103927# Don't throw for invalid nIndex values + return -1; + } + } + else + { + // not within our paragraph + return -1; + } + } + + OUString SAL_CALL AccessibleEditableTextPara::getSelectedText() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectedText: index value overflow"); + + if( !HaveEditView() ) + return OUString(); + + return OCommonAccessibleText::getSelectedText(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getSelectionStart() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectionStart: index value overflow"); + + if( !HaveEditView() ) + return -1; + + return OCommonAccessibleText::getSelectionStart(); + } + + sal_Int32 SAL_CALL AccessibleEditableTextPara::getSelectionEnd() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getSelectionEnd: index value overflow"); + + if( !HaveEditView() ) + return -1; + + return OCommonAccessibleText::getSelectionEnd(); + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::setSelection: paragraph index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + return rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + OUString SAL_CALL AccessibleEditableTextPara::getText() + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getText: paragraph index value overflow"); + + return implGetText(); + } + + OUString SAL_CALL AccessibleEditableTextPara::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextRange: paragraph index value overflow"); + + return OCommonAccessibleText::implGetTextRange(implGetText(), nStartIndex, nEndIndex); + } + + void AccessibleEditableTextPara::_correctValues( uno::Sequence< PropertyValue >& rValues) + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nRes = rValues.getLength(); + beans::PropertyValue *pRes = rValues.getArray(); + for (sal_Int32 i = 0; i < nRes; ++i) + { + beans::PropertyValue &rRes = pRes[i]; + // Char color + if (rRes.Name == "CharColor") + { + uno::Any &anyChar = rRes.Value; + Color crChar(ColorTransparency, static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>(anyChar.pReserved))); + if (COL_AUTO == crChar ) + { + uno::Reference< css::accessibility::XAccessibleComponent > xComponent(mxParent,uno::UNO_QUERY); + if (xComponent.is()) + { + uno::Reference< css::accessibility::XAccessibleContext > xContext(xComponent,uno::UNO_QUERY); + if (xContext->getAccessibleRole() == AccessibleRole::SHAPE + || xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + anyChar <<= COL_BLACK; + } + else + { + Color cr(ColorTransparency, xComponent->getBackground()); + crChar = cr.IsDark() ? COL_WHITE : COL_BLACK; + anyChar <<= crChar; + } + } + } + continue; + } + // Underline + if (rRes.Name == "CharUnderline") + { + continue; + } + // Underline color && Mis-spell + if (rRes.Name == "CharUnderlineColor") + { + uno::Any &anyCharUnderLine = rRes.Value; + Color crCharUnderLine(ColorTransparency, static_cast<sal_uInt32>( reinterpret_cast<sal_uIntPtr>( anyCharUnderLine.pReserved))); + if (COL_AUTO == crCharUnderLine ) + { + uno::Reference< css::accessibility::XAccessibleComponent > xComponent(mxParent,uno::UNO_QUERY); + if (xComponent.is()) + { + uno::Reference< css::accessibility::XAccessibleContext > xContext(xComponent,uno::UNO_QUERY); + if (xContext->getAccessibleRole() == AccessibleRole::SHAPE + || xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL) + { + anyCharUnderLine <<= COL_BLACK; + } + else + { + Color cr(ColorTransparency, xComponent->getBackground()); + crCharUnderLine = cr.IsDark() ? COL_WHITE : COL_BLACK; + anyCharUnderLine <<= crCharUnderLine; + } + } + } + continue; + } + // NumberingLevel + if (rRes.Name == "NumberingLevel") + { + if(rCacheTF.GetParaAttribs(GetParagraphIndex()).Get(EE_PARA_NUMBULLET).GetNumRule().GetLevelCount()==0) + { + rRes.Value <<= sal_Int16(-1); + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + } + else + { +// SvxAccessibleTextPropertySet aPropSet( &GetEditSource(), +// ImplGetSvxCharAndParaPropertiesMap() ); + // MT IA2 TODO: Check if this is the correct replacement for ImplGetSvxCharAndParaPropertiesMap + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), ImplGetSvxTextPortionSvxPropertySet() ) ); + + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + rRes.Value = xPropSet->_getPropertyValue( rRes.Name, mnParagraphIndex ); + rRes.State = xPropSet->_getPropertyState( rRes.Name, mnParagraphIndex ); + rRes.Handle = -1; + } + continue; + } + // NumberingRules + if (rRes.Name == "NumberingRules") + { + SfxItemSet aAttribs = rCacheTF.GetParaAttribs(GetParagraphIndex()); + bool bVis = aAttribs.Get( EE_PARA_BULLETSTATE ).GetValue(); + if(bVis) + { + rRes.Value <<= sal_Int16(-1); + rRes.Handle = -1; + rRes.State = PropertyState_DIRECT_VALUE; + } + else + { + // MT IA2 TODO: Check if this is the correct replacement for ImplGetSvxCharAndParaPropertiesMap + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), ImplGetSvxTextPortionSvxPropertySet() ) ); + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + rRes.Value = xPropSet->_getPropertyValue( rRes.Name, mnParagraphIndex ); + rRes.State = xPropSet->_getPropertyState( rRes.Name, mnParagraphIndex ); + rRes.Handle = -1; + } + continue; + } + } + } + sal_Int32 AccessibleEditableTextPara::SkipField(sal_Int32 nIndex, bool bForward) + { + sal_Int32 nParaIndex = GetParagraphIndex(); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex), nFoundFieldIndex = -1; + sal_Int32 reeBegin=0, reeEnd=0; + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + reeBegin = ree.aPosition.nIndex + nAllFieldLen; + reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if (nIndex < reeBegin) + break; + if (!ree.pFieldItem) + continue; + if (nIndex < reeEnd) + { + if (ree.pFieldItem->GetField()->GetClassId() != text::textfield::Type::URL) + { + nFoundFieldIndex = j; + break; + } + } + } + if( nFoundFieldIndex >= 0 ) + { + if( bForward ) + return reeEnd - 1; + else + return reeBegin; + } + return nIndex; + } + void AccessibleEditableTextPara::ExtendByField( css::accessibility::TextSegment& Segment ) + { + sal_Int32 nParaIndex = GetParagraphIndex(); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); + sal_Int32 nAllFieldLen = 0; + sal_Int32 nField = rCacheTF.GetFieldCount(nParaIndex), nFoundFieldIndex = -1; + sal_Int32 reeBegin=0, reeEnd=0; + for (sal_Int32 j = 0; j < nField; ++j) + { + EFieldInfo ree = rCacheTF.GetFieldInfo(nParaIndex, j); + reeBegin = ree.aPosition.nIndex + nAllFieldLen; + reeEnd = reeBegin + ree.aCurrentText.getLength(); + nAllFieldLen += (ree.aCurrentText.getLength() - 1); + if( reeBegin > Segment.SegmentEnd ) + { + break; + } + if (!ree.pFieldItem) + continue; + if( (Segment.SegmentEnd > reeBegin && Segment.SegmentEnd <= reeEnd) || + (Segment.SegmentStart >= reeBegin && Segment.SegmentStart < reeEnd) ) + { + if(ree.pFieldItem->GetField()->GetClassId() != text::textfield::Type::URL) + { + nFoundFieldIndex = j; + break; + } + } + } + if( nFoundFieldIndex < 0 ) + return; + + bool bExtend = false; + if( Segment.SegmentEnd < reeEnd ) + { + Segment.SegmentEnd = reeEnd; + bExtend = true; + } + if( Segment.SegmentStart > reeBegin ) + { + Segment.SegmentStart = reeBegin; + bExtend = true; + } + if( !bExtend ) + return; + + //If there is a bullet before the field, should add the bullet length into the segment. + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + if (nBulletLen > 0) + { + Segment.SegmentEnd += nBulletLen; + if (nFoundFieldIndex > 0) + Segment.SegmentStart += nBulletLen; + Segment.SegmentText = GetTextRange(Segment.SegmentStart, Segment.SegmentEnd); + //After get the correct field name, should restore the offset value which don't contain the bullet. + Segment.SegmentEnd -= nBulletLen; + if (nFoundFieldIndex > 0) + Segment.SegmentStart -= nBulletLen; + } + else + Segment.SegmentText = GetTextRange(Segment.SegmentStart, Segment.SegmentEnd); + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextAtIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + + switch( aTextType ) + { + case AccessibleTextType::CHARACTER: + case AccessibleTextType::WORD: + { + aResult = OCommonAccessibleText::getTextAtIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + // Not yet handled by OCommonAccessibleText. Missing + // implGetAttributeRunBoundary() method there + case AccessibleTextType::ATTRIBUTE_RUN: + { + const sal_Int32 nTextLen = GetTextForwarder().GetTextLen( GetParagraphIndex() ); + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + aResult.SegmentStart = aResult.SegmentEnd = nTextLen; + } + else + { + sal_Int32 nStartIndex, nEndIndex; + //For the bullet paragraph, the bullet string is ignored for IAText::attributes() function. + SvxTextForwarder& rCacheTF = GetTextForwarder(); + // MT IA2: Not used? sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(GetParagraphIndex()); + if (aBulletInfo.bVisible) + nIndex += aBulletInfo.aText.getLength(); + if (nIndex != 0 && nIndex >= getCharacterCount()) + nIndex = getCharacterCount()-1; + CheckPosition(nIndex); + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + if (aBulletInfo.bVisible) + { + nStartIndex -= aBulletInfo.aText.getLength(); + nEndIndex -= aBulletInfo.aText.getLength(); + } + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + break; + } + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + CheckPosition(nIndex); + if (nIndex != 0 && nIndex == getCharacterCount()) + --nIndex; + sal_Int32 nLine, nLineCount=rCacheTF.GetLineCount( nParaIndex ); + sal_Int32 nCurIndex; + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nBulletLen = 0; + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo( nParaIndex ); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + sal_Int32 nLineLen = rCacheTF.GetLineLen(nParaIndex, nLine); + if (nLine == 0) + nCurIndex += nLineLen - nBulletLen; + else + nCurIndex += nLineLen; + if( nCurIndex > nIndex ) + { + if (nLine ==0) + { + aResult.SegmentStart = 0; + aResult.SegmentEnd = nCurIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd + nBulletLen); + break; + } + else + { + aResult.SegmentStart = nCurIndex - nLineLen; + aResult.SegmentEnd = nCurIndex; + //aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd ); + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + } + break; + } + default: + aResult = OCommonAccessibleText::getTextAtIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextBeforeIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + i18n::Boundary aBoundary; + switch( aTextType ) + { + // Not yet handled by OCommonAccessibleText. Missing + // implGetAttributeRunBoundary() method there + case AccessibleTextType::ATTRIBUTE_RUN: + { + const sal_Int32 nTextLen = GetTextForwarder().GetTextLen( GetParagraphIndex() ); + sal_Int32 nStartIndex, nEndIndex; + + if( nIndex == nTextLen ) + { + // #i17014# Special-casing one-behind-the-end character + if( nIndex > 0 && + GetAttributeRun(nStartIndex, nEndIndex, nIndex-1) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + else + { + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + // already at the left border? If not, query + // one index further left + if( nStartIndex > 0 && + GetAttributeRun(nStartIndex, nEndIndex, nStartIndex-1) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + } + break; + } + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + + CheckPosition(nIndex); + + sal_Int32 nLine, nLineCount=rCacheTF.GetLineCount( nParaIndex ); + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nCurIndex=0, nLastIndex=0, nCurLineLen=0; + sal_Int32 nLastLineLen = 0, nBulletLen = 0; + // get the line before the line the index points into + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + nLastIndex = nCurIndex; + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + if (nLine == 1) + nLastLineLen = nCurLineLen - nBulletLen; + else + nLastLineLen = nCurLineLen; + nCurLineLen = rCacheTF.GetLineLen( nParaIndex, nLine); + //nCurIndex += nCurLineLen; + if (nLine == 0) + nCurIndex += nCurLineLen - nBulletLen; + else + nCurIndex += nCurLineLen; + + //if( nCurIndex > nIndex && + //nLastIndex > nCurLineLen ) + if (nCurIndex > nIndex) + { + if (nLine == 0) + { + break; + } + else if (nLine == 1) + { + aResult.SegmentStart = 0; + aResult.SegmentEnd = nLastIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart, aResult.SegmentEnd + nBulletLen); + break; + } + else + { + //aResult.SegmentStart = nLastIndex - nCurLineLen; + aResult.SegmentStart = nLastIndex - nLastLineLen; + aResult.SegmentEnd = nLastIndex; + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + } + + break; + } + case AccessibleTextType::WORD: + { + nIndex = SkipField( nIndex, false); + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + // get word at index + implGetWordBoundary( sText, aBoundary, nIndex ); + + + //sal_Int32 curWordStart = aBoundary.startPos; + //sal_Int32 preWordStart = curWordStart; + sal_Int32 curWordStart , preWordStart; + if( aBoundary.startPos == -1 || aBoundary.startPos > nIndex) + curWordStart = preWordStart = nIndex; + else + curWordStart = preWordStart = aBoundary.startPos; + + // get previous word + + bool bWord = false; + + //while ( preWordStart > 0 && aBoundary.startPos == curWordStart) + while ( (preWordStart >= 0 && !bWord ) || ( aBoundary.endPos > curWordStart ) ) + { + preWordStart--; + bWord = implGetWordBoundary( sText, aBoundary, preWordStart ); + } + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + ExtendByField( aResult ); + } + } + break; + case AccessibleTextType::CHARACTER: + { + nIndex = SkipField( nIndex, false); + aResult = OCommonAccessibleText::getTextBeforeIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + default: + aResult = OCommonAccessibleText::getTextBeforeIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getTextBehindIndex: paragraph index value overflow"); + + css::accessibility::TextSegment aResult; + aResult.SegmentStart = -1; + aResult.SegmentEnd = -1; + i18n::Boundary aBoundary; + switch( aTextType ) + { + case AccessibleTextType::ATTRIBUTE_RUN: + { + sal_Int32 nStartIndex, nEndIndex; + + if( GetAttributeRun(nStartIndex, nEndIndex, nIndex) ) + { + // already at the right border? + if( nEndIndex < GetTextLen() ) + { + if( GetAttributeRun(nStartIndex, nEndIndex, nEndIndex) ) + { + aResult.SegmentText = GetTextRange(nStartIndex, nEndIndex); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + } + break; + } + + case AccessibleTextType::LINE: + { + SvxTextForwarder& rCacheTF = GetTextForwarder(); + sal_Int32 nParaIndex = GetParagraphIndex(); + + CheckPosition(nIndex); + + sal_Int32 nLine, nLineCount = rCacheTF.GetLineCount( nParaIndex ); + sal_Int32 nCurIndex; + //the problem is that rCacheTF.GetLineLen() will include the bullet length. But for the bullet line, + //the text value doesn't contain the bullet characters. all of the bullet and numbering info are exposed + //by the IAText::attributes(). So here must do special support for bullet line. + sal_Int32 nBulletLen = 0; + // get the line after the line the index points into + for( nLine=0, nCurIndex=0; nLine<nLineCount; ++nLine ) + { + if (nLine == 0) + { + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo(nParaIndex); + if (aBulletInfo.bVisible) + { + //in bullet or numbering; + nBulletLen = aBulletInfo.aText.getLength(); + } + } + sal_Int32 nLineLen = rCacheTF.GetLineLen( nParaIndex, nLine); + + if (nLine == 0) + nCurIndex += nLineLen - nBulletLen; + else + nCurIndex += nLineLen; + + if( nCurIndex > nIndex && + nLine < nLineCount-1 ) + { + aResult.SegmentStart = nCurIndex; + aResult.SegmentEnd = nCurIndex + rCacheTF.GetLineLen( nParaIndex, nLine+1); + aResult.SegmentText = GetTextRange( aResult.SegmentStart + nBulletLen, aResult.SegmentEnd + nBulletLen); + break; + } + } + + break; + } + case AccessibleTextType::WORD: + { + nIndex = SkipField( nIndex, true); + OUString sText( implGetText() ); + sal_Int32 nLength = sText.getLength(); + + // get word at index + bool bWord = implGetWordBoundary( sText, aBoundary, nIndex ); + + // real current world + sal_Int32 nextWord = nIndex; + //if( nIndex >= aBoundary.startPos && nIndex <= aBoundary.endPos ) + if( nIndex <= aBoundary.endPos ) + { + nextWord = aBoundary.endPos; + if (nextWord < sText.getLength() && sText[nextWord] == u' ') nextWord++; + bWord = implGetWordBoundary( sText, aBoundary, nextWord ); + } + + if ( bWord && implIsValidBoundary( aBoundary, nLength ) ) + { + aResult.SegmentText = sText.copy( aBoundary.startPos, aBoundary.endPos - aBoundary.startPos ); + aResult.SegmentStart = aBoundary.startPos; + aResult.SegmentEnd = aBoundary.endPos; + + // If the end position of aBoundary is inside a field, extend the result to the end of the field + + ExtendByField( aResult ); + } + } + break; + + case AccessibleTextType::CHARACTER: + { + nIndex = SkipField( nIndex, true); + aResult = OCommonAccessibleText::getTextBehindIndex( nIndex, aTextType ); + ExtendByField( aResult ); + break; + } + default: + aResult = OCommonAccessibleText::getTextBehindIndex( nIndex, aTextType ); + break; + } /* end of switch( aTextType ) */ + + return aResult; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + bool aRetVal; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::copyText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + //Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + // save current selection + ESelection aOldSelection; + + rCacheVF.GetSelection( aOldSelection ); + //rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + rCacheVF.SetSelection( MakeSelection(nStartIndex + nBulletLen, nEndIndex + nBulletLen) ); + aRetVal = rCacheVF.Copy(); + rCacheVF.SetSelection( aOldSelection ); // restore + + return aRetVal; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) + { + return false; + } + + // XAccessibleEditableText + sal_Bool SAL_CALL AccessibleEditableTextPara::cutText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::cutText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + // don't save selection, might become invalid after cut! + //rCacheVF.SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + rCacheVF.SetSelection( aSelection ); + + return rCacheVF.Cut(); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::pasteText( sal_Int32 nIndex ) + { + + SolarMutexGuard aGuard; + + try + { + SvxEditViewForwarder& rCacheVF = GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::pasteText: index value overflow"); + + CheckPosition(nIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + if( !rCacheTF.IsEditable( MakeSelection(nIndex + nBulletLen) ) ) + return false; // non-editable area selected + + // #104400# set empty selection (=> cursor) to given index + //rCacheVF.SetSelection( MakeCursor(nIndex) ); + rCacheVF.SetSelection( MakeCursor(nIndex + nBulletLen) ); + + return rCacheVF.Paste(); + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::deleteText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::deleteText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + //sal_Bool bRet = rCacheTF.Delete( MakeSelection(nStartIndex, nEndIndex) ); + bool bRet = rCacheTF.Delete( aSelection ); + + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::insertText( const OUString& sText, sal_Int32 nIndex ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::insertText: index value overflow"); + + CheckPosition(nIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + + if( !rCacheTF.IsEditable( MakeSelection(nIndex + nBulletLen) ) ) + return false; // non-editable area selected + + // #104400# insert given text at empty selection (=> cursor) + bool bRet = rCacheTF.InsertText( sText, MakeCursor(nIndex + nBulletLen) ); + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::replaceText( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const OUString& sReplacement ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::replaceText: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + // Because bullet may occupy one or more characters, the TextAdapter will include bullet to calculate the selection. Add offset to handle bullet + sal_Int32 nBulletLen = 0; + EBulletInfo aBulletInfo = GetTextForwarder().GetBulletInfo(GetParagraphIndex()); + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && aBulletInfo.bVisible ) + nBulletLen = aBulletInfo.aText.getLength(); + ESelection aSelection = MakeSelection (nStartIndex + nBulletLen, nEndIndex + nBulletLen); + + //if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + if( !rCacheTF.IsEditable( aSelection ) ) + return false; // non-editable area selected + + // insert given text into given range => replace + //sal_Bool bRet = rCacheTF.InsertText( sReplacement, MakeSelection(nStartIndex, nEndIndex) ); + bool bRet = rCacheTF.InsertText( sReplacement, aSelection ); + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return bRet; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setAttributes( sal_Int32 nStartIndex, sal_Int32 nEndIndex, const uno::Sequence< beans::PropertyValue >& aAttributeSet ) + { + + SolarMutexGuard aGuard; + + try + { + // #102710# Request edit view when doing changes + // AccessibleEmptyEditSource relies on this behaviour + GetEditViewForwarder( true ); + SvxAccessibleTextAdapter& rCacheTF = GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + sal_Int32 nPara = GetParagraphIndex(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::setAttributes: index value overflow"); + + CheckRange(nStartIndex, nEndIndex); + + if( !rCacheTF.IsEditable( MakeSelection(nStartIndex, nEndIndex) ) ) + return false; // non-editable area selected + + // do the indices span the whole paragraph? Then use the outliner map + // TODO: hold it as a member? + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + 0 == nStartIndex && + rCacheTF.GetTextLen(nPara) == nEndIndex ? + ImplGetSvxUnoOutlinerTextCursorSvxPropertySet() : + ImplGetSvxTextPortionSvxPropertySet() ) ); + + xPropSet->SetSelection( MakeSelection(nStartIndex, nEndIndex) ); + + // convert from PropertyValue to Any + for(const beans::PropertyValue& rProp : aAttributeSet) + { + try + { + xPropSet->setPropertyValue(rProp.Name, rProp.Value); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "dbaccess", "AccessibleEditableTextPara::setAttributes exception in setPropertyValue"); + } + } + + rCacheTF.QuickFormatDoc(); + GetEditSource().UpdateData(); + + return true; + } + catch (const uno::RuntimeException&) + { + return false; + } + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::setText( const OUString& sText ) + { + + SolarMutexGuard aGuard; + + return replaceText(0, getCharacterCount(), sText); + } + + // XAccessibleTextAttributes + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getDefaultAttributes( + const uno::Sequence< OUString >& rRequestedAttributes ) + { + SolarMutexGuard aGuard; + + GetTextForwarder(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterAttributes: index value overflow"); + + // get XPropertySetInfo for paragraph attributes and + // character attributes that span all the paragraphs text. + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + ImplGetSvxCharAndParaPropertiesSet() ) ); + xPropSet->SetSelection( MakeSelection( 0, GetTextLen() ) ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + throw uno::RuntimeException("Cannot query XPropertySetInfo", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + + // build sequence of available properties to check + uno::Sequence< beans::Property > aProperties; + if (const sal_Int32 nLenReqAttr = rRequestedAttributes.getLength()) + { + aProperties.realloc( nLenReqAttr ); + beans::Property *pProperties = aProperties.getArray(); + sal_Int32 nCurLen = 0; + for (const OUString& rRequestedAttribute : rRequestedAttributes) + { + beans::Property aProp; + try + { + aProp = xPropSetInfo->getPropertyByName( rRequestedAttribute ); + } + catch (const beans::UnknownPropertyException&) + { + continue; + } + pProperties[ nCurLen++ ] = aProp; + } + aProperties.realloc( nCurLen ); + } + else + aProperties = xPropSetInfo->getProperties(); + + // build resulting sequence + uno::Sequence< beans::PropertyValue > aOutSequence( aProperties.getLength() ); + beans::PropertyValue* pOutSequence = aOutSequence.getArray(); + sal_Int32 nOutLen = 0; + for (const beans::Property& rProperty : std::as_const(aProperties)) + { + // calling implementation functions: + // _getPropertyState and _getPropertyValue (see below) to provide + // the proper paragraph number when retrieving paragraph attributes + PropertyState eState = xPropSet->_getPropertyState( rProperty.Name, mnParagraphIndex ); + if ( eState == PropertyState_AMBIGUOUS_VALUE ) + { + OSL_FAIL( "ambiguous property value encountered" ); + } + + //if (eState == PropertyState_DIRECT_VALUE) + // per definition all paragraph properties and all character + // properties spanning the whole paragraph should be returned + // and declared as default value + { + pOutSequence->Name = rProperty.Name; + pOutSequence->Handle = rProperty.Handle; + pOutSequence->Value = xPropSet->_getPropertyValue( rProperty.Name, mnParagraphIndex ); + pOutSequence->State = PropertyState_DEFAULT_VALUE; + + ++pOutSequence; + ++nOutLen; + } + } + aOutSequence.realloc( nOutLen ); + + return aOutSequence; + } + + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleEditableTextPara::getRunAttributes( + sal_Int32 nIndex, + const uno::Sequence< OUString >& rRequestedAttributes ) + { + + SolarMutexGuard aGuard; + + GetTextForwarder(); + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::getCharacterAttributes: index value overflow"); + + if( getCharacterCount() > 0 ) + CheckIndex(nIndex); + else + CheckPosition(nIndex); + + rtl::Reference< SvxAccessibleTextPropertySet > xPropSet( new SvxAccessibleTextPropertySet( &GetEditSource(), + ImplGetSvxCharAndParaPropertiesSet() ) ); + xPropSet->SetSelection( MakeSelection( nIndex ) ); + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + throw uno::RuntimeException("Cannot query XPropertySetInfo", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + + // build sequence of available properties to check + uno::Sequence< beans::Property > aProperties; + if (const sal_Int32 nLenReqAttr = rRequestedAttributes.getLength()) + { + aProperties.realloc( nLenReqAttr ); + beans::Property *pProperties = aProperties.getArray(); + sal_Int32 nCurLen = 0; + for (const OUString& rRequestedAttribute : rRequestedAttributes) + { + beans::Property aProp; + try + { + aProp = xPropSetInfo->getPropertyByName( rRequestedAttribute ); + } + catch (const beans::UnknownPropertyException&) + { + continue; + } + pProperties[ nCurLen++ ] = aProp; + } + aProperties.realloc( nCurLen ); + } + else + aProperties = xPropSetInfo->getProperties(); + + // build resulting sequence + uno::Sequence< beans::PropertyValue > aOutSequence( aProperties.getLength() ); + beans::PropertyValue* pOutSequence = aOutSequence.getArray(); + sal_Int32 nOutLen = 0; + for (const beans::Property& rProperty : std::as_const(aProperties)) + { + // calling 'regular' functions that will operate on the selection + PropertyState eState = xPropSet->getPropertyState( rProperty.Name ); + if (eState == PropertyState_DIRECT_VALUE) + { + pOutSequence->Name = rProperty.Name; + pOutSequence->Handle = rProperty.Handle; + pOutSequence->Value = xPropSet->getPropertyValue( rProperty.Name ); + pOutSequence->State = eState; + + ++pOutSequence; + ++nOutLen; + } + } + aOutSequence.realloc( nOutLen ); + + return aOutSequence; + } + + // XAccessibleHypertext + ::sal_Int32 SAL_CALL AccessibleEditableTextPara::getHyperLinkCount( ) + { + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + const sal_Int32 nPara = GetParagraphIndex(); + + sal_Int32 nHyperLinks = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >(aField.pFieldItem->GetField() ) != nullptr) + nHyperLinks++; + } + return nHyperLinks; + } + + css::uno::Reference< css::accessibility::XAccessibleHyperlink > SAL_CALL AccessibleEditableTextPara::getHyperLink( ::sal_Int32 nLinkIndex ) + { + css::uno::Reference< css::accessibility::XAccessibleHyperlink > xRef; + + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + const sal_Int32 nPara = GetParagraphIndex(); + + sal_Int32 nHyperLink = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >(aField.pFieldItem->GetField()) != nullptr ) + { + if ( nHyperLink == nLinkIndex ) + { + sal_Int32 nEEStart = aField.aPosition.nIndex; + + // Translate EE Index to accessible index + sal_Int32 nStart = rT.CalcEditEngineIndex( nPara, nEEStart ); + sal_Int32 nEnd = nStart + aField.aCurrentText.getLength(); + xRef = new AccessibleHyperlink( rT, new SvxFieldItem( *aField.pFieldItem ), nStart, nEnd, aField.aCurrentText ); + break; + } + nHyperLink++; + } + } + + return xRef; + } + + ::sal_Int32 SAL_CALL AccessibleEditableTextPara::getHyperLinkIndex( ::sal_Int32 nCharIndex ) + { + const sal_Int32 nPara = GetParagraphIndex(); + SvxAccessibleTextAdapter& rT = GetTextForwarder(); + + const sal_Int32 nEEIndex = rT.CalcEditEngineIndex( nPara, nCharIndex ); + sal_Int32 nHLIndex = -1; //i123620 + sal_Int32 nHyperLink = 0; + sal_Int32 nFields = rT.GetFieldCount( nPara ); + for (sal_Int32 n = 0; n < nFields; ++n) + { + EFieldInfo aField = rT.GetFieldInfo( nPara, n ); + if ( dynamic_cast<const SvxURLField* >( aField.pFieldItem->GetField() ) != nullptr) + { + if ( aField.aPosition.nIndex == nEEIndex ) + { + nHLIndex = nHyperLink; + break; + } + nHyperLink++; + } + } + + return nHLIndex; + } + + // XAccessibleMultiLineText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getLineNumberAtIndex( sal_Int32 nIndex ) + { + + sal_Int32 nRes = -1; + sal_Int32 nPara = GetParagraphIndex(); + + SvxTextForwarder &rCacheTF = GetTextForwarder(); + const bool bValidPara = 0 <= nPara && nPara < rCacheTF.GetParagraphCount(); + DBG_ASSERT( bValidPara, "getLineNumberAtIndex: current paragraph index out of range" ); + if (bValidPara) + { + // we explicitly allow for the index to point at the character right behind the text + if (0 > nIndex || nIndex > rCacheTF.GetTextLen( nPara )) + throw lang::IndexOutOfBoundsException(); + nRes = rCacheTF.GetLineNumberAtIndex( nPara, nIndex ); + } + return nRes; + } + + // XAccessibleMultiLineText + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtLineNumber( sal_Int32 nLineNo ) + { + + css::accessibility::TextSegment aResult; + sal_Int32 nPara = GetParagraphIndex(); + SvxTextForwarder &rCacheTF = GetTextForwarder(); + const bool bValidPara = 0 <= nPara && nPara < rCacheTF.GetParagraphCount(); + DBG_ASSERT( bValidPara, "getTextAtLineNumber: current paragraph index out of range" ); + if (bValidPara) + { + if (0 > nLineNo || nLineNo >= rCacheTF.GetLineCount( nPara )) + throw lang::IndexOutOfBoundsException(); + sal_Int32 nStart = 0, nEnd = 0; + rCacheTF.GetLineBoundaries( nStart, nEnd, nPara, nLineNo ); + if (nStart >= 0 && nEnd >= 0) + { + try + { + aResult.SegmentText = getTextRange( nStart, nEnd ); + aResult.SegmentStart = nStart; + aResult.SegmentEnd = nEnd; + } + catch (const lang::IndexOutOfBoundsException&) + { + // this is not the exception that should be raised in this function ... + DBG_UNHANDLED_EXCEPTION("editeng"); + } + } + } + return aResult; + } + + // XAccessibleMultiLineText + css::accessibility::TextSegment SAL_CALL AccessibleEditableTextPara::getTextAtLineWithCaret( ) + { + + css::accessibility::TextSegment aResult; + try + { + aResult = getTextAtLineNumber( getNumberOfLineWithCaret() ); + } + catch (const lang::IndexOutOfBoundsException&) + { + // this one needs to be caught since this interface does not allow for it. + } + return aResult; + } + + // XAccessibleMultiLineText + sal_Int32 SAL_CALL AccessibleEditableTextPara::getNumberOfLineWithCaret( ) + { + + sal_Int32 nRes = -1; + try + { + nRes = getLineNumberAtIndex( getCaretPosition() ); + } + catch (const lang::IndexOutOfBoundsException&) + { + // this one needs to be caught since this interface does not allow for it. + } + return nRes; + } + + + // XServiceInfo + OUString SAL_CALL AccessibleEditableTextPara::getImplementationName() + { + + return "AccessibleEditableTextPara"; + } + + sal_Bool SAL_CALL AccessibleEditableTextPara::supportsService (const OUString& sServiceName) + { + + return cppu::supportsService(this, sServiceName); + } + + uno::Sequence< OUString> SAL_CALL AccessibleEditableTextPara::getSupportedServiceNames() + { + // #105185# Using correct service now + return { OUString("com.sun.star.text.AccessibleParagraphView") }; + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleHyperlink.cxx b/editeng/source/accessibility/AccessibleHyperlink.cxx new file mode 100644 index 0000000000..25d9683fce --- /dev/null +++ b/editeng/source/accessibility/AccessibleHyperlink.cxx @@ -0,0 +1,128 @@ +/* -*- 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 <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <comphelper/accessiblekeybindinghelper.hxx> + +#include "AccessibleHyperlink.hxx" +#include <editeng/unoedprx.hxx> +#include <editeng/flditem.hxx> +#include <vcl/keycodes.hxx> + +using namespace ::com::sun::star; + + +// AccessibleHyperlink implementation + + +namespace accessibility +{ + + AccessibleHyperlink::AccessibleHyperlink( SvxAccessibleTextAdapter& r, SvxFieldItem* p, sal_Int32 nStt, sal_Int32 nEnd, const OUString& rD ) + : rTA( r ) + { + pFld.reset( p ); + nStartIdx = nStt; + nEndIdx = nEnd; + aDescription = rD; + } + + AccessibleHyperlink::~AccessibleHyperlink() + { + } + + // XAccessibleAction + sal_Int32 SAL_CALL AccessibleHyperlink::getAccessibleActionCount() + { + return isValid() ? 1 : 0; + } + + sal_Bool SAL_CALL AccessibleHyperlink::doAccessibleAction( sal_Int32 nIndex ) + { + bool bRet = false; + if ( isValid() && ( nIndex == 0 ) ) + { + rTA.FieldClicked( *pFld ); + bRet = true; + } + return bRet; + } + + OUString SAL_CALL AccessibleHyperlink::getAccessibleActionDescription( sal_Int32 nIndex ) + { + OUString aDesc; + + if ( isValid() && ( nIndex == 0 ) ) + aDesc = aDescription; + + return aDesc; + } + + uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL AccessibleHyperlink::getAccessibleActionKeyBinding( sal_Int32 nIndex ) + { + uno::Reference< css::accessibility::XAccessibleKeyBinding > xKeyBinding; + + if( isValid() && ( nIndex == 0 ) ) + { + rtl::Reference<::comphelper::OAccessibleKeyBindingHelper> pKeyBindingHelper = new ::comphelper::OAccessibleKeyBindingHelper(); + xKeyBinding = pKeyBindingHelper; + + awt::KeyStroke aKeyStroke; + aKeyStroke.Modifiers = 0; + aKeyStroke.KeyCode = KEY_RETURN; + aKeyStroke.KeyChar = 0; + aKeyStroke.KeyFunc = 0; + pKeyBindingHelper->AddKeyBinding( aKeyStroke ); + } + + return xKeyBinding; + } + + // XAccessibleHyperlink + uno::Any SAL_CALL AccessibleHyperlink::getAccessibleActionAnchor( sal_Int32 /*nIndex*/ ) + { + return uno::Any(); + } + + uno::Any SAL_CALL AccessibleHyperlink::getAccessibleActionObject( sal_Int32 /*nIndex*/ ) + { + return uno::Any(); + } + + sal_Int32 SAL_CALL AccessibleHyperlink::getStartIndex() + { + return nStartIdx; + } + + sal_Int32 SAL_CALL AccessibleHyperlink::getEndIndex() + { + return nEndIdx; + } + + sal_Bool SAL_CALL AccessibleHyperlink::isValid( ) + { + return rTA.IsValid(); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleHyperlink.hxx b/editeng/source/accessibility/AccessibleHyperlink.hxx new file mode 100644 index 0000000000..7e4f36a6bb --- /dev/null +++ b/editeng/source/accessibility/AccessibleHyperlink.hxx @@ -0,0 +1,64 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/accessibility/XAccessibleHyperlink.hpp> + +#include <memory> + +class SvxFieldItem; +class SvxAccessibleTextAdapter; + +namespace accessibility +{ + + class AccessibleHyperlink : public ::cppu::WeakImplHelper< css::accessibility::XAccessibleHyperlink > + { + private: + + SvxAccessibleTextAdapter& rTA; + std::unique_ptr<SvxFieldItem> pFld; + sal_Int32 nStartIdx, nEndIdx; // translated values + OUString aDescription; + + public: + AccessibleHyperlink( SvxAccessibleTextAdapter& r, SvxFieldItem* p, sal_Int32 nStt, sal_Int32 nEnd, const OUString& rD ); + virtual ~AccessibleHyperlink() override; + + // XAccessibleAction + virtual sal_Int32 SAL_CALL getAccessibleActionCount() override; + virtual sal_Bool SAL_CALL doAccessibleAction( sal_Int32 nIndex ) override; + virtual OUString SAL_CALL getAccessibleActionDescription( sal_Int32 nIndex ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleKeyBinding > SAL_CALL getAccessibleActionKeyBinding( sal_Int32 nIndex ) override; + + // XAccessibleHyperlink + virtual css::uno::Any SAL_CALL getAccessibleActionAnchor( sal_Int32 nIndex ) override; + virtual css::uno::Any SAL_CALL getAccessibleActionObject( sal_Int32 nIndex ) override; + virtual sal_Int32 SAL_CALL getStartIndex() override; + virtual sal_Int32 SAL_CALL getEndIndex() override; + virtual sal_Bool SAL_CALL isValid() override; + }; + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleImageBullet.cxx b/editeng/source/accessibility/AccessibleImageBullet.cxx new file mode 100644 index 0000000000..c3a051cf01 --- /dev/null +++ b/editeng/source/accessibility/AccessibleImageBullet.cxx @@ -0,0 +1,517 @@ +/* -*- 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 <tools/gen.hxx> +#include <tools/debug.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <rtl/ustring.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> +#include <editeng/eerdll.hxx> + +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editrids.hrc> +#include <editeng/unoedsrc.hxx> +#include <svtools/colorcfg.hxx> + +#include "AccessibleImageBullet.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility +{ + + AccessibleImageBullet::AccessibleImageBullet ( uno::Reference< XAccessible > xParent ) : + mnParagraphIndex( 0 ), + mnIndexInParent( 0 ), + mpEditSource( nullptr ), + maEEOffset( 0, 0 ), + mxParent(std::move( xParent )), + // well, that's strictly (UNO) exception safe, though not + // really robust. We rely on the fact that this member is + // constructed last, and that the constructor body catches + // exceptions, thus no chance for exceptions once the Id is + // fetched. Nevertheless, normally should employ RAII here... + mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()) + { + try + { + // Create the state set. + mnStateSet = 0; + + // these are always on + mnStateSet |= AccessibleStateType::VISIBLE; + mnStateSet |= AccessibleStateType::SHOWING; + mnStateSet |= AccessibleStateType::ENABLED; + mnStateSet |= AccessibleStateType::SENSITIVE; + } + catch( const uno::Exception& ) {} + } + + AccessibleImageBullet::~AccessibleImageBullet() + { + + // sign off from event notifier + if( getNotifierClientId() != -1 ) + { + try + { + ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); + } + catch( const uno::Exception& ) {} + } + } + + uno::Reference< XAccessibleContext > SAL_CALL AccessibleImageBullet::getAccessibleContext( ) + { + + // We implement the XAccessibleContext interface in the same object + return uno::Reference< XAccessibleContext > ( this ); + } + + sal_Int64 SAL_CALL AccessibleImageBullet::getAccessibleChildCount() + { + + return 0; + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleChild( sal_Int64 ) + { + throw lang::IndexOutOfBoundsException("No children available", + getXWeak() ); + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleParent() + { + + return mxParent; + } + + sal_Int64 SAL_CALL AccessibleImageBullet::getAccessibleIndexInParent() + { + + return mnIndexInParent; + } + + sal_Int16 SAL_CALL AccessibleImageBullet::getAccessibleRole() + { + + return AccessibleRole::GRAPHIC; + } + + OUString SAL_CALL AccessibleImageBullet::getAccessibleDescription() + { + // Get the string from the resource for the specified id. + return EditResId(RID_SVXSTR_A11Y_IMAGEBULLET_DESCRIPTION); + } + + OUString SAL_CALL AccessibleImageBullet::getAccessibleName() + { + // Get the string from the resource for the specified id. + return EditResId(RID_SVXSTR_A11Y_IMAGEBULLET_NAME); + } + + uno::Reference< XAccessibleRelationSet > SAL_CALL AccessibleImageBullet::getAccessibleRelationSet() + { + + // no relations, therefore empty + return uno::Reference< XAccessibleRelationSet >(); + } + + sal_Int64 SAL_CALL AccessibleImageBullet::getAccessibleStateSet() + { + SolarMutexGuard aGuard; + + // Create a copy of the state set and return it. + + return mnStateSet; + } + + lang::Locale SAL_CALL AccessibleImageBullet::getLocale() + { + + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleImageBullet::getLocale: paragraph index value overflow"); + + // return locale of first character in the paragraph + return LanguageTag(GetTextForwarder().GetLanguage( GetParagraphIndex(), 0 )).getLocale(); + } + + void SAL_CALL AccessibleImageBullet::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + + if( getNotifierClientId() != -1 ) + ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); + } + + void SAL_CALL AccessibleImageBullet::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) + { + + if( getNotifierClientId() == -1 ) + return; + + const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() ); + mnNotifierClientId = -1; + ::comphelper::AccessibleEventNotifier::revokeClient( nId ); + } + } + + sal_Bool SAL_CALL AccessibleImageBullet::containsPoint( const awt::Point& rPoint ) + { + + SolarMutexGuard aGuard; + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::contains: index value overflow"); + + awt::Rectangle aTmpRect = implGetBounds(); + tools::Rectangle aRect( Point(aTmpRect.X, aTmpRect.Y), Size(aTmpRect.Width, aTmpRect.Height) ); + Point aPoint( rPoint.X, rPoint.Y ); + + return aRect.Contains( aPoint ); + } + + uno::Reference< XAccessible > SAL_CALL AccessibleImageBullet::getAccessibleAtPoint( const awt::Point& /*aPoint*/ ) + { + + // as we have no children, empty reference + return uno::Reference< XAccessible >(); + } + + awt::Rectangle SAL_CALL AccessibleImageBullet::getBounds( ) + { + SolarMutexGuard aGuard; + + return implGetBounds(); + } + awt::Rectangle AccessibleImageBullet::implGetBounds( ) + { + + DBG_ASSERT(GetParagraphIndex() >= 0, + "AccessibleEditableTextPara::implGetBounds: index value overflow"); + + SvxTextForwarder& rCacheTF = GetTextForwarder(); + EBulletInfo aBulletInfo = rCacheTF.GetBulletInfo( GetParagraphIndex() ); + tools::Rectangle aParentRect = rCacheTF.GetParaBounds( GetParagraphIndex() ); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ) + { + tools::Rectangle aRect = aBulletInfo.aBounds; + + // subtract paragraph position (bullet pos is absolute in EditEngine/Outliner) + aRect.Move( -aParentRect.Left(), -aParentRect.Top() ); + + // convert to screen coordinates + tools::Rectangle aScreenRect = AccessibleEditableTextPara::LogicToPixel( aRect, + rCacheTF.GetMapMode(), + GetViewForwarder() ); + + // offset from shape/cell + Point aOffset = maEEOffset; + + return awt::Rectangle( aScreenRect.Left() + aOffset.X(), + aScreenRect.Top() + aOffset.Y(), + aScreenRect.GetSize().Width(), + aScreenRect.GetSize().Height() ); + } + + return awt::Rectangle(); + } + + awt::Point SAL_CALL AccessibleImageBullet::getLocation( ) + { + + SolarMutexGuard aGuard; + + awt::Rectangle aRect = implGetBounds(); + + return awt::Point( aRect.X, aRect.Y ); + } + + awt::Point SAL_CALL AccessibleImageBullet::getLocationOnScreen( ) + { + + SolarMutexGuard aGuard; + + // relate us to parent + uno::Reference< XAccessible > xParent = getAccessibleParent(); + if( xParent.is() ) + { + uno::Reference< XAccessibleComponent > xParentComponent( xParent, uno::UNO_QUERY ); + if( xParentComponent.is() ) + { + awt::Point aRefPoint = xParentComponent->getLocationOnScreen(); + awt::Point aPoint = getLocation(); + aPoint.X += aRefPoint.X; + aPoint.Y += aRefPoint.Y; + + return aPoint; + } + } + + throw uno::RuntimeException("Cannot access parent", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + awt::Size SAL_CALL AccessibleImageBullet::getSize( ) + { + + SolarMutexGuard aGuard; + + awt::Rectangle aRect = implGetBounds(); + + return awt::Size( aRect.Width, aRect.Height ); + } + + void SAL_CALL AccessibleImageBullet::grabFocus( ) + { + + throw uno::RuntimeException("Not focusable", + uno::Reference< uno::XInterface > + ( static_cast< XAccessible* > (this) ) ); // disambiguate hierarchy + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getForeground( ) + { + + // #104444# Added to XAccessibleComponent interface + svtools::ColorConfig aColorConfig; + Color nColor = aColorConfig.GetColorValue( svtools::FONTCOLOR ).nColor; + return static_cast<sal_Int32>(nColor); + } + + sal_Int32 SAL_CALL AccessibleImageBullet::getBackground( ) + { + + // #104444# Added to XAccessibleComponent interface + Color aColor( Application::GetSettings().GetStyleSettings().GetWindowColor() ); + + // the background is transparent + aColor.SetAlpha(0); + + return static_cast<sal_Int32>( aColor ); + } + + OUString SAL_CALL AccessibleImageBullet::getImplementationName() + { + + return "AccessibleImageBullet"; + } + + sal_Bool SAL_CALL AccessibleImageBullet::supportsService (const OUString& sServiceName) + { + + return cppu::supportsService(this, sServiceName); + } + + uno::Sequence< OUString > SAL_CALL AccessibleImageBullet::getSupportedServiceNames() + { + return { "com.sun.star.accessibility.AccessibleContext" }; + } + + void AccessibleImageBullet::SetIndexInParent( sal_Int32 nIndex ) + { + + mnIndexInParent = nIndex; + } + + void AccessibleImageBullet::SetEEOffset( const Point& rOffset ) + { + + maEEOffset = rOffset; + } + + void AccessibleImageBullet::Dispose() + { + + int nClientId( getNotifierClientId() ); + + // #108212# drop all references before notifying dispose + mxParent = nullptr; + mnNotifierClientId = -1; + mpEditSource = nullptr; + + // notify listeners + if( nClientId != -1 ) + { + try + { + uno::Reference < XAccessibleContext > xThis = getAccessibleContext(); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, xThis ); + } + catch( const uno::Exception& ) {} + } + } + + void AccessibleImageBullet::SetEditSource( SvxEditSource* pEditSource ) + { + + mpEditSource = pEditSource; + + if( !mpEditSource ) + { + // going defunc + UnSetState( AccessibleStateType::SHOWING ); + UnSetState( AccessibleStateType::VISIBLE ); + SetState( AccessibleStateType::INVALID ); + SetState( AccessibleStateType::DEFUNC ); + + Dispose(); + } + } + + void AccessibleImageBullet::FireEvent(const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const + { + + uno::Reference < XAccessibleContext > xThis( const_cast< AccessibleImageBullet* > (this)->getAccessibleContext() ); + + AccessibleEventObject aEvent(xThis, nEventId, rNewValue, rOldValue, -1); + + // #106234# Delegate to EventNotifier + ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), + aEvent ); + } + + void AccessibleImageBullet::SetState( const sal_Int64 nStateId ) + { + if( !(mnStateSet & nStateId) ) + { + mnStateSet |= nStateId; + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any( nStateId ) ); + } + } + + void AccessibleImageBullet::UnSetState( const sal_Int64 nStateId ) + { + if( mnStateSet & nStateId ) + { + mnStateSet &= ~nStateId; + FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::Any( nStateId ) ); + } + } + + + void AccessibleImageBullet::SetParagraphIndex( sal_Int32 nIndex ) + { + + uno::Any aOldDesc; + uno::Any aOldName; + + try + { + aOldDesc <<= getAccessibleDescription(); + aOldName <<= getAccessibleName(); + } + catch( const uno::Exception& ) {} // optional behaviour + + sal_Int32 nOldIndex = mnParagraphIndex; + + mnParagraphIndex = nIndex; + + try + { + if( nOldIndex != nIndex ) + { + // index and therefore description changed + FireEvent( AccessibleEventId::DESCRIPTION_CHANGED, uno::Any( getAccessibleDescription() ), aOldDesc ); + FireEvent( AccessibleEventId::NAME_CHANGED, uno::Any( getAccessibleName() ), aOldName ); + } + } + catch( const uno::Exception& ) {} // optional behaviour + } + + + SvxEditSource& AccessibleImageBullet::GetEditSource() const + { + + if( !mpEditSource ) + throw uno::RuntimeException("No edit source, object is defunct", + cppu::getXWeak + ( const_cast< AccessibleImageBullet* > (this) ) ); // disambiguate hierarchy + return *mpEditSource; + } + + SvxTextForwarder& AccessibleImageBullet::GetTextForwarder() const + { + + SvxEditSource& rEditSource = GetEditSource(); + SvxTextForwarder* pTextForwarder = rEditSource.GetTextForwarder(); + + if( !pTextForwarder ) + throw uno::RuntimeException("Unable to fetch text forwarder, object is defunct", + cppu::getXWeak + ( const_cast< AccessibleImageBullet* > (this) ) ); // disambiguate hierarchy + + if( !pTextForwarder->IsValid() ) + throw uno::RuntimeException("Text forwarder is invalid, object is defunct", + cppu::getXWeak + ( const_cast< AccessibleImageBullet* > (this) ) ); // disambiguate hierarchy + return *pTextForwarder; + } + + SvxViewForwarder& AccessibleImageBullet::GetViewForwarder() const + { + + SvxEditSource& rEditSource = GetEditSource(); + SvxViewForwarder* pViewForwarder = rEditSource.GetViewForwarder(); + + if( !pViewForwarder ) + { + throw uno::RuntimeException("Unable to fetch view forwarder, object is defunct", + cppu::getXWeak + ( const_cast< AccessibleImageBullet* > (this) ) ); // disambiguate hierarchy + } + + if( !pViewForwarder->IsValid() ) + throw uno::RuntimeException("View forwarder is invalid, object is defunct", + cppu::getXWeak + ( const_cast< AccessibleImageBullet* > (this) ) ); // disambiguate hierarchy + return *pViewForwarder; + } + + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleImageBullet.hxx b/editeng/source/accessibility/AccessibleImageBullet.hxx new file mode 100644 index 0000000000..97fd98cae7 --- /dev/null +++ b/editeng/source/accessibility/AccessibleImageBullet.hxx @@ -0,0 +1,201 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> +#include <tools/gen.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +class SvxEditSource; +class SvxTextForwarder; +class SvxViewForwarder; + +namespace accessibility +{ + typedef ::cppu::WeakImplHelper< css::accessibility::XAccessible, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo > AccessibleImageBulletInterfaceBase; + + /** This class implements the image bullets for the EditEngine/Outliner UAA + */ + class AccessibleImageBullet final : public AccessibleImageBulletInterfaceBase + { + + public: + /// Create accessible object for given parent + AccessibleImageBullet ( css::uno::Reference< css::accessibility::XAccessible > xParent ); + + virtual ~AccessibleImageBullet () override; + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleContext + virtual sal_Int64 SAL_CALL getAccessibleChildCount() override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int64 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent() override; + virtual sal_Int64 SAL_CALL getAccessibleIndexInParent() override; + virtual sal_Int16 SAL_CALL getAccessibleRole() override; + virtual OUString SAL_CALL getAccessibleDescription() override; + virtual OUString SAL_CALL getAccessibleName() override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet() override; + virtual sal_Int64 SAL_CALL getAccessibleStateSet() override; + virtual css::lang::Locale SAL_CALL getLocale() override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService (const OUString& sServiceName) override; + virtual css::uno::Sequence< OUString> SAL_CALL getSupportedServiceNames() override; + + /** Set the current index in the accessibility parent + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetIndexInParent( sal_Int32 nIndex ); + + /** Set the edit engine offset + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetEEOffset( const Point& rOffset ); + + /** Set the EditEngine offset + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetEditSource( SvxEditSource* pEditSource ); + + /** Dispose this object + + Notifies and deregisters the listeners, drops all references. + */ + void Dispose(); + + /** Set the current paragraph number + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + void SetParagraphIndex( sal_Int32 nIndex ); + + /** Query the current paragraph number (0 - nParas-1) + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + sal_Int32 GetParagraphIndex() const { return mnParagraphIndex; } + + /// Calls all Listener objects to tell them the change. Don't hold locks when calling this! + void FireEvent(const sal_Int16 nEventId, const css::uno::Any& rNewValue, const css::uno::Any& rOldValue = css::uno::Any() ) const; + + private: + AccessibleImageBullet( const AccessibleImageBullet& ) = delete; + AccessibleImageBullet& operator= ( const AccessibleImageBullet& ) = delete; + + // maintain state set and send STATE_CHANGE events + void SetState( const sal_Int64 nStateId ); + void UnSetState( const sal_Int64 nStateId ); + + SvxEditSource& GetEditSource() const; + + int getNotifierClientId() const { return mnNotifierClientId; } + + /** Query the SvxTextForwarder for EditEngine access. + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + SvxTextForwarder& GetTextForwarder() const; + + /** Query the SvxViewForwarder for EditEngine access. + + @attention This method does not lock the SolarMutex, + leaving that to the calling code. This is because only + there potential deadlock situations can be resolved. Thus, + make sure SolarMutex is locked when calling this. + */ + SvxViewForwarder& GetViewForwarder() const; + + css::awt::Rectangle implGetBounds(); + + // the paragraph index in the edit engine (guarded by solar mutex) + sal_Int32 mnParagraphIndex; + + // our current index in the parent (guarded by solar mutex) + sal_Int32 mnIndexInParent; + + // the current edit source (guarded by solar mutex) + SvxEditSource* mpEditSource; + + // the offset of the underlying EditEngine from the shape/cell (guarded by solar mutex) + Point maEEOffset; + + // the current state set (updated from SetState/UnSetState and guarded by solar mutex) + sal_Int64 mnStateSet = 0; + + /// The shape we're the accessible for (unguarded) + css::uno::Reference< css::accessibility::XAccessible > mxParent; + + /// Our listeners (guarded by maMutex) + int mnNotifierClientId; + }; + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleParaManager.cxx b/editeng/source/accessibility/AccessibleParaManager.cxx new file mode 100644 index 0000000000..c88f82d677 --- /dev/null +++ b/editeng/source/accessibility/AccessibleParaManager.cxx @@ -0,0 +1,408 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + + +// Project-local header + + +#include <editeng/AccessibleParaManager.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + + +namespace accessibility +{ + AccessibleParaManager::AccessibleParaManager() : + maChildren(1), + mnChildStates( 0 ), + maEEOffset( 0, 0 ), + mnFocusedChild( -1 ), + mbActive( false ) + { + } + + AccessibleParaManager::~AccessibleParaManager() + { + // owner is responsible for possible child death + } + + void AccessibleParaManager::SetAdditionalChildStates( sal_Int64 nChildStates ) + { + mnChildStates = nChildStates; + } + + void AccessibleParaManager::SetNum( sal_Int32 nNumParas ) + { + if( o3tl::make_unsigned(nNumParas) < maChildren.size() ) + Release( nNumParas, maChildren.size() ); + + maChildren.resize( nNumParas ); + + if( mnFocusedChild >= nNumParas ) + mnFocusedChild = -1; + } + + sal_Int32 AccessibleParaManager::GetNum() const + { + size_t nSize = maChildren.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "AccessibleParaManager::GetNum - overflow " << nSize); + return SAL_MAX_INT32; + } + return static_cast<sal_Int32>(nSize); + } + + AccessibleParaManager::VectorOfChildren::iterator AccessibleParaManager::begin() + { + return maChildren.begin(); + } + + AccessibleParaManager::VectorOfChildren::iterator AccessibleParaManager::end() + { + return maChildren.end(); + } + + void AccessibleParaManager::FireEvent( sal_Int32 nPara, + const sal_Int16 nEventId ) const + { + DBG_ASSERT( 0 <= nPara && maChildren.size() > o3tl::make_unsigned(nPara), + "AccessibleParaManager::FireEvent: invalid index" ); + + if( 0 <= nPara && maChildren.size() > o3tl::make_unsigned(nPara) ) + { + auto aChild( GetChild( nPara ).first.get() ); + if( aChild.is() ) + aChild->FireEvent( nEventId ); + } + } + + bool AccessibleParaManager::IsReferencable( + rtl::Reference<AccessibleEditableTextPara> const & aChild) + { + return aChild.is(); + } + + bool AccessibleParaManager::IsReferencable( sal_Int32 nChild ) const + { + DBG_ASSERT( 0 <= nChild && maChildren.size() > o3tl::make_unsigned(nChild), + "AccessibleParaManager::IsReferencable: invalid index" ); + + if( 0 <= nChild && maChildren.size() > o3tl::make_unsigned(nChild) ) + { + // retrieve hard reference from weak one + return IsReferencable( GetChild( nChild ).first.get() ); + } + else + { + return false; + } + } + + AccessibleParaManager::WeakChild AccessibleParaManager::GetChild( sal_Int32 nParagraphIndex ) const + { + DBG_ASSERT( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex), + "AccessibleParaManager::GetChild: invalid index" ); + + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + return maChildren[ nParagraphIndex ]; + } + else + { + return WeakChild(); + } + } + + bool AccessibleParaManager::HasCreatedChild( sal_Int32 nParagraphIndex ) const + { + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + auto const & rChild = maChildren[ nParagraphIndex ]; + return rChild.second.Width != 0 || rChild.second.Height != 0; + } + else + return false; + } + + AccessibleParaManager::Child AccessibleParaManager::CreateChild( sal_Int32 nChild, + const uno::Reference< XAccessible >& xFrontEnd, + SvxEditSourceAdapter& rEditSource, + sal_Int32 nParagraphIndex ) + { + DBG_ASSERT( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex), + "AccessibleParaManager::CreateChild: invalid index" ); + + if( 0 <= nParagraphIndex && maChildren.size() > o3tl::make_unsigned(nParagraphIndex) ) + { + // retrieve hard reference from weak one + auto aChild( GetChild( nParagraphIndex ).first.get() ); + + if( !IsReferencable( nParagraphIndex ) ) + { + // there is no hard reference available, create object then + // #i27138# + aChild = new AccessibleEditableTextPara(xFrontEnd, this); + + InitChild( *aChild, rEditSource, nChild, nParagraphIndex ); + + maChildren[ nParagraphIndex ] = WeakChild( aChild, aChild->getBounds() ); + } + + return Child( aChild.get(), GetChild( nParagraphIndex ).second ); + } + else + { + return Child(); + } + } + + void AccessibleParaManager::SetEEOffset( const Point& rOffset ) + { + maEEOffset = rOffset; + + MemFunAdapter< const Point& > aAdapter( &::accessibility::AccessibleEditableTextPara::SetEEOffset, rOffset ); + std::for_each( begin(), end(), aAdapter ); + } + + void AccessibleParaManager::SetActive( bool bActive ) + { + mbActive = bActive; + + if( bActive ) + { + SetState( AccessibleStateType::ACTIVE ); + SetState( AccessibleStateType::EDITABLE ); + } + else + { + UnSetState( AccessibleStateType::ACTIVE ); + UnSetState( AccessibleStateType::EDITABLE ); + } + } + + void AccessibleParaManager::SetFocus( sal_Int32 nChild ) + { + if( mnFocusedChild != -1 ) + UnSetState( mnFocusedChild, AccessibleStateType::FOCUSED ); + + mnFocusedChild = nChild; + + if( mnFocusedChild != -1 ) + SetState( mnFocusedChild, AccessibleStateType::FOCUSED ); + } + + void AccessibleParaManager::InitChild( AccessibleEditableTextPara& rChild, + SvxEditSourceAdapter& rEditSource, + sal_Int32 nChild, + sal_Int32 nParagraphIndex ) const + { + rChild.SetEditSource( &rEditSource ); + rChild.SetIndexInParent( nChild ); + rChild.SetParagraphIndex( nParagraphIndex ); + + rChild.SetEEOffset( maEEOffset ); + + if( mbActive ) + { + rChild.SetState( AccessibleStateType::ACTIVE ); + rChild.SetState( AccessibleStateType::EDITABLE ); + } + + if( mnFocusedChild == nParagraphIndex ) + rChild.SetState( AccessibleStateType::FOCUSED ); + + // add states passed from outside + for (int i=0; i<63; i++) + { + sal_Int64 nState = sal_Int64(1) << i; + if ( nState & mnChildStates ) + rChild.SetState( nState ); + } + } + + void AccessibleParaManager::SetState( sal_Int32 nChild, const sal_Int64 nStateId ) + { + MemFunAdapter< const sal_Int64 > aFunc( &AccessibleEditableTextPara::SetState, + nStateId ); + aFunc( GetChild(nChild) ); + } + + void AccessibleParaManager::SetState( const sal_Int64 nStateId ) + { + std::for_each( begin(), end(), + MemFunAdapter< const sal_Int64 >( &AccessibleEditableTextPara::SetState, + nStateId ) ); + } + + void AccessibleParaManager::UnSetState( sal_Int32 nChild, const sal_Int64 nStateId ) + { + MemFunAdapter< const sal_Int64 > aFunc( &AccessibleEditableTextPara::UnSetState, + nStateId ); + aFunc( GetChild(nChild) ); + } + + void AccessibleParaManager::UnSetState( const sal_Int64 nStateId ) + { + std::for_each( begin(), end(), + MemFunAdapter< const sal_Int64 >( &AccessibleEditableTextPara::UnSetState, + nStateId ) ); + } + + namespace { + + // not generic yet, no arguments... + class AccessibleParaManager_DisposeChildren + { + public: + AccessibleParaManager_DisposeChildren() {} + void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) + { + rPara.Dispose(); + } + }; + + } + + void AccessibleParaManager::Dispose() + { + AccessibleParaManager_DisposeChildren aFunctor; + + std::for_each( begin(), end(), + WeakChildAdapter< AccessibleParaManager_DisposeChildren > (aFunctor) ); + } + + namespace { + + // not generic yet, too many method arguments... + class StateChangeEvent + { + public: + StateChangeEvent( const sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue ) : + mnEventId( nEventId ), + mrNewValue( rNewValue ), + mrOldValue( rOldValue ) {} + void operator()( ::accessibility::AccessibleEditableTextPara const & rPara ) + { + rPara.FireEvent( mnEventId, mrNewValue, mrOldValue ); + } + + private: + const sal_Int16 mnEventId; + const uno::Any& mrNewValue; + const uno::Any& mrOldValue; + }; + + } + + void AccessibleParaManager::FireEvent( sal_Int32 nStartPara, + sal_Int32 nEndPara, + const sal_Int16 nEventId, + const uno::Any& rNewValue, + const uno::Any& rOldValue ) const + { + DBG_ASSERT( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) && + nEndPara >= nStartPara, "AccessibleParaManager::FireEvent: invalid index" ); + + + if( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) && + nEndPara >= nStartPara ) + { + VectorOfChildren::const_iterator front = maChildren.begin(); + VectorOfChildren::const_iterator back = front; + + std::advance( front, nStartPara ); + std::advance( back, nEndPara ); + + StateChangeEvent aFunctor( nEventId, rNewValue, rOldValue ); + + std::for_each( front, back, AccessibleParaManager::WeakChildAdapter< StateChangeEvent >( aFunctor ) ); + } + } + + namespace { + + class ReleaseChild + { + public: + AccessibleParaManager::WeakChild operator()( const AccessibleParaManager::WeakChild& rPara ) + { + AccessibleParaManager::ShutdownPara( rPara ); + + // clear reference + return AccessibleParaManager::WeakChild(); + } + }; + + } + + void AccessibleParaManager::Release( sal_Int32 nStartPara, sal_Int32 nEndPara ) + { + DBG_ASSERT( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara), + "AccessibleParaManager::Release: invalid index" ); + + if( 0 <= nStartPara && 0 <= nEndPara && + maChildren.size() > o3tl::make_unsigned(nStartPara) && + maChildren.size() >= o3tl::make_unsigned(nEndPara) ) + { + VectorOfChildren::iterator front = maChildren.begin(); + VectorOfChildren::iterator back = front; + + std::advance( front, nStartPara ); + std::advance( back, nEndPara ); + + std::transform( front, back, front, ReleaseChild() ); + } + } + + void AccessibleParaManager::ShutdownPara( const WeakChild& rChild ) + { + auto aChild( rChild.first.get() ); + + if( IsReferencable( aChild ) ) + aChild->SetEditSource( nullptr ); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleSelectionBase.cxx b/editeng/source/accessibility/AccessibleSelectionBase.cxx new file mode 100644 index 0000000000..e70b618408 --- /dev/null +++ b/editeng/source/accessibility/AccessibleSelectionBase.cxx @@ -0,0 +1,91 @@ +/* -*- 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 <editeng/AccessibleSelectionBase.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace accessibility +{ + + // - AccessibleSelectionBase - + + + AccessibleSelectionBase::AccessibleSelectionBase() + { + } + + + AccessibleSelectionBase::~AccessibleSelectionBase() + { + } + + + void SAL_CALL AccessibleSelectionBase::selectAccessibleChild( sal_Int64 nChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::selectAccessibleChild( nChildIndex ); + } + + + sal_Bool SAL_CALL AccessibleSelectionBase::isAccessibleChildSelected( sal_Int64 nChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::isAccessibleChildSelected( nChildIndex ); + } + + + void SAL_CALL AccessibleSelectionBase::clearAccessibleSelection( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::clearAccessibleSelection(); + } + + + void SAL_CALL AccessibleSelectionBase::selectAllAccessibleChildren( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::selectAllAccessibleChildren(); + } + + + sal_Int64 SAL_CALL AccessibleSelectionBase::getSelectedAccessibleChildCount( ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::getSelectedAccessibleChildCount(); + } + + + uno::Reference< XAccessible > SAL_CALL AccessibleSelectionBase::getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + return OCommonAccessibleSelection::getSelectedAccessibleChild( nSelectedChildIndex ); + } + + + void SAL_CALL AccessibleSelectionBase::deselectAccessibleChild( sal_Int64 nSelectedChildIndex ) + { + ::osl::MutexGuard aGuard( implGetMutex() ); + OCommonAccessibleSelection::deselectAccessibleChild( nSelectedChildIndex ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleStaticTextBase.cxx b/editeng/source/accessibility/AccessibleStaticTextBase.cxx new file mode 100644 index 0000000000..f7fd934dfc --- /dev/null +++ b/editeng/source/accessibility/AccessibleStaticTextBase.cxx @@ -0,0 +1,964 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <utility> +#include <memory> +#include <vector> +#include <algorithm> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/accessibility/AccessibleTextType.hpp> + + +// Project-local header + + +#include <editeng/editdata.hxx> +#include <editeng/unoedprx.hxx> +#include <editeng/AccessibleStaticTextBase.hxx> +#include <editeng/AccessibleEditableTextPara.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +/* TODO: + ===== + + - separate adapter functionality from AccessibleStaticText class + + - refactor common loops into templates, using mem_fun + + */ + +namespace accessibility +{ + typedef std::vector< beans::PropertyValue > PropertyValueVector; + + namespace { + + class PropertyValueEqualFunctor + { + const beans::PropertyValue& m_rPValue; + + public: + explicit PropertyValueEqualFunctor(const beans::PropertyValue& rPValue) + : m_rPValue(rPValue) + {} + bool operator() ( const beans::PropertyValue& rhs ) const + { + return ( m_rPValue.Name == rhs.Name && m_rPValue.Value == rhs.Value ); + } + }; + + } + + sal_Unicode const cNewLine(0x0a); + + + // Static Helper + + + static ESelection MakeSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + DBG_ASSERT(nStartPara >= 0 && + nStartIndex >= 0 && + nEndPara >= 0 && + nEndIndex >= 0, + "AccessibleStaticTextBase_Impl::MakeSelection: index value overflow"); + + return ESelection(nStartPara, nStartIndex, nEndPara, nEndIndex); + } + + + // AccessibleStaticTextBase_Impl declaration + + + /** AccessibleStaticTextBase_Impl + + This class implements the AccessibleStaticTextBase + functionality, mainly by forwarding the calls to an aggregated + AccessibleEditableTextPara. As this is a therefore non-trivial + adapter, factoring out the common functionality from + AccessibleEditableTextPara might be a profitable future task. + */ + class AccessibleStaticTextBase_Impl + { + friend class AccessibleStaticTextBase; + public: + + // receive pointer to our frontend class and view window + AccessibleStaticTextBase_Impl(); + + void SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ); + + void SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { + mpThis = rInterface.get(); + } + + void SetOffset( const Point& ); + + void Dispose(); + + AccessibleEditableTextPara& GetParagraph( sal_Int32 nPara ) const; + sal_Int32 GetParagraphCount() const; + + EPosition Index2Internal( sal_Int32 nFlatIndex ) const + { + + return ImpCalcInternal( nFlatIndex, false ); + } + + EPosition Range2Internal( sal_Int32 nFlatIndex ) const + { + + return ImpCalcInternal( nFlatIndex, true ); + } + + sal_Int32 Internal2Index( EPosition nEEIndex ) const; + + void CorrectTextSegment( TextSegment& aTextSegment, + int nPara ) const; + + bool SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ); + bool CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ); + + tools::Rectangle GetParagraphBoundingBox() const; + bool RemoveLineBreakCount( sal_Int32& rIndex ); + + private: + + EPosition ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const; + + // our frontend class (the one implementing the actual + // interface). That's not necessarily the one containing the impl + // pointer. Note that this is not an uno::Reference to prevent ref-counting cycles and leaks. + XAccessible* mpThis; + + // implements our functionality, we're just an adapter (guarded by solar mutex) + mutable rtl::Reference<AccessibleEditableTextPara> mxTextParagraph; + + // a wrapper for the text forwarders (guarded by solar mutex) + mutable SvxEditSourceAdapter maEditSource; + }; + + + // AccessibleStaticTextBase_Impl implementation + + + AccessibleStaticTextBase_Impl::AccessibleStaticTextBase_Impl() + : mpThis(nullptr) + , mxTextParagraph(new AccessibleEditableTextPara(nullptr)) + { + + // TODO: this is still somewhat of a hack, all the more since + // now the maTextParagraph has an empty parent reference set + } + + void AccessibleStaticTextBase_Impl::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ) + { + + maEditSource.SetEditSource( std::move(pEditSource) ); + if( mxTextParagraph.is() ) + mxTextParagraph->SetEditSource( &maEditSource ); + } + + void AccessibleStaticTextBase_Impl::SetOffset( const Point& rPoint ) + { + if( mxTextParagraph.is() ) + mxTextParagraph->SetEEOffset( rPoint ); + } + + void AccessibleStaticTextBase_Impl::Dispose() + { + + // we're the owner of the paragraph, so destroy it, too + if( mxTextParagraph.is() ) + mxTextParagraph->Dispose(); + + // drop references + mpThis = nullptr; + mxTextParagraph.clear(); + } + + AccessibleEditableTextPara& AccessibleStaticTextBase_Impl::GetParagraph( sal_Int32 nPara ) const + { + + if( !mxTextParagraph.is() ) + throw lang::DisposedException ("object has been already disposed", mpThis ); + + // TODO: Have a different method on AccessibleEditableTextPara + // that does not care about state changes + mxTextParagraph->SetParagraphIndex( nPara ); + + return *mxTextParagraph; + } + + sal_Int32 AccessibleStaticTextBase_Impl::GetParagraphCount() const + { + + if( !mxTextParagraph.is() ) + return 0; + else + return mxTextParagraph->GetTextForwarder().GetParagraphCount(); + } + + sal_Int32 AccessibleStaticTextBase_Impl::Internal2Index( EPosition nEEIndex ) const + { + // XXX checks for overflow and returns maximum if so + sal_Int32 aRes(0); + for(sal_Int32 i=0; i<nEEIndex.nPara; ++i) + { + sal_Int32 nCount = GetParagraph(i).getCharacterCount(); + if (SAL_MAX_INT32 - aRes > nCount) + return SAL_MAX_INT32; + aRes += nCount; + } + + if (SAL_MAX_INT32 - aRes > nEEIndex.nIndex) + return SAL_MAX_INT32; + return aRes + nEEIndex.nIndex; + } + + void AccessibleStaticTextBase_Impl::CorrectTextSegment( TextSegment& aTextSegment, + int nPara ) const + { + // Keep 'invalid' values at the TextSegment + if( aTextSegment.SegmentStart != -1 && + aTextSegment.SegmentEnd != -1 ) + { + // #112814# Correct TextSegment by paragraph offset + sal_Int32 nOffset(0); + int i; + for(i=0; i<nPara; ++i) + nOffset += GetParagraph(i).getCharacterCount(); + + aTextSegment.SegmentStart += nOffset; + aTextSegment.SegmentEnd += nOffset; + } + } + + EPosition AccessibleStaticTextBase_Impl::ImpCalcInternal( sal_Int32 nFlatIndex, bool bExclusive ) const + { + + if( nFlatIndex < 0 ) + throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds", + mpThis); + // gratuitously accepting larger indices here, AccessibleEditableTextPara will throw eventually + + sal_Int32 nCurrPara, nCurrIndex, nParas, nCurrCount; + for( nCurrPara=0, nParas=GetParagraphCount(), nCurrCount=0, nCurrIndex=0; nCurrPara<nParas; ++nCurrPara ) + { + nCurrCount = GetParagraph( nCurrPara ).getCharacterCount(); + nCurrIndex += nCurrCount; + if( nCurrIndex >= nFlatIndex ) + { + // check overflow + DBG_ASSERT(nCurrPara >= 0 && + nFlatIndex - nCurrIndex + nCurrCount >= 0, + "AccessibleStaticTextBase_Impl::Index2Internal: index value overflow"); + + return EPosition(nCurrPara, nFlatIndex - nCurrIndex + nCurrCount); + } + } + + // #102170# Allow one-past the end for ranges + if( bExclusive && nCurrIndex == nFlatIndex ) + { + // check overflow + DBG_ASSERT(nCurrPara > 0 && + nFlatIndex - nCurrIndex + nCurrCount >= 0, + "AccessibleStaticTextBase_Impl::Index2Internal: index value overflow"); + + return EPosition(nCurrPara-1, nFlatIndex - nCurrIndex + nCurrCount); + } + + // not found? Out of bounds + throw lang::IndexOutOfBoundsException("AccessibleStaticTextBase_Impl::Index2Internal: character index out of bounds", + mpThis); + } + + bool AccessibleStaticTextBase_Impl::SetSelection( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + + if( !mxTextParagraph.is() ) + return false; + + try + { + SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true ); + return rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) ); + } + catch( const uno::RuntimeException& ) + { + return false; + } + } + + bool AccessibleStaticTextBase_Impl::CopyText( sal_Int32 nStartPara, sal_Int32 nStartIndex, + sal_Int32 nEndPara, sal_Int32 nEndIndex ) + { + + if( !mxTextParagraph.is() ) + return false; + + try + { + SvxEditViewForwarder& rCacheVF = mxTextParagraph->GetEditViewForwarder( true ); + mxTextParagraph->GetTextForwarder(); // MUST be after GetEditViewForwarder(), see method docs + bool aRetVal; + + // save current selection + ESelection aOldSelection; + + rCacheVF.GetSelection( aOldSelection ); + rCacheVF.SetSelection( MakeSelection(nStartPara, nStartIndex, nEndPara, nEndIndex) ); + aRetVal = rCacheVF.Copy(); + rCacheVF.SetSelection( aOldSelection ); // restore + + return aRetVal; + } + catch( const uno::RuntimeException& ) + { + return false; + } + } + + tools::Rectangle AccessibleStaticTextBase_Impl::GetParagraphBoundingBox() const + { + tools::Rectangle aRect; + if( mxTextParagraph.is() ) + { + awt::Rectangle aAwtRect = mxTextParagraph->getBounds(); + aRect = tools::Rectangle( Point( aAwtRect.X, aAwtRect.Y ), Size( aAwtRect.Width, aAwtRect.Height ) ); + } + else + { + aRect.SetEmpty(); + } + return aRect; + } + //the input argument is the index(including "\n" ) in the string. + //the function will calculate the actual index(not including "\n") in the string. + //and return true if the index is just at a "\n" + bool AccessibleStaticTextBase_Impl::RemoveLineBreakCount( sal_Int32& rIndex ) + { + // get the total char number inside the cell. + sal_Int32 i, nCount, nParas; + for( i=0, nCount=0, nParas=GetParagraphCount(); i<nParas; ++i ) + nCount += GetParagraph(i).getCharacterCount(); + nCount = nCount + (nParas-1); + if( nCount == 0 && rIndex == 0) return false; + + + sal_Int32 nCurrPara, nCurrCount; + sal_Int32 nLineBreakPos = 0, nLineBreakCount = 0; + sal_Int32 nParaCount = GetParagraphCount(); + for ( nCurrCount = 0, nCurrPara = 0; nCurrPara < nParaCount; nCurrPara++ ) + { + nCurrCount += GetParagraph( nCurrPara ).getCharacterCount(); + nLineBreakPos = nCurrCount++; + if ( rIndex == nLineBreakPos ) + { + rIndex -= (++nLineBreakCount);//(++nLineBreakCount); + if ( rIndex < 0) + { + rIndex = 0; + } + //if the index is at the last position of the last paragraph + //there is no "\n" , so we should increase rIndex by 1 and return false. + if ( (nCurrPara+1) == nParaCount ) + { + rIndex++; + return false; + } + else + { + return true; + } + } + else if ( rIndex < nLineBreakPos ) + { + rIndex -= nLineBreakCount; + return false; + } + else + { + nLineBreakCount++; + } + } + return false; + } + + + // AccessibleStaticTextBase implementation + + AccessibleStaticTextBase::AccessibleStaticTextBase( std::unique_ptr< SvxEditSource > && pEditSource ) : + mpImpl( new AccessibleStaticTextBase_Impl() ) + { + SolarMutexGuard aGuard; + + SetEditSource( std::move(pEditSource) ); + } + + AccessibleStaticTextBase::~AccessibleStaticTextBase() + { + } + + void AccessibleStaticTextBase::SetEditSource( std::unique_ptr< SvxEditSource > && pEditSource ) + { + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->SetEditSource( std::move(pEditSource) ); + } + + void AccessibleStaticTextBase::SetEventSource( const uno::Reference< XAccessible >& rInterface ) + { + mpImpl->SetEventSource( rInterface ); + + } + + void AccessibleStaticTextBase::SetOffset( const Point& rPoint ) + { + // precondition: solar mutex locked + DBG_TESTSOLARMUTEX(); + + mpImpl->SetOffset( rPoint ); + } + + void AccessibleStaticTextBase::Dispose() + { + mpImpl->Dispose(); + + } + + // XAccessibleContext + sal_Int64 AccessibleStaticTextBase::getAccessibleChildCount() + { + // no children at all + return 0; + } + + uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleChild( sal_Int64 /*i*/ ) + { + // no children at all + return uno::Reference< XAccessible >(); + } + + uno::Reference< XAccessible > AccessibleStaticTextBase::getAccessibleAtPoint( const awt::Point& /*_aPoint*/ ) + { + // no children at all + return uno::Reference< XAccessible >(); + } + + // XAccessibleText + sal_Int32 SAL_CALL AccessibleStaticTextBase::getCaretPosition() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getCaretPosition()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::setCaretPosition( sal_Int32 nIndex ) + { + return setSelection(nIndex, nIndex); + } + + sal_Unicode SAL_CALL AccessibleStaticTextBase::getCharacter( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + EPosition aPos( mpImpl->Index2Internal(nIndex) ); + + return mpImpl->GetParagraph( aPos.nPara ).getCharacter( aPos.nIndex ); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getCharacterAttributes( sal_Int32 nIndex, const css::uno::Sequence< OUString >& aRequestedAttributes ) + { + SolarMutexGuard aGuard; + + //get the actual index without "\n" + mpImpl->RemoveLineBreakCount( nIndex ); + + EPosition aPos( mpImpl->Index2Internal(nIndex) ); + + return mpImpl->GetParagraph( aPos.nPara ).getCharacterAttributes( aPos.nIndex, aRequestedAttributes ); + } + + awt::Rectangle SAL_CALL AccessibleStaticTextBase::getCharacterBounds( sal_Int32 nIndex ) + { + SolarMutexGuard aGuard; + + // #108900# Allow ranges for nIndex, as one-past-the-end + // values are now legal, too. + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + // #i70916# Text in spread sheet cells return the wrong extents + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara ); + awt::Rectangle aParaBounds( rPara.getBounds() ); + awt::Rectangle aBounds( rPara.getCharacterBounds( aPos.nIndex ) ); + aBounds.X += aParaBounds.X; + aBounds.Y += aParaBounds.Y; + + return aBounds; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getCharacterCount() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nCount, nParas; + for( i=0, nCount=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + nCount += mpImpl->GetParagraph(i).getCharacterCount(); + //count on the number of "\n" which equals number of paragraphs decrease 1. + nCount = nCount + (nParas-1); + return nCount; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getIndexAtPoint( const awt::Point& rPoint ) + { + SolarMutexGuard aGuard; + + const sal_Int32 nParas( mpImpl->GetParagraphCount() ); + sal_Int32 nIndex; + int i; + for( i=0; i<nParas; ++i ) + { + // TODO: maybe exploit the fact that paragraphs are + // ordered vertically for early exit + + // #i70916# Text in spread sheet cells return the wrong extents + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( i ); + awt::Rectangle aParaBounds( rPara.getBounds() ); + awt::Point aPoint( rPoint ); + aPoint.X -= aParaBounds.X; + aPoint.Y -= aParaBounds.Y; + + // #112814# Use correct index offset + if ( ( nIndex = rPara.getIndexAtPoint( aPoint ) ) != -1 ) + return mpImpl->Internal2Index(EPosition(i, nIndex)); + } + + return -1; + } + + OUString SAL_CALL AccessibleStaticTextBase::getSelectedText() + { + SolarMutexGuard aGuard; + + sal_Int32 nStart( getSelectionStart() ); + sal_Int32 nEnd( getSelectionEnd() ); + + // #104481# Return the empty string for 'no selection' + if( nStart < 0 || nEnd < 0 ) + return OUString(); + + return getTextRange( nStart, nEnd ); + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionStart() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getSelectionStart()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Int32 SAL_CALL AccessibleStaticTextBase::getSelectionEnd() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nPos, nParas; + for( i=0, nPos=-1, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + { + if( (nPos=mpImpl->GetParagraph(i).getSelectionEnd()) != -1 ) + return nPos; + } + + return nPos; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::setSelection( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + return mpImpl->SetSelection( aStartIndex.nPara, aStartIndex.nIndex, + aEndIndex.nPara, aEndIndex.nIndex ); + } + + OUString SAL_CALL AccessibleStaticTextBase::getText() + { + SolarMutexGuard aGuard; + + sal_Int32 i, nParas; + OUStringBuffer aRes; + for( i=0, nParas=mpImpl->GetParagraphCount(); i<nParas; ++i ) + aRes.append(mpImpl->GetParagraph(i).getText()); + + return aRes.makeStringAndClear(); + } + + OUString SAL_CALL AccessibleStaticTextBase::getTextRange( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + if( nStartIndex > nEndIndex ) + std::swap(nStartIndex, nEndIndex); + //if startindex equals endindex we will get nothing. So return an empty string directly. + if ( nStartIndex == nEndIndex ) + { + return OUString(); + } + bool bStart = mpImpl->RemoveLineBreakCount( nStartIndex ); + //if the start index is just at a "\n", we need to begin from the next char + if ( bStart ) + { + nStartIndex++; + } + //we need to find out whether the previous position of the current endindex is at "\n" or not + //if yes we need to mark it and add "\n" at the end of the result + sal_Int32 nTemp = nEndIndex - 1; + bool bEnd = mpImpl->RemoveLineBreakCount( nTemp ); + bool bTemp = mpImpl->RemoveLineBreakCount( nEndIndex ); + //if the below condition is true it indicates an empty paragraph with just a "\n" + //so we need to set one "\n" flag to avoid duplication. + if ( bStart && bEnd && ( nStartIndex == nEndIndex) ) + { + bEnd = false; + } + //if the current endindex is at a "\n", we need to increase endindex by 1 to make sure + //the char before "\n" is included. Because string returned by this function will not include + //the char at the endindex. + if ( bTemp ) + { + nEndIndex++; + } + OUStringBuffer aRes; + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + // #102170# Special case: start and end paragraph are identical + if( aStartIndex.nPara == aEndIndex.nPara ) + { + //we don't return the string directly now for that we have to do some further process for "\n" + aRes = mpImpl->GetParagraph( aStartIndex.nPara ).getTextRange( aStartIndex.nIndex, aEndIndex.nIndex ); + } + else + { + sal_Int32 i( aStartIndex.nPara ); + aRes = mpImpl->GetParagraph(i).getTextRange( aStartIndex.nIndex, + mpImpl->GetParagraph(i).getCharacterCount()/*-1*/); + ++i; + + // paragraphs inbetween are fully included + for( ; i<aEndIndex.nPara; ++i ) + { + aRes.append(OUStringChar(cNewLine) + mpImpl->GetParagraph(i).getText()); + } + + if( i<=aEndIndex.nPara ) + { + //if the below condition is matched it means that endindex is at mid of the last paragraph + //we need to add a "\n" before we add the last part of the string. + if ( !bEnd && aEndIndex.nIndex ) + { + aRes.append(cNewLine); + } + aRes.append(mpImpl->GetParagraph(i).getTextRange( 0, aEndIndex.nIndex )); + } + } + //According to the flag we marked before, we have to add "\n" at the beginning + //or at the end of the result string. + if ( bStart ) + { + aRes.insert(0, OUStringChar(cNewLine)); + } + if ( bEnd ) + { + aRes.append(OUStringChar(cNewLine)); + } + return aRes.makeStringAndClear(); + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextAtIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + // #106393# Special casing one behind last paragraph is + // not necessary, since then, we return the content and + // boundary of that last paragraph. Range2Internal is + // tolerant against that, and returns the last paragraph + // in aPos.nPara. + + // retrieve full text of the paragraph + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) ); + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + else if ( AccessibleTextType::ATTRIBUTE_RUN == aTextType ) + { + SvxAccessibleTextAdapter& rTextForwarder = mpImpl->GetParagraph( aPos.nIndex ).GetTextForwarder(); + sal_Int32 nStartIndex, nEndIndex; + if ( rTextForwarder.GetAttributeRun( nStartIndex, nEndIndex, aPos.nPara, aPos.nIndex, true ) ) + { + aResult.SegmentText = getTextRange( nStartIndex, nEndIndex ); + aResult.SegmentStart = nStartIndex; + aResult.SegmentEnd = nEndIndex; + } + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextAtIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak ) + { + aResult.SegmentText = OUString(cNewLine); + } + } + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBeforeIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + sal_Int32 nOldIdx = nIndex; + bool bLineBreak = mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + if( aPos.nIndex == mpImpl->GetParagraph( aPos.nPara ).getCharacterCount() ) + { + // #103589# Special casing one behind the last paragraph + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara, 0 ) ); + } + else if( aPos.nPara > 0 ) + { + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara - 1 ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara - 1, 0 ) ); + } + + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBeforeIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak && (nOldIdx-1) >= 0) + { + aResult = getTextAtIndex( nOldIdx-1, aTextType ); + } + } + + return aResult; + } + + css::accessibility::TextSegment SAL_CALL AccessibleStaticTextBase::getTextBehindIndex( sal_Int32 nIndex, sal_Int16 aTextType ) + { + SolarMutexGuard aGuard; + + sal_Int32 nTemp = nIndex+1; + bool bLineBreak = mpImpl->RemoveLineBreakCount( nTemp ); + mpImpl->RemoveLineBreakCount( nIndex ); + EPosition aPos( mpImpl->Range2Internal(nIndex) ); + + css::accessibility::TextSegment aResult; + + if( AccessibleTextType::PARAGRAPH == aTextType ) + { + // Special casing one behind the last paragraph is not + // necessary, this case is invalid here for + // getTextBehindIndex + if( aPos.nPara + 1 < mpImpl->GetParagraphCount() ) + { + aResult.SegmentText = mpImpl->GetParagraph( aPos.nPara + 1 ).getText(); + + // #112814# Adapt the start index with the paragraph offset + aResult.SegmentStart = mpImpl->Internal2Index( EPosition( aPos.nPara + 1, 0 ) ); + aResult.SegmentEnd = aResult.SegmentStart + aResult.SegmentText.getLength(); + } + } + else + { + // No special handling required, forward to wrapped class + aResult = mpImpl->GetParagraph( aPos.nPara ).getTextBehindIndex( aPos.nIndex, aTextType ); + + // #112814# Adapt the start index with the paragraph offset + mpImpl->CorrectTextSegment( aResult, aPos.nPara ); + if ( bLineBreak ) + { + aResult.SegmentText = OUStringChar(cNewLine) + aResult.SegmentText; + } + } + + return aResult; + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::copyText( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) + { + SolarMutexGuard aGuard; + + if( nStartIndex > nEndIndex ) + std::swap(nStartIndex, nEndIndex); + + EPosition aStartIndex( mpImpl->Range2Internal(nStartIndex) ); + EPosition aEndIndex( mpImpl->Range2Internal(nEndIndex) ); + + return mpImpl->CopyText( aStartIndex.nPara, aStartIndex.nIndex, + aEndIndex.nPara, aEndIndex.nIndex ); + } + + sal_Bool SAL_CALL AccessibleStaticTextBase::scrollSubstringTo( sal_Int32, sal_Int32, AccessibleScrollType ) + { + return false; + } + + // XAccessibleTextAttributes + uno::Sequence< beans::PropertyValue > AccessibleStaticTextBase::getDefaultAttributes( const uno::Sequence< OUString >& RequestedAttributes ) + { + // get the intersection of the default attributes of all paragraphs + + SolarMutexGuard aGuard; + + PropertyValueVector aDefAttrVec( + comphelper::sequenceToContainer<PropertyValueVector>(mpImpl->GetParagraph( 0 ).getDefaultAttributes( RequestedAttributes )) ); + + const sal_Int32 nParaCount = mpImpl->GetParagraphCount(); + for ( sal_Int32 nPara = 1; nPara < nParaCount; ++nPara ) + { + uno::Sequence< beans::PropertyValue > aSeq = mpImpl->GetParagraph( nPara ).getDefaultAttributes( RequestedAttributes ); + PropertyValueVector aIntersectionVec; + + for ( const auto& rDefAttr : aDefAttrVec ) + { + const beans::PropertyValue* pItr = aSeq.getConstArray(); + const beans::PropertyValue* pEnd = pItr + aSeq.getLength(); + const beans::PropertyValue* pFind = std::find_if( pItr, pEnd, PropertyValueEqualFunctor(rDefAttr) ); + if ( pFind != pEnd ) + { + aIntersectionVec.push_back( *pFind ); + } + } + + aDefAttrVec.swap( aIntersectionVec ); + + if ( aDefAttrVec.empty() ) + { + break; + } + } + + return comphelper::containerToSequence(aDefAttrVec); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL AccessibleStaticTextBase::getRunAttributes( sal_Int32 nIndex, const uno::Sequence< OUString >& RequestedAttributes ) + { + // get those default attributes of the paragraph, which are not part + // of the intersection of all paragraphs and add them to the run attributes + + SolarMutexGuard aGuard; + + EPosition aPos( mpImpl->Index2Internal( nIndex ) ); + AccessibleEditableTextPara& rPara = mpImpl->GetParagraph( aPos.nPara ); + uno::Sequence< beans::PropertyValue > aDefAttrSeq = rPara.getDefaultAttributes( RequestedAttributes ); + uno::Sequence< beans::PropertyValue > aRunAttrSeq = rPara.getRunAttributes( aPos.nIndex, RequestedAttributes ); + uno::Sequence< beans::PropertyValue > aIntersectionSeq = getDefaultAttributes( RequestedAttributes ); + PropertyValueVector aDiffVec; + + const beans::PropertyValue* pDefAttr = aDefAttrSeq.getConstArray(); + const sal_Int32 nLength = aDefAttrSeq.getLength(); + for ( sal_Int32 i = 0; i < nLength; ++i ) + { + const beans::PropertyValue* pItr = aIntersectionSeq.getConstArray(); + const beans::PropertyValue* pEnd = pItr + aIntersectionSeq.getLength(); + bool bNone = std::none_of( pItr, pEnd, PropertyValueEqualFunctor( pDefAttr[i] ) ); + if ( bNone && pDefAttr[i].Handle != 0) + { + aDiffVec.push_back( pDefAttr[i] ); + } + } + + return ::comphelper::concatSequences( aRunAttrSeq, comphelper::containerToSequence(aDiffVec) ); + } + + tools::Rectangle AccessibleStaticTextBase::GetParagraphBoundingBox() const + { + return mpImpl->GetParagraphBoundingBox(); + } + +} // end of namespace accessibility + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/accessibility/AccessibleStringWrap.cxx b/editeng/source/accessibility/AccessibleStringWrap.cxx new file mode 100644 index 0000000000..5461aad9f4 --- /dev/null +++ b/editeng/source/accessibility/AccessibleStringWrap.cxx @@ -0,0 +1,90 @@ +/* -*- 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 <algorithm> +#include <cstdlib> + +#include <tools/debug.hxx> +#include <utility> +#include <vcl/outdev.hxx> + +#include <editeng/svxfont.hxx> +#include <AccessibleStringWrap.hxx> + + +// AccessibleStringWrap implementation + + +AccessibleStringWrap::AccessibleStringWrap( OutputDevice& rDev, SvxFont& rFont, OUString aText ) : + mrDev( rDev ), + mrFont( rFont ), + maText(std::move( aText )) +{ +} + +void AccessibleStringWrap::GetCharacterBounds( sal_Int32 nIndex, tools::Rectangle& rRect ) +{ + DBG_ASSERT(nIndex >= 0, + "SvxAccessibleStringWrap::GetCharacterBounds: index value overflow"); + + mrFont.SetPhysFont(mrDev); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= maText.getLength() ) + { + // create a caret bounding rect that has the height of the + // current font and is one pixel wide. + rRect.SetLeft( mrDev.GetTextWidth(maText) ); + rRect.SetTop( 0 ); + rRect.SetSize( Size(mrDev.GetTextHeight(), 1) ); + } + else + { + KernArray aDXArray; + mrDev.GetTextArray(maText, &aDXArray, nIndex, 1); + rRect.SetLeft( 0 ); + rRect.SetTop( 0 ); + rRect.SetSize(Size(mrDev.GetTextHeight(), aDXArray[0])); + } + + if( mrFont.IsVertical() ) + { + // #101701# Rotate to vertical + rRect = tools::Rectangle( Point(-rRect.Top(), rRect.Left()), + Point(-rRect.Bottom(), rRect.Right())); + } +} + +sal_Int32 AccessibleStringWrap::GetIndexAtPoint( const Point& rPoint ) +{ + // search for character bounding box containing given point + tools::Rectangle aRect; + sal_Int32 i, nLen = maText.getLength(); + for( i=0; i<nLen; ++i ) + { + GetCharacterBounds(i, aRect); + if( aRect.Contains(rPoint) ) + return i; + } + + return -1; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editattr.cxx b/editeng/source/editeng/editattr.cxx new file mode 100644 index 0000000000..75bbcabc5a --- /dev/null +++ b/editeng/source/editeng/editattr.cxx @@ -0,0 +1,459 @@ +/* -*- 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 <vcl/outdev.hxx> + +#include <svl/grabbagitem.hxx> +#include <svl/voiditem.hxx> +#include <libxml/xmlwriter.h> +#include <editeng/svxfont.hxx> +#include <editeng/flditem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/cmapitem.hxx> + +#include <editattr.hxx> + + +EditCharAttrib::EditCharAttrib(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nS, sal_Int32 nE ) +: maItemHolder(rPool, &rItem) +, nStart(nS) +, nEnd(nE) +, bFeature(false) +, bEdge(false) +{ + assert((rItem.Which() >= EE_ITEMS_START) && (rItem.Which() <= EE_ITEMS_END)); + assert((rItem.Which() < EE_FEATURE_START) || (rItem.Which() > EE_FEATURE_END) || (nE == (nS+1))); +} + +EditCharAttrib::~EditCharAttrib() +{ +} + +void EditCharAttrib::SetFont( SvxFont&, OutputDevice* ) +{ +} + +void EditCharAttrib::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditCharAttrib")); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("nStart"), "%" SAL_PRIdINT32, nStart); + (void)xmlTextWriterWriteFormatAttribute( + pWriter, BAD_CAST("nEnd"), "%" SAL_PRIdINT32, nEnd); + GetItem()->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + + +EditCharAttribFont::EditCharAttribFont(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_FONTINFO || rItem.Which() == EE_CHAR_FONTINFO_CJK || rItem.Which() == EE_CHAR_FONTINFO_CTL); +} + +void EditCharAttribFont::SetFont( SvxFont& rFont, OutputDevice* ) +{ + const SvxFontItem& rAttr = static_cast<const SvxFontItem&>(*GetItem()); + + rFont.SetFamilyName( rAttr.GetFamilyName() ); + rFont.SetFamily( rAttr.GetFamily() ); + rFont.SetPitch( rAttr.GetPitch() ); + rFont.SetCharSet( rAttr.GetCharSet() ); +} + + + +EditCharAttribItalic::EditCharAttribItalic(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_ITALIC || rItem.Which() == EE_CHAR_ITALIC_CJK || rItem.Which() == EE_CHAR_ITALIC_CTL); +} + +void EditCharAttribItalic::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetItalic( static_cast<const SvxPostureItem*>(GetItem())->GetPosture() ); +} + + + +EditCharAttribWeight::EditCharAttribWeight(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_WEIGHT || rItem.Which() == EE_CHAR_WEIGHT_CJK || rItem.Which() == EE_CHAR_WEIGHT_CTL); +} + +void EditCharAttribWeight::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetWeight( static_cast<const SvxWeightItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribUnderline::EditCharAttribUnderline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_UNDERLINE); +} + +void EditCharAttribUnderline::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + rFont.SetUnderline( static_cast<const SvxUnderlineItem*>(GetItem())->GetValue() ); + + if ( pOutDev ) + pOutDev->SetTextLineColor( static_cast<const SvxUnderlineItem*>(GetItem())->GetColor() ); + +} + + + +EditCharAttribOverline::EditCharAttribOverline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_OVERLINE); +} + +void EditCharAttribOverline::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + rFont.SetOverline( static_cast<const SvxOverlineItem*>(GetItem())->GetValue() ); + if ( pOutDev ) + pOutDev->SetOverlineColor( static_cast<const SvxOverlineItem*>(GetItem())->GetColor() ); +} + + + +EditCharAttribFontHeight::EditCharAttribFontHeight(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_FONTHEIGHT || rItem.Which() == EE_CHAR_FONTHEIGHT_CJK || rItem.Which() == EE_CHAR_FONTHEIGHT_CTL); +} + +void EditCharAttribFontHeight::SetFont( SvxFont& rFont, OutputDevice* ) +{ + // Property is ignored + rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem*>(GetItem())->GetHeight() ) ); +} + + + +EditCharAttribFontWidth::EditCharAttribFontWidth(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_FONTWIDTH); +} + +void EditCharAttribFontWidth::SetFont( SvxFont& /*rFont*/, OutputDevice* ) +{ + // must be calculated outside, because f(device)... +} + + + +EditCharAttribStrikeout::EditCharAttribStrikeout(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_STRIKEOUT); +} + +void EditCharAttribStrikeout::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetStrikeout( static_cast<const SvxCrossedOutItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribCaseMap::EditCharAttribCaseMap(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_CASEMAP); +} + +void EditCharAttribCaseMap::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetCaseMap( static_cast<const SvxCaseMapItem*>(GetItem())->GetCaseMap() ); +} + + + +EditCharAttribColor::EditCharAttribColor(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_COLOR); +} + +void EditCharAttribColor::SetFont( SvxFont& rFont, OutputDevice* ) +{ + Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue(); + rFont.SetColor( aColor); +} + + +EditCharAttribBackgroundColor::EditCharAttribBackgroundColor(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_BKGCOLOR); +} + +void EditCharAttribBackgroundColor::SetFont( SvxFont& rFont, OutputDevice* ) +{ + Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue(); + rFont.SetTransparent(aColor.IsTransparent()); + rFont.SetFillColor(aColor); +} + +EditCharAttribLanguage::EditCharAttribLanguage(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert((rItem.Which() == EE_CHAR_LANGUAGE) || (rItem.Which() == EE_CHAR_LANGUAGE_CJK) || (rItem.Which() == EE_CHAR_LANGUAGE_CTL)); +} + +void EditCharAttribLanguage::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetLanguage( static_cast<const SvxLanguageItem*>(GetItem())->GetLanguage() ); +} + + + +EditCharAttribShadow::EditCharAttribShadow(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_SHADOW); +} + +void EditCharAttribShadow::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetShadow( static_cast<const SvxShadowedItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribEscapement::EditCharAttribEscapement(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_ESCAPEMENT); +} + +void EditCharAttribEscapement::SetFont( SvxFont& rFont, OutputDevice* pOutDev ) +{ + sal_uInt16 const nProp = static_cast<const SvxEscapementItem*>(GetItem())->GetProportionalHeight(); + rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); + + short nEsc = static_cast<const SvxEscapementItem*>(GetItem())->GetEsc(); + rFont.SetNonAutoEscapement( nEsc, pOutDev ); +} + + + +EditCharAttribOutline::EditCharAttribOutline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_OUTLINE); +} + +void EditCharAttribOutline::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetOutline( static_cast<const SvxContourItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribTab::EditCharAttribTab(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos) +: EditCharAttrib(rPool, rItem, nPos, nPos+1) +{ + SetFeature( true ); +} + +void EditCharAttribTab::SetFont( SvxFont&, OutputDevice* ) +{ +} + + + +EditCharAttribLineBreak::EditCharAttribLineBreak(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos) +: EditCharAttrib(rPool, rItem, nPos, nPos+1) +{ + SetFeature( true ); +} + +void EditCharAttribLineBreak::SetFont( SvxFont&, OutputDevice* ) +{ +} + + + +EditCharAttribField::EditCharAttribField(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos) +: EditCharAttrib(rPool, rItem, nPos, nPos+1) +{ + SetFeature( true ); // !!! +} + +void EditCharAttribField::SetFont( SvxFont& rFont, OutputDevice* ) +{ + if ( mxFldColor ) + { + rFont.SetFillColor( *mxFldColor ); + rFont.SetTransparent( false ); + } + if ( mxTxtColor ) + rFont.SetColor( *mxTxtColor ); + if ( mxFldLineStyle ) + rFont.SetUnderline( *mxFldLineStyle ); +} + + +void EditCharAttribField::SetFieldValue(const OUString& rVal) +{ + aFieldValue = rVal; +} + +void EditCharAttribField::Reset() +{ + aFieldValue.clear(); + mxTxtColor.reset(); + mxFldColor.reset(); + mxFldLineStyle.reset(); +} + +EditCharAttribField::EditCharAttribField(const EditCharAttribField& rAttr) +: EditCharAttrib(rAttr.GetHolder().getPool(), *rAttr.GetHolder().getItem(), rAttr.GetStart(), rAttr.GetEnd()) +, aFieldValue( rAttr.aFieldValue ) +{ + // Use this constructor only for temporary Objects, Item is not pooled. + mxTxtColor = rAttr.mxTxtColor; + mxFldColor = rAttr.mxFldColor; + mxFldLineStyle = rAttr.mxFldLineStyle; +} + +EditCharAttribField::~EditCharAttribField() +{ + Reset(); +} + +bool EditCharAttribField::operator == ( const EditCharAttribField& rAttr ) const +{ + if ( aFieldValue != rAttr.aFieldValue ) + return false; + + if ( ( mxTxtColor && !rAttr.mxTxtColor ) || ( !mxTxtColor && rAttr.mxTxtColor ) ) + return false; + if ( ( mxTxtColor && rAttr.mxTxtColor ) && ( *mxTxtColor != *rAttr.mxTxtColor ) ) + return false; + + if ( ( mxFldColor && !rAttr.mxFldColor ) || ( !mxFldColor && rAttr.mxFldColor ) ) + return false; + if ( ( mxFldColor && rAttr.mxFldColor ) && ( *mxFldColor != *rAttr.mxFldColor ) ) + return false; + + if ( ( mxFldLineStyle && !rAttr.mxFldLineStyle ) || ( !mxFldLineStyle && rAttr.mxFldLineStyle ) ) + return false; + if ( ( mxFldLineStyle && rAttr.mxFldLineStyle ) && ( *mxFldLineStyle != *rAttr.mxFldLineStyle ) ) + return false; + + return true; +} + + + +EditCharAttribPairKerning::EditCharAttribPairKerning(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_PAIRKERNING); +} + +void EditCharAttribPairKerning::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetKerning( static_cast<const SvxAutoKernItem*>(GetItem())->GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); +} + + + +EditCharAttribKerning::EditCharAttribKerning(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_KERNING); +} + +void EditCharAttribKerning::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetFixKerning( static_cast<const SvxKerningItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribWordLineMode::EditCharAttribWordLineMode(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_WLM); +} + +void EditCharAttribWordLineMode::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetWordLineMode( static_cast<const SvxWordLineModeItem*>(GetItem())->GetValue() ); +} + + + +EditCharAttribEmphasisMark::EditCharAttribEmphasisMark(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_EMPHASISMARK); +} + +void EditCharAttribEmphasisMark::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetEmphasisMark( static_cast<const SvxEmphasisMarkItem*>(GetItem())->GetEmphasisMark() ); +} + + + +EditCharAttribRelief::EditCharAttribRelief(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_RELIEF); +} + +void EditCharAttribRelief::SetFont( SvxFont& rFont, OutputDevice* ) +{ + rFont.SetRelief( static_cast<const SvxCharReliefItem*>(GetItem())->GetValue() ); +} + + +EditCharAttribGrabBag::EditCharAttribGrabBag(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd) +: EditCharAttrib(rPool, rItem, _nStart, _nEnd) +{ + assert(rItem.Which() == EE_CHAR_GRABBAG); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdata.cxx b/editeng/source/editeng/editdata.cxx new file mode 100644 index 0000000000..f272a9afd5 --- /dev/null +++ b/editeng/source/editeng/editdata.cxx @@ -0,0 +1,16 @@ +/* -*- 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/. + */ + +#include <editeng/editdata.hxx> + +#include <limits> + +const size_t EE_APPEND = std::numeric_limits<size_t>::max(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdbg.cxx b/editeng/source/editeng/editdbg.cxx new file mode 100644 index 0000000000..31ec9d0930 --- /dev/null +++ b/editeng/source/editeng/editdbg.cxx @@ -0,0 +1,531 @@ +/* -*- 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 <memory> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <editeng/lspcitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> + +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editdoc.hxx> + +#include <rtl/strbuf.hxx> +#include <osl/diagnose.h> + +#if defined( DBG_UTIL ) || ( OSL_DEBUG_LEVEL > 1 ) + +namespace +{ +struct DebOutBuffer +{ + OStringBuffer str; + void append(std::string_view descr, const SfxEnumItemInterface& rItem) + { + str.append(descr + OString::number(rItem.GetEnumValue())); + } + void append(std::string_view descr, const SvxLRSpaceItem& rItem) + { + str.append(OString::Concat(descr) + "FI=" + OString::number(rItem.GetTextFirstLineOffset()) + + ", LI=" + OString::number(rItem.GetTextLeft()) + + ", RI=" + OString::number(rItem.GetRight())); + } + void append(std::string_view descr, const SvxNumBulletItem& rItem) + { + str.append(descr); + for (sal_uInt16 nLevel = 0; nLevel < 3; nLevel++) + { + str.append("Level" + OString::number(nLevel) + "="); + const SvxNumberFormat* pFmt = rItem.GetNumRule().Get(nLevel); + if (pFmt) + { + str.append("(" + OString::number(pFmt->GetFirstLineOffset()) + "," + + OString::number(pFmt->GetAbsLSpace()) + ","); + if (pFmt->GetNumberingType() == SVX_NUM_BITMAP) + str.append("Bitmap"); + else if (pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL) + str.append("Number"); + else + { + str.append("Char=[" + OString::number(pFmt->GetBulletChar()) + "]"); + } + str.append(") "); + } + } + } + void append(std::string_view descr, const SfxBoolItem& rItem) + { + str.append(descr + OString::number(static_cast<int>(rItem.GetValue()))); + } + void append(std::string_view descr, const SfxInt16Item& rItem) + { + str.append(descr + OString::number(rItem.GetValue())); + } + void append(std::string_view descr, const SfxUInt16Item& rItem) + { + str.append(descr + OString::number(rItem.GetValue())); + } + void append(const SvxULSpaceItem& rItem) + { + str.append("SB=" + OString::number(rItem.GetUpper()) + + ", SA=" + OString::number(rItem.GetLower())); + } + void append(std::string_view descr, const SvxLineSpacingItem& rItem) + { + str.append(descr); + if (rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min) + { + str.append("Min: " + OString::number(rItem.GetInterLineSpace())); + } + else if (rItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop) + { + str.append("Prop: " + OString::number(rItem.GetPropLineSpace())); + } + else + str.append("Unsupported Type!"); + } + void append(const SvxTabStopItem& rTabs) + { + str.append("Tabs: " + OString::number(rTabs.Count())); + if (rTabs.Count()) + { + str.append("( "); + for (sal_uInt16 i = 0; i < rTabs.Count(); ++i) + { + const SvxTabStop& rTab = rTabs[i]; + str.append(OString::number(rTab.GetTabPos()) + " "); + } + str.append(')'); + } + } + void append(std::string_view descr, const SvxColorItem& rItem) + { + Color aColor(rItem.GetValue()); + str.append(descr + OString::number(aColor.GetRed()) + ", " + + OString::number(aColor.GetGreen()) + ", " + OString::number(aColor.GetBlue())); + } + void append(std::string_view descr, const SvxFontItem& rItem) + { + str.append(descr + OUStringToOString(rItem.GetFamilyName(), RTL_TEXTENCODING_ASCII_US) + + " (CharSet: " + OString::number(rItem.GetCharSet()) + ")"); + } + void append(std::string_view descr, const SvxEscapementItem& rItem) + { + str.append(descr + OString::number(rItem.GetEsc()) + ", " + + OString::number(rItem.GetProportionalHeight())); + } + void appendHeightAndPts(std::string_view descr, tools::Long h, MapUnit eUnit) + { + MapMode aItemMapMode(eUnit); + MapMode aPntMap(MapUnit::MapPoint); + Size aSz = OutputDevice::LogicToLogic(Size(0, h), aItemMapMode, aPntMap); + str.append(descr + OString::number(h) + " Points=" + OString::number(aSz.Height())); + } + void append(std::string_view descr, const SvxFontHeightItem& rItem, const SfxItemPool& rPool) + { + appendHeightAndPts(descr, rItem.GetHeight(), rPool.GetMetric(rItem.Which())); + } + void append(std::string_view descr, const SvxKerningItem& rItem, const SfxItemPool& rPool) + { + appendHeightAndPts(descr, rItem.GetValue(), rPool.GetMetric(rItem.Which())); + } +}; +} + +static OString DbgOutItem(const SfxItemPool& rPool, const SfxPoolItem& rItem) +{ + DebOutBuffer buffer; + switch ( rItem.Which() ) + { + case EE_PARA_WRITINGDIR: + buffer.append("WritingDir=", rItem.StaticWhichCast(EE_PARA_WRITINGDIR)); + break; + case EE_PARA_OUTLLRSPACE: + buffer.append("Outline ", rItem.StaticWhichCast(EE_PARA_OUTLLRSPACE)); + break; + case EE_PARA_LRSPACE: + buffer.append("", rItem.StaticWhichCast(EE_PARA_LRSPACE)); + break; + case EE_PARA_NUMBULLET: + buffer.append("NumItem ", rItem.StaticWhichCast(EE_PARA_NUMBULLET)); + break; + case EE_PARA_BULLETSTATE: + buffer.append("ShowBullet=", rItem.StaticWhichCast(EE_PARA_BULLETSTATE)); + break; + case EE_PARA_HYPHENATE: + buffer.append("Hyphenate=", rItem.StaticWhichCast(EE_PARA_HYPHENATE)); + break; + case EE_PARA_OUTLLEVEL: + buffer.append("Level=", rItem.StaticWhichCast(EE_PARA_OUTLLEVEL)); + break; + case EE_PARA_ULSPACE: + buffer.append(rItem.StaticWhichCast(EE_PARA_ULSPACE)); + break; + case EE_PARA_SBL: + buffer.append("SBL=", rItem.StaticWhichCast(EE_PARA_SBL)); + break; + case EE_PARA_JUST: + buffer.append("SvxAdust=", rItem.StaticWhichCast(EE_PARA_JUST)); + break; + case EE_PARA_TABS: + buffer.append(rItem.StaticWhichCast(EE_PARA_TABS)); + break; + case EE_CHAR_LANGUAGE: + buffer.append("Language=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE)); + break; + case EE_CHAR_LANGUAGE_CJK: + buffer.append("LanguageCJK=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE_CJK)); + break; + case EE_CHAR_LANGUAGE_CTL: + buffer.append("LanguageCTL=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE_CTL)); + break; + case EE_CHAR_COLOR: + buffer.append("Color= ", rItem.StaticWhichCast(EE_CHAR_COLOR)); + break; + case EE_CHAR_BKGCOLOR: + buffer.append("FillColor= ", rItem.StaticWhichCast(EE_CHAR_BKGCOLOR)); + break; + case EE_CHAR_FONTINFO: + buffer.append("Font=", rItem.StaticWhichCast(EE_CHAR_FONTINFO)); + break; + case EE_CHAR_FONTINFO_CJK: + buffer.append("FontCJK=", rItem.StaticWhichCast(EE_CHAR_FONTINFO_CJK)); + break; + case EE_CHAR_FONTINFO_CTL: + buffer.append("FontCTL=", rItem.StaticWhichCast(EE_CHAR_FONTINFO_CTL)); + break; + case EE_CHAR_FONTHEIGHT: + buffer.append("Size=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT), rPool); + break; + case EE_CHAR_FONTHEIGHT_CJK: + buffer.append("SizeCJK=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT_CJK), rPool); + break; + case EE_CHAR_FONTHEIGHT_CTL: + buffer.append("SizeCTL=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT_CTL), rPool); + break; + case EE_CHAR_FONTWIDTH: + buffer.append("Width=", rItem.StaticWhichCast(EE_CHAR_FONTWIDTH)); + break; + case EE_CHAR_WEIGHT: + buffer.append("FontWeight=", rItem.StaticWhichCast(EE_CHAR_WEIGHT)); + break; + case EE_CHAR_WEIGHT_CJK: + buffer.append("FontWeightCJK=", rItem.StaticWhichCast(EE_CHAR_WEIGHT_CJK)); + break; + case EE_CHAR_WEIGHT_CTL: + buffer.append("FontWeightCTL=", rItem.StaticWhichCast(EE_CHAR_WEIGHT_CTL)); + break; + case EE_CHAR_UNDERLINE: + buffer.append("FontUnderline=", rItem.StaticWhichCast(EE_CHAR_UNDERLINE)); + break; + case EE_CHAR_OVERLINE: + buffer.append("FontOverline=", rItem.StaticWhichCast(EE_CHAR_OVERLINE)); + break; + case EE_CHAR_EMPHASISMARK: + buffer.append("FontEmphasisMark=", rItem.StaticWhichCast(EE_CHAR_EMPHASISMARK)); + break; + case EE_CHAR_RELIEF: + buffer.append("FontRelief=", rItem.StaticWhichCast(EE_CHAR_RELIEF)); + break; + case EE_CHAR_STRIKEOUT: + buffer.append("FontStrikeout=", rItem.StaticWhichCast(EE_CHAR_STRIKEOUT)); + break; + case EE_CHAR_ITALIC: + buffer.append("FontPosture=", rItem.StaticWhichCast(EE_CHAR_ITALIC)); + break; + case EE_CHAR_ITALIC_CJK: + buffer.append("FontPostureCJK=", rItem.StaticWhichCast(EE_CHAR_ITALIC_CJK)); + break; + case EE_CHAR_ITALIC_CTL: + buffer.append("FontPostureCTL=", rItem.StaticWhichCast(EE_CHAR_ITALIC_CTL)); + break; + case EE_CHAR_OUTLINE: + buffer.append("FontOutline=", rItem.StaticWhichCast(EE_CHAR_OUTLINE)); + break; + case EE_CHAR_SHADOW: + buffer.append("FontShadowed=", rItem.StaticWhichCast(EE_CHAR_SHADOW)); + break; + case EE_CHAR_ESCAPEMENT: + buffer.append("Escape=", rItem.StaticWhichCast(EE_CHAR_ESCAPEMENT)); + break; + case EE_CHAR_PAIRKERNING: + buffer.append("PairKerning=", rItem.StaticWhichCast(EE_CHAR_PAIRKERNING)); + break; + case EE_CHAR_KERNING: + buffer.append("Kerning=", rItem.StaticWhichCast(EE_CHAR_KERNING), rPool); + break; + case EE_CHAR_WLM: + buffer.append("WordLineMode=", rItem.StaticWhichCast(EE_CHAR_WLM)); + break; + case EE_CHAR_XMLATTRIBS: + buffer.str.append("XMLAttribs=..."); + break; + } + return buffer.str.makeStringAndClear(); +} + +static void DbgOutItemSet(FILE* fp, const SfxItemSet& rSet, bool bSearchInParent, bool bShowALL) +{ + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + fprintf( fp, "\nWhich: %i\t", nWhich ); + if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DEFAULT ) + fprintf( fp, "ITEM_OFF " ); + else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DONTCARE ) + fprintf( fp, "ITEM_DC " ); + else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::SET ) + fprintf( fp, "ITEM_ON *" ); + + if ( !bShowALL && ( rSet.GetItemState( nWhich, bSearchInParent ) != SfxItemState::SET ) ) + continue; + + const SfxPoolItem& rItem = rSet.Get( nWhich, bSearchInParent ); + OString aDebStr = DbgOutItem( *rSet.GetPool(), rItem ); + fprintf( fp, "%s", aDebStr.getStr() ); + } +} + +void EditEngine::DumpData(const EditEngine* pEE, bool bInfoBox) +{ + if (!pEE) + return; + + FILE* fp = fopen( "editenginedump.log", "w" ); + if ( fp == nullptr ) + { + OSL_FAIL( "Log file could not be created!" ); + return; + } + + const SfxItemPool& rPool = *pEE->GetEmptyItemSet().GetPool(); + + fprintf( fp, "================================================================================" ); + fprintf( fp, "\n================== Document ================================================" ); + fprintf( fp, "\n================================================================================" ); + for ( sal_Int32 nPortion = 0; nPortion < pEE->pImpEditEngine->GetParaPortions().Count(); nPortion++) + { + ParaPortion* pPPortion = pEE->pImpEditEngine->GetParaPortions()[nPortion]; + fprintf( fp, "\nParagraph %" SAL_PRIdINT32 ": Length = %" SAL_PRIdINT32 ", Invalid = %i\nText = '%s'", + nPortion, pPPortion->GetNode()->Len(), pPPortion->IsInvalid(), + OUStringToOString(pPPortion->GetNode()->GetString(), RTL_TEXTENCODING_UTF8).getStr() ); + fprintf( fp, "\nVorlage:" ); + SfxStyleSheet* pStyle = pPPortion->GetNode()->GetStyleSheet(); + if ( pStyle ) + fprintf( fp, " %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_UTF8).getStr() ); + fprintf( fp, "\nParagraph attribute:" ); + DbgOutItemSet( fp, pPPortion->GetNode()->GetContentAttribs().GetItems(), false, false ); + + fprintf( fp, "\nCharacter attribute:" ); + bool bZeroAttr = false; + for ( sal_Int32 z = 0; z < pPPortion->GetNode()->GetCharAttribs().Count(); ++z ) + { + const std::unique_ptr<EditCharAttrib>& rAttr = pPPortion->GetNode()->GetCharAttribs().GetAttribs()[z]; + OString aCharAttribs = + "\nA" + + OString::number(nPortion) + + ": " + + OString::number(rAttr->GetItem()->Which()) + + "\t" + + OString::number(rAttr->GetStart()) + + "\t" + + OString::number(rAttr->GetEnd()); + if ( rAttr->IsEmpty() ) + bZeroAttr = true; + fprintf(fp, "%s => ", aCharAttribs.getStr()); + + OString aDebStr = DbgOutItem( rPool, *rAttr->GetItem() ); + fprintf( fp, "%s", aDebStr.getStr() ); + } + if ( bZeroAttr ) + fprintf( fp, "\nNULL-Attribute!" ); + + const sal_Int32 nTextPortions = pPPortion->GetTextPortions().Count(); + OStringBuffer aPortionStr("\nText portions: #" + + OString::number(nTextPortions) + + " \nA" + + OString::number(nPortion) + + ": Paragraph Length = " + + OString::number(pPPortion->GetNode()->Len()) + + "\nA" + + OString::number(nPortion) + + ": "); + sal_Int32 n = 0; + for ( sal_Int32 z = 0; z < nTextPortions; ++z ) + { + TextPortion& rPortion = pPPortion->GetTextPortions()[z]; + aPortionStr.append(" " + + OString::number(rPortion.GetLen()) + + "(" + + OString::number(rPortion.GetSize().Width()) + + ")" + "[" + + OString::number(static_cast<sal_Int32>(rPortion.GetKind())) + + "];"); + n += rPortion.GetLen(); + } + aPortionStr.append("\nA" + + OString::number(nPortion) + + ": Total length: " + + OString::number(n)); + if ( pPPortion->GetNode()->Len() != n ) + aPortionStr.append(" => Error !!!"); + fprintf(fp, "%s", aPortionStr.getStr()); + + fprintf( fp, "\n\nLines:" ); + // First the content ... + for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ ) + { + EditLine& rLine = pPPortion->GetLines()[nLine]; + + OString aLine(OUStringToOString(pPPortion->GetNode()->Copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()), RTL_TEXTENCODING_ASCII_US)); + fprintf( fp, "\nLine %" SAL_PRIdINT32 "\t>%s<", nLine, aLine.getStr() ); + } + // then the internal data ... + for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ ) + { + EditLine& rLine = pPPortion->GetLines()[nLine]; + fprintf( fp, "\nLine %" SAL_PRIdINT32 ":\tStart: %" SAL_PRIdINT32 ",\tEnd: %" SAL_PRIdINT32, nLine, rLine.GetStart(), rLine.GetEnd() ); + fprintf( fp, "\t\tPortions: %" SAL_PRIdINT32 " - %" SAL_PRIdINT32 ".\tHight: %i, Ascent=%i", rLine.GetStartPortion(), rLine.GetEndPortion(), rLine.GetHeight(), rLine.GetMaxAscent() ); + } + + fprintf( fp, "\n-----------------------------------------------------------------------------" ); + } + + if ( pEE->pImpEditEngine->GetStyleSheetPool() ) + { + SfxStyleSheetIterator aIter( pEE->pImpEditEngine->GetStyleSheetPool(), SfxStyleFamily::All ); + sal_uInt16 nStyles = aIter.Count(); + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Stylesheets =============================================" ); + fprintf( fp, "\n================================================================================" ); + fprintf( fp, "\n#Template: %" SAL_PRIuUINT32 "\n", sal_uInt32(nStyles) ); + SfxStyleSheetBase* pStyle = aIter.First(); + while ( pStyle ) + { + fprintf( fp, "\nTemplate: %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + fprintf( fp, "\nParent: %s", OUStringToOString( pStyle->GetParent(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + fprintf( fp, "\nFollow: %s", OUStringToOString( pStyle->GetFollow(), RTL_TEXTENCODING_ASCII_US ).getStr() ); + DbgOutItemSet( fp, pStyle->GetItemSet(), false, false ); + fprintf( fp, "\n----------------------------------" ); + + pStyle = aIter.Next(); + } + } + + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Defaults ================================================" ); + fprintf( fp, "\n================================================================================" ); + DbgOutItemSet( fp, pEE->pImpEditEngine->GetEmptyItemSet(), true, true ); + + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== EditEngine & Views ======================================" ); + fprintf( fp, "\n================================================================================" ); + fprintf( fp, "\nControl: %x", unsigned( pEE->GetControlWord() ) ); + fprintf( fp, "\nRefMapMode: %i", int( pEE->pImpEditEngine->pRefDev->GetMapMode().GetMapUnit() ) ); + fprintf( fp, "\nPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetPaperSize().Width()), sal_Int64(pEE->GetPaperSize().Height()) ); + fprintf( fp, "\nMaxAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetMaxAutoPaperSize().Width()), sal_Int64(pEE->GetMaxAutoPaperSize().Height()) ); + fprintf( fp, "\nMinAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64 , sal_Int64(pEE->GetMinAutoPaperSize().Width()), sal_Int64(pEE->GetMinAutoPaperSize().Height()) ); + fprintf( fp, "\nCalculateLayout: %i", pEE->IsUpdateLayout() ); + fprintf( fp, "\nNumber of Views: %" SAL_PRI_SIZET "i", pEE->GetViewCount() ); + for ( size_t nView = 0; nView < pEE->GetViewCount(); nView++ ) + { + EditView* pV = pEE->GetView( nView ); + DBG_ASSERT( pV, "View not found!" ); + fprintf( fp, "\nView %zu: Focus=%i", nView, pV->GetWindow()->HasFocus() ); + tools::Rectangle aR( pV->GetOutputArea() ); + fprintf( fp, "\n OutputArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64 ", MapMode = %i", + sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) , int( pV->GetWindow()->GetMapMode().GetMapUnit() ) ); + aR = pV->GetVisArea(); + fprintf( fp, "\n VisArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64, + sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) ); + ESelection aSel = pV->GetSelection(); + fprintf( fp, "\n Selection: Start=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32 ", End=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32, aSel.nStartPara, aSel.nStartPos, aSel.nEndPara, aSel.nEndPos ); + } + if ( pEE->GetActiveView() ) + { + fprintf( fp, "\n\n================================================================================" ); + fprintf( fp, "\n================== Current View ===========================================" ); + fprintf( fp, "\n================================================================================" ); + DbgOutItemSet( fp, pEE->GetActiveView()->GetAttribs(), true, false ); + } + fclose( fp ); + if ( bInfoBox ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + "Dumped editenginedump.log!" )); + xInfoBox->run(); + } +} +#endif + +#if OSL_DEBUG_LEVEL > 0 +bool ParaPortion::DbgCheckTextPortions(ParaPortion const& rPara) +{ + // check, if Portion length ok: + sal_uInt16 nXLen = 0; + for (sal_Int32 nPortion = 0; nPortion < rPara.aTextPortionList.Count(); nPortion++) + { + nXLen = nXLen + rPara.aTextPortionList[nPortion].GetLen(); + } + return nXLen == rPara.pNode->Len(); +} +#endif + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CheckOrderedList(const CharAttribList::AttribsType& rAttribs) +{ + sal_Int32 nPrev = 0; + for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs) + { + sal_Int32 const nCur = rAttr->GetStart(); + assert(nCur >= nPrev); + nPrev = nCur; + } +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx new file mode 100644 index 0000000000..d892bd1c3a --- /dev/null +++ b/editeng/source/editeng/editdoc.cxx @@ -0,0 +1,3006 @@ +/* -*- 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 <editeng/tstpitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/editids.hrc> +#include <editeng/editdata.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> + +#include <editdoc.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include "impedit.hxx" + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <svl/grabbagitem.hxx> +#include <svl/voiditem.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <libxml/xmlwriter.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <limits> +#include <memory> +#include <set> +#include <string_view> +#include <utility> + +using namespace ::com::sun::star; + + +sal_uInt16 GetScriptItemId( sal_uInt16 nItemId, SvtScriptType nScriptType ) +{ + sal_uInt16 nId = nItemId; + + if ( ( nScriptType == SvtScriptType::ASIAN ) || + ( nScriptType == SvtScriptType::COMPLEX ) ) + { + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_LANGUAGE_CJK : EE_CHAR_LANGUAGE_CTL; + break; + case EE_CHAR_FONTINFO: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTINFO_CJK : EE_CHAR_FONTINFO_CTL; + break; + case EE_CHAR_FONTHEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTHEIGHT_CJK : EE_CHAR_FONTHEIGHT_CTL; + break; + case EE_CHAR_WEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_WEIGHT_CJK : EE_CHAR_WEIGHT_CTL; + break; + case EE_CHAR_ITALIC: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_ITALIC_CJK : EE_CHAR_ITALIC_CTL; + break; + } + } + + return nId; +} + +bool IsScriptItemValid( sal_uInt16 nItemId, short nScriptType ) +{ + bool bValid = true; + + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_LANGUAGE_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_LANGUAGE_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTINFO: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTINFO_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTINFO_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTHEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTHEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTHEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_WEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_WEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_WEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_ITALIC: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_ITALIC_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_ITALIC_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + } + + return bValid; +} + +const SfxItemInfo aItemInfos[EDITITEMCOUNT] = +{ + // _nSID, _bNeedsPoolRegistration, _bShareable + { SID_ATTR_FRAMEDIRECTION, false, true }, // EE_PARA_WRITINGDIR + { 0, true, true }, // EE_PARA_XMLATTRIBS + { SID_ATTR_PARA_HANGPUNCTUATION, false, true }, // EE_PARA_HANGINGPUNCTUATION + { SID_ATTR_PARA_FORBIDDEN_RULES, false, true }, // EE_PARA_FORBIDDENRULES + { SID_ATTR_PARA_SCRIPTSPACE, false, true }, // EE_PARA_ASIANCJKSPACING + { SID_ATTR_NUMBERING_RULE, false, true }, // EE_PARA_NUMBULL + { 0, false, true }, // EE_PARA_HYPHENATE + { 0, false, true }, // EE_PARA_HYPHENATE_NO_CAPS + { 0, false, true }, // EE_PARA_HYPHENATE_NO_LAST_WORD + { 0, false, true }, // EE_PARA_BULLETSTATE + { 0, false, true }, // EE_PARA_OUTLLRSPACE + { SID_ATTR_PARA_OUTLLEVEL, false, true }, // EE_PARA_OUTLLEVEL + { SID_ATTR_PARA_BULLET, false, true }, // EE_PARA_BULLET + { SID_ATTR_LRSPACE, false, true }, // EE_PARA_LRSPACE + { SID_ATTR_ULSPACE, false, true }, // EE_PARA_ULSPACE + { SID_ATTR_PARA_LINESPACE, false, true }, // EE_PARA_SBL + { SID_ATTR_PARA_ADJUST, false, true }, // EE_PARA_JUST + { SID_ATTR_TABSTOP, false, true }, // EE_PARA_TABS + { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, false, true }, // EE_PARA_JUST_METHOD + { SID_ATTR_ALIGN_VER_JUSTIFY, false, true }, // EE_PARA_VER_JUST + { SID_ATTR_CHAR_COLOR, true, true }, // EE_CHAR_COLOR + { SID_ATTR_CHAR_FONT, true, true }, // EE_CHAR_FONTINFO + { SID_ATTR_CHAR_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT + { SID_ATTR_CHAR_SCALEWIDTH, false, true }, // EE_CHAR_FONTWIDTH + { SID_ATTR_CHAR_WEIGHT, false, true }, // EE_CHAR_WEIGHT + { SID_ATTR_CHAR_UNDERLINE, false, true }, // EE_CHAR_UNDERLINE + { SID_ATTR_CHAR_STRIKEOUT, false, true }, // EE_CHAR_STRIKEOUT + { SID_ATTR_CHAR_POSTURE, false, true }, // EE_CHAR_ITALIC + { SID_ATTR_CHAR_CONTOUR, false, true }, // EE_CHAR_OUTLINE + { SID_ATTR_CHAR_SHADOWED, false, true }, // EE_CHAR_SHADOW + { SID_ATTR_CHAR_ESCAPEMENT, false, true }, // EE_CHAR_ESCAPEMENT + { SID_ATTR_CHAR_AUTOKERN, false, true }, // EE_CHAR_PAIRKERNING + { SID_ATTR_CHAR_KERNING, false, true }, // EE_CHAR_KERNING + { SID_ATTR_CHAR_WORDLINEMODE, false, true }, // EE_CHAR_WLM + { SID_ATTR_CHAR_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE + { SID_ATTR_CHAR_CJK_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE_CJK + { SID_ATTR_CHAR_CTL_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE_CTL + { SID_ATTR_CHAR_CJK_FONT, true, true }, // EE_CHAR_FONTINFO_CJK + { SID_ATTR_CHAR_CTL_FONT, true, true }, // EE_CHAR_FONTINFO_CTL + { SID_ATTR_CHAR_CJK_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT_CJK + { SID_ATTR_CHAR_CTL_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT_CTL + { SID_ATTR_CHAR_CJK_WEIGHT, false, true }, // EE_CHAR_WEIGHT_CJK + { SID_ATTR_CHAR_CTL_WEIGHT, false, true }, // EE_CHAR_WEIGHT_CTL + { SID_ATTR_CHAR_CJK_POSTURE, false, true }, // EE_CHAR_ITALIC_CJK + { SID_ATTR_CHAR_CTL_POSTURE, false, true }, // EE_CHAR_ITALIC_CTL + { SID_ATTR_CHAR_EMPHASISMARK, false, true }, // EE_CHAR_EMPHASISMARK + { SID_ATTR_CHAR_RELIEF, false, true }, // EE_CHAR_RELIEF + { 0, false, true }, // EE_CHAR_RUBI_DUMMY + { 0, true, true }, // EE_CHAR_XMLATTRIBS + { SID_ATTR_CHAR_OVERLINE, false, true }, // EE_CHAR_OVERLINE + { SID_ATTR_CHAR_CASEMAP, false, true }, // EE_CHAR_CASEMAP + { SID_ATTR_CHAR_GRABBAG, false, true }, // EE_CHAR_GRABBAG + { SID_ATTR_CHAR_BACK_COLOR, false, true }, // EE_CHAR_BKGCOLOR + { 0, false, true }, // EE_FEATURE_TAB + { 0, false, true }, // EE_FEATURE_LINEBR + { SID_ATTR_CHAR_CHARSETCOLOR, false, true }, // EE_FEATURE_NOTCONV + { SID_FIELD, true, true }, // EE_FEATURE_FIELD +}; + +EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ) +{ + // Create a new attribute in the pool + switch( rAttr.Which() ) + { + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + { + return new EditCharAttribLanguage(rPool, rAttr, nS, nE); + } + break; + case EE_CHAR_COLOR: + { + return new EditCharAttribColor(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + return new EditCharAttribFont(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + return new EditCharAttribFontHeight(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_FONTWIDTH: + { + return new EditCharAttribFontWidth(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + return new EditCharAttribWeight(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_UNDERLINE: + { + return new EditCharAttribUnderline(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_OVERLINE: + { + return new EditCharAttribOverline(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_EMPHASISMARK: + { + return new EditCharAttribEmphasisMark(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_RELIEF: + { + return new EditCharAttribRelief(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_STRIKEOUT: + { + return new EditCharAttribStrikeout(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + return new EditCharAttribItalic(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_OUTLINE: + { + return new EditCharAttribOutline(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_SHADOW: + { + return new EditCharAttribShadow(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_ESCAPEMENT: + { + return new EditCharAttribEscapement(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_PAIRKERNING: + { + return new EditCharAttribPairKerning(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_KERNING: + { + return new EditCharAttribKerning(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_WLM: + { + return new EditCharAttribWordLineMode(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_XMLATTRIBS: + { + return new EditCharAttrib(rPool, rAttr, nS, nE); // Attribute is only for holding XML information... + } + break; + case EE_CHAR_CASEMAP: + { + return new EditCharAttribCaseMap(rPool, rAttr, nS, nE ); + } + break; + case EE_CHAR_GRABBAG: + { + return new EditCharAttribGrabBag(rPool, rAttr, nS, nE ); + } + break; + case EE_FEATURE_TAB: + { + return new EditCharAttribTab(rPool, rAttr, nS ); + } + break; + case EE_FEATURE_LINEBR: + { + return new EditCharAttribLineBreak(rPool, rAttr, nS ); + } + break; + case EE_FEATURE_FIELD: + { + return new EditCharAttribField(rPool, rAttr, nS ); + } + break; + case EE_CHAR_BKGCOLOR: + { + return new EditCharAttribBackgroundColor(rPool, rAttr, nS, nE ); + } + break; + default: + break; + } + + OSL_FAIL( "Invalid Attribute!" ); + return nullptr; +} + +TextPortionList::TextPortionList() +{ +} + +TextPortionList::~TextPortionList() +{ + Reset(); +} + +void TextPortionList::Reset() +{ + maPortions.clear(); +} + +void TextPortionList::DeleteFromPortion(sal_Int32 nDelFrom) +{ + assert((nDelFrom < static_cast<sal_Int32>(maPortions.size())) || ((nDelFrom == 0) && maPortions.empty())); + PortionsType::iterator it = maPortions.begin(); + std::advance(it, nDelFrom); + maPortions.erase(it, maPortions.end()); +} + +sal_Int32 TextPortionList::Count() const +{ + return static_cast<sal_Int32>(maPortions.size()); +} + +const TextPortion& TextPortionList::operator[](sal_Int32 nPos) const +{ + return *maPortions[nPos]; +} + +TextPortion& TextPortionList::operator[](sal_Int32 nPos) +{ + return *maPortions[nPos]; +} + +void TextPortionList::Append(TextPortion* p) +{ + maPortions.push_back(std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Insert(sal_Int32 nPos, TextPortion* p) +{ + maPortions.insert(maPortions.begin()+nPos, std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Remove(sal_Int32 nPos) +{ + maPortions.erase(maPortions.begin()+nPos); +} + +namespace { + +class FindTextPortionByAddress +{ + const TextPortion* mp; +public: + explicit FindTextPortionByAddress(const TextPortion* p) : mp(p) {} + bool operator() (const std::unique_ptr<TextPortion>& v) const + { + return v.get() == mp; + } +}; + +} + +sal_Int32 TextPortionList::GetPos(const TextPortion* p) const +{ + PortionsType::const_iterator it = + std::find_if(maPortions.begin(), maPortions.end(), FindTextPortionByAddress(p)); + + if (it == maPortions.end()) + return std::numeric_limits<sal_Int32>::max(); // not found. + + return std::distance(maPortions.begin(), it); +} + +sal_Int32 TextPortionList::FindPortion( + sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion) const +{ + // When nCharPos at portion limit, the left portion is found + sal_Int32 nTmpPos = 0; + sal_Int32 n = maPortions.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nTmpPos = nTmpPos + rPortion.GetLen(); + if ( nTmpPos >= nCharPos ) + { + // take this one if we don't prefer the starting portion, or if it's the last one + if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( i == n-1 ) ) + { + nPortionStart = nTmpPos - rPortion.GetLen(); + return i; + } + } + } + OSL_FAIL( "FindPortion: Not found!" ); + return n - 1; +} + +sal_Int32 TextPortionList::GetStartPos(sal_Int32 nPortion) +{ + sal_Int32 nPos = 0; + for (sal_Int32 i = 0; i < nPortion; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nPos = nPos + rPortion.GetLen(); + } + return nPos; +} + +ExtraPortionInfo::ExtraPortionInfo() +: nOrgWidth(0) +, nWidthFullCompression(0) +, nPortionOffsetX(0) +, nMaxCompression100thPercent(0) +, nAsianCompressionTypes(AsianCompressionFlags::Normal) +, bFirstCharIsRightPunktuation(false) +, bCompressed(false) +{ +} + +ExtraPortionInfo::~ExtraPortionInfo() +{ +} + +void ExtraPortionInfo::SaveOrgDXArray( const sal_Int32* pDXArray, sal_Int32 nLen ) +{ + if (pDXArray) + { + pOrgDXArray.reset(new sal_Int32[nLen]); + memcpy( pOrgDXArray.get(), pDXArray, nLen * sizeof(sal_Int32) ); + } + else + pOrgDXArray.reset(); +} + +ParaPortion::ParaPortion( ContentNode* pN ) : + pNode(pN), + nHeight(0), + nInvalidPosStart(0), + nFirstLineOffset(0), + nBulletX(0), + nInvalidDiff(0), + bInvalid(true), + bSimple(false), + bVisible(true), + bForceRepaint(false) +{ +} + +ParaPortion::~ParaPortion() +{ +} + +void ParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff ) +{ + if ( !bInvalid ) + { +// nInvalidPosEnd = nStart; // ??? => CreateLines + nInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff ); + nInvalidDiff = nDiff; + } + else + { + // Simple tap in succession + if ( ( nDiff > 0 ) && ( nInvalidDiff > 0 ) && + ( ( nInvalidPosStart+nInvalidDiff ) == nStart ) ) + { + nInvalidDiff = nInvalidDiff + nDiff; + } + // Simple delete in succession + else if ( ( nDiff < 0 ) && ( nInvalidDiff < 0 ) && ( nInvalidPosStart == nStart ) ) + { + nInvalidPosStart = nInvalidPosStart + nDiff; + nInvalidDiff = nInvalidDiff + nDiff; + } + else + { +// nInvalidPosEnd = pNode->Len(); + DBG_ASSERT( ( nDiff >= 0 ) || ( (nStart+nDiff) >= 0 ), "MarkInvalid: Diff out of Range" ); + nInvalidPosStart = std::min( nInvalidPosStart, ( nDiff < 0 ? nStart+nDiff : nDiff ) ); + nInvalidDiff = 0; + bSimple = false; + } + } + bInvalid = true; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +void ParaPortion::MarkSelectionInvalid( sal_Int32 nStart ) +{ + if ( !bInvalid ) + { + nInvalidPosStart = nStart; + } + else + { + nInvalidPosStart = std::min( nInvalidPosStart, nStart ); + } + nInvalidDiff = 0; + bInvalid = true; + bSimple = false; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +sal_Int32 ParaPortion::GetLineNumber( sal_Int32 nIndex ) const +{ + SAL_WARN_IF( !aLineList.Count(), "editeng", "Empty ParaPortion in GetLine!" ); + DBG_ASSERT( bVisible, "Why GetLine() on an invisible paragraph?" ); + + for ( sal_Int32 nLine = 0; nLine < aLineList.Count(); nLine++ ) + { + if ( aLineList[nLine].IsIn( nIndex ) ) + return nLine; + } + + // Then it should be at the end of the last line! + DBG_ASSERT( nIndex == aLineList[ aLineList.Count() - 1 ].GetEnd(), "Index dead wrong!" ); + return (aLineList.Count()-1); +} + +void ParaPortion::SetVisible( bool bMakeVisible ) +{ + bVisible = bMakeVisible; +} + +void ParaPortion::CorrectValuesBehindLastFormattedLine( sal_Int32 nLastFormattedLine ) +{ + sal_Int32 nLines = aLineList.Count(); + DBG_ASSERT( nLines, "CorrectPortionNumbersFromLine: Empty Portion?" ); + if ( nLastFormattedLine < ( nLines - 1 ) ) + { + const EditLine& rLastFormatted = aLineList[ nLastFormattedLine ]; + const EditLine& rUnformatted = aLineList[ nLastFormattedLine+1 ]; + sal_Int32 nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion(); + sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd(); + nTextDiff++; // LastFormatted->GetEnd() was included => 1 deducted too much! + + // The first unformatted must begin exactly one Portion behind the last + // of the formatted: + // If the modified line was split into one portion, can + // nLastEnd > nNextStart! + int nPDiff = -( nPortionDiff-1 ); + int nTDiff = -( nTextDiff-1 ); + if ( nPDiff || nTDiff ) + { + for ( sal_Int32 nL = nLastFormattedLine+1; nL < nLines; nL++ ) + { + EditLine& rLine = aLineList[ nL ]; + + rLine.GetStartPortion() = rLine.GetStartPortion() + nPDiff; + rLine.GetEndPortion() = rLine.GetEndPortion() + nPDiff; + + rLine.GetStart() = rLine.GetStart() + nTDiff; + rLine.GetEnd() = rLine.GetEnd() + nTDiff; + + rLine.SetValid(); + } + } + } + DBG_ASSERT( aLineList[ aLineList.Count()-1 ].GetEnd() == pNode->Len(), "CorrectLines: The end is not right!" ); +} + +// Shared reverse lookup acceleration pieces ... + +namespace { + +template<typename Array, typename Val> +sal_Int32 FastGetPos(const Array& rArray, const Val* p, sal_Int32& rLastPos) +{ + sal_Int32 nArrayLen = rArray.size(); + + // Through certain filter code-paths we do a lot of appends, which in + // turn call GetPos - creating some N^2 nightmares. If we have a + // non-trivially large list, do a few checks from the end first. + if (rLastPos > 16 && nArrayLen > 16) + { + sal_Int32 nEnd; + if (rLastPos > nArrayLen - 2) + nEnd = nArrayLen; + else + nEnd = rLastPos + 2; + + for (sal_Int32 nIdx = rLastPos - 2; nIdx < nEnd; ++nIdx) + { + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return nIdx; + } + } + } + // The world's lamest linear search from svarray... + for (sal_Int32 nIdx = 0; nIdx < nArrayLen; ++nIdx) + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return rLastPos; + } + + // XXX "not found" condition for sal_Int32 indexes + return EE_PARA_NOT_FOUND; +} + +} + +ParaPortionList::ParaPortionList() : nLastCache( 0 ) +{ +} + +ParaPortionList::~ParaPortionList() +{ +} + +sal_Int32 ParaPortionList::GetPos(const ParaPortion* p) const +{ + return FastGetPos(maPortions, p, nLastCache); +} + +ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +const ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +std::unique_ptr<ParaPortion> ParaPortionList::Release(sal_Int32 nPos) +{ + if (nPos < 0 || maPortions.size() <= o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Release - out of bounds pos " << nPos); + return nullptr; + } + std::unique_ptr<ParaPortion> p = std::move(maPortions[nPos]); + maPortions.erase(maPortions.begin()+nPos); + return p; +} + +void ParaPortionList::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || maPortions.size() <= o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Remove - out of bounds pos " << nPos); + return; + } + maPortions.erase(maPortions.begin()+nPos); +} + +void ParaPortionList::Insert(sal_Int32 nPos, std::unique_ptr<ParaPortion> p) +{ + if (nPos < 0 || maPortions.size() < o3tl::make_unsigned(nPos)) + { + SAL_WARN( "editeng", "ParaPortionList::Insert - out of bounds pos " << nPos); + return; + } + maPortions.insert(maPortions.begin()+nPos, std::move(p)); +} + +void ParaPortionList::Append(std::unique_ptr<ParaPortion> p) +{ + maPortions.push_back(std::move(p)); +} + +sal_Int32 ParaPortionList::Count() const +{ + size_t nSize = maPortions.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "ParaPortionList::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +void ParaPortionList::Reset() +{ + maPortions.clear(); +} + +tools::Long ParaPortionList::GetYOffset(const ParaPortion* pPPortion) const +{ + tools::Long nHeight = 0; + for (const auto & rPortion : maPortions) + { + const ParaPortion* pTmpPortion = rPortion.get(); + if ( pTmpPortion == pPPortion ) + return nHeight; + nHeight += pTmpPortion->GetHeight(); + } + OSL_FAIL( "GetYOffset: Portion not found" ); + return nHeight; +} + +sal_Int32 ParaPortionList::FindParagraph(tools::Long nYOffset) const +{ + tools::Long nY = 0; + for (size_t i = 0, n = maPortions.size(); i < n; ++i) + { + nY += maPortions[i]->GetHeight(); // should also be correct even in bVisible! + if ( nY > nYOffset ) + return i <= SAL_MAX_INT32 ? static_cast<sal_Int32>(i) : SAL_MAX_INT32; + } + return EE_PARA_NOT_FOUND; +} + +const ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void +ParaPortionList::DbgCheck(ParaPortionList const& rParas, EditDoc const& rDoc) +{ + assert(rParas.Count() == rDoc.Count()); + for (sal_Int32 i = 0; i < rParas.Count(); ++i) + { + assert(rParas.SafeGetObject(i) != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() == rDoc.GetObject(i)); + } +} +#endif + +ContentAttribsInfo::ContentAttribsInfo( SfxItemSet aParaAttribs ) : + aPrevParaAttribs(std::move( aParaAttribs)) +{ +} + +void ContentAttribsInfo::AppendCharAttrib(EditCharAttrib* pNew) +{ + aPrevCharAttribs.push_back(std::unique_ptr<EditCharAttrib>(pNew)); +} + +void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit ) +{ + DBG_ASSERT( eSourceUnit != eDestUnit, "ConvertItem - Why?!" ); + + switch ( rPoolItem->Which() ) + { + case EE_PARA_LRSPACE: + { + assert(dynamic_cast<const SvxLRSpaceItem *>(rPoolItem.get()) != nullptr); + SvxLRSpaceItem& rItem = static_cast<SvxLRSpaceItem&>(*rPoolItem); + rItem.SetTextFirstLineOffset( sal::static_int_cast< short >( OutputDevice::LogicToLogic( rItem.GetTextFirstLineOffset(), eSourceUnit, eDestUnit ) ) ); + rItem.SetTextLeft( OutputDevice::LogicToLogic( rItem.GetTextLeft(), eSourceUnit, eDestUnit ) ); + rItem.SetRight( OutputDevice::LogicToLogic( rItem.GetRight(), eSourceUnit, eDestUnit ) ); + } + break; + case EE_PARA_ULSPACE: + { + assert(dynamic_cast<const SvxULSpaceItem *>(rPoolItem.get()) != nullptr); + SvxULSpaceItem& rItem = static_cast<SvxULSpaceItem&>(*rPoolItem); + rItem.SetUpper( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetUpper(), eSourceUnit, eDestUnit ) ) ); + rItem.SetLower( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLower(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_SBL: + { + assert(dynamic_cast<const SvxLineSpacingItem *>(rPoolItem.get()) != nullptr); + SvxLineSpacingItem& rItem = static_cast<SvxLineSpacingItem&>(*rPoolItem); + // SetLineHeight changes also eLineSpace! + if ( rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + rItem.SetLineHeight( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLineHeight(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_TABS: + { + assert(dynamic_cast<const SvxTabStopItem *>(rPoolItem.get()) != nullptr); + SvxTabStopItem& rItem = static_cast<SvxTabStopItem&>(*rPoolItem); + SvxTabStopItem* pNewItem(new SvxTabStopItem(EE_PARA_TABS)); + + if (sal_Int32 nDefTabDistance = rItem.GetDefaultDistance()) + { + pNewItem->SetDefaultDistance( + OutputDevice::LogicToLogic(nDefTabDistance, eSourceUnit, eDestUnit)); + } + + for ( sal_uInt16 i = 0; i < rItem.Count(); i++ ) + { + const SvxTabStop& rTab = rItem[i]; + SvxTabStop aNewStop( OutputDevice::LogicToLogic( rTab.GetTabPos(), eSourceUnit, eDestUnit ), rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() ); + pNewItem->Insert( aNewStop ); + } + rPoolItem.reset(pNewItem); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + assert(dynamic_cast<const SvxFontHeightItem *>(rPoolItem.get()) != nullptr); + SvxFontHeightItem& rItem = static_cast<SvxFontHeightItem&>(*rPoolItem); + rItem.SetHeight( OutputDevice::LogicToLogic( rItem.GetHeight(), eSourceUnit, eDestUnit ) ); + } + break; + } +} + +void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit, const MapUnit* pDestUnit ) +{ + const SfxItemPool* pSourcePool = rSource.GetPool(); + const SfxItemPool* pDestPool = rDest.GetPool(); + + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // If possible go through SlotID ... + + sal_uInt16 nSourceWhich = nWhich; + sal_uInt16 nSlot = pDestPool->GetTrueSlotId( nWhich ); + if ( nSlot ) + { + sal_uInt16 nW = pSourcePool->GetTrueWhich( nSlot ); + if ( nW ) + nSourceWhich = nW; + } + + if ( rSource.GetItemState( nSourceWhich, false ) == SfxItemState::SET ) + { + MapUnit eSourceUnit = pSourceUnit ? *pSourceUnit : pSourcePool->GetMetric( nSourceWhich ); + MapUnit eDestUnit = pDestUnit ? *pDestUnit : pDestPool->GetMetric( nWhich ); + if ( eSourceUnit != eDestUnit ) + { + std::unique_ptr<SfxPoolItem> pItem(rSource.Get( nSourceWhich ).Clone()); + ConvertItem( pItem, eSourceUnit, eDestUnit ); + pItem->SetWhich(nWhich); + rDest.Put( std::move(pItem) ); + } + else + { + rDest.Put( rSource.Get( nSourceWhich ).CloneSetWhich(nWhich) ); + } + } + } +} + +EditLine::EditLine() : + nTxtWidth(0), + nStartPosX(0), + nStart(0), + nEnd(0), + nStartPortion(0), // to be able to tell the difference between a line + // without Portions from one with the Portion number 0 + nEndPortion(0), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(false), + bInvalid(true) +{ +} + +EditLine::EditLine( const EditLine& r ) : + nTxtWidth(0), + nStartPosX(0), + nStart(r.nStart), + nEnd(r.nEnd), + nStartPortion(r.nStartPortion), + nEndPortion(r.nEndPortion), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(r.bHangingPunctuation), + bInvalid(true) +{ +} + +EditLine::~EditLine() +{ +} + + +EditLine* EditLine::Clone() const +{ + EditLine* pL = new EditLine; + pL->aPositions = aPositions; + pL->nStartPosX = nStartPosX; + pL->nStart = nStart; + pL->nEnd = nEnd; + pL->nStartPortion = nStartPortion; + pL->nEndPortion = nEndPortion; + pL->nHeight = nHeight; + pL->nTxtWidth = nTxtWidth; + pL->nTxtHeight = nTxtHeight; + pL->nMaxAscent = nMaxAscent; + + return pL; +} + +bool operator == ( const EditLine& r1, const EditLine& r2 ) +{ + if ( r1.nStart != r2.nStart ) + return false; + + if ( r1.nEnd != r2.nEnd ) + return false; + + if ( r1.nStartPortion != r2.nStartPortion ) + return false; + + if ( r1.nEndPortion != r2.nEndPortion ) + return false; + + return true; +} + +EditLine& EditLine::operator = ( const EditLine& r ) +{ + nEnd = r.nEnd; + nStart = r.nStart; + nEndPortion = r.nEndPortion; + nStartPortion = r.nStartPortion; + return *this; +} + + +void EditLine::SetHeight( sal_uInt16 nH, sal_uInt16 nTxtH ) +{ + nHeight = nH; + nTxtHeight = ( nTxtH ? nTxtH : nH ); +} + +void EditLine::SetStartPosX( sal_Int32 start ) +{ + if (start > 0) + nStartPosX = start; + else + nStartPosX = 0; +} + +Size EditLine::CalcTextSize( ParaPortion& rParaPortion ) +{ + Size aSz; + Size aTmpSz; + + DBG_ASSERT( rParaPortion.GetTextPortions().Count(), "GetTextSize before CreatePortions !" ); + + for ( sal_Int32 n = nStartPortion; n <= nEndPortion; n++ ) + { + TextPortion& rPortion = rParaPortion.GetTextPortions()[n]; + switch ( rPortion.GetKind() ) + { + case PortionKind::TEXT: + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + { + aTmpSz = rPortion.GetSize(); + aSz.AdjustWidth(aTmpSz.Width() ); + if ( aSz.Height() < aTmpSz.Height() ) + aSz.setHeight( aTmpSz.Height() ); + } + break; + case PortionKind::TAB: + { + aSz.AdjustWidth(rPortion.GetSize().Width() ); + } + break; + case PortionKind::LINEBREAK: break; + } + } + + SetHeight( static_cast<sal_uInt16>(aSz.Height()) ); + return aSz; +} + +EditLineList::EditLineList() +{ +} + +EditLineList::~EditLineList() +{ + Reset(); +} + +void EditLineList::Reset() +{ + maLines.clear(); +} + +void EditLineList::DeleteFromLine(sal_Int32 nDelFrom) +{ + assert(nDelFrom <= (static_cast<sal_Int32>(maLines.size()) - 1)); + LinesType::iterator it = maLines.begin(); + std::advance(it, nDelFrom); + maLines.erase(it, maLines.end()); +} + +sal_Int32 EditLineList::FindLine(sal_Int32 nChar, bool bInclEnd) +{ + sal_Int32 n = maLines.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const EditLine& rLine = *maLines[i]; + if ( (bInclEnd && (rLine.GetEnd() >= nChar)) || + (rLine.GetEnd() > nChar) ) + { + return i; + } + } + + DBG_ASSERT( !bInclEnd, "Line not found: FindLine" ); + return n - 1; +} + +sal_Int32 EditLineList::Count() const +{ + return maLines.size(); +} + +const EditLine& EditLineList::operator[](sal_Int32 nPos) const +{ + return *maLines[nPos]; +} + +EditLine& EditLineList::operator[](sal_Int32 nPos) +{ + return *maLines[nPos]; +} + +void EditLineList::Append(EditLine* p) +{ + maLines.push_back(std::unique_ptr<EditLine>(p)); +} + +void EditLineList::Insert(sal_Int32 nPos, EditLine* p) +{ + maLines.insert(maLines.begin()+nPos, std::unique_ptr<EditLine>(p)); +} + +EditPaM::EditPaM() : pNode(nullptr), nIndex(0) {} +EditPaM::EditPaM(ContentNode* p, sal_Int32 n) : pNode(p), nIndex(n) {} + + +void EditPaM::SetNode(ContentNode* p) +{ + pNode = p; +} + +bool EditPaM::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return !pNode || + rDoc.GetPos( pNode ) >= rDoc.Count() || + nIndex > pNode->Len(); +} + +bool EditSelection::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return aStartPaM.DbgIsBuggy( rDoc ) || aEndPaM.DbgIsBuggy( rDoc ); +} + +EditSelection::EditSelection() +{ +} + +EditSelection::EditSelection( const EditPaM& rStartAndAnd ) : + aStartPaM(rStartAndAnd), + aEndPaM(rStartAndAnd) +{ +} + +EditSelection::EditSelection( const EditPaM& rStart, const EditPaM& rEnd ) : + aStartPaM(rStart), + aEndPaM(rEnd) +{ +} + +EditSelection& EditSelection::operator = ( const EditPaM& rPaM ) +{ + aStartPaM = rPaM; + aEndPaM = rPaM; + return *this; +} + +void EditSelection::Adjust( const EditDoc& rNodes ) +{ + DBG_ASSERT( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in Adjust(1)" ); + DBG_ASSERT( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in Adjust(2)" ); + + const ContentNode* pStartNode = aStartPaM.GetNode(); + const ContentNode* pEndNode = aEndPaM.GetNode(); + + sal_Int32 nStartNode = rNodes.GetPos( pStartNode ); + sal_Int32 nEndNode = rNodes.GetPos( pEndNode ); + + DBG_ASSERT( nStartNode != SAL_MAX_INT32, "Node out of range in Adjust(1)" ); + DBG_ASSERT( nEndNode != SAL_MAX_INT32, "Node out of range in Adjust(2)" ); + + const bool bSwap = ( nStartNode > nEndNode ) || + ( ( nStartNode == nEndNode ) && + ( aStartPaM.GetIndex() > aEndPaM.GetIndex() ) ); + + if ( bSwap ) + { + EditPaM aTmpPaM( aStartPaM ); + aStartPaM = aEndPaM; + aEndPaM = aTmpPaM; + } +} + +bool operator == ( const EditPaM& r1, const EditPaM& r2 ) +{ + return ( r1.GetNode() == r2.GetNode() ) && + ( r1.GetIndex() == r2.GetIndex() ); +} + +bool operator != ( const EditPaM& r1, const EditPaM& r2 ) +{ + return !( r1 == r2 ); +} + +ContentNode::ContentNode( SfxItemPool& rPool ) : aContentAttribs( rPool ) +{ +} + +ContentNode::ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs ) : + maString(rStr), aContentAttribs(rContentAttribs) +{ +} + +ContentNode::~ContentNode() +{ +} + +void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew ) +{ + if ( !nNew ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list. // In every if ..., in the next (n) opportunities due to bFeature or + // an existing special case, must (n-1) opportunities be provided with + // bResort. The most likely possibility receives no bResort, so that is + // not sorted anew when all attributes are the same. + bool bResort = false; + bool bExpandedEmptyAtIndexNull = false; + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all attributes behind the insertion point... + if ( pAttrib->GetStart() > nIndex ) + { + pAttrib->MoveForward( nNew ); + } + // 0: Expand empty attribute, if at insertion point + else if ( pAttrib->IsEmpty() ) + { + // Do not check Index, an empty one could only be there + // When later checking it anyhow: + // Special case: Start == 0; AbsLen == 1, nNew = 1 + // => Expand, because of paragraph break! + // Start <= nIndex, End >= nIndex => Start=End=nIndex! +// if ( pAttrib->GetStart() == nIndex ) + pAttrib->Expand( nNew ); + bResort = true; + if ( pAttrib->GetStart() == 0 ) + bExpandedEmptyAtIndexNull = true; + } + // 1: Attribute starts before, goes to index ... + else if ( pAttrib->GetEnd() == nIndex ) // Start must be before + { + // Only expand when there is no feature + // and if not in exclude list! + // Otherwise, a UL will go on until a new ULDB, expanding both +// if ( !pAttrib->IsFeature() && !rExclList.FindAttrib( pAttrib->Which() ) ) + if ( !pAttrib->IsFeature() && !aCharAttribList.FindEmptyAttrib( pAttrib->Which(), nIndex ) ) + { + if ( !pAttrib->IsEdge() ) + pAttrib->Expand( nNew ); + } + else + bResort = true; + } + // 2: Attribute starts before, goes past the Index... + else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Large Feature?!" ); + pAttrib->Expand( nNew ); + } + // 3: Attribute starts on index... + else if ( pAttrib->GetStart() == nIndex ) + { + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveForward( nNew ); + bResort = true; + } + else + { + bool bExpand = false; + if ( nIndex == 0 ) + { + bExpand = true; + if( bExpandedEmptyAtIndexNull ) + { + // Check if this kind of attribute was empty and expanded here... + sal_uInt16 nW = pAttrib->GetItem()->Which(); + for ( std::size_t nA = 0; nA < nAttr; nA++ ) + { + const EditCharAttrib& r = *aCharAttribList.GetAttribs()[nA]; + if ( ( r.GetStart() == 0 ) && ( r.GetItem()->Which() == nW ) ) + { + bExpand = false; + break; + } + } + + } + } + if ( bExpand ) + { + pAttrib->Expand( nNew ); + bResort = true; + } + else + { + pAttrib->MoveForward( nNew ); + } + } + } + } + + if ( pAttrib->IsEdge() ) + pAttrib->SetEdge(false); + + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len() ), "Expand: Attribute larger than paragraph!" ); + if ( pAttrib->IsEmpty() ) + { + OSL_FAIL( "Empty Attribute after ExpandAttribs?" ); + bResort = true; + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + ++nAttr; + } + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + { + bool bSep = ( maString[ nIndex ] == ' ' ) || IsFeature( nIndex ); + mpWrongList->TextInserted( nIndex, nNew, bSep ); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted ) +{ + if ( !nDeleted ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list + bool bResort = false; + sal_Int32 nEndChanges = nIndex+nDeleted; + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + bool bDelAttr = false; + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all Attribute behind the insert point... + if ( pAttrib->GetStart() >= nEndChanges ) + { + pAttrib->MoveBackward( nDeleted ); + } + // 1. Delete Internal attributes... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) ) + { + // Special case: Attribute covers the area exactly + // => keep as empty Attribute. + if ( !pAttrib->IsFeature() && ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) ) + { + pAttrib->GetEnd() = nIndex; // empty + bResort = true; + } + else + bDelAttr = true; + } + // 2. Attribute starts earlier, ends inside or behind it ... + else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Collapsing Feature!" ); + if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside + pAttrib->GetEnd() = nIndex; + else + pAttrib->Collaps( nDeleted ); // ends behind + } + // 3. Attribute starts inside, ending behind ... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) ) + { + // Features not allowed to expand! + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveBackward( nDeleted ); + bResort = true; + } + else + { + pAttrib->GetStart() = nEndChanges; + pAttrib->MoveBackward( nDeleted ); + } + } + } + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collapse: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len()) || bDelAttr, "Collapse: Attribute larger than paragraph!" ); + if ( bDelAttr ) + { + bResort = true; + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + if ( pAttrib->IsEmpty() ) + aCharAttribList.SetHasEmptyAttribs(true); + nAttr++; + } + + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + mpWrongList->TextDeleted(nIndex, nDeleted); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CopyAndCutAttribs( ContentNode* pPrevNode, SfxItemPool& rPool, bool bKeepEndingAttribs ) +{ + assert(pPrevNode); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif + + sal_Int32 nCut = pPrevNode->Len(); + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rPrevAttribs = pPrevNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rPrevAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() < nCut ) + { + // remain unchanged... + nAttr++; + } + else if ( pAttrib->GetEnd() == nCut ) + { + // must be copied as an empty attributes. + if ( bKeepEndingAttribs && !pAttrib->IsFeature() && !aCharAttribList.FindAttrib( pAttrib->GetItem()->Which(), 0 ) ) + { + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, 0 ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + } + nAttr++; + } + else if ( pAttrib->IsInside( nCut ) || ( !nCut && !pAttrib->GetStart() && !pAttrib->IsFeature() ) ) + { + // If cut is done right at the front then the attribute must be + // kept! Has to be copied and changed. + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, pAttrib->GetEnd()-nCut ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + pAttrib->GetEnd() = nCut; + nAttr++; + } + else + { + // Move all attributes in the current node (this) + CharAttribList::AttribsType::iterator it = rPrevAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rPrevAttribs.erase(it); + pAttrib->MoveBackward( nCut ); + } + pAttrib = GetAttrib(rPrevAttribs, nAttr); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif +} + +void ContentNode::AppendAttribs( ContentNode* pNextNode ) +{ + assert(pNextNode); + + sal_Int32 nNewStart = maString.getLength(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif + + std::size_t nAttr = 0; + CharAttribList::AttribsType& rNextAttribs = pNextNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rNextAttribs, nAttr); + while ( pAttrib ) + { + // Move all attributes in the current node (this) + bool bMelted = false; + if ( ( pAttrib->GetStart() == 0 ) && ( !pAttrib->IsFeature() ) ) + { + // Attributes can possibly be summarized as: + std::size_t nTmpAttr = 0; + EditCharAttrib* pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + while ( !bMelted && pTmpAttrib ) + { + ++nTmpAttr; + if ( pTmpAttrib->GetEnd() == nNewStart ) + { + if (pTmpAttrib->Which() == pAttrib->Which()) + { + // prevent adding 2 0-length attributes at same position + if ((*(pTmpAttrib->GetItem()) == *(pAttrib->GetItem())) + || (0 == pAttrib->GetLen())) + { + pTmpAttrib->GetEnd() = + pTmpAttrib->GetEnd() + pAttrib->GetLen(); + rNextAttribs.erase(rNextAttribs.begin()+nAttr); + // Unsubscribe from the pool?! + bMelted = true; + } + else if (0 == pTmpAttrib->GetLen()) + { + --nTmpAttr; // to cancel earlier increment... + aCharAttribList.Remove(nTmpAttr); + } + } + } + pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + } + } + + if ( !bMelted ) + { + pAttrib->GetStart() = pAttrib->GetStart() + nNewStart; + pAttrib->GetEnd() = pAttrib->GetEnd() + nNewStart; + CharAttribList::AttribsType::iterator it = rNextAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rNextAttribs.erase(it); + } + pAttrib = GetAttrib(rNextAttribs, nAttr); + } + // For the Attributes that just moved over: + rNextAttribs.clear(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif +} + +void ContentNode::CreateDefFont() +{ + // First use the information from the style ... + SfxStyleSheet* pS = aContentAttribs.GetStyleSheet(); + if ( pS ) + CreateFont( GetCharAttribs().GetDefFont(), pS->GetItemSet() ); + + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, const SvxFont& rFontFromStyle ) +{ + aContentAttribs.SetStyleSheet( pS ); + + + // First use the information from the style ... + GetCharAttribs().GetDefFont() = rFontFromStyle; + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, bool bRecalcFont ) +{ + aContentAttribs.SetStyleSheet( pS ); + if ( bRecalcFont ) + CreateDefFont(); +} + +bool ContentNode::IsFeature( sal_Int32 nPos ) const +{ + return maString[nPos] == CH_FEATURE; +} + +sal_Int32 ContentNode::Len() const +{ + return maString.getLength(); +} + +sal_Int32 ContentNode::GetExpandedLen() const +{ + sal_Int32 nLen = maString.getLength(); + + // Fields can be longer than the placeholder in the Node + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (sal_Int32 nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + nLen += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + --nLen; // Standalone, to avoid corner cases when previous getLength() returns 0 + } + } + + return nLen; +} + +OUString ContentNode::GetExpandedText(sal_Int32 nStartPos, sal_Int32 nEndPos) const +{ + if ( nEndPos < 0 || nEndPos > Len() ) + nEndPos = Len(); + + DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" ); + + sal_Int32 nIndex = nStartPos; + OUStringBuffer aStr(256); + const EditCharAttrib* pNextFeature = GetCharAttribs().FindFeature( nIndex ); + while ( nIndex < nEndPos ) + { + sal_Int32 nEnd = nEndPos; + if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) ) + nEnd = pNextFeature->GetStart(); + else + pNextFeature = nullptr; // Feature does not interest the below + + DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" ); + //!! beware of sub string length of -1 + if (nEnd > nIndex) + aStr.append( GetString().subView(nIndex, nEnd - nIndex) ); + + if ( pNextFeature ) + { + switch ( pNextFeature->GetItem()->Which() ) + { + case EE_FEATURE_TAB: aStr.append( "\t" ); + break; + case EE_FEATURE_LINEBR: aStr.append( "\x0A" ); + break; + case EE_FEATURE_FIELD: + aStr.append( static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue() ); + break; + default: OSL_FAIL( "What feature?" ); + } + pNextFeature = GetCharAttribs().FindFeature( ++nEnd ); + } + nIndex = nEnd; + } + return aStr.makeStringAndClear(); +} + +void ContentNode::UnExpandPosition( sal_Int32 &rPos, bool bBiasStart ) +{ + sal_Int32 nOffset = 0; + + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (size_t nAttr = 0; nAttr < rAttrs.size(); ++nAttr ) + { + const EditCharAttrib& rAttr = *rAttrs[nAttr]; + assert (!(nAttr < rAttrs.size() - 1) || + rAttrs[nAttr]->GetStart() <= rAttrs[nAttr + 1]->GetStart()); + + nOffset = rAttr.GetStart(); + + if (nOffset >= rPos) // happens after the position + return; + + if (rAttr.Which() == EE_FEATURE_FIELD) + { + sal_Int32 nChunk = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + nChunk--; // Character representing the field in the string + + if (nOffset + nChunk >= rPos) // we're inside the field + { + if (bBiasStart) + rPos = rAttr.GetStart(); + else + rPos = rAttr.GetEnd(); + return; + } + // Adjust for the position + rPos -= nChunk; + } + } + assert (rPos <= Len()); +} + +/* + * Fields are represented by a single character in the underlying string + * and/or selection, however, they can be expanded to the full value of + * the field. When we're dealing with selection / offsets however we need + * to deal in character positions inside the real (unexpanded) string. + * This method maps us back to character offsets. + */ +void ContentNode::UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos ) +{ + UnExpandPosition( rStartPos, true ); + UnExpandPosition( rEndPos, false ); +} + +void ContentNode::SetChar(sal_Int32 nPos, sal_Unicode c) +{ + maString = maString.replaceAt(nPos, 1, rtl::OUStringChar(c)); +} + +void ContentNode::Insert(std::u16string_view rStr, sal_Int32 nPos) +{ + maString = maString.replaceAt(nPos, 0, rStr); +} + +void ContentNode::Append(std::u16string_view rStr) +{ + maString += rStr; +} + +void ContentNode::Erase(sal_Int32 nPos) +{ + maString = maString.copy(0, nPos); +} + +void ContentNode::Erase(sal_Int32 nPos, sal_Int32 nCount) +{ + maString = maString.replaceAt(nPos, nCount, u""); +} + +OUString ContentNode::Copy(sal_Int32 nPos) const +{ + return maString.copy(nPos); +} + +OUString ContentNode::Copy(sal_Int32 nPos, sal_Int32 nCount) const +{ + return maString.copy(nPos, nCount); +} + +sal_Unicode ContentNode::GetChar(sal_Int32 nPos) const +{ + return maString[nPos]; +} + +void ContentNode::EnsureWrongList() +{ + if (!mpWrongList) + CreateWrongList(); +} + +WrongList* ContentNode::GetWrongList() +{ + return mpWrongList.get(); +} + +const WrongList* ContentNode::GetWrongList() const +{ + return mpWrongList.get(); +} + +void ContentNode::SetWrongList( WrongList* p ) +{ + mpWrongList.reset(p); +} + +void ContentNode::CreateWrongList() +{ + SAL_WARN_IF( mpWrongList && !mpWrongList->empty(), "editeng", "WrongList already exist!"); + if (!mpWrongList || !mpWrongList->empty()) + mpWrongList.reset(new WrongList); +} + +void ContentNode::DestroyWrongList() +{ + mpWrongList.reset(); +} + +void ContentNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentNode")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("maString"), BAD_CAST(maString.toUtf8().getStr())); + aContentAttribs.dumpAsXml(pWriter); + aCharAttribList.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +void ContentNode::checkAndDeleteEmptyAttribs() const +{ + // Delete empty attributes, but only if paragraph is not empty! + if (GetCharAttribs().HasEmptyAttribs() && Len()) + { + const_cast<ContentNode*>(this)->GetCharAttribs().DeleteEmptyAttribs(); + } +} + +ContentAttribs::ContentAttribs( SfxItemPool& rPool ) +: pStyle(nullptr) +, aAttribSet( rPool ) +{ +} + + +SvxTabStop ContentAttribs::FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab ) +{ + const SvxTabStopItem& rTabs = GetItem( EE_PARA_TABS ); + for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ ) + { + const SvxTabStop& rTab = rTabs[i]; + if ( rTab.GetTabPos() > nCurPos ) + return rTab; + } + + // if there's a default tab size defined for this item use that instead + if (rTabs.GetDefaultDistance()) + nDefTab = rTabs.GetDefaultDistance(); + + // Determine DefTab ... + SvxTabStop aTabStop; + const sal_Int32 x = nCurPos / nDefTab + 1; + aTabStop.GetTabPos() = nDefTab * x; + return aTabStop; +} + +void ContentAttribs::SetStyleSheet( SfxStyleSheet* pS ) +{ + bool bStyleChanged = ( pStyle != pS ); + pStyle = pS; + // Only when other style sheet, not when current style sheet modified + if ( !(pStyle && bStyleChanged) ) + return; + + // Selectively remove the attributes from the paragraph formatting + // which are specified in the style, so that the attributes of the + // style can have an affect. + const SfxItemSet& rStyleAttribs = pStyle->GetItemSet(); + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // Don't change bullet on/off + if ( ( nWhich != EE_PARA_BULLETSTATE ) && ( rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET ) ) + aAttribSet.ClearItem( nWhich ); + } +} + +const SfxPoolItem& ContentAttribs::GetItem( sal_uInt16 nWhich ) const +{ + // Hard paragraph attributes take precedence! + const SfxItemSet* pTakeFrom = &aAttribSet; + if ( pStyle && ( aAttribSet.GetItemState( nWhich, false ) != SfxItemState::SET ) ) + pTakeFrom = &pStyle->GetItemSet(); + + return pTakeFrom->Get( nWhich ); +} + +bool ContentAttribs::HasItem( sal_uInt16 nWhich ) const +{ + bool bHasItem = false; + if ( aAttribSet.GetItemState( nWhich, false ) == SfxItemState::SET ) + bHasItem = true; + else if ( pStyle && pStyle->GetItemSet().GetItemState( nWhich ) == SfxItemState::SET ) + bHasItem = true; + + return bHasItem; +} + +void ContentAttribs::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentAttribs")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("style"), "%s", pStyle->GetName().toUtf8().getStr()); + aAttribSet.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + +ItemList::ItemList() : CurrentItem( 0 ) +{ +} + +const SfxPoolItem* ItemList::First() +{ + CurrentItem = 0; + return aItemPool.empty() ? nullptr : aItemPool[ 0 ]; +} + +const SfxPoolItem* ItemList::Next() +{ + if ( CurrentItem + 1 < static_cast<sal_Int32>(aItemPool.size()) ) + { + ++CurrentItem; + return aItemPool[ CurrentItem ]; + } + return nullptr; +} + +void ItemList::Insert( const SfxPoolItem* pItem ) +{ + aItemPool.push_back( pItem ); + CurrentItem = aItemPool.size() - 1; +} + + +EditDoc::EditDoc( SfxItemPool* pPool ) : + nLastCache(0), + pItemPool(pPool ? pPool : new EditEngineItemPool()), + nDefTab(DEFTAB), + bIsVertical(false), + mnRotation(TextRotation::NONE), + bIsFixedCellHeight(false), + bModified(false), + bDisableAttributeExpanding(false) +{ + // Don't create an empty node, Clear() will be called in EditEngine-CTOR +}; + +EditDoc::~EditDoc() +{ + maContents.clear(); +} + +void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType ) +{ + vcl::Font aPrevFont( rFont ); + rFont.SetAlignment( ALIGN_BASELINE ); + + sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ); + sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); + sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ); + sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ); + sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType ); + + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == SfxItemState::SET ) ) + { + const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSet.Get( nWhich_FontInfo )); + rFont.SetFamilyName( rFontItem.GetFamilyName() ); + rFont.SetFamily( rFontItem.GetFamily() ); + rFont.SetPitch( rFontItem.GetPitch() ); + rFont.SetCharSet( rFontItem.GetCharSet() ); + } + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == SfxItemState::SET ) ) + rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( nWhich_Language )).GetLanguage() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == SfxItemState::SET ) ) + rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == SfxItemState::SET ) ) + { + auto& aColor = rSet.Get( EE_CHAR_BKGCOLOR ).GetValue(); + rFont.SetTransparent(aColor.IsTransparent()); + rFont.SetFillColor(aColor); + } + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == SfxItemState::SET ) ) + rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) ).GetHeight() ) ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == SfxItemState::SET ) ) + rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( nWhich_Weight )).GetWeight() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) ) + rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == SfxItemState::SET ) ) + rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) ) + rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == SfxItemState::SET ) ) + rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == SfxItemState::SET ) ) + rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( nWhich_Italic )).GetPosture() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == SfxItemState::SET ) ) + rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == SfxItemState::SET ) ) + rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == SfxItemState::SET ) ) + { + const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT ); + + sal_uInt16 const nProp = rEsc.GetProportionalHeight(); + rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); + + short nEsc = rEsc.GetEsc(); + rFont.SetNonAutoEscapement( nEsc ); + } + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == SfxItemState::SET ) ) + rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == SfxItemState::SET ) ) + rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == SfxItemState::SET ) ) + rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == SfxItemState::SET ) ) + rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK ).GetEmphasisMark() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == SfxItemState::SET ) ) + rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() ); + + // Operator == compares the individual members of the font if the impl pointer is + // not equal. If all members are the same, this assignment makes + // sure that both also point to the same internal instance of the font. + // To avoid this assignment, you would need to check in + // every if statement above whether or not the new value differs from the + // old value before making an assignment. + if ( rFont == aPrevFont ) + rFont = aPrevFont; // => The same ImpPointer for IsSameInstance +} + +void EditDoc::CreateDefFont( bool bUseStyles ) +{ + SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aTmpSet( GetItemPool() ); + CreateFont(maDefFont, aTmpSet); + maDefFont.SetVertical( IsEffectivelyVertical() ); + maDefFont.SetOrientation( Degree10(IsEffectivelyVertical() ? (IsTopToBottom() ? 2700 : 900) : 0) ); + + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + ContentNode* pNode = GetObject( nNode ); + pNode->GetCharAttribs().GetDefFont() = maDefFont; + if ( bUseStyles ) + pNode->CreateDefFont(); + } +} + +bool EditDoc::IsEffectivelyVertical() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation != TextRotation::NONE); +} + +bool EditDoc::IsTopToBottom() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation == TextRotation::TOPTOBOTTOM); +} + +bool EditDoc::GetVertical() const +{ + return bIsVertical; +} + +sal_Int32 EditDoc::GetPos(const ContentNode* p) const +{ + return FastGetPos(maContents, p, nLastCache); +} + +const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; +} + +ContentNode* EditDoc::GetObject(sal_Int32 nPos) +{ + return 0 <= nPos && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr; +} + +const ContentNode* EditDoc::operator[](sal_Int32 nPos) const +{ + return GetObject(nPos); +} + +ContentNode* EditDoc::operator[](sal_Int32 nPos) +{ + return GetObject(nPos); +} + +void EditDoc::Insert(sal_Int32 nPos, ContentNode* p) +{ + if (nPos < 0 || nPos == SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos); + return; + } + maContents.insert(maContents.begin()+nPos, std::unique_ptr<ContentNode>(p)); +} + +void EditDoc::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + { + SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos); + return; + } + maContents.erase(maContents.begin() + nPos); +} + +void EditDoc::Release(sal_Int32 nPos) +{ + if (nPos < 0 || o3tl::make_unsigned(nPos) >= maContents.size()) + { + SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos); + return; + } + // coverity[leaked_storage] - this is on purpose, ownership should be transferred to undo/redo + (void)maContents[nPos].release(); + maContents.erase(maContents.begin() + nPos); +} + +sal_Int32 EditDoc::Count() const +{ + size_t nSize = maContents.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +OUString EditDoc::GetSepStr( LineEnd eEnd ) +{ + if ( eEnd == LINEEND_CR ) + return "\015"; // 0x0d + if ( eEnd == LINEEND_LF ) + return "\012"; // 0x0a + return "\015\012"; // 0x0d, 0x0a +} + +OUString EditDoc::GetText( LineEnd eEnd ) const +{ + const sal_Int32 nNodes = Count(); + if (nNodes == 0) + return OUString(); + + const OUString aSep = EditDoc::GetSepStr( eEnd ); + const sal_Int32 nSepSize = aSep.getLength(); + const sal_Int32 nLen = GetTextLen() + (nNodes - 1)*nSepSize; + + OUStringBuffer aBuffer(nLen + 16); // leave some slack + + for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ ) + { + if ( nSepSize && nNode>0 ) + { + aBuffer.append(aSep); + } + aBuffer.append(GetParaAsString( GetObject(nNode) )); + } + + return aBuffer.makeStringAndClear(); +} + +OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const +{ + return GetParaAsString( GetObject( nNode ) ); +} + +OUString EditDoc::GetParaAsString( + const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + return pNode->GetExpandedText(nStartPos, nEndPos); +} + +EditPaM EditDoc::GetStartPaM() const +{ + ContentNode* p = const_cast<ContentNode*>(GetObject(0)); + return EditPaM(p, 0); +} + +EditPaM EditDoc::GetEndPaM() const +{ + ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1)); + return EditPaM( pLastNode, pLastNode->Len() ); +} + +sal_Int32 EditDoc::GetTextLen() const +{ + sal_Int32 nLen = 0; + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + const ContentNode* pNode = GetObject( nNode ); + nLen += pNode->GetExpandedLen(); + } + return nLen; +} + +EditPaM EditDoc::Clear() +{ + maContents.clear(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + CreateDefFont(false); + + SetModified(false); + + return EditPaM( pNode, 0 ); +} + +namespace +{ +struct ClearSpellErrorsHandler +{ + void operator() (std::unique_ptr<ContentNode> const & rNode) + { + rNode->DestroyWrongList(); + } +}; +} + +void EditDoc::ClearSpellErrors() +{ + std::for_each(maContents.begin(), maContents.end(), ClearSpellErrorsHandler()); +} + +void EditDoc::SetModified( bool b ) +{ + bModified = b; + if ( bModified ) + { + aModifyHdl.Call( nullptr ); + } +} + +EditPaM EditDoc::RemoveText() +{ + // Keep the old ItemSet, to keep the chart Font. + ContentNode* pPrevFirstNode = GetObject(0); + SfxStyleSheet* pPrevStyle = pPrevFirstNode->GetStyleSheet(); + SfxItemSet aPrevSet( pPrevFirstNode->GetContentAttribs().GetItems() ); + vcl::Font aPrevFont( pPrevFirstNode->GetCharAttribs().GetDefFont() ); + + maContents.clear(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + pNode->SetStyleSheet(pPrevStyle, false); + pNode->GetContentAttribs().GetItems().Set( aPrevSet ); + pNode->GetCharAttribs().GetDefFont() = aPrevFont; + + SetModified(true); + + return EditPaM( pNode, 0 ); +} + +EditPaM EditDoc::InsertText( EditPaM aPaM, std::u16string_view rStr ) +{ + DBG_ASSERT( rStr.find( 0x0A ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.find( 0x0D ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.find( '\t' ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( rStr, aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), rStr.size() ); + aPaM.SetIndex( aPaM.GetIndex() + rStr.size() ); + + SetModified( true ); + + return aPaM; +} + +EditPaM EditDoc::InsertParaBreak( EditPaM aPaM, bool bKeepEndingAttribs ) +{ + assert(aPaM.GetNode()); + ContentNode* pCurNode = aPaM.GetNode(); + sal_Int32 nPos = GetPos( pCurNode ); + OUString aStr = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + aPaM.GetNode()->Erase( aPaM.GetIndex() ); + + // the paragraph attributes... + ContentAttribs aContentAttribs( aPaM.GetNode()->GetContentAttribs() ); + + // for a new paragraph we like to have the bullet/numbering visible by default + aContentAttribs.GetItems().Put( SfxBoolItem( EE_PARA_BULLETSTATE, true) ); + + // ContentNode constructor copies also the paragraph attributes + ContentNode* pNode = new ContentNode( aStr, std::move(aContentAttribs) ); + + // Copy the Default Font + pNode->GetCharAttribs().GetDefFont() = aPaM.GetNode()->GetCharAttribs().GetDefFont(); + SfxStyleSheet* pStyle = aPaM.GetNode()->GetStyleSheet(); + if ( pStyle ) + { + OUString aFollow( pStyle->GetFollow() ); + if ( !aFollow.isEmpty() && ( aFollow != pStyle->GetName() ) ) + { + SfxStyleSheetBase* pNext = pStyle->GetPool()->Find( aFollow, pStyle->GetFamily() ); + pNode->SetStyleSheet( static_cast<SfxStyleSheet*>(pNext) ); + } + } + + // Character attributes may need to be copied or trimmed: + pNode->CopyAndCutAttribs( aPaM.GetNode(), GetItemPool(), bKeepEndingAttribs ); + + Insert(nPos+1, pNode); + + SetModified(true); + + aPaM.SetNode( pNode ); + aPaM.SetIndex( 0 ); + return aPaM; +} + +EditPaM EditDoc::InsertFeature( EditPaM aPaM, const SfxPoolItem& rItem ) +{ + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( rtl::OUStringChar(CH_FEATURE), aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), 1 ); + + // Create a feature-attribute for the feature... + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rItem, aPaM.GetIndex(), aPaM.GetIndex()+1 ); + assert(pAttrib); + aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); + + aPaM.SetIndex( aPaM.GetIndex() + 1 ); + return aPaM; +} + +EditPaM EditDoc::ConnectParagraphs( ContentNode* pLeft, ContentNode* pRight ) +{ + const EditPaM aPaM( pLeft, pLeft->Len() ); + + // First the attributes, otherwise nLen will not be correct! + pLeft->AppendAttribs( pRight ); + // then the Text... + pLeft->Append(pRight->GetString()); + + // the one to the right disappears. + sal_Int32 nRight = GetPos( pRight ); + Remove( nRight ); + + SetModified(true); + + return aPaM; +} + +void EditDoc::RemoveChars( EditPaM aPaM, sal_Int32 nChars ) +{ + // Maybe remove Features! + aPaM.GetNode()->Erase( aPaM.GetIndex(), nChars ); + aPaM.GetNode()->CollapseAttribs( aPaM.GetIndex(), nChars ); + + SetModified( true ); +} + +void EditDoc::InsertAttribInSelection( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // for Optimization: + // This ends at the beginning of the selection => can be expanded + EditCharAttrib* pEndingAttrib = nullptr; + // This starts at the end of the selection => can be expanded + EditCharAttrib* pStartingAttrib = nullptr; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + + RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() ); + + // tdf#132288 By default inserting an attribute beside another that is of + // the same type expands the original instead of inserting another. But the + // spell check dialog doesn't want that behaviour + if (bDisableAttributeExpanding) + { + pStartingAttrib = nullptr; + pEndingAttrib = nullptr; + } + + if ( pStartingAttrib && pEndingAttrib && + ( *(pStartingAttrib->GetItem()) == rPoolItem ) && + ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + { + // Will become a large Attribute. + pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd(); + pNode->GetCharAttribs().Remove(pStartingAttrib); + } + else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) ) + pStartingAttrib->GetStart() = nStart; + else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + pEndingAttrib->GetEnd() = nEnd; + else + InsertAttrib( rPoolItem, pNode, nStart, nEnd ); + + if ( pStartingAttrib ) + pNode->GetCharAttribs().ResortAttribs(); + + SetModified(true); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nWhich ) +{ + EditCharAttrib* pStarting; + EditCharAttrib* pEnding; + return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich ); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, EditCharAttrib*& rpStarting, EditCharAttrib*& rpEnding, sal_uInt16 nWhich ) +{ + + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // This ends at the beginning of the selection => can be expanded + rpEnding = nullptr; + // This starts at the end of the selection => can be expanded + rpStarting = nullptr; + + bool bChanged = false; + bool bNeedsSorting = false; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + // iterate over the attributes ... + std::size_t nAttr = 0; + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttr = GetAttrib(rAttribs, nAttr); + while ( pAttr ) + { + bool bRemoveAttrib = false; + sal_uInt16 nAttrWhich = pAttr->Which(); + if ( ( nAttrWhich < EE_FEATURE_START ) && ( !nWhich || ( nAttrWhich == nWhich ) ) ) + { + // Attribute starts in Selection + if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetEnd() > nEnd ) + { + bNeedsSorting = true; + pAttr->GetStart() = nEnd; // then it starts after this + rpStarting = pAttr; + if ( nWhich ) + break; // There can be no further attributes here + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + + // Attribute ends in Selection + else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) ) + { + bChanged = true; + if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() ) + { + pAttr->GetEnd() = nStart; // then it ends here + rpEnding = pAttr; + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + // Attribute overlaps the selection + else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetStart() == nStart ) + { + bNeedsSorting = true; + pAttr->GetStart() = nEnd; + rpStarting = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else if ( pAttr->GetEnd() == nEnd ) + { + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else // Attribute must be split ... + { + bNeedsSorting = true; + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd ); + if ( nWhich ) + break; // There can be further attributes! + } + } + } + if ( bRemoveAttrib ) + { + DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Delete and retain the same attribute?" ); + DBG_ASSERT( !pAttr->IsFeature(), "RemoveAttribs: Remove a feature?!" ); + rAttribs.erase(rAttribs.begin()+nAttr); + } + else + { + nAttr++; + } + pAttr = GetAttrib(rAttribs, nAttr); + } + + if ( bChanged ) + { + // char attributes need to be sorted by start again + if (bNeedsSorting) + pNode->GetCharAttribs().ResortAttribs(); + SetModified(true); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + return bChanged; +} + +void EditDoc::InsertAttrib( const SfxPoolItem& rPoolItem, ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd ) +{ + // This method no longer checks whether a corresponding attribute already + // exists at this place! + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rPoolItem, nStart, nEnd ); + assert(pAttrib); + pNode->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); +} + +void EditDoc::InsertAttrib( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + if ( nStart != nEnd ) + { + InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem ); + } + else + { + // Check whether already a new attribute with WhichId exists at this place: + CharAttribList& rAttrList = pNode->GetCharAttribs(); + EditCharAttrib* pAttr = rAttrList.FindEmptyAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + // Remove attribute... + rAttrList.Remove(pAttr); + } + + // check whether 'the same' attribute exist at this place. + pAttr = rAttrList.FindAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + if ( pAttr->IsInside( nStart ) ) // split + { + // check again if really splitting, or return ! + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + EditCharAttrib* pNew = MakeCharAttrib( GetItemPool(), *(pAttr->GetItem()), nStart, nOldEnd ); + rAttrList.InsertAttrib(pNew); + } + else if ( pAttr->GetEnd() == nStart ) + { + DBG_ASSERT( !pAttr->IsEmpty(), "Still an empty attribute?" ); + // Check if exactly the same attribute + if ( *(pAttr->GetItem()) == rPoolItem ) + return; + } + } + InsertAttrib( rPoolItem, pNode, nStart, nStart ); + } + + SetModified( true ); +} + +void EditDoc::FindAttribs( ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, SfxItemSet& rCurSet ) +{ + assert(pNode); + DBG_ASSERT( nStartPos <= nEndPos, "Invalid region!" ); + + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + // No Selection... + if ( nStartPos == nEndPos ) + { + while ( pAttr && ( pAttr->GetStart() <= nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + pItem = pAttr->GetItem(); + // Attribute ending here is not empty + else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) ) + pItem = pAttr->GetItem(); + } + // Attribute ending here is empty + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + pItem = pAttr->GetItem(); + } + // Attribute starts here + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + { + if ( nStartPos == 0 ) // special case + pItem = pAttr->GetItem(); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } + else // Selection + { + while ( pAttr && ( pAttr->GetStart() < nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) ) + pItem = pAttr->GetItem(); + // Attribute starts right in the middle ... + else if ( pAttr->GetStart() >= nStartPos ) + { + // !!! pItem = pAttr->GetItem(); + // PItem is simply not enough, since one for example in case + // of Shadow, would never find an unequal item, since such a + // item represents its presence by absence! + // If (...) + // It needs to be examined on exactly the same attribute at the + // break point, which is quite expensive. + // Since optimization is done when inserting the attributes + // this case does not appear so fast... + // So based on the need for speed: + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + + } + // Attribute ends in the middle of it ... + else if ( pAttr->GetEnd() > nStartPos ) + { + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } +} + +void EditDoc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("editdoc.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditDoc")); + for (auto const & i : maContents) + { + i->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + + +namespace { + +struct LessByStart +{ + bool operator() (const std::unique_ptr<EditCharAttrib>& left, const std::unique_ptr<EditCharAttrib>& right) const + { + return left->GetStart() < right->GetStart(); + } +}; + +} + +CharAttribList::CharAttribList() +: bHasEmptyAttribs(false) +{ +} + +CharAttribList::~CharAttribList() +{ +} + +void CharAttribList::InsertAttrib( EditCharAttrib* pAttrib ) +{ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// optimize: binary search? ! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Maybe just simply iterate backwards: + // The most common and critical case: Attributes are already sorted + // (InsertTextObject!) binary search would not be optimal here. + // => Would bring something! + + const sal_Int32 nStart = pAttrib->GetStart(); // may be better for Comp.Opt. + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + + if ( pAttrib->IsEmpty() ) + bHasEmptyAttribs = true; + + bool bInsert(true); + for (sal_Int32 i = 0, n = aAttribs.size(); i < n; ++i) + { + const EditCharAttrib& rCurAttrib = *aAttribs[i]; + if (rCurAttrib.GetStart() > nStart) + { + aAttribs.insert(aAttribs.begin()+i, std::unique_ptr<EditCharAttrib>(pAttrib)); + bInsert = false; + break; + } + } + + if (bInsert) aAttribs.push_back(std::unique_ptr<EditCharAttrib>(pAttrib)); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::ResortAttribs() +{ + std::sort(aAttribs.begin(), aAttribs.end(), LessByStart()); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::OptimizeRanges() +{ +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAttribs.size()); ++i) + { + EditCharAttrib& rAttr = *aAttribs[i]; + for (sal_Int32 nNext = i+1; nNext < static_cast<sal_Int32>(aAttribs.size()); ++nNext) + { + EditCharAttrib& rNext = *aAttribs[nNext]; + if (!rAttr.IsFeature() && rNext.GetStart() == rAttr.GetEnd() && rNext.Which() == rAttr.Which()) + { + if (*rNext.GetItem() == *rAttr.GetItem()) + { + rAttr.GetEnd() = rNext.GetEnd(); + aAttribs.erase(aAttribs.begin()+nNext); + } + break; // only 1 attr with same which can start here. + } + else if (rNext.GetStart() > rAttr.GetEnd()) + { + break; + } + } + } +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +sal_Int32 CharAttribList::Count() const +{ + return aAttribs.size(); +} + +const EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](const AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + const EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +const EditCharAttrib* CharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_Int32 nFromPos ) const +{ + assert(nWhich); + for (auto const& attrib : aAttribs) + { + const EditCharAttrib& rAttr = *attrib; + if (rAttr.GetStart() >= nFromPos && rAttr.Which() == nWhich) + return &rAttr; + } + return nullptr; +} + +bool CharAttribList::HasAttrib( sal_Int32 nStartPos, sal_Int32 nEndPos ) const +{ + return std::any_of(aAttribs.rbegin(), aAttribs.rend(), + [&nStartPos, &nEndPos](const AttribsType::value_type& rxAttr) { + return rxAttr->GetStart() < nEndPos && rxAttr->GetEnd() > nStartPos; }); +} + + +namespace { + +class FindByAddress +{ + const EditCharAttrib* mpAttr; +public: + explicit FindByAddress(const EditCharAttrib* p) : mpAttr(p) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r.get() == mpAttr; + } +}; + +} + +void CharAttribList::Remove(const EditCharAttrib* p) +{ + AttribsType::iterator it = std::find_if(aAttribs.begin(), aAttribs.end(), FindByAddress(p)); + if (it != aAttribs.end()) + aAttribs.erase(it); +} + +void CharAttribList::Remove(sal_Int32 nPos) +{ + if (nPos >= static_cast<sal_Int32>(aAttribs.size())) + return; + + aAttribs.erase(aAttribs.begin()+nPos); +} + +void CharAttribList::SetHasEmptyAttribs(bool b) +{ + bHasEmptyAttribs = b; +} + +bool CharAttribList::HasBoundingAttrib( sal_Int32 nBound ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = aAttribs.rbegin(), itEnd = aAttribs.rend(); + for (; it != itEnd; ++it) + { + const EditCharAttrib& rAttr = **it; + if (rAttr.GetEnd() < nBound) + return false; + + if (rAttr.GetStart() == nBound || rAttr.GetEnd() == nBound) + return true; + } + return false; +} + +EditCharAttrib* CharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + if ( !bHasEmptyAttribs ) + return nullptr; + + for (const std::unique_ptr<EditCharAttrib>& rAttr : aAttribs) + { + if (rAttr->GetStart() == nPos && rAttr->GetEnd() == nPos && rAttr->Which() == nWhich) + return rAttr.get(); + } + return nullptr; +} + +namespace { + +class FindByStartPos +{ + sal_Int32 mnPos; +public: + explicit FindByStartPos(sal_Int32 nPos) : mnPos(nPos) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r->GetStart() >= mnPos; + } +}; + +} + +const EditCharAttrib* CharAttribList::FindFeature( sal_Int32 nPos ) const +{ + // First, find the first attribute that starts at or after specified position. + AttribsType::const_iterator it = + std::find_if(aAttribs.begin(), aAttribs.end(), FindByStartPos(nPos)); + + if (it == aAttribs.end()) + // All attributes are before the specified position. + return nullptr; + + // And find the first attribute with feature. + it = std::find_if(it, aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsFeature(); } ); + return it == aAttribs.end() ? nullptr : it->get(); +} + +void CharAttribList::DeleteEmptyAttribs() +{ + std::erase_if(aAttribs, [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsEmpty(); } ); + bHasEmptyAttribs = false; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CharAttribList::DbgCheckAttribs(CharAttribList const& rAttribs) +{ + std::set<std::pair<sal_Int32, sal_uInt16>> zero_set; + for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs.aAttribs) + { + assert(rAttr->GetStart() <= rAttr->GetEnd()); + assert(!rAttr->IsFeature() || rAttr->GetLen() == 1); + if (0 == rAttr->GetLen()) + { + // not sure if 0-length attributes allowed at all in non-empty para? + assert(zero_set.insert(std::make_pair(rAttr->GetStart(), rAttr->Which())).second && "duplicate 0-length attribute detected"); + } + } + CheckOrderedList(rAttribs.GetAttribs()); +} +#endif + +void CharAttribList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("CharAttribList")); + for (auto const & i : aAttribs) { + i->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); +} + +EditEngineItemPool::EditEngineItemPool() + : SfxItemPool( "EditEngineItemPool", EE_ITEMS_START, EE_ITEMS_END, + aItemInfos, nullptr ) +{ + m_xDefItems = EditDLL::Get().GetGlobalData()->GetDefItems(); + SetDefaults(&m_xDefItems->getDefaults()); +} + +EditEngineItemPool::~EditEngineItemPool() +{ + ClearDefaults(); + SetSecondaryPool(nullptr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx new file mode 100644 index 0000000000..4dbb93ce2c --- /dev/null +++ b/editeng/source/editeng/editeng.cxx @@ -0,0 +1,2938 @@ +/* -*- 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 <memory> +#include <utility> + +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <config_global.h> +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> + +#include <tools/stream.hxx> + +#include <editeng/svxfont.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editstat.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <editeng/flditem.hxx> +#include <editeng/txtrange.hxx> +#include <editeng/cmapitem.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> + +#include <sot/exchange.hxx> +#include <sot/formats.hxx> + +#include <editeng/numitem.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <vcl/help.hxx> +#include <vcl/transfer.hxx> +#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> +#include <com/sun/star/frame/Desktop.hpp> + +#if OSL_DEBUG_LEVEL > 1 +#include <editeng/frmdiritem.hxx> +#endif +#include <basegfx/polygon/b2dpolygon.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + + +#if (OSL_DEBUG_LEVEL > 1) || defined ( DBG_UTIL ) +static bool bDebugPaint = false; +#endif + + +static rtl::Reference<SfxItemPool> pGlobalPool; + +EditEngine::EditEngine( SfxItemPool* pItemPool ) +{ + pImpEditEngine.reset( new ImpEditEngine( this, pItemPool ) ); +} + +EditEngine::~EditEngine() +{ +} + +void EditEngine::EnableUndo( bool bEnable ) +{ + pImpEditEngine->EnableUndo( bEnable ); +} + +bool EditEngine::IsUndoEnabled() const +{ + return pImpEditEngine->IsUndoEnabled(); +} + +bool EditEngine::IsInUndo() const +{ + return pImpEditEngine->IsInUndo(); +} + +EditUndoManager& EditEngine::GetUndoManager() +{ + return pImpEditEngine->GetUndoManager(); +} + +EditUndoManager* EditEngine::SetUndoManager(EditUndoManager* pNew) +{ + return pImpEditEngine->SetUndoManager(pNew); +} + +void EditEngine::UndoActionStart( sal_uInt16 nId ) +{ + DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionStart in Undomode!" ); + if ( !pImpEditEngine->IsInUndo() ) + pImpEditEngine->UndoActionStart( nId ); +} + +void EditEngine::UndoActionStart(sal_uInt16 nId, const ESelection& rSel) +{ + pImpEditEngine->UndoActionStart(nId, rSel); +} + +void EditEngine::UndoActionEnd() +{ + DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionEnd in Undomode!" ); + if ( !pImpEditEngine->IsInUndo() ) + pImpEditEngine->UndoActionEnd(); +} + +bool EditEngine::HasTriedMergeOnLastAddUndo() const +{ + return pImpEditEngine->mbLastTryMerge; +} + +void EditEngine::SetRefDevice( OutputDevice* pRefDev ) +{ + pImpEditEngine->SetRefDevice( pRefDev ); +} + +OutputDevice* EditEngine::GetRefDevice() const +{ + return pImpEditEngine->GetRefDevice(); +} + +void EditEngine::SetRefMapMode( const MapMode& rMapMode ) +{ + pImpEditEngine->SetRefMapMode( rMapMode ); +} + +MapMode const & EditEngine::GetRefMapMode() const +{ + return pImpEditEngine->GetRefMapMode(); +} + +void EditEngine::SetBackgroundColor( const Color& rColor ) +{ + pImpEditEngine->SetBackgroundColor( rColor ); +} + +Color const & EditEngine::GetBackgroundColor() const +{ + return pImpEditEngine->GetBackgroundColor(); +} + +Color EditEngine::GetAutoColor() const +{ + return pImpEditEngine->GetAutoColor(); +} + +void EditEngine::EnableAutoColor( bool b ) +{ + pImpEditEngine->EnableAutoColor( b ); +} + +void EditEngine::ForceAutoColor( bool b ) +{ + pImpEditEngine->ForceAutoColor( b ); +} + +bool EditEngine::IsForceAutoColor() const +{ + return pImpEditEngine->IsForceAutoColor(); +} + +const SfxItemSet& EditEngine::GetEmptyItemSet() const +{ + return pImpEditEngine->GetEmptyItemSet(); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect ) +{ + Draw( rOutDev, rOutRect, Point( 0, 0 ) ); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation ) +{ + // Create with 2 points, as with positive points it will end up with + // LONGMAX as Size, Bottom and Right in the range > LONGMAX. + tools::Rectangle aBigRect( -0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF ); + if( rOutDev.GetConnectMetaFile() ) + rOutDev.Push(); + Point aStartPos( rStartPos ); + if ( IsEffectivelyVertical() ) + { + aStartPos.AdjustX(GetPaperSize().Width() ); + rStartPos.RotateAround(aStartPos, nOrientation); + } + pImpEditEngine->Paint(rOutDev, aBigRect, aStartPos, false, nOrientation); + if( rOutDev.GetConnectMetaFile() ) + rOutDev.Pop(); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos ) +{ + Draw( rOutDev, rOutRect, rStartDocPos, true ); +} + +void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos, bool bClip ) +{ +#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1) + if ( bDebugPaint ) + DumpData(this, false); +#endif + + // Align to the pixel boundary, so that it becomes exactly the same + // as Paint () + tools::Rectangle aOutRect( rOutDev.LogicToPixel( rOutRect ) ); + aOutRect = rOutDev.PixelToLogic( aOutRect ); + + Point aStartPos; + if ( !IsEffectivelyVertical() ) + { + aStartPos.setX( aOutRect.Left() - rStartDocPos.X() ); + aStartPos.setY( aOutRect.Top() - rStartDocPos.Y() ); + } + else + { + aStartPos.setX( aOutRect.Right() + rStartDocPos.Y() ); + aStartPos.setY( aOutRect.Top() - rStartDocPos.X() ); + } + + bool bClipRegion = rOutDev.IsClipRegion(); + bool bMetafile = rOutDev.GetConnectMetaFile(); + vcl::Region aOldRegion = rOutDev.GetClipRegion(); + + // If one existed => intersection! + // Use Push/pop for creating the Meta file + if ( bMetafile ) + rOutDev.Push(); + + // Always use the Intersect method, it is a must for Metafile! + if ( bClip ) + { + // Clip only if necessary... + if ( rStartDocPos.X() || rStartDocPos.Y() || + ( rOutRect.GetHeight() < static_cast<tools::Long>(GetTextHeight()) ) || + ( rOutRect.GetWidth() < static_cast<tools::Long>(CalcTextWidth()) ) ) + { + // Some printer drivers cause problems if characters graze the + // ClipRegion, therefore rather add a pixel more ... + tools::Rectangle aClipRect( aOutRect ); + if ( rOutDev.GetOutDevType() == OUTDEV_PRINTER ) + { + Size aPixSz( 1, 0 ); + aPixSz = rOutDev.PixelToLogic( aPixSz ); + aClipRect.AdjustRight(aPixSz.Width() ); + aClipRect.AdjustBottom(aPixSz.Width() ); + } + rOutDev.IntersectClipRegion( aClipRect ); + } + } + + pImpEditEngine->Paint( rOutDev, aOutRect, aStartPos ); + + if ( bMetafile ) + rOutDev.Pop(); + else if ( bClipRegion ) + rOutDev.SetClipRegion( aOldRegion ); + else + rOutDev.SetClipRegion(); +} + +void EditEngine::InsertView(EditView* pEditView, size_t nIndex) +{ + + if ( nIndex > pImpEditEngine->GetEditViews().size() ) + nIndex = pImpEditEngine->GetEditViews().size(); + + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + rViews.insert(rViews.begin()+nIndex, pEditView); + + EditSelection aStartSel = pImpEditEngine->GetEditDoc().GetStartPaM(); + pEditView->pImpEditView->SetEditSelection( aStartSel ); + if ( !pImpEditEngine->GetActiveView() ) + pImpEditEngine->SetActiveView( pEditView ); + + pEditView->pImpEditView->AddDragAndDropListeners(); +} + +EditView* EditEngine::RemoveView( EditView* pView ) +{ + + pView->HideCursor(); + EditView* pRemoved = nullptr; + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + ImpEditEngine::ViewsType::iterator it = std::find(rViews.begin(), rViews.end(), pView); + + DBG_ASSERT( it != rViews.end(), "RemoveView with invalid index" ); + if (it != rViews.end()) + { + pRemoved = *it; + rViews.erase(it); + if ( pImpEditEngine->GetActiveView() == pView ) + { + pImpEditEngine->SetActiveView( nullptr ); + pImpEditEngine->GetSelEngine().SetCurView( nullptr ); + } + pView->pImpEditView->RemoveDragAndDropListeners(); + + } + return pRemoved; +} + +void EditEngine::RemoveView(size_t nIndex) +{ + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + if (nIndex >= rViews.size()) + return; + + EditView* pView = rViews[nIndex]; + if ( pView ) + RemoveView( pView ); +} + +EditView* EditEngine::GetView(size_t nIndex) const +{ + return pImpEditEngine->GetEditViews()[nIndex]; +} + +size_t EditEngine::GetViewCount() const +{ + return pImpEditEngine->GetEditViews().size(); +} + +bool EditEngine::HasView( EditView* pView ) const +{ + ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews(); + return std::find(rViews.begin(), rViews.end(), pView) != rViews.end(); +} + +EditView* EditEngine::GetActiveView() const +{ + return pImpEditEngine->GetActiveView(); +} + +void EditEngine::SetActiveView(EditView* pView) +{ + pImpEditEngine->SetActiveView(pView); +} + +void EditEngine::SetDefTab( sal_uInt16 nDefTab ) +{ + pImpEditEngine->GetEditDoc().SetDefTab( nDefTab ); + if ( pImpEditEngine->IsFormatted() ) + { + pImpEditEngine->FormatFullDoc(); + pImpEditEngine->UpdateViews(); + } +} + +void EditEngine::SetPaperSize( const Size& rNewSize ) +{ + + Size aOldSize( pImpEditEngine->GetPaperSize() ); + pImpEditEngine->SetValidPaperSize( rNewSize ); + Size aNewSize( pImpEditEngine->GetPaperSize() ); + + bool bAutoPageSize = pImpEditEngine->GetStatus().AutoPageSize(); + if ( !(bAutoPageSize || ( aNewSize.Width() != aOldSize.Width() )) ) + return; + + for (EditView* pView : pImpEditEngine->aEditViews) + { + if ( bAutoPageSize ) + pView->pImpEditView->RecalcOutputArea(); + else if ( pView->pImpEditView->DoAutoSize() ) + { + pView->pImpEditView->ResetOutputArea( tools::Rectangle( + pView->pImpEditView->GetOutputArea().TopLeft(), aNewSize ) ); + } + } + + if ( bAutoPageSize || pImpEditEngine->IsFormatted() ) + { + // Changing the width has no effect for AutoPageSize, as this is + // determined by the text width. + // Optimization first after Vobis delivery was enabled ... + pImpEditEngine->FormatFullDoc(); + + pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() ); + + if ( pImpEditEngine->IsUpdateLayout() && pImpEditEngine->GetActiveView() ) + pImpEditEngine->pActiveView->ShowCursor( false, false ); + } +} + +const Size& EditEngine::GetPaperSize() const +{ + return pImpEditEngine->GetPaperSize(); +} + +void EditEngine::SetVertical(bool bVertical) +{ + pImpEditEngine->SetVertical(bVertical); +} + +void EditEngine::SetRotation(TextRotation nRotation) +{ + pImpEditEngine->SetRotation(nRotation); +} + +TextRotation EditEngine::GetRotation() const +{ + return pImpEditEngine->GetRotation(); +} + +bool EditEngine::IsEffectivelyVertical() const +{ + return pImpEditEngine->IsEffectivelyVertical(); +} + +bool EditEngine::IsTopToBottom() const +{ + return pImpEditEngine->IsTopToBottom(); +} + +bool EditEngine::GetVertical() const +{ + return pImpEditEngine->GetVertical(); +} + +void EditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + pImpEditEngine->SetTextColumns(nColumns, nSpacing); +} + +void EditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + pImpEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +} + +void EditEngine::SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) +{ + pImpEditEngine->SetDefaultHorizontalTextDirection( eHTextDir ); +} + +EEHorizontalTextDirection EditEngine::GetDefaultHorizontalTextDirection() const +{ + return pImpEditEngine->GetDefaultHorizontalTextDirection(); +} + +SvtScriptType EditEngine::GetScriptType( const ESelection& rSelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) ); + return pImpEditEngine->GetItemScriptType( aSel ); +} + +editeng::LanguageSpan EditEngine::GetLanguage(const EditPaM& rPaM) const +{ + return pImpEditEngine->GetLanguage(rPaM); +} + +editeng::LanguageSpan EditEngine::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + DBG_ASSERT( pNode, "GetLanguage - nPara is invalid!" ); + return pNode ? pImpEditEngine->GetLanguage( EditPaM( pNode, nPos ) ) : editeng::LanguageSpan{}; +} + + +void EditEngine::TransliterateText( const ESelection& rSelection, TransliterationFlags nTransliterationMode ) +{ + pImpEditEngine->TransliterateText( pImpEditEngine->CreateSel( rSelection ), nTransliterationMode ); +} + +EditSelection EditEngine::TransliterateText(const EditSelection& rSelection, TransliterationFlags nTransliterationMode) +{ + return pImpEditEngine->TransliterateText(rSelection, nTransliterationMode); +} + +void EditEngine::SetAsianCompressionMode( CharCompressType n ) +{ + pImpEditEngine->SetAsianCompressionMode( n ); +} + +void EditEngine::SetKernAsianPunctuation( bool b ) +{ + pImpEditEngine->SetKernAsianPunctuation( b ); +} + +void EditEngine::SetAddExtLeading( bool b ) +{ + pImpEditEngine->SetAddExtLeading( b ); +} + +void EditEngine::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + SetPolygon( rPolyPolygon, nullptr ); +} + +void EditEngine::SetPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DPolyPolygon* pLinePolyPolygon) +{ + bool bSimple(false); + + if(pLinePolyPolygon && 1 == rPolyPolygon.count()) + { + if(rPolyPolygon.getB2DPolygon(0).isClosed()) + { + // open polygon + bSimple = true; + } + } + + TextRanger* pRanger = new TextRanger( rPolyPolygon, pLinePolyPolygon, 30, 2, 2, bSimple, true ); + pImpEditEngine->SetTextRanger( std::unique_ptr<TextRanger>(pRanger) ); + pImpEditEngine->SetPaperSize( pRanger->GetBoundRect().GetSize() ); +} + +void EditEngine::ClearPolygon() +{ + pImpEditEngine->SetTextRanger( nullptr ); +} + +const Size& EditEngine::GetMinAutoPaperSize() const +{ + return pImpEditEngine->GetMinAutoPaperSize(); +} + +void EditEngine::SetMinAutoPaperSize( const Size& rSz ) +{ + pImpEditEngine->SetMinAutoPaperSize( rSz ); +} + +const Size& EditEngine::GetMaxAutoPaperSize() const +{ + return pImpEditEngine->GetMaxAutoPaperSize(); +} + +void EditEngine::SetMaxAutoPaperSize( const Size& rSz ) +{ + pImpEditEngine->SetMaxAutoPaperSize( rSz ); +} + +void EditEngine::SetMinColumnWrapHeight(tools::Long nVal) +{ + pImpEditEngine->SetMinColumnWrapHeight(nVal); +} + +OUString EditEngine::GetText( LineEnd eEnd ) const +{ + return pImpEditEngine->GetEditDoc().GetText( eEnd ); +} + +OUString EditEngine::GetText( const ESelection& rESelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) ); + return pImpEditEngine->GetSelected( aSel ); +} + +sal_Int32 EditEngine::GetTextLen() const +{ + return pImpEditEngine->GetEditDoc().GetTextLen(); +} + +sal_Int32 EditEngine::GetParagraphCount() const +{ + return pImpEditEngine->maEditDoc.Count(); +} + +sal_Int32 EditEngine::GetLineCount( sal_Int32 nParagraph ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineCount( nParagraph ); +} + +sal_Int32 EditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineLen( nParagraph, nLine ); +} + +void EditEngine::GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineBoundaries( rStart, rEnd, nParagraph, nLine ); +} + +sal_Int32 EditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineNumberAtIndex( nPara, nIndex ); +} + +sal_uInt32 EditEngine::GetLineHeight( sal_Int32 nParagraph ) +{ + // If someone calls GetLineHeight() with an empty Engine. + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + return pImpEditEngine->GetLineHeight( nParagraph, 0 ); +} + +tools::Rectangle EditEngine::GetParaBounds( sal_Int32 nPara ) +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + Point aPnt = GetDocPosTopLeft( nPara ); + + if( IsEffectivelyVertical() ) + { + sal_Int32 nTextHeight = pImpEditEngine->GetTextHeight(); + sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true ); + sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara ); + + return tools::Rectangle( nTextHeight - aPnt.Y() - nParaHeight, 0, nTextHeight - aPnt.Y(), nParaWidth ); + } + else + { + sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true ); + sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara ); + + return tools::Rectangle( 0, aPnt.Y(), nParaWidth, aPnt.Y() + nParaHeight ); + } +} + +sal_uInt32 EditEngine::GetTextHeight( sal_Int32 nParagraph ) const +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nHeight = pImpEditEngine->GetParaHeight( nParagraph ); + return nHeight; +} + +OUString EditEngine::GetWord( sal_Int32 nPara, sal_Int32 nIndex ) +{ + ESelection aESel( nPara, nIndex, nPara, nIndex ); + EditSelection aSel( pImpEditEngine->CreateSel( aESel ) ); + aSel = pImpEditEngine->SelectWord( aSel ); + return pImpEditEngine->GetSelected( aSel ); +} + +ESelection EditEngine::GetWord( const ESelection& rSelection, sal_uInt16 nWordType ) const +{ + // ImpEditEngine-Iteration-Methods should be const! + EditEngine* pE = const_cast<EditEngine*>(this); + + EditSelection aSel( pE->pImpEditEngine->CreateSel( rSelection ) ); + aSel = pE->pImpEditEngine->SelectWord( aSel, nWordType ); + return pE->pImpEditEngine->CreateESel( aSel ); +} + +void EditEngine::CheckIdleFormatter() +{ + pImpEditEngine->CheckIdleFormatter(); +} + +bool EditEngine::IsIdleFormatterActive() const +{ + return pImpEditEngine->aIdleFormatter.IsActive(); +} + +ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode) +{ + return pImpEditEngine->FindParaPortion(pNode); +} + +const ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode) const +{ + return pImpEditEngine->FindParaPortion(pNode); +} + +const ParaPortion* EditEngine::GetPrevVisPortion(const ParaPortion* pCurPortion) const +{ + return pImpEditEngine->GetPrevVisPortion(pCurPortion); +} + +SvtScriptType EditEngine::GetScriptType(const EditSelection& rSel) const +{ + return pImpEditEngine->GetItemScriptType(rSel); +} + +void EditEngine::RemoveParaPortion(sal_Int32 nNode) +{ + pImpEditEngine->GetParaPortions().Remove(nNode); +} + +void EditEngine::SetCallParaInsertedOrDeleted(bool b) +{ + pImpEditEngine->SetCallParaInsertedOrDeleted(b); +} + +bool EditEngine::IsCallParaInsertedOrDeleted() const +{ + return pImpEditEngine->IsCallParaInsertedOrDeleted(); +} + +void EditEngine::AppendDeletedNodeInfo(DeletedNodeInfo* pInfo) +{ + pImpEditEngine->aDeletedNodes.push_back(std::unique_ptr<DeletedNodeInfo>(pInfo)); +} + +void EditEngine::UpdateSelections() +{ + pImpEditEngine->UpdateSelections(); +} + +void EditEngine::InsertContent(ContentNode* pNode, sal_Int32 nPos) +{ + pImpEditEngine->InsertContent(pNode, nPos); +} + +EditPaM EditEngine::SplitContent(sal_Int32 nNode, sal_Int32 nSepPos) +{ + return pImpEditEngine->SplitContent(nNode, nSepPos); +} + +EditPaM EditEngine::ConnectContents(sal_Int32 nLeftNode, bool bBackward) +{ + return pImpEditEngine->ConnectContents(nLeftNode, bBackward); +} + +void EditEngine::InsertFeature(const EditSelection& rEditSelection, const SfxPoolItem& rItem) +{ + pImpEditEngine->ImpInsertFeature(rEditSelection, rItem); +} + +EditSelection EditEngine::MoveParagraphs(const Range& rParagraphs, sal_Int32 nNewPos) +{ + return pImpEditEngine->MoveParagraphs(rParagraphs, nNewPos, nullptr); +} + +void EditEngine::RemoveCharAttribs(sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures) +{ + pImpEditEngine->RemoveCharAttribs(nPara, nWhich, bRemoveFeatures); +} + +void EditEngine::RemoveCharAttribs(const EditSelection& rSel, bool bRemoveParaAttribs, sal_uInt16 nWhich) +{ + const EERemoveParaAttribsMode eMode = bRemoveParaAttribs? + EERemoveParaAttribsMode::RemoveAll : + EERemoveParaAttribsMode::RemoveCharItems; + pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich); +} + +void EditEngine::RemoveCharAttribs(const EditSelection& rSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich) +{ + pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich); +} + +EditEngine::ViewsType& EditEngine::GetEditViews() +{ + return pImpEditEngine->GetEditViews(); +} + +const EditEngine::ViewsType& EditEngine::GetEditViews() const +{ + return pImpEditEngine->GetEditViews(); +} + +void EditEngine::SetUndoMode(bool b) +{ + pImpEditEngine->SetUndoMode(b); +} + +void EditEngine::FormatAndLayout(EditView* pCurView, bool bCalledFromUndo) +{ + pImpEditEngine->FormatAndLayout(pCurView, bCalledFromUndo); +} + +void EditEngine::Undo(EditView* pView) +{ + pImpEditEngine->Undo(pView); +} + +void EditEngine::Redo(EditView* pView) +{ + pImpEditEngine->Redo(pView); +} + +uno::Reference<datatransfer::XTransferable> EditEngine::CreateTransferable(const EditSelection& rSelection) +{ + return pImpEditEngine->CreateTransferable(rSelection); +} + +void EditEngine::ParaAttribsToCharAttribs(ContentNode* pNode) +{ + pImpEditEngine->ParaAttribsToCharAttribs(pNode); +} + +EditPaM EditEngine::CreateEditPaM(const EPaM& rEPaM) +{ + return pImpEditEngine->CreateEditPaM(rEPaM); +} + +EditPaM EditEngine::ConnectParagraphs( + ContentNode* pLeft, ContentNode* pRight, bool bBackward) +{ + return pImpEditEngine->ImpConnectParagraphs(pLeft, pRight, bBackward); +} + +EditPaM EditEngine::InsertField(const EditSelection& rEditSelection, const SvxFieldItem& rFld) +{ + return pImpEditEngine->InsertField(rEditSelection, rFld); +} + +EditPaM EditEngine::InsertText(const EditSelection& aCurEditSelection, const OUString& rStr) +{ + return pImpEditEngine->InsertText(aCurEditSelection, rStr); +} + +EditSelection EditEngine::InsertText(const EditTextObject& rTextObject, const EditSelection& rSel) +{ + return pImpEditEngine->InsertText(rTextObject, rSel); +} + +EditSelection EditEngine::InsertText( + uno::Reference<datatransfer::XTransferable > const & rxDataObj, + const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format) +{ + return pImpEditEngine->PasteText(rxDataObj, rBaseURL, rPaM, bUseSpecial, format); +} + +EditPaM EditEngine::EndOfWord(const EditPaM& rPaM) +{ + return pImpEditEngine->EndOfWord(rPaM); +} + +EditPaM EditEngine::GetPaM(const Point& aDocPos, bool bSmart) +{ + return pImpEditEngine->GetPaM(aDocPos, bSmart); +} + +EditSelection EditEngine::SelectWord( + const EditSelection& rCurSelection, sal_Int16 nWordType) +{ + return pImpEditEngine->SelectWord(rCurSelection, nWordType); +} + +tools::Long EditEngine::GetXPos( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const +{ + return pImpEditEngine->GetXPos(pParaPortion, pLine, nIndex, bPreferPortionStart); +} + +Range EditEngine::GetLineXPosStartEnd( + const ParaPortion* pParaPortion, const EditLine* pLine) const +{ + return pImpEditEngine->GetLineXPosStartEnd(pParaPortion, pLine); +} + +bool EditEngine::IsFormatted() const +{ + return pImpEditEngine->IsFormatted(); +} + +EditPaM EditEngine::CursorLeft(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode) +{ + return pImpEditEngine->CursorLeft(rPaM, nCharacterIteratorMode); +} + +EditPaM EditEngine::CursorRight(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode) +{ + return pImpEditEngine->CursorRight(rPaM, nCharacterIteratorMode); +} + +InternalEditStatus& EditEngine::GetInternalEditStatus() +{ + return pImpEditEngine->GetStatus(); +} + +EditDoc& EditEngine::GetEditDoc() +{ + return pImpEditEngine->GetEditDoc(); +} + +const EditDoc& EditEngine::GetEditDoc() const +{ + return pImpEditEngine->GetEditDoc(); +} + +void EditEngine::dumpAsXmlEditDoc(xmlTextWriterPtr pWriter) const +{ + pImpEditEngine->GetEditDoc().dumpAsXml(pWriter); +} + +ParaPortionList& EditEngine::GetParaPortions() +{ + return pImpEditEngine->GetParaPortions(); +} + +const ParaPortionList& EditEngine::GetParaPortions() const +{ + return pImpEditEngine->GetParaPortions(); +} + +void EditEngine::SeekCursor(ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont) +{ + pImpEditEngine->SeekCursor(pNode, nPos, rFont); +} + +EditPaM EditEngine::DeleteSelection(const EditSelection& rSel) +{ + return pImpEditEngine->ImpDeleteSelection(rSel); +} + +ESelection EditEngine::CreateESelection(const EditSelection& rSel) const +{ + return pImpEditEngine->CreateESel(rSel); +} + +EditSelection EditEngine::CreateSelection(const ESelection& rSel) +{ + return pImpEditEngine->CreateSel(rSel); +} + +const SfxItemSet& EditEngine::GetBaseParaAttribs(sal_Int32 nPara) const +{ + return pImpEditEngine->GetParaAttribs(nPara); +} + +void EditEngine::SetParaAttribsOnly(sal_Int32 nPara, const SfxItemSet& rSet) +{ + pImpEditEngine->SetParaAttribs(nPara, rSet); +} + +void EditEngine::SetAttribs(const EditSelection& rSel, const SfxItemSet& rSet, SetAttribsMode nSpecial) +{ + pImpEditEngine->SetAttribs(rSel, rSet, nSpecial); +} + +OUString EditEngine::GetSelected(const EditSelection& rSel) const +{ + return pImpEditEngine->GetSelected(rSel); +} + +EditPaM EditEngine::DeleteSelected(const EditSelection& rSel) +{ + return pImpEditEngine->DeleteSelected(rSel); +} + +void EditEngine::HandleBeginPasteOrDrop(PasteOrDropInfos& rInfos) +{ + pImpEditEngine->aBeginPasteOrDropHdl.Call(rInfos); +} + +void EditEngine::HandleEndPasteOrDrop(PasteOrDropInfos& rInfos) +{ + pImpEditEngine->aEndPasteOrDropHdl.Call(rInfos); +} + +bool EditEngine::HasText() const +{ + return pImpEditEngine->ImplHasText(); +} + +const EditSelectionEngine& EditEngine::GetSelectionEngine() const +{ + return pImpEditEngine->aSelEngine; +} + +void EditEngine::SetInSelectionMode(bool b) +{ + pImpEditEngine->mbInSelection = b; +} + +bool EditEngine::PostKeyEvent( const KeyEvent& rKeyEvent, EditView* pEditView, vcl::Window const * pFrameWin ) +{ + DBG_ASSERT( pEditView, "no View - no cookie !" ); + + bool bDone = true; + + bool bModified = false; + bool bMoved = false; + bool bAllowIdle = true; + bool bReadOnly = pEditView->IsReadOnly(); + + GetCursorFlags nNewCursorFlags = GetCursorFlags::NONE; + bool bSetCursorFlags = true; + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + DBG_ASSERT( !aCurSel.IsInvalid(), "Blinde Selection in EditEngine::PostKeyEvent" ); + + OUString aAutoText( pImpEditEngine->GetAutoCompleteText() ); + if (!pImpEditEngine->GetAutoCompleteText().isEmpty()) + pImpEditEngine->SetAutoCompleteText(OUString(), true); + + sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::UNDO: + { + if ( !bReadOnly ) + pEditView->Undo(); + return true; + } + case KeyFuncType::REDO: + { + if ( !bReadOnly ) + pEditView->Redo(); + return true; + } + + default: // is then possible edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( nCode ) + { +#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1) + case KEY_F1: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + sal_Int32 nParas = GetParagraphCount(); + Point aPos; + Point aViewStart( pEditView->GetOutputArea().TopLeft() ); + tools::Long n20 = 40 * pImpEditEngine->nOnePixelInRef; + for ( sal_Int32 n = 0; n < nParas; n++ ) + { + tools::Long nH = GetTextHeight( n ); + Point P1( aViewStart.X() + n20 + n20*(n%2), aViewStart.Y() + aPos.Y() ); + Point P2( P1 ); + P2.AdjustX(n20 ); + P2.AdjustY(nH ); + pEditView->GetWindow()->GetOutDev()->SetLineColor(); + pEditView->GetWindow()->GetOutDev()->SetFillColor( (n%2) ? COL_YELLOW : COL_LIGHTGREEN ); + pEditView->GetWindow()->GetOutDev()->DrawRect( tools::Rectangle( P1, P2 ) ); + aPos.AdjustY(nH ); + } + } + bDone = false; + } + break; + case KEY_F11: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + bDebugPaint = !bDebugPaint; + OStringBuffer aInfo("DebugPaint: "); + aInfo.append(bDebugPaint ? "On" : "Off"); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pEditView->GetWindow()->GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + OStringToOUString(aInfo, RTL_TEXTENCODING_ASCII_US))); + xInfoBox->run(); + + } + bDone = false; + } + break; + case KEY_F12: + { + if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + DumpData(this, true); + bDone = false; + } + break; +#endif + case KEY_UP: + case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + case css::awt::Key::MOVE_WORD_FORWARD: + case css::awt::Key::SELECT_WORD_FORWARD: + case css::awt::Key::MOVE_WORD_BACKWARD: + case css::awt::Key::SELECT_WORD_BACKWARD: + case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: + case css::awt::Key::MOVE_TO_END_OF_LINE: + case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: + case css::awt::Key::SELECT_TO_END_OF_LINE: + case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: + case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: + case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: + case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: + case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: + case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: + { + if ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) + { + if ( ImpEditEngine::DoVisualCursorTraveling() && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) /* || ( nCode == KEY_HOME ) || ( nCode == KEY_END ) */ ) ) + bSetCursorFlags = false; // Will be manipulated within visual cursor move + + aCurSel = pImpEditEngine->MoveCursor( rKeyEvent, pEditView ); + + if ( aCurSel.HasRange() ) { + Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection()); + pEditView->pImpEditView->CutCopy( aSelection, false ); + } + + bMoved = true; + if ( nCode == KEY_HOME ) + nNewCursorFlags |= GetCursorFlags::StartOfLine; + else if ( nCode == KEY_END ) + nNewCursorFlags |= GetCursorFlags::EndOfLine; + + } +#if OSL_DEBUG_LEVEL > 1 + GetLanguage( pImpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ), aCurSel.Max().GetIndex() ); +#endif + } + break; + case KEY_BACKSPACE: + case KEY_DELETE: + case css::awt::Key::DELETE_WORD_BACKWARD: + case css::awt::Key::DELETE_WORD_FORWARD: + case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH: + case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH: + { + if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod2() ) + { + // check if we are behind a bullet and using the backspace key + ContentNode *pNode = aCurSel.Min().GetNode(); + const SvxNumberFormat *pFmt = pImpEditEngine->GetNumberFormat( pNode ); + if (pFmt && nCode == KEY_BACKSPACE && + !aCurSel.HasRange() && aCurSel.Min().GetIndex() == 0) + { + // if the bullet is still visible, just make it invisible. + // Otherwise continue as usual. + + + sal_Int32 nPara = pImpEditEngine->GetEditDoc().GetPos( pNode ); + SfxBoolItem aBulletState( pImpEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ) ); + + if ( aBulletState.GetValue() ) + { + + aBulletState.SetValue( false ); + SfxItemSet aSet( pImpEditEngine->GetParaAttribs( nPara ) ); + aSet.Put( aBulletState ); + pImpEditEngine->SetParaAttribs( nPara, aSet ); + + // have this and the following paragraphs formatted and repainted. + // (not painting a numbering in the list may cause the following + // numberings to have different numbers than before and thus the + // length may have changed as well ) + pImpEditEngine->FormatAndLayout( pImpEditEngine->GetActiveView() ); + + break; + } + } + + sal_uInt8 nDel = 0; + DeleteMode nMode = DeleteMode::Simple; + switch( nCode ) + { + case css::awt::Key::DELETE_WORD_BACKWARD: + nMode = DeleteMode::RestOfWord; + nDel = DEL_LEFT; + break; + case css::awt::Key::DELETE_WORD_FORWARD: + nMode = DeleteMode::RestOfWord; + nDel = DEL_RIGHT; + break; + case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH: + nMode = DeleteMode::RestOfContent; + nDel = DEL_LEFT; + break; + case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH: + nMode = DeleteMode::RestOfContent; + nDel = DEL_RIGHT; + break; + default: + nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT; + nMode = rKeyEvent.GetKeyCode().IsMod1() ? DeleteMode::RestOfWord : DeleteMode::Simple; + if ( ( nMode == DeleteMode::RestOfWord ) && rKeyEvent.GetKeyCode().IsShift() ) + nMode = DeleteMode::RestOfContent; + break; + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pImpEditEngine->UndoActionStart( EDITUNDO_DELETE ); + aCurSel = pImpEditEngine->DeleteLeftOrRight( aCurSel, nDel, nMode ); + pImpEditEngine->UndoActionEnd(); + bModified = true; + bAllowIdle = false; + } + } + break; + case KEY_TAB: + { + if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + { + bool bShift = rKeyEvent.GetKeyCode().IsShift(); + if ( !bShift ) + { + bool bSel = pEditView->HasSelection(); + if ( bSel ) + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + if ( pImpEditEngine->GetStatus().DoAutoCorrect() ) + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertTab( aCurSel ); + if ( bSel ) + pImpEditEngine->UndoActionEnd(); + bModified = true; + } + } + else + bDone = false; + } + break; + case KEY_RETURN: + { + if ( !bReadOnly ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + { + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + if ( rKeyEvent.GetKeyCode().IsShift() ) + { + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertLineBreak( aCurSel ); + } + else + { + if (aAutoText.isEmpty()) + { + if ( pImpEditEngine->GetStatus().DoAutoCorrect() ) + aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin ); + aCurSel = pImpEditEngine->InsertParaBreak( aCurSel ); + } + else + { + DBG_ASSERT( !aCurSel.HasRange(), "Selection on complete?!" ); + EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) ); + aCurSel = pImpEditEngine->InsertText( + EditSelection( aStart, aCurSel.Max() ), aAutoText ); + pImpEditEngine->SetAutoCompleteText( OUString(), true ); + } + } + pImpEditEngine->UndoActionEnd(); + bModified = true; + } + } + } + break; + case KEY_INSERT: + { + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + pEditView->SetInsertMode( !pEditView->IsInsertMode() ); + } + break; + default: + { + #if (OSL_DEBUG_LEVEL > 1) && defined(DBG_UTIL) + if ( ( nCode == KEY_W ) && rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() ) + { + SfxItemSet aAttribs = pEditView->GetAttribs(); + const SvxFrameDirectionItem& rCurrentWritingMode = (const SvxFrameDirectionItem&)aAttribs.Get( EE_PARA_WRITINGDIR ); + SvxFrameDirectionItem aNewItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR ); + if ( rCurrentWritingMode.GetValue() != SvxFrameDirection::Horizontal_RL_TB ) + aNewItem.SetValue( SvxFrameDirection::Horizontal_RL_TB ); + aAttribs.Put( aNewItem ); + pEditView->SetAttribs( aAttribs ); + } + #endif + if ( !bReadOnly && IsSimpleCharInput( rKeyEvent ) ) + { + sal_Unicode nCharCode = rKeyEvent.GetCharCode(); + pEditView->pImpEditView->DrawSelectionXOR(); + // Autocorrection? + if ( ( pImpEditEngine->GetStatus().DoAutoCorrect() ) && + ( SvxAutoCorrect::IsAutoCorrectChar( nCharCode ) || + pImpEditEngine->IsNbspRunNext() ) ) + { + aCurSel = pImpEditEngine->AutoCorrect( + aCurSel, nCharCode, !pEditView->IsInsertMode(), pFrameWin ); + } + else + { + aCurSel = pImpEditEngine->InsertTextUserInput( aCurSel, nCharCode, !pEditView->IsInsertMode() ); + } + // AutoComplete ??? + if ( pImpEditEngine->GetStatus().DoAutoComplete() && ( nCharCode != ' ' ) ) + { + // Only at end of word... + sal_Int32 nIndex = aCurSel.Max().GetIndex(); + if ( ( nIndex >= aCurSel.Max().GetNode()->Len() ) || + ( pImpEditEngine->aWordDelimiters.indexOf( aCurSel.Max().GetNode()->GetChar( nIndex ) ) != -1 ) ) + { + EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) ); + OUString aWord = pImpEditEngine->GetSelected( EditSelection( aStart, aCurSel.Max() ) ); + if ( aWord.getLength() >= 3 ) + { + OUString aComplete; + + LanguageType eLang = pImpEditEngine->GetLanguage( EditPaM( aStart.GetNode(), aStart.GetIndex()+1)).nLang; + LanguageTag aLanguageTag( eLang); + + if (!pImpEditEngine->xLocaleDataWrapper.isInitialized()) + pImpEditEngine->xLocaleDataWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), aLanguageTag); + else + pImpEditEngine->xLocaleDataWrapper.changeLocale( aLanguageTag); + + if (!pImpEditEngine->xTransliterationWrapper.isInitialized()) + pImpEditEngine->xTransliterationWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), eLang); + else + pImpEditEngine->xTransliterationWrapper.changeLocale( eLang); + + const ::utl::TransliterationWrapper* pTransliteration = pImpEditEngine->xTransliterationWrapper.get(); + Sequence< i18n::CalendarItem2 > xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarDays(); + sal_Int32 nCount = xItem.getLength(); + const i18n::CalendarItem2* pArr = xItem.getConstArray(); + for( sal_Int32 n = 0; n <= nCount; ++n ) + { + const OUString& rDay = pArr[n].FullName; + if( pTransliteration->isMatch( aWord, rDay) ) + { + aComplete = rDay; + break; + } + } + + if ( aComplete.isEmpty() ) + { + xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarMonths(); + sal_Int32 nMonthCount = xItem.getLength(); + const i18n::CalendarItem2* pMonthArr = xItem.getConstArray(); + for( sal_Int32 n = 0; n <= nMonthCount; ++n ) + { + const OUString& rMon = pMonthArr[n].FullName; + if( pTransliteration->isMatch( aWord, rMon) ) + { + aComplete = rMon; + break; + } + } + } + + if( !aComplete.isEmpty() && ( ( aWord.getLength() + 1 ) < aComplete.getLength() ) ) + { + pImpEditEngine->SetAutoCompleteText( aComplete, false ); + Point aPos = pImpEditEngine->PaMtoEditCursor( aCurSel.Max() ).TopLeft(); + aPos = pEditView->pImpEditView->GetWindowPos( aPos ); + aPos = pEditView->pImpEditView->GetWindow()->LogicToPixel( aPos ); + aPos = pEditView->GetWindow()->OutputToScreenPixel( aPos ); + aPos.AdjustY( -3 ); + Help::ShowQuickHelp( pEditView->GetWindow(), tools::Rectangle( aPos, Size( 1, 1 ) ), aComplete, QuickHelpFlags::Bottom|QuickHelpFlags::Left ); + } + } + } + } + bModified = true; + } + else + bDone = false; + } + } + } + + pEditView->pImpEditView->SetEditSelection( aCurSel ); + if (comphelper::LibreOfficeKit::isActive()) + { + pEditView->pImpEditView->DrawSelectionXOR(); + } + pImpEditEngine->UpdateSelections(); + + if ( ( !IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) ) || + ( IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) )) + { + pEditView->pImpEditView->nTravelXPos = TRAVEL_X_DONTKNOW; + } + + if ( /* ( nCode != KEY_HOME ) && ( nCode != KEY_END ) && */ + ( !IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) ) || + ( IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) )) + { + pEditView->pImpEditView->SetCursorBidiLevel( CURSOR_BIDILEVEL_DONTKNOW ); + } + + if ( bSetCursorFlags ) + pEditView->pImpEditView->nExtraCursorFlags = nNewCursorFlags; + + if ( bModified ) + { + DBG_ASSERT( !bReadOnly, "ReadOnly but modified???" ); + // Idle-Formatter only when AnyInput. + if ( bAllowIdle && pImpEditEngine->GetStatus().UseIdleFormatter() + && Application::AnyInput( VclInputFlags::KEYBOARD) ) + pImpEditEngine->IdleFormatAndLayout( pEditView ); + else + pImpEditEngine->FormatAndLayout( pEditView ); + } + else if ( bMoved ) + { + bool bGotoCursor = pEditView->pImpEditView->DoAutoScroll(); + pEditView->pImpEditView->ShowCursor( bGotoCursor, true ); + pImpEditEngine->CallStatusHdl(); + } + + return bDone; +} + +sal_uInt32 EditEngine::GetTextHeight() const +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nHeight = !IsEffectivelyVertical() ? pImpEditEngine->GetTextHeight() : pImpEditEngine->CalcTextWidth( true ); + return nHeight; +} + +sal_uInt32 EditEngine::GetTextHeightNTP() const +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + if ( IsEffectivelyVertical() ) + return pImpEditEngine->CalcTextWidth( true ); + + return pImpEditEngine->GetTextHeightNTP(); +} + +sal_uInt32 EditEngine::CalcTextWidth() +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + sal_uInt32 nWidth = !IsEffectivelyVertical() ? pImpEditEngine->CalcTextWidth( true ) : pImpEditEngine->GetTextHeight(); + return nWidth; +} + +bool EditEngine::SetUpdateLayout(bool bUpdate, bool bRestoring) +{ + bool bPrevUpdateLayout = pImpEditEngine->SetUpdateLayout( bUpdate ); + if (pImpEditEngine->pActiveView) + { + // Not an activation if we are restoring the previous update mode. + pImpEditEngine->pActiveView->ShowCursor(false, false, /*bActivate=*/!bRestoring); + } + return bPrevUpdateLayout; +} + +bool EditEngine::IsUpdateLayout() const +{ + return pImpEditEngine->IsUpdateLayout(); +} + +void EditEngine::Clear() +{ + pImpEditEngine->Clear(); +} + +void EditEngine::SetText( const OUString& rText ) +{ + pImpEditEngine->SetText( rText ); + if ( !rText.isEmpty() && pImpEditEngine->IsUpdateLayout() ) + pImpEditEngine->FormatAndLayout(); +} + +ErrCode EditEngine::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs /* = NULL */ ) +{ + bool bUndoEnabled = pImpEditEngine->IsUndoEnabled(); + pImpEditEngine->EnableUndo( false ); + pImpEditEngine->SetText( OUString() ); + EditPaM aPaM( pImpEditEngine->GetEditDoc().GetStartPaM() ); + pImpEditEngine->Read( rInput, rBaseURL, eFormat, EditSelection( aPaM, aPaM ), pHTTPHeaderAttrs ); + pImpEditEngine->EnableUndo( bUndoEnabled ); + return rInput.GetError(); +} + +void EditEngine::Write( SvStream& rOutput, EETextFormat eFormat ) +{ + EditPaM aStartPaM( pImpEditEngine->GetEditDoc().GetStartPaM() ); + EditPaM aEndPaM( pImpEditEngine->GetEditDoc().GetEndPaM() ); + pImpEditEngine->Write( rOutput, eFormat, EditSelection( aStartPaM, aEndPaM ) ); +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject() +{ + return pImpEditEngine->CreateTextObject(); +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( const ESelection& rESelection ) +{ + EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) ); + return pImpEditEngine->CreateTextObject( aSel ); +} + +std::unique_ptr<EditTextObject> EditEngine::GetEmptyTextObject() const +{ + return pImpEditEngine->GetEmptyTextObject(); +} + + +void EditEngine::SetText( const EditTextObject& rTextObject ) +{ + pImpEditEngine->SetText( rTextObject ); + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) +{ + pImpEditEngine->ShowParagraph( nParagraph, bShow ); +} + +void EditEngine::SetNotifyHdl( const Link<EENotify&,void>& rLink ) +{ + pImpEditEngine->SetNotifyHdl( rLink ); +} + +Link<EENotify&,void> const & EditEngine::GetNotifyHdl() const +{ + return pImpEditEngine->GetNotifyHdl(); +} + +void EditEngine::SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) +{ + pImpEditEngine->SetStatusEventHdl( rLink ); +} + +Link<EditStatus&, void> const & EditEngine::GetStatusEventHdl() const +{ + return pImpEditEngine->GetStatusEventHdl(); +} + +void EditEngine::SetHtmlImportHdl( const Link<HtmlImportInfo&,void>& rLink ) +{ + pImpEditEngine->aHtmlImportHdl = rLink; +} + +const Link<HtmlImportInfo&,void>& EditEngine::GetHtmlImportHdl() const +{ + return pImpEditEngine->aHtmlImportHdl; +} + +void EditEngine::SetRtfImportHdl( const Link<RtfImportInfo&,void>& rLink ) +{ + pImpEditEngine->aRtfImportHdl = rLink; +} + +const Link<RtfImportInfo&,void>& EditEngine::GetRtfImportHdl() const +{ + return pImpEditEngine->aRtfImportHdl; +} + +void EditEngine::SetBeginMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink ) +{ + pImpEditEngine->aBeginMovingParagraphsHdl = rLink; +} + +void EditEngine::SetEndMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink ) +{ + pImpEditEngine->aEndMovingParagraphsHdl = rLink; +} + +void EditEngine::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink ) +{ + + pImpEditEngine->aBeginPasteOrDropHdl = rLink; +} + +void EditEngine::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink ) +{ + pImpEditEngine->aEndPasteOrDropHdl = rLink; +} + +std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( sal_Int32 nPara, sal_Int32 nParas ) +{ + DBG_ASSERT( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count(), "CreateTextObject: Startpara out of Range" ); + DBG_ASSERT( nParas <= pImpEditEngine->GetEditDoc().Count() - nPara, "CreateTextObject: Endpara out of Range" ); + + ContentNode* pStartNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + ContentNode* pEndNode = pImpEditEngine->GetEditDoc().GetObject( nPara+nParas-1 ); + DBG_ASSERT( pStartNode, "Start-Paragraph does not exist: CreateTextObject" ); + DBG_ASSERT( pEndNode, "End-Paragraph does not exist: CreateTextObject" ); + + if ( pStartNode && pEndNode ) + { + EditSelection aTmpSel; + aTmpSel.Min() = EditPaM( pStartNode, 0 ); + aTmpSel.Max() = EditPaM( pEndNode, pEndNode->Len() ); + return pImpEditEngine->CreateTextObject( aTmpSel ); + } + return nullptr; +} + +void EditEngine::RemoveParagraph( sal_Int32 nPara ) +{ + DBG_ASSERT( pImpEditEngine->GetEditDoc().Count() > 1, "The first paragraph should not be deleted!" ); + if( pImpEditEngine->GetEditDoc().Count() <= 1 ) + return; + + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + const ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + DBG_ASSERT( pPortion && pNode, "Paragraph not found: RemoveParagraph" ); + if ( pNode && pPortion ) + { + // No Undo encapsulation needed. + pImpEditEngine->ImpRemoveParagraph( nPara ); + pImpEditEngine->InvalidateFromParagraph( nPara ); + pImpEditEngine->UpdateSelections(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + } +} + +sal_Int32 EditEngine::GetTextLen( sal_Int32 nPara ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + DBG_ASSERT( pNode, "Paragraph not found: GetTextLen" ); + if ( pNode ) + return pNode->Len(); + return 0; +} + +OUString EditEngine::GetText( sal_Int32 nPara ) const +{ + OUString aStr; + if ( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count() ) + aStr = pImpEditEngine->GetEditDoc().GetParaAsString( nPara ); + return aStr; +} + +void EditEngine::SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) +{ + pImpEditEngine->SetModifyHdl( rLink ); +} + +void EditEngine::ClearModifyFlag() +{ + pImpEditEngine->SetModifyFlag( false ); +} + +void EditEngine::SetModified() +{ + pImpEditEngine->SetModifyFlag( true ); +} + +bool EditEngine::IsModified() const +{ + return pImpEditEngine->IsModified(); +} + +bool EditEngine::IsInSelectionMode() const +{ + return ( pImpEditEngine->IsInSelectionMode() || + pImpEditEngine->GetSelEngine().IsInSelection() ); +} + +void EditEngine::InsertParagraph( sal_Int32 nPara, const EditTextObject& rTxtObj, bool bAppend ) +{ + if ( nPara > GetParagraphCount() ) + { + SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" ); + nPara = GetParagraphCount(); + } + + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + + // No Undo compounding needed. + EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) ); + // When InsertParagraph from the outside, no hard attributes + // should be taken over! + pImpEditEngine->RemoveCharAttribs( nPara ); + pImpEditEngine->InsertText( rTxtObj, EditSelection( aPaM, aPaM ) ); + + if ( bAppend && nPara ) + pImpEditEngine->ConnectContents( nPara-1, /*bBackwards=*/false ); + + pImpEditEngine->UndoActionEnd(); + + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::InsertParagraph(sal_Int32 nPara, const OUString& rTxt) +{ + if ( nPara > GetParagraphCount() ) + { + SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" ); + nPara = GetParagraphCount(); + } + + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) ); + // When InsertParagraph from the outside, no hard attributes + // should be taken over! + pImpEditEngine->RemoveCharAttribs( nPara ); + pImpEditEngine->UndoActionEnd(); + pImpEditEngine->ImpInsertText( EditSelection( aPaM, aPaM ), rTxt ); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::SetText(sal_Int32 nPara, const OUString& rTxt) +{ + std::optional<EditSelection> pSel = pImpEditEngine->SelectParagraph( nPara ); + if ( pSel ) + { + pImpEditEngine->UndoActionStart( EDITUNDO_INSERT ); + pImpEditEngine->ImpInsertText( *pSel, rTxt ); + pImpEditEngine->UndoActionEnd(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + } +} + +void EditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + pImpEditEngine->SetParaAttribs( nPara, rSet ); + if ( pImpEditEngine->IsUpdateLayout() ) + pImpEditEngine->FormatAndLayout(); +} + +const SfxItemSet& EditEngine::GetParaAttribs( sal_Int32 nPara ) const +{ + return pImpEditEngine->GetParaAttribs( nPara ); +} + +bool EditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + return pImpEditEngine->HasParaAttrib( nPara, nWhich ); +} + +const SfxPoolItem& EditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + return pImpEditEngine->GetParaAttrib( nPara, nWhich ); +} + +void EditEngine::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet) +{ + EditSelection aSel(pImpEditEngine->ConvertSelection(nPara, 0, nPara, GetTextLen(nPara))); + // This is called by sd::View::OnBeginPasteOrDrop(), updating the cursor position on undo is not + // wanted. + pImpEditEngine->SetAttribs(aSel, rSet, /*nSpecial=*/SetAttribsMode::NONE, /*bSetSelection=*/false); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +void EditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + pImpEditEngine->GetCharAttribs( nPara, rLst ); +} + +SfxItemSet EditEngine::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) +{ + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + return pImpEditEngine->GetAttribs( aSel, nOnlyHardAttrib ); +} + +SfxItemSet EditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const +{ + return pImpEditEngine->GetAttribs( nPara, nStart, nEnd, nFlags ); +} + +void EditEngine::RemoveAttribs( const ESelection& rSelection, bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + const EERemoveParaAttribsMode eMode = bRemoveParaAttribs? + EERemoveParaAttribsMode::RemoveAll : + EERemoveParaAttribsMode::RemoveCharItems; + + pImpEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + EditSelection aSel( pImpEditEngine->ConvertSelection( rSelection.nStartPara, rSelection.nStartPos, rSelection.nEndPara, rSelection.nEndPos ) ); + pImpEditEngine->RemoveCharAttribs( aSel, eMode, nWhich ); + pImpEditEngine->UndoActionEnd(); + if (pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); +} + +vcl::Font EditEngine::GetStandardFont( sal_Int32 nPara ) +{ + return GetStandardSvxFont( nPara ); +} + +SvxFont EditEngine::GetStandardSvxFont( sal_Int32 nPara ) +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + return pNode->GetCharAttribs().GetDefFont(); +} + +void EditEngine::StripPortions() +{ + ScopedVclPtrInstance< VirtualDevice > aTmpDev; + tools::Rectangle aBigRect( Point( 0, 0 ), Size( 0x7FFFFFFF, 0x7FFFFFFF ) ); + if ( IsEffectivelyVertical() ) + { + if( IsTopToBottom() ) + { + aBigRect.SetRight( 0 ); + aBigRect.SetLeft( -0x7FFFFFFF ); + } + else + { + aBigRect.SetTop( -0x7FFFFFFF ); + aBigRect.SetBottom( 0 ); + } + } + pImpEditEngine->Paint(*aTmpDev, aBigRect, Point(), true); +} + +void EditEngine::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) +{ + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatFullDoc(); + + const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pParaPortion ) + { + sal_Int32 nEnd = 0; + sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count(); + for ( sal_Int32 n = 0; n < nTextPortions; n++ ) + { + nEnd = nEnd + pParaPortion->GetTextPortions()[n].GetLen(); + rList.push_back( nEnd ); + } + } +} + +void EditEngine::SetFlatMode( bool bFlat) +{ + pImpEditEngine->SetFlatMode( bFlat ); +} + +bool EditEngine::IsFlatMode() const +{ + return !( pImpEditEngine->GetStatus().UseCharAttribs() ); +} + +void EditEngine::SetSingleLine(bool bValue) +{ + if (bValue == pImpEditEngine->GetStatus().IsSingleLine()) + return; + + if (bValue) + pImpEditEngine->GetStatus().TurnOnFlags(EEControlBits::SINGLELINE); + else + pImpEditEngine->GetStatus().TurnOffFlags(EEControlBits::SINGLELINE); +} + +void EditEngine::SetControlWord( EEControlBits nWord ) +{ + + if ( nWord == pImpEditEngine->GetStatus().GetControlWord() ) + return; + + EEControlBits nPrev = pImpEditEngine->GetStatus().GetControlWord(); + pImpEditEngine->GetStatus().GetControlWord() = nWord; + + EEControlBits nChanges = nPrev ^ nWord; + if ( pImpEditEngine->IsFormatted() ) + { + // possibly reformat: + if ( ( nChanges & EEControlBits::USECHARATTRIBS ) || + ( nChanges & EEControlBits::ONECHARPERLINE ) || + ( nChanges & EEControlBits::STRETCHING ) || + ( nChanges & EEControlBits::OUTLINER ) || + ( nChanges & EEControlBits::NOCOLORS ) || + ( nChanges & EEControlBits::OUTLINER2 ) ) + { + if ( nChanges & EEControlBits::USECHARATTRIBS ) + { + pImpEditEngine->GetEditDoc().CreateDefFont( true ); + } + + pImpEditEngine->FormatFullDoc(); + pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() ); + } + } + + bool bSpellingChanged = bool(nChanges & EEControlBits::ONLINESPELLING); + + if ( !bSpellingChanged ) + return; + + pImpEditEngine->StopOnlineSpellTimer(); + if (nWord & EEControlBits::ONLINESPELLING) + { + // Create WrongList, start timer... + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + pNode->CreateWrongList(); + } + if (pImpEditEngine->IsFormatted()) + pImpEditEngine->StartOnlineSpellTimer(); + } + else + { + tools::Long nY = 0; + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + const ParaPortion* pPortion = pImpEditEngine->GetParaPortions()[n]; + bool bWrongs = false; + if (pNode->GetWrongList() != nullptr) + bWrongs = !pNode->GetWrongList()->empty(); + pNode->DestroyWrongList(); + if ( bWrongs ) + { + pImpEditEngine->aInvalidRect.SetLeft( 0 ); + pImpEditEngine->aInvalidRect.SetRight( pImpEditEngine->GetPaperSize().Width() ); + pImpEditEngine->aInvalidRect.SetTop( nY+1 ); + pImpEditEngine->aInvalidRect.SetBottom( nY+pPortion->GetHeight()-1 ); + pImpEditEngine->UpdateViews( pImpEditEngine->pActiveView ); + } + nY += pPortion->GetHeight(); + } + } +} + +EEControlBits EditEngine::GetControlWord() const +{ + return pImpEditEngine->GetStatus().GetControlWord(); +} + +tools::Long EditEngine::GetFirstLineStartX( sal_Int32 nParagraph ) +{ + + tools::Long nX = 0; + const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph ); + if ( pPPortion ) + { + DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetFirstLineStartX: Doc not formatted - unable to format!" ); + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + const EditLine& rFirstLine = pPPortion->GetLines()[0]; + nX = rFirstLine.GetStartPosX(); + } + return nX; +} + +Point EditEngine::GetDocPos( const Point& rPaperPos ) const +{ + Point aDocPos( rPaperPos ); + if ( IsEffectivelyVertical() ) + { + if ( IsTopToBottom() ) + { + aDocPos.setX( rPaperPos.Y() ); + aDocPos.setY( GetPaperSize().Width() - rPaperPos.X() ); + } + else + { + aDocPos.setX( rPaperPos.Y() ); + aDocPos.setY( rPaperPos.X() ); + } + } + return aDocPos; +} + +Point EditEngine::GetDocPosTopLeft( sal_Int32 nParagraph ) +{ + const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph ); + DBG_ASSERT( pPPortion, "Paragraph not found: GetWindowPosTopLeft" ); + Point aPoint; + if ( pPPortion ) + { + + // If someone calls GetLineHeight() with an empty Engine. + DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetDocPosTopLeft: Doc not formatted - unable to format!" ); + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatAndLayout(); + if ( pPPortion->GetLines().Count() ) + { + // Correct it if large Bullet. + const EditLine& rFirstLine = pPPortion->GetLines()[0]; + aPoint.setX( rFirstLine.GetStartPosX() ); + } + else + { + const SvxLRSpaceItem& rLRItem = pImpEditEngine->GetLRSpaceItem( pPPortion->GetNode() ); + sal_Int32 nSpaceBefore = 0; + pImpEditEngine->GetSpaceBeforeAndMinLabelWidth( pPPortion->GetNode(), &nSpaceBefore ); + short nX = static_cast<short>(rLRItem.GetTextLeft() + + rLRItem.GetTextFirstLineOffset() + + nSpaceBefore); + + aPoint.setX(pImpEditEngine->scaleXSpacingValue(nX)); + } + aPoint.setY( pImpEditEngine->GetParaPortions().GetYOffset( pPPortion ) ); + } + return aPoint; +} + +const SvxNumberFormat* EditEngine::GetNumberFormat( sal_Int32 ) const +{ + // derived objects may override this function to give access to + // bullet information (see Outliner) + return nullptr; +} + +bool EditEngine::IsRightToLeft( sal_Int32 nPara ) const +{ + return pImpEditEngine->IsRightToLeft( nPara ); +} + +bool EditEngine::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder ) +{ + + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + // take unrotated positions for calculation here + Point aDocPos = GetDocPos( rPaperPos ); + + if ( ( aDocPos.Y() > 0 ) && ( o3tl::make_unsigned(aDocPos.Y()) < pImpEditEngine->GetTextHeight() ) ) + return pImpEditEngine->IsTextPos(aDocPos, nBorder); + return false; +} + +void EditEngine::SetEditTextObjectPool( SfxItemPool* pPool ) +{ + pImpEditEngine->SetEditTextObjectPool( pPool ); +} + +SfxItemPool* EditEngine::GetEditTextObjectPool() const +{ + return pImpEditEngine->GetEditTextObjectPool(); +} + +void EditEngine::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->SetAttribs( aSel, rSet ); +} + +void EditEngine::QuickMarkInvalid( const ESelection& rSel ) +{ + DBG_ASSERT( rSel.nStartPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: Start out of Range!" ); + DBG_ASSERT( rSel.nEndPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: End out of Range!" ); + for ( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ ) + { + ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pPortion ) + pPortion->MarkSelectionInvalid( 0 ); + } +} + +void EditEngine::QuickInsertText(const OUString& rText, const ESelection& rSel) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpInsertText( aSel, rText ); +} + +void EditEngine::QuickDelete( const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpDeleteSelection( aSel ); +} + +void EditEngine::QuickMarkToBeRepainted( sal_Int32 nPara ) +{ + ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara ); + if ( pPortion ) + pPortion->SetMustRepaint( true ); +} + +void EditEngine::QuickInsertLineBreak( const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->InsertLineBreak( aSel ); +} + +void EditEngine::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + + EditSelection aSel( pImpEditEngine-> + ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) ); + + pImpEditEngine->ImpInsertFeature( aSel, rFld ); +} + +void EditEngine::QuickFormatDoc( bool bFull ) +{ + if ( bFull ) + pImpEditEngine->FormatFullDoc(); + else + pImpEditEngine->FormatDoc(); + + // Don't pass active view, maybe selection is not updated yet... + pImpEditEngine->UpdateViews(); +} + +void EditEngine::SetStyleSheet(const EditSelection& aSel, SfxStyleSheet* pStyle) +{ + pImpEditEngine->SetStyleSheet(aSel, pStyle); +} + +void EditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + pImpEditEngine->SetStyleSheet( nPara, pStyle ); +} + +const SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara ) const +{ + return pImpEditEngine->GetStyleSheet( nPara ); +} + +SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara ) +{ + return pImpEditEngine->GetStyleSheet( nPara ); +} + +void EditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + pImpEditEngine->SetStyleSheetPool( pSPool ); +} + +SfxStyleSheetPool* EditEngine::GetStyleSheetPool() +{ + return pImpEditEngine->GetStyleSheetPool(); +} + +void EditEngine::SetWordDelimiters( const OUString& rDelimiters ) +{ + pImpEditEngine->aWordDelimiters = rDelimiters; + if (pImpEditEngine->aWordDelimiters.indexOf(CH_FEATURE) == -1) + pImpEditEngine->aWordDelimiters += OUStringChar(CH_FEATURE); +} + +const OUString& EditEngine::GetWordDelimiters() const +{ + return pImpEditEngine->aWordDelimiters; +} + +void EditEngine::EraseVirtualDevice() +{ + pImpEditEngine->EraseVirtualDevice(); +} + +void EditEngine::SetSpeller( Reference< XSpellChecker1 > const &xSpeller ) +{ + pImpEditEngine->SetSpeller( xSpeller ); +} + +Reference< XSpellChecker1 > const & EditEngine::GetSpeller() +{ + return pImpEditEngine->GetSpeller(); +} + +void EditEngine::SetHyphenator( Reference< XHyphenator > const & xHyph ) +{ + pImpEditEngine->SetHyphenator( xHyph ); +} + +void EditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const +{ + pImpEditEngine->GetAllMisspellRanges(rRanges); +} + +void EditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ) +{ + pImpEditEngine->SetAllMisspellRanges(rRanges); +} + +void EditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + ImpEditEngine::SetForbiddenCharsTable( xForbiddenChars ); +} + +void EditEngine::SetDefaultLanguage( LanguageType eLang ) +{ + pImpEditEngine->SetDefaultLanguage( eLang ); +} + +LanguageType EditEngine::GetDefaultLanguage() const +{ + return pImpEditEngine->GetDefaultLanguage(); +} + +bool EditEngine::SpellNextDocument() +{ + return false; +} + +EESpellState EditEngine::HasSpellErrors() +{ + if ( !pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditEngine->HasSpellErrors(); +} + +void EditEngine::ClearSpellErrors() +{ + pImpEditEngine->ClearSpellErrors(); +} + +bool EditEngine::SpellSentence(EditView const & rView, svx::SpellPortions& rToFill ) +{ + return pImpEditEngine->SpellSentence( rView, rToFill ); +} + +void EditEngine::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + pImpEditEngine->PutSpellingToSentenceStart( rEditView ); +} + +void EditEngine::ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ) +{ + pImpEditEngine->ApplyChangedSentence( rEditView, rNewPortions, bRecheck ); +} + +bool EditEngine::HasConvertibleTextPortion( LanguageType nLang ) +{ + return pImpEditEngine->HasConvertibleTextPortion( nLang ); +} + +bool EditEngine::ConvertNextDocument() +{ + return false; +} + +bool EditEngine::HasText( const SvxSearchItem& rSearchItem ) +{ + return pImpEditEngine->HasText( rSearchItem ); +} + +void EditEngine::setGlobalScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY) +{ + pImpEditEngine->setScale(fFontScaleX, fFontScaleY, fSpacingScaleX, fSpacingScaleY); +} + +void EditEngine::getGlobalSpacingScale(double& rX, double& rY) const +{ + pImpEditEngine->getSpacingScale(rX, rY); +} + +basegfx::B2DTuple EditEngine::getGlobalSpacingScale() const +{ + double x = 0.0; + double y = 0.0; + pImpEditEngine->getSpacingScale(x, y); + return {x, y}; +} + +void EditEngine::getGlobalFontScale(double& rX, double& rY) const +{ + pImpEditEngine->getFontScale(rX, rY); +} + +basegfx::B2DTuple EditEngine::getGlobalFontScale() const +{ + double x = 0.0; + double y = 0.0; + pImpEditEngine->getFontScale(x, y); + return {x, y}; +} + +void EditEngine::setRoundFontSizeToPt(bool bRound) const +{ + pImpEditEngine->setRoundToNearestPt(bRound); +} + +bool EditEngine::ShouldCreateBigTextObject() const +{ + sal_Int32 nTextPortions = 0; + sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara]; + nTextPortions = nTextPortions + pParaPortion->GetTextPortions().Count(); + } + return nTextPortions >= pImpEditEngine->GetBigTextObjectStart(); +} + +sal_uInt16 EditEngine::GetFieldCount( sal_Int32 nPara ) const +{ + sal_uInt16 nFields = 0; + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + if ( pNode ) + { + for (auto const& attrib : pNode->GetCharAttribs().GetAttribs()) + { + if (attrib->Which() == EE_FEATURE_FIELD) + ++nFields; + } + } + + return nFields; +} + +EFieldInfo EditEngine::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + if ( pNode ) + { + sal_uInt16 nCurrentField = 0; + for (auto const& attrib : pNode->GetCharAttribs().GetAttribs()) + { + const EditCharAttrib& rAttr = *attrib; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + if ( nCurrentField == nField ) + { + const SvxFieldItem* p = static_cast<const SvxFieldItem*>(rAttr.GetItem()); + EFieldInfo aInfo(*p, nPara, rAttr.GetStart()); + aInfo.aCurrentText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue(); + return aInfo; + } + + ++nCurrentField; + } + } + } + return EFieldInfo(); +} + + +bool EditEngine::UpdateFields() +{ + bool bChanges = pImpEditEngine->UpdateFields(); + if ( bChanges && pImpEditEngine->IsUpdateLayout()) + pImpEditEngine->FormatAndLayout(); + return bChanges; +} + +bool EditEngine::UpdateFieldsOnly() +{ + return pImpEditEngine->UpdateFields(); +} + +void EditEngine::RemoveFields( const std::function<bool ( const SvxFieldData* )>& isFieldData ) +{ + pImpEditEngine->UpdateFields(); + + sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara ); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField(); + if ( pFldData && ( isFieldData( pFldData ) ) ) + { + DBG_ASSERT( dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "no field item..." ); + EditSelection aSel( EditPaM(pNode, rAttr.GetStart()), EditPaM(pNode, rAttr.GetEnd()) ); + OUString aFieldText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue(); + pImpEditEngine->ImpInsertText( aSel, aFieldText ); + } + } + } + } +} + +bool EditEngine::HasOnlineSpellErrors() const +{ + sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n ); + if ( pNode->GetWrongList() && !pNode->GetWrongList()->empty() ) + return true; + } + return false; +} + +void EditEngine::CompleteOnlineSpelling() +{ + if ( pImpEditEngine->GetStatus().DoOnlineSpelling() ) + { + if( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatAndLayout(); + + pImpEditEngine->StopOnlineSpellTimer(); + pImpEditEngine->DoOnlineSpelling( nullptr, true, false ); + } +} + +sal_Int32 EditEngine::FindParagraph( tools::Long nDocPosY ) +{ + return pImpEditEngine->GetParaPortions().FindParagraph( nDocPosY ); +} + +EPosition EditEngine::FindDocPosition( const Point& rDocPos ) const +{ + EPosition aPos; + // From the point of the API, this is const... + EditPaM aPaM = const_cast<EditEngine*>(this)->pImpEditEngine->GetPaM( rDocPos, false ); + if ( aPaM.GetNode() ) + { + aPos.nPara = pImpEditEngine->maEditDoc.GetPos( aPaM.GetNode() ); + aPos.nIndex = aPaM.GetIndex(); + } + return aPos; +} + +tools::Rectangle EditEngine::GetCharacterBounds( const EPosition& rPos ) const +{ + tools::Rectangle aBounds; + ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( rPos.nPara ); + + // Check against index, not paragraph + if ( pNode && ( rPos.nIndex < pNode->Len() ) ) + { + aBounds = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex ), GetCursorFlags::TextOnly ); + tools::Rectangle aR2 = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex+1 ), GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine ); + if ( aR2.Right() > aBounds.Right() ) + aBounds.SetRight( aR2.Right() ); + } + return aBounds; +} + +ParagraphInfos EditEngine::GetParagraphInfos( sal_Int32 nPara ) +{ + + // This only works if not already in the format ... + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + + ParagraphInfos aInfos; + aInfos.bValid = pImpEditEngine->IsFormatted(); + if ( pImpEditEngine->IsFormatted() ) + { + const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara]; + const EditLine* pLine = (pParaPortion && pParaPortion->GetLines().Count()) ? + &pParaPortion->GetLines()[0] : nullptr; + DBG_ASSERT( pParaPortion && pLine, "GetParagraphInfos - Paragraph out of range" ); + if ( pParaPortion && pLine ) + { + aInfos.nFirstLineHeight = pLine->GetHeight(); + aInfos.nFirstLineTextHeight = pLine->GetTxtHeight(); + aInfos.nFirstLineMaxAscent = pLine->GetMaxAscent(); + } + } + return aInfos; +} + +css::uno::Reference< css::datatransfer::XTransferable > + EditEngine::CreateTransferable( const ESelection& rSelection ) const +{ + EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) ); + return pImpEditEngine->CreateTransferable( aSel ); +} + + +// ====================== Virtual Methods ======================== + +void EditEngine::DrawingText( const Point&, const OUString&, sal_Int32, sal_Int32, + std::span<const sal_Int32>, std::span<const sal_Bool>, + const SvxFont&, sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/, + const EEngineData::WrongSpellVector*, const SvxFieldData*, bool, bool, + const css::lang::Locale*, const Color&, const Color&) + +{ +} + +void EditEngine::DrawingTab( const Point& /*rStartPos*/, tools::Long /*nWidth*/, + const OUString& /*rChar*/, const SvxFont& /*rFont*/, + sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/, bool /*bEndOfLine*/, + bool /*bEndOfParagraph*/, const Color& /*rOverlineColor*/, + const Color& /*rTextLineColor*/) +{ +} + +void EditEngine::PaintingFirstLine(sal_Int32, const Point&, const Point&, Degree10, OutputDevice&) +{ +} + +void EditEngine::ParagraphInserted( sal_Int32 nPara ) +{ + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHINSERTED ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } +} + +void EditEngine::ParagraphDeleted( sal_Int32 nPara ) +{ + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHREMOVED ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } +} +void EditEngine::ParagraphConnected( sal_Int32 /*nLeftParagraph*/, sal_Int32 /*nRightParagraph*/ ) +{ +} + +void EditEngine::ParaAttribsChanged( sal_Int32 /* nParagraph */ ) +{ +} + +void EditEngine::StyleSheetChanged( SfxStyleSheet* /* pStyle */ ) +{ +} + +void EditEngine::ParagraphHeightChanged( sal_Int32 nPara ) +{ + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TextHeightChanged ); + aNotify.nParagraph = nPara; + pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + + for (EditView* pView : pImpEditEngine->aEditViews) + pView->pImpEditView->ScrollStateChange(); +} + +OUString EditEngine::GetUndoComment( sal_uInt16 nId ) const +{ + OUString aComment; + switch ( nId ) + { + case EDITUNDO_REMOVECHARS: + case EDITUNDO_CONNECTPARAS: + case EDITUNDO_DELCONTENT: + case EDITUNDO_DELETE: + case EDITUNDO_CUT: + aComment = EditResId(RID_EDITUNDO_DEL); + break; + case EDITUNDO_MOVEPARAGRAPHS: + case EDITUNDO_MOVEPARAS: + case EDITUNDO_DRAGANDDROP: + aComment = EditResId(RID_EDITUNDO_MOVE); + break; + case EDITUNDO_INSERTFEATURE: + case EDITUNDO_SPLITPARA: + case EDITUNDO_INSERTCHARS: + case EDITUNDO_PASTE: + case EDITUNDO_INSERT: + case EDITUNDO_READ: + aComment = EditResId(RID_EDITUNDO_INSERT); + break; + case EDITUNDO_REPLACEALL: + aComment = EditResId(RID_EDITUNDO_REPLACE); + break; + case EDITUNDO_ATTRIBS: + case EDITUNDO_PARAATTRIBS: + aComment = EditResId(RID_EDITUNDO_SETATTRIBS); + break; + case EDITUNDO_RESETATTRIBS: + aComment = EditResId(RID_EDITUNDO_RESETATTRIBS); + break; + case EDITUNDO_STYLESHEET: + aComment = EditResId(RID_EDITUNDO_SETSTYLE); + break; + case EDITUNDO_TRANSLITERATE: + aComment = EditResId(RID_EDITUNDO_TRANSLITERATE); + break; + case EDITUNDO_INDENTBLOCK: + case EDITUNDO_UNINDENTBLOCK: + aComment = EditResId(RID_EDITUNDO_INDENT); + break; + } + return aComment; +} + +tools::Rectangle EditEngine::GetBulletArea( sal_Int32 ) +{ + return tools::Rectangle( Point(), Point() ); +} + +OUString EditEngine::CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>&, std::optional<FontLineStyle>& ) +{ + return OUString(' '); +} + +bool EditEngine::FieldClicked( const SvxFieldItem& ) +{ + return false; +} + + +// ====================== Static Methods ======================= + +rtl::Reference<SfxItemPool> EditEngine::CreatePool() +{ + return new EditEngineItemPool(); +} + + +/** If we let the libc runtime clean us up, we trigger a crash */ +namespace +{ +class TerminateListener : public ::cppu::WeakImplHelper< css::frame::XTerminateListener > +{ + void SAL_CALL queryTermination( const lang::EventObject& ) override + {} + void SAL_CALL notifyTermination( const lang::EventObject& ) override + { + pGlobalPool.clear(); + } + virtual void SAL_CALL disposing( const ::css::lang::EventObject& ) override + {} +}; +}; + +SfxItemPool& EditEngine::GetGlobalItemPool() +{ + if ( !pGlobalPool ) + { + pGlobalPool = CreatePool(); +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + // TerminateListener option not available, force it to leak + pGlobalPool->acquire(); +#else + uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext()); + uno::Reference< frame::XTerminateListener > xListener( new TerminateListener ); + xDesktop->addTerminateListener( xListener ); +#endif + } + return *pGlobalPool; +} + +void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const vcl::Font& rFont ) +{ + SvxFont aSvxFont( rFont ); + SetFontInfoInItemSet( rSet, aSvxFont ); + +} + +void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const SvxFont& rFont ) +{ + rSet.Put( SvxLanguageItem( rFont.GetLanguage(), EE_CHAR_LANGUAGE ) ); + rSet.Put( SvxFontItem( rFont.GetFamilyType(), rFont.GetFamilyName(), OUString(), rFont.GetPitch(), rFont.GetCharSet(), EE_CHAR_FONTINFO ) ); + rSet.Put( SvxFontHeightItem( rFont.GetFontSize().Height(), 100, EE_CHAR_FONTHEIGHT ) ); + rSet.Put( SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH ) ); + rSet.Put( SvxShadowedItem( rFont.IsShadow(), EE_CHAR_SHADOW ) ); + rSet.Put( SvxEscapementItem( rFont.GetEscapement(), rFont.GetPropr(), EE_CHAR_ESCAPEMENT ) ); + rSet.Put( SvxWeightItem( rFont.GetWeight(), EE_CHAR_WEIGHT ) ); + rSet.Put( SvxColorItem( rFont.GetColor(), EE_CHAR_COLOR ) ); + rSet.Put( SvxColorItem( rFont.GetFillColor(), EE_CHAR_BKGCOLOR ) ); + rSet.Put( SvxUnderlineItem( rFont.GetUnderline(), EE_CHAR_UNDERLINE ) ); + rSet.Put( SvxOverlineItem( rFont.GetOverline(), EE_CHAR_OVERLINE ) ); + rSet.Put( SvxCrossedOutItem( rFont.GetStrikeout(), EE_CHAR_STRIKEOUT ) ); + rSet.Put( SvxCaseMapItem( rFont.GetCaseMap(), EE_CHAR_CASEMAP ) ); + rSet.Put( SvxPostureItem( rFont.GetItalic(), EE_CHAR_ITALIC ) ); + rSet.Put( SvxContourItem( rFont.IsOutline(), EE_CHAR_OUTLINE ) ); + rSet.Put( SvxAutoKernItem( rFont.IsKerning(), EE_CHAR_PAIRKERNING ) ); + rSet.Put( SvxKerningItem( rFont.GetFixKerning(), EE_CHAR_KERNING ) ); + rSet.Put( SvxWordLineModeItem( rFont.IsWordLineMode(), EE_CHAR_WLM ) ); + rSet.Put( SvxEmphasisMarkItem( rFont.GetEmphasisMark(), EE_CHAR_EMPHASISMARK ) ); + rSet.Put( SvxCharReliefItem( rFont.GetRelief(), EE_CHAR_RELIEF ) ); +} + +vcl::Font EditEngine::CreateFontFromItemSet( const SfxItemSet& rItemSet, SvtScriptType nScriptType ) +{ + SvxFont aFont; + CreateFont( aFont, rItemSet, true, nScriptType ); +#if HAVE_P1155R3 + return aFont; +#else + return std::move(aFont); +#endif +} + +SvxFont EditEngine::CreateSvxFontFromItemSet( const SfxItemSet& rItemSet ) +{ + SvxFont aFont; + CreateFont( aFont, rItemSet ); + return aFont; +} + +bool EditEngine::DoesKeyMoveCursor( const KeyEvent& rKeyEvent ) +{ + bool bDoesMove = false; + + switch ( rKeyEvent.GetKeyCode().GetCode() ) + { + case KEY_UP: + case KEY_DOWN: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_HOME: + case KEY_END: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + { + if ( !rKeyEvent.GetKeyCode().IsMod2() ) + bDoesMove = true; + } + break; + } + return bDoesMove; +} + +bool EditEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent ) +{ + bool bDoesChange = false; + + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::UNDO: + case KeyFuncType::REDO: + case KeyFuncType::CUT: + case KeyFuncType::PASTE: bDoesChange = true; + break; + default: // is then possibly edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( rKeyEvent.GetKeyCode().GetCode() ) + { + case KEY_DELETE: + case KEY_BACKSPACE: bDoesChange = true; + break; + case KEY_RETURN: + case KEY_TAB: + { + if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() ) + bDoesChange = true; + } + break; + default: + { + bDoesChange = IsSimpleCharInput( rKeyEvent ); + } + } + } + return bDoesChange; +} + +bool EditEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent ) +{ + return EditEngine::IsPrintable( rKeyEvent.GetCharCode() ) && + ( KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) ) && + ( KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) ); +} + +bool EditEngine::HasValidData( const css::uno::Reference< css::datatransfer::XTransferable >& rTransferable ) +{ + bool bValidData = false; + + if ( comphelper::LibreOfficeKit::isActive()) + return true; + + if ( rTransferable.is() ) + { + // Every application that copies rtf or any other text format also copies plain text into the clipboard... + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + bValidData = rTransferable->isDataFlavorSupported( aFlavor ); + } + + return bValidData; +} + +/** sets a link that is called at the beginning of a drag operation at an edit view */ +void EditEngine::SetBeginDropHdl( const Link<EditView*,void>& rLink ) +{ + pImpEditEngine->SetBeginDropHdl( rLink ); +} + +Link<EditView*,void> const & EditEngine::GetBeginDropHdl() const +{ + return pImpEditEngine->GetBeginDropHdl(); +} + +/** sets a link that is called at the end of a drag operation at an edit view */ +void EditEngine::SetEndDropHdl( const Link<EditView*,void>& rLink ) +{ + pImpEditEngine->SetEndDropHdl( rLink ); +} + +Link<EditView*,void> const & EditEngine::GetEndDropHdl() const +{ + return pImpEditEngine->GetEndDropHdl(); +} + +void EditEngine::SetFirstWordCapitalization( bool bCapitalize ) +{ + pImpEditEngine->SetFirstWordCapitalization( bCapitalize ); +} + +void EditEngine::SetReplaceLeadingSingleQuotationMark( bool bReplace ) +{ + pImpEditEngine->SetReplaceLeadingSingleQuotationMark( bReplace ); +} + +bool EditEngine::IsHtmlImportHandlerSet() const +{ + return pImpEditEngine->aHtmlImportHdl.IsSet(); +} + +bool EditEngine::IsRtfImportHandlerSet() const +{ + return pImpEditEngine->aRtfImportHdl.IsSet(); +} + +bool EditEngine::IsImportRTFStyleSheetsSet() const +{ + return pImpEditEngine->GetStatus().DoImportRTFStyleSheets(); +} + +void EditEngine::CallHtmlImportHandler(HtmlImportInfo& rInfo) +{ + pImpEditEngine->aHtmlImportHdl.Call(rInfo); +} + +void EditEngine::CallRtfImportHandler(RtfImportInfo& rInfo) +{ + pImpEditEngine->aRtfImportHdl.Call(rInfo); +} + +EditPaM EditEngine::InsertParaBreak(const EditSelection& rEditSelection) +{ + return pImpEditEngine->ImpInsertParaBreak(rEditSelection); +} + +EditPaM EditEngine::InsertLineBreak(const EditSelection& rEditSelection) +{ + return pImpEditEngine->InsertLineBreak(rEditSelection); +} + +sal_Int32 EditEngine::GetOverflowingParaNum() const { + return pImpEditEngine->GetOverflowingParaNum(); +} + +sal_Int32 EditEngine::GetOverflowingLineNum() const { + return pImpEditEngine->GetOverflowingLineNum(); +} + +void EditEngine::ClearOverflowingParaNum() { + pImpEditEngine->ClearOverflowingParaNum(); +} + +bool EditEngine::IsPageOverflow() { + pImpEditEngine->CheckPageOverflow(); + return pImpEditEngine->IsPageOverflow(); +} + +void EditEngine::DisableAttributeExpanding() { + pImpEditEngine->GetEditDoc().DisableAttributeExpanding(); +} + +void EditEngine::EnableSkipOutsideFormat(bool set) +{ + pImpEditEngine->EnableSkipOutsideFormat(set); +} + +void EditEngine::SetLOKSpecialPaperSize(const Size& rSize) +{ + pImpEditEngine->SetLOKSpecialPaperSize(rSize); +} + +const Size& EditEngine::GetLOKSpecialPaperSize() const +{ + return pImpEditEngine->GetLOKSpecialPaperSize(); +} + +EFieldInfo::EFieldInfo() +{ +} + + +EFieldInfo::EFieldInfo( const SvxFieldItem& rFieldItem, sal_Int32 nPara, sal_Int32 nPos ) : + pFieldItem( new SvxFieldItem( rFieldItem ) ), + aPosition( nPara, nPos ) +{ +} + +EFieldInfo::~EFieldInfo() +{ +} + +EFieldInfo::EFieldInfo( const EFieldInfo& rFldInfo ) +{ + *this = rFldInfo; +} + +EFieldInfo& EFieldInfo::operator= ( const EFieldInfo& rFldInfo ) +{ + if( this == &rFldInfo ) + return *this; + + pFieldItem.reset( rFldInfo.pFieldItem ? new SvxFieldItem( *rFldInfo.pFieldItem ) : nullptr ); + aCurrentText = rFldInfo.aCurrentText; + aPosition = rFldInfo.aPosition; + + return *this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editobj.cxx b/editeng/source/editeng/editobj.cxx new file mode 100644 index 0000000000..762cac112d --- /dev/null +++ b/editeng/source/editeng/editobj.cxx @@ -0,0 +1,759 @@ +/* -*- 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 <memory> + +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <editeng/macros.hxx> +#include <editeng/section.hxx> +#include "editobj2.hxx" +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <editeng/flditem.hxx> + +#include <svl/sharedstringpool.hxx> + +#include <libxml/xmlwriter.h> +#include <algorithm> +#include <cassert> + +#if DEBUG_EDIT_ENGINE +#include <iostream> +using std::cout; +using std::endl; +#endif + +using namespace com::sun::star; + + +XEditAttribute::XEditAttribute(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nS, sal_Int32 nE) +: maItemHolder(rPool, &rItem) +, nStart(nS) +, nEnd(nE) +{ +} + +bool XEditAttribute::IsFeature() const +{ + sal_uInt16 nWhich = GetItem()->Which(); + return ((nWhich >= EE_FEATURE_START) && (nWhich <= EE_FEATURE_END)); +} + +void XEditAttribute::SetItem(SfxItemPool& rPool, const SfxPoolItem& rItem) +{ + maItemHolder = SfxPoolItemHolder(rPool, &rItem); +} + +XParaPortionList::XParaPortionList(OutputDevice* pRefDev, sal_uInt32 nPW, + double fFontScaleX, double fFontScaleY, + double fSpacingScaleX, double fSpacingScaleY) + : pRefDevPtr(pRefDev) + , mfFontScaleX(fFontScaleX) + , mfFontScaleY(fFontScaleY) + , mfSpacingScaleX(fSpacingScaleX) + , mfSpacingScaleY(fSpacingScaleY) + , nPaperWidth(nPW) +{ +} + +void XParaPortionList::push_back(XParaPortion* p) +{ + maList.push_back(std::unique_ptr<XParaPortion>(p)); +} + +const XParaPortion& XParaPortionList::operator [](size_t i) const +{ + return *maList[i]; +} + +ContentInfo::ContentInfo( SfxItemPool& rPool ) : + eFamily(SfxStyleFamily::Para), + aParaAttribs(rPool) +{ +} + +// the real Copy constructor is nonsense, since I have to work with another Pool! +ContentInfo::ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse ) : + maText(rCopyFrom.maText), + aStyle(rCopyFrom.aStyle), + eFamily(rCopyFrom.eFamily), + aParaAttribs(rPoolToUse) +{ + // this should ensure that the Items end up in the correct Pool! + aParaAttribs.Set( rCopyFrom.GetParaAttribs() ); + + for (const XEditAttribute & rAttr : rCopyFrom.maCharAttribs) + { + maCharAttribs.emplace_back(rPoolToUse, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd()); + } + + if ( rCopyFrom.GetWrongList() ) + mpWrongs.reset(rCopyFrom.GetWrongList()->Clone()); +} + +ContentInfo::~ContentInfo() +{ + maCharAttribs.clear(); +} + +void ContentInfo::NormalizeString( svl::SharedStringPool& rPool ) +{ + maText = rPool.intern(OUString(maText.getData())); +} + + +OUString ContentInfo::GetText() const +{ + rtl_uString* p = const_cast<rtl_uString*>(maText.getData()); + return OUString(p); +} + +void ContentInfo::SetText( const OUString& rStr ) +{ + maText = svl::SharedString(rStr.pData, nullptr); +} + +void ContentInfo::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentInfo")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("style"), BAD_CAST(aStyle.toUtf8().getStr())); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("text")); + OUString aText = GetText(); + // TODO share code with sax_fastparser::FastSaxSerializer::write(). + (void)xmlTextWriterWriteString(pWriter, BAD_CAST(aText.replaceAll("\x01", "").toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); + aParaAttribs.dumpAsXml(pWriter); + for (auto const& rCharAttribs : maCharAttribs) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("attribs")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("start"), "%" SAL_PRIdINT32, rCharAttribs.GetStart()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("end"), "%" SAL_PRIdINT32, rCharAttribs.GetEnd()); + rCharAttribs.GetItem()->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); +} + +const WrongList* ContentInfo::GetWrongList() const +{ + return mpWrongs.get(); +} + +void ContentInfo::SetWrongList( WrongList* p ) +{ + mpWrongs.reset(p); +} + +// #i102062# +bool ContentInfo::isWrongListEqual(const ContentInfo& rCompare) const +{ + if(GetWrongList() == rCompare.GetWrongList()) + return true; + + if(!GetWrongList() || !rCompare.GetWrongList()) + return false; + + return (*GetWrongList() == *rCompare.GetWrongList()); +} + +#if DEBUG_EDIT_ENGINE +void ContentInfo::Dump() const +{ + cout << "--" << endl; + cout << "text: '" << OUString(const_cast<rtl_uString*>(maText.getData())) << "'" << endl; + cout << "style: '" << aStyle << "'" << endl; + + for (auto const& attrib : aAttribs) + { + cout << "attribute: " << endl; + cout << " span: [begin=" << attrib.GetStart() << ", end=" << attrib.GetEnd() << "]" << endl; + cout << " feature: " << (attrib.IsFeature() ? "yes":"no") << endl; + } +} +#endif + +bool ContentInfo::Equals(const ContentInfo& rCompare, bool bComparePool) const +{ + return maText == rCompare.maText && aStyle == rCompare.aStyle && eFamily == rCompare.eFamily + && aParaAttribs.Equals(rCompare.aParaAttribs, bComparePool) + && maCharAttribs == rCompare.maCharAttribs; +} + +EditTextObject::~EditTextObject() = default; + +std::unique_ptr<EditTextObject> EditTextObjectImpl::Clone() const +{ + return std::make_unique<EditTextObjectImpl>(*this); +} + +bool EditTextObject::Equals( const EditTextObject& rCompare ) const +{ + return toImpl(*this).Equals(toImpl(rCompare), false /*bComparePool*/); +} + +void EditTextObjectImpl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("editTextObject.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditTextObject")); + sal_Int32 nCount = GetParagraphCount(); + for (sal_Int32 i = 0; i < nCount; ++i) + { + maContents[i]->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +#if DEBUG_EDIT_ENGINE +void EditTextObjectImpl::Dump() const +{ + for (auto const& content : maContents) + content.Dump(); +} +#endif + +static rtl::Reference<SfxItemPool> getEditEngineItemPool(SfxItemPool* pPool, MapUnit eDefaultMetric) +{ + // #i101239# ensure target is an EditEngineItemPool, so that at + // pool destruction time of an alien pool, the pool is still alive. + // When registering would happen at an alien pool which just uses an + // EditEngineItemPool as some sub-pool, that pool could already + // be decoupled and deleted which would lead to crashes. + for (; pPool; pPool = pPool->GetSecondaryPool()) + if (dynamic_cast<EditEngineItemPool*>(pPool)) + return pPool; + + auto pRetval = EditEngine::CreatePool(); + pRetval->SetDefaultMetric(eDefaultMetric); + return pRetval; +} + +EditTextObjectImpl::EditTextObjectImpl(SfxItemPool* pP, MapUnit eDefaultMetric, bool bVertical, + TextRotation eRotation, SvtScriptType eScriptType) + : mpPool(getEditEngineItemPool(pP, eDefaultMetric)) + , meUserType(OutlinerMode::DontKnow) + , meScriptType(eScriptType) + , meRotation(eRotation) + , meMetric(eDefaultMetric) + , mbVertical(bVertical) +{ +} + +EditTextObjectImpl::EditTextObjectImpl( const EditTextObjectImpl& r ) + : mpPool(r.mpPool) + , meUserType(r.meUserType) + , meScriptType(r.meScriptType) + , meRotation(r.meRotation) + , meMetric(r.meMetric) + , mbVertical(r.mbVertical) +{ + // Do not copy PortionInfo + + maContents.reserve(r.maContents.size()); + for (auto const& content : r.maContents) + maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*content, *mpPool))); +} + +EditTextObjectImpl::~EditTextObjectImpl() +{ + ClearPortionInfo(); + + // Remove contents before deleting the pool instance since each content + // has to access the pool instance in its destructor. + maContents.clear(); +} + + +void EditTextObjectImpl::SetUserType( OutlinerMode n ) +{ + meUserType = n; +} + +void EditTextObjectImpl::NormalizeString( svl::SharedStringPool& rPool ) +{ + for (auto const& content : maContents) + { + ContentInfo& rInfo = *content; + rInfo.NormalizeString(rPool); + } +} + +std::vector<svl::SharedString> EditTextObjectImpl::GetSharedStrings() const +{ + std::vector<svl::SharedString> aSSs; + aSSs.reserve(maContents.size()); + for (auto const& content : maContents) + { + const ContentInfo& rInfo = *content; + aSSs.push_back(rInfo.GetSharedString()); + } + return aSSs; +} + +bool EditTextObjectImpl::IsEffectivelyVertical() const +{ + return (mbVertical && meRotation == TextRotation::NONE) || + (!mbVertical && meRotation != TextRotation::NONE); +} + +bool EditTextObjectImpl::IsTopToBottom() const +{ + return (mbVertical && meRotation == TextRotation::NONE) || + (!mbVertical && meRotation == TextRotation::TOPTOBOTTOM); +} + +void EditTextObjectImpl::SetVertical( bool bVert) +{ + if (bVert != mbVertical) + { + mbVertical = bVert; + ClearPortionInfo(); + } +} + +bool EditTextObjectImpl::GetVertical() const +{ + return mbVertical; +} + +void EditTextObjectImpl::SetRotation(TextRotation nRotation) +{ + if (meRotation != nRotation) + { + meRotation = nRotation; + ClearPortionInfo(); + } +} + +TextRotation EditTextObjectImpl::GetRotation() const +{ + return meRotation; +} + +XEditAttribute EditTextObjectImpl::CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd ) +{ + return XEditAttribute(*mpPool, rItem, nStart, nEnd); +} + +ContentInfo* EditTextObjectImpl::CreateAndInsertContent() +{ + maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*mpPool))); + return maContents.back().get(); +} + +sal_Int32 EditTextObjectImpl::GetParagraphCount() const +{ + size_t nSize = maContents.size(); + if (nSize > EE_PARA_MAX_COUNT) + { + SAL_WARN( "editeng", "EditTextObjectImpl::GetParagraphCount - overflow " << nSize); + return EE_PARA_MAX_COUNT; + } + return static_cast<sal_Int32>(nSize); +} + +OUString EditTextObjectImpl::GetText(sal_Int32 nPara) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return OUString(); + + return maContents[nPara]->GetText(); +} + +void EditTextObjectImpl::ClearPortionInfo() +{ + mpPortionInfo.reset(); +} + +bool EditTextObjectImpl::HasOnlineSpellErrors() const +{ + for (auto const& content : maContents) + { + if ( content->GetWrongList() && !content->GetWrongList()->empty() ) + return true; + } + return false; +} + +void EditTextObjectImpl::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + rLst.clear(); + const ContentInfo& rC = *maContents[nPara]; + for (const XEditAttribute & rAttr : rC.maCharAttribs) + { + EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem()); + rLst.push_back(aEEAttr); + } +} + +bool EditTextObjectImpl::IsFieldObject() const +{ + return GetField() != nullptr; +} + +const SvxFieldItem* EditTextObjectImpl::GetField() const +{ + if (maContents.size() == 1) + { + const ContentInfo& rC = *maContents[0]; + if (rC.GetText().getLength() == 1) + { + size_t nAttribs = rC.maCharAttribs.size(); + for (size_t nAttr = nAttribs; nAttr; ) + { + const XEditAttribute& rX = rC.maCharAttribs[--nAttr]; + if (rX.GetItem()->Which() == EE_FEATURE_FIELD) + return static_cast<const SvxFieldItem*>(rX.GetItem()); + } + } + } + return nullptr; +} + +const SvxFieldData* EditTextObjectImpl::GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return nullptr; + + const ContentInfo& rC = *maContents[nPara]; + if (nPos >= rC.maCharAttribs.size()) + // URL position is out-of-bound. + return nullptr; + + size_t nCurPos = 0; + for (XEditAttribute const& rAttr : rC.maCharAttribs) + { + if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD) + // Skip attributes that are not fields. + continue; + + const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(rAttr.GetItem()); + const SvxFieldData* pFldData = pField->GetField(); + if (nType != text::textfield::Type::UNSPECIFIED && nType != pFldData->GetClassId()) + // Field type doesn't match. Skip it. UNSPECIFIED matches all field types. + continue; + + if (nCurPos == nPos) + // Found it! + return pFldData; + + ++nCurPos; + } + + return nullptr; // field not found. +} + +bool EditTextObjectImpl::HasField( sal_Int32 nType ) const +{ + size_t nParagraphs = maContents.size(); + for (size_t nPara = 0; nPara < nParagraphs; ++nPara) + { + const ContentInfo& rC = *maContents[nPara]; + size_t nAttrs = rC.maCharAttribs.size(); + for (size_t nAttr = 0; nAttr < nAttrs; ++nAttr) + { + const XEditAttribute& rAttr = rC.maCharAttribs[nAttr]; + if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD) + continue; + + if (nType == text::textfield::Type::UNSPECIFIED) + // Match any field type. + return true; + + const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField(); + if (pFldData && pFldData->GetClassId() == nType) + return true; + } + } + return false; +} + +const SfxItemSet& EditTextObjectImpl::GetParaAttribs(sal_Int32 nPara) const +{ + const ContentInfo& rC = *maContents[nPara]; + return rC.GetParaAttribs(); +} + +bool EditTextObjectImpl::RemoveCharAttribs( sal_uInt16 _nWhich ) +{ + bool bChanged = false; + + for ( size_t nPara = maContents.size(); nPara; ) + { + ContentInfo& rC = *maContents[--nPara]; + + for (size_t nAttr = rC.maCharAttribs.size(); nAttr; ) + { + XEditAttribute& rAttr = rC.maCharAttribs[--nAttr]; + if ( !_nWhich || (rAttr.GetItem()->Which() == _nWhich) ) + { + rC.maCharAttribs.erase(rC.maCharAttribs.begin()+nAttr); + bChanged = true; + } + } + } + + if ( bChanged ) + ClearPortionInfo(); + + return bChanged; +} + +namespace { + +class FindByParagraph +{ + sal_Int32 mnPara; +public: + explicit FindByParagraph(sal_Int32 nPara) : mnPara(nPara) {} + bool operator() (const editeng::Section& rAttr) const + { + return rAttr.mnParagraph == mnPara; + } +}; + +class FindBySectionStart +{ + sal_Int32 mnPara; + sal_Int32 mnStart; +public: + FindBySectionStart(sal_Int32 nPara, sal_Int32 nStart) : mnPara(nPara), mnStart(nStart) {} + bool operator() (const editeng::Section& rAttr) const + { + return rAttr.mnParagraph == mnPara && rAttr.mnStart == mnStart; + } +}; + +} + +void EditTextObjectImpl::GetAllSections( std::vector<editeng::Section>& rAttrs ) const +{ + std::vector<editeng::Section> aAttrs; + aAttrs.reserve(maContents.size()); + std::vector<size_t> aBorders; + + for (size_t nPara = 0; nPara < maContents.size(); ++nPara) + { + aBorders.clear(); + const ContentInfo& rC = *maContents[nPara]; + aBorders.push_back(0); + aBorders.push_back(rC.GetText().getLength()); + for (const XEditAttribute & rAttr : rC.maCharAttribs) + { + const SfxPoolItem* pItem = rAttr.GetItem(); + if (!pItem) + continue; + + aBorders.push_back(rAttr.GetStart()); + aBorders.push_back(rAttr.GetEnd()); + } + + // Sort and remove duplicates for each paragraph. + std::sort(aBorders.begin(), aBorders.end()); + auto itUniqueEnd = std::unique(aBorders.begin(), aBorders.end()); + aBorders.erase(itUniqueEnd, aBorders.end()); + + // Create storage for each section. Note that this creates storage even + // for unformatted sections. The entries are sorted first by paragraph, + // then by section positions. They don't overlap with each other. + + if (aBorders.size() == 1 && aBorders[0] == 0) + { + // Empty paragraph. Push an empty section. + aAttrs.emplace_back(nPara, 0, 0); + continue; + } + + auto itBorder = aBorders.begin(), itBorderEnd = aBorders.end(); + size_t nPrev = *itBorder; + size_t nCur; + for (++itBorder; itBorder != itBorderEnd; ++itBorder, nPrev = nCur) + { + nCur = *itBorder; + aAttrs.emplace_back(nPara, nPrev, nCur); + } + } + + if (aAttrs.empty()) + return; + + // Go through all formatted paragraphs, and store format items. + std::vector<editeng::Section>::iterator itAttr = aAttrs.begin(); + for (sal_Int32 nPara = 0; nPara < static_cast<sal_Int32>(maContents.size()); ++nPara) + { + const ContentInfo& rC = *maContents[nPara]; + + itAttr = std::find_if(itAttr, aAttrs.end(), FindByParagraph(nPara)); + if (itAttr == aAttrs.end()) + { + // This should never happen. There is a logic error somewhere... + assert(false); + return; + } + + for (const XEditAttribute & rXAttr : rC.maCharAttribs) + { + const SfxPoolItem* pItem = rXAttr.GetItem(); + if (!pItem) + continue; + + sal_Int32 nStart = rXAttr.GetStart(), nEnd = rXAttr.GetEnd(); + + // Find the container whose start position matches. + std::vector<editeng::Section>::iterator itCurAttr = std::find_if(itAttr, aAttrs.end(), FindBySectionStart(nPara, nStart)); + if (itCurAttr == aAttrs.end()) + { + // This should never happen. There is a logic error somewhere... + assert(false); + return; + } + + for (; itCurAttr != aAttrs.end() && itCurAttr->mnParagraph == nPara && itCurAttr->mnEnd <= nEnd; ++itCurAttr) + { + editeng::Section& rSecAttr = *itCurAttr; + // serious bug: will cause duplicate attributes to be exported + if (std::none_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(), + [&pItem](SfxPoolItem const*const pIt) + { return pIt->Which() == pItem->Which(); })) + { + rSecAttr.maAttributes.push_back(pItem); + } + else + { + SAL_WARN("editeng", "GetAllSections(): duplicate attribute suppressed"); + } + } + } + } + + rAttrs.swap(aAttrs); +} + +void EditTextObjectImpl::GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& rFamily) const +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + const ContentInfo& rC = *maContents[nPara]; + rName = rC.GetStyle(); + rFamily = rC.GetFamily(); +} + +void EditTextObjectImpl::SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& rFamily) +{ + if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size()) + return; + + ContentInfo& rC = *maContents[nPara]; + rC.SetStyle(rName); + rC.SetFamily(rFamily); +} + +bool EditTextObjectImpl::ImpChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily ) +{ + const size_t nParagraphs = maContents.size(); + bool bChanges = false; + + for (size_t nPara = 0; nPara < nParagraphs; ++nPara) + { + ContentInfo& rC = *maContents[nPara]; + if ( rC.GetFamily() == eOldFamily ) + { + if ( rC.GetStyle() == rOldName ) + { + rC.SetStyle(rNewName); + rC.SetFamily(eNewFamily); + bChanges = true; + } + } + } + return bChanges; +} + +bool EditTextObjectImpl::ChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily) +{ + bool bChanges = ImpChangeStyleSheets( rOldName, eOldFamily, rNewName, eNewFamily ); + if ( bChanges ) + ClearPortionInfo(); + + return bChanges; +} + +void EditTextObjectImpl::ChangeStyleSheetName( SfxStyleFamily eFamily, + std::u16string_view rOldName, const OUString& rNewName ) +{ + ImpChangeStyleSheets( rOldName, eFamily, rNewName, eFamily ); +} + +bool EditTextObjectImpl::operator==( const EditTextObject& rCompare ) const +{ + return Equals(toImpl(rCompare), true); +} + +bool EditTextObjectImpl::Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const +{ + if( this == &rCompare ) + return true; + + if( ( bComparePool && mpPool != rCompare.mpPool ) || + ( meMetric != rCompare.meMetric ) || + ( meUserType!= rCompare.meUserType ) || + ( meScriptType != rCompare.meScriptType ) || + ( mbVertical != rCompare.mbVertical ) || + ( meRotation != rCompare.meRotation ) ) + return false; + + return std::equal( + maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(), + [bComparePool](const auto& c1, const auto& c2) { return c1->Equals(*c2, bComparePool); }); +} + +// #i102062# +bool EditTextObjectImpl::isWrongListEqual(const EditTextObject& rComp) const +{ + const EditTextObjectImpl& rCompare = toImpl(rComp); + return std::equal( + maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(), + [](const auto& c1, const auto& c2) { return c1->isWrongListEqual(*c2); }); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editobj2.hxx b/editeng/source/editeng/editobj2.hxx new file mode 100644 index 0000000000..fd1f1437e9 --- /dev/null +++ b/editeng/source/editeng/editobj2.hxx @@ -0,0 +1,282 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/editobj.hxx> +#include <editeng/fieldupdater.hxx> +#include <editeng/outliner.hxx> +#include <editdoc.hxx> + +#include <svl/sharedstring.hxx> +#include <svl/languageoptions.hxx> +#include <tools/long.hxx> +#include <tools/mapunit.hxx> + +#include <memory> +#include <vector> + +namespace editeng { + +struct Section; + +} + +namespace svl { + +class SharedStringPool; + +} + +class XEditAttribute +{ +private: + SfxPoolItemHolder maItemHolder; + sal_Int32 nStart; + sal_Int32 nEnd; + +public: + XEditAttribute(SfxItemPool&, const SfxPoolItem&, sal_Int32 nStart, sal_Int32 nEnd ); + + const SfxPoolItem* GetItem() const { return maItemHolder.getItem(); } + + sal_Int32& GetStart() { return nStart; } + sal_Int32& GetEnd() { return nEnd; } + + sal_Int32 GetStart() const { return nStart; } + sal_Int32 GetEnd() const { return nEnd; } + + sal_Int32 GetLen() const { return nEnd-nStart; } + + bool IsFeature() const; + void SetItem(SfxItemPool&, const SfxPoolItem&); + + inline bool operator==( const XEditAttribute& rCompare ) const; +}; + +inline bool XEditAttribute::operator==( const XEditAttribute& rCompare ) const +{ + return (nStart == rCompare.nStart) && + (nEnd == rCompare.nEnd) && + SfxPoolItem::areSame(GetItem(), rCompare.GetItem()); +} + +struct XParaPortion +{ + tools::Long nHeight; + sal_uInt16 nFirstLineOffset; + + EditLineList aLines; + TextPortionList aTextPortions; +}; + +class XParaPortionList +{ + typedef std::vector<std::unique_ptr<XParaPortion> > ListType; + ListType maList; + + VclPtr<OutputDevice> pRefDevPtr; + double mfFontScaleX; + double mfFontScaleY; + double mfSpacingScaleX; + double mfSpacingScaleY; + sal_uInt32 nPaperWidth; + +public: + XParaPortionList(OutputDevice* pRefDev, sal_uInt32 nPW, double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY); + + void push_back(XParaPortion* p); + const XParaPortion& operator[](size_t i) const; + + OutputDevice* GetRefDevPtr() const { return pRefDevPtr; } + sal_uInt32 GetPaperWidth() const { return nPaperWidth; } + bool RefDevIsVirtual() const {return pRefDevPtr->IsVirtual();} + const MapMode& GetRefMapMode() const { return pRefDevPtr->GetMapMode(); } + double getFontScaleX() const { return mfFontScaleX; } + double getFontScaleY() const { return mfFontScaleY; } + double getSpacingScaleX() const { return mfSpacingScaleX; } + double getSpacingScaleY() const { return mfSpacingScaleY; } +}; + +class ContentInfo +{ + friend class EditTextObjectImpl; + +private: + svl::SharedString maText; + OUString aStyle; + + std::vector<XEditAttribute> maCharAttribs; + SfxStyleFamily eFamily; + SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aParaAttribs; + std::unique_ptr<WrongList> + mpWrongs; + + ContentInfo( SfxItemPool& rPool ); + ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse ); + +public: + ~ContentInfo(); + ContentInfo(const ContentInfo&) = delete; + ContentInfo& operator=(const ContentInfo&) = delete; + + void NormalizeString( svl::SharedStringPool& rPool ); + const svl::SharedString& GetSharedString() const { return maText;} + OUString GetText() const; + void SetText( const OUString& rStr ); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + const std::vector<XEditAttribute>& GetCharAttribs() const { return maCharAttribs; } + std::vector<XEditAttribute>& GetCharAttribs() { return maCharAttribs; } + + const OUString& GetStyle() const { return aStyle; } + SfxStyleFamily GetFamily() const { return eFamily; } + + void SetStyle(const OUString& rStyle) { aStyle = rStyle; } + void SetFamily(const SfxStyleFamily& rFamily) { eFamily = rFamily; } + + const SfxItemSet& GetParaAttribs() const { return aParaAttribs; } + SfxItemSet& GetParaAttribs() { return aParaAttribs; } + + const WrongList* GetWrongList() const; + void SetWrongList( WrongList* p ); + bool Equals( const ContentInfo& rCompare, bool bComparePool ) const; + + // #i102062# + bool isWrongListEqual(const ContentInfo& rCompare) const; + +#if DEBUG_EDIT_ENGINE + void Dump() const; +#endif +}; + +class EditTextObjectImpl final : public EditTextObject +{ +public: + typedef std::vector<std::unique_ptr<ContentInfo> > ContentInfosType; + +private: + ContentInfosType maContents; + rtl::Reference<SfxItemPool> mpPool; + std::unique_ptr<XParaPortionList> mpPortionInfo; + + OutlinerMode meUserType; + SvtScriptType meScriptType; + TextRotation meRotation; + MapUnit meMetric; + + bool mbVertical; + + bool ImpChangeStyleSheets( std::u16string_view rOldName, SfxStyleFamily eOldFamily, + const OUString& rNewName, SfxStyleFamily eNewFamily ); + +public: + EditTextObjectImpl(SfxItemPool* pPool, MapUnit eDefaultMetric, bool bVertical, + TextRotation eRotation, SvtScriptType eScriptType); + EditTextObjectImpl( const EditTextObjectImpl& r ); + virtual ~EditTextObjectImpl() override; + + EditTextObjectImpl& operator=(const EditTextObjectImpl&) = delete; + + virtual OutlinerMode GetUserType() const override { return meUserType;} + virtual void SetUserType( OutlinerMode n ) override; + + virtual void NormalizeString( svl::SharedStringPool& rPool ) override; + virtual std::vector<svl::SharedString> GetSharedStrings() const override; + + virtual bool IsEffectivelyVertical() const override; + virtual bool GetVertical() const override; + virtual bool IsTopToBottom() const override; + virtual void SetVertical( bool bVert) override; + virtual void SetRotation(TextRotation nRotation) override; + virtual TextRotation GetRotation() const override; + + virtual SvtScriptType GetScriptType() const override { return meScriptType;} + + virtual std::unique_ptr<EditTextObject> Clone() const override; + + ContentInfo* CreateAndInsertContent(); + XEditAttribute CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd ); + + ContentInfosType& GetContents() { return maContents;} + const ContentInfosType& GetContents() const { return maContents;} + SfxItemPool* GetPool() { return mpPool.get(); } + virtual const SfxItemPool* GetPool() const override { return mpPool.get(); } + XParaPortionList* GetPortionInfo() const { return mpPortionInfo.get(); } + void SetPortionInfo( std::unique_ptr<XParaPortionList> pP ) + { mpPortionInfo = std::move(pP); } + + virtual sal_Int32 GetParagraphCount() const override; + virtual OUString GetText(sal_Int32 nParagraph) const override; + + virtual void ClearPortionInfo() override; + + virtual bool HasOnlineSpellErrors() const override; + + virtual void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const override; + + virtual bool RemoveCharAttribs( sal_uInt16 nWhich ) override; + + virtual void GetAllSections( std::vector<editeng::Section>& rAttrs ) const override; + + virtual bool IsFieldObject() const override; + virtual const SvxFieldItem* GetField() const override; + virtual const SvxFieldData* GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const override; + + virtual bool HasField( sal_Int32 nType = css::text::textfield::Type::UNSPECIFIED ) const override; + + virtual const SfxItemSet& GetParaAttribs(sal_Int32 nPara) const override; + + virtual void GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& eFamily) const override; + virtual void SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& eFamily) override; + virtual bool ChangeStyleSheets( + std::u16string_view rOldName, SfxStyleFamily eOldFamily, const OUString& rNewName, SfxStyleFamily eNewFamily) override; + virtual void ChangeStyleSheetName(SfxStyleFamily eFamily, std::u16string_view rOldName, const OUString& rNewName) override; + + virtual editeng::FieldUpdater GetFieldUpdater() override { return editeng::FieldUpdater(*this); } + + bool HasMetric() const { return meMetric != MapUnit::LASTENUMDUMMY; } + MapUnit GetMetric() const { return meMetric; } + + virtual bool operator==( const EditTextObject& rCompare ) const override; + bool Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const; + + // #i102062# + virtual bool isWrongListEqual(const EditTextObject& rCompare) const override; + +#if DEBUG_EDIT_ENGINE + virtual void Dump() const override; +#endif + virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override; +}; + +inline EditTextObjectImpl& toImpl(EditTextObject& rObj) +{ + assert(dynamic_cast<EditTextObjectImpl*>(&rObj)); + return static_cast<EditTextObjectImpl&>(rObj); +} + +inline const EditTextObjectImpl& toImpl(const EditTextObject& rObj) +{ + assert(dynamic_cast<const EditTextObjectImpl*>(&rObj)); + return static_cast<const EditTextObjectImpl&>(rObj); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editsel.cxx b/editeng/source/editeng/editsel.cxx new file mode 100644 index 0000000000..3aeed7a6e3 --- /dev/null +++ b/editeng/source/editeng/editsel.cxx @@ -0,0 +1,94 @@ +/* -*- 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 "editsel.hxx" +#include "impedit.hxx" +#include <editeng/editview.hxx> + + + +EditSelFunctionSet::EditSelFunctionSet() +{ + pCurView = nullptr; +} + +void EditSelFunctionSet::CreateAnchor() +{ + if ( pCurView ) + pCurView->pImpEditView->CreateAnchor(); +} + +void EditSelFunctionSet::DestroyAnchor() +{ + // Only with multiple selection +} + +void EditSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool ) +{ + if ( pCurView ) + pCurView->pImpEditView->SetCursorAtPoint( rPointPixel ); +} + +bool EditSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel ) +{ + if ( pCurView ) + return pCurView->pImpEditView->IsSelectionAtPoint( rPointPixel ); + + return false; +} + +void EditSelFunctionSet::DeselectAtPoint( const Point& ) +{ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// ! Implement when multiple selection is possible ! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +} + +void EditSelFunctionSet::BeginDrag() +{ + // Only with multiple selection +} + + +void EditSelFunctionSet::DeselectAll() +{ + if ( pCurView ) + pCurView->pImpEditView->DeselectAll(); +} + + + +EditSelectionEngine::EditSelectionEngine() : SelectionEngine( nullptr ) +{ + SetSelectionMode( SelectionMode::Range ); + EnableDrag( true ); +} + +void EditSelectionEngine::SetCurView( EditView* pNewView ) +{ + if ( GetFunctionSet() ) + const_cast<EditSelFunctionSet*>(static_cast<const EditSelFunctionSet*>(GetFunctionSet()))->SetCurView( pNewView ); + + if ( pNewView ) + SetWindow( pNewView->GetWindow() ); + else + SetWindow( nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editsel.hxx b/editeng/source/editeng/editsel.hxx new file mode 100644 index 0000000000..8d2adfb670 --- /dev/null +++ b/editeng/source/editeng/editsel.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/seleng.hxx> + +class EditView; + +class EditSelFunctionSet: public FunctionSet +{ +private: + EditView* pCurView; + +public: + EditSelFunctionSet(); + + virtual void BeginDrag() override; + + virtual void CreateAnchor() override; + virtual void DestroyAnchor() override; + + virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override; + + virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override; + virtual void DeselectAtPoint( const Point& rPointPixel ) override; + virtual void DeselectAll() override; + + void SetCurView( EditView* pView ) { pCurView = pView; } +}; + +class EditSelectionEngine : public SelectionEngine +{ +private: + +public: + EditSelectionEngine(); + + void SetCurView( EditView* pNewView ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editstt2.hxx b/editeng/source/editeng/editstt2.hxx new file mode 100644 index 0000000000..334622b234 --- /dev/null +++ b/editeng/source/editeng/editstt2.hxx @@ -0,0 +1,99 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/editstat.hxx> + +class InternalEditStatus : public EditStatus +{ + +public: + void TurnOnFlags( EEControlBits nFlags ) + { nControlBits |= nFlags; } + + void TurnOffFlags( EEControlBits nFlags ) + { nControlBits &= ~nFlags; } + + bool UseCharAttribs() const + { return bool( nControlBits & EEControlBits::USECHARATTRIBS ); } + + bool UseIdleFormatter() const + { return bool( nControlBits & EEControlBits::DOIDLEFORMAT); } + + bool AllowPasteSpecial() const + { return bool( nControlBits & EEControlBits::PASTESPECIAL ); } + + bool DoAutoIndenting() const + { return bool( nControlBits & EEControlBits::AUTOINDENTING ); } + + bool DoUndoAttribs() const + { return bool( nControlBits & EEControlBits::UNDOATTRIBS ); } + + bool OneCharPerLine() const + { return bool( nControlBits & EEControlBits::ONECHARPERLINE ); } + + bool IsOutliner() const + { return bool( nControlBits & EEControlBits::OUTLINER ); } + + bool DoNotUseColors() const + { return bool( nControlBits & EEControlBits::NOCOLORS ); } + + bool AllowBigObjects() const + { return bool( nControlBits & EEControlBits::ALLOWBIGOBJS ); } + + bool DoOnlineSpelling() const + { return bool( nControlBits & EEControlBits::ONLINESPELLING ); } + + bool DoStretch() const + { return bool( nControlBits & EEControlBits::STRETCHING ); } + + bool AutoPageSize() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZE ); } + bool AutoPageWidth() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZEX ); } + bool AutoPageHeight() const + { return bool( nControlBits & EEControlBits::AUTOPAGESIZEY ); } + + bool MarkNonUrlFields() const + { return bool( nControlBits & EEControlBits::MARKNONURLFIELDS ); } + + bool MarkUrlFields() const + { return bool( nControlBits & EEControlBits::MARKURLFIELDS ); } + + bool DoImportRTFStyleSheets() const + { return bool( nControlBits & EEControlBits::RTFSTYLESHEETS ); } + + bool DoAutoCorrect() const + { return bool( nControlBits & EEControlBits::AUTOCORRECT ); } + + bool DoAutoComplete() const + { return bool( nControlBits & EEControlBits::AUTOCOMPLETE ); } + + bool DoFormat100() const + { return bool( nControlBits & EEControlBits::FORMAT100 ); } + + bool ULSpaceSummation() const + { return bool( nControlBits & EEControlBits::ULSPACESUMMATION ); } + + bool IsSingleLine() const + { return bool( nControlBits & EEControlBits::SINGLELINE ); } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editundo.cxx b/editeng/source/editeng/editundo.cxx new file mode 100644 index 0000000000..5854d1683e --- /dev/null +++ b/editeng/source/editeng/editundo.cxx @@ -0,0 +1,661 @@ +/* -*- 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 <memory> +#include "impedit.hxx" +#include "editundo.hxx" +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <utility> +#include <osl/diagnose.h> + + +static void lcl_DoSetSelection( EditView const * pView, sal_uInt16 nPara ) +{ + EPaM aEPaM( nPara, 0 ); + EditPaM aPaM( pView->GetImpEditEngine()->CreateEditPaM( aEPaM ) ); + aPaM.SetIndex( aPaM.GetNode()->Len() ); + EditSelection aSel( aPaM, aPaM ); + pView->GetImpEditView()->SetEditSelection( aSel ); +} + +EditUndoManager::EditUndoManager(sal_uInt16 nMaxUndoActionCount ) +: SfxUndoManager(nMaxUndoActionCount), + mpEditEngine(nullptr) +{ +} + +void EditUndoManager::SetEditEngine(EditEngine* pNew) +{ + mpEditEngine = pNew; +} + +bool EditUndoManager::Undo() +{ + if ( !mpEditEngine || GetUndoActionCount() == 0 ) + return false; + + DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" ); + + if ( !mpEditEngine->GetActiveView() ) + { + if (!mpEditEngine->GetEditViews().empty()) + mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]); + else + { + OSL_FAIL("Undo in engine is not possible without a View! "); + return false; + } + } + + mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection + + mpEditEngine->SetUndoMode( true ); + bool bDone = SfxUndoManager::Undo(); + mpEditEngine->SetUndoMode( false ); + + EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() ); + DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () "); + DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () "); + + aNewSel.Min() = aNewSel.Max(); + mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); + if (mpEditEngine->IsUpdateLayout()) + mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView(), true ); + + return bDone; +} + +bool EditUndoManager::Redo() +{ + if ( !mpEditEngine || GetRedoActionCount() == 0 ) + return false; + + DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" ); + + if ( !mpEditEngine->GetActiveView() ) + { + if (!mpEditEngine->GetEditViews().empty()) + mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]); + else + { + OSL_FAIL( "Redo in Engine without View not possible!" ); + return false; + } + } + + mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection + + mpEditEngine->SetUndoMode( true ); + bool bDone = SfxUndoManager::Redo(); + mpEditEngine->SetUndoMode( false ); + + EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() ); + DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () "); + DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () "); + + aNewSel.Min() = aNewSel.Max(); + mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); + if (mpEditEngine->IsUpdateLayout()) + mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView() ); + + return bDone; +} + +EditUndo::EditUndo(sal_uInt16 nI, EditEngine* pEE) : + nId(nI), mnViewShellId(-1), mpEditEngine(pEE) +{ + const EditView* pEditView = mpEditEngine ? mpEditEngine->GetActiveView() : nullptr; + const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr; + if (pViewShell) + mnViewShellId = pViewShell->GetViewShellId(); +} + +EditUndo::~EditUndo() +{ +} + + +sal_uInt16 EditUndo::GetId() const +{ + return nId; +} + +bool EditUndo::CanRepeat(SfxRepeatTarget&) const +{ + return false; +} + +OUString EditUndo::GetComment() const +{ + OUString aComment; + + if (mpEditEngine) + aComment = mpEditEngine->GetUndoComment( GetId() ); + + return aComment; +} + +ViewShellId EditUndo::GetViewShellId() const +{ + return mnViewShellId; +} + +EditUndoDelContent::EditUndoDelContent( + EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion) : + EditUndo(EDITUNDO_DELCONTENT, pEE), + bDelObject(true), + nNode(nPortion), + pContentNode(pNode) {} + +EditUndoDelContent::~EditUndoDelContent() +{ + if ( bDelObject ) + delete pContentNode; +} + +void EditUndoDelContent::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->InsertContent( pContentNode, nNode ); + bDelObject = false; // belongs to the Engine again + EditSelection aSel( EditPaM( pContentNode, 0 ), EditPaM( pContentNode, pContentNode->Len() ) ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoDelContent::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + EditEngine* pEE = GetEditEngine(); + + // pNode is no longer correct, if the paragraphs where merged + // in between Undos + pContentNode = pEE->GetEditDoc().GetObject( nNode ); + DBG_ASSERT( pContentNode, "EditUndoDelContent::Redo(): Node?!" ); + + pEE->RemoveParaPortion(nNode); + + // Do not delete node, depends on the undo! + pEE->GetEditDoc().Release( nNode ); + if (pEE->IsCallParaInsertedOrDeleted()) + pEE->ParagraphDeleted( nNode ); + + DeletedNodeInfo* pInf = new DeletedNodeInfo( pContentNode, nNode ); + pEE->AppendDeletedNodeInfo(pInf); + pEE->UpdateSelections(); + + ContentNode* pN = ( nNode < pEE->GetEditDoc().Count() ) + ? pEE->GetEditDoc().GetObject( nNode ) + : pEE->GetEditDoc().GetObject( nNode-1 ); + DBG_ASSERT( pN && ( pN != pContentNode ), "?! RemoveContent !? " ); + EditPaM aPaM( pN, pN->Len() ); + + bDelObject = true; // belongs to the Engine again + + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoConnectParas::EditUndoConnectParas( + EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP, + SfxItemSet _aLeftParaAttribs, SfxItemSet _aRightParaAttribs, + const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBkwrd) : + EditUndo(EDITUNDO_CONNECTPARAS, pEE), + nNode(nN), + nSepPos(nSP), + aLeftParaAttribs(std::move(_aLeftParaAttribs)), + aRightParaAttribs(std::move(_aRightParaAttribs)), + eLeftStyleFamily(SfxStyleFamily::All), + eRightStyleFamily(SfxStyleFamily::All), + bBackward(bBkwrd) +{ + if ( pLeftStyle ) + { + aLeftStyleName = pLeftStyle->GetName(); + eLeftStyleFamily = pLeftStyle->GetFamily(); + } + if ( pRightStyle ) + { + aRightStyleName = pRightStyle->GetName(); + eRightStyleFamily = pRightStyle->GetFamily(); + } +} + +EditUndoConnectParas::~EditUndoConnectParas() +{ +} + +void EditUndoConnectParas::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + // For SplitContent ParagraphInserted can not be called yet because the + // Outliner relies on the attributes to initialize the depth + + bool bCall = GetEditEngine()->IsCallParaInsertedOrDeleted(); + GetEditEngine()->SetCallParaInsertedOrDeleted(false); + + EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos); + + GetEditEngine()->SetCallParaInsertedOrDeleted( bCall ); + if (GetEditEngine()->IsCallParaInsertedOrDeleted()) + { + GetEditEngine()->ParagraphInserted( nNode+1 ); + GetEditEngine()->SetParaAttribs( nNode+1, aRightParaAttribs ); + } + + // Calling SetParaAttribs is effective only after ParagraphInserted + GetEditEngine()->SetParaAttribs( nNode, aLeftParaAttribs ); + + if (GetEditEngine()->GetStyleSheetPool()) + { + if ( !aLeftStyleName.isEmpty() ) + GetEditEngine()->SetStyleSheet( nNode, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aLeftStyleName, eLeftStyleFamily )) ); + if ( !aRightStyleName.isEmpty() ) + GetEditEngine()->SetStyleSheet( nNode+1, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aRightStyleName, eRightStyleFamily )) ); + } + + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +void EditUndoConnectParas::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: Np Active View!" ); + EditPaM aPaM = GetEditEngine()->ConnectContents( nNode, bBackward ); + + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoSplitPara::EditUndoSplitPara( + EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP) : + EditUndo(EDITUNDO_SPLITPARA, pEE), + nNode(nN), nSepPos(nSP) {} + +EditUndoSplitPara::~EditUndoSplitPara() {} + +void EditUndoSplitPara::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->ConnectContents(nNode, false); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +void EditUndoSplitPara::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) ); +} + +EditUndoInsertChars::EditUndoInsertChars( + EditEngine* pEE, const EPaM& rEPaM, OUString aStr) : + EditUndo(EDITUNDO_INSERTCHARS, pEE), + aEPaM(rEPaM), + aText(std::move(aStr)) {} + +void EditUndoInsertChars::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + EditPaM aNewPaM( GetEditEngine()->DeleteSelection(aSel) ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aNewPaM, aNewPaM ) ); +} + +void EditUndoInsertChars::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + GetEditEngine()->InsertText(EditSelection(aPaM, aPaM), aText); + EditPaM aNewPaM( aPaM ); + aNewPaM.SetIndex( aNewPaM.GetIndex() + aText.getLength() ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aNewPaM ) ); +} + +bool EditUndoInsertChars::Merge( SfxUndoAction* pNextAction ) +{ + EditUndoInsertChars* pNext = dynamic_cast<EditUndoInsertChars*>(pNextAction); + if (!pNext) + return false; + + if ( aEPaM.nPara != pNext->aEPaM.nPara ) + return false; + + if ( ( aEPaM.nIndex + aText.getLength() ) == pNext->aEPaM.nIndex ) + { + aText += pNext->aText; + return true; + } + return false; +} + +EditUndoRemoveChars::EditUndoRemoveChars( + EditEngine* pEE, const EPaM& rEPaM, OUString aStr) : + EditUndo(EDITUNDO_REMOVECHARS, pEE), + aEPaM(rEPaM), aText(std::move(aStr)) {} + +void EditUndoRemoveChars::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + GetEditEngine()->InsertText(aSel, aText); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoRemoveChars::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() ); + EditPaM aNewPaM = GetEditEngine()->DeleteSelection(aSel); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aNewPaM); +} + +EditUndoInsertFeature::EditUndoInsertFeature( + EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature) : + EditUndo(EDITUNDO_INSERTFEATURE, pEE), + aEPaM(rEPaM), + pFeature(rFeature.Clone()) +{ + DBG_ASSERT( pFeature, "Feature could not be duplicated: EditUndoInsertFeature" ); +} + +EditUndoInsertFeature::~EditUndoInsertFeature() +{ +} + +void EditUndoInsertFeature::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + // Attributes are then corrected implicitly by the document ... + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + GetEditEngine()->DeleteSelection(aSel); + aSel.Max().SetIndex( aSel.Max().GetIndex()-1 ); // For Selection + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +void EditUndoInsertFeature::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM); + EditSelection aSel( aPaM, aPaM ); + GetEditEngine()->InsertFeature(aSel, *pFeature); + if ( pFeature->Which() == EE_FEATURE_FIELD ) + GetEditEngine()->UpdateFieldsOnly(); + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +EditUndoMoveParagraphs::EditUndoMoveParagraphs( + EditEngine* pEE, const Range& rParas, sal_Int32 n) : + EditUndo(EDITUNDO_MOVEPARAGRAPHS, pEE), nParagraphs(rParas), nDest(n) {} + +EditUndoMoveParagraphs::~EditUndoMoveParagraphs() {} + +void EditUndoMoveParagraphs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + Range aTmpRange( nParagraphs ); + tools::Long nTmpDest = aTmpRange.Min(); + + tools::Long nDiff = nDest - aTmpRange.Min(); + aTmpRange.Min() += nDiff; + aTmpRange.Max() += nDiff; + + if ( nParagraphs.Min() < static_cast<tools::Long>(nDest) ) + { + tools::Long nLen = aTmpRange.Len(); + aTmpRange.Min() -= nLen; + aTmpRange.Max() -= nLen; + } + else + nTmpDest += aTmpRange.Len(); + + EditSelection aNewSel = GetEditEngine()->MoveParagraphs(aTmpRange, nTmpDest); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +void EditUndoMoveParagraphs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditSelection aNewSel = GetEditEngine()->MoveParagraphs(nParagraphs, nDest); + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +EditUndoSetStyleSheet::EditUndoSetStyleSheet( + EditEngine* pEE, sal_Int32 nP, OUString _aPrevName, SfxStyleFamily ePrevFam, + OUString _aNewName, SfxStyleFamily eNewFam, SfxItemSet _aPrevParaAttribs) : + EditUndo(EDITUNDO_STYLESHEET, pEE), + nPara(nP), + aPrevName(std::move(_aPrevName)), + aNewName(std::move(_aNewName)), + ePrevFamily(ePrevFam), + eNewFamily(eNewFam), + aPrevParaAttribs(std::move(_aPrevParaAttribs)) +{ +} + +EditUndoSetStyleSheet::~EditUndoSetStyleSheet() +{ +} + +void EditUndoSetStyleSheet::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aPrevName, ePrevFamily )) ); + GetEditEngine()->SetParaAttribsOnly( nPara, aPrevParaAttribs ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +void EditUndoSetStyleSheet::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aNewName, eNewFamily )) ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +EditUndoSetParaAttribs::EditUndoSetParaAttribs( + EditEngine* pEE, sal_Int32 nP, SfxItemSet _aPrevItems, SfxItemSet _aNewItems) : + EditUndo(EDITUNDO_PARAATTRIBS, pEE), + nPara(nP), + aPrevItems(std::move(_aPrevItems)), + aNewItems(std::move(_aNewItems)) {} + +EditUndoSetParaAttribs::~EditUndoSetParaAttribs() {} + +void EditUndoSetParaAttribs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetParaAttribsOnly( nPara, aPrevItems ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +void EditUndoSetParaAttribs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + GetEditEngine()->SetParaAttribsOnly( nPara, aNewItems ); + lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara ); +} + +EditUndoSetAttribs::EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems) : + EditUndo(EDITUNDO_ATTRIBS, pEE), + aESel(rESel), + aNewAttribs(std::move(aNewItems)), + nSpecial(SetAttribsMode::NONE), + m_bSetSelection(true), + // When EditUndoSetAttribs actually is a RemoveAttribs this could be + // recognize by the empty itemset, but then it would have to be caught in + // its own place, which possible a setAttribs does with an empty itemset. + bSetIsRemove(false), + bRemoveParaAttribs(false), + nRemoveWhich(0) +{ +} + +EditUndoSetAttribs::~EditUndoSetAttribs() +{ +} + +void EditUndoSetAttribs::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + bool bFields = false; + for ( sal_Int32 nPara = aESel.nStartPara; nPara <= aESel.nEndPara; nPara++ ) + { + const ContentAttribsInfo& rInf = *aPrevAttribs[nPara-aESel.nStartPara]; + + // first the paragraph attributes ... + pEE->SetParaAttribsOnly(nPara, rInf.GetPrevParaAttribs()); + + // Then the character attributes ... + // Remove all attributes including features, are later re-established. + pEE->RemoveCharAttribs(nPara, 0, true); + DBG_ASSERT( pEE->GetEditDoc().GetObject( nPara ), "Undo (SetAttribs): pNode = NULL!" ); + ContentNode* pNode = pEE->GetEditDoc().GetObject( nPara ); + for (const auto & nAttr : rInf.GetPrevCharAttribs()) + { + const EditCharAttrib& rX = *nAttr; + // is automatically "poolsized" + pEE->GetEditDoc().InsertAttrib(pNode, rX.GetStart(), rX.GetEnd(), *rX.GetItem()); + if (rX.Which() == EE_FEATURE_FIELD) + bFields = true; + } + } + if ( bFields ) + pEE->UpdateFieldsOnly(); + if (m_bSetSelection) + { + ImpSetSelection(); + } +} + +void EditUndoSetAttribs::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aESel); + if ( !bSetIsRemove ) + pEE->SetAttribs( aSel, aNewAttribs, nSpecial ); + else + pEE->RemoveCharAttribs( aSel, bRemoveParaAttribs, nRemoveWhich ); + + if (m_bSetSelection) + { + ImpSetSelection(); + } +} + +void EditUndoSetAttribs::AppendContentInfo(ContentAttribsInfo* pNew) +{ + aPrevAttribs.push_back(std::unique_ptr<ContentAttribsInfo>(pNew)); +} + +void EditUndoSetAttribs::ImpSetSelection() +{ + EditEngine* pEE = GetEditEngine(); + EditSelection aSel = pEE->CreateSelection(aESel); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection(aSel); +} + +EditUndoTransliteration::EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nM) : + EditUndo(EDITUNDO_TRANSLITERATE, pEE), + aOldESel(rESel), nMode(nM) {} + +EditUndoTransliteration::~EditUndoTransliteration() +{ +} + +void EditUndoTransliteration::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aNewESel); + + // Insert text, but don't expand Attribs at the current position: + aSel = pEE->DeleteSelected( aSel ); + EditSelection aDelSel( aSel ); + aSel = pEE->InsertParaBreak( aSel ); + aDelSel.Max() = aSel.Min(); + aDelSel.Max().GetNode()->GetCharAttribs().DeleteEmptyAttribs(); + EditSelection aNewSel; + if ( pTxtObj ) + { + aNewSel = pEE->InsertText( *pTxtObj, aSel ); + } + else + { + aNewSel = pEE->InsertText( aSel, aText ); + } + if ( aNewSel.Min().GetNode() == aDelSel.Max().GetNode() ) + { + aNewSel.Min().SetNode( aDelSel.Min().GetNode() ); + aNewSel.Min().SetIndex( aNewSel.Min().GetIndex() + aDelSel.Min().GetIndex() ); + } + if ( aNewSel.Max().GetNode() == aDelSel.Max().GetNode() ) + { + aNewSel.Max().SetNode( aDelSel.Min().GetNode() ); + aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + aDelSel.Min().GetIndex() ); + } + pEE->DeleteSelected( aDelSel ); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +void EditUndoTransliteration::Redo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + EditEngine* pEE = GetEditEngine(); + + EditSelection aSel = pEE->CreateSelection(aOldESel); + EditSelection aNewSel = pEE->TransliterateText( aSel, nMode ); + pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel ); +} + +EditUndoMarkSelection::EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel) : + EditUndo(EDITUNDO_MARKSELECTION, pEE), aSelection(rSel) {} + +EditUndoMarkSelection::~EditUndoMarkSelection() {} + +void EditUndoMarkSelection::Undo() +{ + DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" ); + if ( GetEditEngine()->GetActiveView() ) + { + if ( GetEditEngine()->IsFormatted() ) + GetEditEngine()->GetActiveView()->SetSelection( aSelection ); + else + GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( GetEditEngine()->CreateSelection(aSelection) ); + } +} + +void EditUndoMarkSelection::Redo() +{ + // For redo unimportant, because at the beginning of the undo parentheses +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editundo.hxx b/editeng/source/editeng/editundo.hxx new file mode 100644 index 0000000000..d08bc4810b --- /dev/null +++ b/editeng/source/editeng/editundo.hxx @@ -0,0 +1,291 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editdoc.hxx> +#include <editeng/editund2.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editobj.hxx> +#include <vector> +#include <memory> + +class EditTextObject; +class EditEngine; +enum class SetAttribsMode; +enum class TransliterationFlags; + +// EditUndoDelContent + +class EditUndoDelContent : public EditUndo +{ +private: + bool bDelObject; + sal_Int32 nNode; + ContentNode* pContentNode; // Points to the valid, + // undestroyed object! + +public: + EditUndoDelContent(EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion); + virtual ~EditUndoDelContent() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoConnectParas + +class EditUndoConnectParas : public EditUndo +{ +private: + sal_Int32 nNode; + sal_uInt16 nSepPos; + SfxItemSet aLeftParaAttribs; + SfxItemSet aRightParaAttribs; + + // 2 Pointers would be nicer but then it would have to be a SfxListener. + OUString aLeftStyleName; + OUString aRightStyleName; + SfxStyleFamily eLeftStyleFamily; + SfxStyleFamily eRightStyleFamily; + + bool bBackward; + +public: + EditUndoConnectParas(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos, + SfxItemSet aLeftParaAttribs, SfxItemSet aRightParaAttribs, + const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBackward); + virtual ~EditUndoConnectParas() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSplitPara + +class EditUndoSplitPara : public EditUndo +{ +private: + sal_Int32 nNode; + sal_uInt16 nSepPos; + +public: + EditUndoSplitPara(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos); + virtual ~EditUndoSplitPara() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoInsertChars + +class EditUndoInsertChars : public EditUndo +{ +private: + EPaM aEPaM; + OUString aText; + +public: + EditUndoInsertChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr); + + virtual void Undo() override; + virtual void Redo() override; + + virtual bool Merge( SfxUndoAction *pNextAction ) override; +}; + + +// EditUndoRemoveChars + +class EditUndoRemoveChars : public EditUndo +{ +private: + EPaM aEPaM; + OUString aText; + +public: + EditUndoRemoveChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr); + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoInsertFeature + +class EditUndoInsertFeature : public EditUndo +{ +private: + EPaM aEPaM; + std::unique_ptr<SfxPoolItem> pFeature; + +public: + EditUndoInsertFeature(EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature); + virtual ~EditUndoInsertFeature() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoMoveParagraphs + +class EditUndoMoveParagraphs: public EditUndo +{ +private: + Range nParagraphs; + sal_Int32 nDest; + +public: + EditUndoMoveParagraphs(EditEngine* pEE, const Range& rParas, sal_Int32 nDest); + virtual ~EditUndoMoveParagraphs() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetStyleSheet + +class EditUndoSetStyleSheet: public EditUndo +{ +private: + sal_Int32 nPara; + OUString aPrevName; + OUString aNewName; + SfxStyleFamily ePrevFamily; + SfxStyleFamily eNewFamily; + SfxItemSet aPrevParaAttribs; + +public: + EditUndoSetStyleSheet(EditEngine* pEE, sal_Int32 nPara, + OUString aPrevName, SfxStyleFamily ePrevFamily, + OUString aNewName, SfxStyleFamily eNewFamily, + SfxItemSet aPrevParaAttribs); + virtual ~EditUndoSetStyleSheet() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetParaAttribs + +class EditUndoSetParaAttribs: public EditUndo +{ +private: + sal_Int32 nPara; + SfxItemSet aPrevItems; + SfxItemSet aNewItems; + +public: + EditUndoSetParaAttribs(EditEngine* pEE, sal_Int32 nPara, SfxItemSet aPrevItems, SfxItemSet aNewItems); + virtual ~EditUndoSetParaAttribs() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoSetAttribs + +class EditUndoSetAttribs: public EditUndo +{ +private: + typedef std::vector<std::unique_ptr<ContentAttribsInfo> > InfoArrayType; + + ESelection aESel; + SfxItemSet aNewAttribs; + InfoArrayType aPrevAttribs; + + SetAttribsMode nSpecial; + /// Once the attributes are set / unset, set the selection to the end of the formatted range? + bool m_bSetSelection; + bool bSetIsRemove; + bool bRemoveParaAttribs; + sal_uInt16 nRemoveWhich; + + void ImpSetSelection(); + + +public: + EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems); + virtual ~EditUndoSetAttribs() override; + + SfxItemSet& GetNewAttribs() { return aNewAttribs; } + + void SetSpecial( SetAttribsMode n ) { nSpecial = n; } + void SetUpdateSelection( bool bSetSelection ) { m_bSetSelection = bSetSelection; } + void SetRemoveAttribs( bool b ) { bSetIsRemove = b; } + void SetRemoveParaAttribs( bool b ) { bRemoveParaAttribs = b; } + void SetRemoveWhich( sal_uInt16 n ) { nRemoveWhich = n; } + + virtual void Undo() override; + virtual void Redo() override; + + void AppendContentInfo(ContentAttribsInfo* pNew); +}; + + +// EditUndoTransliteration + +class EditUndoTransliteration: public EditUndo +{ +private: + ESelection aOldESel; + ESelection aNewESel; + + TransliterationFlags + nMode; + std::unique_ptr<EditTextObject> + pTxtObj; + OUString aText; + +public: + EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nMode); + virtual ~EditUndoTransliteration() override; + + void SetText( const OUString& rText ) { aText = rText; } + void SetText( std::unique_ptr<EditTextObject> pObj ) { pTxtObj = std::move( pObj ); } + void SetNewSelection( const ESelection& rSel ) { aNewESel = rSel; } + + virtual void Undo() override; + virtual void Redo() override; +}; + + +// EditUndoMarkSelection + +class EditUndoMarkSelection: public EditUndo +{ +private: + ESelection aSelection; + +public: + EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel); + virtual ~EditUndoMarkSelection() override; + + virtual void Undo() override; + virtual void Redo() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx new file mode 100644 index 0000000000..e047e6a41f --- /dev/null +++ b/editeng/source/editeng/editview.cxx @@ -0,0 +1,1800 @@ +/* -*- 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 <memory> +#include <vcl/image.hxx> + +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> + +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <svl/languageoptions.hxx> +#include <svtools/ctrltool.hxx> +#include <svtools/langtab.hxx> +#include <tools/stream.hxx> + +#include <svl/srchitem.hxx> + +#include "impedit.hxx" +#include <comphelper/propertyvalue.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include <editeng/editrids.hrc> +#include <editeng.hxx> +#include <i18nlangtag/lang.h> +#include <vcl/window.hxx> +#include <editeng/acorrcfg.hxx> +#include <editeng/unolingu.hxx> +#include <unotools/lingucfg.hxx> + +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <linguistic/lngprops.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/viewsh.hxx> +#include <osl/diagnose.h> +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; + + +// static +LanguageType EditView::CheckLanguage( + const OUString &rText, + const Reference< linguistic2::XSpellChecker1 >& xSpell, + const Reference< linguistic2::XLanguageGuessing >& xLangGuess, + bool bIsParaText ) +{ + LanguageType nLang = LANGUAGE_NONE; + if (bIsParaText) // check longer texts with language-guessing... + { + if (!xLangGuess.is()) + return nLang; + + LanguageTag aGuessTag( xLangGuess->guessPrimaryLanguage( rText, 0, rText.getLength()) ); + + // If the result from language guessing does not provide a 'Country' + // part, try to get it by looking up the locale setting of the office, + // "Tools/Options - Languages and Locales - General: Locale setting", if + // the language matches. + if ( aGuessTag.getCountry().isEmpty() ) + { + const LanguageTag& rAppLocaleTag = Application::GetSettings().GetLanguageTag(); + if (rAppLocaleTag.getLanguage() == aGuessTag.getLanguage()) + nLang = rAppLocaleTag.getLanguageType(); + } + if (nLang == LANGUAGE_NONE) // language not found by looking up the system language... + nLang = aGuessTag.makeFallback().getLanguageType(); // best known locale match + if (nLang == LANGUAGE_SYSTEM) + nLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + if (nLang == LANGUAGE_DONTKNOW) + nLang = LANGUAGE_NONE; + } + else // check single word + { + if (!xSpell.is()) + return nLang; + + + // build list of languages to check + + LanguageType aLangList[4]; + const AllSettings& rSettings = Application::GetSettings(); + SvtLinguOptions aLinguOpt; + SvtLinguConfig().GetOptions( aLinguOpt ); + // The default document language from "Tools/Options - Languages and Locales - General: Western" + aLangList[0] = MsLangId::resolveSystemLanguageByScriptType( aLinguOpt.nDefaultLanguage, + css::i18n::ScriptType::LATIN); + // The one from "Tools/Options - Languages and Locales - General: User interface" + aLangList[1] = rSettings.GetUILanguageTag().getLanguageType(); + // The one from "Tools/Options - Languages and Locales - General: Locale setting" + aLangList[2] = rSettings.GetLanguageTag().getLanguageType(); + // en-US + aLangList[3] = LANGUAGE_ENGLISH_US; +#ifdef DEBUG + lang::Locale a0( LanguageTag::convertToLocale( aLangList[0] ) ); + lang::Locale a1( LanguageTag::convertToLocale( aLangList[1] ) ); + lang::Locale a2( LanguageTag::convertToLocale( aLangList[2] ) ); + lang::Locale a3( LanguageTag::convertToLocale( aLangList[3] ) ); +#endif + + for (const LanguageType& nTmpLang : aLangList) + { + if (nTmpLang != LANGUAGE_NONE && nTmpLang != LANGUAGE_DONTKNOW) + { + if (xSpell->hasLanguage( static_cast<sal_uInt16>(nTmpLang) ) && + xSpell->isValid( rText, static_cast<sal_uInt16>(nTmpLang), Sequence< PropertyValue >() )) + { + nLang = nTmpLang; + break; + } + } + } + } + + return nLang; +} + +EditViewCallbacks::~EditViewCallbacks() +{ +} + +EditView::EditView( EditEngine* pEng, vcl::Window* pWindow ) +{ + pImpEditView.reset( new ImpEditView( this, pEng, pWindow ) ); +} + +EditView::~EditView() +{ +} + +void EditView::setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks) +{ + pImpEditView->setEditViewCallbacks(pEditViewCallbacks); +} + +EditViewCallbacks* EditView::getEditViewCallbacks() const +{ + return pImpEditView->getEditViewCallbacks(); +} + +ImpEditEngine* EditView::GetImpEditEngine() const +{ + return pImpEditView->pEditEngine->pImpEditEngine.get(); +} + +EditEngine* EditView::GetEditEngine() const +{ + return pImpEditView->pEditEngine; +} + +tools::Rectangle EditView::GetInvalidateRect() const +{ + if ( !pImpEditView->DoInvalidateMore() ) + return pImpEditView->aOutArea; + else + { + tools::Rectangle aRect( pImpEditView->aOutArea ); + tools::Long nMore = pImpEditView->GetOutputDevice().PixelToLogic( Size( pImpEditView->GetInvalidateMore(), 0 ) ).Width(); + aRect.AdjustLeft( -nMore ); + aRect.AdjustRight(nMore ); + aRect.AdjustTop( -nMore ); + aRect.AdjustBottom(nMore ); + return aRect; + } +} + +namespace { + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()); +} + +} + +void EditView::InvalidateWindow(const tools::Rectangle& rClipRect) +{ + bool bNegativeX = IsNegativeX(); + if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks()) + { + // do not invalidate and trigger a global repaint, but forward + // the need for change to the applied EditViewCallback, can e.g. + // be used to visualize the active edit text in an OverlayObject + pEditViewCallbacks->EditViewInvalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect); + } + else + { + // classic mode: invalidate and trigger full repaint + // of the changed area + GetWindow()->Invalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect); + } +} + +void EditView::InvalidateOtherViewWindows( const tools::Rectangle& rInvRect ) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + bool bNegativeX = IsNegativeX(); + for (auto& pWin : pImpEditView->aOutWindowSet) + { + if (pWin) + pWin->Invalidate( bNegativeX ? lcl_negateRectX(rInvRect) : rInvRect ); + } + } +} + +void EditView::Invalidate() +{ + const tools::Rectangle& rInvRect = GetInvalidateRect(); + pImpEditView->InvalidateAtWindow(rInvRect); + InvalidateOtherViewWindows(rInvRect); +} + +void EditView::SetReadOnly( bool bReadOnly ) +{ + pImpEditView->bReadOnly = bReadOnly; +} + +bool EditView::IsReadOnly() const +{ + return pImpEditView->bReadOnly; +} + +void EditView::SetSelection( const ESelection& rESel ) +{ + // If someone has just left an empty attribute, and then the outliner manipulates the + // selection, call the CursorMoved method so that empty attributes get cleaned up. + if ( !HasSelection() ) + { + // tdf#113591 Get node from EditDoc, as the selection might have a pointer to an + // already deleted node. + const ContentNode* pNode(pImpEditView->pEditEngine->GetEditDoc().GetEndPaM().GetNode()); + if (nullptr != pNode) + pNode->checkAndDeleteEmptyAttribs(); + } + EditSelection aNewSelection( pImpEditView->pEditEngine->pImpEditEngine->ConvertSelection( + rESel.nStartPara, rESel.nStartPos, rESel.nEndPara, rESel.nEndPos ) ); + + // If the selection is manipulated after a KeyInput: + pImpEditView->pEditEngine->CheckIdleFormatter(); + + // Selection may not start/end at an invisible paragraph: + const ParaPortion* pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Min().GetNode() ); + if ( !pPortion->IsVisible() ) + { + pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion ); + ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 ); + aNewSelection.Min() = EditPaM( pNode, pNode->Len() ); + } + pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Max().GetNode() ); + if ( !pPortion->IsVisible() ) + { + pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion ); + ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 ); + aNewSelection.Max() = EditPaM( pNode, pNode->Len() ); + } + + pImpEditView->DrawSelectionXOR(); + pImpEditView->SetEditSelection( aNewSelection ); + pImpEditView->DrawSelectionXOR(); + bool bGotoCursor = pImpEditView->DoAutoScroll(); + + // comments section in Writer: + // don't scroll to the selection if it is + // out of visible area of comment canvas. + if (HasSelection()) + ShowCursor( bGotoCursor ); +} + +ESelection EditView::GetSelection() const +{ + ESelection aSelection; + + aSelection.nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Min().GetNode() ); + aSelection.nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Max().GetNode() ); + + aSelection.nStartPos = pImpEditView->GetEditSelection().Min().GetIndex(); + aSelection.nEndPos = pImpEditView->GetEditSelection().Max().GetIndex(); + + return aSelection; +} + +bool EditView::HasSelection() const +{ + return pImpEditView->HasSelection(); +} + +bool EditView::IsSelectionFullPara() const +{ + return pImpEditView->IsSelectionFullPara(); +} + +bool EditView::IsSelectionWithinSinglePara() const +{ + return pImpEditView->IsSelectionInSinglePara(); +} + +bool EditView::IsSelectionAtPoint(const Point& rPointPixel) +{ + return pImpEditView->IsSelectionAtPoint(rPointPixel); +} + +void EditView::DeleteSelected() +{ + pImpEditView->DeleteSelected(); +} + +SvtScriptType EditView::GetSelectedScriptType() const +{ + return pImpEditView->pEditEngine->GetScriptType( pImpEditView->GetEditSelection() ); +} + +void EditView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects) const +{ + return pImpEditView->GetSelectionRectangles(pImpEditView->GetEditSelection(), rLogicRects); +} + +void EditView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + pImpEditView->pEditEngine->pImpEditEngine->Paint( pImpEditView.get(), rRect, pTargetDevice ); +} + +void EditView::SetEditEngine( EditEngine* pEditEng ) +{ + pImpEditView->pEditEngine = pEditEng; + EditSelection aStartSel = pImpEditView->pEditEngine->GetEditDoc().GetStartPaM(); + pImpEditView->SetEditSelection( aStartSel ); +} + +void EditView::SetWindow( vcl::Window* pWin ) +{ + pImpEditView->pOutWin = pWin; + pImpEditView->pEditEngine->pImpEditEngine->GetSelEngine().Reset(); +} + +vcl::Window* EditView::GetWindow() const +{ + return pImpEditView->pOutWin; +} + +OutputDevice& EditView::GetOutputDevice() const +{ + return pImpEditView->GetOutputDevice(); +} + +LanguageType EditView::GetInputLanguage() const +{ + // it might make sense to add this to getEditViewCallbacks + if (const vcl::Window* pWindow = GetWindow()) + return pWindow->GetInputLanguage(); + return LANGUAGE_DONTKNOW; +} + +bool EditView::HasOtherViewWindow( vcl::Window* pWin ) +{ + OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet; + auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin); + return (found != rOutWindowSet.end()); +} + +bool EditView::AddOtherViewWindow( vcl::Window* pWin ) +{ + if (HasOtherViewWindow(pWin)) + return false; + pImpEditView->aOutWindowSet.emplace_back(pWin); + return true; +} + +bool EditView::RemoveOtherViewWindow( vcl::Window* pWin ) +{ + OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet; + auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin); + if (found == rOutWindowSet.end()) + return false; + rOutWindowSet.erase(found); + return true; +} + +void EditView::SetVisArea( const tools::Rectangle& rRect ) +{ + pImpEditView->SetVisDocStartPos( rRect.TopLeft() ); +} + +tools::Rectangle EditView::GetVisArea() const +{ + return pImpEditView->GetVisDocArea(); +} + +void EditView::SetOutputArea( const tools::Rectangle& rRect ) +{ + pImpEditView->SetOutputArea( rRect ); + + // the rest here only if it is an API call: + pImpEditView->CalcAnchorPoint(); + if ( pImpEditView->pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) + pImpEditView->RecalcOutputArea(); + pImpEditView->ShowCursor( false, false ); +} + +const tools::Rectangle& EditView::GetOutputArea() const +{ + return pImpEditView->GetOutputArea(); +} + +PointerStyle EditView::GetPointer() const +{ + return pImpEditView->GetPointer(); +} + +vcl::Cursor* EditView::GetCursor() const +{ + return pImpEditView->pCursor.get(); +} + +void EditView::InsertText( const OUString& rStr, bool bSelect, bool bLOKShowSelect ) +{ + + EditEngine* pEE = pImpEditView->pEditEngine; + + if (bLOKShowSelect) + pImpEditView->DrawSelectionXOR(); + + EditPaM aPaM1; + if ( bSelect ) + { + EditSelection aTmpSel( pImpEditView->GetEditSelection() ); + aTmpSel.Adjust( pEE->GetEditDoc() ); + aPaM1 = aTmpSel.Min(); + } + + pEE->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM2( pEE->InsertText( pImpEditView->GetEditSelection(), rStr ) ); + pEE->UndoActionEnd(); + + if ( bSelect ) + { + DBG_ASSERT( !aPaM1.DbgIsBuggy( pEE->GetEditDoc() ), "Insert: PaM broken" ); + pImpEditView->SetEditSelection( EditSelection( aPaM1, aPaM2 ) ); + } + else + pImpEditView->SetEditSelection( EditSelection( aPaM2, aPaM2 ) ); + + if (bLOKShowSelect) + pEE->FormatAndLayout( this ); +} + +bool EditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ) +{ + return pImpEditView->PostKeyEvent( rKeyEvent, pFrameWin ); +} + +bool EditView::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseButtonUp( rMouseEvent ); +} + +void EditView::ReleaseMouse() +{ + return pImpEditView->ReleaseMouse(); +} + +bool EditView::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseButtonDown( rMouseEvent ); +} + +bool EditView::MouseMove( const MouseEvent& rMouseEvent ) +{ + return pImpEditView->MouseMove( rMouseEvent ); +} + +bool EditView::Command(const CommandEvent& rCEvt) +{ + return pImpEditView->Command(rCEvt); +} + +void EditView::SetBroadcastLOKViewCursor(bool bSet) +{ + pImpEditView->SetBroadcastLOKViewCursor(bSet); +} + +tools::Rectangle EditView::GetEditCursor() const +{ + return pImpEditView->GetEditCursor(); +} + +void EditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bActivate ) +{ + if ( !pImpEditView->pEditEngine->HasView( this ) ) + return; + + // The control word is more important: + if ( !pImpEditView->DoAutoScroll() ) + bGotoCursor = false; + pImpEditView->ShowCursor( bGotoCursor, bForceVisCursor ); + + if (pImpEditView->mpViewShell && !bActivate) + { + if (!pImpEditView->pOutWin) + return; + VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId() != 0) + return; + + static const OString aPayload = OString::boolean(true); + pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload); + pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible"_ostr, aPayload); + } +} + +void EditView::HideCursor(bool bDeactivate) +{ + pImpEditView->GetCursor()->Hide(); + + if (pImpEditView->mpViewShell && !bDeactivate) + { + if (!pImpEditView->pOutWin) + return; + VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId() != 0) + return; + + OString aPayload = OString::boolean(false); + pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload); + pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible"_ostr, aPayload); + } +} + +Pair EditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck ) +{ + return pImpEditView->Scroll( ndX, ndY, nRangeCheck ); +} + +const SfxItemSet& EditView::GetEmptyItemSet() const +{ + return pImpEditView->pEditEngine->GetEmptyItemSet(); +} + +void EditView::SetAttribs( const SfxItemSet& rSet ) +{ + DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." ); + + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->SetAttribs( pImpEditView->GetEditSelection(), rSet, SetAttribsMode::WholeWord ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs ) +{ + + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + EditSelection aSelection( pImpEditView->GetEditSelection() ); + + for (sal_uInt16 nWID = EE_ITEMS_START; nWID <= EE_ITEMS_END; ++nWID) + { + bool bIsLang = EE_CHAR_LANGUAGE == nWID || + EE_CHAR_LANGUAGE_CJK == nWID || + EE_CHAR_LANGUAGE_CTL == nWID; + if (!bIsLang) + pImpEditView->pEditEngine->RemoveCharAttribs( aSelection, bRemoveParaAttribs, nWID ); + } + + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveAttribs( bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + RemoveAttribs(bRemoveParaAttribs ? EERemoveParaAttribsMode::RemoveAll + : EERemoveParaAttribsMode::RemoveCharItems, nWhich); +} + +void EditView::RemoveAttribs( EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ) +{ + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + pImpEditView->pEditEngine->RemoveCharAttribs( pImpEditView->GetEditSelection(), eMode, nWhich ); + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich ) +{ + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS ); + pImpEditView->pEditEngine->RemoveCharAttribs( nPara, nWhich ); + pImpEditView->pEditEngine->UndoActionEnd(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +SfxItemSet EditView::GetAttribs() +{ + DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." ); + return pImpEditView->pEditEngine->pImpEditEngine->GetAttribs( pImpEditView->GetEditSelection() ); +} + +void EditView::Undo() +{ + pImpEditView->pEditEngine->Undo( this ); +} + +void EditView::Redo() +{ + pImpEditView->pEditEngine->Redo( this ); +} + +ErrCode EditView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + EditSelection aOldSel( pImpEditView->GetEditSelection() ); + pImpEditView->DrawSelectionXOR(); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_READ ); + EditPaM aEndPaM = pImpEditView->pEditEngine->pImpEditEngine->Read( rInput, "", eFormat, aOldSel, pHTTPHeaderAttrs ); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd(); + EditSelection aNewSel( aEndPaM, aEndPaM ); + + pImpEditView->SetEditSelection( aNewSel ); + bool bGotoCursor = pImpEditView->DoAutoScroll(); + ShowCursor( bGotoCursor ); + + return rInput.GetError(); +} + +void EditView::Cut() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->CutCopy( aClipBoard, true ); +} + +Reference<css::datatransfer::clipboard::XClipboard> EditView::GetClipboard() const +{ + return pImpEditView->GetClipboard(); +} + +css::uno::Reference< css::datatransfer::XTransferable > EditView::GetTransferable() const +{ + uno::Reference< datatransfer::XTransferable > xData = + GetEditEngine()->CreateTransferable( pImpEditView->GetEditSelection() ); + return xData; +} + +void EditView::Copy() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->CutCopy( aClipBoard, false ); +} + +void EditView::Paste() +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->Paste( aClipBoard ); +} + +void EditView::PasteSpecial(SotClipboardFormatId format) +{ + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + pImpEditView->Paste(aClipBoard, true, format ); +} + +Point EditView::GetWindowPosTopLeft( sal_Int32 nParagraph ) +{ + Point aDocPos( pImpEditView->pEditEngine->GetDocPosTopLeft( nParagraph ) ); + return pImpEditView->GetWindowPos( aDocPos ); +} + +void EditView::SetSelectionMode( EESelectionMode eMode ) +{ + pImpEditView->SetSelectionMode( eMode ); +} + +OUString EditView::GetSelected() const +{ + return pImpEditView->pEditEngine->pImpEditEngine->GetSelected( pImpEditView->GetEditSelection() ); +} + +void EditView::MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos ) +{ + pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_MOVEPARAS ); + pImpEditView->pEditEngine->pImpEditEngine->MoveParagraphs( aParagraphs, nNewPos, this ); + pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd(); +} + +void EditView::MoveParagraphs( tools::Long nDiff ) +{ + ESelection aSel = GetSelection(); + Range aRange( aSel.nStartPara, aSel.nEndPara ); + aRange.Normalize(); + tools::Long nDest = ( nDiff > 0 ? aRange.Max() : aRange.Min() ) + nDiff; + if ( nDiff > 0 ) + nDest++; + DBG_ASSERT( ( nDest >= 0 ) && ( nDest <= pImpEditView->pEditEngine->GetParagraphCount() ), "MoveParagraphs - wrong Parameters!" ); + MoveParagraphs( aRange, sal::static_int_cast< sal_Int32 >( nDest ) ); +} + +void EditView::SetBackgroundColor( const Color& rColor ) +{ + pImpEditView->SetBackgroundColor( rColor ); + pImpEditView->pEditEngine->SetBackgroundColor( rColor ); +} + +Color const & EditView::GetBackgroundColor() const +{ + return pImpEditView->GetBackgroundColor(); +} + +void EditView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + pImpEditView->RegisterViewShell(pViewShell); +} + +void EditView::RegisterOtherShell(OutlinerViewShell* pOtherShell) +{ + pImpEditView->RegisterOtherShell(pOtherShell); +} + +void EditView::SetControlWord( EVControlBits nWord ) +{ + pImpEditView->nControl = nWord; +} + +EVControlBits EditView::GetControlWord() const +{ + return pImpEditView->nControl; +} + +std::unique_ptr<EditTextObject> EditView::CreateTextObject() +{ + return pImpEditView->pEditEngine->pImpEditEngine->CreateTextObject( pImpEditView->GetEditSelection() ); +} + +void EditView::InsertText( const EditTextObject& rTextObject ) +{ + pImpEditView->DrawSelectionXOR(); + + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT ); + EditSelection aTextSel( pImpEditView->pEditEngine->InsertText( rTextObject, pImpEditView->GetEditSelection() ) ); + pImpEditView->pEditEngine->UndoActionEnd(); + + aTextSel.Min() = aTextSel.Max(); // Selection not retained. + pImpEditView->SetEditSelection( aTextSel ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +void EditView::InsertText( css::uno::Reference< css::datatransfer::XTransferable > const & xDataObj, const OUString& rBaseURL, bool bUseSpecial ) +{ + pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT ); + pImpEditView->DeleteSelected(); + EditSelection aTextSel = + pImpEditView->pEditEngine->InsertText(xDataObj, rBaseURL, pImpEditView->GetEditSelection().Max(), bUseSpecial); + pImpEditView->pEditEngine->UndoActionEnd(); + + aTextSel.Min() = aTextSel.Max(); // Selection not retained. + pImpEditView->SetEditSelection( aTextSel ); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout( this ); +} + +bool EditView::SetEditEngineUpdateLayout( bool bUpdate ) +{ + return pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( bUpdate, this ); +} + +void EditView::ForceLayoutCalculation() +{ + pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( true, this, true ); +} + +SfxStyleSheet* EditView::GetStyleSheet() +{ + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + sal_Int32 nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + + SfxStyleSheet* pStyle = nullptr; + for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ ) + { + SfxStyleSheet* pTmpStyle = pImpEditView->pEditEngine->GetStyleSheet( n ); + if ( ( n != nStartPara ) && ( pStyle != pTmpStyle ) ) + return nullptr; // Not unique. + pStyle = pTmpStyle; + } + return pStyle; +} + +const SfxStyleSheet* EditView::GetStyleSheet() const +{ + return const_cast< EditView* >( this )->GetStyleSheet(); +} + +bool EditView::IsInsertMode() const +{ + return pImpEditView->IsInsertMode(); +} + +void EditView::SetInsertMode( bool bInsert ) +{ + pImpEditView->SetInsertMode( bInsert ); +} + +void EditView::SetAnchorMode( EEAnchorMode eMode ) +{ + pImpEditView->SetAnchorMode( eMode ); +} + +EEAnchorMode EditView::GetAnchorMode() const +{ + return pImpEditView->GetAnchorMode(); +} + +void EditView::TransliterateText( TransliterationFlags nTransliterationMode ) +{ + EditSelection aOldSel( pImpEditView->GetEditSelection() ); + EditSelection aNewSel = pImpEditView->pEditEngine->TransliterateText( pImpEditView->GetEditSelection(), nTransliterationMode ); + if ( aNewSel != aOldSel ) + { + pImpEditView->DrawSelectionXOR(); + pImpEditView->SetEditSelection( aNewSel ); + pImpEditView->DrawSelectionXOR(); + } +} + +void EditView::CompleteAutoCorrect( vcl::Window const * pFrameWin ) +{ + if ( !HasSelection() && pImpEditView->pEditEngine->pImpEditEngine->GetStatus().DoAutoCorrect() ) + { + pImpEditView->DrawSelectionXOR(); + EditSelection aSel = pImpEditView->GetEditSelection(); + aSel = pImpEditView->pEditEngine->EndOfWord( aSel.Max() ); + aSel = pImpEditView->pEditEngine->pImpEditEngine->AutoCorrect( aSel, 0, !IsInsertMode(), pFrameWin ); + pImpEditView->SetEditSelection( aSel ); + if ( pImpEditView->pEditEngine->IsModified() ) + pImpEditView->pEditEngine->FormatAndLayout( this ); + } +} + +EESpellState EditView::StartSpeller(weld::Widget* pDialogParent, bool bMultipleDoc) +{ + if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditView->pEditEngine->pImpEditEngine->Spell(this, pDialogParent, bMultipleDoc); +} + +EESpellState EditView::StartThesaurus(weld::Widget* pDialogParent) +{ + if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() ) + return EESpellState::NoSpeller; + + return pImpEditView->pEditEngine->pImpEditEngine->StartThesaurus(this, pDialogParent); +} + +void EditView::StartTextConversion(weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + pImpEditView->pEditEngine->pImpEditEngine->Convert(this, pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc); +} + +sal_Int32 EditView::StartSearchAndReplace( const SvxSearchItem& rSearchItem ) +{ + return pImpEditView->pEditEngine->pImpEditEngine->StartSearchAndReplace( this, rSearchItem ); +} + +bool EditView::IsCursorAtWrongSpelledWord() +{ + bool bIsWrong = false; + if ( !HasSelection() ) + { + EditPaM aPaM = pImpEditView->GetEditSelection().Max(); + bIsWrong = pImpEditView->IsWrongSpelledWord( aPaM, false/*bMarkIfWrong*/ ); + } + return bIsWrong; +} + +bool EditView::IsWrongSpelledWordAtPos( const Point& rPosPixel, bool bMarkIfWrong ) +{ + Point aPos(pImpEditView->GetOutputDevice().PixelToLogic(rPosPixel)); + aPos = pImpEditView->GetDocPos( aPos ); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false); + return pImpEditView->IsWrongSpelledWord( aPaM , bMarkIfWrong ); +} + +static void LOKSendSpellPopupMenu(const weld::Menu& rMenu, LanguageType nGuessLangWord, + LanguageType nGuessLangPara, sal_uInt16 nSuggestions) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + // Generate the menu structure and send it to the client code. + SfxViewShell* pViewShell = SfxViewShell::Current(); + if (!pViewShell) + return; + + boost::property_tree::ptree aMenu; + + boost::property_tree::ptree aItemTree; + if (nSuggestions) + { + for(int i = 0; i < nSuggestions; ++i) + { + OUString sItemId = OUString::number(MN_ALTSTART + i); + OUString sText = rMenu.get_label(sItemId); + aItemTree.put("text", sText.toUtf8().getStr()); + aItemTree.put("type", "command"); + OUString sCommandString = ".uno:SpellCheckApplySuggestion?ApplyRule:string=Spelling_" + sText; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive(sItemId)); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + } + + aItemTree.put("type", "separator"); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + } + + // First we need to set item commands for the context menu. + OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) ); + OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) ); + + aItemTree.put("text", rMenu.get_label("ignore").toUtf8().getStr()); + aItemTree.put("type", "command"); + aItemTree.put("command", ".uno:SpellCheckIgnoreAll?Type:string=Spelling"); + aItemTree.put("enabled", rMenu.get_sensitive("ignore")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("type", "separator"); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("text", rMenu.get_label("wordlanguage").toUtf8().getStr()); + aItemTree.put("type", "command"); + OUString sCommandString = ".uno:LanguageStatus?Language:string=Current_" + aTmpWord; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive("wordlanguage")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + aItemTree.put("text", rMenu.get_label("paralanguage").toUtf8().getStr()); + aItemTree.put("type", "command"); + sCommandString = ".uno:LanguageStatus?Language:string=Paragraph_" + aTmpPara; + aItemTree.put("command", sCommandString.toUtf8().getStr()); + aItemTree.put("enabled", rMenu.get_sensitive("paralanguage")); + aMenu.push_back(std::make_pair("", aItemTree)); + aItemTree.clear(); + + boost::property_tree::ptree aRoot; + aRoot.add_child("menu", aMenu); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aRoot, true); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_MENU, OString(aStream.str())); +} + +bool EditView::ExecuteSpellPopup(const Point& rPosPixel, const Link<SpellCallbackInfo&,void> &rCallBack) +{ + OutputDevice& rDevice = pImpEditView->GetOutputDevice(); + Point aPos(rDevice.PixelToLogic(rPosPixel)); + aPos = pImpEditView->GetDocPos( aPos ); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false); + Reference< linguistic2::XSpellChecker1 > xSpeller( pImpEditView->pEditEngine->pImpEditEngine->GetSpeller() ); + ESelection aOldSel = GetSelection(); + if ( !(xSpeller.is() && pImpEditView->IsWrongSpelledWord( aPaM, true )) ) + return false; + + // PaMtoEditCursor returns Logical units + tools::Rectangle aTempRect = pImpEditView->pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly ); + // GetWindowPos works in Logical units + aTempRect = pImpEditView->GetWindowPos(aTempRect); + // Convert to pixels + aTempRect = rDevice.LogicToPixel(aTempRect); + + weld::Widget* pPopupParent = pImpEditView->GetPopupParent(aTempRect); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "editeng/ui/spellmenu.ui")); + std::unique_ptr<weld::Menu> xPopupMenu(xBuilder->weld_menu("editviewspellmenu")); + std::unique_ptr<weld::Menu> xInsertMenu(xBuilder->weld_menu("insertmenu")); // add word to user-dictionaries + std::unique_ptr<weld::Menu> xAutoMenu(xBuilder->weld_menu("automenu")); + + EditPaM aPaM2( aPaM ); + aPaM2.SetIndex( aPaM2.GetIndex()+1 ); + + // Are there any replace suggestions? + OUString aSelected( GetSelected() ); + + // restrict the maximal number of suggestions displayed + // in the context menu. + // Note: That could of course be done by clipping the + // resulting sequence but the current third party + // implementations result differs greatly if the number of + // suggestions to be returned gets changed. Statistically + // it gets much better if told to return e.g. only 7 strings + // than returning e.g. 16 suggestions and using only the + // first 7. Thus we hand down the value to use to that + // implementation here by providing an additional parameter. + Sequence< PropertyValue > aPropVals { comphelper::makePropertyValue(UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) }; + + // Are there any replace suggestions? + Reference< linguistic2::XSpellAlternatives > xSpellAlt = + xSpeller->spell( aSelected, static_cast<sal_uInt16>(pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang), aPropVals ); + + Reference< linguistic2::XLanguageGuessing > xLangGuesser( EditDLL::Get().GetGlobalData()->GetLanguageGuesser() ); + + // check if text might belong to a different language... + LanguageType nGuessLangWord = LANGUAGE_NONE; + LanguageType nGuessLangPara = LANGUAGE_NONE; + if (xSpellAlt.is() && xLangGuesser.is()) + { + OUString aParaText; + ContentNode *pNode = aPaM.GetNode(); + if (pNode) + { + aParaText = pNode->GetString(); + } + else + { + OSL_FAIL( "content node is NULL" ); + } + + nGuessLangWord = CheckLanguage( xSpellAlt->getWord(), xSpeller, xLangGuesser, false ); + nGuessLangPara = CheckLanguage( aParaText, xSpeller, xLangGuesser, true ); + } + if (nGuessLangWord != LANGUAGE_NONE || nGuessLangPara != LANGUAGE_NONE) + { + // make sure LANGUAGE_NONE gets not used as menu entry + if (nGuessLangWord == LANGUAGE_NONE) + nGuessLangWord = nGuessLangPara; + if (nGuessLangPara == LANGUAGE_NONE) + nGuessLangPara = nGuessLangWord; + + xPopupMenu->append_separator("separator1"); + OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) ); + OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) ); + OUString aWordStr( EditResId( RID_STR_WORD ) ); + aWordStr = aWordStr.replaceFirst( "%x", aTmpWord ); + OUString aParaStr( EditResId( RID_STR_PARAGRAPH ) ); + aParaStr = aParaStr.replaceFirst( "%x", aTmpPara ); + xPopupMenu->append("wordlanguage", aWordStr); + xPopupMenu->append("paralanguage", aParaStr); + } + + // Replace suggestions... + Sequence< OUString > aAlt; + if (xSpellAlt.is()) + aAlt = xSpellAlt->getAlternatives(); + const OUString *pAlt = aAlt.getConstArray(); + sal_uInt16 nWords = static_cast<sal_uInt16>(aAlt.getLength()); + if ( nWords ) + { + for ( sal_uInt16 nW = 0; nW < nWords; nW++ ) + { + OUString aAlternate( pAlt[nW] ); + OUString sId(OUString::number(MN_ALTSTART + nW)); + xPopupMenu->insert(nW, sId, aAlternate, nullptr, nullptr, nullptr, TRISTATE_INDET); + xAutoMenu->append(sId, aAlternate); + } + xPopupMenu->insert_separator(nWords, "separator2"); + } + else + { + xAutoMenu.reset(); + xPopupMenu->remove("autocorrect"); + } + + SvtLinguConfig aCfg; + + Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + Sequence< Reference< linguistic2::XDictionary > > aDics; + if (xDicList.is()) + { + const Reference< linguistic2::XDictionary > *pDic = nullptr; + // add the default positive dictionary to dic-list (if not already done). + // This is to ensure that there is at least one dictionary to which + // words could be added. + uno::Reference< linguistic2::XDictionary > xDic( LinguMgr::GetStandardDic() ); + if (xDic.is()) + xDic->setActive( true ); + + aDics = xDicList->getDictionaries(); + pDic = aDics.getConstArray(); + LanguageType nCheckedLanguage = pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang; + sal_uInt16 nDicCount = static_cast<sal_uInt16>(aDics.getLength()); + for (sal_uInt16 i = 0; i < nDicCount; i++) + { + uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i]; + if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp) + continue; + + uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY ); + LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType(); + if( xDicTmp->isActive() + && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE + && (nCheckedLanguage == nActLanguage || LANGUAGE_NONE == nActLanguage ) + && (!xStor.is() || !xStor->isReadonly()) ) + { + OUString sImage; + + uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY ); + if (xSvcInfo.is()) + { + OUString aDictionaryImageUrl( aCfg.GetSpellAndGrammarContextDictionaryImage( + xSvcInfo->getImplementationName()) ); + if (!aDictionaryImageUrl.isEmpty() ) + sImage = aDictionaryImageUrl; + } + + if (sImage.isEmpty()) + { + xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName()); + } + else + { + Image aImage(sImage); + ScopedVclPtr<VirtualDevice> xVirDev(pPopupParent->create_virtual_device()); + Size aSize(aImage.GetSizePixel()); + xVirDev->SetOutputSizePixel(aSize); + xVirDev->DrawImage(Point(0, 0), aImage); + xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName(), *xVirDev); + } + aDicNameSingle = xDicTmp->getName(); + } + } + } + + if (xInsertMenu->n_children() != 1) + xPopupMenu->remove("add"); + if (xInsertMenu->n_children() < 2) + { + xInsertMenu.reset(); + xPopupMenu->remove("insert"); + } + + //tdf#106123 store and restore the EditPaM around the menu Execute + //because the loss of focus in the current editeng causes writer + //annotations to save their contents, making the pContent of the + //current EditPams invalid + EPaM aP = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM); + EPaM aP2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM2); + + if (comphelper::LibreOfficeKit::isActive()) + { + xPopupMenu->remove("autocorrect"); + xPopupMenu->remove("autocorrectdlg"); + + LOKSendSpellPopupMenu(*xPopupMenu, nGuessLangWord, nGuessLangPara, nWords); + return true; + } + + OUString sId = xPopupMenu->popup_at_rect(pPopupParent, aTempRect); + + aPaM2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP2); + aPaM = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP); + + if (sId == "ignore") + { + OUString aWord = pImpEditView->SpellIgnoreWord(); + SpellCallbackInfo aInf( SpellCallbackCommand::IGNOREWORD, aWord ); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if (sId == "wordlanguage" || sId == "paralanguage") + { + LanguageType nLangToUse = (sId == "wordlanguage") ? nGuessLangWord : nGuessLangPara; + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLangToUse ); + + SfxItemSet aAttrs = GetEditEngine()->GetEmptyItemSet(); + if (nScriptType == SvtScriptType::LATIN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE ) ); + if (nScriptType == SvtScriptType::COMPLEX) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CTL ) ); + if (nScriptType == SvtScriptType::ASIAN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CJK ) ); + if (sId == "paralanguage") + { + ESelection aSel = GetSelection(); + aSel.nStartPos = 0; + aSel.nEndPos = EE_TEXTPOS_ALL; + SetSelection( aSel ); + } + SetAttribs( aAttrs ); + pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + + SpellCallbackInfo aInf((sId == "wordlanguage") ? SpellCallbackCommand::WORDLANGUAGE : SpellCallbackCommand::PARALANGUAGE); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if (sId == "check") + { + SpellCallbackInfo aInf( SpellCallbackCommand::STARTSPELLDLG, OUString() ); + rCallBack.Call(aInf); + } + else if (sId == "autocorrectdlg") + { + SpellCallbackInfo aInf( SpellCallbackCommand::AUTOCORRECT_OPTIONS, OUString() ); + rCallBack.Call(aInf); + } + else if ( sId.toInt32() >= MN_DICTSTART || sId == "add") + { + OUString aDicName; + if (sId.toInt32() >= MN_DICTSTART) + { + assert(xInsertMenu && "this case only occurs when xInsertMenu exists"); + // strip_mnemonic is necessary to retrieve the correct dictionary name + aDicName = pPopupParent->strip_mnemonic(xInsertMenu->get_label(sId)); + } + else + aDicName = aDicNameSingle; + + uno::Reference< linguistic2::XDictionary > xDic; + if (xDicList.is()) + xDic = xDicList->getDictionaryByName( aDicName ); + + if (xDic.is()) + xDic->add( aSelected, false, OUString() ); + // save modified user-dictionary if it is persistent + Reference< frame::XStorable > xSavDic( xDic, UNO_QUERY ); + if (xSavDic.is()) + xSavDic->store(); + + aPaM.GetNode()->GetWrongList()->ResetInvalidRange(0, aPaM.GetNode()->Len()); + pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + + SpellCallbackInfo aInf( SpellCallbackCommand::ADDTODICTIONARY, aSelected ); + rCallBack.Call(aInf); + SetSelection( aOldSel ); + } + else if ( sId.toInt32() >= MN_AUTOSTART ) + { + DBG_ASSERT(sId.toInt32() - MN_AUTOSTART < aAlt.getLength(), "index out of range"); + OUString aWord = pAlt[sId.toInt32() - MN_AUTOSTART]; + SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if ( pAutoCorrect ) + pAutoCorrect->PutText( aSelected, aWord, pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang ); + InsertText( aWord ); + } + else if ( sId.toInt32() >= MN_ALTSTART ) // Replace + { + DBG_ASSERT(sId.toInt32() - MN_ALTSTART < aAlt.getLength(), "index out of range"); + OUString aWord = pAlt[sId.toInt32() - MN_ALTSTART]; + InsertText( aWord ); + } + else + { + SetSelection( aOldSel ); + } + return true; +} + +OUString EditView::SpellIgnoreWord() +{ + return pImpEditView->SpellIgnoreWord(); +} + +void EditView::SelectCurrentWord( sal_Int16 nWordType ) +{ + EditSelection aCurSel( pImpEditView->GetEditSelection() ); + pImpEditView->DrawSelectionXOR(); + aCurSel = pImpEditView->pEditEngine->SelectWord(aCurSel.Max(), nWordType); + pImpEditView->SetEditSelection( aCurSel ); + pImpEditView->DrawSelectionXOR(); + ShowCursor( true, false ); +} + +void EditView::InsertParaBreak() +{ + pImpEditView->pEditEngine->UndoActionStart(EDITUNDO_INSERT); + pImpEditView->DeleteSelected(); + EditPaM aPaM(pImpEditView->pEditEngine->InsertParaBreak(pImpEditView->GetEditSelection())); + pImpEditView->pEditEngine->UndoActionEnd(); + pImpEditView->SetEditSelection(EditSelection(aPaM, aPaM)); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pImpEditView->pEditEngine->FormatAndLayout(this); +} + +void EditView::InsertField( const SvxFieldItem& rFld ) +{ + EditEngine* pEE = pImpEditView->pEditEngine; + pImpEditView->DrawSelectionXOR(); + pEE->UndoActionStart( EDITUNDO_INSERT ); + EditPaM aPaM( pEE->InsertField( pImpEditView->GetEditSelection(), rFld ) ); + pEE->UndoActionEnd(); + pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); + pEE->UpdateFields(); + if (pImpEditView->pEditEngine->IsUpdateLayout()) + pEE->FormatAndLayout( this ); +} + +const SvxFieldItem* EditView::GetFieldUnderMousePointer() const +{ + sal_Int32 nPara; + sal_Int32 nPos; + return GetFieldUnderMousePointer( nPara, nPos ); +} + +const SvxFieldItem* EditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const +{ + return pImpEditView->GetField( rPos, pPara, pPos ); +} + +const SvxFieldItem* EditView::GetFieldUnderMousePointer( sal_Int32& nPara, sal_Int32& nPos ) const +{ + Point aPos; + if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks()) + aPos = pEditViewCallbacks->EditViewPointerPosPixel(); + else + aPos = pImpEditView->GetWindow()->GetPointerPosPixel(); + OutputDevice& rDevice = pImpEditView->GetOutputDevice(); + aPos = rDevice.PixelToLogic(aPos); + return GetField( aPos, &nPara, &nPos ); +} + +const SvxFieldItem* EditView::GetFieldAtSelection(bool bAlsoCheckBeforeCursor) const +{ + bool* pIsBeforeCursor = bAlsoCheckBeforeCursor ? &bAlsoCheckBeforeCursor : nullptr; + return GetFieldAtSelection(pIsBeforeCursor); +} + +// If pIsBeforeCursor != nullptr, the position before the cursor will also be checked for a field +// and pIsBeforeCursor will return true if that fallback field is returned. +// If no field is returned, the value in pIsBeforeCursor is meaningless. +const SvxFieldItem* EditView::GetFieldAtSelection(bool* pIsBeforeCursor) const +{ + // a field is a dummy character - so it cannot span nodes or be a selection larger than 1 + EditSelection aSel( pImpEditView->GetEditSelection() ); + if (aSel.Min().GetNode() != aSel.Max().GetNode()) + return nullptr; + + // normalize: min < max + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + + const sal_Int32 nMinIndex = aSel.Min().GetIndex(); + const sal_Int32 nMaxIndex = aSel.Max().GetIndex(); + if (nMaxIndex > nMinIndex + 1) + return nullptr; + + // Only when cursor is in font of field, no selection, + // or only selecting field + bool bAlsoCheckBeforeCursor = false; + if (pIsBeforeCursor) + { + *pIsBeforeCursor = false; + bAlsoCheckBeforeCursor = nMaxIndex == nMinIndex; + } + const SvxFieldItem* pFoundBeforeCursor = nullptr; + const CharAttribList::AttribsType& rAttrs = aSel.Min().GetNode()->GetCharAttribs().GetAttribs(); + for (const auto& rAttr: rAttrs) + { + if (rAttr->Which() == EE_FEATURE_FIELD) + { + DBG_ASSERT(dynamic_cast<const SvxFieldItem*>(rAttr->GetItem()), "No FieldItem..."); + if (rAttr->GetStart() == nMinIndex) + return static_cast<const SvxFieldItem*>(rAttr->GetItem()); + + // perhaps the cursor is behind the field? + if (nMinIndex && rAttr->GetStart() == nMinIndex - 1) + pFoundBeforeCursor = static_cast<const SvxFieldItem*>(rAttr->GetItem()); + } + } + if (bAlsoCheckBeforeCursor) + { + *pIsBeforeCursor = /*(bool)*/pFoundBeforeCursor; + return pFoundBeforeCursor; + } + return nullptr; +} + +void EditView::SelectFieldAtCursor() +{ + bool bIsBeforeCursor = false; + const SvxFieldItem* pFieldItem = GetFieldAtSelection(&bIsBeforeCursor); + if (!pFieldItem) + return; + + // Make sure the whole field is selected + // A field is represented by a dummy character - so it cannot be a selection larger than 1 + ESelection aSel = GetSelection(); + if (aSel.nStartPos == aSel.nEndPos) // not yet selected + { + if (bIsBeforeCursor) + { + assert (aSel.nStartPos); + --aSel.nStartPos; + } + else + aSel.nEndPos++; + SetSelection(aSel); + } + else + assert(std::abs(aSel.nStartPos - aSel.nEndPos) == 1); +} + +const SvxFieldData* EditView::GetFieldUnderMouseOrInSelectionOrAtCursor(bool bAlsoCheckBeforeCursor) const +{ + const SvxFieldItem* pFieldItem = GetFieldUnderMousePointer(); + if (!pFieldItem) + pFieldItem = GetFieldAtSelection(bAlsoCheckBeforeCursor); + + return pFieldItem ? pFieldItem->GetField() : nullptr; +} + +sal_Int32 EditView::countFieldsOffsetSum(sal_Int32 nPara, sal_Int32 nPos, bool bCanOverflow) const +{ + if (!pImpEditView || !pImpEditView->pEditEngine) + return 0; + + int nOffset = 0; + + for (int nCurrentPara = 0; nCurrentPara <= nPara; nCurrentPara++) + { + int nFields = pImpEditView->pEditEngine->GetFieldCount( nCurrentPara ); + for (int nField = 0; nField < nFields; nField++) + { + EFieldInfo aFieldInfo + = pImpEditView->pEditEngine->GetFieldInfo( nCurrentPara, nField ); + + bool bLastPara = nCurrentPara == nPara; + sal_Int32 nFieldPos = aFieldInfo.aPosition.nIndex; + + if (bLastPara && nFieldPos >= nPos) + break; + + sal_Int32 nFieldLen = aFieldInfo.aCurrentText.getLength(); + + // position in the middle of a field + if (!bCanOverflow && bLastPara && nFieldPos + nFieldLen > nPos) + nFieldLen = nPos - nFieldPos; + + nOffset += nFieldLen - 1; + } + } + + return nOffset; +} + +sal_Int32 EditView::GetPosNoField(sal_Int32 nPara, sal_Int32 nPos) const +{ + sal_Int32 nOffset = countFieldsOffsetSum(nPara, nPos, false); + assert(nPos >= nOffset); + return nPos - nOffset; +} + +sal_Int32 EditView::GetPosWithField(sal_Int32 nPara, sal_Int32 nPos) const +{ + sal_Int32 nOffset = countFieldsOffsetSum(nPara, nPos, true); + return nPos + nOffset; +} + +void EditView::SetInvalidateMore( sal_uInt16 nPixel ) +{ + pImpEditView->SetInvalidateMore( nPixel ); +} + +sal_uInt16 EditView::GetInvalidateMore() const +{ + return pImpEditView->GetInvalidateMore(); +} + +static void ChangeFontSizeImpl( EditView* pEditView, bool bGrow, const ESelection& rSel, const FontList* pFontList ) +{ + pEditView->SetSelection( rSel ); + + SfxItemSet aSet( pEditView->GetAttribs() ); + if( EditView::ChangeFontSize( bGrow, aSet, pFontList ) ) + { + SfxItemSet aNewSet( pEditView->GetEmptyItemSet() ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT ) ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CJK ) ); + aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CTL ) ); + pEditView->SetAttribs( aNewSet ); + } +} + +void EditView::ChangeFontSize( bool bGrow, const FontList* pFontList ) +{ + + EditEngine& rEditEngine = *pImpEditView->pEditEngine; + + ESelection aSel( GetSelection() ); + ESelection aOldSelection( aSel ); + aSel.Adjust(); + + if( !aSel.HasRange() ) + { + aSel = rEditEngine.GetWord( aSel, css::i18n::WordType::DICTIONARY_WORD ); + } + + if( aSel.HasRange() ) + { + for( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + std::vector<sal_Int32> aPortions; + rEditEngine.GetPortions( nPara, aPortions ); + + if( aPortions.empty() ) + aPortions.push_back( rEditEngine.GetTextLen(nPara) ); + + const sal_Int32 nBeginPos = (nPara == aSel.nStartPara) ? aSel.nStartPos : 0; + const sal_Int32 nEndPos = (nPara == aSel.nEndPara) ? aSel.nEndPos : EE_TEXTPOS_ALL; + + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + sal_Int32 nPortionEnd = aPortions[ nPos ]; + sal_Int32 nPortionStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + if( (nPortionEnd < nBeginPos) || (nPortionStart > nEndPos) ) + continue; + + if( nPortionStart < nBeginPos ) + nPortionStart = nBeginPos; + if( nPortionEnd > nEndPos ) + nPortionEnd = nEndPos; + + if( nPortionStart == nPortionEnd ) + continue; + + ESelection aPortionSel( nPara, nPortionStart, nPara, nPortionEnd ); + ChangeFontSizeImpl( this, bGrow, aPortionSel, pFontList ); + } + } + } + else + { + ChangeFontSizeImpl( this, bGrow, aSel, pFontList ); + } + + SetSelection( aOldSelection ); +} + +bool EditView::ChangeFontSize( bool bGrow, SfxItemSet& rSet, const FontList* pFontList ) +{ + if (!pFontList) + return false; + + static const sal_uInt16 gFontSizeWichMap[] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL, 0 }; + bool bRet = false; + + const sal_uInt16* pWhich = gFontSizeWichMap; + while( *pWhich ) + { + SvxFontHeightItem aFontHeightItem( static_cast<const SvxFontHeightItem&>(rSet.Get( *pWhich )) ); + tools::Long nHeight = aFontHeightItem.GetHeight(); + const MapUnit eUnit = rSet.GetPool()->GetMetric( *pWhich ); + nHeight = OutputDevice::LogicToLogic(nHeight * 10, eUnit, MapUnit::MapPoint); + + const int* pAry = FontList::GetStdSizeAry(); + + if( bGrow ) + { + while( *pAry ) + { + if( *pAry > nHeight ) + { + nHeight = *pAry; + break; + } + pAry++; + } + + if( *pAry == 0 ) + { + nHeight += (nHeight + 5) / 10; + if( nHeight > 9999 ) + nHeight = 9999; + } + + } + else if( *pAry ) + { + bool bFound = false; + if( *pAry < nHeight ) + { + pAry++; + while( *pAry ) + { + if( *pAry >= nHeight ) + { + nHeight = pAry[-1]; + bFound = true; + break; + } + pAry++; + } + } + + if( !bFound ) + { + nHeight -= (nHeight + 5) / 10; + if( nHeight < 2 ) + nHeight = 2; + } + } + + if( (nHeight >= 2) && (nHeight <= 9999 ) ) + { + nHeight = OutputDevice::LogicToLogic( nHeight, MapUnit::MapPoint, eUnit ) / 10; + + if( nHeight != static_cast<tools::Long>(aFontHeightItem.GetHeight()) ) + { + aFontHeightItem.SetHeight( nHeight ); + rSet.Put( aFontHeightItem.CloneSetWhich(*pWhich) ); + bRet = true; + } + } + pWhich++; + } + return bRet; +} + +OUString EditView::GetSurroundingText() const +{ + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + + if( HasSelection() ) + { + OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel); + + // Stop reconversion if the selected text includes a line break. + if ( aStr.indexOf( 0x0A ) == -1 ) + return aStr; + else + return OUString(); + } + else + { + aSel.Min().SetIndex( 0 ); + aSel.Max().SetIndex( aSel.Max().GetNode()->Len() ); + return pImpEditView->pEditEngine->GetSelected(aSel); + } +} + +Selection EditView::GetSurroundingTextSelection() const +{ + ESelection aSelection( GetSelection() ); + aSelection.Adjust(); + + if( HasSelection() ) + { + EditSelection aSel( pImpEditView->GetEditSelection() ); + aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() ); + OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel); + + // Stop reconversion if the selected text includes a line break. + if ( aStr.indexOf( 0x0A ) == -1 ) + return Selection( 0, aSelection.nEndPos - aSelection.nStartPos ); + else + return Selection( 0, 0 ); + } + else + { + return Selection( aSelection.nStartPos, aSelection.nEndPos ); + } +} + +bool EditView::DeleteSurroundingText(const Selection& rRange) +{ + ESelection aSel(GetSelection()); + aSel.nEndPara = aSel.nStartPara; + aSel.nStartPos = rRange.Min(); + aSel.nEndPos = rRange.Max(); + SetSelection(aSel); + DeleteSelected(); + return true; +} + +void EditView::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark) +{ + Point aDocPos(pImpEditView->GetDocPos(rPosition)); + EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aDocPos); + EditSelection aSelection(pImpEditView->GetEditSelection()); + + // Explicitly create or delete the selection. + if (bClearMark) + { + pImpEditView->DeselectAll(); + aSelection = pImpEditView->GetEditSelection(); + } + else + pImpEditView->CreateAnchor(); + + if (bPoint) + aSelection.Max() = aPaM; + else + aSelection.Min() = aPaM; + + if (pImpEditView->GetEditSelection().Min() != aSelection.Min()) + { + const ContentNode* pNode(pImpEditView->GetEditSelection().Min().GetNode()); + if (nullptr != pNode) + pNode->checkAndDeleteEmptyAttribs(); + } + + pImpEditView->DrawSelectionXOR(aSelection); + if (pImpEditView->GetEditSelection() != aSelection) + pImpEditView->SetEditSelection(aSelection); + ShowCursor(/*bGotoCursor=*/false); +} + +void EditView::DrawSelectionXOR(OutlinerViewShell* pOtherShell) +{ + pImpEditView->RegisterOtherShell(pOtherShell); + pImpEditView->DrawSelectionXOR(); + pImpEditView->RegisterOtherShell(nullptr); +} + +void EditView::InitLOKSpecialPositioning(MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) +{ + pImpEditView->InitLOKSpecialPositioning(eUnit, rOutputArea, rVisDocStartPos); +} + +void EditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea) +{ + pImpEditView->SetLOKSpecialOutputArea(rOutputArea); +} + +const tools::Rectangle & EditView::GetLOKSpecialOutputArea() const +{ + return pImpEditView->GetLOKSpecialOutputArea(); +} + +void EditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea) +{ + pImpEditView->SetLOKSpecialVisArea(rVisArea); +} + +tools::Rectangle EditView::GetLOKSpecialVisArea() const +{ + return pImpEditView->GetLOKSpecialVisArea(); +} + +bool EditView::HasLOKSpecialPositioning() const +{ + return pImpEditView->HasLOKSpecialPositioning(); +} + +void EditView::SetLOKSpecialFlags(LOKSpecialFlags eFlags) +{ + pImpEditView->SetLOKSpecialFlags(eFlags); +} + +void EditView::SuppressLOKMessages(bool bSet) +{ + pImpEditView->SuppressLOKMessages(bSet); +} + +bool EditView::IsSuppressLOKMessages() const +{ + return pImpEditView->IsSuppressLOKMessages(); +} + +void EditView::SetNegativeX(bool bSet) +{ + pImpEditView->SetNegativeX(bSet); +} + +bool EditView::IsNegativeX() const +{ + return pImpEditView->IsNegativeX(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/edtspell.cxx b/editeng/source/editeng/edtspell.cxx new file mode 100644 index 0000000000..c07361bd19 --- /dev/null +++ b/editeng/source/editeng/edtspell.cxx @@ -0,0 +1,708 @@ +/* -*- 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 "impedit.hxx" +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <edtspell.hxx> +#include <editeng/flditem.hxx> +#include <svl/intitem.hxx> +#include <svl/eitem.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XDictionary.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::linguistic2; + + +EditSpellWrapper::EditSpellWrapper(weld::Widget* pWindow, + bool bIsStart, EditView* pView ) + : SvxSpellWrapper(pWindow, bIsStart, false/*bIsAllRight*/) +{ + SAL_WARN_IF( !pView, "editeng", "One view has to be abandoned!" ); + // Keep IgnoreList, delete ReplaceList... + if (LinguMgr::GetChangeAllList().is()) + LinguMgr::GetChangeAllList()->clear(); + pEditView = pView; +} + +void EditSpellWrapper::SpellStart( SvxSpellArea eArea ) +{ + EditEngine* pEE = pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + + if ( eArea == SvxSpellArea::BodyStart ) + { + // Is called when + // a) Spell-Forward has arrived at the end and should restart at the top + // IsEndDone() returns also true, when backward-spelling is started at the end! + if ( IsEndDone() ) + { + pSpellInfo->bSpellToEnd = false; + pSpellInfo->aSpellTo = pSpellInfo->aSpellStart; + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + else + { + pSpellInfo->bSpellToEnd = true; + pSpellInfo->aSpellTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetStartPaM() ); + } + } + else if ( eArea == SvxSpellArea::BodyEnd ) + { + // Is called when + // a) Spell-Forward is launched + // IsStartDone() return also true, when forward-spelling is started at the beginning! + if ( !IsStartDone() ) + { + pSpellInfo->bSpellToEnd = true; + pSpellInfo->aSpellTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + else + { + pSpellInfo->bSpellToEnd = false; + pSpellInfo->aSpellTo = pSpellInfo->aSpellStart; + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetEndPaM() ); + } + } + else if ( eArea == SvxSpellArea::Body ) + { + ; // Is handled by the App through SpellNextDocument + } + else + { + OSL_FAIL( "SpellStart: Unknown Area!" ); + } +} + +void EditSpellWrapper::SpellContinue() +{ + SetLast( pEditView->GetImpEditEngine()->ImpSpell( pEditView ) ); +} + +bool EditSpellWrapper::SpellMore() +{ + EditEngine* pEE = pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + bool bMore = false; + if ( pSpellInfo->bMultipleDoc ) + { + bMore = pEE->SpellNextDocument(); + if ( bMore ) + { + // The text has been entered into the engine, when backwards then + // it must be behind the selection. + pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + } + return bMore; +} + +void EditSpellWrapper::ReplaceAll( const OUString &rNewText ) +{ + // Is called when the word is in ReplaceList of the spell checker + pEditView->InsertText( rNewText ); + CheckSpellTo(); +} + +void EditSpellWrapper::CheckSpellTo() +{ + ImpEditEngine* pImpEE = pEditView->GetImpEditEngine(); + SpellInfo* pSpellInfo = pImpEE->GetSpellInfo(); + EditPaM aPaM( pEditView->GetImpEditView()->GetEditSelection().Max() ); + EPaM aEPaM = pImpEE->CreateEPaM( aPaM ); + if ( aEPaM.nPara == pSpellInfo->aSpellTo.nPara ) + { + // Check if SpellToEnd still has a valid Index, if replace has been + // performed in the paragraph. + if ( pSpellInfo->aSpellTo.nIndex > aPaM.GetNode()->Len() ) + pSpellInfo->aSpellTo.nIndex = aPaM.GetNode()->Len(); + } +} + +WrongList::WrongList() : mnInvalidStart(0), mnInvalidEnd(Valid) {} + +void WrongList::SetRanges( std::vector<editeng::MisspellRange>&&rRanges ) +{ + maRanges = std::move(rRanges); +} + +bool WrongList::IsValid() const +{ + return mnInvalidStart == Valid; +} + +void WrongList::SetValid() +{ + mnInvalidStart = Valid; + mnInvalidEnd = 0; +} + +void WrongList::SetInvalidRange( size_t nStart, size_t nEnd ) +{ + if (mnInvalidStart == Valid || nStart < mnInvalidStart) + mnInvalidStart = nStart; + + if (mnInvalidEnd < nEnd) + mnInvalidEnd = nEnd; +} + +void WrongList::ResetInvalidRange( size_t nStart, size_t nEnd ) +{ + mnInvalidStart = nStart; + mnInvalidEnd = nEnd; +} + +void WrongList::TextInserted( size_t nPos, size_t nLength, bool bPosIsSep ) +{ + if (IsValid()) + { + mnInvalidStart = nPos; + mnInvalidEnd = nPos + nLength; + } + else + { + if ( mnInvalidStart > nPos ) + mnInvalidStart = nPos; + if ( mnInvalidEnd >= nPos ) + mnInvalidEnd = mnInvalidEnd + nLength; + else + mnInvalidEnd = nPos + nLength; + } + + for (size_t i = 0, n = maRanges.size(); i < n; ++i) + { + editeng::MisspellRange& rWrong = maRanges[i]; + bool bRefIsValid = true; + if (rWrong.mnEnd >= nPos) + { + // Move all Wrongs after the insert position... + if (rWrong.mnStart > nPos) + { + rWrong.mnStart += nLength; + rWrong.mnEnd += nLength; + } + // 1: Starts before and goes until nPos... + else if (rWrong.mnEnd == nPos) + { + // Should be halted at a blank! + if ( !bPosIsSep ) + rWrong.mnEnd += nLength; + } + // 2: Starts before and goes until after nPos... + else if ((rWrong.mnStart < nPos) && (rWrong.mnEnd > nPos)) + { + rWrong.mnEnd += nLength; + // When a separator remove and re-examine the Wrong + if ( bPosIsSep ) + { + // Split Wrong... + editeng::MisspellRange aNewWrong(rWrong.mnStart, nPos); + rWrong.mnStart = nPos + 1; + maRanges.insert(maRanges.begin() + i, aNewWrong); + // Reference no longer valid after Insert, the other + // was inserted in front of this position + bRefIsValid = false; + ++i; // Not this again... + } + } + // 3: Attribute starts at position .. + else if (rWrong.mnStart == nPos) + { + rWrong.mnEnd += nLength; + if ( bPosIsSep ) + ++(rWrong.mnStart); + } + } + SAL_WARN_IF(bRefIsValid && rWrong.mnStart >= rWrong.mnEnd, "editeng", + "TextInserted, editeng::MisspellRange: Start >= End?!"); + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!"); +} + +void WrongList::TextDeleted( size_t nPos, size_t nLength ) +{ + size_t nEndPos = nPos + nLength; + if (IsValid()) + { + const size_t nNewInvalidStart = nPos ? nPos - 1 : 0; + mnInvalidStart = nNewInvalidStart; + mnInvalidEnd = nNewInvalidStart + 1; + } + else + { + if ( mnInvalidStart > nPos ) + mnInvalidStart = nPos; + if ( mnInvalidEnd > nPos ) + { + if (mnInvalidEnd > nEndPos) + mnInvalidEnd = mnInvalidEnd - nLength; + else + mnInvalidEnd = nPos+1; + } + } + + for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); ) + { + bool bDelWrong = false; + if (i->mnEnd >= nPos) + { + // Move all Wrongs after the insert position... + if (i->mnStart >= nEndPos) + { + i->mnStart -= nLength; + i->mnEnd -= nLength; + } + // 1. Delete Internal Wrongs ... + else if (i->mnStart >= nPos && i->mnEnd <= nEndPos) + { + bDelWrong = true; + } + // 2. Wrong begins before, ends inside or behind it ... + else if (i->mnStart <= nPos && i->mnEnd > nPos) + { + if (i->mnEnd <= nEndPos) // ends inside + i->mnEnd = nPos; + else + i->mnEnd -= nLength; // ends after + } + // 3. Wrong begins inside, ending after ... + else if (i->mnStart >= nPos && i->mnEnd > nEndPos) + { + i->mnStart = nEndPos - nLength; + i->mnEnd -= nLength; + } + } + SAL_WARN_IF(i->mnStart >= i->mnEnd, "editeng", + "TextDeleted, editeng::MisspellRange: Start >= End?!"); + if ( bDelWrong ) + { + i = maRanges.erase(i); + } + else + { + ++i; + } + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "TextDeleted: WrongList broken!"); +} + +bool WrongList::NextWrong( size_t& rnStart, size_t& rnEnd ) const +{ + /* + rnStart get the start position, is possibly adjusted wrt. Wrong start + rnEnd does not have to be initialized. + */ + for (auto const& range : maRanges) + { + if (range.mnEnd > rnStart) + { + rnStart = range.mnStart; + rnEnd = range.mnEnd; + return true; + } + } + return false; +} + +bool WrongList::HasWrong( size_t nStart, size_t nEnd ) const +{ + for (auto const& range : maRanges) + { + if (range.mnStart == nStart && range.mnEnd == nEnd) + return true; + else if (range.mnStart >= nStart) + break; + } + return false; +} + +bool WrongList::HasAnyWrong( size_t nStart, size_t nEnd ) const +{ + for (auto const& range : maRanges) + { + if (range.mnEnd >= nStart && range.mnStart < nEnd) + return true; + else if (range.mnStart >= nEnd) + break; + } + return false; +} + +void WrongList::ClearWrongs( size_t nStart, size_t nEnd, + const ContentNode* pNode ) +{ + for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); ) + { + if (i->mnEnd > nStart && i->mnStart < nEnd) + { + if (i->mnEnd > nEnd) // Runs out + { + i->mnStart = nEnd; + // Blanks? + while (i->mnStart < o3tl::make_unsigned(pNode->Len()) && + (pNode->GetChar(i->mnStart) == ' ' || + pNode->IsFeature(i->mnStart))) + { + ++i->mnStart; + } + ++i; + } + else + { + i = maRanges.erase(i); + // no increment here + } + } + else + { + ++i; + } + } + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "ClearWrongs: WrongList broken!"); +} + +void WrongList::InsertWrong( size_t nStart, size_t nEnd ) +{ + WrongList::iterator nPos = std::find_if(maRanges.begin(), maRanges.end(), + [&nStart](const editeng::MisspellRange& rRange) { return rRange.mnStart >= nStart; }); + + if (nPos != maRanges.end()) + { + { + // It can really only happen that the Wrong starts exactly here + // and runs along, but not that there are several ranges ... + // Exactly in the range is no one allowed to be, otherwise this + // Method can not be called! + SAL_WARN_IF((nPos->mnStart != nStart || nPos->mnEnd <= nEnd) && nPos->mnStart <= nEnd, "editeng", "InsertWrong: RangeMismatch!"); + if (nPos->mnStart == nStart && nPos->mnEnd > nEnd) + nPos->mnStart = nEnd + 1; + } + maRanges.insert(nPos, editeng::MisspellRange(nStart, nEnd)); + } + else + maRanges.emplace_back(nStart, nEnd); + + SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!"); +} + +void WrongList::MarkWrongsInvalid() +{ + if (!maRanges.empty()) + SetInvalidRange(maRanges.front().mnStart, maRanges.back().mnEnd); +} + +WrongList* WrongList::Clone() const +{ + return new WrongList(*this); +} + +// #i102062# +bool WrongList::operator==(const WrongList& rCompare) const +{ + // check direct members + if(GetInvalidStart() != rCompare.GetInvalidStart() + || GetInvalidEnd() != rCompare.GetInvalidEnd()) + return false; + + return std::equal(maRanges.begin(), maRanges.end(), rCompare.maRanges.begin(), rCompare.maRanges.end(), + [](const editeng::MisspellRange& a, const editeng::MisspellRange& b) { + return a.mnStart == b.mnStart && a.mnEnd == b.mnEnd; }); +} + +bool WrongList::empty() const +{ + return maRanges.empty(); +} + +void WrongList::push_back(const editeng::MisspellRange& rRange) +{ + maRanges.push_back(rRange); +} + +editeng::MisspellRange& WrongList::back() +{ + return maRanges.back(); +} + +const editeng::MisspellRange& WrongList::back() const +{ + return maRanges.back(); +} + +WrongList::iterator WrongList::begin() +{ + return maRanges.begin(); +} + +WrongList::iterator WrongList::end() +{ + return maRanges.end(); +} + +WrongList::const_iterator WrongList::begin() const +{ + return maRanges.begin(); +} + +WrongList::const_iterator WrongList::end() const +{ + return maRanges.end(); +} + +bool WrongList::DbgIsBuggy() const +{ + // Check if the ranges overlap. + bool bError = false; + for (WrongList::const_iterator i = maRanges.begin(); !bError && (i != maRanges.end()); ++i) + { + bError = std::any_of(i + 1, maRanges.end(), [&i](const editeng::MisspellRange& rRange) { + return i->mnStart <= rRange.mnEnd && rRange.mnStart <= i->mnEnd; }); + } + return bError; +} + + +EdtAutoCorrDoc::EdtAutoCorrDoc( + EditEngine* pE, ContentNode* pN, sal_Int32 nCrsr, sal_Unicode cIns) : + mpEditEngine(pE), + pCurNode(pN), + nCursor(nCrsr), + bAllowUndoAction(cIns != 0), + bUndoAction(false) {} + +EdtAutoCorrDoc::~EdtAutoCorrDoc() +{ + if ( bUndoAction ) + mpEditEngine->UndoActionEnd(); +} + +bool EdtAutoCorrDoc::Delete(sal_Int32 nStt, sal_Int32 nEnd) +{ + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEnd, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEnd-nStt ); + bAllowUndoAction = false; + return true; +} + +bool EdtAutoCorrDoc::Insert(sal_Int32 nPos, const OUString& rTxt) +{ + EditSelection aSel = EditPaM( pCurNode, nPos ); + mpEditEngine->InsertText(aSel, rTxt); + SAL_WARN_IF(nCursor < nPos, "editeng", + "Cursor in the heart of the action?!"); + nCursor = nCursor + rTxt.getLength(); + + if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) ) + ImplStartUndoAction(); + bAllowUndoAction = false; + + return true; +} + +bool EdtAutoCorrDoc::Replace(sal_Int32 nPos, const OUString& rTxt) +{ + return ReplaceRange( nPos, rTxt.getLength(), rTxt ); +} + +bool EdtAutoCorrDoc::ReplaceRange(sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rTxt) +{ + // Actually a Replace introduce => corresponds to UNDO + sal_Int32 nEnd = nPos+nSourceLength; + if ( nEnd > pCurNode->Len() ) + nEnd = pCurNode->Len(); + + // #i5925# First insert new text behind to be deleted text, for keeping attributes. + mpEditEngine->InsertText(EditSelection(EditPaM(pCurNode, nEnd)), rTxt); + mpEditEngine->DeleteSelection( + EditSelection(EditPaM(pCurNode, nPos), EditPaM(pCurNode, nEnd))); + + if ( nPos == nCursor ) + nCursor = nCursor + rTxt.getLength(); + + if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) ) + ImplStartUndoAction(); + + bAllowUndoAction = false; + + return true; +} + +void EdtAutoCorrDoc::SetAttr(sal_Int32 nStt, sal_Int32 nEnd, + sal_uInt16 nSlotId, SfxPoolItem& rItem) +{ + SfxItemPool* pPool = &mpEditEngine->GetEditDoc().GetItemPool(); + while ( pPool->GetSecondaryPool() && + pPool->GetName() != "EditEngineItemPool" ) + { + pPool = pPool->GetSecondaryPool(); + + } + sal_uInt16 nWhich = pPool->GetWhich( nSlotId ); + if ( nWhich ) + { + rItem.SetWhich( nWhich ); + + SfxItemSet aSet = mpEditEngine->GetEmptyItemSet(); + aSet.Put( rItem ); + + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + aSel.Max().SetIndex( nEnd ); // ??? + mpEditEngine->SetAttribs( aSel, aSet, SetAttribsMode::Edge ); + bAllowUndoAction = false; + } +} + +bool EdtAutoCorrDoc::SetINetAttr(sal_Int32 nStt, sal_Int32 nEnd, + const OUString& rURL) +{ + // Turn the Text into a command field ... + EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) ); + OUString aText = mpEditEngine->GetSelected(aSel); + aSel = mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEnd, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEnd-nStt ); + SvxFieldItem aField( SvxURLField( rURL, aText, SvxURLFormat::Repr ), + EE_FEATURE_FIELD ); + mpEditEngine->InsertField(aSel, aField); + nCursor++; + mpEditEngine->UpdateFieldsOnly(); + bAllowUndoAction = false; + return true; +} + +OUString const* EdtAutoCorrDoc::GetPrevPara(bool const) +{ + // Return previous paragraph, so that it can be determined, + // whether the current word is at the beginning of a sentence. + + bAllowUndoAction = false; // Not anymore ... + + EditDoc& rNodes = mpEditEngine->GetEditDoc(); + sal_Int32 nPos = rNodes.GetPos( pCurNode ); + + // Special case: Bullet => Paragraph start => simply return NULL... + const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib( nPos, EE_PARA_BULLETSTATE ); + bool bBullet = rBulletState.GetValue(); + if ( !bBullet && (mpEditEngine->GetControlWord() & EEControlBits::OUTLINER) ) + { + // The Outliner has still a Bullet at Level 0. + const SfxInt16Item& rLevel = mpEditEngine->GetParaAttrib( nPos, EE_PARA_OUTLLEVEL ); + if ( rLevel.GetValue() == 0 ) + bBullet = true; + } + if ( bBullet ) + return nullptr; + + for ( sal_Int32 n = nPos; n; ) + { + n--; + ContentNode* pNode = rNodes[n]; + if ( pNode->Len() ) + return & pNode->GetString(); + } + return nullptr; + +} + +bool EdtAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos, + sal_Int32 nEndPos, SvxAutoCorrect& rACorrect, + OUString* pPara ) +{ + // Paragraph-start or a blank found, search for the word + // shortcut in Auto + bAllowUndoAction = false; // Not anymore ... + + OUString aShort( pCurNode->Copy( rSttPos, nEndPos - rSttPos ) ); + bool bRet = false; + + if( aShort.isEmpty() ) + return bRet; + + LanguageTag aLanguageTag( mpEditEngine->GetLanguage( EditPaM( pCurNode, rSttPos+1 ) ).nLang ); + const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList( + pCurNode->GetString(), rSttPos, nEndPos, *this, aLanguageTag); + if( pFnd && pFnd->IsTextOnly() ) + { + + // replace also last colon of keywords surrounded by colons (for example, ":name:") + bool replaceLastChar = pFnd->GetShort()[0] == ':' && pFnd->GetShort().endsWith(":"); + + // then replace + EditSelection aSel( EditPaM( pCurNode, rSttPos ), + EditPaM( pCurNode, nEndPos + (replaceLastChar ? 1 : 0) )); + aSel = mpEditEngine->DeleteSelection(aSel); + SAL_WARN_IF(nCursor < nEndPos, "editeng", + "Cursor in the heart of the action?!"); + nCursor -= ( nEndPos-rSttPos ); + mpEditEngine->InsertText(aSel, pFnd->GetLong()); + nCursor = nCursor + pFnd->GetLong().getLength(); + if( pPara ) + *pPara = pCurNode->GetString(); + bRet = true; + } + + return bRet; +} + +bool EdtAutoCorrDoc::TransliterateRTLWord( sal_Int32& /*rSttPos*/, + sal_Int32 /*nEndPos*/, bool /*bApply*/ ) +{ + // Paragraph-start or a blank found, search for the word + // shortcut in Auto + bool bRet = false; + + return bRet; +} + + +LanguageType EdtAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const +{ + return mpEditEngine->GetLanguage( EditPaM( pCurNode, nPos+1 ) ).nLang; +} + +void EdtAutoCorrDoc::ImplStartUndoAction() +{ + sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos( pCurNode ); + ESelection aSel( nPara, nCursor, nPara, nCursor ); + mpEditEngine->UndoActionStart( EDITUNDO_INSERT, aSel ); + bUndoAction = true; + bAllowUndoAction = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eehtml.cxx b/editeng/source/editeng/eehtml.cxx new file mode 100644 index 0000000000..b3ed283955 --- /dev/null +++ b/editeng/source/editeng/eehtml.cxx @@ -0,0 +1,813 @@ +/* -*- 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 "eehtml.hxx" +#include <editeng/adjustitem.hxx> +#include <editeng/flditem.hxx> +#include <tools/urlobj.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <svtools/htmltokn.h> +#include <svtools/htmlkywd.hxx> +#include <tools/tenccvt.hxx> + +#include <editeng/editeng.hxx> +#include <utility> + +#define STYLE_PRE 101 + +EditHTMLParser::EditHTMLParser( SvStream& rIn, OUString _aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs ) + : HTMLParser( rIn, true ), + aBaseURL(std::move( _aBaseURL )), + mpEditEngine(nullptr), + bInPara(false), + bWasInPara(false), + bFieldsInserted(false), + bInTitle(false), + nInTable(0), + nInCell(0), + nDefListLevel(0) +{ + DBG_ASSERT( !IsSwitchToUCS2(), "EditHTMLParser::::EditHTMLParser: Switch to UCS2?" ); + + // Although the real default encoding is ISO8859-1, we use MS-1252 + // as default encoding. + SetSrcEncoding( GetExtendedCompatibilityTextEncoding( RTL_TEXTENCODING_ISO_8859_1 ) ); + + // If the file starts with a BOM, switch to UCS2. + SetSwitchToUCS2( true ); + + if ( pHTTPHeaderAttrs ) + SetEncodingByHTTPHeader( pHTTPHeaderAttrs ); +} + +EditHTMLParser::~EditHTMLParser() +{ +} + +SvParserState EditHTMLParser::CallParser(EditEngine* pEE, const EditPaM& rPaM) +{ + DBG_ASSERT(pEE, "CallParser: ImpEditEngine ?!"); + mpEditEngine = pEE; + SvParserState _eState = SvParserState::NotStarted; + if ( mpEditEngine ) + { + // Build in wrap mimic in RTF import? + aCurSel = EditSelection( rPaM, rPaM ); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::Start, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + ImpSetStyleSheet( 0 ); + _eState = HTMLParser::CallParser(); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::End, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + if ( bFieldsInserted ) + mpEditEngine->UpdateFieldsOnly(); + } + return _eState; +} + +void EditHTMLParser::NextToken( HtmlTokenId nToken ) +{ + switch( nToken ) + { + case HtmlTokenId::META: + { + const HTMLOptions& aOptions = GetOptions(); + size_t nArrLen = aOptions.size(); + bool bEquiv = false; + for ( size_t i = 0; i < nArrLen; i++ ) + { + const HTMLOption& aOption = aOptions[i]; + switch( aOption.GetToken() ) + { + case HtmlOptionId::HTTPEQUIV: + { + bEquiv = true; + } + break; + case HtmlOptionId::CONTENT: + { + if ( bEquiv ) + { + rtl_TextEncoding eEnc = GetEncodingByMIME( aOption.GetString() ); + if ( eEnc != RTL_TEXTENCODING_DONTKNOW ) + SetSrcEncoding( eEnc ); + } + } + break; + default: break; + } + } + + } + break; + case HtmlTokenId::PLAINTEXT_ON: + case HtmlTokenId::PLAINTEXT2_ON: + bInPara = true; + break; + case HtmlTokenId::PLAINTEXT_OFF: + case HtmlTokenId::PLAINTEXT2_OFF: + bInPara = false; + break; + + case HtmlTokenId::LINEBREAK: + case HtmlTokenId::NEWPARA: + { + if ( ( bInPara || nInTable ) && + ( ( nToken == HtmlTokenId::LINEBREAK ) || HasTextInCurrentPara() ) ) + { + ImpInsertParaBreak(); + } + } + break; + case HtmlTokenId::HORZRULE: + { + if ( HasTextInCurrentPara() ) + ImpInsertParaBreak(); + ImpInsertParaBreak(); + } + break; + case HtmlTokenId::NONBREAKSPACE: + { + if ( bInPara ) + { + ImpInsertText( " " ); + } + } + break; + case HtmlTokenId::RAWDATA: + if (IsReadStyle() && !aToken.isEmpty()) + { + // Each token represents a single line. + maStyleSource.append(aToken); + maStyleSource.append('\n'); + } + break; + case HtmlTokenId::TEXTTOKEN: + { + // #i110937# for <title> content, call aImportHdl (no SkipGroup), but don't insert the text into the EditEngine + if (!bInTitle) + { + if ( !bInPara ) + StartPara( false ); + + OUString aText = aToken.toString(); + if ( aText.startsWith(" ") && ThrowAwayBlank() && !IsReadPRE() ) + aText = aText.copy( 1 ); + + if ( moCurAnchor ) + { + moCurAnchor->aText += aText; + } + else + { + // Only written until HTML with 319? + if ( IsReadPRE() ) + aText = aText.replaceAll(u"\t", u" "); + ImpInsertText( aText ); + } + } + } + break; + + case HtmlTokenId::CENTER_ON: + case HtmlTokenId::CENTER_OFF: + { + sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ); + SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() ); + aItems.ClearItem( EE_PARA_JUST ); + if ( nToken == HtmlTokenId::CENTER_ON ) + aItems.Put( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) ); + mpEditEngine->SetParaAttribsOnly(nNode, aItems); + } + break; + + case HtmlTokenId::ANCHOR_ON: AnchorStart(); + break; + case HtmlTokenId::ANCHOR_OFF: AnchorEnd(); + break; + + case HtmlTokenId::PARABREAK_ON: + if( bInPara && HasTextInCurrentPara() ) + EndPara(); + StartPara( true ); + break; + + case HtmlTokenId::PARABREAK_OFF: + if( bInPara ) + EndPara(); + break; + + case HtmlTokenId::HEAD1_ON: + case HtmlTokenId::HEAD2_ON: + case HtmlTokenId::HEAD3_ON: + case HtmlTokenId::HEAD4_ON: + case HtmlTokenId::HEAD5_ON: + case HtmlTokenId::HEAD6_ON: + { + HeadingStart( nToken ); + } + break; + + case HtmlTokenId::HEAD1_OFF: + case HtmlTokenId::HEAD2_OFF: + case HtmlTokenId::HEAD3_OFF: + case HtmlTokenId::HEAD4_OFF: + case HtmlTokenId::HEAD5_OFF: + case HtmlTokenId::HEAD6_OFF: + { + HeadingEnd(); + } + break; + + case HtmlTokenId::PREFORMTXT_ON: + case HtmlTokenId::XMP_ON: + case HtmlTokenId::LISTING_ON: + { + StartPara( true ); + ImpSetStyleSheet( STYLE_PRE ); + } + break; + + case HtmlTokenId::DEFLIST_ON: + { + nDefListLevel++; + } + break; + + case HtmlTokenId::DEFLIST_OFF: + { + if( nDefListLevel ) + nDefListLevel--; + } + break; + + case HtmlTokenId::TABLE_ON: nInTable++; + break; + case HtmlTokenId::TABLE_OFF: DBG_ASSERT( nInTable, "Not in Table, but TABLE_OFF?" ); + nInTable--; + break; + + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + nInCell++; + [[fallthrough]]; + case HtmlTokenId::BLOCKQUOTE_ON: + case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::BLOCKQUOTE30_ON: + case HtmlTokenId::BLOCKQUOTE30_OFF: + case HtmlTokenId::LISTHEADER_ON: + case HtmlTokenId::LI_ON: + case HtmlTokenId::DD_ON: + case HtmlTokenId::DT_ON: + case HtmlTokenId::ORDERLIST_ON: + case HtmlTokenId::UNORDERLIST_ON: + { + bool bHasText = HasTextInCurrentPara(); + if ( bHasText ) + ImpInsertParaBreak(); + StartPara( false ); + } + break; + + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + { + if ( nInCell ) + nInCell--; + [[fallthrough]]; + } + case HtmlTokenId::LISTHEADER_OFF: + case HtmlTokenId::LI_OFF: + case HtmlTokenId::DD_OFF: + case HtmlTokenId::DT_OFF: + case HtmlTokenId::ORDERLIST_OFF: + case HtmlTokenId::UNORDERLIST_OFF: EndPara(); + break; + + case HtmlTokenId::TABLEROW_ON: + case HtmlTokenId::TABLEROW_OFF: // A RETURN only after a CELL, for Calc + + case HtmlTokenId::COL_ON: + case HtmlTokenId::COLGROUP_ON: + case HtmlTokenId::COLGROUP_OFF: break; + + case HtmlTokenId::FONT_ON: + break; + case HtmlTokenId::FONT_OFF: + break; + + case HtmlTokenId::TITLE_ON: + bInTitle = true; + break; + case HtmlTokenId::TITLE_OFF: + bInTitle = false; + break; + + // globals + case HtmlTokenId::HTML_ON: + case HtmlTokenId::HTML_OFF: + case HtmlTokenId::STYLE_ON: + case HtmlTokenId::STYLE_OFF: + case HtmlTokenId::BODY_ON: + case HtmlTokenId::BODY_OFF: + case HtmlTokenId::HEAD_ON: + case HtmlTokenId::HEAD_OFF: + case HtmlTokenId::FORM_ON: + case HtmlTokenId::FORM_OFF: + case HtmlTokenId::THEAD_ON: + case HtmlTokenId::THEAD_OFF: + case HtmlTokenId::TBODY_ON: + case HtmlTokenId::TBODY_OFF: + // inline elements, structural markup + // HTML 3.0 + case HtmlTokenId::BANNER_ON: + case HtmlTokenId::BANNER_OFF: + case HtmlTokenId::DIVISION_ON: + case HtmlTokenId::DIVISION_OFF: +// case HtmlTokenId::LISTHEADER_ON: //! special handling +// case HtmlTokenId::LISTHEADER_OFF: + case HtmlTokenId::NOTE_ON: + case HtmlTokenId::NOTE_OFF: + // inline elements, logical markup + // HTML 2.0 + case HtmlTokenId::ADDRESS_ON: + case HtmlTokenId::ADDRESS_OFF: +// case HtmlTokenId::BLOCKQUOTE_ON: //! special handling +// case HtmlTokenId::BLOCKQUOTE_OFF: + case HtmlTokenId::CITATION_ON: + case HtmlTokenId::CITATION_OFF: + case HtmlTokenId::CODE_ON: + case HtmlTokenId::CODE_OFF: + case HtmlTokenId::DEFINSTANCE_ON: + case HtmlTokenId::DEFINSTANCE_OFF: + case HtmlTokenId::EMPHASIS_ON: + case HtmlTokenId::EMPHASIS_OFF: + case HtmlTokenId::KEYBOARD_ON: + case HtmlTokenId::KEYBOARD_OFF: + case HtmlTokenId::SAMPLE_ON: + case HtmlTokenId::SAMPLE_OFF: + case HtmlTokenId::STRIKE_ON: + case HtmlTokenId::STRIKE_OFF: + case HtmlTokenId::STRONG_ON: + case HtmlTokenId::STRONG_OFF: + case HtmlTokenId::VARIABLE_ON: + case HtmlTokenId::VARIABLE_OFF: + // HTML 3.0 + case HtmlTokenId::ABBREVIATION_ON: + case HtmlTokenId::ABBREVIATION_OFF: + case HtmlTokenId::ACRONYM_ON: + case HtmlTokenId::ACRONYM_OFF: + case HtmlTokenId::AUTHOR_ON: + case HtmlTokenId::AUTHOR_OFF: +// case HtmlTokenId::BLOCKQUOTE30_ON: //! special handling +// case HtmlTokenId::BLOCKQUOTE30_OFF: + case HtmlTokenId::DELETEDTEXT_ON: + case HtmlTokenId::DELETEDTEXT_OFF: + case HtmlTokenId::INSERTEDTEXT_ON: + case HtmlTokenId::INSERTEDTEXT_OFF: + case HtmlTokenId::LANGUAGE_ON: + case HtmlTokenId::LANGUAGE_OFF: + case HtmlTokenId::PERSON_ON: + case HtmlTokenId::PERSON_OFF: + case HtmlTokenId::SHORTQUOTE_ON: + case HtmlTokenId::SHORTQUOTE_OFF: + case HtmlTokenId::SUBSCRIPT_ON: + case HtmlTokenId::SUBSCRIPT_OFF: + case HtmlTokenId::SUPERSCRIPT_ON: + case HtmlTokenId::SUPERSCRIPT_OFF: + // inline elements, visual markup + // HTML 2.0 + case HtmlTokenId::BOLD_ON: + case HtmlTokenId::BOLD_OFF: + case HtmlTokenId::ITALIC_ON: + case HtmlTokenId::ITALIC_OFF: + case HtmlTokenId::TELETYPE_ON: + case HtmlTokenId::TELETYPE_OFF: + case HtmlTokenId::UNDERLINE_ON: + case HtmlTokenId::UNDERLINE_OFF: + // HTML 3.0 + case HtmlTokenId::BIGPRINT_ON: + case HtmlTokenId::BIGPRINT_OFF: + case HtmlTokenId::STRIKETHROUGH_ON: + case HtmlTokenId::STRIKETHROUGH_OFF: + case HtmlTokenId::SMALLPRINT_ON: + case HtmlTokenId::SMALLPRINT_OFF: + // figures + case HtmlTokenId::FIGURE_ON: + case HtmlTokenId::FIGURE_OFF: + case HtmlTokenId::CAPTION_ON: + case HtmlTokenId::CAPTION_OFF: + case HtmlTokenId::CREDIT_ON: + case HtmlTokenId::CREDIT_OFF: + // misc + case HtmlTokenId::DIRLIST_ON: + case HtmlTokenId::DIRLIST_OFF: + case HtmlTokenId::FOOTNOTE_ON: //! they land so in the text + case HtmlTokenId::FOOTNOTE_OFF: + case HtmlTokenId::MENULIST_ON: + case HtmlTokenId::MENULIST_OFF: +// case HtmlTokenId::PLAINTEXT_ON: //! special handling +// case HtmlTokenId::PLAINTEXT_OFF: +// case HtmlTokenId::PREFORMTXT_ON: //! special handling +// case HtmlTokenId::PREFORMTXT_OFF: + case HtmlTokenId::SPAN_ON: + case HtmlTokenId::SPAN_OFF: + // obsolete +// case HtmlTokenId::XMP_ON: //! special handling +// case HtmlTokenId::XMP_OFF: +// case HtmlTokenId::LISTING_ON: //! special handling +// case HtmlTokenId::LISTING_OFF: + // Netscape + case HtmlTokenId::BLINK_ON: + case HtmlTokenId::BLINK_OFF: + case HtmlTokenId::NOBR_ON: + case HtmlTokenId::NOBR_OFF: + case HtmlTokenId::NOEMBED_ON: + case HtmlTokenId::NOEMBED_OFF: + case HtmlTokenId::NOFRAMES_ON: + case HtmlTokenId::NOFRAMES_OFF: + // Internet Explorer + case HtmlTokenId::MARQUEE_ON: + case HtmlTokenId::MARQUEE_OFF: +// case HtmlTokenId::PLAINTEXT2_ON: //! special handling +// case HtmlTokenId::PLAINTEXT2_OFF: + break; + + default: + { + if ( nToken >= HtmlTokenId::ONOFF_START ) + { + if ( ( nToken == HtmlTokenId::UNKNOWNCONTROL_ON ) || ( nToken == HtmlTokenId::UNKNOWNCONTROL_OFF ) ) + { + ; + } + else if ( !isOffToken(nToken) ) + { + DBG_ASSERT( !isOffToken( nToken ), "No Start-Token ?!" ); + SkipGroup( static_cast<HtmlTokenId>(static_cast<int>(nToken) + 1) ); + } + } + } + } // SWITCH + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + if ( nToken == HtmlTokenId::TEXTTOKEN ) + aImportInfo.aText = aToken; + else if (nToken == HtmlTokenId::STYLE_OFF) + aImportInfo.aText = maStyleSource.makeStringAndClear(); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + +} + +void EditHTMLParser::ImpInsertParaBreak() +{ + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); +} + +void EditHTMLParser::ImpSetAttribs( const SfxItemSet& rItems ) +{ + // pSel, when character attributes, otherwise paragraph attributes for + // the current paragraph. + DBG_ASSERT( aCurSel.Min().GetNode() == aCurSel.Max().GetNode(), "ImpInsertAttribs: Selection?" ); + + EditPaM aStartPaM( aCurSel.Min() ); + EditPaM aEndPaM( aCurSel.Max() ); + + aStartPaM.SetIndex( 0 ); + aEndPaM.SetIndex( aEndPaM.GetNode()->Len() ); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + EditSelection aSel( aStartPaM, aEndPaM ); + HtmlImportInfo aImportInfo(HtmlImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + ContentNode* pSN = aStartPaM.GetNode(); + sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN ); + + // If an attribute goes from 0 to current Paragraph length, + // then it should be a paragraph attribute! + + // Note: Selection can reach over several Paragraphs. + // All complete paragraphs are paragraph attributes ... + + // not really HTML: +#ifdef DBG_UTIL + ContentNode* pEN = aEndPaM.GetNode(); + sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN ); + DBG_ASSERT( nStartNode == nEndNode, "ImpSetAttribs: Several paragraphs?" ); +#endif + + if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) ) + { + // Has to be merged: + SfxItemSet aItems = mpEditEngine->GetBaseParaAttribs(nStartNode); + aItems.Put( rItems ); + mpEditEngine->SetParaAttribsOnly(nStartNode, aItems); + } + else + mpEditEngine->SetAttribs( EditSelection( aStartPaM, aEndPaM ), rItems ); +} + +void EditHTMLParser::ImpSetStyleSheet( sal_uInt16 nHLevel ) +{ + /* + nHLevel: 0: Turn off + 1-6: Heading + STYLE_PRE: Preformatted + */ + // Create hard attributes ... + // Enough for Calc, would have to be clarified with StyleSheets + // that they should also be in the app so that when they are feed + // in a different engine still are here ... + sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ); + + SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() ); + + aItems.ClearItem( EE_PARA_ULSPACE ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT ); + aItems.ClearItem( EE_CHAR_FONTINFO ); + aItems.ClearItem( EE_CHAR_WEIGHT ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT_CJK ); + aItems.ClearItem( EE_CHAR_FONTINFO_CJK ); + aItems.ClearItem( EE_CHAR_WEIGHT_CJK ); + + aItems.ClearItem( EE_CHAR_FONTHEIGHT_CTL ); + aItems.ClearItem( EE_CHAR_FONTINFO_CTL ); + aItems.ClearItem( EE_CHAR_WEIGHT_CTL ); + + // Bold in the first 3 Headings + if ( ( nHLevel >= 1 ) && ( nHLevel <= 3 ) ) + { + SvxWeightItem aWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT ); + aItems.Put( aWeightItem ); + + SvxWeightItem aWeightItemCJK( WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK ); + aItems.Put( aWeightItemCJK ); + + SvxWeightItem aWeightItemCTL( WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL ); + aItems.Put( aWeightItemCTL ); + } + + // Font height and margins, when LogicToLogic is possible: + MapUnit eUnit = mpEditEngine->GetRefMapMode().GetMapUnit(); + if ( ( eUnit != MapUnit::MapPixel ) && ( eUnit != MapUnit::MapSysFont ) && + ( eUnit != MapUnit::MapAppFont ) && ( eUnit != MapUnit::MapRelative ) ) + { + tools::Long nPoints = 10; + if ( nHLevel == 1 ) + nPoints = 22; + else if ( nHLevel == 2 ) + nPoints = 16; + else if ( nHLevel == 3 ) + nPoints = 12; + else if ( nHLevel == 4 ) + nPoints = 11; + + nPoints = OutputDevice::LogicToLogic( nPoints, MapUnit::MapPoint, eUnit ); + + SvxFontHeightItem aHeightItem( nPoints, 100, EE_CHAR_FONTHEIGHT ); + aItems.Put( aHeightItem ); + + SvxFontHeightItem aHeightItemCJK( nPoints, 100, EE_CHAR_FONTHEIGHT_CJK ); + aItems.Put( aHeightItemCJK ); + + SvxFontHeightItem aHeightItemCTL( nPoints, 100, EE_CHAR_FONTHEIGHT_CTL ); + aItems.Put( aHeightItemCTL ); + + // Paragraph margins, when Heading: + if (nHLevel <= 6) + { + SvxULSpaceItem aULSpaceItem( EE_PARA_ULSPACE ); + aULSpaceItem.SetUpper( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 42, MapUnit::Map10thMM, eUnit )) ); + aULSpaceItem.SetLower( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 35, MapUnit::Map10thMM, eUnit )) ); + aItems.Put( aULSpaceItem ); + } + } + + // Choose a proportional Font for Pre + if ( nHLevel == STYLE_PRE ) + { + vcl::Font aFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE ); + SvxFontItem aFontItem( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO ); + aItems.Put( aFontItem ); + + SvxFontItem aFontItemCJK( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CJK ); + aItems.Put( aFontItemCJK ); + + SvxFontItem aFontItemCTL( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CTL ); + aItems.Put( aFontItemCTL ); + } + + mpEditEngine->SetParaAttribsOnly(nNode, aItems); +} + +void EditHTMLParser::ImpInsertText( const OUString& rText ) +{ + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.aText = rText; + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } + + aCurSel = mpEditEngine->InsertText(aCurSel, rText); +} + +void EditHTMLParser::SkipGroup( HtmlTokenId nEndToken ) +{ + // groups in cells are closed upon leaving the cell, because those + // ******* web authors don't know their job + // for example: <td><form></td> lacks a closing </form> + sal_uInt8 nCellLevel = nInCell; + HtmlTokenId nToken; + while( nCellLevel <= nInCell ) + { + nToken = GetNextToken(); + if (nToken == nEndToken || nToken == HtmlTokenId::NONE) + break; + switch ( nToken ) + { + case HtmlTokenId::TABLEHEADER_ON: + case HtmlTokenId::TABLEDATA_ON: + nInCell++; + break; + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: + if ( nInCell ) + nInCell--; + break; + default: break; + } + } +} + +void EditHTMLParser::StartPara( bool bReal ) +{ + if ( bReal ) + { + const HTMLOptions& aOptions = GetOptions(); + SvxAdjust eAdjust = SvxAdjust::Left; + for (const auto & aOption : aOptions) + { + if( aOption.GetToken() == HtmlOptionId::ALIGN ) + { + OUString const& rTmp(aOption.GetString()); + if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_right)) + eAdjust = SvxAdjust::Right; + else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_middle)) + eAdjust = SvxAdjust::Center; + else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_center)) + eAdjust = SvxAdjust::Center; + else + eAdjust = SvxAdjust::Left; + } + } + SfxItemSet aItemSet = mpEditEngine->GetEmptyItemSet(); + aItemSet.Put( SvxAdjustItem( eAdjust, EE_PARA_JUST ) ); + ImpSetAttribs( aItemSet ); + } + bInPara = true; +} + +void EditHTMLParser::EndPara() +{ + if ( bInPara ) + { + bool bHasText = HasTextInCurrentPara(); + if ( bHasText ) + ImpInsertParaBreak(); + } + bInPara = false; +} + +bool EditHTMLParser::ThrowAwayBlank() +{ + // A blank must be thrown away if the new text begins with a Blank and + // if the current paragraph is empty or ends with a Blank... + ContentNode* pNode = aCurSel.Max().GetNode(); + return !(pNode->Len() && ( pNode->GetChar( pNode->Len()-1 ) != ' ' )); +} + +bool EditHTMLParser::HasTextInCurrentPara() +{ + return aCurSel.Max().GetNode()->Len() != 0; +} + +void EditHTMLParser::AnchorStart() +{ + // ignore anchor in anchor + if ( moCurAnchor ) + return; + + const HTMLOptions& aOptions = GetOptions(); + OUString aRef; + + for (const auto & aOption : aOptions) + { + if( aOption.GetToken() == HtmlOptionId::HREF) + aRef = aOption.GetString(); + } + + if ( aRef.isEmpty() ) + return; + + OUString aURL = aRef; + if ( !aURL.isEmpty() && ( aURL[ 0 ] != '#' ) ) + { + INetURLObject aTargetURL; + INetURLObject aRootURL( aBaseURL ); + aRootURL.GetNewAbsURL( aRef, &aTargetURL ); + aURL = aTargetURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + } + moCurAnchor.emplace(); + moCurAnchor->aHRef = aURL; +} + +void EditHTMLParser::AnchorEnd() +{ + if ( !moCurAnchor ) + return; + + // Insert as URL-Field... + SvxFieldItem aFld( SvxURLField( moCurAnchor->aHRef, moCurAnchor->aText, SvxURLFormat::Repr ), EE_FEATURE_FIELD ); + aCurSel = mpEditEngine->InsertField(aCurSel, aFld); + bFieldsInserted = true; + moCurAnchor.reset(); + + if (mpEditEngine->IsHtmlImportHandlerSet()) + { + HtmlImportInfo aImportInfo(HtmlImportState::InsertField, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallHtmlImportHandler(aImportInfo); + } +} + +void EditHTMLParser::HeadingStart( HtmlTokenId nToken ) +{ + bWasInPara = bInPara; + StartPara( false ); + + if ( bWasInPara && HasTextInCurrentPara() ) + ImpInsertParaBreak(); + + sal_uInt16 nId = sal::static_int_cast< sal_uInt16 >( + 1 + ( ( static_cast<int>(nToken) - int(HtmlTokenId::HEAD1_ON) ) / 2 ) ); + DBG_ASSERT( (nId >= 1) && (nId <= 9), "HeadingStart: ID can not be correct!" ); + ImpSetStyleSheet( nId ); +} + +void EditHTMLParser::HeadingEnd() +{ + EndPara(); + ImpSetStyleSheet( 0 ); + + if ( bWasInPara ) + { + bInPara = true; + bWasInPara = false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eehtml.hxx b/editeng/source/editeng/eehtml.hxx new file mode 100644 index 0000000000..fddd567ac6 --- /dev/null +++ b/editeng/source/editeng/eehtml.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> +#include <optional> +#include <editdoc.hxx> +#include <rtl/ustrbuf.hxx> +#include <svtools/parhtml.hxx> + +class EditEngine; + +struct AnchorInfo +{ + OUString aHRef; + OUString aText; +}; + +class EditHTMLParser : public HTMLParser +{ + using HTMLParser::CallParser; +private: + OUStringBuffer maStyleSource; + EditSelection aCurSel; + OUString aBaseURL; + EditEngine* mpEditEngine; + std::optional<AnchorInfo> moCurAnchor; + + bool bInPara:1; + bool bWasInPara:1; // Remember bInPara before HeadingStart, because afterwards it will be gone. + bool bFieldsInserted:1; + bool bInTitle:1; + + sal_uInt8 nInTable; + sal_uInt8 nInCell; + sal_uInt8 nDefListLevel; + + void StartPara( bool bReal ); + void EndPara(); + void AnchorStart(); + void AnchorEnd(); + void HeadingStart( HtmlTokenId nToken ); + void HeadingEnd(); + void SkipGroup( HtmlTokenId nEndToken ); + bool ThrowAwayBlank(); + bool HasTextInCurrentPara(); + + void ImpInsertParaBreak(); + void ImpInsertText( const OUString& rText ); + void ImpSetAttribs( const SfxItemSet& rItems ); + void ImpSetStyleSheet( sal_uInt16 nHeadingLevel ); + +protected: + virtual void NextToken( HtmlTokenId nToken ) override; + +public: + EditHTMLParser(SvStream& rIn, OUString aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs); + virtual ~EditHTMLParser() override; + + SvParserState CallParser(EditEngine* pEE, const EditPaM& rPaM); + + const EditSelection& GetCurSelection() const { return aCurSel; } +}; + +typedef tools::SvRef<EditHTMLParser> EditHTMLParserRef; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eeobj.cxx b/editeng/source/editeng/eeobj.cxx new file mode 100644 index 0000000000..bfd81c40c3 --- /dev/null +++ b/editeng/source/editeng/eeobj.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include "eeobj.hxx" +#include <sot/exchange.hxx> +#include <sot/formats.hxx> + +using namespace ::com::sun::star; + + +EditDataObject::EditDataObject() +{ +} + +EditDataObject::~EditDataObject() +{ +} + +// datatransfer::XTransferable +uno::Any EditDataObject::getTransferData( const datatransfer::DataFlavor& rFlavor ) +{ + uno::Any aAny; + + SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); + if ( nT == SotClipboardFormatId::STRING ) + { + aAny <<= GetString(); + } + else if ( ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT ) || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) + { + // No RTF on demand any more: + // 1) Was not working, because I had to flush() the clipboard immediately anyway + // 2) Don't have the old pool defaults and the StyleSheetPool here. + + SvMemoryStream* pStream = (nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ? &GetODFStream() : &GetRTFStream(); + sal_Int32 nLen = pStream->TellEnd(); + if (nLen < 0) { abort(); } + + aAny <<= uno::Sequence< sal_Int8 >( static_cast< const sal_Int8* >(pStream->GetData()), pStream->TellEnd() ); + } + else + { + datatransfer::UnsupportedFlavorException aException; + throw aException; + } + + return aAny; +} + +uno::Sequence< datatransfer::DataFlavor > EditDataObject::getTransferDataFlavors( ) +{ + uno::Sequence< datatransfer::DataFlavor > aDataFlavors(4); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aDataFlavors.getArray()[0] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[1] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aDataFlavors.getArray()[2] ); + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aDataFlavors.getArray()[3] ); + + return aDataFlavors; +} + +sal_Bool EditDataObject::isDataFlavorSupported( const datatransfer::DataFlavor& rFlavor ) +{ + bool bSupported = false; + + SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); + if ( ( nT == SotClipboardFormatId::STRING ) || ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT ) + || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) + bSupported = true; + + return bSupported; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eeobj.hxx b/editeng/source/editeng/eeobj.hxx new file mode 100644 index 0000000000..de06f1b703 --- /dev/null +++ b/editeng/source/editeng/eeobj.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/XTransferable.hpp> + +#include <tools/stream.hxx> + +class EditDataObject : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable> +{ +private: + SvMemoryStream maRTFData; + SvMemoryStream maODFData; + OUString maText; + OUString maOfficeBookmark; + +public: + EditDataObject(); + virtual ~EditDataObject() override; + + SvMemoryStream& GetRTFStream() { return maRTFData; } + SvMemoryStream& GetODFStream() { return maODFData; } + OUString& GetString() { return maText; } + OUString& GetURL() { return maOfficeBookmark; } + + // css::datatransfer::XTransferable + css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override; + css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override; + sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eerdll.cxx b/editeng/source/editeng/eerdll.cxx new file mode 100644 index 0000000000..9e3e8c4cf8 --- /dev/null +++ b/editeng/source/editeng/eerdll.cxx @@ -0,0 +1,228 @@ +/* -*- 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 <unotools/resmgr.hxx> +#include <com/sun/star/linguistic2/LanguageGuessing.hpp> + +#include <comphelper/processfactory.hxx> + +#include <editeng/eeitem.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/bulletitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <svl/grabbagitem.hxx> +#include <svl/voiditem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/xmlcnitm.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <editeng/justifyitem.hxx> +#include <tools/mapunit.hxx> +#include <vcl/lazydelete.hxx> + +using namespace ::com::sun::star; + +EditDLL& EditDLL::Get() +{ + /** + Prevent use-after-free errors during application shutdown. + Previously this data was function-static, but then data in i18npool would + be torn down before the destructor here ran, causing a crash. + */ + static vcl::DeleteOnDeinit< EditDLL > gaEditDll; + return *gaEditDll.get(); +} + +DefItems::DefItems() + : mvDefItems(EDITITEMCOUNT) +{ + std::vector<SfxPoolItem*>& rDefItems = mvDefItems; + + // Paragraph attributes: + SvxNumRule aDefaultNumRule( SvxNumRuleFlags::NONE, 0, false ); + + rDefItems[0] = new SvxFrameDirectionItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR ); + rDefItems[1] = new SvXMLAttrContainerItem( EE_PARA_XMLATTRIBS ); + rDefItems[2] = new SvxHangingPunctuationItem(false, EE_PARA_HANGINGPUNCTUATION); + rDefItems[3] = new SvxForbiddenRuleItem(true, EE_PARA_FORBIDDENRULES); + rDefItems[4] = new SvxScriptSpaceItem( true, EE_PARA_ASIANCJKSPACING ); + rDefItems[5] = new SvxNumBulletItem( aDefaultNumRule, EE_PARA_NUMBULLET ); + rDefItems[6] = new SfxBoolItem( EE_PARA_HYPHENATE, false ); + rDefItems[7] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_CAPS, false ); + rDefItems[8] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_LAST_WORD, false ); + rDefItems[9] = new SfxBoolItem( EE_PARA_BULLETSTATE, true ); + rDefItems[10] = new SvxLRSpaceItem( EE_PARA_OUTLLRSPACE ); + rDefItems[11] = new SfxInt16Item( EE_PARA_OUTLLEVEL, -1 ); + rDefItems[12] = new SvxBulletItem( EE_PARA_BULLET ); + rDefItems[13] = new SvxLRSpaceItem( EE_PARA_LRSPACE ); + rDefItems[14] = new SvxULSpaceItem( EE_PARA_ULSPACE ); + rDefItems[15] = new SvxLineSpacingItem( 0, EE_PARA_SBL ); + rDefItems[16] = new SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST ); + rDefItems[17] = new SvxTabStopItem( 0, 0, SvxTabAdjust::Left, EE_PARA_TABS ); + rDefItems[18] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, EE_PARA_JUST_METHOD ); + rDefItems[19] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, EE_PARA_VER_JUST ); + + // Character attributes: + rDefItems[20] = new SvxColorItem( COL_AUTO, EE_CHAR_COLOR ); + rDefItems[21] = new SvxFontItem( EE_CHAR_FONTINFO ); + rDefItems[22] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT ); + rDefItems[23] = new SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH ); + rDefItems[24] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT ); + rDefItems[25] = new SvxUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE ); + rDefItems[26] = new SvxCrossedOutItem( STRIKEOUT_NONE, EE_CHAR_STRIKEOUT ); + rDefItems[27] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC ); + rDefItems[28] = new SvxContourItem( false, EE_CHAR_OUTLINE ); + rDefItems[29] = new SvxShadowedItem( false, EE_CHAR_SHADOW ); + rDefItems[30] = new SvxEscapementItem( 0, 100, EE_CHAR_ESCAPEMENT ); + rDefItems[31] = new SvxAutoKernItem( false, EE_CHAR_PAIRKERNING ); + rDefItems[32] = new SvxKerningItem( 0, EE_CHAR_KERNING ); + rDefItems[33] = new SvxWordLineModeItem( false, EE_CHAR_WLM ); + rDefItems[34] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE ); + rDefItems[35] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CJK ); + rDefItems[36] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CTL ); + rDefItems[37] = new SvxFontItem( EE_CHAR_FONTINFO_CJK ); + rDefItems[38] = new SvxFontItem( EE_CHAR_FONTINFO_CTL ); + rDefItems[39] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CJK ); + rDefItems[40] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CTL ); + rDefItems[41] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CJK ); + rDefItems[42] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CTL ); + rDefItems[43] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CJK ); + rDefItems[44] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CTL ); + rDefItems[45] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, EE_CHAR_EMPHASISMARK ); + rDefItems[46] = new SvxCharReliefItem( FontRelief::NONE, EE_CHAR_RELIEF ); + rDefItems[47] = new SfxVoidItem( EE_CHAR_RUBI_DUMMY ); + rDefItems[48] = new SvXMLAttrContainerItem( EE_CHAR_XMLATTRIBS ); + rDefItems[49] = new SvxOverlineItem( LINESTYLE_NONE, EE_CHAR_OVERLINE ); + rDefItems[50] = new SvxCaseMapItem( SvxCaseMap::NotMapped, EE_CHAR_CASEMAP ); + rDefItems[51] = new SfxGrabBagItem( EE_CHAR_GRABBAG ); + rDefItems[52] = new SvxColorItem( COL_AUTO, EE_CHAR_BKGCOLOR ); + // Features + rDefItems[53] = new SfxVoidItem( EE_FEATURE_TAB ); + rDefItems[54] = new SfxVoidItem( EE_FEATURE_LINEBR ); + rDefItems[55] = new SvxColorItem( COL_RED, EE_FEATURE_NOTCONV ); + rDefItems[56] = new SvxFieldItem( SvxFieldData(), EE_FEATURE_FIELD ); + + assert(EDITITEMCOUNT == 57 && "ITEMCOUNT changed, adjust DefItems!"); + + // Init DefFonts: + GetDefaultFonts( *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO - EE_ITEMS_START]), + *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CJK - EE_ITEMS_START]), + *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CTL - EE_ITEMS_START]) ); +} + +DefItems::~DefItems() +{ + for (const auto& rItem : mvDefItems) + delete rItem; +} + +std::shared_ptr<DefItems> GlobalEditData::GetDefItems() +{ + auto xDefItems = m_xDefItems.lock(); + if (!xDefItems) + { + xDefItems = std::make_shared<DefItems>(); + m_xDefItems = xDefItems; + } + return xDefItems; +} + +std::shared_ptr<SvxForbiddenCharactersTable> const & GlobalEditData::GetForbiddenCharsTable() +{ + if (!xForbiddenCharsTable) + xForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext()); + return xForbiddenCharsTable; +} + +uno::Reference< linguistic2::XLanguageGuessing > const & GlobalEditData::GetLanguageGuesser() +{ + if (!xLanguageGuesser.is()) + { + xLanguageGuesser = linguistic2::LanguageGuessing::create( comphelper::getProcessComponentContext() ); + } + return xLanguageGuesser; +} + +OUString EditResId(TranslateId aId) +{ + return Translate::get(aId, Translate::Create("editeng")); +} + +EditDLL::EditDLL() + : pGlobalData( new GlobalEditData ) +{ +} + +EditDLL::~EditDLL() +{ +} + +editeng::SharedVclResources::SharedVclResources() + : m_pVirDev(VclPtr<VirtualDevice>::Create()) +{ + m_pVirDev->SetMapMode(MapMode(MapUnit::MapTwip)); +} + +editeng::SharedVclResources::~SharedVclResources() + { m_pVirDev.disposeAndClear(); } + +VclPtr<VirtualDevice> const & editeng::SharedVclResources::GetVirtualDevice() const + { return m_pVirDev; } + +std::shared_ptr<editeng::SharedVclResources> EditDLL::GetSharedVclResources() +{ + SolarMutexGuard g; + auto pLocked(pSharedVcl.lock()); + if(!pLocked) + pSharedVcl = pLocked = std::make_shared<editeng::SharedVclResources>(); + return pLocked; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eertfpar.cxx b/editeng/source/editeng/eertfpar.cxx new file mode 100644 index 0000000000..948216f33d --- /dev/null +++ b/editeng/source/editeng/eertfpar.cxx @@ -0,0 +1,633 @@ +/* -*- 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 <comphelper/string.hxx> +#include <utility> + +#include "eertfpar.hxx" +#include "impedit.hxx" +#include <svl/intitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/editeng.hxx> + +#include <svtools/rtftoken.h> +#include <svtools/htmltokn.h> +#include <unotools/configmgr.hxx> + +using namespace com::sun::star; + +HtmlImportInfo::HtmlImportInfo( HtmlImportState eSt, SvParser<HtmlTokenId>* pPrsrs, const ESelection& rSel ) + : aSelection( rSel ) +{ + pParser = pPrsrs; + eState = eSt; + nToken = HtmlTokenId::NONE; +} + +HtmlImportInfo::~HtmlImportInfo() +{ +} + +RtfImportInfo::RtfImportInfo( RtfImportState eSt, SvParser<int>* pPrsrs, const ESelection& rSel ) + : aSelection( rSel ) +{ + pParser = pPrsrs; + eState = eSt; + nToken = 0; + nTokenValue = 0; +} + +constexpr MapUnit gRTFMapUnit = MapUnit::MapTwip; + +EditRTFParser::EditRTFParser( + SvStream& rIn, EditSelection aSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine) : + SvxRTFParser(rAttrPool, rIn), + aCurSel(std::move(aSel)), + mpEditEngine(pEditEngine), + nDefFont(0), + bLastActionInsertParaBreak(false) +{ + SetInsPos(EditPosition(mpEditEngine, &aCurSel)); + + // Convert the twips values ... + SetCalcValue(true); + SetChkStyleAttr(mpEditEngine->IsImportRTFStyleSheetsSet()); + SetNewDoc(false); // So that the Pool-Defaults are not overwritten... + aEditMapMode = MapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit()); +} + +EditRTFParser::~EditRTFParser() +{ +} + +SvParserState EditRTFParser::CallParser() +{ + DBG_ASSERT( !aCurSel.HasRange(), "Selection for CallParser!" ); + // Separate the part that is imported from the rest. + // This expression should be used for all imports. + // aStart1PaM: Last position before the imported content + // aEnd1PaM: First position after the imported content + // aStart2PaM: First position of the imported content + // aEnd2PaM: Last position of the imported content + EditPaM aStart1PaM( aCurSel.Min().GetNode(), aCurSel.Min().GetIndex() ); + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + EditPaM aStart2PaM = aCurSel.Min(); + // Useful or not? + aStart2PaM.GetNode()->GetContentAttribs().GetItems().ClearItem(); + AddRTFDefaultValues( aStart2PaM, aStart2PaM ); + EditPaM aEnd1PaM = mpEditEngine->InsertParaBreak(aCurSel.Max()); + // aCurCel now points to the gap + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::Start, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + SvParserState _eState = SvxRTFParser::CallParser(); + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::End, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + if (bLastActionInsertParaBreak) + { + ContentNode* pCurNode = aCurSel.Max().GetNode(); + sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos(pCurNode); + ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject(nPara-1); + DBG_ASSERT( pPrevNode, "Invalid RTF-Document?!" ); + EditSelection aSel; + aSel.Min() = EditPaM( pPrevNode, pPrevNode->Len() ); + aSel.Max() = EditPaM( pCurNode, 0 ); + aCurSel.Max() = mpEditEngine->DeleteSelection(aSel); + } + EditPaM aEnd2PaM( aCurSel.Max() ); + //AddRTFDefaultValues( aStart2PaM, aEnd2PaM ); + bool bOnlyOnePara = ( aEnd2PaM.GetNode() == aStart2PaM.GetNode() ); + // Paste the chunk again ... + // Problem: Paragraph attributes may not possibly be taken over + // => Do Character attributes. + + bool bSpecialBackward = aStart1PaM.GetNode()->Len() == 0; + if ( bOnlyOnePara || aStart1PaM.GetNode()->Len() ) + mpEditEngine->ParaAttribsToCharAttribs( aStart2PaM.GetNode() ); + aCurSel.Min() = mpEditEngine->ConnectParagraphs( + aStart1PaM.GetNode(), aStart2PaM.GetNode(), bSpecialBackward ); + bSpecialBackward = aEnd1PaM.GetNode()->Len() != 0; + // when bOnlyOnePara, then the node is gone on Connect. + if ( !bOnlyOnePara && aEnd1PaM.GetNode()->Len() ) + mpEditEngine->ParaAttribsToCharAttribs( aEnd2PaM.GetNode() ); + aCurSel.Max() = mpEditEngine->ConnectParagraphs( + ( bOnlyOnePara ? aStart1PaM.GetNode() : aEnd2PaM.GetNode() ), + aEnd1PaM.GetNode(), bSpecialBackward ); + + return _eState; +} + +void EditRTFParser::AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd ) +{ + // Problem: DefFont and DefFontHeight + Size aSz( 12, 0 ); + MapMode aPntMode( MapUnit::MapPoint ); + MapMode _aEditMapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit()); + aSz = mpEditEngine->GetRefDevice()->LogicToLogic(aSz, &aPntMode, &_aEditMapMode); + SvxFontHeightItem aFontHeightItem( aSz.Width(), 100, EE_CHAR_FONTHEIGHT ); + vcl::Font aDefFont( GetFont( nDefFont ) ); + SvxFontItem aFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), + aDefFont.GetStyleName(), aDefFont.GetPitch(), aDefFont.GetCharSet(), EE_CHAR_FONTINFO ); + + sal_Int32 nStartPara = mpEditEngine->GetEditDoc().GetPos( rStart.GetNode() ); + sal_Int32 nEndPara = mpEditEngine->GetEditDoc().GetPos( rEnd.GetNode() ); + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( nPara ); + assert(pNode && "AddRTFDefaultValues - No paragraph?!"); + if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTINFO ) ) + pNode->GetContentAttribs().GetItems().Put( aFontItem ); + if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTHEIGHT ) ) + pNode->GetContentAttribs().GetItems().Put( aFontHeightItem ); + } +} + +void EditRTFParser::NextToken( int nToken ) +{ + switch( nToken ) + { + case RTF_DEFF: + { + nDefFont = sal_uInt16(nTokenValue); + } + break; + case RTF_DEFTAB: + break; + case RTF_CELL: + { + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + } + break; + case RTF_LINE: + { + aCurSel = mpEditEngine->InsertLineBreak(aCurSel); + } + break; + case RTF_FIELD: + { + ReadField(); + } + break; + case RTF_SHPINST: // fdo#76776 process contents of shpinst + break; + case RTF_SP: // fdo#76776 but skip SP groups + { + SkipGroup(); + } + break; + case RTF_LISTTEXT: + { + SkipGroup(); + } + break; + default: + { + SvxRTFParser::NextToken( nToken ); + if ( nToken == RTF_STYLESHEET ) + CreateStyleSheets(); + } + break; + } + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + aImportInfo.nTokenValue = short(nTokenValue); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } +} + +void EditRTFParser::UnknownAttrToken( int nToken ) +{ + // for Tokens which are not evaluated in ReadAttr + // Actually, only for Calc (RTFTokenHdl), so that RTF_INTBL + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::UnknownAttr, this, mpEditEngine->CreateESelection(aCurSel)); + aImportInfo.nToken = nToken; + aImportInfo.nTokenValue = short(nTokenValue); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } +} + +void EditRTFParser::InsertText() +{ + OUString aText( aToken ); + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertText(aCurSel, aText); + bLastActionInsertParaBreak = false; +} + +void EditRTFParser::InsertPara() +{ + if (mpEditEngine->IsRtfImportHandlerSet()) + { + RtfImportInfo aImportInfo(RtfImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + aCurSel = mpEditEngine->InsertParaBreak(aCurSel); + bLastActionInsertParaBreak = true; +} + +void EditRTFParser::MovePos( bool const bForward ) +{ + if( bForward ) + aCurSel = mpEditEngine->CursorRight( + aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER); + else + aCurSel = mpEditEngine->CursorLeft( + aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER); +} + +void EditRTFParser::SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos, + sal_Int32& rCntPos ) +{ + // The Intention is to: determine the current insert position of the + // previous paragraph and set the end from this. + // This "\pard" always apply on the right paragraph. + + ContentNode* pN = aCurSel.Max().GetNode(); + sal_Int32 nCurPara = mpEditEngine->GetEditDoc().GetPos( pN ); + DBG_ASSERT( nCurPara != 0, "Paragraph equal to 0: SetEnfPrevPara" ); + if ( nCurPara ) + nCurPara--; + ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject( nCurPara ); + assert(pPrevNode && "pPrevNode = 0!"); + rpNodePos = EditNodeIdx(mpEditEngine, pPrevNode); + rCntPos = pPrevNode->Len(); +} + +bool EditRTFParser::IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const +{ + return nCnt == pNd->GetNode()->Len(); +} + +void EditRTFParser::SetAttrInDoc( SvxRTFItemStackType &rSet ) +{ + ContentNode* pSttNode = const_cast<EditNodeIdx&>(rSet.GetSttNode()).GetNode(); + ContentNode* pEndNode = const_cast<EditNodeIdx&>(rSet.GetEndNode()).GetNode(); + + EditPaM aStartPaM( pSttNode, rSet.GetSttCnt() ); + EditPaM aEndPaM( pEndNode, rSet.GetEndCnt() ); + + // If possible adjust the Escapement-Item: + + // #i66167# adapt font heights to destination MapUnit if necessary + const MapUnit eDestUnit = mpEditEngine->GetEditDoc().GetItemPool().GetMetric(0); + if (eDestUnit != gRTFMapUnit) + { + sal_uInt16 const aFntHeightIems[3] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL }; + for (unsigned short aFntHeightIem : aFntHeightIems) + { + const SfxPoolItem* pItem; + if (SfxItemState::SET == rSet.GetAttrSet().GetItemState( aFntHeightIem, false, &pItem )) + { + sal_uInt32 nHeight = static_cast<const SvxFontHeightItem*>(pItem)->GetHeight(); + tools::Long nNewHeight; + nNewHeight = OutputDevice::LogicToLogic( static_cast<tools::Long>(nHeight), gRTFMapUnit, eDestUnit ); + + SvxFontHeightItem aFntHeightItem( nNewHeight, 100, aFntHeightIem ); + aFntHeightItem.SetProp( + static_cast<const SvxFontHeightItem*>(pItem)->GetProp(), + static_cast<const SvxFontHeightItem*>(pItem)->GetPropUnit()); + rSet.GetAttrSet().Put( aFntHeightItem ); + } + } + } + + if( const SvxEscapementItem* pItem = rSet.GetAttrSet().GetItemIfSet( EE_CHAR_ESCAPEMENT, false ) ) + { + // the correct one + tools::Long nEsc = pItem->GetEsc(); + tools::Long nEscFontHeight = 0; + if( ( DFLT_ESC_AUTO_SUPER != nEsc ) && ( DFLT_ESC_AUTO_SUB != nEsc ) ) + { + nEsc *= 10; //HalfPoints => Twips was embezzled in RTFITEM.CXX! + SvxFont aFont; + if (utl::ConfigManager::IsFuzzing()) + { + // ofz#24932 detecting RTL vs LTR is slow + aFont = aStartPaM.GetNode()->GetCharAttribs().GetDefFont(); + } + else + mpEditEngine->SeekCursor(aStartPaM.GetNode(), aStartPaM.GetIndex()+1, aFont); + nEscFontHeight = aFont.GetFontSize().Height(); + } + if (nEscFontHeight) + { + nEsc = nEsc * 100 / nEscFontHeight; + + SvxEscapementItem aEscItem( static_cast<short>(nEsc), pItem->GetProportionalHeight(), EE_CHAR_ESCAPEMENT ); + rSet.GetAttrSet().Put( aEscItem ); + } + } + + if (mpEditEngine->IsRtfImportHandlerSet()) + { + EditSelection aSel( aStartPaM, aEndPaM ); + RtfImportInfo aImportInfo(RtfImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel)); + mpEditEngine->CallRtfImportHandler(aImportInfo); + } + + ContentNode* pSN = aStartPaM.GetNode(); + ContentNode* pEN = aEndPaM.GetNode(); + sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN ); + sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN ); + sal_Int16 nOutlLevel = 0xff; + + if (rSet.StyleNo() && mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet()) + { + SvxRTFStyleTbl::iterator it = GetStyleTbl().find( rSet.StyleNo() ); + DBG_ASSERT( it != GetStyleTbl().end(), "Template not defined in RTF!" ); + if ( it != GetStyleTbl().end() ) + { + auto const& pS = it->second; + mpEditEngine->SetStyleSheet( + EditSelection(aStartPaM, aEndPaM), + static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find(pS.sName, SfxStyleFamily::All))); + nOutlLevel = pS.nOutlineNo; + } + } + + // When an Attribute goes from 0 to the current paragraph length, + // it should be a paragraph attribute! + + // Note: Selection can reach over several paragraphs. + // All Complete paragraphs are paragraph attributes ... + for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ ) + { + DBG_ASSERT(mpEditEngine->GetEditDoc().GetObject(z), "Node does not exist yet(RTF)"); + mpEditEngine->SetParaAttribsOnly(z, rSet.GetAttrSet()); + } + + if ( aStartPaM.GetNode() != aEndPaM.GetNode() ) + { + // The rest of the StartNodes... + if ( aStartPaM.GetIndex() == 0 ) + mpEditEngine->SetParaAttribsOnly(nStartNode, rSet.GetAttrSet()); + else + mpEditEngine->SetAttribs( + EditSelection(aStartPaM, EditPaM(aStartPaM.GetNode(), aStartPaM.GetNode()->Len())), rSet.GetAttrSet()); + + // the beginning of the EndNodes... + if ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) + mpEditEngine->SetParaAttribsOnly(nEndNode, rSet.GetAttrSet()); + else + mpEditEngine->SetAttribs( + EditSelection(EditPaM(aEndPaM.GetNode(), 0), aEndPaM), rSet.GetAttrSet()); + } + else + { + if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) ) + { + // When settings char attribs as para attribs, we must merge with existing attribs, not overwrite the ItemSet! + SfxItemSet aAttrs = mpEditEngine->GetBaseParaAttribs(nStartNode); + aAttrs.Put( rSet.GetAttrSet() ); + mpEditEngine->SetParaAttribsOnly(nStartNode, aAttrs); + } + else + { + mpEditEngine->SetAttribs( + EditSelection(aStartPaM, aEndPaM), rSet.GetAttrSet()); + } + } + + // OutlLevel... + if ( nOutlLevel != 0xff ) + { + for ( sal_Int32 n = nStartNode; n <= nEndNode; n++ ) + { + ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( n ); + pNode->GetContentAttribs().GetItems().Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nOutlLevel ) ); + } + } +} + +SvxRTFStyleType* EditRTFParser::FindStyleSheet( std::u16string_view rName ) +{ + SvxRTFStyleTbl& rTable = GetStyleTbl(); + for (auto & iter : rTable) + { + if (iter.second.sName == rName) + return &iter.second; + } + return nullptr; +} + +SfxStyleSheet* EditRTFParser::CreateStyleSheet( SvxRTFStyleType const * pRTFStyle ) +{ + // Check if a template exists, then it will not be changed! + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( pRTFStyle->sName, SfxStyleFamily::All )); + if ( pStyle ) + return pStyle; + + OUString aName( pRTFStyle->sName ); + OUString aParent; + if ( pRTFStyle->nBasedOn ) + { + SvxRTFStyleTbl::iterator it = GetStyleTbl().find( pRTFStyle->nBasedOn ); + if ( it != GetStyleTbl().end()) + { + SvxRTFStyleType const& rS = it->second; + if ( &rS != pRTFStyle ) + aParent = rS.sName; + } + } + + pStyle = static_cast<SfxStyleSheet*>( &mpEditEngine->GetStyleSheetPool()->Make( aName, SfxStyleFamily::Para ) ); + + // 1) convert and take over Items ... + ConvertAndPutItems( pStyle->GetItemSet(), pRTFStyle->aAttrSet ); + + // 2) As long as Parent is not in the pool, also create this ... + if ( !aParent.isEmpty() && ( aParent != aName ) ) + { + SfxStyleSheet* pS = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( aParent, SfxStyleFamily::All )); + if ( !pS ) + { + // If not found anywhere, create from RTF ... + SvxRTFStyleType* _pRTFStyle = FindStyleSheet( aParent ); + if ( _pRTFStyle ) + pS = CreateStyleSheet( _pRTFStyle ); + } + // 2b) Link Itemset with Parent ... + if ( pS ) + pStyle->GetItemSet().SetParent( &pS->GetItemSet() ); + } + return pStyle; +} + +void EditRTFParser::CreateStyleSheets() +{ + // the SvxRTFParser has now created the template... + if (mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet()) + { + for (auto & elem : GetStyleTbl()) + { + SvxRTFStyleType& rRTFStyle = elem.second; + CreateStyleSheet( &rRTFStyle ); + } + } +} + +void EditRTFParser::CalcValue() +{ + const MapUnit eDestUnit = aEditMapMode.GetMapUnit(); + if (eDestUnit != gRTFMapUnit) + nTokenValue = OutputDevice::LogicToLogic( nTokenValue, gRTFMapUnit, eDestUnit ); +} + +void EditRTFParser::ReadField() +{ + // From SwRTFParser::ReadField() + int _nOpenBrackets = 1; // the first was already detected earlier + bool bFldInst = false; + bool bFldRslt = false; + OUString aFldInst; + OUString aFldRslt; + + while( _nOpenBrackets && IsParserWorking() ) + { + switch( GetNextToken() ) + { + case '}': + { + _nOpenBrackets--; + if ( _nOpenBrackets == 1 ) + { + bFldInst = false; + bFldRslt = false; + } + } + break; + + case '{': _nOpenBrackets++; + break; + + case RTF_FIELD: SkipGroup(); + break; + + case RTF_FLDINST: bFldInst = true; + break; + + case RTF_FLDRSLT: bFldRslt = true; + break; + + case RTF_TEXTTOKEN: + { + if ( bFldInst ) + aFldInst += aToken; + else if ( bFldRslt ) + aFldRslt += aToken; + } + break; + } + } + if ( !aFldInst.isEmpty() ) + { + OUString aHyperLinkMarker( "HYPERLINK " ); + if ( aFldInst.startsWithIgnoreAsciiCase( aHyperLinkMarker ) ) + { + aFldInst = aFldInst.copy( aHyperLinkMarker.getLength() ); + aFldInst = comphelper::string::strip(aFldInst, ' '); + // strip start and end quotes + aFldInst = aFldInst.copy( 1, aFldInst.getLength()-2 ); + + if ( aFldRslt.isEmpty() ) + aFldRslt = aFldInst; + + SvxFieldItem aField( SvxURLField( aFldInst, aFldRslt, SvxURLFormat::Repr ), EE_FEATURE_FIELD ); + aCurSel = mpEditEngine->InsertField(aCurSel, aField); + mpEditEngine->UpdateFieldsOnly(); + bLastActionInsertParaBreak = false; + } + } + + SkipToken(); // the closing brace is evaluated "above" +} + +void EditRTFParser::SkipGroup() +{ + int _nOpenBrackets = 1; // the first was already detected earlier + + while( _nOpenBrackets && IsParserWorking() ) + { + switch( GetNextToken() ) + { + case '}': + { + _nOpenBrackets--; + } + break; + + case '{': + { + _nOpenBrackets++; + } + break; + } + } + + SkipToken(); // the closing brace is evaluated "above" +} + +EditNodeIdx::EditNodeIdx(EditEngine* pEE, ContentNode* pNd) : + mpEditEngine(pEE), mpNode(pNd) {} + +sal_Int32 EditNodeIdx::GetIdx() const +{ + return mpEditEngine->GetEditDoc().GetPos(mpNode); +} + +EditPosition::EditPosition(EditEngine* pEE, EditSelection* pSel) : + mpEditEngine(pEE), mpCurSel(pSel) {} + +EditNodeIdx EditPosition::MakeNodeIdx() const +{ + return EditNodeIdx(mpEditEngine, mpCurSel->Max().GetNode()); +} + +sal_Int32 EditPosition::GetNodeIdx() const +{ + ContentNode* pN = mpCurSel->Max().GetNode(); + return mpEditEngine->GetEditDoc().GetPos(pN); +} + +sal_Int32 EditPosition::GetCntIdx() const +{ + return mpCurSel->Max().GetIndex(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/eertfpar.hxx b/editeng/source/editeng/eertfpar.hxx new file mode 100644 index 0000000000..19ce00ce32 --- /dev/null +++ b/editeng/source/editeng/eertfpar.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ +#pragma once + +#include <editeng/svxrtf.hxx> + +#include <editdoc.hxx> + +class EditEngine; + +class EditRTFParser final : public SvxRTFParser +{ +private: + EditSelection aCurSel; + EditEngine* mpEditEngine; + MapMode aEditMapMode; + + sal_uInt16 nDefFont; + bool bLastActionInsertParaBreak; + + virtual void InsertPara() override; + virtual void InsertText() override; + virtual void MovePos( bool bForward = true ) override; + virtual void SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos, + sal_Int32& rCntPos ) override; + + virtual void UnknownAttrToken( int nToken ) override; + virtual void NextToken( int nToken ) override; + virtual void SetAttrInDoc( SvxRTFItemStackType &rSet ) override; + virtual bool IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const override; + virtual void CalcValue() override; + void CreateStyleSheets(); + SfxStyleSheet* CreateStyleSheet( SvxRTFStyleType const * pRTFStyle ); + SvxRTFStyleType* FindStyleSheet( std::u16string_view rName ); + void AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd ); + void ReadField(); + void SkipGroup(); + +public: + EditRTFParser(SvStream& rIn, EditSelection aCurSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine); + virtual ~EditRTFParser() override; + + virtual SvParserState CallParser() override; + + EditPaM const & GetCurPaM() const { return aCurSel.Max(); } +}; + +typedef tools::SvRef<EditRTFParser> EditRTFParserRef; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/fieldupdater.cxx b/editeng/source/editeng/fieldupdater.cxx new file mode 100644 index 0000000000..05eca45755 --- /dev/null +++ b/editeng/source/editeng/fieldupdater.cxx @@ -0,0 +1,70 @@ +/* -*- 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/. + */ + +#include <memory> +#include <editeng/fieldupdater.hxx> +#include <editeng/flditem.hxx> +#include "editobj2.hxx" + +#include <com/sun/star/text/textfield/Type.hpp> + +using namespace com::sun::star; + +namespace editeng { + +class FieldUpdaterImpl +{ + EditTextObjectImpl& mrObj; +public: + explicit FieldUpdaterImpl(EditTextObject& rObj) : mrObj(toImpl(rObj)) {} + + void updateTableFields(int nTab) + { + SfxItemPool* pPool = mrObj.GetPool(); + EditTextObjectImpl::ContentInfosType& rContents = mrObj.GetContents(); + for (std::unique_ptr<ContentInfo> & i : rContents) + { + ContentInfo& rContent = *i; + for (XEditAttribute & rAttr : rContent.GetCharAttribs()) + { + const SfxPoolItem* pItem = rAttr.GetItem(); + if (pItem->Which() != EE_FEATURE_FIELD) + // This is not a field item. + continue; + + const SvxFieldItem* pFI = static_cast<const SvxFieldItem*>(pItem); + const SvxFieldData* pData = pFI->GetField(); + if (pData->GetClassId() != text::textfield::Type::TABLE) + // This is not a table field. + continue; + + // Create a new table field with the new ID, and set it to the + // attribute object. + SvxFieldItem aNewItem(SvxTableField(nTab), EE_FEATURE_FIELD); + rAttr.SetItem(*pPool, aNewItem); + } + } + } +}; + +FieldUpdater::FieldUpdater(EditTextObject& rObj) : mpImpl(new FieldUpdaterImpl(rObj)) {} +FieldUpdater::FieldUpdater(const FieldUpdater& r) : mpImpl(new FieldUpdaterImpl(*r.mpImpl)) {} + +FieldUpdater::~FieldUpdater() +{ +} + +void FieldUpdater::updateTableFields(int nTab) +{ + mpImpl->updateTableFields(nTab); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit.cxx b/editeng/source/editeng/impedit.cxx new file mode 100644 index 0000000000..92fb5affa6 --- /dev/null +++ b/editeng/source/editeng/impedit.cxx @@ -0,0 +1,2787 @@ +/* -*- 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 "impedit.hxx" +#include <sal/log.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/outliner.hxx> +#include <editeng/urlfieldhelper.hxx> +#include <tools/poly.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XDictionary.hpp> +#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp> +#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp> +#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <comphelper/lok.hxx> +#include <editeng/flditem.hxx> +#include <svl/intitem.hxx> +#include <vcl/inputctx.hxx> +#include <vcl/transfer.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> +#include <vcl/window.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/string.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/lokhelper.hxx> +#include <boost/property_tree/ptree.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + +#define SCRLRANGE 20 // Scroll 1/20 of the width/height, when in QueryDrop + +static void lcl_AlignToPixel(Point& rPoint, const OutputDevice& rOutDev, short nDiffX, short nDiffY) +{ + rPoint = rOutDev.LogicToPixel( rPoint ); + + if ( nDiffX ) + rPoint.AdjustX(nDiffX ); + if ( nDiffY ) + rPoint.AdjustY(nDiffY ); + + rPoint = rOutDev.PixelToLogic( rPoint ); +} + +LOKSpecialPositioning::LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) : + mrImpEditView(rImpEditView), + maOutArea(rOutputArea), + maVisDocStartPos(rVisDocStartPos), + meUnit(eUnit), + meFlags(LOKSpecialFlags::NONE) +{ +} + +void LOKSpecialPositioning::ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos) +{ + meUnit = eUnit; + maOutArea = rOutputArea; + maVisDocStartPos = rVisDocStartPos; +} + +void LOKSpecialPositioning::SetOutputArea(const tools::Rectangle& rOutputArea) +{ + maOutArea = rOutputArea; +} + +const tools::Rectangle& LOKSpecialPositioning::GetOutputArea() const +{ + return maOutArea; +} + +void LOKSpecialPositioning::SetVisDocStartPos(const Point& rVisDocStartPos) +{ + maVisDocStartPos = rVisDocStartPos; +} + +tools::Rectangle LOKSpecialPositioning::GetVisDocArea() const +{ + return tools::Rectangle(GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom()); +} + +bool LOKSpecialPositioning::IsVertical() const +{ + return mrImpEditView.IsVertical(); +} + +bool LOKSpecialPositioning::IsTopToBottom() const +{ + return mrImpEditView.IsTopToBottom(); +} + +Point LOKSpecialPositioning::GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const +{ + const Point aDocPos = convertUnit(rDocPos, eDocPosUnit); + Point aPoint; + if ( !IsVertical() ) + { + aPoint.setX(aDocPos.X() + maOutArea.Left() - GetVisDocLeft()); + aPoint.setY(aDocPos.Y() + maOutArea.Top() - GetVisDocTop()); + } + else + { + if (IsTopToBottom()) + { + aPoint.setX(maOutArea.Right() - aDocPos.Y() + GetVisDocTop()); + aPoint.setY(aDocPos.X() + maOutArea.Top() - GetVisDocLeft()); + } + else + { + aPoint.setX(maOutArea.Left() + aDocPos.Y() - GetVisDocTop()); + aPoint.setY(maOutArea.Bottom() - aDocPos.X() + GetVisDocLeft()); + } + } + + return aPoint; +} + +tools::Rectangle LOKSpecialPositioning::GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const +{ + const tools::Rectangle aDocRect = convertUnit(rDocRect, eDocRectUnit); + Point aPos(GetWindowPos(aDocRect.TopLeft(), meUnit)); + Size aSz = aDocRect.GetSize(); + tools::Rectangle aRect; + if (!IsVertical()) + { + aRect = tools::Rectangle(aPos, aSz); + } + else + { + Point aNewPos(aPos.X() - aSz.Height(), aPos.Y()); + // coverity[swapped_arguments : FALSE] - this is in the correct order + aRect = tools::Rectangle(aNewPos, Size(aSz.Height(), aSz.Width())); + } + return aRect; +} + +Point LOKSpecialPositioning::convertUnit(const Point& rPos, MapUnit ePosUnit) const +{ + if (ePosUnit == meUnit) + return rPos; + + return OutputDevice::LogicToLogic(rPos, MapMode(ePosUnit), MapMode(meUnit)); +} + +tools::Rectangle LOKSpecialPositioning::convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const +{ + if (eRectUnit == meUnit) + return rRect; + + return OutputDevice::LogicToLogic(rRect, MapMode(eRectUnit), MapMode(meUnit)); +} + +Point LOKSpecialPositioning::GetRefPoint() const +{ + return maOutArea.TopLeft(); +} + +// class ImpEditView + +ImpEditView::ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ) : + pEditView(pView), + mpViewShell(nullptr), + mpOtherShell(nullptr), + pEditEngine(pEng), + pOutWin(pWindow), + nInvMore(1), + nControl(EVControlBits::AUTOSCROLL | EVControlBits::ENABLEPASTE), + nTravelXPos(TRAVEL_X_DONTKNOW), + nExtraCursorFlags(GetCursorFlags::NONE), + nCursorBidiLevel(CURSOR_BIDILEVEL_DONTKNOW), + nScrollDiffX(0), + bReadOnly(false), + bClickedInSelection(false), + bActiveDragAndDropListener(false), + aOutArea( Point(), pEng->GetPaperSize() ), + eSelectionMode(EESelectionMode::Std), + eAnchorMode(EEAnchorMode::TopLeft), + mpEditViewCallbacks(nullptr), + mbBroadcastLOKViewCursor(comphelper::LibreOfficeKit::isActive()), + mbSuppressLOKMessages(false), + mbNegativeX(false) +{ + aEditSelection.Min() = pEng->GetEditDoc().GetStartPaM(); + aEditSelection.Max() = pEng->GetEditDoc().GetEndPaM(); + + SelectionChanged(); +} + +ImpEditView::~ImpEditView() +{ + RemoveDragAndDropListeners(); + + if ( pOutWin && ( pOutWin->GetCursor() == pCursor.get() ) ) + pOutWin->SetCursor( nullptr ); +} + +void ImpEditView::SetBackgroundColor( const Color& rColor ) +{ + mxBackgroundColor = rColor; +} + +const Color& ImpEditView::GetBackgroundColor() const +{ + return mxBackgroundColor ? *mxBackgroundColor : GetOutputDevice().GetBackground().GetColor(); +} + +void ImpEditView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + mpViewShell = pViewShell; +} + +void ImpEditView::RegisterOtherShell(OutlinerViewShell* pOtherShell) +{ + mpOtherShell = pOtherShell; +} + +const OutlinerViewShell* ImpEditView::GetViewShell() const +{ + return mpViewShell; +} + +void ImpEditView::SetEditSelection( const EditSelection& rEditSelection ) +{ + // set state before notification + aEditSelection = rEditSelection; + + SelectionChanged(); + + if (comphelper::LibreOfficeKit::isActive()) + // Tiled rendering: selections are only painted when we are in selection mode. + pEditEngine->SetInSelectionMode(aEditSelection.HasRange()); + + if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) + { + const EditDoc& rDoc = pEditEngine->GetEditDoc(); + const EditPaM pmEnd = rDoc.GetEndPaM(); + EENotifyType eNotifyType; + if (rDoc.Count() > 1 && + pmEnd == rEditSelection.Min() && + pmEnd == rEditSelection.Max())//if move cursor to the last para. + { + eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA; + } + else + { + eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED; + } + EENotify aNotify( eNotifyType ); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + if(pEditEngine->pImpEditEngine->IsFormatted()) + { + EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call(aNotify); + } +} + +/// Translate absolute <-> relative twips: LOK wants absolute coordinates as output and gives absolute coordinates as input. +static void lcl_translateTwips(const OutputDevice& rParent, OutputDevice& rChild) +{ + // Don't translate if we already have a non-zero origin. + // This prevents multiple translate calls that negate + // one another. + const Point aOrigin = rChild.GetMapMode().GetOrigin(); + if (aOrigin.getX() != 0 || aOrigin.getY() != 0) + return; + + // Set map mode, so that callback payloads will contain absolute coordinates instead of relative ones. + Point aOffset(rChild.GetOutOffXPixel() - rParent.GetOutOffXPixel(), rChild.GetOutOffYPixel() - rParent.GetOutOffYPixel()); + if (!rChild.IsMapModeEnabled()) + { + MapMode aMapMode(rChild.GetMapMode()); + aMapMode.SetMapUnit(MapUnit::MapTwip); + aMapMode.SetScaleX(rParent.GetMapMode().GetScaleX()); + aMapMode.SetScaleY(rParent.GetMapMode().GetScaleY()); + rChild.SetMapMode(aMapMode); + rChild.EnableMapMode(); + } + aOffset = rChild.PixelToLogic(aOffset); + MapMode aMapMode(rChild.GetMapMode()); + aMapMode.SetOrigin(aOffset); + aMapMode.SetMapUnit(rParent.GetMapMode().GetMapUnit()); + rChild.SetMapMode(aMapMode); + rChild.EnableMapMode(false); +} + +// EditView never had a central/secure place to react on SelectionChange since +// Selection was changed in many places, often by not using SetEditSelection() +// but (mis)using GetEditSelection() and manipulating this non-const return +// value. Sorted this out now to have such a place, this is needed for safely +// change/update the Selection visualization for enhanced mechanisms +void ImpEditView::SelectionChanged() +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + // use callback to tell about change in selection visualisation + pCallbacks->EditViewSelectionChange(); + } +} + +// This function is also called when a text's font || size is changed. Because its highlight rectangle must be updated. +void ImpEditView::lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible) { + VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier(); + vcl::Region aRegion( *pPolyPoly ); + + if (pParent && pParent->GetLOKWindowId() != 0) + { + const tools::Long nX = pOutWin->GetOutOffXPixel() - pParent->GetOutOffXPixel(); + const tools::Long nY = pOutWin->GetOutOffYPixel() - pParent->GetOutOffYPixel(); + + std::vector<tools::Rectangle> aRectangles; + aRegion.GetRegionRectangles(aRectangles); + + std::vector<OString> v; + for (tools::Rectangle & rRectangle : aRectangles) + { + rRectangle = pOutWin->LogicToPixel(rRectangle); + rRectangle.Move(nX, nY); + v.emplace_back(rRectangle.toString().getStr()); + } + OString sRectangle = comphelper::string::join("; ", v); + + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("rectangles", sRectangle); + aItems.emplace_back("startHandleVisible", OString::boolean(bStartHandleVisible)); + aItems.emplace_back("endHandleVisible", OString::boolean(bEndHandleVisible)); + pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems); + } + else if (mpViewShell) + { + pOutWin->GetOutDev()->Push(vcl::PushFlags::MAPMODE); + if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) + { + // Find the parent that is not right + // on top of us to use its offset. + vcl::Window* parent = pOutWin->GetParent(); + while (parent && + parent->GetOutOffXPixel() == pOutWin->GetOutOffXPixel() && + parent->GetOutOffYPixel() == pOutWin->GetOutOffYPixel()) + { + parent = parent->GetParent(); + } + + if (parent) + { + lcl_translateTwips(*parent->GetOutDev(), *pOutWin->GetOutDev()); + } + } + + bool bMm100ToTwip = !mpLOKSpecialPositioning && + (pOutWin->GetMapMode().GetMapUnit() == MapUnit::Map100thMM); + + Point aOrigin; + if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) + // Writer comments: they use editeng, but are separate widgets. + aOrigin = pOutWin->GetMapMode().GetOrigin(); + + OString sRectangle; + OString sRefPoint; + if (mpLOKSpecialPositioning) + sRefPoint = mpLOKSpecialPositioning->GetRefPoint().toString(); + + std::vector<tools::Rectangle> aRectangles; + aRegion.GetRegionRectangles(aRectangles); + + if (!aRectangles.empty()) + { + if (pOutWin->IsChart()) + { + const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) + { + Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); + for (tools::Rectangle& rRect : aRectangles) + rRect.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + + std::vector<OString> v; + for (tools::Rectangle & rRectangle : aRectangles) + { + if (bMm100ToTwip) + { + rRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + rRectangle.Move(aOrigin.getX(), aOrigin.getY()); + v.emplace_back(rRectangle.toString().getStr()); + } + sRectangle = comphelper::string::join("; ", v); + + if (mpLOKSpecialPositioning && !sRectangle.isEmpty()) + sRectangle += ":: " + sRefPoint; + + tools::Rectangle& rStart = aRectangles.front(); + tools::Rectangle aStart(rStart.Left(), rStart.Top(), rStart.Left() + 1, rStart.Bottom()); + + OString aPayload = aStart.toString(); + if (mpLOKSpecialPositioning) + aPayload += ":: " + sRefPoint; + + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, aPayload); + + tools::Rectangle& rEnd = aRectangles.back(); + tools::Rectangle aEnd(rEnd.Right() - 1, rEnd.Top(), rEnd.Right(), rEnd.Bottom()); + + aPayload = aEnd.toString(); + if (mpLOKSpecialPositioning) + aPayload += ":: " + sRefPoint; + + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, aPayload); + } + + if (mpOtherShell) + { + // Another shell wants to know about our existing selection. + if (mpViewShell != mpOtherShell) + mpViewShell->NotifyOtherView(mpOtherShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, sRectangle); + } + else + { + mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangle); + mpViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, sRectangle); + } + + pOutWin->GetOutDev()->Pop(); + } +} + +// renamed from DrawSelection to DrawSelectionXOR to better reflect what this +// method was used for: Paint Selection in XOR, change it and again paint it in XOR. +// This can be safely assumed due to the EditView only being capable of painting the +// selection in XOR until today. +// This also means that all places calling DrawSelectionXOR are thoroughly weighted +// and chosen to make this fragile XOR-paint water-proof and thus contain some +// information in this sense. +// Someone thankfully expanded it to collect the SelectionRectangles when called with +// the Region*, see GetSelectionRectangles below. +void ImpEditView::DrawSelectionXOR( EditSelection aTmpSel, vcl::Region* pRegion, OutputDevice* pTargetDevice ) +{ + if (getEditViewCallbacks() && !pRegion && !comphelper::LibreOfficeKit::isActive()) + { + // we are done, do *not* visualize self + // CAUTION: do not use when comphelper::LibreOfficeKit::isActive() + // due to event stuff triggered below. That *should* probably be moved + // to SelectionChanged() which exists now, but I do not know enough about + // that stuff to do it + return; + } + + if ( eSelectionMode == EESelectionMode::Hidden ) + return; + + // It must be ensured before rendering the selection, that the contents of + // the window is completely valid! Must be here so that in any case if + // empty, then later on two-Paint Events! Must be done even before the + // query from bUpdate, if after Invalidate paints still in the queue, + // but someone switches the update mode! + + // pRegion: When not NULL, then only calculate Region. + + OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : GetOutputDevice(); + bool bClipRegion = rTarget.IsClipRegion(); + vcl::Region aOldRegion = rTarget.GetClipRegion(); + + std::optional<tools::PolyPolygon> pPolyPoly; + + if ( !pRegion && !comphelper::LibreOfficeKit::isActive()) + { + if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() ) + return; + if ( pEditEngine->pImpEditEngine->IsInUndo() ) + return; + + if ( !aTmpSel.HasRange() ) + return; + + // aTmpOutArea: if OutputArea > Paper width and + // Text > Paper width ( over large fields ) + tools::Rectangle aTmpOutArea( aOutArea ); + if ( aTmpOutArea.GetWidth() > pEditEngine->pImpEditEngine->GetPaperSize().Width() ) + aTmpOutArea.SetRight( aTmpOutArea.Left() + pEditEngine->pImpEditEngine->GetPaperSize().Width() ); + rTarget.IntersectClipRegion( aTmpOutArea ); + + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Hide(); + } + + if (comphelper::LibreOfficeKit::isActive() || pRegion) + pPolyPoly = tools::PolyPolygon(); + + DBG_ASSERT( !pEditEngine->IsIdleFormatterActive(), "DrawSelectionXOR: Not formatted!" ); + aTmpSel.Adjust( pEditEngine->GetEditDoc() ); + + ContentNode* pStartNode = aTmpSel.Min().GetNode(); + ContentNode* pEndNode = aTmpSel.Max().GetNode(); + const sal_Int32 nStartPara = pEditEngine->GetEditDoc().GetPos(pStartNode); + const sal_Int32 nEndPara = pEditEngine->GetEditDoc().GetPos(pEndNode); + if (nStartPara == EE_PARA_NOT_FOUND || nEndPara == EE_PARA_NOT_FOUND) + return; + + bool bStartHandleVisible = false; + bool bEndHandleVisible = false; + bool bLOKCalcRTL = mpLOKSpecialPositioning && + (mpLOKSpecialPositioning->IsLayoutRTL() || pEditEngine->IsRightToLeft(nStartPara)); + + auto DrawHighlight = [&, nStartLine = sal_Int32(0), nEndLine = sal_Int32(0)]( + const ImpEditEngine::LineAreaInfo& rInfo) mutable { + if (!rInfo.pLine) // Begin of ParaPortion + { + if (rInfo.nPortion < nStartPara) + return ImpEditEngine::CallbackResult::SkipThisPortion; + if (rInfo.nPortion > nEndPara) + return ImpEditEngine::CallbackResult::Stop; + DBG_ASSERT(!rInfo.rPortion.IsInvalid(), "Portion in Selection not formatted!"); + if (rInfo.rPortion.IsInvalid()) + return ImpEditEngine::CallbackResult::SkipThisPortion; + + if (rInfo.nPortion == nStartPara) + nStartLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Min().GetIndex(), false); + else + nStartLine = 0; + + if (rInfo.nPortion == nEndPara) + nEndLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Max().GetIndex(), true); + else + nEndLine = rInfo.rPortion.GetLines().Count() - 1; + } + else // This is a correct ParaPortion + { + if (rInfo.nLine < nStartLine) + return ImpEditEngine::CallbackResult::Continue; + if (rInfo.nLine > nEndLine) + return ImpEditEngine::CallbackResult::SkipThisPortion; + + bool bPartOfLine = false; + sal_Int32 nStartIndex = rInfo.pLine->GetStart(); + sal_Int32 nEndIndex = rInfo.pLine->GetEnd(); + if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine) + && (nStartIndex != aTmpSel.Min().GetIndex())) + { + nStartIndex = aTmpSel.Min().GetIndex(); + bPartOfLine = true; + } + if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine) + && (nEndIndex != aTmpSel.Max().GetIndex())) + { + nEndIndex = aTmpSel.Max().GetIndex(); + bPartOfLine = true; + } + + // Can happen if at the beginning of a wrapped line. + if (nEndIndex < nStartIndex) + nEndIndex = nStartIndex; + + tools::Rectangle aTmpRect(pEditEngine->pImpEditEngine->GetEditCursor( + &rInfo.rPortion, rInfo.pLine, nStartIndex, GetCursorFlags::NONE)); + const Size aLineOffset = pEditEngine->pImpEditEngine->getTopLeftDocOffset(rInfo.aArea); + aTmpRect.Move(0, aLineOffset.Height()); + + // Only paint if in the visible range ... + if (aTmpRect.Top() > GetVisDocBottom()) + return ImpEditEngine::CallbackResult::Continue; + + if (aTmpRect.Bottom() < GetVisDocTop()) + return ImpEditEngine::CallbackResult::Continue; + + if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine)) + bStartHandleVisible = true; + if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine)) + bEndHandleVisible = true; + + // Now that we have Bidi, the first/last index doesn't have to be the 'most outside' position + if (!bPartOfLine) + { + Range aLineXPosStartEnd + = pEditEngine->GetLineXPosStartEnd(&rInfo.rPortion, rInfo.pLine); + aTmpRect.SetLeft(aLineXPosStartEnd.Min()); + aTmpRect.SetRight(aLineXPosStartEnd.Max()); + aTmpRect.Move(aLineOffset.Width(), 0); + ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), + pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL); + } + else + { + sal_Int32 nTmpStartIndex = nStartIndex; + sal_Int32 nWritingDirStart, nTmpEndIndex; + + while (nTmpStartIndex < nEndIndex) + { + pEditEngine->pImpEditEngine->GetRightToLeft(rInfo.nPortion, nTmpStartIndex + 1, + &nWritingDirStart, &nTmpEndIndex); + if (nTmpEndIndex > nEndIndex) + nTmpEndIndex = nEndIndex; + + DBG_ASSERT(nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?"); + + tools::Long nX1 + = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpStartIndex, true); + tools::Long nX2 + = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpEndIndex); + + aTmpRect.SetLeft(std::min(nX1, nX2)); + aTmpRect.SetRight(std::max(nX1, nX2)); + aTmpRect.Move(aLineOffset.Width(), 0); + + ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), + pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL); + nTmpStartIndex = nTmpEndIndex; + } + } + } + return ImpEditEngine::CallbackResult::Continue; + }; + pEditEngine->pImpEditEngine->IterateLineAreas(DrawHighlight, ImpEditEngine::IterFlag::none); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin) + lokSelectionCallback(pPolyPoly, bStartHandleVisible, bEndHandleVisible); + + if (pRegion || comphelper::LibreOfficeKit::isActive()) + { + if (pRegion) + *pRegion = vcl::Region( *pPolyPoly ); + pPolyPoly.reset(); + } + else + { + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Show(); + + if (bClipRegion) + rTarget.SetClipRegion(aOldRegion); + else + rTarget.SetClipRegion(); + } +} + +void ImpEditView::GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects) +{ + vcl::Region aRegion; + DrawSelectionXOR(aTmpSel, &aRegion); + aRegion.GetRegionRectangles(rLogicRects); +} + +void ImpEditView::ImplDrawHighlightRect( OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL ) +{ + if ( rDocPosTopLeft.X() == rDocPosBottomRight.X() ) + return; + + if (mpLOKSpecialPositioning && pPolyPoly) + { + MapUnit eDevUnit = rTarget.GetMapMode().GetMapUnit(); + tools::Rectangle aSelRect(rDocPosTopLeft, rDocPosBottomRight); + aSelRect = GetWindowPos(aSelRect); + Point aRefPointLogical = GetOutputArea().TopLeft(); + // Get the relative coordinates w.r.t refpoint in display units. + aSelRect.Move(-aRefPointLogical.X(), -aRefPointLogical.Y()); + if (bLOKCalcRTL) + { + tools::Long nMirrorW = GetOutputArea().GetWidth(); + tools::Long nLeft = aSelRect.Left(), nRight = aSelRect.Right(); + aSelRect.SetLeft(nMirrorW - nRight); + aSelRect.SetRight(nMirrorW - nLeft); + } + // Convert from display unit to twips. + aSelRect = OutputDevice::LogicToLogic(aSelRect, MapMode(eDevUnit), MapMode(MapUnit::MapTwip)); + + tools::Polygon aTmpPoly(4); + aTmpPoly[0] = aSelRect.TopLeft(); + aTmpPoly[1] = aSelRect.TopRight(); + aTmpPoly[2] = aSelRect.BottomRight(); + aTmpPoly[3] = aSelRect.BottomLeft(); + pPolyPoly->Insert(aTmpPoly); + return; + } + + bool bPixelMode = rTarget.GetMapMode().GetMapUnit() == MapUnit::MapPixel; + + Point aPnt1( GetWindowPos( rDocPosTopLeft ) ); + Point aPnt2( GetWindowPos( rDocPosBottomRight ) ); + + if ( !IsVertical() ) + { + lcl_AlignToPixel(aPnt1, rTarget, +1, 0); + lcl_AlignToPixel(aPnt2, rTarget, 0, (bPixelMode ? 0 : -1)); + } + else + { + lcl_AlignToPixel(aPnt1, rTarget, 0, +1 ); + lcl_AlignToPixel(aPnt2, rTarget, (bPixelMode ? 0 : +1), 0); + } + + tools::Rectangle aRect( aPnt1, aPnt2 ); + if ( pPolyPoly ) + { + tools::Polygon aTmpPoly( 4 ); + aTmpPoly[0] = aRect.TopLeft(); + aTmpPoly[1] = aRect.TopRight(); + aTmpPoly[2] = aRect.BottomRight(); + aTmpPoly[3] = aRect.BottomLeft(); + pPolyPoly->Insert( aTmpPoly ); + } + else + { + vcl::Window* pWindow = rTarget.GetOwnerWindow(); + + if (pWindow) + { + pWindow->GetOutDev()->Invert( aRect ); + } + else + { + rTarget.Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR|vcl::PushFlags::RASTEROP); + rTarget.SetLineColor(); + rTarget.SetFillColor(COL_BLACK); + rTarget.SetRasterOp(RasterOp::Invert); + rTarget.DrawRect(aRect); + rTarget.Pop(); + } + } +} + + +bool ImpEditView::IsVertical() const +{ + return pEditEngine->pImpEditEngine->IsEffectivelyVertical(); +} + +bool ImpEditView::IsTopToBottom() const +{ + return pEditEngine->pImpEditEngine->IsTopToBottom(); +} + +tools::Rectangle ImpEditView::GetVisDocArea() const +{ + return tools::Rectangle( GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom() ); +} + +Point ImpEditView::GetDocPos( const Point& rWindowPos ) const +{ + // Window Position => Position Document + Point aPoint; + + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aPoint.setX( rWindowPos.X() - aOutArea.Left() + GetVisDocLeft() ); + aPoint.setY( rWindowPos.Y() - aOutArea.Top() + GetVisDocTop() ); + } + else + { + if (pEditEngine->pImpEditEngine->IsTopToBottom()) + { + aPoint.setX( rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft() ); + aPoint.setY( aOutArea.Right() - rWindowPos.X() + GetVisDocTop() ); + } + else + { + aPoint.setX( aOutArea.Bottom() - rWindowPos.Y() + GetVisDocLeft() ); + aPoint.setY( rWindowPos.X() - aOutArea.Left() + GetVisDocTop() ); + } + } + + return aPoint; +} + +Point ImpEditView::GetWindowPos( const Point& rDocPos ) const +{ + // Document position => window position + Point aPoint; + + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aPoint.setX( rDocPos.X() + aOutArea.Left() - GetVisDocLeft() ); + aPoint.setY( rDocPos.Y() + aOutArea.Top() - GetVisDocTop() ); + } + else + { + if (pEditEngine->pImpEditEngine->IsTopToBottom()) + { + aPoint.setX( aOutArea.Right() - rDocPos.Y() + GetVisDocTop() ); + aPoint.setY( rDocPos.X() + aOutArea.Top() - GetVisDocLeft() ); + } + else + { + aPoint.setX( aOutArea.Left() + rDocPos.Y() - GetVisDocTop() ); + aPoint.setY( aOutArea.Bottom() - rDocPos.X() + GetVisDocLeft() ); + } + } + + return aPoint; +} + +tools::Rectangle ImpEditView::GetWindowPos( const tools::Rectangle& rDocRect ) const +{ + // Document position => window position + Point aPos( GetWindowPos( rDocRect.TopLeft() ) ); + Size aSz = rDocRect.GetSize(); + tools::Rectangle aRect; + if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() ) + { + aRect = tools::Rectangle( aPos, aSz ); + } + else + { + Point aNewPos( aPos.X()-aSz.Height(), aPos.Y() ); + // coverity[swapped_arguments : FALSE] - this is in the correct order + aRect = tools::Rectangle( aNewPos, Size( aSz.Height(), aSz.Width() ) ); + } + return aRect; +} + +void ImpEditView::SetSelectionMode( EESelectionMode eNewMode ) +{ + if ( eSelectionMode != eNewMode ) + { + DrawSelectionXOR(); + eSelectionMode = eNewMode; + DrawSelectionXOR(); // redraw + } +} + +OutputDevice& ImpEditView::GetOutputDevice() const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + return pCallbacks->EditViewOutputDevice(); + return *pOutWin->GetOutDev(); +} + +weld::Widget* ImpEditView::GetPopupParent(tools::Rectangle& rRect) const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + weld::Widget* pParent = pCallbacks->EditViewPopupParent(); + if (pParent) + return pParent; + } + return weld::GetPopupParent(*pOutWin, rRect); +} + +void ImpEditView::SetOutputArea( const tools::Rectangle& rRect ) +{ + const OutputDevice& rOutDev = GetOutputDevice(); + // should be better be aligned on pixels! + tools::Rectangle aNewRect(rOutDev.LogicToPixel(rRect)); + aNewRect = rOutDev.PixelToLogic(aNewRect); + aOutArea = aNewRect; + if ( !aOutArea.IsWidthEmpty() && aOutArea.Right() < aOutArea.Left() ) + aOutArea.SetRight( aOutArea.Left() ); + if ( !aOutArea.IsHeightEmpty() && aOutArea.Bottom() < aOutArea.Top() ) + aOutArea.SetBottom( aOutArea.Top() ); + + SetScrollDiffX( static_cast<sal_uInt16>(aOutArea.GetWidth()) * 2 / 10 ); +} + +namespace { + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()); +} + +} + +void ImpEditView::InvalidateAtWindow(const tools::Rectangle& rRect) +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + { + // do not invalidate and trigger a global repaint, but forward + // the need for change to the applied EditViewCallback, can e.g. + // be used to visualize the active edit text in an OverlayObject + pCallbacks->EditViewInvalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect); + } + else + { + // classic mode: invalidate and trigger full repaint + // of the changed area + GetWindow()->Invalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect); + } +} + +void ImpEditView::ResetOutputArea( const tools::Rectangle& rRect ) +{ + // remember old out area + const tools::Rectangle aOldArea(aOutArea); + + // apply new one + SetOutputArea(rRect); + + // invalidate surrounding areas if update is true + if(aOldArea.IsEmpty() || !pEditEngine->pImpEditEngine->IsUpdateLayout()) + return; + + // #i119885# use grown area if needed; do when getting bigger OR smaller + const sal_Int32 nMore(DoInvalidateMore() ? GetOutputDevice().PixelToLogic(Size(nInvMore, 0)).Width() : 0); + + if(aOldArea.Left() > aOutArea.Left()) + { + const tools::Rectangle aRect(aOutArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Left(), aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Left() < aOutArea.Left()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOutArea.Left(), aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Right() > aOutArea.Right()) + { + const tools::Rectangle aRect(aOutArea.Right(), aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Right() < aOutArea.Right()) + { + const tools::Rectangle aRect(aOldArea.Right(), aOldArea.Top() - nMore, aOutArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Top() > aOutArea.Top()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Top()); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Top() < aOutArea.Top()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOutArea.Top()); + InvalidateAtWindow(aRect); + } + + if(aOldArea.Bottom() > aOutArea.Bottom()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Bottom(), aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } + else if(aOldArea.Bottom() < aOutArea.Bottom()) + { + const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Bottom(), aOldArea.Right() + nMore, aOutArea.Bottom() + nMore); + InvalidateAtWindow(aRect); + } +} + +void ImpEditView::RecalcOutputArea() +{ + Point aNewTopLeft( aOutArea.TopLeft() ); + Size aNewSz( aOutArea.GetSize() ); + + // X: + if ( DoAutoWidth() ) + { + if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageWidth() ) + aNewSz.setWidth( pEditEngine->pImpEditEngine->GetPaperSize().Width() ); + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::BottomLeft: + { + aNewTopLeft.setX( aAnchorPoint.X() ); + } + break; + case EEAnchorMode::TopHCenter: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::BottomHCenter: + { + aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() / 2 ); + } + break; + case EEAnchorMode::TopRight: + case EEAnchorMode::VCenterRight: + case EEAnchorMode::BottomRight: + { + aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() - 1 ); + } + break; + } + } + + // Y: + if ( DoAutoHeight() ) + { + if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageHeight() ) + aNewSz.setHeight( pEditEngine->pImpEditEngine->GetPaperSize().Height() ); + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::TopHCenter: + case EEAnchorMode::TopRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() ); + } + break; + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::VCenterRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() / 2 ); + } + break; + case EEAnchorMode::BottomLeft: + case EEAnchorMode::BottomHCenter: + case EEAnchorMode::BottomRight: + { + aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() - 1 ); + } + break; + } + } + ResetOutputArea( tools::Rectangle( aNewTopLeft, aNewSz ) ); +} + +void ImpEditView::SetAnchorMode( EEAnchorMode eMode ) +{ + eAnchorMode = eMode; + CalcAnchorPoint(); +} + +void ImpEditView::CalcAnchorPoint() +{ + // GetHeight() and GetWidth() -1, because rectangle calculation not preferred. + + // X: + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::BottomLeft: + { + aAnchorPoint.setX( aOutArea.Left() ); + } + break; + case EEAnchorMode::TopHCenter: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::BottomHCenter: + { + aAnchorPoint.setX( aOutArea.Left() + (aOutArea.GetWidth()-1) / 2 ); + } + break; + case EEAnchorMode::TopRight: + case EEAnchorMode::VCenterRight: + case EEAnchorMode::BottomRight: + { + aAnchorPoint.setX( aOutArea.Right() ); + } + break; + } + + // Y: + switch ( eAnchorMode ) + { + case EEAnchorMode::TopLeft: + case EEAnchorMode::TopHCenter: + case EEAnchorMode::TopRight: + { + aAnchorPoint.setY( aOutArea.Top() ); + } + break; + case EEAnchorMode::VCenterLeft: + case EEAnchorMode::VCenterHCenter: + case EEAnchorMode::VCenterRight: + { + aAnchorPoint.setY( aOutArea.Top() + (aOutArea.GetHeight()-1) / 2 ); + } + break; + case EEAnchorMode::BottomLeft: + case EEAnchorMode::BottomHCenter: + case EEAnchorMode::BottomRight: + { + aAnchorPoint.setY( aOutArea.Bottom() - 1 ); + } + break; + } +} + +namespace +{ + +// For building JSON message to be sent to Online +boost::property_tree::ptree getHyperlinkPropTree(const OUString& sText, const OUString& sLink) +{ + boost::property_tree::ptree aTree; + aTree.put("text", sText); + aTree.put("link", sLink); + return aTree; +} + +} // End of anon namespace + +tools::Rectangle ImpEditView::ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags, sal_Int32& nTextPortionStart, + const ParaPortion* pParaPortion) const +{ + tools::Rectangle aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, nShowCursorFlags ); + if ( !IsInsertMode() && !aEditSelection.HasRange() ) + { + if ( aPaM.GetNode()->Len() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) + { + // If we are behind a portion, and the next portion has other direction, we must change position... + aEditCursor.SetLeft( pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly|GetCursorFlags::PreferPortionStart ).Left() ); + aEditCursor.SetRight( aEditCursor.Left() ); + + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if ( rTextPortion.GetKind() == PortionKind::TAB ) + { + aEditCursor.AdjustRight(rTextPortion.GetSize().Width() ); + } + else + { + EditPaM aNext = pEditEngine->CursorRight( aPaM ); + tools::Rectangle aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly ); + if ( aTmpRect.Top() != aEditCursor.Top() ) + aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine ); + aEditCursor.SetRight( aTmpRect.Left() ); + } + } + } + + tools::Long nMaxHeight = !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth(); + if ( aEditCursor.GetHeight() > nMaxHeight ) + { + aEditCursor.SetBottom( aEditCursor.Top() + nMaxHeight - 1 ); + } + + return aEditCursor; +} + +tools::Rectangle ImpEditView::GetEditCursor() const +{ + EditPaM aPaM( aEditSelection.Max() ); + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if (nPara == EE_PARA_NOT_FOUND) // #i94322 + return tools::Rectangle(); + + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + + GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly; + + // Use CursorBidiLevel 0/1 in meaning of + // 0: prefer portion end, normal mode + // 1: prefer portion start + + if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() ) + { + nShowCursorFlags |= GetCursorFlags::PreferPortionStart; + } + + return ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion); +} + +void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) +{ + // No ShowCursor in an empty View ... + if (aOutArea.IsEmpty()) + return; + if ( ( aOutArea.Left() >= aOutArea.Right() ) && ( aOutArea.Top() >= aOutArea.Bottom() ) ) + return; + + pEditEngine->CheckIdleFormatter(); + if (!pEditEngine->IsFormatted()) + pEditEngine->pImpEditEngine->FormatDoc(); + + // For some reasons I end up here during the formatting, if the Outliner + // is initialized in Paint, because no SetPool(); + if ( pEditEngine->pImpEditEngine->IsFormatting() ) + return; + if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() ) + return; + if ( pEditEngine->pImpEditEngine->IsInUndo() ) + return; + + if (pOutWin && pOutWin->GetCursor() != GetCursor()) + pOutWin->SetCursor(GetCursor()); + + EditPaM aPaM( aEditSelection.Max() ); + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if (nPara == EE_PARA_NOT_FOUND) // #i94322 + return; + + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + + GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly; + + // Use CursorBidiLevel 0/1 in meaning of + // 0: prefer portion end, normal mode + // 1: prefer portion start + + if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() ) + { + nShowCursorFlags |= GetCursorFlags::PreferPortionStart; + } + + tools::Rectangle aEditCursor = ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion); + + if ( bGotoCursor ) // && (!pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) ) + { + // check if scrolling is necessary... + // if scrolling, then update () and Scroll ()! + tools::Long nDocDiffX = 0; + tools::Long nDocDiffY = 0; + + tools::Rectangle aTmpVisArea( GetVisDocArea() ); + // aTmpOutArea: if OutputArea > Paper width and + // Text > Paper width ( over large fields ) + tools::Long nMaxTextWidth = !IsVertical() ? pEditEngine->pImpEditEngine->GetPaperSize().Width() : pEditEngine->pImpEditEngine->GetPaperSize().Height(); + if ( aTmpVisArea.GetWidth() > nMaxTextWidth ) + aTmpVisArea.SetRight( aTmpVisArea.Left() + nMaxTextWidth ); + + if ( aEditCursor.Bottom() > aTmpVisArea.Bottom() ) + { // Scroll up, here positive + nDocDiffY = aEditCursor.Bottom() - aTmpVisArea.Bottom(); + } + else if ( aEditCursor.Top() < aTmpVisArea.Top() ) + { // Scroll down, here negative + nDocDiffY = aEditCursor.Top() - aTmpVisArea.Top(); + } + + if ( aEditCursor.Right() > aTmpVisArea.Right() ) + { + // Scroll left, positive + nDocDiffX = aEditCursor.Right() - aTmpVisArea.Right(); + // Can it be a little more? + if ( aEditCursor.Right() < ( nMaxTextWidth - GetScrollDiffX() ) ) + nDocDiffX += GetScrollDiffX(); + else + { + tools::Long n = nMaxTextWidth - aEditCursor.Right(); + // If MapMode != RefMapMode then the EditCursor can go beyond + // the paper width! + nDocDiffX += ( n > 0 ? n : -n ); + } + } + else if ( aEditCursor.Left() < aTmpVisArea.Left() ) + { + // Scroll right, negative: + nDocDiffX = aEditCursor.Left() - aTmpVisArea.Left(); + // Can it be a little more? + if ( aEditCursor.Left() > ( - static_cast<tools::Long>(GetScrollDiffX()) ) ) + nDocDiffX -= GetScrollDiffX(); + else + nDocDiffX -= aEditCursor.Left(); + } + if ( aPaM.GetIndex() == 0 ) // Olli needed for the Outliner + { + // But make sure that the cursor is not leaving visible area + // because of this! + if ( aEditCursor.Left() < aTmpVisArea.GetWidth() ) + { + nDocDiffX = -aTmpVisArea.Left(); + } + } + + if ( nDocDiffX | nDocDiffY ) + { + tools::Long nDiffX = !IsVertical() ? nDocDiffX : (IsTopToBottom() ? -nDocDiffY : nDocDiffY); + tools::Long nDiffY = !IsVertical() ? nDocDiffY : (IsTopToBottom() ? nDocDiffX : -nDocDiffX); + + if ( nDiffX ) + pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::HSCROLL; + if ( nDiffY ) + pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::VSCROLL; + Scroll( -nDiffX, -nDiffY ); + pEditEngine->pImpEditEngine->DelayedCallStatusHdl(); + } + } + + // Cursor may trim a little ... + if ( ( aEditCursor.Bottom() > GetVisDocTop() ) && + ( aEditCursor.Top() < GetVisDocBottom() ) ) + { + if ( aEditCursor.Bottom() > GetVisDocBottom() ) + aEditCursor.SetBottom( GetVisDocBottom() ); + if ( aEditCursor.Top() < GetVisDocTop() ) + aEditCursor.SetTop( GetVisDocTop() ); + } + + const OutputDevice& rOutDev = GetOutputDevice(); + + tools::Long nOnePixel = rOutDev.PixelToLogic( Size( 1, 0 ) ).Width(); + + if ( ( aEditCursor.Top() + nOnePixel >= GetVisDocTop() ) && + ( aEditCursor.Bottom() - nOnePixel <= GetVisDocBottom() ) && + ( aEditCursor.Left() + nOnePixel >= GetVisDocLeft() ) && + ( aEditCursor.Right() - nOnePixel <= GetVisDocRight() ) ) + { + tools::Rectangle aCursorRect = GetWindowPos( aEditCursor ); + GetCursor()->SetPos( aCursorRect.TopLeft() ); + Size aCursorSz( aCursorRect.GetSize() ); + // Rectangle is inclusive + aCursorSz.AdjustWidth( -1 ); + aCursorSz.AdjustHeight( -1 ); + if ( !aCursorSz.Width() || !aCursorSz.Height() ) + { + tools::Long nCursorSz = rOutDev.GetSettings().GetStyleSettings().GetCursorSize(); + nCursorSz = rOutDev.PixelToLogic( Size( nCursorSz, 0 ) ).Width(); + if ( !aCursorSz.Width() ) + aCursorSz.setWidth( nCursorSz ); + if ( !aCursorSz.Height() ) + aCursorSz.setHeight( nCursorSz ); + } + // #111036# Let VCL do orientation for cursor, otherwise problem when cursor has direction flag + if ( IsVertical() ) + { + Size aOldSz( aCursorSz ); + aCursorSz.setWidth( aOldSz.Height() ); + aCursorSz.setHeight( aOldSz.Width() ); + GetCursor()->SetPos( aCursorRect.TopRight() ); + GetCursor()->SetOrientation( Degree10(IsTopToBottom() ? 2700 : 900) ); + } + else + // #i32593# Reset correct orientation in horizontal layout + GetCursor()->SetOrientation(); + + GetCursor()->SetSize( aCursorSz ); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && !mbSuppressLOKMessages) + { + Point aPos = GetCursor()->GetPos(); + boost::property_tree::ptree aMessageParams; + if (mpLOKSpecialPositioning) + { + // Sending the absolute (pure) logical coordinates of the cursor to the client is not + // enough for it to accurately reconstruct the corresponding tile-twips coordinates of the cursor. + // This is because the editeng(doc) positioning is not pixel aligned for each cell involved in the output-area + // (it better not be!). A simple solution is to send the coordinates of a point ('refpoint') in the output-area + // along with the relative position of the cursor w.r.t this chosen 'refpoint'. + + MapUnit eDevUnit = rOutDev.GetMapMode().GetMapUnit(); + tools::Rectangle aCursorRectPureLogical(aEditCursor.TopLeft(), GetCursor()->GetSize()); + // Get rectangle in window-coordinates from editeng(doc) coordinates in hmm. + aCursorRectPureLogical = GetWindowPos(aCursorRectPureLogical); + Point aRefPointLogical = GetOutputArea().TopLeft(); + // Get the relative coordinates w.r.t refpoint in display hmm. + aCursorRectPureLogical.Move(-aRefPointLogical.X(), -aRefPointLogical.Y()); + if (pEditEngine->IsRightToLeft(nPara) || mpLOKSpecialPositioning->IsLayoutRTL()) + { + tools::Long nMirrorW = GetOutputArea().GetWidth(); + tools::Long nLeft = aCursorRectPureLogical.Left(), nRight = aCursorRectPureLogical.Right(); + aCursorRectPureLogical.SetLeft(nMirrorW - nRight); + aCursorRectPureLogical.SetRight(nMirrorW - nLeft); + } + // Convert to twips. + aCursorRectPureLogical = OutputDevice::LogicToLogic(aCursorRectPureLogical, MapMode(eDevUnit), MapMode(MapUnit::MapTwip)); + // "refpoint" in print twips. + const Point aRefPoint = mpLOKSpecialPositioning->GetRefPoint(); + aMessageParams.put("relrect", aCursorRectPureLogical.toString()); + aMessageParams.put("refpoint", aRefPoint.toString()); + } + + if (pOutWin && pOutWin->IsChart()) + { + const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); + if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) + { + Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); + Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); + aPos.Move(aLogicOffset.getX(), aLogicOffset.getY()); + } + } + + tools::Rectangle aRect(aPos.getX(), aPos.getY(), aPos.getX() + GetCursor()->GetWidth(), aPos.getY() + GetCursor()->GetHeight()); + + // LOK output is always in twips, convert from mm100 if necessary. + if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRect = o3tl::convert(aRect, o3tl::Length::mm100, o3tl::Length::twip); + } + else if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::MapTwip) + { + // Writer comments: they use editeng, but are separate widgets. + Point aOrigin = rOutDev.GetMapMode().GetOrigin(); + // Move the rectangle, so that we output absolute twips. + aRect.Move(aOrigin.getX(), aOrigin.getY()); + } + // Let the LOK client decide the cursor width. + aRect.setWidth(0); + + OString sRect = aRect.toString(); + aMessageParams.put("rectangle", sRect); + + SfxViewShell* pThisShell = dynamic_cast<SfxViewShell*>(mpViewShell); + SfxViewShell* pOtherShell = dynamic_cast<SfxViewShell*>(mpOtherShell); + assert(pThisShell); + + if (pOtherShell && pThisShell != pOtherShell) + { + // Another shell wants to know about our existing cursor. + SfxLokHelper::notifyOtherView(pThisShell, pOtherShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams); + } + else + { + // is cursor at a misspelled word ? + Reference< linguistic2::XSpellChecker1 > xSpeller( pEditEngine->pImpEditEngine->GetSpeller() ); + bool bIsWrong = xSpeller.is() && IsWrongSpelledWord(aPaM, /*bMarkIfWrong*/ false); + EditView* pActiveView = GetEditViewPtr(); + + boost::property_tree::ptree aHyperlinkTree; + if (pActiveView && URLFieldHelper::IsCursorAtURLField(*pActiveView)) + { + if (const SvxFieldItem* pFld = GetField(aPos, nullptr, nullptr)) + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) + aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL()); + } + else if (GetEditSelection().HasRange()) + { + if (pActiveView) + { + const SvxFieldItem* pFieldItem = pActiveView->GetFieldAtSelection(); + if (pFieldItem) + { + const SvxFieldData* pField = pFieldItem->GetField(); + if ( auto pUrlField = dynamic_cast<const SvxURLField*>( pField) ) + { + aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL()); + } + } + } + } + + if (mbBroadcastLOKViewCursor) + SfxLokHelper::notifyOtherViews(pThisShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams); + + aMessageParams.put("mispelledWord", bIsWrong ? 1 : 0); + aMessageParams.add_child("hyperlink", aHyperlinkTree); + + if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + SfxLokHelper::notifyOtherView(pThisShell, pThisShell, + LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, aMessageParams); + else + pThisShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, + OString(aMessageParams.get<std::string>("rectangle"))); + } + } + + CursorDirection nCursorDir = CursorDirection::NONE; + if ( IsInsertMode() && !aEditSelection.HasRange() && ( pEditEngine->pImpEditEngine->HasDifferentRTLLevels( aPaM.GetNode() ) ) ) + { + sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, bool(nShowCursorFlags & GetCursorFlags::PreferPortionStart) ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if (rTextPortion.IsRightToLeft()) + nCursorDir = CursorDirection::RTL; + else + nCursorDir = CursorDirection::LTR; + + } + GetCursor()->SetDirection( nCursorDir ); + + if ( bForceVisCursor ) + GetCursor()->Show(); + { + SvxFont aFont; + pEditEngine->SeekCursor( aPaM.GetNode(), aPaM.GetIndex()+1, aFont ); + + InputContext aInputContext(std::move(aFont), InputContextFlags::Text | InputContextFlags::ExtText); + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewInputContext(aInputContext); + else if (auto xWindow = GetWindow()) + xWindow->SetInputContext(aInputContext); + } + } + else + { + pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() = pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() | EditStatusFlags::CURSOROUT; + GetCursor()->Hide(); + GetCursor()->SetPos( Point( -1, -1 ) ); + GetCursor()->SetSize( Size( 0, 0 ) ); + } +} + +// call this so users of EditViewCallbacks can update their scrollbar state +// so called when we have either scrolled to a new location +// or the size of document has changed +void ImpEditView::ScrollStateChange() +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewScrollStateChange(); +} + +Pair ImpEditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck ) +{ + DBG_ASSERT( pEditEngine->pImpEditEngine->IsFormatted(), "Scroll: Not formatted!" ); + if ( !ndX && !ndY ) + return Pair( 0, 0 ); + + const OutputDevice& rOutDev = GetOutputDevice(); + +#ifdef DBG_UTIL + tools::Rectangle aR( aOutArea ); + aR = rOutDev.LogicToPixel( aR ); + aR = rOutDev.PixelToLogic( aR ); + SAL_WARN_IF( aR != aOutArea, "editeng", "OutArea before Scroll not aligned" ); +#endif + + tools::Rectangle aNewVisArea( GetVisDocArea() ); + + // Vertical: + if ( !IsVertical() ) + { + aNewVisArea.AdjustTop( -ndY ); + aNewVisArea.AdjustBottom( -ndY ); + } + else + { + if( IsTopToBottom() ) + { + aNewVisArea.AdjustTop(ndX ); + aNewVisArea.AdjustBottom(ndX ); + } + else + { + aNewVisArea.AdjustTop( -ndX ); + aNewVisArea.AdjustBottom( -ndX ); + } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Bottom() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->GetTextHeight()) ) ) + { + // GetTextHeight still optimizing! + tools::Long nDiff = pEditEngine->pImpEditEngine->GetTextHeight() - aNewVisArea.Bottom(); // negative + aNewVisArea.Move( 0, nDiff ); // could end up in the negative area... + } + if ( aNewVisArea.Top() < 0 ) + aNewVisArea.Move( 0, -aNewVisArea.Top() ); + + // Horizontal: + if ( !IsVertical() ) + { + aNewVisArea.AdjustLeft( -ndX ); + aNewVisArea.AdjustRight( -ndX ); + } + else + { + if (IsTopToBottom()) + { + aNewVisArea.AdjustLeft( -ndY ); + aNewVisArea.AdjustRight( -ndY ); + } + else + { + aNewVisArea.AdjustLeft(ndY ); + aNewVisArea.AdjustRight(ndY ); + } + } + if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Right() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->CalcTextWidth( false )) ) ) + { + tools::Long nDiff = pEditEngine->pImpEditEngine->CalcTextWidth( false ) - aNewVisArea.Right(); // negative + aNewVisArea.Move( nDiff, 0 ); // could end up in the negative area... + } + if ( aNewVisArea.Left() < 0 ) + aNewVisArea.Move( -aNewVisArea.Left(), 0 ); + + // The difference must be alignt on pixel (due to scroll!) + tools::Long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : (IsTopToBottom() ? -( GetVisDocTop() - aNewVisArea.Top() ) : (GetVisDocTop() - aNewVisArea.Top())); + tools::Long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : (IsTopToBottom() ? (GetVisDocLeft() - aNewVisArea.Left()) : -(GetVisDocTop() - aNewVisArea.Top())); + + Size aDiffs( nDiffX, nDiffY ); + aDiffs = rOutDev.LogicToPixel( aDiffs ); + aDiffs = rOutDev.PixelToLogic( aDiffs ); + + tools::Long nRealDiffX = aDiffs.Width(); + tools::Long nRealDiffY = aDiffs.Height(); + + + if ( nRealDiffX || nRealDiffY ) + { + vcl::Cursor* pCrsr = GetCursor(); + bool bVisCursor = pCrsr->IsVisible(); + pCrsr->Hide(); + if (pOutWin) + pOutWin->PaintImmediately(); + if ( !IsVertical() ) + aVisDocStartPos.Move( -nRealDiffX, -nRealDiffY ); + else + { + if (IsTopToBottom()) + aVisDocStartPos.Move(-nRealDiffY, nRealDiffX); + else + aVisDocStartPos.Move(nRealDiffY, -nRealDiffX); + } + // Move by aligned value does not necessarily result in aligned + // rectangle ... + aVisDocStartPos = rOutDev.LogicToPixel( aVisDocStartPos ); + aVisDocStartPos = rOutDev.PixelToLogic( aVisDocStartPos ); + tools::Rectangle aRect( aOutArea ); + + if (pOutWin) + { + pOutWin->Scroll( nRealDiffX, nRealDiffY, aRect, ScrollFlags::Clip ); + } + + if (comphelper::LibreOfficeKit::isActive() || getEditViewCallbacks()) + { + // Need to invalidate the window, otherwise no tile will be re-painted. + pEditView->Invalidate(); + } + + if (pOutWin) + pOutWin->PaintImmediately(); + pCrsr->SetPos( pCrsr->GetPos() + Point( nRealDiffX, nRealDiffY ) ); + if ( bVisCursor ) + { + tools::Rectangle aCursorRect( pCrsr->GetPos(), pCrsr->GetSize() ); + if ( aOutArea.Contains( aCursorRect ) ) + pCrsr->Show(); + } + + if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TEXTVIEWSCROLLED ); + pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); + } + + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + pCallbacks->EditViewScrollStateChange(); + + if (comphelper::LibreOfficeKit::isActive()) + { + DrawSelectionXOR(); + } + } + + return Pair( nRealDiffX, nRealDiffY ); +} + +Reference<css::datatransfer::clipboard::XClipboard> ImpEditView::GetClipboard() const +{ + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + return pCallbacks->GetClipboard(); + if (vcl::Window* pWindow = GetWindow()) + return pWindow->GetClipboard(); + SAL_WARN("editeng", "falling back to using GetSystemClipboard"); + return GetSystemClipboard(); +} + +bool ImpEditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ) +{ + bool bDone = false; + + KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::CUT: + { + if ( !bReadOnly ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + CutCopy( aClipBoard, true ); + bDone = true; + } + } + break; + case KeyFuncType::COPY: + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + CutCopy( aClipBoard, false ); + bDone = true; + } + break; + case KeyFuncType::PASTE: + { + if ( !bReadOnly && IsPasteEnabled() ) + { + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard()); + Paste( aClipBoard, pEditEngine->pImpEditEngine->GetStatus().AllowPasteSpecial() ); + pEditEngine->pImpEditEngine->UndoActionEnd(); + bDone = true; + } + } + break; + default: + break; + } + } + + if( !bDone ) + bDone = pEditEngine->PostKeyEvent( rKeyEvent, GetEditViewPtr(), pFrameWin ); + + return bDone; +} + +bool ImpEditView::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + nTravelXPos = TRAVEL_X_DONTKNOW; + nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; + nExtraCursorFlags = GetCursorFlags::NONE; + bClickedInSelection = false; + + if ( rMouseEvent.IsMiddle() && !bReadOnly && + Application::GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection()); + Paste( aClipBoard ); + } + else if ( rMouseEvent.IsLeft() && GetEditSelection().HasRange() ) + { + Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection()); + CutCopy( aClipBoard, false ); + } + + return pEditEngine->pImpEditEngine->MouseButtonUp( rMouseEvent, GetEditViewPtr() ); +} + +void ImpEditView::ReleaseMouse() +{ + pEditEngine->pImpEditEngine->ReleaseMouse(); +} + +bool ImpEditView::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button downs + nTravelXPos = TRAVEL_X_DONTKNOW; + nExtraCursorFlags = GetCursorFlags::NONE; + nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; + bool bPrevUpdateLayout = pEditEngine->pImpEditEngine->SetUpdateLayout(true); + bClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() ); + bool bRet = pEditEngine->pImpEditEngine->MouseButtonDown( rMouseEvent, GetEditViewPtr() ); + pEditEngine->pImpEditEngine->SetUpdateLayout(bPrevUpdateLayout); + return bRet; +} + +bool ImpEditView::MouseMove( const MouseEvent& rMouseEvent ) +{ + return pEditEngine->pImpEditEngine->MouseMove( rMouseEvent, GetEditViewPtr() ); +} + +bool ImpEditView::Command(const CommandEvent& rCEvt) +{ + pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button down + return pEditEngine->pImpEditEngine->Command(rCEvt, GetEditViewPtr()); +} + + +void ImpEditView::SetInsertMode( bool bInsert ) +{ + if ( bInsert != IsInsertMode() ) + { + SetFlags( nControl, EVControlBits::OVERWRITE, !bInsert ); + ShowCursor( DoAutoScroll(), false ); + } +} + +bool ImpEditView::IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong ) +{ + bool bIsWrong = false; + if ( rPaM.GetNode()->GetWrongList() ) + { + EditSelection aSel = pEditEngine->SelectWord( rPaM, css::i18n::WordType::DICTIONARY_WORD ); + bIsWrong = rPaM.GetNode()->GetWrongList()->HasWrong( aSel.Min().GetIndex(), aSel.Max().GetIndex() ); + if ( bIsWrong && bMarkIfWrong ) + { + DrawSelectionXOR(); + SetEditSelection( aSel ); + DrawSelectionXOR(); + } + } + return bIsWrong; +} + +OUString ImpEditView::SpellIgnoreWord() +{ + OUString aWord; + if ( pEditEngine->pImpEditEngine->GetSpeller().is() ) + { + EditPaM aPaM = GetEditSelection().Max(); + if ( !HasSelection() ) + { + EditSelection aSel = pEditEngine->SelectWord(aPaM); + aWord = pEditEngine->pImpEditEngine->GetSelected( aSel ); + } + else + { + aWord = pEditEngine->pImpEditEngine->GetSelected( GetEditSelection() ); + // And deselect + DrawSelectionXOR(); + SetEditSelection( EditSelection( aPaM, aPaM ) ); + DrawSelectionXOR(); + } + + if ( !aWord.isEmpty() ) + { + Reference< XDictionary > xDic = LinguMgr::GetIgnoreAllList(); + if (xDic.is()) + xDic->add( aWord, false, OUString() ); + EditDoc& rDoc = pEditEngine->GetEditDoc(); + sal_Int32 nNodes = rDoc.Count(); + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = rDoc.GetObject( n ); + pNode->GetWrongList()->MarkWrongsInvalid(); + } + pEditEngine->pImpEditEngine->DoOnlineSpelling( aPaM.GetNode() ); + pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); + } + } + return aWord; +} + +void ImpEditView::DeleteSelected() +{ + DrawSelectionXOR(); + + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DELETE ); + + EditPaM aPaM = pEditEngine->pImpEditEngine->DeleteSelected( GetEditSelection() ); + + pEditEngine->pImpEditEngine->UndoActionEnd(); + + SetEditSelection( EditSelection( aPaM, aPaM ) ); + + DrawSelectionXOR(); + + pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() ); + ShowCursor( DoAutoScroll(), true ); +} + +const SvxFieldItem* ImpEditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const +{ + if( !GetOutputArea().Contains( rPos ) ) + return nullptr; + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + if (!aPaM) + return nullptr; + + if ( aPaM.GetIndex() == aPaM.GetNode()->Len() ) + { + // Otherwise, whenever the Field at the very end and mouse under the text + return nullptr; + } + + const CharAttribList::AttribsType& rAttrs = aPaM.GetNode()->GetCharAttribs().GetAttribs(); + const sal_Int32 nXPos = aPaM.GetIndex(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.GetStart() == nXPos || rAttr.GetEnd() == nXPos) + { + if (rAttr.Which() == EE_FEATURE_FIELD) + { + DBG_ASSERT(dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "No FieldItem..."); + if ( pPara ) + *pPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + if ( pPos ) + *pPos = rAttr.GetStart(); + return static_cast<const SvxFieldItem*>(rAttr.GetItem()); + } + } + } + return nullptr; +} + +bool ImpEditView::IsBulletArea( const Point& rPos, sal_Int32* pPara ) +{ + if ( pPara ) + *pPara = EE_PARA_NOT_FOUND; + + if( !GetOutputArea().Contains( rPos ) ) + return false; + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + if (!aPaM) + return false; + + if ( aPaM.GetIndex() == 0 ) + { + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + tools::Rectangle aBulletArea = pEditEngine->GetBulletArea( nPara ); + tools::Long nY = pEditEngine->GetDocPosTopLeft( nPara ).Y(); + const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; + nY += pParaPortion->GetFirstLineOffset(); + if ( ( aDocPos.Y() > ( nY + aBulletArea.Top() ) ) && + ( aDocPos.Y() < ( nY + aBulletArea.Bottom() ) ) && + ( aDocPos.X() > ( aBulletArea.Left() ) ) && + ( aDocPos.X() < ( aBulletArea.Right() ) ) ) + { + if ( pPara ) + *pPara = nPara; + return true; + } + } + + return false; +} + +void ImpEditView::CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut ) +{ + if ( !(rxClipboard.is() && HasSelection()) ) + return; + + uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable( GetEditSelection() ); + + { + SolarMutexReleaser aReleaser; + + try + { + rxClipboard->setContents( xData, nullptr ); + + // #87756# FlushClipboard, but it would be better to become a TerminateListener to the Desktop and flush on demand... + uno::Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, uno::UNO_QUERY ); + if( xFlushableClipboard.is() ) + xFlushableClipboard->flushClipboard(); + } + catch( const css::uno::Exception& ) + { + } + + } + + if (bCut) + { + pEditEngine->pImpEditEngine->UndoActionStart(EDITUNDO_CUT); + DeleteSelected(); + pEditEngine->pImpEditEngine->UndoActionEnd(); + } +} + +void ImpEditView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial, SotClipboardFormatId format) +{ + if ( !rxClipboard.is() ) + return; + + uno::Reference< datatransfer::XTransferable > xDataObj; + + try + { + SolarMutexReleaser aReleaser; + xDataObj = rxClipboard->getContents(); + } + catch( const css::uno::Exception& ) + { + } + + if ( !xDataObj.is() || !EditEngine::HasValidData( xDataObj ) ) + return; + + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); + + EditSelection aSel( GetEditSelection() ); + if ( aSel.HasRange() ) + { + DrawSelectionXOR(); + aSel = pEditEngine->DeleteSelection(aSel); + } + + PasteOrDropInfos aPasteOrDropInfos; + aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); + + if ( DoSingleLinePaste() ) + { + datatransfer::DataFlavor aFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + if ( xDataObj->isDataFlavorSupported( aFlavor ) ) + { + try + { + uno::Any aData = xDataObj->getTransferData( aFlavor ); + OUString aTmpText; + aData >>= aTmpText; + OUString aText(convertLineEnd(aTmpText, LINEEND_LF)); + aText = aText.replaceAll( OUStringChar(LINE_SEP), " " ); + aSel = pEditEngine->InsertText(aSel, aText); + } + catch( ... ) + { + ; // #i9286# can happen, even if isDataFlavorSupported returns true... + } + } + } + else + { + // Prevent notifications of paragraph inserts et al that would trigger + // a11y to format content in a half-ready state when obtaining + // paragraphs. Collect and broadcast when done instead. + aSel = pEditEngine->InsertText( + xDataObj, OUString(), aSel.Min(), + bUseSpecial && pEditEngine->GetInternalEditStatus().AllowPasteSpecial(), format); + } + + aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); + + pEditEngine->pImpEditEngine->UndoActionEnd(); + SetEditSelection( aSel ); + pEditEngine->pImpEditEngine->UpdateSelections(); + pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() ); + ShowCursor( DoAutoScroll(), true ); +} + + +bool ImpEditView::IsInSelection( const EditPaM& rPaM ) +{ + EditSelection aSel = GetEditSelection(); + if ( !aSel.HasRange() ) + return false; + + aSel.Adjust( pEditEngine->GetEditDoc() ); + + sal_Int32 nStartNode = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); + sal_Int32 nCurNode = pEditEngine->GetEditDoc().GetPos( rPaM.GetNode() ); + + if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) ) + return true; + + if ( nStartNode == nEndNode ) + { + if ( nCurNode == nStartNode ) + if ( ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) + return true; + } + else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) ) + return true; + else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) + return true; + + return false; +} + +bool ImpEditView::IsSelectionFullPara() const +{ + if (!IsSelectionInSinglePara()) + return false; + + sal_Int32 nSelectionStartPos = GetEditSelection().Min().GetIndex(); + sal_Int32 nSelectionEndPos = GetEditSelection().Max().GetIndex(); + + if (nSelectionStartPos > nSelectionEndPos) + std::swap(nSelectionStartPos, nSelectionEndPos); + + if (nSelectionStartPos != 0) + return false; + + const ContentNode* pNode = GetEditSelection().Min().GetNode(); + return pNode->Len() == nSelectionEndPos; +} + +bool ImpEditView::IsSelectionInSinglePara() const +{ + return GetEditSelection().Min().GetNode() == GetEditSelection().Max().GetNode(); +} + +void ImpEditView::CreateAnchor() +{ + pEditEngine->SetInSelectionMode(true); + EditSelection aNewSelection(GetEditSelection()); + aNewSelection.Min() = aNewSelection.Max(); + SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max(); +} + +void ImpEditView::DeselectAll() +{ + pEditEngine->SetInSelectionMode(false); + DrawSelectionXOR(); + EditSelection aNewSelection(GetEditSelection()); + aNewSelection.Min() = aNewSelection.Max(); + SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max(); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin) + { + VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier(); + if (pParent && pParent->GetLOKWindowId()) + { + const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier(); + std::vector<vcl::LOKPayloadItem> aItems; + aItems.emplace_back("rectangles", ""); + pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems); + } + } +} + +bool ImpEditView::IsSelectionAtPoint( const Point& rPosPixel ) +{ + if ( pDragAndDropInfo && pDragAndDropInfo->pField ) + return true; + + // Logical units ... + const OutputDevice& rOutDev = GetOutputDevice(); + Point aMousePos = rOutDev.PixelToLogic(rPosPixel); + + if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) + { + return false; + } + + Point aDocPos( GetDocPos( aMousePos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); + return IsInSelection( aPaM ); +} + +bool ImpEditView::SetCursorAtPoint( const Point& rPointPixel ) +{ + pEditEngine->CheckIdleFormatter(); + + Point aMousePos( rPointPixel ); + + // Logical units ... + const OutputDevice& rOutDev = GetOutputDevice(); + aMousePos = rOutDev.PixelToLogic( aMousePos ); + + if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) + { + return false; + } + + Point aDocPos( GetDocPos( aMousePos ) ); + + // Can be optimized: first go through the lines within a paragraph for PAM, + // then again with the PaM for the Rect, even though the line is already + // known... This must not be, though! + EditPaM aPaM = pEditEngine->GetPaM(aDocPos); + bool bGotoCursor = DoAutoScroll(); + + // aTmpNewSel: Diff between old and new, not the new selection, unless for tiled rendering + EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? GetEditSelection().Min() : GetEditSelection().Max(), aPaM ); + + // #i27299# + // work on copy of current selection and set new selection, if it has changed. + EditSelection aNewEditSelection( GetEditSelection() ); + + aNewEditSelection.Max() = aPaM; + if (!pEditEngine->GetSelectionEngine().HasAnchor()) + { + if ( aNewEditSelection.Min() != aPaM ) + { + const ContentNode* pNode(aNewEditSelection.Min().GetNode()); + if (nullptr != pNode) + pNode->checkAndDeleteEmptyAttribs(); + } + aNewEditSelection.Min() = aPaM; + } + else + { + DrawSelectionXOR( aTmpNewSel ); + } + + // set changed text selection + if ( GetEditSelection() != aNewEditSelection ) + { + SetEditSelection( aNewEditSelection ); + } + + bool bForceCursor = pDragAndDropInfo == nullptr && !pEditEngine->pImpEditEngine->IsInSelectionMode(); + ShowCursor( bGotoCursor, bForceCursor ); + return true; +} + +void ImpEditView::HideDDCursor() +{ + if ( pDragAndDropInfo && pDragAndDropInfo->bVisCursor ) + { + OutputDevice& rOutDev = GetOutputDevice(); + rOutDev.DrawOutDev( pDragAndDropInfo->aCurSavedCursor.TopLeft(), pDragAndDropInfo->aCurSavedCursor.GetSize(), + Point(0,0), pDragAndDropInfo->aCurSavedCursor.GetSize(),*pDragAndDropInfo->pBackground ); + pDragAndDropInfo->bVisCursor = false; + } +} + +void ImpEditView::ShowDDCursor( const tools::Rectangle& rRect ) +{ + if ( !pDragAndDropInfo || pDragAndDropInfo->bVisCursor ) + return; + + if (pOutWin && pOutWin->GetCursor()) + pOutWin->GetCursor()->Hide(); + + OutputDevice& rOutDev = GetOutputDevice(); + Color aOldFillColor = rOutDev.GetFillColor(); + rOutDev.SetFillColor( Color(4210752) ); // GRAY BRUSH_50, OLDSV, change to DDCursor! + + // Save background ... + tools::Rectangle aSaveRect( rOutDev.LogicToPixel( rRect ) ); + // prefer to save some more ... + aSaveRect.AdjustRight(1 ); + aSaveRect.AdjustBottom(1 ); + + if ( !pDragAndDropInfo->pBackground ) + { + pDragAndDropInfo->pBackground = VclPtr<VirtualDevice>::Create(rOutDev); + MapMode aMapMode( rOutDev.GetMapMode() ); + aMapMode.SetOrigin( Point( 0, 0 ) ); + pDragAndDropInfo->pBackground->SetMapMode( aMapMode ); + + } + + Size aNewSzPx( aSaveRect.GetSize() ); + Size aCurSzPx( pDragAndDropInfo->pBackground->GetOutputSizePixel() ); + if ( ( aCurSzPx.Width() < aNewSzPx.Width() ) ||( aCurSzPx.Height() < aNewSzPx.Height() ) ) + { + bool bDone = pDragAndDropInfo->pBackground->SetOutputSizePixel( aNewSzPx ); + DBG_ASSERT( bDone, "Virtual Device broken?" ); + } + + aSaveRect = rOutDev.PixelToLogic( aSaveRect ); + + pDragAndDropInfo->pBackground->DrawOutDev( Point(0,0), aSaveRect.GetSize(), + aSaveRect.TopLeft(), aSaveRect.GetSize(), rOutDev ); + pDragAndDropInfo->aCurSavedCursor = aSaveRect; + + // Draw Cursor... + rOutDev.DrawRect( rRect ); + + pDragAndDropInfo->bVisCursor = true; + pDragAndDropInfo->aCurCursor = rRect; + + rOutDev.SetFillColor( aOldFillColor ); +} + +void ImpEditView::dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& rDGE) +{ + DBG_ASSERT( !pDragAndDropInfo, "dragGestureRecognized - DragAndDropInfo exist!" ); + + SolarMutexGuard aVclGuard; + + pDragAndDropInfo.reset(); + + Point aMousePosPixel( rDGE.DragOriginX, rDGE.DragOriginY ); + + EditSelection aCopySel( GetEditSelection() ); + aCopySel.Adjust( pEditEngine->GetEditDoc() ); + + if ( HasSelection() && bClickedInSelection ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + } + else + { + // Field?! + sal_Int32 nPara; + sal_Int32 nPos; + Point aMousePos = GetOutputDevice().PixelToLogic( aMousePosPixel ); + const SvxFieldItem* pField = GetField( aMousePos, &nPara, &nPos ); + if ( pField ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + pDragAndDropInfo->pField = pField; + ContentNode* pNode = pEditEngine->GetEditDoc().GetObject( nPara ); + aCopySel = EditSelection( EditPaM( pNode, nPos ), EditPaM( pNode, nPos+1 ) ); + SetEditSelection(aCopySel); + DrawSelectionXOR(); + bool bGotoCursor = DoAutoScroll(); + ShowCursor( bGotoCursor, /*bForceCursor=*/false ); + } + else if ( IsBulletArea( aMousePos, &nPara ) ) + { + pDragAndDropInfo.reset(new DragAndDropInfo()); + pDragAndDropInfo->bOutlinerMode = true; + EditPaM aStartPaM( pEditEngine->GetEditDoc().GetObject( nPara ), 0 ); + EditPaM aEndPaM( aStartPaM ); + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + for ( sal_Int32 n = nPara +1; n < pEditEngine->GetEditDoc().Count(); n++ ) + { + const SfxInt16Item& rL = pEditEngine->GetParaAttrib( n, EE_PARA_OUTLLEVEL ); + if ( rL.GetValue() > rLevel.GetValue() ) + { + aEndPaM.SetNode( pEditEngine->GetEditDoc().GetObject( n ) ); + } + else + { + break; + } + } + aEndPaM.SetIndex( aEndPaM.GetNode()->Len() ); + SetEditSelection( EditSelection( aStartPaM, aEndPaM ) ); + } + } + + if ( !pDragAndDropInfo ) + return; + + + pDragAndDropInfo->bStarterOfDD = true; + + // Sensitive area to be scrolled. + Size aSz( 5, 0 ); + aSz = GetOutputDevice().PixelToLogic( aSz ); + pDragAndDropInfo->nSensibleRange = static_cast<sal_uInt16>(aSz.Width()); + pDragAndDropInfo->nCursorWidth = static_cast<sal_uInt16>(aSz.Width()) / 2; + pDragAndDropInfo->aBeginDragSel = pEditEngine->pImpEditEngine->CreateESel( aCopySel ); + + uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable(aCopySel); + + sal_Int8 nActions = bReadOnly ? datatransfer::dnd::DNDConstants::ACTION_COPY : datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE; + + rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, xData, mxDnDListener ); + // If Drag&Move in an Engine, then Copy&Del has to be optional! + GetCursor()->Hide(); +} + +void ImpEditView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE ) +{ + SolarMutexGuard aVclGuard; + + DBG_ASSERT( pDragAndDropInfo, "ImpEditView::dragDropEnd: pDragAndDropInfo is NULL!" ); + + // #123688# Shouldn't happen, but seems to happen... + if ( !pDragAndDropInfo ) + return; + + if ( !bReadOnly && rDSDE.DropSuccess && !pDragAndDropInfo->bOutlinerMode && ( rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE ) ) + { + if ( pDragAndDropInfo->bStarterOfDD && pDragAndDropInfo->bDroppedInMe ) + { + // DropPos: Where was it dropped, irrespective of length. + ESelection aDropPos( pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos, pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos ); + ESelection aToBeDelSel = pDragAndDropInfo->aBeginDragSel; + ESelection aNewSel( pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos, + pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos ); + bool bBeforeSelection = aDropPos < pDragAndDropInfo->aBeginDragSel; + sal_Int32 nParaDiff = pDragAndDropInfo->aBeginDragSel.nEndPara - pDragAndDropInfo->aBeginDragSel.nStartPara; + if ( bBeforeSelection ) + { + // Adjust aToBeDelSel. + DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara >= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); + aToBeDelSel.nStartPara = aToBeDelSel.nStartPara + nParaDiff; + aToBeDelSel.nEndPara = aToBeDelSel.nEndPara + nParaDiff; + // To correct the character? + if ( aToBeDelSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) + { + sal_uInt16 nMoreChars; + if ( pDragAndDropInfo->aDropSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) + nMoreChars = pDragAndDropInfo->aDropSel.nEndPos - pDragAndDropInfo->aDropSel.nStartPos; + else + nMoreChars = pDragAndDropInfo->aDropSel.nEndPos; + aToBeDelSel.nStartPos = + aToBeDelSel.nStartPos + nMoreChars; + if ( aToBeDelSel.nStartPara == aToBeDelSel.nEndPara ) + aToBeDelSel.nEndPos = + aToBeDelSel.nEndPos + nMoreChars; + } + } + else + { + // aToBeDelSel is ok, but the selection of the View + // has to be adapted, if it was deleted before! + DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara <= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); + aNewSel.nStartPara = aNewSel.nStartPara - nParaDiff; + aNewSel.nEndPara = aNewSel.nEndPara - nParaDiff; + // To correct the character? + if ( pDragAndDropInfo->aBeginDragSel.nEndPara == pDragAndDropInfo->aDropSel.nStartPara ) + { + sal_uInt16 nLessChars; + if ( pDragAndDropInfo->aBeginDragSel.nStartPara == pDragAndDropInfo->aBeginDragSel.nEndPara ) + nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos - pDragAndDropInfo->aBeginDragSel.nStartPos; + else + nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos; + aNewSel.nStartPos = aNewSel.nStartPos - nLessChars; + if ( aNewSel.nStartPara == aNewSel.nEndPara ) + aNewSel.nEndPos = aNewSel.nEndPos - nLessChars; + } + } + + DrawSelectionXOR(); + EditSelection aDelSel( pEditEngine->pImpEditEngine->CreateSel( aToBeDelSel ) ); + DBG_ASSERT( !aDelSel.DbgIsBuggy( pEditEngine->GetEditDoc() ), "ToBeDel is buggy!" ); + pEditEngine->DeleteSelection(aDelSel); + if ( !bBeforeSelection ) + { + DBG_ASSERT( !pEditEngine->pImpEditEngine->CreateSel( aNewSel ).DbgIsBuggy(pEditEngine->GetEditDoc()), "Bad" ); + SetEditSelection( pEditEngine->pImpEditEngine->CreateSel( aNewSel ) ); + } + pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() ); + DrawSelectionXOR(); + } + else + { + // other EditEngine ... + if (pEditEngine->HasText()) // #88630# SC is removing the content when switching the task + DeleteSelected(); + } + } + + if ( pDragAndDropInfo->bUndoAction ) + pEditEngine->pImpEditEngine->UndoActionEnd(); + + HideDDCursor(); + ShowCursor( DoAutoScroll(), true ); + pDragAndDropInfo.reset(); + pEditEngine->GetEndDropHdl().Call(GetEditViewPtr()); +} + +void ImpEditView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE ) +{ + SolarMutexGuard aVclGuard; + + DBG_ASSERT( pDragAndDropInfo, "Drop - No Drag&Drop info?!" ); + + if ( !(pDragAndDropInfo && pDragAndDropInfo->bDragAccepted) ) + return; + + pEditEngine->GetBeginDropHdl().Call(GetEditViewPtr()); + bool bChanges = false; + + HideDDCursor(); + + if ( pDragAndDropInfo->bStarterOfDD ) + { + pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DRAGANDDROP ); + pDragAndDropInfo->bUndoAction = true; + } + + if ( pDragAndDropInfo->bOutlinerMode ) + { + bChanges = true; + GetEditViewPtr()->MoveParagraphs( Range( pDragAndDropInfo->aBeginDragSel.nStartPara, pDragAndDropInfo->aBeginDragSel.nEndPara ), pDragAndDropInfo->nOutlinerDropDest ); + } + else + { + uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable; + if ( xDataObj.is() ) + { + bChanges = true; + // remove Selection ... + DrawSelectionXOR(); + EditPaM aPaM( pDragAndDropInfo->aDropDest ); + + PasteOrDropInfos aPasteOrDropInfos; + aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); + + EditSelection aNewSel = pEditEngine->InsertText( + xDataObj, OUString(), aPaM, pEditEngine->GetInternalEditStatus().AllowPasteSpecial()); + + aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); + pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); + + SetEditSelection( aNewSel ); + pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() ); + if ( pDragAndDropInfo->bStarterOfDD ) + { + // Only set if the same engine! + pDragAndDropInfo->aDropSel.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + pDragAndDropInfo->aDropSel.nStartPos = aPaM.GetIndex(); + pDragAndDropInfo->aDropSel.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); + pDragAndDropInfo->aDropSel.nEndPos = aNewSel.Max().GetIndex(); + pDragAndDropInfo->bDroppedInMe = true; + } + } + } + + if ( bChanges ) + { + rDTDE.Context->acceptDrop( rDTDE.DropAction ); + } + + if ( !pDragAndDropInfo->bStarterOfDD ) + { + pDragAndDropInfo.reset(); + } + + rDTDE.Context->dropComplete( bChanges ); +} + +void ImpEditView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDEE ) +{ + SolarMutexGuard aVclGuard; + + if ( !pDragAndDropInfo ) + pDragAndDropInfo.reset(new DragAndDropInfo()); + + pDragAndDropInfo->bHasValidData = false; + + // Check for supported format... + // Only check for text, will also be there if bin or rtf + datatransfer::DataFlavor aTextFlavor; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aTextFlavor ); + const css::datatransfer::DataFlavor* pFlavors = rDTDEE.SupportedDataFlavors.getConstArray(); + int nFlavors = rDTDEE.SupportedDataFlavors.getLength(); + for ( int n = 0; n < nFlavors; n++ ) + { + if( TransferableDataHelper::IsEqual( pFlavors[n], aTextFlavor ) ) + { + pDragAndDropInfo->bHasValidData = true; + break; + } + } + + dragOver( rDTDEE ); +} + +void ImpEditView::dragExit( const css::datatransfer::dnd::DropTargetEvent& ) +{ + SolarMutexGuard aVclGuard; + + HideDDCursor(); + + if ( pDragAndDropInfo && !pDragAndDropInfo->bStarterOfDD ) + { + pDragAndDropInfo.reset(); + } +} + +void ImpEditView::dragOver(const css::datatransfer::dnd::DropTargetDragEvent& rDTDE) +{ + SolarMutexGuard aVclGuard; + + const OutputDevice& rOutDev = GetOutputDevice(); + + Point aMousePos( rDTDE.LocationX, rDTDE.LocationY ); + aMousePos = rOutDev.PixelToLogic( aMousePos ); + + bool bAccept = false; + + if ( GetOutputArea().Contains( aMousePos ) && !bReadOnly ) + { + if ( pDragAndDropInfo && pDragAndDropInfo->bHasValidData ) + { + bAccept = true; + + bool bAllowScroll = DoAutoScroll(); + if ( bAllowScroll ) + { + tools::Long nScrollX = 0; + tools::Long nScrollY = 0; + // Check if in the sensitive area + if ( ( (aMousePos.X()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Left() ) && ( ( aMousePos.X() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Left() ) ) + nScrollX = GetOutputArea().GetWidth() / SCRLRANGE; + else if ( ( (aMousePos.X()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Right() ) && ( ( aMousePos.X() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Right() ) ) + nScrollX = -( GetOutputArea().GetWidth() / SCRLRANGE ); + + if ( ( (aMousePos.Y()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Top() ) && ( ( aMousePos.Y() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Top() ) ) + nScrollY = GetOutputArea().GetHeight() / SCRLRANGE; + else if ( ( (aMousePos.Y()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Bottom() ) && ( ( aMousePos.Y() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Bottom() ) ) + nScrollY = -( GetOutputArea().GetHeight() / SCRLRANGE ); + + if ( nScrollX || nScrollY ) + { + HideDDCursor(); + Scroll( nScrollX, nScrollY, ScrollRangeCheck::PaperWidthTextSize ); + } + } + + Point aDocPos( GetDocPos( aMousePos ) ); + EditPaM aPaM = pEditEngine->GetPaM( aDocPos ); + pDragAndDropInfo->aDropDest = aPaM; + if ( pDragAndDropInfo->bOutlinerMode ) + { + sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara ); + if (pPPortion) + { + tools::Long nDestParaStartY = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); + tools::Long nRel = aDocPos.Y() - nDestParaStartY; + if ( nRel < ( pPPortion->GetHeight() / 2 ) ) + { + pDragAndDropInfo->nOutlinerDropDest = nPara; + } + else + { + pDragAndDropInfo->nOutlinerDropDest = nPara+1; + } + + if( ( pDragAndDropInfo->nOutlinerDropDest >= pDragAndDropInfo->aBeginDragSel.nStartPara ) && + ( pDragAndDropInfo->nOutlinerDropDest <= (pDragAndDropInfo->aBeginDragSel.nEndPara+1) ) ) + { + bAccept = false; + } + } + } + else if ( HasSelection() ) + { + // it must not be dropped into a selection + EPaM aP = pEditEngine->pImpEditEngine->CreateEPaM( aPaM ); + ESelection aDestSel( aP.nPara, aP.nIndex, aP.nPara, aP.nIndex); + ESelection aCurSel = pEditEngine->pImpEditEngine->CreateESel( GetEditSelection() ); + aCurSel.Adjust(); + if ( !(aDestSel < aCurSel) && !(aDestSel > aCurSel) ) + { + bAccept = false; + } + } + if ( bAccept ) + { + tools::Rectangle aEditCursor; + if ( pDragAndDropInfo->bOutlinerMode ) + { + tools::Long nDDYPos(0); + if ( pDragAndDropInfo->nOutlinerDropDest < pEditEngine->GetEditDoc().Count() ) + { + ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( pDragAndDropInfo->nOutlinerDropDest ); + if (pPPortion) + nDDYPos = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); + } + else + { + nDDYPos = pEditEngine->pImpEditEngine->GetTextHeight(); + } + Point aStartPos( 0, nDDYPos ); + aStartPos = GetWindowPos( aStartPos ); + Point aEndPos( GetOutputArea().GetWidth(), nDDYPos ); + aEndPos = GetWindowPos( aEndPos ); + aEditCursor = rOutDev.LogicToPixel( tools::Rectangle( aStartPos, aEndPos ) ); + if ( !pEditEngine->IsEffectivelyVertical() ) + { + aEditCursor.AdjustTop( -1 ); + aEditCursor.AdjustBottom( 1 ); + } + else + { + if( IsTopToBottom() ) + { + aEditCursor.AdjustLeft( -1 ); + aEditCursor.AdjustRight( 1 ); + } + else + { + aEditCursor.AdjustLeft( 1 ); + aEditCursor.AdjustRight( -1 ); + } + } + aEditCursor = rOutDev.PixelToLogic( aEditCursor ); + } + else + { + aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM ); + Point aTopLeft( GetWindowPos( aEditCursor.TopLeft() ) ); + aEditCursor.SetPos( aTopLeft ); + aEditCursor.SetRight( aEditCursor.Left() + pDragAndDropInfo->nCursorWidth ); + aEditCursor = rOutDev.LogicToPixel( aEditCursor ); + aEditCursor = rOutDev.PixelToLogic( aEditCursor ); + } + + bool bCursorChanged = !pDragAndDropInfo->bVisCursor || ( pDragAndDropInfo->aCurCursor != aEditCursor ); + if ( bCursorChanged ) + { + HideDDCursor(); + ShowDDCursor(aEditCursor ); + } + pDragAndDropInfo->bDragAccepted = true; + rDTDE.Context->acceptDrag( rDTDE.DropAction ); + } + } + } + + if ( !bAccept ) + { + HideDDCursor(); + if (pDragAndDropInfo) + pDragAndDropInfo->bDragAccepted = false; + rDTDE.Context->rejectDrag(); + } +} + +void ImpEditView::AddDragAndDropListeners() +{ + if (bActiveDragAndDropListener) + return; + + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget; + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + xDropTarget = pCallbacks->GetDropTarget(); + else if (auto xWindow = GetWindow()) + xDropTarget = xWindow->GetDropTarget(); + + if (!xDropTarget.is()) + return; + + mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this); + + css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); + if (xDragGestureRecognizer.is()) + { + uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY); + xDragGestureRecognizer->addDragGestureListener(xDGL); + } + + uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY); + xDropTarget->addDropTargetListener(xDTL); + xDropTarget->setActive(true); + xDropTarget->setDefaultActions(datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE); + + bActiveDragAndDropListener = true; +} + +void ImpEditView::RemoveDragAndDropListeners() +{ + if (!bActiveDragAndDropListener) + return; + + css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget; + if (EditViewCallbacks* pCallbacks = getEditViewCallbacks()) + xDropTarget = pCallbacks->GetDropTarget(); + else if (auto xWindow = GetWindow()) + xDropTarget = xWindow->GetDropTarget(); + + if (xDropTarget.is()) + { + css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); + if (xDragGestureRecognizer.is()) + { + uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY); + xDragGestureRecognizer->removeDragGestureListener(xDGL); + } + + uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY); + xDropTarget->removeDropTargetListener(xDTL); + } + + if ( mxDnDListener.is() ) + { + mxDnDListener->disposing( lang::EventObject() ); // #95154# Empty Source means it's the Client + mxDnDListener.clear(); + } + + bActiveDragAndDropListener = false; +} + +void ImpEditView::InitLOKSpecialPositioning(MapUnit eUnit, + const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos) +{ + if (!mpLOKSpecialPositioning) + mpLOKSpecialPositioning.reset(new LOKSpecialPositioning(*this, eUnit, rOutputArea, rVisDocStartPos)); + else + mpLOKSpecialPositioning->ReInit(eUnit, rOutputArea, rVisDocStartPos); +} + +void ImpEditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea) +{ + assert(mpLOKSpecialPositioning); + mpLOKSpecialPositioning->SetOutputArea(rOutputArea); +} + +const tools::Rectangle & ImpEditView::GetLOKSpecialOutputArea() const +{ + assert(mpLOKSpecialPositioning); + return mpLOKSpecialPositioning->GetOutputArea(); +} + +void ImpEditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea) +{ + assert(mpLOKSpecialPositioning); + mpLOKSpecialPositioning->SetVisDocStartPos(rVisArea.TopLeft()); +} + +tools::Rectangle ImpEditView::GetLOKSpecialVisArea() const +{ + assert(mpLOKSpecialPositioning); + return mpLOKSpecialPositioning->GetVisDocArea(); +} + +bool ImpEditView::HasLOKSpecialPositioning() const +{ + return bool(mpLOKSpecialPositioning); +} + +void ImpEditView::SetLOKSpecialFlags(LOKSpecialFlags eFlags) +{ + assert(mpLOKSpecialPositioning); + mpLOKSpecialPositioning->SetFlags(eFlags); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx new file mode 100644 index 0000000000..e4352f298f --- /dev/null +++ b/editeng/source/editeng/impedit.hxx @@ -0,0 +1,1362 @@ +/* -*- 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 . + */ +#pragma once + +#include <eerdll2.hxx> +#include <editdoc.hxx> +#include "editsel.hxx" +#include "editundo.hxx" +#include "editstt2.hxx" +#include <editeng/editdata.hxx> +#include <editeng/SpellPortions.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/outliner.hxx> +#include <vcl/virdev.hxx> +#include <vcl/cursor.hxx> +#include <vcl/vclptr.hxx> +#include <tools/fract.hxx> +#include <vcl/idle.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/ptrstyle.hxx> + +#include <vcl/dndhelp.hxx> +#include <svl/ondemand.hxx> +#include <svl/languageoptions.hxx> +#include <com/sun/star/linguistic2/XSpellAlternatives.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XExtendedInputSequenceChecker.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <i18nlangtag/lang.h> +#include <o3tl/deleter.hxx> +#include <o3tl/typed_flags_set.hxx> + +#include <functional> +#include <optional> +#include <memory> +#include <tuple> +#include <string_view> +#include <vector> + +class EditView; +class EditEngine; +class OutlinerSearchable; + +class SvxSearchItem; +class SvxLRSpaceItem; +class TextRanger; +class SvKeyValueIterator; +class SvxForbiddenCharactersTable; +namespace vcl { class Window; } +class SvxNumberFormat; +namespace com::sun::star::datatransfer::clipboard { + class XClipboard; +} + +namespace editeng { + struct MisspellRanges; +} + +#define DEL_LEFT 1 +#define DEL_RIGHT 2 +#define TRAVEL_X_DONTKNOW 0xFFFFFFFF +#define CURSOR_BIDILEVEL_DONTKNOW 0xFFFF +#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW // Max 16K, because WYSIWYG array +#define LINE_SEP '\x0A' + +#define ATTRSPECIAL_WHOLEWORD 1 +#define ATTRSPECIAL_EDGE 2 + +enum class GetCursorFlags { + NONE = 0x0000, + TextOnly = 0x0001, + StartOfLine = 0x0002, + EndOfLine = 0x0004, + PreferPortionStart = 0x0008, +}; +namespace o3tl { + template<> struct typed_flags<GetCursorFlags> : is_typed_flags<GetCursorFlags, 0x0f> {}; +} + + +struct DragAndDropInfo +{ + tools::Rectangle aCurCursor; + tools::Rectangle aCurSavedCursor; + sal_uInt16 nSensibleRange; + sal_uInt16 nCursorWidth; + ESelection aBeginDragSel; + EditPaM aDropDest; + sal_Int32 nOutlinerDropDest; + ESelection aDropSel; + VclPtr<VirtualDevice> pBackground; + const SvxFieldItem* pField; + bool bVisCursor : 1; + bool bDroppedInMe : 1; + bool bStarterOfDD : 1; + bool bHasValidData : 1; + bool bUndoAction : 1; + bool bOutlinerMode : 1; + bool bDragAccepted : 1; + + DragAndDropInfo() + : nSensibleRange(0), nCursorWidth(0), nOutlinerDropDest(0), pBackground(nullptr), + pField(nullptr), bVisCursor(false), bDroppedInMe(false), bStarterOfDD(false), + bHasValidData(false), bUndoAction(false), bOutlinerMode(false), bDragAccepted(false) + { + } + ~DragAndDropInfo() + { + pBackground.disposeAndClear(); + } +}; + +struct ImplIMEInfos +{ + OUString aOldTextAfterStartPos; + std::unique_ptr<ExtTextInputAttr[]> pAttribs; + EditPaM aPos; + sal_Int32 nLen; + bool bWasCursorOverwrite; + + ImplIMEInfos( const EditPaM& rPos, OUString aOldTextAfterStartPos ); + ~ImplIMEInfos(); + + void CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL ); + void DestroyAttribs(); +}; + +// #i18881# to be able to identify the positions of changed words +// the positions of each portion need to be saved +typedef std::vector<EditSelection> SpellContentSelections; + +struct SpellInfo +{ + EditPaM aCurSentenceStart; + svx::SpellPortions aLastSpellPortions; + SpellContentSelections aLastSpellContentSelections; + EESpellState eState; + EPaM aSpellStart; + EPaM aSpellTo; + bool bSpellToEnd; + bool bMultipleDoc; + SpellInfo() : eState(EESpellState::Ok), bSpellToEnd(true), bMultipleDoc(false) + { } +}; + +// used for text conversion +struct ConvInfo +{ + EPaM aConvStart; + EPaM aConvTo; + EPaM aConvContinue; // position to start search for next text portion (word) with + bool bConvToEnd; + bool bMultipleDoc; + + ConvInfo() : bConvToEnd(true), bMultipleDoc(false) {} +}; + +struct FormatterFontMetric +{ + sal_uInt16 nMaxAscent; + sal_uInt16 nMaxDescent; + + FormatterFontMetric() : nMaxAscent(0), nMaxDescent(0) { /* nMinLeading = 0xFFFF; */ } + sal_uInt16 GetHeight() const { return nMaxAscent+nMaxDescent; } +}; + +class IdleFormattter : public Idle +{ +private: + EditView* pView; + int nRestarts; + +public: + IdleFormattter(); + virtual ~IdleFormattter() override; + + void DoIdleFormat( EditView* pV ); + void ForceTimeout(); + void ResetRestarts() { nRestarts = 0; } + EditView* GetView() { return pView; } +}; + +class ImpEditView; +/// This is meant just for Calc, where all positions in logical units (twips for LOK) are computed by +/// doing independent pixel-alignment for each cell's size. LOKSpecialPositioning stores +/// both 'output-area' and 'visible-doc-position' in pure logical unit (twips for LOK). +/// This allows the cursor/selection messages to be in regular(print) twips unit like in Writer. +class LOKSpecialPositioning +{ +public: + LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit, const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos); + + void ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos); + + void SetOutputArea(const tools::Rectangle& rOutputArea); + const tools::Rectangle& GetOutputArea() const; + void SetVisDocStartPos(const Point& rVisDocStartPos); + + bool IsVertical() const; + bool IsTopToBottom() const; + + tools::Long GetVisDocLeft() const { return maVisDocStartPos.X(); } + tools::Long GetVisDocTop() const { return maVisDocStartPos.Y(); } + tools::Long GetVisDocRight() const { return maVisDocStartPos.X() + (!IsVertical() ? maOutArea.GetWidth() : maOutArea.GetHeight()); } + tools::Long GetVisDocBottom() const { return maVisDocStartPos.Y() + (!IsVertical() ? maOutArea.GetHeight() : maOutArea.GetWidth()); } + tools::Rectangle GetVisDocArea() const; + + Point GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const; + tools::Rectangle GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const; + + void SetFlags(LOKSpecialFlags eFlags) { meFlags = eFlags; } + bool IsLayoutRTL() { return bool(meFlags & LOKSpecialFlags::LayoutRTL); } + + Point GetRefPoint() const; + +private: + Point convertUnit(const Point& rPos, MapUnit ePosUnit) const; + tools::Rectangle convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const; + + const ImpEditView& mrImpEditView; + tools::Rectangle maOutArea; + Point maVisDocStartPos; + MapUnit meUnit; + LOKSpecialFlags meFlags; +}; + + + +class ImpEditView : public vcl::unohelper::DragAndDropClient +{ + friend class EditView; + friend class EditEngine; + friend class ImpEditEngine; + using vcl::unohelper::DragAndDropClient::dragEnter; + using vcl::unohelper::DragAndDropClient::dragExit; + using vcl::unohelper::DragAndDropClient::dragOver; + +private: + EditView* pEditView; + std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> pCursor; + std::optional<Color> mxBackgroundColor; + /// Containing view shell, if any. + OutlinerViewShell* mpViewShell; + /// Another shell, just listening to our state, if any. + OutlinerViewShell* mpOtherShell; + EditEngine* pEditEngine; + VclPtr<vcl::Window> pOutWin; + EditView::OutWindowSet aOutWindowSet; + std::optional<PointerStyle> mxPointer; + std::unique_ptr<DragAndDropInfo> pDragAndDropInfo; + + css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener; + + + tools::Long nInvMore; + EVControlBits nControl; + sal_uInt32 nTravelXPos; + GetCursorFlags nExtraCursorFlags; + sal_uInt16 nCursorBidiLevel; + sal_uInt16 nScrollDiffX; + bool bReadOnly; + bool bClickedInSelection; + bool bActiveDragAndDropListener; + + Point aAnchorPoint; + tools::Rectangle aOutArea; + Point aVisDocStartPos; + EESelectionMode eSelectionMode; + EditSelection aEditSelection; + EEAnchorMode eAnchorMode; + + /// mechanism to change from the classic refresh mode that simply + // invalidates the area where text was changed. When set, the invalidate + // and the direct repaint of the Window-plugged EditView will be suppressed. + // Instead, a consumer that has registered using an EditViewCallbacks + // incarnation has to handle that. Used e.g. to represent the edited text + // in Draw/Impress in an OverlayObject which avoids evtl. expensive full + // repaints of the EditView(s) + EditViewCallbacks* mpEditViewCallbacks; + std::unique_ptr<LOKSpecialPositioning> mpLOKSpecialPositioning; + bool mbBroadcastLOKViewCursor:1; + bool mbSuppressLOKMessages:1; + bool mbNegativeX:1; + + EditViewCallbacks* getEditViewCallbacks() const + { + return mpEditViewCallbacks; + } + + void lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible); + + void setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks) + { + mpEditViewCallbacks = pEditViewCallbacks; + } + + void InvalidateAtWindow(const tools::Rectangle& rRect); + + css::uno::Reference<css::datatransfer::clipboard::XClipboard> GetClipboard() const; + + void SetBroadcastLOKViewCursor(bool bSet) + { + mbBroadcastLOKViewCursor = bSet; + } + +protected: + + // DragAndDropClient + void dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& dge) override; + void dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& dsde ) override; + void drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde) override; + void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee ) override; + void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) override; + void dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde) override; + + void ShowDDCursor( const tools::Rectangle& rRect ); + void HideDDCursor(); + + void ImplDrawHighlightRect(OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL); + tools::Rectangle ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags, + sal_Int32& nTextPortionStart, const ParaPortion* pParaPortion) const; + +public: + ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ); + virtual ~ImpEditView() override; + + EditView* GetEditViewPtr() { return pEditView; } + + sal_uInt16 GetScrollDiffX() const { return nScrollDiffX; } + void SetScrollDiffX( sal_uInt16 n ) { nScrollDiffX = n; } + + sal_uInt16 GetCursorBidiLevel() const { return nCursorBidiLevel; } + void SetCursorBidiLevel( sal_uInt16 n ) { nCursorBidiLevel = n; } + + Point GetDocPos( const Point& rWindowPos ) const; + Point GetWindowPos( const Point& rDocPos ) const; + tools::Rectangle GetWindowPos( const tools::Rectangle& rDocPos ) const; + + void SetOutputArea( const tools::Rectangle& rRect ); + void ResetOutputArea( const tools::Rectangle& rRect ); + const tools::Rectangle& GetOutputArea() const { return aOutArea; } + + bool IsVertical() const; + bool IsTopToBottom() const; + + bool PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ); + + bool MouseButtonUp( const MouseEvent& rMouseEvent ); + bool MouseButtonDown( const MouseEvent& rMouseEvent ); + void ReleaseMouse(); + bool MouseMove( const MouseEvent& rMouseEvent ); + bool Command(const CommandEvent& rCEvt); + + void CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut ); + void Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial = false, SotClipboardFormatId format = SotClipboardFormatId::NONE); + + void SetVisDocStartPos( const Point& rPos ) { aVisDocStartPos = rPos; } + + tools::Long GetVisDocLeft() const { return aVisDocStartPos.X(); } + tools::Long GetVisDocTop() const { return aVisDocStartPos.Y(); } + tools::Long GetVisDocRight() const { return aVisDocStartPos.X() + ( !IsVertical() ? aOutArea.GetWidth() : aOutArea.GetHeight() ); } + tools::Long GetVisDocBottom() const { return aVisDocStartPos.Y() + ( !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth() ); } + tools::Rectangle GetVisDocArea() const; + + const EditSelection& GetEditSelection() const { return aEditSelection; } + void SetEditSelection( const EditSelection& rEditSelection ); + bool HasSelection() const { return aEditSelection.HasRange(); } + + void SelectionChanged(); + void DrawSelectionXOR() { DrawSelectionXOR( aEditSelection ); } + void DrawSelectionXOR( EditSelection, vcl::Region* pRegion = nullptr, OutputDevice* pTargetDevice = nullptr ); + void GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects); + + void ScrollStateChange(); + + OutputDevice& GetOutputDevice() const; + weld::Widget* GetPopupParent(tools::Rectangle& rRect) const; + vcl::Window* GetWindow() const { return pOutWin; } + + void SetSelectionMode( EESelectionMode eMode ); + + inline PointerStyle GetPointer(); + + inline vcl::Cursor* GetCursor(); + + void AddDragAndDropListeners(); + void RemoveDragAndDropListeners(); + + bool IsBulletArea( const Point& rPos, sal_Int32* pPara ); + +// For the Selection Engine... + void CreateAnchor(); + void DeselectAll(); + bool SetCursorAtPoint( const Point& rPointPixel ); + bool IsSelectionAtPoint( const Point& rPosPixel ); + bool IsInSelection( const EditPaM& rPaM ); + + bool IsSelectionFullPara() const; + bool IsSelectionInSinglePara() const; + + void SetAnchorMode( EEAnchorMode eMode ); + EEAnchorMode GetAnchorMode() const { return eAnchorMode; } + void CalcAnchorPoint(); + void RecalcOutputArea(); + + tools::Rectangle GetEditCursor() const; + + void ShowCursor( bool bGotoCursor, bool bForceVisCursor ); + Pair Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck = ScrollRangeCheck::NoNegative ); + + void SetInsertMode( bool bInsert ); + bool IsInsertMode() const { return !( nControl & EVControlBits::OVERWRITE ); } + + bool IsPasteEnabled() const { return bool( nControl & EVControlBits::ENABLEPASTE ); } + + bool DoSingleLinePaste() const { return bool( nControl & EVControlBits::SINGLELINEPASTE ); } + bool DoAutoScroll() const { return bool( nControl & EVControlBits::AUTOSCROLL ); } + bool DoAutoSize() const { return bool( nControl & EVControlBits::AUTOSIZE ); } + bool DoAutoWidth() const { return bool( nControl & EVControlBits::AUTOSIZEX); } + bool DoAutoHeight() const { return bool( nControl & EVControlBits::AUTOSIZEY); } + bool DoInvalidateMore() const { return bool( nControl & EVControlBits::INVONEMORE ); } + + void SetBackgroundColor( const Color& rColor ); + const Color& GetBackgroundColor() const; + + /// Informs this edit view about which view shell contains it. + void RegisterViewShell(OutlinerViewShell* pViewShell); + const OutlinerViewShell* GetViewShell() const; + /// Informs this edit view about which other shell listens to it. + void RegisterOtherShell(OutlinerViewShell* pViewShell); + + bool IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong ); + OUString SpellIgnoreWord(); + + const SvxFieldItem* GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const; + void DeleteSelected(); + + // If possible invalidate more than OutputArea, for the DrawingEngine text frame + void SetInvalidateMore( sal_uInt16 nPixel ) { nInvMore = nPixel; } + sal_uInt16 GetInvalidateMore() const { return static_cast<sal_uInt16>(nInvMore); } + + void InitLOKSpecialPositioning(MapUnit eUnit, const tools::Rectangle& rOutputArea, + const Point& rVisDocStartPos); + void SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea); + const tools::Rectangle & GetLOKSpecialOutputArea() const; + void SetLOKSpecialVisArea(const tools::Rectangle& rVisArea); + tools::Rectangle GetLOKSpecialVisArea() const; + bool HasLOKSpecialPositioning() const; + + void SetLOKSpecialFlags(LOKSpecialFlags eFlags); + + void SuppressLOKMessages(bool bSet) { mbSuppressLOKMessages = bSet; } + bool IsSuppressLOKMessages() const { return mbSuppressLOKMessages; } + + void SetNegativeX(bool bSet) { mbNegativeX = bSet; } + bool IsNegativeX() const { return mbNegativeX; } +}; + + +// ImpEditEngine + + +class ImpEditEngine : public SfxListener, public svl::StyleSheetUser +{ + friend class EditEngine; + + typedef EditEngine::ViewsType ViewsType; + +private: + std::shared_ptr<editeng::SharedVclResources> pSharedVCL; + + // Document Specific data ... + ParaPortionList maParaPortionList; // Formatting + Size maPaperSize; // Layout + Size maMinAutoPaperSize; // Layout ? + Size maMaxAutoPaperSize; // Layout ? + tools::Long mnMinColumnWrapHeight = 0; // Corresponds to graphic object height + EditDoc maEditDoc; // Document content + + // Engine Specific data ... + EditEngine* pEditEngine; + ViewsType aEditViews; + EditView* pActiveView; + std::unique_ptr<TextRanger> pTextRanger; + + SfxStyleSheetPool* pStylePool; + SfxItemPool* pTextObjectPool; + + VclPtr< VirtualDevice> pVirtDev; + VclPtr< OutputDevice > pRefDev; + VclPtr<VirtualDevice> mpOwnDev; + + svtools::ColorConfig maColorConfig; + + mutable std::unique_ptr<SfxItemSet> pEmptyItemSet; + EditUndoManager* pUndoManager; + std::optional<ESelection> moUndoMarkSelection; + + std::unique_ptr<ImplIMEInfos> mpIMEInfos; + + OUString aWordDelimiters; + + EditSelFunctionSet aSelFuncSet; + EditSelectionEngine aSelEngine; + + Color maBackgroundColor; + + double mfFontScaleX; + double mfFontScaleY; + double mfSpacingScaleX; + double mfSpacingScaleY; + bool mbRoundToNearestPt; + + CharCompressType mnAsianCompressionMode; + + EEHorizontalTextDirection eDefaultHorizontalTextDirection; + + sal_Int32 mnBigTextObjectStart; + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller; + css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator; + std::unique_ptr<SpellInfo> pSpellInfo; + mutable css::uno::Reference < css::i18n::XBreakIterator > xBI; + mutable css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > xISC; + + std::unique_ptr<ConvInfo> pConvInfo; + + OUString maAutoCompleteText; + + InternalEditStatus maStatus; + + LanguageType meDefLanguage; + + OnDemandLocaleDataWrapper xLocaleDataWrapper; + OnDemandTransliterationWrapper xTransliterationWrapper; + + // For Formatting / Update... + std::vector<std::unique_ptr<DeletedNodeInfo> > aDeletedNodes; + tools::Rectangle aInvalidRect; + tools::Long nCurTextHeight; + tools::Long nCurTextHeightNTP; // without trailing empty paragraphs + sal_uInt16 nOnePixelInRef; + + IdleFormattter aIdleFormatter; + + Timer aOnlineSpellTimer; + + // For Chaining + sal_Int32 mnOverflowingPara = -1; + sal_Int32 mnOverflowingLine = -1; + bool mbNeedsChainingHandling = false; + + sal_Int16 mnColumns = 1; + sal_Int32 mnColumnSpacing = 0; + + // If it is detected at one point that the StatusHdl has to be called, but + // this should not happen immediately (critical section): + Timer aStatusTimer; + Size aLOKSpecialPaperSize; + + Link<EditStatus&,void> aStatusHdlLink; + Link<EENotify&,void> aNotifyHdl; + Link<HtmlImportInfo&,void> aHtmlImportHdl; + Link<RtfImportInfo&,void> aRtfImportHdl; + Link<MoveParagraphsInfo&,void> aBeginMovingParagraphsHdl; + Link<MoveParagraphsInfo&,void> aEndMovingParagraphsHdl; + Link<PasteOrDropInfos&,void> aBeginPasteOrDropHdl; + Link<PasteOrDropInfos&,void> aEndPasteOrDropHdl; + Link<LinkParamNone*,void> aModifyHdl; + Link<EditView*,void> maBeginDropHdl; + Link<EditView*,void> maEndDropHdl; + + bool mbKernAsianPunctuation : 1; + bool mbAddExtLeading : 1; + bool mbIsFormatting : 1; + bool mbFormatted : 1; + bool mbInSelection : 1; + bool mbIsInUndo : 1; + bool mbUpdateLayout : 1; + bool mbUndoEnabled : 1; + bool mbDowning : 1; + bool mbUseAutoColor : 1; + bool mbForceAutoColor : 1; + bool mbCallParaInsertedOrDeleted : 1; + bool mbFirstWordCapitalization : 1; // specifies if auto-correction should capitalize the first word or not + bool mbLastTryMerge : 1; + bool mbReplaceLeadingSingleQuotationMark : 1; + bool mbSkipOutsideFormat : 1; + bool mbFuzzing : 1; + + bool mbNbspRunNext; // can't be a bitfield as it is passed as bool& + + // Methods... + + + void ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck = false ); + void TextModified(); + void CalcHeight( ParaPortion* pPortion ); + + void InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge = false ); + void ResetUndoManager(); + bool HasUndoManager() const { return pUndoManager != nullptr; } + + std::unique_ptr<EditUndoSetAttribs> CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet ); + + std::unique_ptr<EditTextObject> GetEmptyTextObject(); + + std::tuple<const ParaPortion*, const EditLine*, tools::Long> GetPortionAndLine(Point aDocPos); + EditPaM GetPaM( Point aDocPos, bool bSmart = true ); + bool IsTextPos(const Point& rDocPos, sal_uInt16 nBorder); + tools::Long GetXPos(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart = false) const; + tools::Long GetPortionXOffset(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const; + sal_Int32 GetChar(const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nX, bool bSmart = true); + Range GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const; + + void ParaAttribsToCharAttribs( ContentNode* pNode ); + void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const; + + std::unique_ptr<EditTextObject> + CreateTextObject(EditSelection aSelection, SfxItemPool*, bool bAllowBigObjects = false, sal_Int32 nBigObjStart = 0); + EditSelection InsertTextObject( const EditTextObject&, EditPaM aPaM ); + EditSelection PasteText( css::uno::Reference< css::datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format = SotClipboardFormatId::NONE); + + void CheckPageOverflow(); + + void Clear(); + EditPaM RemoveText(); + bool CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ); + void CreateAndInsertEmptyLine( ParaPortion* pParaPortion ); + bool FinishCreateLines( ParaPortion* pParaPortion ); + void CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStartPos /*, sal_Bool bCreateBlockPortions */ ); + void RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ); + sal_Int32 SplitTextPortion( ParaPortion* pParaPortion, sal_Int32 nPos, EditLine* pCurLine = nullptr ); + void SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut = nullptr ); + void RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont ); + void CheckAutoPageSize(); + + void ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate ); + void ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace ); + EditPaM ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward = false ); + EditPaM ImpDeleteSelection(const EditSelection& rCurSel); + EditPaM ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs = true ); + EditPaM ImpInsertParaBreak( const EditSelection& rEditSelection ); + EditPaM ImpInsertText(const EditSelection& aCurEditSelection, const OUString& rStr); + EditPaM ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem); + void ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ); + void ImpRemoveParagraph( sal_Int32 nPara ); + EditSelection ImpMoveParagraphs( Range aParagraphs, sal_Int32 nNewPos ); + + EditPaM ImpFastInsertText( EditPaM aPaM, const OUString& rStr ); + EditPaM ImpFastInsertParagraph( sal_Int32 nPara ); + + bool ImpCheckRefMapMode(); + + bool ImplHasText() const; + + void ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ); + + void InsertContent( ContentNode* pNode, sal_Int32 nPos ); + EditPaM SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ); + EditPaM ConnectContents( sal_Int32 nLeftNode, bool bBackward ); + + void ShowParagraph( sal_Int32 nParagraph, bool bShow ); + + EditPaM PageUp( const EditPaM& rPaM, EditView const * pView); + EditPaM PageDown( const EditPaM& rPaM, EditView const * pView); + EditPaM CursorUp( const EditPaM& rPaM, EditView const * pEditView ); + EditPaM CursorDown( const EditPaM& rPaM, EditView const * pEditView ); + EditPaM CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL ); + EditPaM CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL ); + EditPaM CursorStartOfLine( const EditPaM& rPaM ); + EditPaM CursorEndOfLine( const EditPaM& rPaM ); + static EditPaM CursorStartOfParagraph( const EditPaM& rPaM ); + static EditPaM CursorEndOfParagraph( const EditPaM& rPaM ); + EditPaM CursorStartOfDoc(); + EditPaM CursorEndOfDoc(); + EditPaM WordLeft( const EditPaM& rPaM ); + EditPaM WordRight( const EditPaM& rPaM, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES ); + EditPaM StartOfWord( const EditPaM& rPaM ); + EditPaM EndOfWord( const EditPaM& rPaM ); + EditSelection SelectWord( const EditSelection& rCurSelection, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, bool bAcceptStartOfWord = true ); + EditSelection SelectSentence( const EditSelection& rCurSel ) const; + EditPaM CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bToLeft ); + EditPaM CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ); + + + void InitScriptTypes( sal_Int32 nPara ); + sal_uInt16 GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const; + SvtScriptType GetItemScriptType( const EditSelection& rSel ) const; + bool IsScriptChange( const EditPaM& rPaM ) const; + bool HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const; + + bool ImplCalcAsianCompression( ContentNode* pNode, TextPortion* pTextPortion, sal_Int32 nStartPos, + sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, bool bManipulateDXArray ); + void ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth ); + + void ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex); + static LanguageType ImplCalcDigitLang(LanguageType eCurLang); + static void ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eLang); + static OUString convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang); + + EditPaM ReadText( SvStream& rInput, EditSelection aSel ); + EditPaM ReadRTF( SvStream& rInput, EditSelection aSel ); + EditPaM ReadXML( SvStream& rInput, EditSelection aSel ); + EditPaM ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs ); + ErrCode WriteText( SvStream& rOutput, EditSelection aSel ); + ErrCode WriteRTF( SvStream& rOutput, EditSelection aSel ); + void WriteXML(SvStream& rOutput, const EditSelection& rSel); + + void WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ); + bool WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ); + sal_Int32 LogicToTwips( sal_Int32 n ); + + double scaleXSpacingValue(tools::Long nXValue) const + { + if (!maStatus.DoStretch() || mfSpacingScaleX == 100.0) + return nXValue; + + return double(nXValue) * mfSpacingScaleX / 100.0; + } + + double scaleYSpacingValue(sal_uInt16 nYValue) const + { + if (!maStatus.DoStretch() || mfSpacingScaleY == 100.0) + return nYValue; + + return double(nYValue) * mfSpacingScaleY / 100.0; + } + + double scaleYFontValue(sal_uInt16 nYValue) const + { + if (!maStatus.DoStretch() || (mfFontScaleY == 100.0)) + return nYValue; + + return double(nYValue) * mfFontScaleY / 100.0; + } + + double scaleXFontValue(tools::Long nXValue) const + { + if (!maStatus.DoStretch() || (mfFontScaleX == 100.0)) + return nXValue; + + return double(nXValue) * mfFontScaleY / 100.0; + } + + void setRoundToNearestPt(bool bRound) { mbRoundToNearestPt = bRound; } + double roundToNearestPt(double fInput) const; + + ContentNode* GetPrevVisNode( ContentNode const * pCurNode ); + ContentNode* GetNextVisNode( ContentNode const * pCurNode ); + + const ParaPortion* GetPrevVisPortion( const ParaPortion* pCurPortion ) const; + const ParaPortion* GetNextVisPortion( const ParaPortion* pCurPortion ) const; + + void SetBackgroundColor( const Color& rColor ) { maBackgroundColor = rColor; } + const Color& GetBackgroundColor() const { return maBackgroundColor; } + + tools::Long CalcVertLineSpacing(Point& rStartPos) const; + + Color GetAutoColor() const; + void EnableAutoColor( bool b ) { mbUseAutoColor = b; } + bool IsAutoColorEnabled() const { return mbUseAutoColor; } + void ForceAutoColor( bool b ) { mbForceAutoColor = b; } + bool IsForceAutoColor() const { return mbForceAutoColor; } + + inline VirtualDevice* GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode ); + void EraseVirtualDevice() { pVirtDev.disposeAndClear(); } + + DECL_LINK( StatusTimerHdl, Timer *, void); + DECL_LINK( IdleFormatHdl, Timer *, void); + DECL_LINK( OnlineSpellHdl, Timer *, void); + DECL_LINK( DocModified, LinkParamNone*, void ); + + void CheckIdleFormatter(); + + inline const ParaPortion* FindParaPortion( const ContentNode* pNode ) const; + inline ParaPortion* FindParaPortion( ContentNode const * pNode ); + + css::uno::Reference< css::datatransfer::XTransferable > CreateTransferable( const EditSelection& rSelection ); + + void SetValidPaperSize( const Size& rSz ); + + css::uno::Reference < css::i18n::XBreakIterator > const & ImplGetBreakIterator() const; + css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > const & ImplGetInputSequenceChecker() const; + + void ImplUpdateOverflowingParaNum(tools::Long); + void ImplUpdateOverflowingLineNum(tools::Long, sal_uInt32, tools::Long); + + void CreateSpellInfo( bool bMultipleDocs ); + /// Obtains a view shell ID from the active EditView. + ViewShellId CreateViewShellId(); + + ImpEditEngine(EditEngine* pEditEngine, SfxItemPool* pPool); + void InitDoc(bool bKeepParaAttribs); + EditDoc& GetEditDoc() { return maEditDoc; } + const EditDoc& GetEditDoc() const { return maEditDoc; } + + const ParaPortionList& GetParaPortions() const { return maParaPortionList; } + ParaPortionList& GetParaPortions() { return maParaPortionList; } + + tools::Long Calc1ColumnTextHeight(tools::Long* pHeightNTP); + + void IdleFormatAndLayout(EditView* pCurView) { aIdleFormatter.DoIdleFormat(pCurView); } + +protected: + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + +public: + virtual ~ImpEditEngine() override; + ImpEditEngine(const ImpEditEngine&) = delete; + ImpEditEngine& operator=(const ImpEditEngine&) = delete; + + inline EditUndoManager& GetUndoManager(); + inline EditUndoManager* SetUndoManager(EditUndoManager* pNew); + + // @return the previous bUpdateLayout state + bool SetUpdateLayout( bool bUpdate, EditView* pCurView = nullptr, bool bForceUpdate = false ); + bool IsUpdateLayout() const { return mbUpdateLayout; } + + ViewsType& GetEditViews() { return aEditViews; } + const ViewsType& GetEditViews() const { return aEditViews; } + + const Size& GetPaperSize() const { return maPaperSize; } + void SetPaperSize(const Size& rSize) { maPaperSize = rSize; } + + void SetVertical( bool bVertical); + bool IsEffectivelyVertical() const { return GetEditDoc().IsEffectivelyVertical(); } + bool IsTopToBottom() const { return GetEditDoc().IsTopToBottom(); } + bool GetVertical() const { return GetEditDoc().GetVertical(); } + void SetRotation( TextRotation nRotation); + TextRotation GetRotation() const { return GetEditDoc().GetRotation(); } + + void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing); + + bool IsPageOverflow( ) const; + + void SetFixedCellHeight( bool bUseFixedCellHeight ); + bool IsFixedCellHeight() const { return GetEditDoc().IsFixedCellHeight(); } + + void SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) { eDefaultHorizontalTextDirection = eHTextDir; } + EEHorizontalTextDirection GetDefaultHorizontalTextDirection() const { return eDefaultHorizontalTextDirection; } + + + void InitWritingDirections( sal_Int32 nPara ); + bool IsRightToLeft( sal_Int32 nPara ) const; + sal_uInt8 GetRightToLeft( sal_Int32 nPara, sal_Int32 nChar, sal_Int32* pStart = nullptr, sal_Int32* pEnd = nullptr ); + bool HasDifferentRTLLevels( const ContentNode* pNode ); + + void SetTextRanger( std::unique_ptr<TextRanger> pRanger ); + TextRanger* GetTextRanger() const { return pTextRanger.get(); } + + const Size& GetMinAutoPaperSize() const { return maMinAutoPaperSize; } + void SetMinAutoPaperSize(const Size& rSize) { maMinAutoPaperSize = rSize; } + + const Size& GetMaxAutoPaperSize() const { return maMaxAutoPaperSize; } + void SetMaxAutoPaperSize(const Size& rSize) { maMaxAutoPaperSize = rSize; } + + void SetMinColumnWrapHeight(tools::Long nVal) { mnMinColumnWrapHeight = nVal; } + + void FormatDoc(); + void FormatFullDoc(); + void UpdateViews( EditView* pCurView = nullptr ); + void Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ); + void Paint(OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly = false, Degree10 nOrientation = 0_deg10); + + bool MouseButtonUp( const MouseEvent& rMouseEvent, EditView* pView ); + bool MouseButtonDown( const MouseEvent& rMouseEvent, EditView* pView ); + void ReleaseMouse(); + bool MouseMove( const MouseEvent& rMouseEvent, EditView* pView ); + bool Command(const CommandEvent& rCEvt, EditView* pView); + + EditSelectionEngine& GetSelEngine() { return aSelEngine; } + OUString GetSelected( const EditSelection& rSel ) const; + + const SfxItemSet& GetEmptyItemSet() const; + + void UpdateSelections(); + + void EnableUndo( bool bEnable ); + bool IsUndoEnabled() const { return mbUndoEnabled; } + void SetUndoMode( bool b ) { mbIsInUndo = b; } + bool IsInUndo() const { return mbIsInUndo; } + + void SetCallParaInsertedOrDeleted( bool b ) { mbCallParaInsertedOrDeleted = b; } + bool IsCallParaInsertedOrDeleted() const { return mbCallParaInsertedOrDeleted; } + + bool IsFormatted() const { return mbFormatted; } + bool IsFormatting() const { return mbIsFormatting; } + + void SetText(const OUString& rText); + EditPaM DeleteSelected(const EditSelection& rEditSelection); + EditPaM InsertTextUserInput( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite ); + EditPaM InsertText(const EditSelection& aCurEditSelection, const OUString& rStr); + EditPaM AutoCorrect( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite, vcl::Window const * pFrameWin = nullptr ); + EditPaM DeleteLeftOrRight( const EditSelection& rEditSelection, sal_uInt8 nMode, DeleteMode nDelMode ); + EditPaM InsertParaBreak(const EditSelection& rEditSelection); + EditPaM InsertLineBreak(const EditSelection& aEditSelection); + EditPaM InsertTab(const EditSelection& rEditSelection); + EditPaM InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld); + bool UpdateFields(); + + EditPaM Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs = nullptr); + void Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel); + + std::unique_ptr<EditTextObject> CreateTextObject(); + std::unique_ptr<EditTextObject> CreateTextObject(const EditSelection& rSel); + void SetText( const EditTextObject& rTextObject ); + EditSelection InsertText( const EditTextObject& rTextObject, EditSelection aSel ); + + EditSelection const & MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ); + + EditSelection MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos, EditView* pCurView ); + + tools::Long CalcTextHeight( tools::Long* pHeightNTP ); + sal_uInt32 GetTextHeight() const; + sal_uInt32 GetTextHeightNTP() const; + sal_uInt32 CalcTextWidth( bool bIgnoreExtraSpace); + sal_uInt32 CalcParaWidth( sal_Int32 nParagraph, bool bIgnoreExtraSpace ); + sal_uInt32 CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace); + sal_Int32 GetLineCount( sal_Int32 nParagraph ) const; + sal_Int32 GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const; + void GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const; + sal_Int32 GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const; + sal_uInt16 GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ); + sal_uInt32 GetParaHeight( sal_Int32 nParagraph ); + + SfxItemSet GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags = GetAttribsFlags::ALL ) const; + SfxItemSet GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib = EditEngineAttribs::All ); + void SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial = SetAttribsMode::NONE, bool bSetSelection = true ); + void RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ); + void RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich = 0, bool bRemoveFeatures = false ); + void SetFlatMode( bool bFlat ); + + void SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ); + const SfxItemSet& GetParaAttribs( sal_Int32 nPara ) const; + + bool HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const; + const SfxPoolItem& GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const; + template<class T> + const T& GetParaAttrib( sal_Int32 nPara, TypedWhichId<T> nWhich ) const + { + return static_cast<const T&>(GetParaAttrib(nPara, sal_uInt16(nWhich))); + } + + tools::Rectangle PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags = GetCursorFlags::NONE ); + tools::Rectangle GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, + sal_Int32 nIndex, GetCursorFlags nFlags); + + bool IsModified() const { return maEditDoc.IsModified(); } + void SetModifyFlag(bool b) { maEditDoc.SetModified( b ); } + void SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) { aModifyHdl = rLink; } + + bool IsInSelectionMode() const { return mbInSelection; } + +// For Undo/Redo + void Undo( EditView* pView ); + void Redo( EditView* pView ); + +// OV-Special + void InvalidateFromParagraph( sal_Int32 nFirstInvPara ); + EditPaM InsertParagraph( sal_Int32 nPara ); + std::optional<EditSelection> SelectParagraph( sal_Int32 nPara ); + + void SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) { aStatusHdlLink = rLink; } + const Link<EditStatus&,void>& GetStatusEventHdl() const { return aStatusHdlLink; } + + void SetNotifyHdl( const Link<EENotify&,void>& rLink ) { aNotifyHdl = rLink; } + const Link<EENotify&,void>& GetNotifyHdl() const { return aNotifyHdl; } + + void FormatAndLayout( EditView* pCurView = nullptr, bool bCalledFromUndo = false ); + + const svtools::ColorConfig& GetColorConfig() const { return maColorConfig; } + static bool IsVisualCursorTravelingEnabled(); + static bool DoVisualCursorTraveling(); + + EditSelection ConvertSelection( sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ); + inline EPaM CreateEPaM( const EditPaM& rPaM ) const; + inline EditPaM CreateEditPaM( const EPaM& rEPaM ); + inline ESelection CreateESel( const EditSelection& rSel ) const; + inline EditSelection CreateSel( const ESelection& rSel ); + + void SetStyleSheetPool( SfxStyleSheetPool* pSPool ); + SfxStyleSheetPool* GetStyleSheetPool() const { return pStylePool; } + + void SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle ); + void SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ); + const SfxStyleSheet* GetStyleSheet( sal_Int32 nPara ) const; + SfxStyleSheet* GetStyleSheet( sal_Int32 nPara ); + + void UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle ); + void RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle ); + + bool isUsedByModel() const override { return true; } + + OutputDevice* GetRefDevice() const { return pRefDev.get(); } + void SetRefDevice( OutputDevice* pRefDef ); + + const MapMode& GetRefMapMode() const { return pRefDev->GetMapMode(); } + void SetRefMapMode( const MapMode& rMapMode ); + + InternalEditStatus& GetStatus() { return maStatus; } + void CallStatusHdl(); + void DelayedCallStatusHdl() { aStatusTimer.Start(); } + + void UndoActionStart( sal_uInt16 nId ); + void UndoActionStart( sal_uInt16 nId, const ESelection& rSel ); + void UndoActionEnd(); + + EditView* GetActiveView() const { return pActiveView; } + void SetActiveView( EditView* pView ); + + css::uno::Reference< css::linguistic2::XSpellChecker1 > const & + GetSpeller(); + void SetSpeller( css::uno::Reference< css::linguistic2::XSpellChecker1 > const &xSpl ) + { xSpeller = xSpl; } + const css::uno::Reference< css::linguistic2::XHyphenator >& + GetHyphenator() const { return xHyphenator; } + void SetHyphenator( css::uno::Reference< css::linguistic2::XHyphenator > const &xHyph ) + { xHyphenator = xHyph; } + + void GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const; + void SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ); + + SpellInfo* GetSpellInfo() const { return pSpellInfo.get(); } + + void SetDefaultLanguage(LanguageType eLang) { meDefLanguage = eLang; } + LanguageType GetDefaultLanguage() const { return meDefLanguage; } + + editeng::LanguageSpan GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const; + css::lang::Locale GetLocale( const EditPaM& rPaM ) const; + + void DoOnlineSpelling( ContentNode* pThisNodeOnly = nullptr, bool bSpellAtCursorPos = false, bool bInterruptible = true ); + EESpellState Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc); + EESpellState HasSpellErrors(); + void ClearSpellErrors(); + EESpellState StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent); + css::uno::Reference< css::linguistic2::XSpellAlternatives > + ImpSpell( EditView* pEditView ); + + // text conversion functions + void Convert(EditView* pEditView, weld::Widget* pDialogParent, LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc); + void ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang, EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange, + bool bAllowImplicitChangesForNotConvertibleText, LanguageType nTargetLang, const vcl::Font *pTargetFont ); + ConvInfo * GetConvInfo() const { return pConvInfo.get(); } + bool HasConvertibleTextPortion( LanguageType nLang ); + void SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ); + + // returns true if input sequence checking should be applied + bool IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const; + + //find the next error within the given selection - forward only! + css::uno::Reference< css::linguistic2::XSpellAlternatives > + ImpFindNextError(EditSelection& rSelection); + //spell and return a sentence + bool SpellSentence(EditView const & rView, svx::SpellPortions& rToFill ); + //put spelling back to start of current sentence - needed after switch of grammar support + void PutSpellingToSentenceStart( EditView const & rEditView ); + //applies a changed sentence + void ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ); + //adds one or more portions of text to the SpellPortions depending on language changes + void AddPortionIterated( + EditView const & rEditView, + const EditSelection &rSel, + const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill); + //adds one portion to the SpellPortions + void AddPortion( + const EditSelection &rSel, + const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill, + bool bIsField ); + + bool Search( const SvxSearchItem& rSearchItem, EditView* pView ); + bool ImpSearch( const SvxSearchItem& rSearchItem, const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel ); + sal_Int32 StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem ); + bool HasText( const SvxSearchItem& rSearchItem ); + + void SetEditTextObjectPool( SfxItemPool* pP ) { pTextObjectPool = pP; } + SfxItemPool* GetEditTextObjectPool() const { return pTextObjectPool; } + + const SvxNumberFormat * GetNumberFormat( const ContentNode* pNode ) const; + sal_Int32 GetSpaceBeforeAndMinLabelWidth( const ContentNode *pNode, sal_Int32 *pnSpaceBefore = nullptr, sal_Int32 *pnMinLabelWidth = nullptr ) const; + + const SvxLRSpaceItem& GetLRSpaceItem( ContentNode* pNode ); + SvxAdjust GetJustification( sal_Int32 nPara ) const; + SvxCellJustifyMethod GetJustifyMethod( sal_Int32 nPara ) const; + SvxCellVerJustify GetVerJustification( sal_Int32 nPara ) const; + + void setScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY); + + void getFontScale(double& rX, double& rY) const + { + rX = mfFontScaleX; + rY = mfFontScaleY; + } + + void getSpacingScale(double& rX, double& rY) const + { + rX = mfSpacingScaleX; + rY = mfSpacingScaleY; + } + + sal_Int32 GetBigTextObjectStart() const { return mnBigTextObjectStart; } + + EditEngine* GetEditEnginePtr() const { return pEditEngine; } + + void StartOnlineSpellTimer() { aOnlineSpellTimer.Start(); } + void StopOnlineSpellTimer() { aOnlineSpellTimer.Stop(); } + + const OUString& GetAutoCompleteText() const { return maAutoCompleteText; } + void SetAutoCompleteText(const OUString& rStr, bool bUpdateTipWindow); + + EditSelection TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode ); + short ReplaceTextOnly( ContentNode* pNode, sal_Int32 nCurrentStart, std::u16string_view rText, const css::uno::Sequence< sal_Int32 >& rOffsets ); + + void SetAsianCompressionMode( CharCompressType n ); + CharCompressType GetAsianCompressionMode() const { return mnAsianCompressionMode; } + + void SetKernAsianPunctuation( bool b ); + bool IsKernAsianPunctuation() const { return mbKernAsianPunctuation; } + + sal_Int32 GetOverflowingParaNum() const { return mnOverflowingPara; } + sal_Int32 GetOverflowingLineNum() const { return mnOverflowingLine; } + void ClearOverflowingParaNum() { mnOverflowingPara = -1; } + + + void SetAddExtLeading( bool b ); + bool IsAddExtLeading() const { return mbAddExtLeading; } + + static std::shared_ptr<SvxForbiddenCharactersTable> const & GetForbiddenCharsTable(); + static void SetForbiddenCharsTable( const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars ); + + /** sets a link that is called at the beginning of a drag operation at an edit view */ + void SetBeginDropHdl( const Link<EditView*,void>& rLink ) { maBeginDropHdl = rLink; } + const Link<EditView*,void>& GetBeginDropHdl() const { return maBeginDropHdl; } + + /** sets a link that is called at the end of a drag operation at an edit view */ + void SetEndDropHdl( const Link<EditView*,void>& rLink ) { maEndDropHdl = rLink; } + const Link<EditView*,void>& GetEndDropHdl() const { return maEndDropHdl; } + + /// specifies if auto-correction should capitalize the first word or not (default is on) + void SetFirstWordCapitalization( bool bCapitalize ) { mbFirstWordCapitalization = bCapitalize; } + bool IsFirstWordCapitalization() const { return mbFirstWordCapitalization; } + + /** specifies if auto-correction should replace a leading single quotation + mark (apostrophe) or not (default is on) */ + void SetReplaceLeadingSingleQuotationMark( bool bReplace ) { mbReplaceLeadingSingleQuotationMark = bReplace; } + bool IsReplaceLeadingSingleQuotationMark() const { return mbReplaceLeadingSingleQuotationMark; } + + /** Whether last AutoCorrect inserted a NO-BREAK SPACE that may need to be removed again. */ + bool IsNbspRunNext() const { return mbNbspRunNext; } + + void EnableSkipOutsideFormat(bool set) { mbSkipOutsideFormat = set; } + + void Dispose(); + void SetLOKSpecialPaperSize(const Size& rSize) { aLOKSpecialPaperSize = rSize; } + const Size& GetLOKSpecialPaperSize() const { return aLOKSpecialPaperSize; } + + enum class CallbackResult + { + Continue, + SkipThisPortion, // Do not call callback until next portion + Stop, // Stop iteration + }; + struct LineAreaInfo + { + ParaPortion& rPortion; // Current ParaPortion + EditLine* pLine; // Current line, or nullptr for paragraph start + tools::Long nHeightNeededToNotWrap; + tools::Rectangle aArea; // The area for the line (or for rPortion's first line offset) + // Bottom coordinate *does not* belong to the area + sal_Int32 nPortion; + sal_Int32 nLine; + sal_Int16 nColumn; // Column number; when overflowing, equal to total number of columns + }; + using IterateLinesAreasFunc = std::function<CallbackResult(const LineAreaInfo&)>; + enum IterFlag // bitmask + { + none = 0, + inclILS = 1, // rArea includes interline space + }; + + void IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions); + + tools::Long GetColumnWidth(const Size& rPaperSize) const; + Point MoveToNextLine(Point& rMovePos, tools::Long nLineHeight, sal_Int16& nColumn, + Point aOrigin, tools::Long* pnHeightNeededToNotWrap = nullptr) const; + + tools::Long getWidthDirectionAware(const Size& sz) const; + tools::Long getHeightDirectionAware(const Size& sz) const; + void adjustXDirectionAware(Point& pt, tools::Long x) const; + void adjustYDirectionAware(Point& pt, tools::Long y) const; + void setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const; + void setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const; + tools::Long getYOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; + bool isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; + // Offset of the rectangle's direction-aware corners in document coordinates + tools::Long getBottomDocOffset(const tools::Rectangle& rect) const; + Size getTopLeftDocOffset(const tools::Rectangle& rect) const; +}; + +inline EPaM ImpEditEngine::CreateEPaM( const EditPaM& rPaM ) const +{ + const ContentNode* pNode = rPaM.GetNode(); + return EPaM(maEditDoc.GetPos(pNode), rPaM.GetIndex()); +} + +inline EditPaM ImpEditEngine::CreateEditPaM( const EPaM& rEPaM ) +{ + DBG_ASSERT( rEPaM.nPara < maEditDoc.Count(), "CreateEditPaM: invalid paragraph" ); + DBG_ASSERT( maEditDoc[ rEPaM.nPara ]->Len() >= rEPaM.nIndex, "CreateEditPaM: invalid Index" ); + return EditPaM( maEditDoc[ rEPaM.nPara], rEPaM.nIndex ); +} + +inline ESelection ImpEditEngine::CreateESel( const EditSelection& rSel ) const +{ + const ContentNode* pStartNode = rSel.Min().GetNode(); + const ContentNode* pEndNode = rSel.Max().GetNode(); + ESelection aESel; + aESel.nStartPara = maEditDoc.GetPos( pStartNode ); + aESel.nStartPos = rSel.Min().GetIndex(); + aESel.nEndPara = maEditDoc.GetPos( pEndNode ); + aESel.nEndPos = rSel.Max().GetIndex(); + return aESel; +} + +inline EditSelection ImpEditEngine::CreateSel( const ESelection& rSel ) +{ + DBG_ASSERT( rSel.nStartPara < maEditDoc.Count(), "CreateSel: invalid start paragraph" ); + DBG_ASSERT( rSel.nEndPara < maEditDoc.Count(), "CreateSel: invalid end paragraph" ); + EditSelection aSel; + aSel.Min().SetNode( maEditDoc[ rSel.nStartPara ] ); + aSel.Min().SetIndex( rSel.nStartPos ); + aSel.Max().SetNode( maEditDoc[ rSel.nEndPara ] ); + aSel.Max().SetIndex( rSel.nEndPos ); + DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "CreateSel: incorrect selection!" ); + return aSel; +} + +inline VirtualDevice* ImpEditEngine::GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode ) +{ + if ( !pVirtDev ) + pVirtDev = VclPtr<VirtualDevice>::Create(); + + if ( ( pVirtDev->GetMapMode().GetMapUnit() != rMapMode.GetMapUnit() ) || + ( pVirtDev->GetMapMode().GetScaleX() != rMapMode.GetScaleX() ) || + ( pVirtDev->GetMapMode().GetScaleY() != rMapMode.GetScaleY() ) ) + { + MapMode aMapMode( rMapMode ); + aMapMode.SetOrigin( Point( 0, 0 ) ); + pVirtDev->SetMapMode( aMapMode ); + } + + pVirtDev->SetDrawMode( nDrawMode ); + + return pVirtDev; +} + +inline EditUndoManager& ImpEditEngine::GetUndoManager() +{ + if ( !pUndoManager ) + { + pUndoManager = new EditUndoManager(); + pUndoManager->SetEditEngine(pEditEngine); + } + return *pUndoManager; +} + +inline EditUndoManager* ImpEditEngine::SetUndoManager(EditUndoManager* pNew) +{ + EditUndoManager* pRetval = pUndoManager; + + if(pUndoManager) + { + pUndoManager->SetEditEngine(nullptr); + } + + pUndoManager = pNew; + + if(pUndoManager) + { + pUndoManager->SetEditEngine(pEditEngine); + } + + return pRetval; +} + +inline const ParaPortion* ImpEditEngine::FindParaPortion( const ContentNode* pNode ) const +{ + sal_Int32 nPos = maEditDoc.GetPos( pNode ); + DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" ); + return GetParaPortions()[ nPos ]; +} + +inline ParaPortion* ImpEditEngine::FindParaPortion( ContentNode const * pNode ) +{ + sal_Int32 nPos = maEditDoc.GetPos( pNode ); + DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" ); + return GetParaPortions()[ nPos ]; +} + +inline PointerStyle ImpEditView::GetPointer() +{ + if ( !mxPointer ) + { + mxPointer = IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text; + return *mxPointer; + } + + if(PointerStyle::Text == *mxPointer && IsVertical()) + { + mxPointer = PointerStyle::TextVertical; + } + else if(PointerStyle::TextVertical == *mxPointer && !IsVertical()) + { + mxPointer = PointerStyle::Text; + } + + return *mxPointer; +} + +inline vcl::Cursor* ImpEditView::GetCursor() +{ + if ( !pCursor ) + pCursor.reset( new vcl::Cursor ); + return pCursor.get(); +} + +void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit ); +void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit = nullptr, const MapUnit* pDestUnit = nullptr ); +AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx new file mode 100644 index 0000000000..4b8f0a6379 --- /dev/null +++ b/editeng/source/editeng/impedit2.cxx @@ -0,0 +1,4509 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/flditem.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <eerdll2.hxx> +#include <editeng/eerdll.hxx> +#include <edtspell.hxx> +#include "eeobj.hxx" +#include <editeng/txtrange.hxx> +#include <sfx2/app.hxx> +#include <sfx2/mieclip.hxx> +#include <svtools/colorcfg.hxx> +#include <svl/ctloptions.hxx> +#include <unotools/securityoptions.hxx> +#include <editeng/acorrcfg.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/udlnitem.hxx> + +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/i18n/InputSequenceCheckMode.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/i18n/UnicodeType.hpp> + +#include <rtl/character.hxx> + +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <svl/asiancfg.hxx> +#include <svl/voiditem.hxx> +#include <i18nutil/unicode.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/configmgr.hxx> + +#include <unicode/ubidi.h> +#include <algorithm> +#include <limits> +#include <memory> +#include <string_view> +#include <fstream> + +using namespace ::com::sun::star; + +static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem ) +{ + sal_uInt16 nExtra = 0; + if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + { + nExtra = rLSItem.GetInterLineSpace(); + } + + return nExtra; +} + +ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) : + pSharedVCL(EditDLL::Get().GetSharedVclResources()), + maPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), + maMinAutoPaperSize( 0x0, 0x0 ), + maMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ), + maEditDoc( pItemPool ), + pEditEngine(pEE), + pActiveView(nullptr), + pStylePool(nullptr), + pTextObjectPool(nullptr), + pUndoManager(nullptr), + aWordDelimiters(" .,;:-`'?!_=\"{}()[]"), + maBackgroundColor(COL_AUTO), + mfFontScaleX(100.0), + mfFontScaleY(100.0), + mfSpacingScaleX(100.0), + mfSpacingScaleY(100.0), + mbRoundToNearestPt(false), + mnAsianCompressionMode(CharCompressType::NONE), + eDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default), + mnBigTextObjectStart(20), + meDefLanguage(LANGUAGE_DONTKNOW), + nCurTextHeight(0), + nCurTextHeightNTP(0), + aOnlineSpellTimer( "editeng::ImpEditEngine aOnlineSpellTimer" ), + aStatusTimer( "editeng::ImpEditEngine aStatusTimer" ), + mbKernAsianPunctuation(false), + mbAddExtLeading(false), + mbIsFormatting(false), + mbFormatted(false), + mbInSelection(false), + mbIsInUndo(false), + mbUpdateLayout(true), + mbUndoEnabled(true), + mbDowning(false), + mbUseAutoColor(true), + mbForceAutoColor(false), + mbCallParaInsertedOrDeleted(false), + mbFirstWordCapitalization(true), + mbLastTryMerge(false), + mbReplaceLeadingSingleQuotationMark(true), + mbSkipOutsideFormat(false), + mbFuzzing(utl::ConfigManager::IsFuzzing()), + mbNbspRunNext(false) +{ + maStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT | + EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS | + EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS | + EEControlBits::FORMAT100; + + aSelEngine.SetFunctionSet( &aSelFuncSet ); + + aStatusTimer.SetTimeout( 200 ); + aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) ); + + aIdleFormatter.SetPriority( TaskPriority::REPAINT ); + aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) ); + + aOnlineSpellTimer.SetTimeout( 100 ); + aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) ); + + // Access data already from here on! + SetRefDevice( nullptr ); + InitDoc( false ); + + mbCallParaInsertedOrDeleted = true; + + maEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) ); + StartListening(*SfxGetpApp()); +} + +void ImpEditEngine::Dispose() +{ + SolarMutexGuard g; + auto pApp = SfxApplication::Get(); + if(pApp) + EndListening(*pApp); + pVirtDev.disposeAndClear(); + mpOwnDev.disposeAndClear(); + pSharedVCL.reset(); +} + +ImpEditEngine::~ImpEditEngine() +{ + aStatusTimer.Stop(); + aOnlineSpellTimer.Stop(); + aIdleFormatter.Stop(); + + // Destroying templates may otherwise cause unnecessary formatting, + // when a parent template is destroyed. + // And this after the destruction of the data! + mbDowning = true; + SetUpdateLayout( false ); + + Dispose(); + // it's only legal to delete the pUndoManager if it was created by + // ImpEditEngine; if it was set by SetUndoManager() it must be cleared + // before destroying the ImpEditEngine! + assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager)); + delete pUndoManager; + pTextRanger.reset(); + mpIMEInfos.reset(); + pSpellInfo.reset(); +} + +void ImpEditEngine::SetRefDevice( OutputDevice* pRef ) +{ + if (pRef) + pRefDev = pRef; + else + pRefDev = pSharedVCL->GetVirtualDevice(); + + nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews(); + } +} + +void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode ) +{ + if ( GetRefDevice()->GetMapMode() == rMapMode ) + return; + + mpOwnDev.disposeAndClear(); + mpOwnDev = VclPtr<VirtualDevice>::Create(); + pRefDev = mpOwnDev; + pRefDev->SetMapMode(MapMode(MapUnit::MapTwip)); + SetRefDevice( pRefDev ); + + pRefDev->SetMapMode( rMapMode ); + nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width()); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews(); + } +} + +void ImpEditEngine::InitDoc(bool bKeepParaAttribs) +{ + sal_Int32 nParas = maEditDoc.Count(); + for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ ) + { + if ( maEditDoc[n]->GetStyleSheet() ) + EndListening( *maEditDoc[n]->GetStyleSheet() ); + } + + if ( bKeepParaAttribs ) + maEditDoc.RemoveText(); + else + maEditDoc.Clear(); + + GetParaPortions().Reset(); + + GetParaPortions().Insert(0, std::make_unique<ParaPortion>( maEditDoc[0] )); + + mbFormatted = false; + + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL ); + GetEditEnginePtr()->ParagraphInserted( 0 ); + } + + if ( GetStatus().DoOnlineSpelling() ) + maEditDoc.GetObject( 0 )->CreateWrongList(); +} + +EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel) +{ + EditPaM aPaM (ImpDeleteSelection(rSel)); + return aPaM; +} + +OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const +{ + if ( !rSel.HasRange() ) + return OUString(); + + EditSelection aSel( rSel ); + aSel.Adjust( maEditDoc ); + + ContentNode* pStartNode = aSel.Min().GetNode(); + ContentNode* pEndNode = aSel.Max().GetNode(); + sal_Int32 nStartNode = maEditDoc.GetPos( pStartNode ); + sal_Int32 nEndNode = maEditDoc.GetPos( pEndNode ); + + OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" ); + + OUStringBuffer aText(256); + const OUString aSep = EditDoc::GetSepStr( LINEEND_LF ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + const ContentNode* pNode = maEditDoc.GetObject( nNode ); + assert(pNode); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos )); + if ( nNode < nEndNode ) + aText.append(aSep); + } + return aText.makeStringAndClear(); +} + +bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView ) +{ + GetSelEngine().SetCurView( pView ); + SetActiveView( pView ); + + if (!GetAutoCompleteText().isEmpty()) + SetAutoCompleteText( OUString(), true ); + + GetSelEngine().SelMouseButtonDown( rMEvt ); + // Special treatment + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + if ( rMEvt.IsShift() ) + return true; + + if ( rMEvt.GetClicks() == 2 ) + { + // So that the SelectionEngine knows about the anchor. + aSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( SelectWord( aCurSel ) ); + pView->pImpEditView->DrawSelectionXOR(); + pView->pImpEditView->SetEditSelection( aNewSelection ); + pView->pImpEditView->DrawSelectionXOR(); + pView->ShowCursor(); + } + else if ( rMEvt.GetClicks() == 3 ) + { + // So that the SelectionEngine knows about the anchor. + aSelEngine.CursorPosChanging( true, false ); + + EditSelection aNewSelection( aCurSel ); + aNewSelection.Min().SetIndex( 0 ); + aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() ); + pView->pImpEditView->DrawSelectionXOR(); + pView->pImpEditView->SetEditSelection( aNewSelection ); + pView->pImpEditView->DrawSelectionXOR(); + pView->ShowCursor(); + } + return true; +} + +bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) +{ + bool bConsumed = true; + + GetSelEngine().SetCurView( pView ); + SetActiveView( pView ); + if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) + { + pView->DeleteSelected(); + mpIMEInfos.reset(); + EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max(); + OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE ); + if ( nMax != -1 ) // don't overwrite features! + aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax ); + mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) ); + mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode(); + UndoActionStart( EDITUNDO_INSERT ); + } + else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) + { + OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" ); + if( mpIMEInfos ) + { + // #102812# convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + // Borrowed from sw: edtwin.cxx + if ( mpIMEInfos->nLen ) + { + EditSelection aSel( mpIMEInfos->aPos ); + aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); + // #102812# convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + // See also tdf#155350 + const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() ); + if ( ( GetStatus().DoAutoCorrect() ) && SvxAutoCorrect::IsAutoCorrectChar(nCharCode) ) + { + aSel = DeleteSelected( aSel ); + aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite ); + pView->pImpEditView->SetEditSelection( aSel ); + } + } + + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); + if (pPortion) + pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); + + bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite; + + mpIMEInfos.reset(); + + FormatAndLayout( pView ); + + pView->SetInsertMode( !bWasCursorOverwrite ); + } + UndoActionEnd(); + } + else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) + { + OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" ); + if( mpIMEInfos ) + { + const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); + + if ( !pData->IsOnlyCursorChanged() ) + { + EditSelection aSel( mpIMEInfos->aPos ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen ); + aSel = DeleteSelected( aSel ); + aSel = ImpInsertText( aSel, pData->GetText() ); + + if ( mpIMEInfos->bWasCursorOverwrite ) + { + sal_Int32 nOldIMETextLen = mpIMEInfos->nLen; + sal_Int32 nNewIMETextLen = pData->GetText().getLength(); + + if ( ( nOldIMETextLen > nNewIMETextLen ) && + ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) + { + // restore old characters + sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen; + EditPaM aPaM( mpIMEInfos->aPos ); + aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); + ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) ); + } + else if ( ( nOldIMETextLen < nNewIMETextLen ) && + ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) + { + // overwrite + sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen; + if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() ) + nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen; + OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" ); + EditPaM aPaM( mpIMEInfos->aPos ); + aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen ); + EditSelection _aSel( aPaM ); + _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite ); + DeleteSelected( _aSel ); + } + } + if ( pData->GetTextAttr() ) + { + mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() ); + } + else + { + mpIMEInfos->DestroyAttribs(); + mpIMEInfos->nLen = pData->GetText().getLength(); + } + + ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() ); + pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() ); + FormatAndLayout( pView ); + } + + EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() ); + pView->SetSelection( CreateESel( aNewSel ) ); + pView->SetInsertMode( !pData->IsCursorOverwrite() ); + + if ( pData->IsCursorVisible() ) + pView->ShowCursor(); + else + pView->HideCursor(); + } + } + else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange ) + { + } + else if ( rCEvt.GetCommand() == CommandEventId::CursorPos ) + { + if (mpIMEInfos) + { + EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + tools::Rectangle aR1 = PaMtoEditCursor( aPaM ); + + sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen; + + if ( !IsFormatted() ) + FormatDoc(); + + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); + if (pParaPortion) + { + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + if ( nInputEnd > rLine.GetEnd() ) + nInputEnd = rLine.GetEnd(); + tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine ); + tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 ); + auto nExtTextInputWidth = aR2.Left() - aR1.Right(); + if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks()) + pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth); + else if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCursorRect(&aRect, nExtTextInputWidth); + } + } + else + { + if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCursorRect(); + } + } + else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange ) + { + const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData(); + + ESelection aSelection = pView->GetSelection(); + aSelection.Adjust(); + + if( pView->HasSelection() ) + { + aSelection.nEndPos = aSelection.nStartPos; + aSelection.nStartPos += pData->GetStart(); + aSelection.nEndPos += pData->GetEnd(); + } + else + { + aSelection.nStartPos = pData->GetStart(); + aSelection.nEndPos = pData->GetEnd(); + } + pView->SetSelection( aSelection ); + } + else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion ) + { + if ( pView->HasSelection() ) + { + ESelection aSelection = pView->GetSelection(); + aSelection.Adjust(); + + if ( aSelection.nStartPara != aSelection.nEndPara ) + { + sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara ); + aSelection.nEndPara = aSelection.nStartPara; + aSelection.nEndPos = aParaLen; + pView->SetSelection( aSelection ); + } + } + } + else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition ) + { + if (mpIMEInfos) + { + EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() ); + if ( !IsFormatted() ) + FormatDoc(); + + sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode()); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos); + if (pParaPortion) + { + const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex(); + const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1; + std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen); + + auto CollectCharPositions = [&](const LineAreaInfo& rInfo) { + if (!rInfo.pLine) // Start of ParaPortion + { + if (rInfo.nPortion < nPortionPos) + return CallbackResult::SkipThisPortion; + if (rInfo.nPortion > nPortionPos) + return CallbackResult::Stop; + assert(&rInfo.rPortion == pParaPortion); + } + else // This is the needed ParaPortion + { + if (rInfo.pLine->GetStart() > nMaxPos) + return CallbackResult::Stop; + if (rInfo.pLine->GetEnd() < nMinPos) + return CallbackResult::Continue; + for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n) + { + if (rInfo.pLine->IsIn(n)) + { + tools::Rectangle aR = GetEditCursor(pParaPortion, rInfo.pLine, n, + GetCursorFlags::NONE); + aR.Move(getTopLeftDocOffset(rInfo.aArea)); + aRects[n - nMinPos] = pView->GetImpEditView()->GetWindowPos(aR); + } + } + } + return CallbackResult::Continue; + }; + IterateLineAreas(CollectCharPositions, IterFlag::none); + + if (vcl::Window* pWindow = pView->GetWindow()) + pWindow->SetCompositionCharRect(aRects.data(), aRects.size()); + } + } + } + else + bConsumed = false; + + return GetSelEngine().Command(rCEvt) || bConsumed; +} + +bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView ) +{ + GetSelEngine().SetCurView( pView ); + GetSelEngine().SelMouseButtonUp( rMEvt ); + + // in the tiled rendering case, setting bInSelection here has unexpected + // consequences - further tiles painting removes the selection + // FIXME I believe resetting bInSelection should not be here even in the + // non-tiled-rendering case, but it has been here since 2000 (and before) + // so who knows what corner case it was supposed to solve back then + if (!comphelper::LibreOfficeKit::isActive()) + mbInSelection = false; + + // Special treatments + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + if ( aCurSel.HasRange() ) + return true; + + if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() ) + return true; + + const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev(); + Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); + const SvxFieldItem* pFld = pView->GetField(aLogicClick); + if (!pFld) + return true; + + // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click + bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld ); + if (bUrlOpened) + return true; + + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField())) + { + bool bCtrlClickHappened = rMEvt.IsMod1(); + bool bCtrlClickSecOption + = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); + if ((bCtrlClickHappened && bCtrlClickSecOption) + || (!bCtrlClickHappened && !bCtrlClickSecOption)) + { + css::uno::Reference<css::system::XSystemShellExecute> exec( + css::system::SystemShellExecute::create( + comphelper::getProcessComponentContext())); + exec->execute(pUrlField->GetURL(), OUString(), + css::system::SystemShellExecuteFlags::DEFAULTS); + } + } + return true; +} + +void ImpEditEngine::ReleaseMouse() +{ + GetSelEngine().ReleaseMouse(); +} + +bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView ) +{ + // MouseMove is called directly after ShowQuickHelp()! + GetSelEngine().SetCurView( pView ); + GetSelEngine().SelMouseMove( rMEvt ); + return true; +} + +EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr) +{ + EditPaM aPaM = ImpInsertText( aSel, rStr ); + return aPaM; +} + +void ImpEditEngine::Clear() +{ + InitDoc( false ); + + EditPaM aPaM = maEditDoc.GetStartPaM(); + EditSelection aSel( aPaM ); + + nCurTextHeight = 0; + nCurTextHeightNTP = 0; + + ResetUndoManager(); + + for (size_t nView = aEditViews.size(); nView; ) + { + EditView* pView = aEditViews[--nView]; + pView->pImpEditView->SetEditSelection( aSel ); + } + + // Related: tdf#82115 Fix crash when handling input method events. + // The nodes in mpIMEInfos may be deleted in ImpEditEngine::Clear() which + // causes a crash in the CommandEventId::ExtTextInput and + // CommandEventId::EndExtTextInput event handlers. + mpIMEInfos.reset(); +} + +EditPaM ImpEditEngine::RemoveText() +{ + InitDoc( true ); + + EditPaM aStartPaM = maEditDoc.GetStartPaM(); + EditSelection aEmptySel( aStartPaM, aStartPaM ); + for (EditView* pView : aEditViews) + { + pView->pImpEditView->SetEditSelection( aEmptySel ); + } + ResetUndoManager(); + return maEditDoc.GetStartPaM(); +} + + +void ImpEditEngine::SetText(const OUString& rText) +{ + // RemoveText deletes the undo list! + EditPaM aStartPaM = RemoveText(); + bool bUndoCurrentlyEnabled = IsUndoEnabled(); + // The text inserted manually can not be made reversible by the user + EnableUndo( false ); + + EditSelection aEmptySel( aStartPaM, aStartPaM ); + EditPaM aPaM = aStartPaM; + if (!rText.isEmpty()) + aPaM = ImpInsertText( aEmptySel, rText ); + + for (EditView* pView : aEditViews) + { + pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) ); + // If no text then also no Format&Update + // => The text remains. + if (rText.isEmpty() && IsUpdateLayout()) + { + tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(), + Size( maPaperSize.Width(), nCurTextHeight ) ); + aTmpRect.Intersection( pView->GetOutputArea() ); + pView->InvalidateWindow( aTmpRect ); + } + } + if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough. + nCurTextHeight = 0; + nCurTextHeightNTP = 0; + } + EnableUndo( bUndoCurrentlyEnabled ); + OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" ); +} + + +const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const +{ + if ( !pEmptyItemSet ) + { + pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(maEditDoc.GetItemPool())); + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + pEmptyItemSet->ClearItem( nWhich ); + } + } + return *pEmptyItemSet; +} + + +// MISC + +void ImpEditEngine::TextModified() +{ + mbFormatted = false; + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_TEXTMODIFIED ); + GetNotifyHdl().Call( aNotify ); + } +} + + +void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck ) +{ + assert(pNode && "ParaAttribsChanged: Which one?"); + + maEditDoc.SetModified( true ); + mbFormatted = false; + + ParaPortion* pPortion = FindParaPortion( pNode ); + assert(pPortion); + pPortion->MarkSelectionInvalid( 0 ); + + sal_Int32 nPara = maEditDoc.GetPos( pNode ); + if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() ) + pEditEngine->ParaAttribsChanged( nPara ); + + ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 ); + // => is formatted again anyway, if Invalid. + if ( pNextPortion && !pNextPortion->IsInvalid() ) + CalcHeight( pNextPortion ); +} + + +// Cursor movements + + +EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView ) +{ + // Actually, only necessary for up/down, but whatever. + CheckIdleFormatter(); + + EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() ); + + EditPaM aOldPaM( aPaM ); + + TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; + if (IsEffectivelyVertical() && IsTopToBottom()) + eTextDirection = TextDirectionality::TopToBottom_RightToLeft; + else if (IsEffectivelyVertical() && !IsTopToBottom()) + eTextDirection = TextDirectionality::BottomToTop_LeftToRight; + else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) ) + eTextDirection = TextDirectionality::RightToLeft_TopToBottom; + + KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection ); + + bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1(); + sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode(); + + if ( DoVisualCursorTraveling() ) + { + // Only for simple cursor movement... + if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) ) + { + aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT ); + nCode = 0; // skip switch statement + } + } + + bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift(); + switch ( nCode ) + { + case KEY_UP: aPaM = CursorUp( aPaM, pEditView ); + break; + case KEY_DOWN: aPaM = CursorDown( aPaM, pEditView ); + break; + case KEY_LEFT: aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); + break; + case KEY_RIGHT: aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL ); + break; + case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM ); + break; + case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM ); + break; + case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView ); + break; + case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView ); + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: + aPaM = CursorStartOfLine( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_LINE: + aPaM = CursorEndOfLine( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_WORD_BACKWARD: + aPaM = WordLeft( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_WORD_FORWARD: + aPaM = WordRight( aPaM ); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: + aPaM = CursorStartOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorLeft( aPaM ); + aPaM = CursorStartOfParagraph( aPaM ); + } + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: + aPaM = CursorEndOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorRight( aPaM ); + aPaM = CursorEndOfParagraph( aPaM ); + } + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: + aPaM = CursorStartOfDoc(); + bKeyModifySelection = false; + break; + case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: + aPaM = CursorEndOfDoc(); + bKeyModifySelection = false; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: + aPaM = CursorStartOfLine( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_LINE: + aPaM = CursorEndOfLine( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_BACKWARD: + aPaM = CursorLeft( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_FORWARD: + aPaM = CursorRight( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_WORD_BACKWARD: + aPaM = WordLeft( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_WORD_FORWARD: + aPaM = WordRight( aPaM ); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: + aPaM = CursorStartOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorLeft( aPaM ); + aPaM = CursorStartOfParagraph( aPaM ); + } + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: + aPaM = CursorEndOfParagraph( aPaM ); + if( aPaM == aOldPaM ) + { + aPaM = CursorRight( aPaM ); + aPaM = CursorEndOfParagraph( aPaM ); + } + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: + aPaM = CursorStartOfDoc(); + bKeyModifySelection = true; + break; + case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: + aPaM = CursorEndOfDoc(); + bKeyModifySelection = true; + break; + } + + if ( aOldPaM != aPaM && nullptr != aOldPaM.GetNode() ) + { + aOldPaM.GetNode()->checkAndDeleteEmptyAttribs(); + } + + // May cause, a CreateAnchor or deselection all + aSelEngine.SetCurView( pEditView ); + aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() ); + EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() ); + + { + EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + aNewSelection.Max() = aPaM; + pEditView->pImpEditView->SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM; + } + + if ( bKeyModifySelection ) + { + // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering. + EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM ); + pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel ); + } + else + { + EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection()); + aNewSelection.Min() = aPaM; + pEditView->pImpEditView->SetEditSelection(aNewSelection); + // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM; + } + + return pEditView->pImpEditView->GetEditSelection(); +} + +EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart ) +{ + EditPaM aPaM( rPaM ); + + sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return aPaM; + + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); + + pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + + if ( !bEmptyLine ) + { + OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); + + const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); + + sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1; + const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError ); + + ubidi_close( pBidi ); + + aPaM.SetIndex( nLogPos + rLine.GetStart() ); + + sal_Int32 nTmp; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + bool bPortionRTL = rTextPortion.IsRightToLeft(); + + if ( bStart ) + { + pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 ); + // Maybe we must be *behind* the character + if ( bPortionRTL && pEditView->IsInsertMode() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + } + else + { + pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 ); + if ( !bPortionRTL && pEditView->IsInsertMode() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + } + } + + return aPaM; +} + +EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft ) +{ + EditPaM aPaM( rPaM ); + + sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return aPaM; + + sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false ); + const EditLine& rLine = pParaPortion->GetLines()[nLine]; + bool bEmptyLine = rLine.GetStart() == rLine.GetEnd(); + + pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE; + + bool bParaRTL = IsRightToLeft( nPara ); + + bool bDone = false; + + if ( bEmptyLine ) + { + if ( bVisualToLeft ) + { + aPaM = CursorUp( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); + } + else + { + aPaM = CursorDown( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); + } + + bDone = true; + } + + bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft; + + if ( !bDone && pEditView->IsInsertMode() ) + { + // Check if we are within a portion and don't have overwrite mode, then it's easy... + sal_Int32 nPortionStart; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + + bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) ); + sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel(); + + // Portion boundary doesn't matter if both have same RTL level + sal_Int32 nRTLLevelNextPortion = -1; + if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) + { + sal_Int32 nTmp; + sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward ); + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion]; + nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel(); + } + + if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) ) + { + if (bVisualToLeft != bool(nRTLLevel % 2)) + { + aPaM = CursorLeft( aPaM, nCharacterIteratorMode ); + pEditView->pImpEditView->SetCursorBidiLevel( 1 ); + } + else + { + aPaM = CursorRight( aPaM, nCharacterIteratorMode ); + pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + } + bDone = true; + } + } + + if ( !bDone ) + { + bool bGotoStartOfNextLine = false; + bool bGotoEndOfPrevLine = false; + + OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()); + const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart(); + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError ); + + const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError ); + + if ( !pEditView->IsInsertMode() ) + { + bool bEndOfLine = nPosInLine == aLine.getLength(); + sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError ); + if ( bVisualToLeft ) + { + bGotoEndOfPrevLine = nVisPos == 0; + if ( !bEndOfLine ) + nVisPos--; + } + else + { + bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1); + if ( !bEndOfLine ) + nVisPos++; + } + + if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) + { + aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); + pEditView->pImpEditView->SetCursorBidiLevel( 0 ); + } + } + else + { + bool bWasBehind = false; + bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1; + if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion + bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows. + + sal_Int32 nPortionStart; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion ); + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; + bool bRTLPortion = rTextPortion.IsRightToLeft(); + + // -1: We are 'behind' the character + tools::Long nVisPos = static_cast<tools::Long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError )); + if ( bVisualToLeft ) + { + if ( !bWasBehind || bRTLPortion ) + nVisPos--; + } + else + { + if ( bWasBehind || bRTLPortion || bBeforePortion ) + nVisPos++; + } + + bGotoEndOfPrevLine = nVisPos < 0; + bGotoStartOfNextLine = nVisPos >= aLine.getLength(); + + if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine ) + { + aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) ); + + // RTL portion, stay visually on the left side. + sal_Int32 _nPortionStart; + // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion ); + sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true ); + const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion]; + if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) ) + aPaM.SetIndex( aPaM.GetIndex()+1 ); + + pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart ); + } + } + + ubidi_close( pBidi ); + + if ( bGotoEndOfPrevLine ) + { + aPaM = CursorUp( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, false ); + } + else if ( bGotoStartOfNextLine ) + { + aPaM = CursorDown( aPaM, pEditView ); + if ( aPaM != rPaM ) + aPaM = CursorVisualStartEnd( pEditView, aPaM, true ); + } + } + return aPaM; +} + + +EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) +{ + EditPaM aCurPaM( rPaM ); + EditPaM aNewPaM( aCurPaM ); + + if ( aCurPaM.GetIndex() ) + { + sal_Int32 nCount = 1; + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + aNewPaM.SetIndex( + _xBI->previousCharacters( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); + } + else + { + ContentNode* pNode = aCurPaM.GetNode(); + pNode = GetPrevVisNode( pNode ); + if ( pNode ) + { + aNewPaM.SetNode( pNode ); + aNewPaM.SetIndex( pNode->Len() ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) +{ + EditPaM aCurPaM( rPaM ); + EditPaM aNewPaM( aCurPaM ); + + if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() ) + { + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int32 nCount = 1; + aNewPaM.SetIndex( + _xBI->nextCharacters( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount)); + } + else + { + ContentNode* pNode = aCurPaM.GetNode(); + pNode = GetNextVisNode( pNode ); + if ( pNode ) + { + aNewPaM.SetNode( pNode ); + aNewPaM.SetIndex( 0 ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView ) +{ + assert(pView && "No View - No Cursor Movement!"); + + const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); + assert(pPPortion); + sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pPPortion->GetLines()[nLine]; + + tools::Long nX; + if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + { + nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); + pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + } + else + nX = pView->pImpEditView->nTravelXPos; + + EditPaM aNewPaM( rPaM ); + if ( nLine ) // same paragraph + { + const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1]; + aNewPaM.SetIndex( GetChar( pPPortion, &rPrevLine, nX ) ); + // If a previous automatically wrapped line, and one has to be exactly + // at the end of this line, the cursor lands on the current line at the + // beginning. See Problem: Last character of an automatically wrapped + // Row = cursor + if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else // previous paragraph + { + const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion ); + if ( pPrevPortion ) + { + const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1]; + aNewPaM.SetNode( pPrevPortion->GetNode() ); + aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView ) +{ + assert(pView); + + const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() ); + assert(pPPortion); + sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() ); + + tools::Long nX; + if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() ); + pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef; + } + else + nX = pView->pImpEditView->nTravelXPos; + + EditPaM aNewPaM( rPaM ); + if ( nLine < pPPortion->GetLines().Count()-1 ) + { + const EditLine& rNextLine = pPPortion->GetLines()[nLine+1]; + aNewPaM.SetIndex( GetChar( pPPortion, &rNextLine, nX ) ); + // Special treatment, see CursorUp ... + if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else // next paragraph + { + const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion ); + if ( pNextPortion ) + { + const EditLine& rLine = pNextPortion->GetLines()[0]; + aNewPaM.SetNode( pNextPortion->GetNode() ); + // Never at the very end when several lines, because then a line + // below the cursor appears. + aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) ); + if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM ) +{ + const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); + assert(pCurPortion); + sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pCurPortion->GetLines()[nLine]; + + EditPaM aNewPaM( rPaM ); + aNewPaM.SetIndex( rLine.GetStart() ); + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM ) +{ + const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() ); + assert(pCurPortion); + sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() ); + const EditLine& rLine = pCurPortion->GetLines()[nLine]; + + EditPaM aNewPaM( rPaM ); + aNewPaM.SetIndex( rLine.GetEnd() ); + if ( rLine.GetEnd() > rLine.GetStart() ) + { + if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) ) + { + // When a soft break, be in front of it! + const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 ); + if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) ) + aNewPaM = CursorLeft( aNewPaM ); + } + else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) ) + { + // For a Blank in an auto wrapped line, it makes sense, to stand + // in front of it, since the user wants to be after the word. + // If this is changed, special treatment for Pos1 to End! + aNewPaM = CursorLeft( aNewPaM ); + } + } + return aNewPaM; +} + +EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM ) +{ + EditPaM aPaM(rPaM); + aPaM.SetIndex(0); + return aPaM; +} + +EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM ) +{ + EditPaM aPaM(rPaM); + aPaM.SetIndex(rPaM.GetNode()->Len()); + return aPaM; +} + +EditPaM ImpEditEngine::CursorStartOfDoc() +{ + EditPaM aPaM( maEditDoc.GetObject( 0 ), 0 ); + return aPaM; +} + +EditPaM ImpEditEngine::CursorEndOfDoc() +{ + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 ); + ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( maEditDoc.Count()-1 ); + OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" ); + if (!(pLastNode && pLastPortion)) + return EditPaM(); + + if ( !pLastPortion->IsVisible() ) + { + pLastNode = GetPrevVisNode( pLastPortion->GetNode() ); + OSL_ENSURE( pLastNode, "No visible paragraph?" ); + if ( !pLastNode ) + pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 ); + } + + EditPaM aPaM( pLastNode, pLastNode->Len() ); + return aPaM; +} + +EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView ) +{ + tools::Rectangle aRect = PaMtoEditCursor( rPaM ); + Point aTopLeft = aRect.TopLeft(); + aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) ); + aTopLeft.AdjustX(nOnePixelInRef ); + if ( aTopLeft.Y() < 0 ) + { + aTopLeft.setY( 0 ); + } + return GetPaM( aTopLeft ); +} + +EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView ) +{ + tools::Rectangle aRect = PaMtoEditCursor( rPaM ); + Point aBottomRight = aRect.BottomRight(); + aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 ); + aBottomRight.AdjustX(nOnePixelInRef ); + tools::Long nHeight = GetTextHeight(); + if ( aBottomRight.Y() > nHeight ) + { + aBottomRight.setY( nHeight-2 ); + } + return GetPaM( aBottomRight ); +} + +EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM ) +{ + const sal_Int32 nCurrentPos = rPaM.GetIndex(); + EditPaM aNewPaM( rPaM ); + if ( nCurrentPos == 0 ) + { + // Previous paragraph... + sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pPrevNode = maEditDoc.GetObject( --nCurPara ); + if ( pPrevNode ) + { + aNewPaM.SetNode( pPrevNode ); + aNewPaM.SetIndex( pPrevNode->Len() ); + } + } + else + { + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = + _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); + if ( aBoundary.startPos >= nCurrentPos ) + aBoundary = _xBI->previousWord( + aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); + aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 ); + } + + return aNewPaM; +} + +EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType ) +{ + const sal_Int32 nMax = rPaM.GetNode()->Len(); + EditPaM aNewPaM( rPaM ); + if ( aNewPaM.GetIndex() < nMax ) + { + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = _xBI->nextWord( + aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType); + aNewPaM.SetIndex( aBoundary.startPos ); + } + // not 'else', maybe the index reached nMax now... + if ( aNewPaM.GetIndex() >= nMax ) + { + // Next paragraph ... + sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() ); + ContentNode* pNextNode = maEditDoc.GetObject( ++nCurPara ); + if ( pNextNode ) + { + aNewPaM.SetNode( pNextNode ); + aNewPaM.SetIndex( 0 ); + } + } + return aNewPaM; +} + +EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM ) +{ + EditPaM aNewPaM( rPaM ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + // tdf#135761 - since this function is only used when a selection is deleted at the left, + // change the search preference of the word boundary from forward to backward. + // For further details of a deletion of a selection check ImpEditEngine::DeleteLeftOrRight. + i18n::Boundary aBoundary = _xBI->getWordBoundary( + rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false); + + aNewPaM.SetIndex( aBoundary.startPos ); + return aNewPaM; +} + +EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM ) +{ + EditPaM aNewPaM( rPaM ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aNewPaM ); + if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + i18n::Boundary aBoundary = _xBI->getWordBoundary( + rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); + + aNewPaM.SetIndex( aBoundary.endPos ); + return aNewPaM; +} + +EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord ) +{ + EditSelection aNewSel( rCurSel ); + EditPaM aPaM( rCurSel.Max() ); + + // we need to increase the position by 1 when retrieving the locale + // since the attribute for the char left to the cursor position is returned + EditPaM aTmpPaM( aPaM ); + if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() ) + aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 ); + lang::Locale aLocale( GetLocale( aTmpPaM ) ); + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int16 nType = _xBI->getWordType( + aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale); + + if ( nType == i18n::WordType::ANY_WORD ) + { + i18n::Boundary aBoundary = _xBI->getWordBoundary( + aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true); + + // don't select when cursor at end of word + if ( ( aBoundary.endPos > aPaM.GetIndex() ) && + ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) ) + { + aNewSel.Min().SetIndex( aBoundary.startPos ); + aNewSel.Max().SetIndex( aBoundary.endPos ); + } + } + + return aNewSel; +} + +EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel ) + const +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + const EditPaM& rPaM = rCurSel.Min(); + const ContentNode* pNode = rPaM.GetNode(); + // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that + const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a"); + //return Null if search starts at the beginning of the string + sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0; + + sal_Int32 nEnd = _xBI->endOfSentence( + pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM)); + + EditSelection aNewSel( rCurSel ); + OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range"); + OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range"); + aNewSel.Min().SetIndex( nStart ); + aNewSel.Max().SetIndex( nEnd ); + return aNewSel; +} + +bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + + // get the index that really is first + const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex()); + + bool bIsSequenceChecking = + SvtCTLOptions::IsCTLFontEnabled() && + SvtCTLOptions::IsCTLSequenceChecking() && + nFirstPos != 0 && /* first char needs not to be checked */ + _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 ); + + return bIsSequenceChecking; +} + +static bool lcl_HasStrongLTR ( std::u16string_view rTxt, sal_Int32 nStart, sal_Int32 nEnd ) + { + for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx ) + { + const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] ); + if ( nCharDir == U_LEFT_TO_RIGHT || + nCharDir == U_LEFT_TO_RIGHT_EMBEDDING || + nCharDir == U_LEFT_TO_RIGHT_OVERRIDE ) + return true; + } + return false; + } + + +void ImpEditEngine::InitScriptTypes( sal_Int32 nPara ) +{ + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return; + + ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + rTypes.clear(); + + ContentNode* pNode = pParaPortion->GetNode(); + if ( !pNode->Len() ) + return; + + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + + OUString aText = pNode->GetString(); + + // To handle fields put the character from the field in the string, + // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK + const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 ); + while ( pField ) + { + const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue(); + if ( !aFldText.isEmpty() ) + { + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) ); + short nFldScriptType = _xBI->getScriptType( aFldText, 0 ); + + for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ ) + { + short nTmpType = _xBI->getScriptType( aFldText, nCharInField ); + + // First char from field wins... + if ( nFldScriptType == i18n::ScriptType::WEAK ) + { + nFldScriptType = nTmpType; + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); + } + + // ... but if the first one is LATIN, and there are CJK or CTL chars too, + // we prefer that ScriptType because we need another font. + if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) ) + { + aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) ); + break; + } + } + } + // #112831# Last Field might go from 0xffff to 0x0000 + pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr; + } + + sal_Int32 nTextLen = aText.getLength(); + + sal_Int32 nPos = 0; + short nScriptType = _xBI->getScriptType( aText, nPos ); + rTypes.emplace_back( nScriptType, nPos, nTextLen ); + nPos = _xBI->endOfScript( aText, nPos, nScriptType ); + while ( ( nPos != -1 ) && ( nPos < nTextLen ) ) + { + rTypes.back().nEndPos = nPos; + + nScriptType = _xBI->getScriptType( aText, nPos ); + tools::Long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType ); + + if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) ) + { + // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions + rTypes.back().nEndPos = nEndPos; + } + else + { + auto nPrevPos = nPos; + auto nPrevChar = aText.iterateCodePoints(&nPrevPos, -1); + if (_xBI->getScriptType(aText, nPrevPos) == i18n::ScriptType::WEAK) + { + auto nChar = aText.iterateCodePoints(&nPos, 0); + auto nType = unicode::getUnicodeType(nChar); + if (nType == css::i18n::UnicodeType::NON_SPACING_MARK || + nType == css::i18n::UnicodeType::ENCLOSING_MARK || + nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK || + (nPrevChar == 0x202F /* NNBSP, tdf#112594 */ && + u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN)) + { + rTypes.back().nEndPos = nPos = nPrevPos; + break; + } + } + rTypes.emplace_back( nScriptType, nPos, nTextLen ); + } + + nPos = nEndPos; + } + + if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK ) + rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); + + // create writing direction information: + if ( pParaPortion->aWritingDirectionInfos.empty() ) + InitWritingDirections( nPara ); + + // i89825: Use CTL font for numbers embedded into an RTL run: + WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rDirInfo : rDirInfos) + { + const sal_Int32 nStart = rDirInfo.nStartPos; + const sal_Int32 nEnd = rDirInfo.nEndPos; + const sal_uInt8 nCurrDirType = rDirInfo.nType; + + if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run + ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run + { + size_t nIdx = 0; + + // Skip entries in ScriptArray which are not inside the RTL run: + while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart ) + ++nIdx; + + // Remove any entries *inside* the current run: + while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd) + { + // coverity[use_iterator] - we're protected from a bad iterator by the above condition + rTypes.erase(rTypes.begin() + nIdx); + } + + // special case: + if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd) + { + rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) ); + rTypes[nIdx].nEndPos = nStart; + } + + if( nIdx ) + rTypes[nIdx - 1].nEndPos = nStart; + + rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) ); + ++nIdx; + + if( nIdx < rTypes.size() ) + rTypes[nIdx].nStartPos = nEnd; + } + } +} + +namespace { + +struct FindByPos +{ + explicit FindByPos(sal_Int32 nPos) + : mnPos(nPos) + { + } + + bool operator()(const ScriptTypePosInfos::value_type& rValue) + { + return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos; + } + +private: + sal_Int32 mnPos; +}; + +} + +sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const +{ + sal_uInt16 nScriptType = 0; + + if ( pEndPos ) + *pEndPos = rPaM.GetNode()->Len(); + + if ( rPaM.GetNode()->Len() ) + { + sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + + const sal_Int32 nPos = rPaM.GetIndex(); + ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos)); + if(itr != rTypes.end()) + { + nScriptType = itr->nScriptType; + if( pEndPos ) + *pEndPos = itr->nEndPos; + } + } + } + return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() ); +} + +SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const +{ + EditSelection aSel( rSel ); + aSel.Adjust( maEditDoc ); + + SvtScriptType nScriptType = SvtScriptType::NONE; + + sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + continue; + + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + + // find all the scripts of this range + sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0; + sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len(); + + //no selection, just bare cursor + if (nStartPara == nEndPara && nS == nE) + { + //If we are not at the start of the paragraph we want the properties of the + //preceding character. Otherwise get the properties of the next (or what the + //next would have if it existed) + if (nS != 0) + --nS; + else + ++nE; + } + + for (const ScriptTypePosInfo & rType : rTypes) + { + bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos; + bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos; + + if (bStartInRange || bEndInRange) + { + if ( rType.nScriptType != i18n::ScriptType::WEAK ) + nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType ); + } + } + } + return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() ); +} + +bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const +{ + bool bScriptChange = false; + + if ( rPaM.GetNode()->Len() ) + { + sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() ); + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + const sal_Int32 nPos = rPaM.GetIndex(); + for (const ScriptTypePosInfo & rType : rTypes) + { + if ( rType.nStartPos == nPos ) + { + bScriptChange = true; + break; + } + } + } + } + return bScriptChange; +} + +bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const +{ + bool bTypeFound = false; + + const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aScriptInfos.empty() ) + const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + for ( size_t n = rTypes.size(); n && !bTypeFound; ) + { + if ( rTypes[--n].nScriptType == nType ) + bTypeFound = true; + } + } + return bTypeFound; +} + +void ImpEditEngine::InitWritingDirections( sal_Int32 nPara ) +{ + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (!pParaPortion) + return; + + WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos; + rInfos.clear(); + + if (pParaPortion->GetNode()->Len() && !mbFuzzing) + { + const OUString aText = pParaPortion->GetNode()->GetString(); + + // Bidi functions from icu 2.0 + + UErrorCode nError = U_ZERO_ERROR; + UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError ); + nError = U_ZERO_ERROR; + + const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/; + ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError ); + nError = U_ZERO_ERROR; + + int32_t nCount = ubidi_countRuns( pBidi, &nError ); + + /* ubidi_countRuns can return -1 in case of error */ + if (nCount > 0) + { + int32_t nStart = 0; + int32_t nEnd; + UBiDiLevel nCurrDir; + + for (int32_t nIdx = 0; nIdx < nCount; ++nIdx) + { + ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir ); + rInfos.emplace_back( nCurrDir, nStart, nEnd ); + nStart = nEnd; + } + } + + ubidi_close( pBidi ); + } + + // No infos mean ubidi error, default to LTR + if ( rInfos.empty() ) + rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() ); + +} + +bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const +{ + bool bR2L = false; + const SvxFrameDirectionItem* pFrameDirItem = nullptr; + + if ( !IsEffectivelyVertical() ) + { + bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L; + pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR ); + if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment ) + { + // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default. + if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default ) + { + pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction + } + else + { + // Use pool default + pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR); + } + } + } + + if ( pFrameDirItem ) + bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB; + + return bR2L; +} + +bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode ) +{ + bool bHasDifferentRTLLevels = false; + + sal_Int32 nPara = GetEditDoc().GetPos( pNode ); + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0; + for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel ) + { + bHasDifferentRTLLevels = true; + break; + } + } + } + return bHasDifferentRTLLevels; +} + + +sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd ) +{ + sal_uInt8 nRightToLeft = 0; + + ContentNode* pNode = maEditDoc.GetObject( nPara ); + if ( pNode && pNode->Len() ) + { + ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara ); + if (pParaPortion) + { + if ( pParaPortion->aWritingDirectionInfos.empty() ) + InitWritingDirections( nPara ); + + WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rDirInfo : rDirInfos) + { + if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) ) + { + nRightToLeft = rDirInfo.nType; + if ( pStart ) + *pStart = rDirInfo.nStartPos; + if ( pEnd ) + *pEnd = rDirInfo.nEndPos; + break; + } + } + } + } + return nRightToLeft; +} + +SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const +{ + SvxAdjust eJustification = SvxAdjust::Left; + + if (!maStatus.IsOutliner()) + { + eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust(); + + if ( IsRightToLeft( nPara ) ) + { + if ( eJustification == SvxAdjust::Left ) + eJustification = SvxAdjust::Right; + else if ( eJustification == SvxAdjust::Right ) + eJustification = SvxAdjust::Left; + } + } + return eJustification; +} + +SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const +{ + const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD); + return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue()); +} + +SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const +{ + const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST); + return static_cast<SvxCellVerJustify>(rItem.GetEnumValue()); +} + +// Text changes +void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) ); + + // Check whether attributes are deleted or changed: + const sal_Int32 nStart = rPaM.GetIndex(); + const sal_Int32 nEnd = nStart + nChars; + const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs(); + for (const auto & rAttrib : rAttribs) + { + const EditCharAttrib& rAttr = *rAttrib; + if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd) + { + EditSelection aSel( rPaM ); + aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars ); + InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) ); + break; // for + } + } + InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr)); + } + + maEditDoc.RemoveChars( rPaM, nChars ); +} + +EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos ) +{ + aOldPositions.Normalize(); + bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() ); + OSL_ENSURE( bValidAction, "Move in itself?" ); + OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" ); + + EditSelection aSelection; + + if ( !bValidAction ) + { + aSelection = maEditDoc.GetStartPaM(); + return aSelection; + } + + sal_Int32 nParaCount = GetParaPortions().Count(); + + if ( nNewPos >= nParaCount ) + nNewPos = nParaCount; + + // Height may change when moving first or last Paragraph + ParaPortion* pRecalc1 = nullptr; + ParaPortion* pRecalc2 = nullptr; + ParaPortion* pRecalc3 = nullptr; + ParaPortion* pRecalc4 = nullptr; + + if ( nNewPos == 0 ) // Move to Start + { + pRecalc1 = GetParaPortions()[0]; + pRecalc2 = GetParaPortions()[aOldPositions.Min()]; + + } + else if ( nNewPos == nParaCount ) + { + pRecalc1 = GetParaPortions()[nParaCount-1]; + pRecalc2 = GetParaPortions()[aOldPositions.Max()]; + } + + if ( aOldPositions.Min() == 0 ) // Move from Start + { + pRecalc3 = GetParaPortions()[0]; + pRecalc4 = GetParaPortions()[aOldPositions.Max()+1]; + } + else if ( aOldPositions.Max() == (nParaCount-1) ) + { + pRecalc3 = GetParaPortions()[aOldPositions.Max()]; + pRecalc4 = GetParaPortions()[aOldPositions.Min()-1]; + } + + MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos ); + aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + + if ( IsUndoEnabled() && !IsInUndo()) + InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos)); + + // do not lose sight of the Position ! + ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos ); + + ParaPortionList aTmpPortionList; + for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ ) + { + // always aOldPositions.Min(), since Remove(). + std::unique_ptr<ParaPortion> pTmpPortion = GetParaPortions().Release(aOldPositions.Min()); + maEditDoc.Release( aOldPositions.Min() ); + aTmpPortionList.Append(std::move(pTmpPortion)); + } + + sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count(); + assert( nRealNewPos != EE_PARA_NOT_FOUND && "ImpMoveParagraphs: Invalid Position!" ); + + sal_Int32 i = 0; + while( aTmpPortionList.Count() > 0 ) + { + std::unique_ptr<ParaPortion> pTmpPortion = aTmpPortionList.Release(0); + if ( i == 0 ) + aSelection.Min().SetNode( pTmpPortion->GetNode() ); + + aSelection.Max().SetNode( pTmpPortion->GetNode() ); + aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() ); + + ContentNode* pN = pTmpPortion->GetNode(); + maEditDoc.Insert(nRealNewPos+i, pN); + + GetParaPortions().Insert(nRealNewPos+i, std::move(pTmpPortion)); + ++i; + } + + aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo ); + + if ( GetNotifyHdl().IsSet() ) + { + EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED ); + aNotify.nParagraph = nNewPos; + aNotify.nParam1 = aOldPositions.Min(); + aNotify.nParam2 = aOldPositions.Max(); + GetNotifyHdl().Call( aNotify ); + } + + maEditDoc.SetModified( true ); + + if ( pRecalc1 ) + CalcHeight( pRecalc1 ); + if ( pRecalc2 ) + CalcHeight( pRecalc2 ); + if ( pRecalc3 ) + CalcHeight( pRecalc3 ); + if ( pRecalc4 ) + CalcHeight( pRecalc4 ); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + ParaPortionList::DbgCheck(GetParaPortions(), maEditDoc); +#endif + return aSelection; +} + + +EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward ) +{ + OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" ); + OSL_ENSURE( maEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" ); + OSL_ENSURE( maEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" ); + + // #i120020# it is possible that left and right are *not* in the desired order (left/right) + // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be + // created from ConnectParagraphs below. Assert this situation, it should be corrected by the + // caller. + if (maEditDoc.GetPos( pLeft ) > maEditDoc.GetPos( pRight )) + { + OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)"); + std::swap(pLeft, pRight); + } + + sal_Int32 nParagraphTobeDeleted = maEditDoc.GetPos( pRight ); + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted )); + + GetEditEnginePtr()->ParagraphConnected( maEditDoc.GetPos( pLeft ), maEditDoc.GetPos( pRight ) ); + + if ( IsUndoEnabled() && !IsInUndo() ) + { + InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine, + maEditDoc.GetPos( pLeft ), pLeft->Len(), + pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(), + pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) ); + } + + if ( bBackward ) + { + pLeft->SetStyleSheet( pRight->GetStyleSheet() ); + // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046 + if ( pRight->Len() ) + pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() ); + pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont(); + } + + ParaAttribsChanged( pLeft, true ); + + // First search for Portions since pRight is gone after ConnectParagraphs. + ParaPortion* pLeftPortion = FindParaPortion( pLeft ); + assert(pLeftPortion); + + if ( GetStatus().DoOnlineSpelling() ) + { + sal_Int32 nEnd = pLeft->Len(); + sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; + pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft ); // Possibly remove one + pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1); + // Take over misspelled words + WrongList* pRWrongs = pRight->GetWrongList(); + for (auto & elem : *pRWrongs) + { + if (elem.mnStart != 0) // Not a subsequent + { + elem.mnStart = elem.mnStart + nEnd; + elem.mnEnd = elem.mnEnd + nEnd; + pLeft->GetWrongList()->push_back(elem); + } + } + } + + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted ); + + EditPaM aPaM = maEditDoc.ConnectParagraphs( pLeft, pRight ); + GetParaPortions().Remove( nParagraphTobeDeleted ); + + pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() ); + + // the right node is deleted by EditDoc:ConnectParagraphs(). + if ( GetTextRanger() ) + { + // By joining together the two, the left is although reformatted, + // however if its height does not change then the formatting receives + // the change of the total text height too late... + for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ ) + { + ParaPortion* pPP = GetParaPortions()[n]; + pPP->MarkSelectionInvalid( 0 ); + pPP->GetLines().Reset(); + } + } + + TextModified(); + + return aPaM; +} + +EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode ) +{ + OSL_ENSURE( !rSel.DbgIsBuggy( maEditDoc ), "Index out of range in DeleteLeftOrRight" ); + + if ( rSel.HasRange() ) // only then Delete Selection + return ImpDeleteSelection( rSel ); + + EditPaM aCurPos( rSel.Max() ); + EditPaM aDelStart( aCurPos ); + EditPaM aDelEnd( aCurPos ); + if ( nMode == DEL_LEFT ) + { + if ( nDelMode == DeleteMode::Simple ) + { + sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER; + // If we are deleting a variation selector, we want to delete the + // whole sequence (cell). + sal_Int32 nIndex = aCurPos.GetIndex(); + if (nIndex > 0) + { + const OUString& rString = aCurPos.GetNode()->GetString(); + sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1); + if (unicode::isVariationSelector(nCode)) + nCharMode = i18n::CharacterIteratorMode::SKIPCELL; + } + aDelStart = CursorLeft(aCurPos, nCharMode); + } + else if ( nDelMode == DeleteMode::RestOfWord ) + { + aDelStart = StartOfWord( aCurPos ); + if ( aDelStart.GetIndex() == aCurPos.GetIndex() ) + aDelStart = WordLeft( aCurPos ); + } + else // DELMODE_RESTOFCONTENT + { + aDelStart.SetIndex( 0 ); + if ( aDelStart == aCurPos ) + { + // Complete paragraph previous + ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() ); + if ( pPrev ) + aDelStart = EditPaM( pPrev, 0 ); + } + } + } + else + { + if ( nDelMode == DeleteMode::Simple ) + { + aDelEnd = CursorRight( aCurPos ); + } + else if ( nDelMode == DeleteMode::RestOfWord ) + { + aDelEnd = EndOfWord( aCurPos ); + if (aDelEnd.GetIndex() == aCurPos.GetIndex()) + { + const sal_Int32 nLen(aCurPos.GetNode()->Len()); + // end of para? + if (aDelEnd.GetIndex() == nLen) + { + ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); + if ( pNext ) + aDelEnd = EditPaM( pNext, 0 ); + } + else // there's still something to delete on the right + { + aDelEnd = EndOfWord( WordRight( aCurPos ) ); + } + } + } + else // DELMODE_RESTOFCONTENT + { + aDelEnd.SetIndex( aCurPos.GetNode()->Len() ); + if ( aDelEnd == aCurPos ) + { + // Complete paragraph next + ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() ); + if ( pNext ) + aDelEnd = EditPaM( pNext, pNext->Len() ); + } + } + } + + // ConnectParagraphs not enough for different Nodes when + // DeleteMode::RestOfContent. + if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) ) + return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) ); + + return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode()); +} + +EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel) +{ + if ( !rCurSel.HasRange() ) + return rCurSel.Min(); + + EditSelection aCurSel(rCurSel); + aCurSel.Adjust( maEditDoc ); + EditPaM aStartPaM(aCurSel.Min()); + EditPaM aEndPaM(aCurSel.Max()); + + if( nullptr != aStartPaM.GetNode() ) + aStartPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear... + if( nullptr != aEndPaM.GetNode() ) + aEndPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear... + + OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); + OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" ); + + sal_Int32 nStartNode = maEditDoc.GetPos( aStartPaM.GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aEndPaM.GetNode() ); + + OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" ); + OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" ); + + // Remove all nodes in between... + for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ ) + { + // Always nStartNode+1, due to Remove()! + ImpRemoveParagraph( nStartNode+1 ); + } + + if ( aStartPaM.GetNode() != aEndPaM.GetNode() ) + { + // The Rest of the StartNodes... + ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() ); + ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); + assert(pPortion); + pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() ); + + // The beginning of the EndNodes... + const sal_Int32 nChars = aEndPaM.GetIndex(); + aEndPaM.SetIndex( 0 ); + ImpRemoveChars( aEndPaM, nChars ); + pPortion = FindParaPortion( aEndPaM.GetNode() ); + assert(pPortion); + pPortion->MarkSelectionInvalid( 0 ); + // Join together... + aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() ); + } + else + { + ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() ); + ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() ); + assert(pPortion); + pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() ); + } + + UpdateSelections(); + TextModified(); + return aStartPaM; +} + +void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara ) +{ + ContentNode* pNode = maEditDoc.GetObject( nPara ); + ContentNode* pNextNode = maEditDoc.GetObject( nPara+1 ); + + assert(pNode); + + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara )); + + // The node is managed by the undo and possibly destroyed! + maEditDoc.Release( nPara ); + GetParaPortions().Remove( nPara ); + + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphDeleted( nPara ); + } + + // Extra-Space may be determined again in the following. For + // ParaAttribsChanged the paragraph is unfortunately formatted again, + // however this method should not be time critical! + if ( pNextNode ) + ParaAttribsChanged( pNextNode ); + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara)); + else + { + if ( pNode->GetStyleSheet() ) + EndListening( *pNode->GetStyleSheet() ); + delete pNode; + } +} + +EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c, + bool bOverwrite, vcl::Window const * pFrameWin ) +{ + // i.e. Calc has special needs regarding a leading single quotation mark + // when starting cell input. + if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() && + rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0) + { + return InsertTextUserInput( rCurSel, c, bOverwrite ); + } + + EditSelection aSel( rCurSel ); + SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if ( pAutoCorrect ) + { + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( rCurSel ); + + // #i78661 allow application to turn off capitalization of + // start sentence explicitly. + // (This is done by setting IsFirstWordCapitalization to sal_False.) + bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence ); + if (!IsFirstWordCapitalization()) + { + ESelection aESel( CreateESel(aSel) ); + EditSelection aFirstWordSel; + EditSelection aSecondWordSel; + if (aESel.nEndPara == 0) // is this the first para? + { + // select first word... + // start by checking if para starts with word. + aFirstWordSel = SelectWord( CreateSel(ESelection()) ); + if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0) + { + // para does not start with word -> select next/first word + EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) ); + aFirstWordSel = SelectWord( EditSelection( aRightWord ) ); + } + + // select second word + // (sometimes aSel might not point to the end of the first word + // but to some following char like '.'. ':', ... + // In those cases we need aSecondWordSel to see if aSel + // will actually effect the first word.) + EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) ); + aSecondWordSel = SelectWord( EditSelection( aRight2Word ) ); + } + bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 && + aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() && + aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex(); + + if (bIsFirstWordInFirstPara) + pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() ); + } + + ContentNode* pNode = aSel.Max().GetNode(); + const sal_Int32 nIndex = aSel.Max().GetIndex(); + EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c); + // FIXME: this _must_ be called with reference to the actual node text! + OUString const& rNodeString(pNode->GetString()); + pAutoCorrect->DoAutoCorrect( + aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin ); + aSel.Max().SetIndex( aAuto.GetCursor() ); + + // #i78661 since the SvxAutoCorrect object used here is + // shared we need to reset the value to its original state. + pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence ); + } + return aSel.Max(); +} + + +EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel, + sal_Unicode c, bool bOverwrite ) +{ + OSL_ENSURE( c != '\t', "Tab for InsertText ?" ); + OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?"); + + EditPaM aPaM( rCurSel.Min() ); + + bool bDoOverwrite = bOverwrite && + ( aPaM.GetIndex() < aPaM.GetNode()->Len() ); + + bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite ); + + if ( bUndoAction ) + UndoActionStart( EDITUNDO_INSERT ); + + if ( rCurSel.HasRange() ) + { + aPaM = ImpDeleteSelection( rCurSel ); + } + else if ( bDoOverwrite ) + { + // If selected, then do not also overwrite a character! + EditSelection aTmpSel( aPaM ); + aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 ); + OSL_ENSURE( !aTmpSel.DbgIsBuggy( maEditDoc ), "Overwrite: Wrong selection! "); + ImpDeleteSelection( aTmpSel ); + } + + if ( aPaM.GetNode()->Len() < MAXCHARSINPARA ) + { + if (IsInputSequenceCheckingRequired( c, rCurSel )) + { + uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() ); + + if (_xISC) + { + const sal_Int32 nTmpPos = aPaM.GetIndex(); + sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ? + i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; + + // the text that needs to be checked is only the one + // before the current cursor position + const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) ); + OUString aNewText( aOldText ); + if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace()) + { + _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode); + + // find position of first character that has changed + sal_Int32 nOldLen = aOldText.getLength(); + sal_Int32 nNewLen = aNewText.getLength(); + const sal_Unicode *pOldTxt = aOldText.getStr(); + const sal_Unicode *pNewTxt = aNewText.getStr(); + sal_Int32 nChgPos = 0; + while ( nChgPos < nOldLen && nChgPos < nNewLen && + pOldTxt[nChgPos] == pNewTxt[nChgPos] ) + ++nChgPos; + + const OUString aChgText( aNewText.copy( nChgPos ) ); + + // select text from first pos to be changed to current pos + EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM ); + + if (!aChgText.isEmpty()) + return InsertText( aSel, aChgText ); // implicitly handles undo + else + return aPaM; + } + else + { + // should the character be ignored (i.e. not get inserted) ? + if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode )) + return aPaM; // nothing to be done -> no need for undo + } + } + + // at this point now we will insert the character 'normally' some lines below... + } + + if ( IsUndoEnabled() && !IsInUndo() ) + { + std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c))); + bool bTryMerge = !bDoOverwrite && ( c != ' ' ); + InsertUndo( std::move(pNewUndo), bTryMerge ); + } + + maEditDoc.InsertText( aPaM, OUStringChar(c) ); + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + assert(pPortion); + pPortion->MarkInvalid( aPaM.GetIndex(), 1 ); + aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore + } + + TextModified(); + + if ( bUndoAction ) + UndoActionEnd(); + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr) +{ + UndoActionStart( EDITUNDO_INSERT ); + + EditPaM aPaM; + if ( aCurSel.HasRange() ) + aPaM = ImpDeleteSelection( aCurSel ); + else + aPaM = aCurSel.Max(); + + EditPaM aCurPaM( aPaM ); // for the Invalidate + + // get word boundaries in order to clear possible WrongList entries + // and invalidate all the necessary text (everything after and including the + // start of the word) + // #i107201# do the expensive SelectWord call only if online spelling is active + EditSelection aCurWord; + if ( GetStatus().DoOnlineSpelling() ) + aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD ); + + OUString aText(convertLineEnd(rStr, LINEEND_LF)); + if (mbFuzzing) //tab expansion performance in editeng is appalling + aText = aText.replaceAll("\t","-"); + SfxVoidItem aTabItem( EE_FEATURE_TAB ); + + // Converts to linesep = \n + // Token LINE_SEP query, + // since the MAC-Compiler makes something else from \n ! + + sal_Int32 nStart = 0; + while ( nStart < aText.getLength() ) + { + sal_Int32 nEnd = !maStatus.IsSingleLine() ? + aText.indexOf( LINE_SEP, nStart ) : -1; + if ( nEnd == -1 ) + nEnd = aText.getLength(); // not dereference! + + // Start == End => empty line + if ( nEnd > nStart ) + { + OUString aLine = aText.copy( nStart, nEnd-nStart ); + sal_Int32 nExistingChars = aPaM.GetNode()->Len(); + sal_Int32 nChars = nExistingChars + aLine.getLength(); + if (nChars > MAXCHARSINPARA) + { + sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars); + // Wherever we break, it may be wrong. However, try to find the + // previous non-alnum/non-letter character. Note this is only + // in the to be appended data, otherwise already existing + // characters would have to be moved and PaM to be updated. + // Restrict to 2*42, if not found by then assume other data or + // language-script uses only letters or idiographs. + sal_Int32 nPos = nMaxNewChars; + while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84) + { + auto nNextPos = nPos; + const auto c = aLine.iterateCodePoints(&nNextPos); + switch (unicode::getUnicodeType(c)) + { + case css::i18n::UnicodeType::UPPERCASE_LETTER: + case css::i18n::UnicodeType::LOWERCASE_LETTER: + case css::i18n::UnicodeType::TITLECASE_LETTER: + case css::i18n::UnicodeType::MODIFIER_LETTER: + case css::i18n::UnicodeType::OTHER_LETTER: + case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER: + case css::i18n::UnicodeType::LETTER_NUMBER: + case css::i18n::UnicodeType::OTHER_NUMBER: + case css::i18n::UnicodeType::CURRENCY_SYMBOL: + break; + default: + { + // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP. + if (c == 0x00A0 || c == 0x202F || c == 0xFEFF) + break; + const auto n = aLine.iterateCodePoints(&nNextPos, 0); + if (c == '-' && nNextPos < nMaxNewChars) + { + // Keep HYPHEN-MINUS with a number to the right. + const sal_Int16 t = unicode::getUnicodeType(n); + if ( t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER || + t == css::i18n::UnicodeType::LETTER_NUMBER || + t == css::i18n::UnicodeType::OTHER_NUMBER) + nMaxNewChars = nPos; // line break before + else + nMaxNewChars = nNextPos; // line break after + } + else + { + nMaxNewChars = nNextPos; // line break after + } + nPos = 0; // will break loop + } + } + } + // Remaining characters end up in the next paragraph. Note that + // new nStart will be nEnd+1 below so decrement by one more. + nEnd -= (aLine.getLength() - nMaxNewChars + 1); + aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest... + } + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine)); + // Tabs ? + if ( aLine.indexOf( '\t' ) == -1 ) + aPaM = maEditDoc.InsertText( aPaM, aLine ); + else + { + sal_Int32 nStart2 = 0; + while ( nStart2 < aLine.getLength() ) + { + sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 ); + if ( nEnd2 == -1 ) + nEnd2 = aLine.getLength(); // not dereference! + + if ( nEnd2 > nStart2 ) + aPaM = maEditDoc.InsertText( aPaM, aLine.subView( nStart2, nEnd2-nStart2 ) ); + if ( nEnd2 < aLine.getLength() ) + { + aPaM = maEditDoc.InsertFeature( aPaM, aTabItem ); + } + nStart2 = nEnd2+1; + } + } + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + assert(pPortion); + + if ( GetStatus().DoOnlineSpelling() ) + { + // now remove the Wrongs (red spell check marks) from both words... + WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList(); + if (pWrongs && !pWrongs->empty()) + pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() ); + // ... and mark both words as 'to be checked again' + pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() ); + } + else + pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() ); + } + if ( nEnd < aText.getLength() ) + aPaM = ImpInsertParaBreak( aPaM ); + + nStart = nEnd+1; + } + + UndoActionEnd(); + + TextModified(); + return aPaM; +} + +EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr ) +{ + OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! "); + OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! "); + OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! "); + + if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA ) + { + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr)); + + aPaM = maEditDoc.InsertText( aPaM, rStr ); + TextModified(); + } + else + { + aPaM = ImpInsertText( aPaM, rStr ); + } + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem) +{ + EditPaM aPaM; + if ( rCurSel.HasRange() ) + aPaM = ImpDeleteSelection( rCurSel ); + else + aPaM = rCurSel.Max(); + + if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 ) + return aPaM; + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem)); + aPaM = maEditDoc.InsertFeature( aPaM, rItem ); + UpdateFields(); + + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + assert(pPortion); + pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 ); + + TextModified(); + + return aPaM; +} + +EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel ) +{ + EditPaM aPaM; + if ( rCurSel.HasRange() ) + aPaM = ImpDeleteSelection( rCurSel ); + else + aPaM = rCurSel.Max(); + + return ImpInsertParaBreak( aPaM ); +} + +EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs ) +{ + if ( maEditDoc.Count() >= EE_PARA_MAX_COUNT ) + { + SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than " + << EE_PARA_MAX_COUNT << " paragraphs!"); + return rPaM; + } + + if ( IsUndoEnabled() && !IsInUndo() ) + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, maEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex())); + + EditPaM aPaM( maEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) ); + if (auto pStyle = aPaM.GetNode()->GetStyleSheet()) + StartListening(*pStyle, DuplicateHandling::Allow); + + if ( GetStatus().DoOnlineSpelling() ) + { + sal_Int32 nEnd = rPaM.GetNode()->Len(); + aPaM.GetNode()->CreateWrongList(); + WrongList* pLWrongs = rPaM.GetNode()->GetWrongList(); + WrongList* pRWrongs = aPaM.GetNode()->GetWrongList(); + // take over misspelled words: + for (auto & elem : *pLWrongs) + { + // Correct only if really a word gets overlapped in the process of + // Spell checking + if (elem.mnStart > o3tl::make_unsigned(nEnd)) + { + pRWrongs->push_back(elem); + editeng::MisspellRange& rRWrong = pRWrongs->back(); + rRWrong.mnStart = rRWrong.mnStart - nEnd; + rRWrong.mnEnd = rRWrong.mnEnd - nEnd; + } + else if (elem.mnStart < o3tl::make_unsigned(nEnd) && elem.mnEnd > o3tl::make_unsigned(nEnd)) + elem.mnEnd = nEnd; + } + sal_Int32 nInv = nEnd ? nEnd-1 : nEnd; + if ( nEnd ) + pLWrongs->SetInvalidRange(nInv, nEnd); + else + pLWrongs->SetValid(); + pRWrongs->SetValid(); + pRWrongs->SetInvalidRange(0, 1); // Only test the first word + } + + ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() ); + assert(pPortion); + pPortion->MarkInvalid( rPaM.GetIndex(), 0 ); + + // Optimization: Do not place unnecessarily many getPos to Listen! + // Here, as in undo, but also in all other methods. + sal_Int32 nPos = GetParaPortions().GetPos( pPortion ); + ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() ); + GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion)); + ParaAttribsChanged( pNewPortion->GetNode() ); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPos+1 ); + + if( nullptr != rPaM.GetNode() ) + rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged. + + TextModified(); + return aPaM; +} + +EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + if ( nPara ) + { + assert(maEditDoc.GetObject(nPara - 1)); + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, maEditDoc.GetObject( nPara-1 )->Len())); + } + else + InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0)); + } + + ContentNode* pNode = new ContentNode( maEditDoc.GetItemPool() ); + // If flat mode, then later no Font is set: + pNode->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont(); + + if ( GetStatus().DoOnlineSpelling() ) + pNode->CreateWrongList(); + + maEditDoc.Insert(nPara, pNode); + + GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode )); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPara ); + + return EditPaM( pNode, 0 ); +} + +EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel) +{ + EditPaM aPaM(ImpInsertParaBreak(rCurSel)); + if ( maStatus.DoAutoIndenting() ) + { + sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() ); + OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" ); + const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) ); + sal_Int32 n = 0; + while ( ( n < aPrevParaText.getLength() ) && + ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) ) + { + if ( aPrevParaText[n] == '\t' ) + aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) ); + else + aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) ); + n++; + } + + } + return aPaM; +} + +EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel) +{ + EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB ))); + return aPaM; +} + +EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld) +{ + return ImpInsertFeature(rCurSel, rFld); +} + +bool ImpEditEngine::UpdateFields() +{ + bool bChanges = false; + sal_Int32 nParas = GetEditDoc().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + bool bChangesInPara = false; + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + assert(pNode); + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) + { + EditCharAttrib& rAttr = *rAttrib; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr); + EditCharAttribField aCurrent(rField); + rField.Reset(); + + if (!maStatus.MarkNonUrlFields() && !maStatus.MarkUrlFields()) + ; // nothing marked + else if (maStatus.MarkNonUrlFields() && maStatus.MarkUrlFields()) + rField.GetFieldColor() = GetColorConfig().GetColorValue(svtools::WRITERFIELDSHADINGS).nColor; + else + { + bool bURL = false; + if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem())) + { + if (const SvxFieldData* pFieldData = pFieldItem->GetField()) + bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr); + } + if ((bURL && maStatus.MarkUrlFields()) || (!bURL && maStatus.MarkNonUrlFields())) + rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor; + } + + const OUString aFldValue = + GetEditEnginePtr()->CalcFieldValue( + static_cast<const SvxFieldItem&>(*rField.GetItem()), + nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor(), rField.GetFldLineStyle() ); + + rField.SetFieldValue(aFldValue); + if (rField != aCurrent) + { + bChanges = true; + bChangesInPara = true; + } + } + } + if ( bChangesInPara ) + { + // If possible be more precise when invalidate. + ParaPortion* pPortion = GetParaPortions()[nPara]; + assert(pPortion); + pPortion->MarkSelectionInvalid( 0 ); + } + } + return bChanges; +} + +EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) +{ + EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) ); + return aPaM; +} + + +// Helper functions + +tools::Rectangle ImpEditEngine::GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, + sal_Int32 nIndex, GetCursorFlags nFlags) +{ + assert(pPortion && pLine); + // nIndex might be not in the line + // Search within the line... + tools::Long nX; + + if ((nIndex == pLine->GetStart()) && (nFlags & GetCursorFlags::StartOfLine)) + { + Range aXRange = GetLineXPosStartEnd(pPortion, pLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Min() + : aXRange.Max(); + } + else if ((nIndex == pLine->GetEnd()) && (nFlags & GetCursorFlags::EndOfLine)) + { + Range aXRange = GetLineXPosStartEnd(pPortion, pLine); + nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Max() + : aXRange.Min(); + } + else + { + nX = GetXPos(pPortion, pLine, nIndex, bool(nFlags & GetCursorFlags::PreferPortionStart)); + } + + tools::Rectangle aEditCursor; + aEditCursor.SetLeft(nX); + aEditCursor.SetRight(nX); + + aEditCursor.SetBottom(pLine->GetHeight() - 1); + if (nFlags & GetCursorFlags::TextOnly) + aEditCursor.SetTop(aEditCursor.Bottom() - pLine->GetTxtHeight() + 1); + else + aEditCursor.SetTop(aEditCursor.Bottom() + - std::min(pLine->GetTxtHeight(), pLine->GetHeight()) + 1); + return aEditCursor; +} + +tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) +{ + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" ); + + tools::Rectangle aEditCursor; + const sal_Int32 nIndex = aPaM.GetIndex(); + const ParaPortion* pPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Rectangle aLineArea; + + auto FindPortionLineAndArea + = [&, bEOL(bool(nFlags & GetCursorFlags::EndOfLine))](const LineAreaInfo& rInfo) { + if (!rInfo.pLine) // start of ParaPortion + { + ContentNode* pNode = rInfo.rPortion.GetNode(); + OSL_ENSURE(pNode, "Invalid Node in Portion!"); + if (pNode != aPaM.GetNode()) + return CallbackResult::SkipThisPortion; + pPortion = &rInfo.rPortion; + } + else // guaranteed that this is the correct ParaPortion + { + pLastLine = rInfo.pLine; + aLineArea = rInfo.aArea; + if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL))) + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindPortionLineAndArea, IterFlag::none); + + if (pLastLine) + { + aEditCursor = GetEditCursor(pPortion, pLastLine, nIndex, nFlags); + aEditCursor.Move(getTopLeftDocOffset(aLineArea)); + } + else + OSL_FAIL("Line not found!"); + + return aEditCursor; +} + +void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions) +{ + const Point aOrigin(0, 0); + Point aLineStart(aOrigin); + const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart); + const tools::Long nColumnWidth = GetColumnWidth(maPaperSize); + sal_Int16 nColumn = 0; + for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n) + { + ParaPortion* pPortion = GetParaPortions()[n]; + bool bSkipThis = true; + if (pPortion->IsVisible()) + { + // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid. + if (pPortion->IsInvalid()) + return; + + LineAreaInfo aInfo{ + *pPortion, // pPortion + nullptr, // pLine + 0, // nHeightNeededToNotWrap + { aLineStart, Size{ nColumnWidth, pPortion->GetFirstLineOffset() } }, // aArea + n, // nPortion + 0, // nLine + nColumn // nColumn + }; + auto eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + + sal_uInt16 nSBL = 0; + if (!maStatus.IsOutliner()) + { + const SvxLineSpacingItem& rLSItem + = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix) + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) + : 0; + } + + adjustYDirectionAware(aLineStart, pPortion->GetFirstLineOffset()); + for (sal_Int32 nLine = 0, nLines = pPortion->GetLines().Count(); nLine < nLines; nLine++) + { + EditLine& rLine = pPortion->GetLines()[nLine]; + tools::Long nLineHeight = rLine.GetHeight(); + if (nLine != nLines - 1) + nLineHeight += nVertLineSpacing; + MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin, + &aInfo.nHeightNeededToNotWrap); + const bool bInclILS = eOptions & IterFlag::inclILS; + if (bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner()) + { + adjustYDirectionAware(aLineStart, nSBL); + nLineHeight += nSBL; + } + + if (!bSkipThis) + { + Point aOtherCorner(aLineStart); + adjustXDirectionAware(aOtherCorner, nColumnWidth); + adjustYDirectionAware(aOtherCorner, -nLineHeight); + + // Calls to f() for each line + aInfo.nColumn = nColumn; + aInfo.pLine = &rLine; + aInfo.nLine = nLine; + aInfo.aArea = tools::Rectangle::Normalize(aLineStart, aOtherCorner); + eResult = f(aInfo); + if (eResult == CallbackResult::Stop) + return; + bSkipThis = eResult == CallbackResult::SkipThisPortion; + } + + if (!bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner()) + adjustYDirectionAware(aLineStart, nSBL); + } + if (!maStatus.IsOutliner()) + { + const SvxULSpaceItem& rULItem + = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); + adjustYDirectionAware(aLineStart, nUL); + } + } + // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it + } +} + +std::tuple<const ParaPortion*, const EditLine*, tools::Long> +ImpEditEngine::GetPortionAndLine(Point aDocPos) +{ + // First find the column from the point + sal_Int32 nClickColumn = 0; + for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(maPaperSize);; + nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn) + { + if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2) + break; + if (nClickColumn >= mnColumns - 1) + break; + } + + const ParaPortion* pLastPortion = nullptr; + const EditLine* pLastLine = nullptr; + tools::Long nLineStartX = 0; + Point aPos; + adjustYDirectionAware(aPos, aDocPos.Y()); + + auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) // Only handle lines, not ParaPortion starts + { + if (rInfo.nColumn > nClickColumn) + return CallbackResult::Stop; + pLastPortion = &rInfo.rPortion; // Candidate paragraph + pLastLine = rInfo.pLine; // Last visible line not later than click position + nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width(); + if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0) + return CallbackResult::Stop; // Found it + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS); + + return { pLastPortion, pLastLine, nLineStartX }; +} + +EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) +{ + assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" ); + + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion) + { + sal_Int32 nCurIndex + = GetChar(pPortion, pLine, aDocPos.X() - nLineStartX, bSmart); + EditPaM aPaM(pPortion->GetNode(), nCurIndex); + + if (nCurIndex && (nCurIndex == pLine->GetEnd()) + && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1])) + { + aPaM = CursorLeft(aPaM); + } + + return aPaM; + } + return {}; +} + +bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder) +{ + if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion) + { + Range aLineXPosStartEnd = GetLineXPosStartEnd(pPortion, pLine); + if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder) + && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder)) + return true; + } + return false; +} + +sal_uInt32 ImpEditEngine::GetTextHeight() const +{ + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); + OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); + return nCurTextHeight; +} + +sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace ) +{ + // If still not formatted and not in the process. + // Will be brought in the formatting for AutoPageSize. + if ( !IsFormatted() && !IsFormatting() ) + FormatDoc(); + + sal_uInt32 nMaxWidth = 0; + + // Over all the paragraphs ... + + sal_Int32 nParas = GetParaPortions().Count(); + for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace)); + } + + return nMaxWidth; +} + +sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace ) +{ + // If still not formatted and not in the process. + // Will be brought in the formatting for AutoPageSize. + if ( !IsFormatted() && !IsFormatting() ) + FormatDoc(); + + tools::Long nMaxWidth = 0; + + // Over all the paragraphs ... + + OSL_ENSURE( 0 <= nPara && nPara < GetParaPortions().Count(), "CalcParaWidth: Out of range" ); + ParaPortion* pPortion = GetParaPortions()[nPara]; + if ( pPortion && pPortion->IsVisible() ) + { + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() ); + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() ); + + + // On the lines of the paragraph ... + + sal_Int32 nLines = pPortion->GetLines().Count(); + for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) + { + EditLine& rLine = pPortion->GetLines()[nLine]; + // nCurWidth = pLine->GetStartPosX(); + // For Center- or Right- alignment it depends on the paper + // width, here not preferred. I general, it is best not leave it + // to StartPosX, also the right indents have to be taken into + // account! + tools::Long nCurWidth = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth); + if ( nLine == 0 ) + { + tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset()); + nCurWidth -= nFI; + if ( pPortion->GetBulletX() > nCurWidth ) + { + nCurWidth += nFI; // LI? + if ( pPortion->GetBulletX() > nCurWidth ) + nCurWidth = pPortion->GetBulletX(); + } + } + nCurWidth += scaleXSpacingValue(rLRItem.GetRight()); + nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace ); + if ( nCurWidth > nMaxWidth ) + { + nMaxWidth = nCurWidth; + } + } + } + + nMaxWidth++; // widen it, because in CreateLines for >= is wrapped. + return static_cast<sal_uInt32>(nMaxWidth); +} + +sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace ) +{ + sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() ); + + // #114278# Saving both layout mode and language (since I'm + // potentially changing both) + GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + + ImplInitLayoutMode(*GetRefDevice(), nPara, -1); + + SvxAdjust eJustification = GetJustification( nPara ); + + // Calculation of the width without the Indents ... + sal_uInt32 nWidth = 0; + sal_Int32 nPos = pLine->GetStart(); + for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ ) + { + const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP]; + switch ( rTextPortion.GetKind() ) + { + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + case PortionKind::TAB: + { + nWidth += rTextPortion.GetSize().Width(); + } + break; + case PortionKind::TEXT: + { + if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) ) + { + nWidth += rTextPortion.GetSize().Width(); + } + else + { + SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); + SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(), + pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr ).Width(); + } + } + break; + case PortionKind::LINEBREAK: break; + } + nPos = nPos + rTextPortion.GetLen(); + } + + GetRefDevice()->Pop(); + + return nWidth; +} + +sal_uInt32 ImpEditEngine::GetTextHeightNTP() const +{ + assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" ); + DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" ); + return nCurTextHeightNTP; +} + +tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP) +{ + tools::Long nHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + // Pretend that we have ~infinite height to get total height + comphelper::ValueRestorationGuard aGuard(nCurTextHeight, + std::numeric_limits<tools::Long>::max()); + + IterateLinesAreasFunc FindLastLineBottom = [&](const LineAreaInfo& rInfo) { + if (rInfo.pLine) + { + // bottom coordinate does not belong to area, so no need to do +1 + nHeight = getBottomDocOffset(rInfo.aArea); + if (pHeightNTP && !rInfo.rPortion.IsEmpty()) + *pHeightNTP = nHeight; + } + return CallbackResult::Continue; + }; + IterateLineAreas(FindLastLineBottom, IterFlag::none); + return nHeight; +} + +tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP) +{ + assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" ); + + if (mnColumns <= 1) + return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done! + + // The final column height can be smaller than total height divided by number of columns (taking + // into account first line offset and interline spacing, that aren't considered in positioning + // after the wrap). The wrap should only happen after the minimal height is exceeded. + tools::Long nTentativeColHeight = mnMinColumnWrapHeight; + tools::Long nWantedIncrease = 0; + tools::Long nCurrentTextHeight; + + // This does the necessary column balancing for the case when the text does not fit min height. + // When the height of column (taken from nCurTextHeight) is too small, the last column will + // overflow, so the resulting height of the text will exceed the set column height. Increasing + // the column height step by step by the minimal value that allows one of columns to accommodate + // one line more, we finally get to the point where all the text fits. At each iteration, the + // height is only increased, so it's impossible to have infinite layout loops. The found value + // is the global minimum. + // + // E.g., given the following four line heights: + // Line 1: 10; + // Line 2: 12; + // Line 3: 10; + // Line 4: 10; + // number of columns 3, and the minimal paper height of 5, the iterations would be: + // * Tentative column height is set to 5 + // <ITERATION 1> + // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1. + // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2. + // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2. + // * Line 4 goes to column 2 after Line 3. + // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4} + // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({5, 17, 17}) = 5. + // * Tentative column height is set to 5 + 5 = 10. + // <ITERATION 2> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION + // * Minimal height increase that allows at least one column to accommodate one more line is + // min({12, 12, 10}) = 10. + // * Tentative column height is set to 10 + 10 == 20. + // <ITERATION 3> + // * Line 1 goes to column 0, no overflow. + // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1. + // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2. + // * Line 4 is attempted to go to column 2 after Line 3; no overflow. + // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} + // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END. + do + { + nTentativeColHeight += nWantedIncrease; + nWantedIncrease = std::numeric_limits<tools::Long>::max(); + nCurrentTextHeight = 0; + if (pHeightNTP) + *pHeightNTP = 0; + auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)]( + const LineAreaInfo& rInfo) mutable { + if (rInfo.pLine) + { + if (lastCol != rInfo.nColumn) + { + minHeight = std::max(nCurrentTextHeight, + minHeight); // total height can't be less than previous columns + nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease); + lastCol = rInfo.nColumn; + } + // bottom coordinate does not belong to area, so no need to do +1 + nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight); + if (pHeightNTP) + { + if (rInfo.rPortion.IsEmpty()) + *pHeightNTP = std::max(*pHeightNTP, minHeight); + else + *pHeightNTP = nCurrentTextHeight; + } + } + return CallbackResult::Continue; + }; + comphelper::ValueRestorationGuard aGuard(nCurTextHeight, nTentativeColHeight); + IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none); + } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0 + && nWantedIncrease != std::numeric_limits<tools::Long>::max()); + return nCurrentTextHeight; +} + +sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" ); + if ( pPPortion ) + return pPPortion->GetLines().Count(); + + return -1; +} + +sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" ); + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + return rLine.GetLen(); + } + + return -1; +} + +void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" ); + rStart = rEnd = -1; // default values in case of error + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + rStart = rLine.GetStart(); + rEnd = rLine.GetEnd(); + } +} + +sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + sal_Int32 nLineNo = -1; + const ContentNode* pNode = GetEditDoc().GetObject( nPara ); + OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" ); + if (pNode) + { + // we explicitly allow for the index to point at the character right behind the text + const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len(); + OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" ); + const sal_Int32 nLineCount = GetLineCount( nPara ); + if (nIndex == pNode->Len()) + nLineNo = nLineCount > 0 ? nLineCount - 1 : 0; + else if (bValidIndex) // nIndex < pNode->Len() + { + sal_Int32 nStart = -1, nEnd = -1; + for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i) + { + GetLineBoundaries( nStart, nEnd, nPara, i ); + if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd) + nLineNo = i; + } + } + } + return nLineNo; +} + +sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine ) +{ + OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" ); + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" ); + if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) ) + { + const EditLine& rLine = pPPortion->GetLines()[nLine]; + return rLine.GetHeight(); + } + + return 0xFFFF; +} + +sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph ) +{ + sal_uInt32 nHeight = 0; + + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" ); + + if ( pPPortion ) + nHeight = pPPortion->GetHeight(); + + return nHeight; +} + +void ImpEditEngine::UpdateSelections() +{ + // Check whether one of the selections is at a deleted node... + // If the node is valid, the index has yet to be examined! + for (EditView* pView : aEditViews) + { + EditSelection aCurSel( pView->pImpEditView->GetEditSelection() ); + bool bChanged = false; + for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes) + { + const DeletedNodeInfo& rInf = *aDeletedNode; + if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) || + ( aCurSel.Max().GetNode() == rInf.GetNode() ) ) + { + // Use ParaPortions, as now also hidden paragraphs have to be + // taken into account! + sal_Int32 nPara = rInf.GetPosition(); + if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph + { + nPara = GetParaPortions().Count()-1; + } + assert(GetParaPortions()[nPara] && "Empty Document in UpdateSelections ?"); + // Do not end up from a hidden paragraph: + sal_Int32 nCurPara = nPara; + sal_Int32 nLastPara = GetParaPortions().Count()-1; + while ( nPara <= nLastPara && !GetParaPortions()[nPara]->IsVisible() ) + nPara++; + if ( nPara > nLastPara ) // then also backwards ... + { + nPara = nCurPara; + while ( nPara && !GetParaPortions()[nPara]->IsVisible() ) + nPara--; + } + OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" ); + + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) ); + pView->pImpEditView->SetEditSelection( aTmpSelection ); + bChanged=true; + break; // for loop + } + } + if ( !bChanged ) + { + // Check Index if node shrunk. + if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() ) + { + aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() ); + pView->pImpEditView->SetEditSelection( aCurSel ); + } + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + pView->pImpEditView->SetEditSelection( aCurSel ); + } + } + } + aDeletedNodes.clear(); +} + +EditSelection ImpEditEngine::ConvertSelection( + sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos ) +{ + EditSelection aNewSelection; + + // Start... + ContentNode* pNode = maEditDoc.GetObject( nStartPara ); + sal_Int32 nIndex = nStartPos; + if ( !pNode ) + { + pNode = maEditDoc[ maEditDoc.Count()-1 ]; + nIndex = pNode->Len(); + } + else if ( nIndex > pNode->Len() ) + nIndex = pNode->Len(); + + aNewSelection.Min().SetNode( pNode ); + aNewSelection.Min().SetIndex( nIndex ); + + // End... + pNode = maEditDoc.GetObject( nEndPara ); + nIndex = nEndPos; + if ( !pNode ) + { + pNode = maEditDoc[ maEditDoc.Count()-1 ]; + nIndex = pNode->Len(); + } + else if ( nIndex > pNode->Len() ) + nIndex = pNode->Len(); + + aNewSelection.Max().SetNode( pNode ); + aNewSelection.Max().SetIndex( nIndex ); + + return aNewSelection; +} + +void ImpEditEngine::SetActiveView( EditView* pView ) +{ + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Actually, now bHasVisSel and HideSelection would be necessary !!! + + if ( pView == pActiveView ) + return; + + if ( pActiveView && pActiveView->HasSelection() ) + pActiveView->pImpEditView->DrawSelectionXOR(); + + pActiveView = pView; + + if ( pActiveView && pActiveView->HasSelection() ) + pActiveView->pImpEditView->DrawSelectionXOR(); + + // NN: Quick fix for #78668#: + // When editing of a cell in Calc is ended, the edit engine is not deleted, + // only the edit views are removed. If mpIMEInfos is still set in that case, + // mpIMEInfos->aPos points to an invalid selection. + // -> reset mpIMEInfos now + // (probably something like this is necessary whenever the content is modified + // from the outside) + + if ( !pView && mpIMEInfos ) + { + mpIMEInfos.reset(); + } +} + +uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection ) +{ + EditSelection aSelection( rSelection ); + aSelection.Adjust( GetEditDoc() ); + + rtl::Reference<EditDataObject> pDataObj = new EditDataObject; + + pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific + + WriteRTF( pDataObj->GetRTFStream(), aSelection ); + pDataObj->GetRTFStream().Seek( 0 ); + + WriteXML( pDataObj->GetODFStream(), aSelection ); + pDataObj->GetODFStream().Seek( 0 ); + + //Dumping the ODFStream to a XML file for testing purpose + /* + std::filebuf afilebuf; + afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out); + std::ostream os(&afilebuf); + os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize()); + afilebuf.close(); + */ + //dumping ends + + if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() ) + && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) ) + { + const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs(). + FindFeature( aSelection.Min().GetIndex() ); + if ( pAttr && + ( pAttr->GetStart() == aSelection.Min().GetIndex() ) && + ( pAttr->Which() == EE_FEATURE_FIELD ) ) + { + const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem()); + const SvxFieldData* pFld = pField->GetField(); + if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) ) + { + // Office-Bookmark + pDataObj->GetURL() = pUrlField->GetURL(); + } + } + } + + return pDataObj; +} + +EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format) +{ + EditSelection aNewSelection( rPaM ); + + if ( !rxDataObj.is() ) + return aNewSelection; + + datatransfer::DataFlavor aFlavor; + bool bDone = false; + + if ( bUseSpecial ) + { + // XML + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor ); + if ( rxDataObj->isDataFlavorSupported( aFlavor ) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT == format)) + { + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); + aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, rPaM ); + } + bDone = true; + } + catch( const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" ); + } + } + + if ( !bDone ) + { + // RTF + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor ); + // RICHTEXT + datatransfer::DataFlavor aFlavorRichtext; + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext ); + bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor ); + bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext ); + if ( (bRtfSupported || bRichtextSupported) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::RICHTEXT == format || SotClipboardFormatId::RTF == format)) + { + if(bRichtextSupported) + { + aFlavor = aFlavorRichtext; + } + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ ); + aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, rPaM ); + } + bDone = true; + } + catch( const css::uno::Exception& ) + { + } + } + } + if (!bDone) { + // HTML + SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML_SIMPLE, aFlavor); + bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor); + if (bHtmlSupported && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::HTML_SIMPLE == format)) { + MSE40HTMLClipFormatObj aMSE40HTMLClipFormatObj; + try + { + uno::Any aData = rxDataObj->getTransferData(aFlavor); + uno::Sequence< sal_Int8 > aSeq; + aData >>= aSeq; + { + SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ); + SvStream* pHtmlStream = aMSE40HTMLClipFormatObj.IsValid(aHtmlStream); + if (pHtmlStream != nullptr) { + aNewSelection = Read(*pHtmlStream, rBaseURL, EETextFormat::Html, rPaM); + } + } + bDone = true; + } + catch (const css::uno::Exception&) + { + } + } + } + } + if ( !bDone ) + { + SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); + if ( rxDataObj->isDataFlavorSupported( aFlavor ) ) + { + try + { + uno::Any aData = rxDataObj->getTransferData( aFlavor ); + OUString aText; + aData >>= aText; + aNewSelection = ImpInsertText( rPaM, aText ); + } + catch( ... ) + { + ; // #i9286# can happen, even if isDataFlavorSupported returns true... + } + } + } + + return aNewSelection; +} + +sal_Int32 ImpEditEngine::GetChar( + const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart) +{ + assert(pLine); + + sal_Int32 nChar = -1; + sal_Int32 nCurIndex = pLine->GetStart(); + + + // Search best matching portion with GetPortionXOffset() + for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ ) + { + const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; + tools::Long nXLeft = GetPortionXOffset( pParaPortion, pLine, i ); + tools::Long nXRight = nXLeft + rPortion.GetSize().Width(); + if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) ) + { + nChar = nCurIndex; + + // Search within Portion... + + // Don't search within special portions... + if ( rPortion.GetKind() != PortionKind::TEXT ) + { + // ...but check on which side + if ( bSmart ) + { + tools::Long nLeftDiff = nXPos-nXLeft; + tools::Long nRightDiff = nXRight-nXPos; + if ( nRightDiff < nLeftDiff ) + nChar++; + } + } + else + { + sal_Int32 nMax = rPortion.GetLen(); + sal_Int32 nOffset = -1; + sal_Int32 nTmpCurIndex = nChar - pLine->GetStart(); + + tools::Long nXInPortion = nXPos - nXLeft; + if ( rPortion.IsRightToLeft() ) + nXInPortion = nXRight - nXPos; + + // Search in Array... + for ( sal_Int32 x = 0; x < nMax; x++ ) + { + tools::Long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x]; + if ( nTmpPosMax > nXInPortion ) + { + // Check whether this or the previous... + tools::Long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0; + tools::Long nDiffLeft = nXInPortion - nTmpPosMin; + tools::Long nDiffRight = nTmpPosMax - nXInPortion; + OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" ); + OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" ); + + if (bSmart && nDiffRight < nDiffLeft) + { + // I18N: If there are character position with the length of 0, + // they belong to the same character, we can not use this position as an index. + // Skip all 0-positions, cheaper than using XBreakIterator: + tools::Long nX = pLine->GetCharPosArray()[nTmpCurIndex + x]; + while(x < nMax && pLine->GetCharPosArray()[nTmpCurIndex + x] == nX) + ++x; + } + nOffset = x; + break; + } + } + + // There should not be any inaccuracies when using the + // CharPosArray! Maybe for kerning? + // 0xFFF happens for example for Outline-Font when at the very end. + if ( nOffset < 0 ) + nOffset = nMax; + + OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" ); + + nChar = nChar + nOffset; + + // Check if index is within a cell: + if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) ) + { + EditPaM aPaM( pParaPortion->GetNode(), nChar+1 ); + sal_uInt16 nScriptType = GetI18NScriptType( aPaM ); + if ( nScriptType == i18n::ScriptType::COMPLEX ) + { + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + sal_Int32 nCount = 1; + lang::Locale aLocale = GetLocale( aPaM ); + sal_Int32 nRight = _xBI->nextCharacters( + pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + sal_Int32 nLeft = _xBI->previousCharacters( + pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); + if ( ( nLeft != nChar ) && ( nRight != nChar ) ) + { + nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft; + } + } + else + { + OUString aStr(pParaPortion->GetNode()->GetString()); + // tdf#102625: don't select middle of a pair of surrogates with mouse cursor + if (rtl::isSurrogate(aStr[nChar])) + --nChar; + } + } + } + } + + nCurIndex = nCurIndex + rPortion.GetLen(); + } + + if ( nChar == -1 ) + { + nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd(); + } + + return nChar; +} + +Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const +{ + Range aLineXPosStartEnd; + + sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + if ( !IsRightToLeft( nPara ) ) + { + aLineXPosStartEnd.Min() = pLine->GetStartPosX(); + aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth(); + } + else + { + aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() ); + aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX(); + } + + + return aLineXPosStartEnd; +} + +tools::Long ImpEditEngine::GetPortionXOffset( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const +{ + tools::Long nX = pLine->GetStartPosX(); + + for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ ) + { + const TextPortion& rPortion = pParaPortion->GetTextPortions()[i]; + switch ( rPortion.GetKind() ) + { + case PortionKind::FIELD: + case PortionKind::TEXT: + case PortionKind::HYPHENATOR: + case PortionKind::TAB: + { + nX += rPortion.GetSize().Width(); + } + break; + case PortionKind::LINEBREAK: break; + } + } + + sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() ); + bool bR2LPara = IsRightToLeft( nPara ); + + const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion]; + if ( rDestPortion.GetKind() != PortionKind::TAB ) + { + if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() ) + { + // Portions behind must be added, visual before this portion + sal_Int32 nTmpPortion = nTextPortion+1; + while ( nTmpPortion <= pLine->GetEndPortion() ) + { + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) + nX += rNextTextPortion.GetSize().Width(); + else + break; + nTmpPortion++; + } + // Portions before must be removed, visual behind this portion + nTmpPortion = nTextPortion; + while ( nTmpPortion > pLine->GetStartPortion() ) + { + --nTmpPortion; + const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) + nX -= rPrevTextPortion.GetSize().Width(); + else + break; + } + } + else if ( bR2LPara && !rDestPortion.IsRightToLeft() ) + { + // Portions behind must be removed, visual behind this portion + sal_Int32 nTmpPortion = nTextPortion+1; + while ( nTmpPortion <= pLine->GetEndPortion() ) + { + const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) ) + nX += rNextTextPortion.GetSize().Width(); + else + break; + nTmpPortion++; + } + // Portions before must be added, visual before this portion + nTmpPortion = nTextPortion; + while ( nTmpPortion > pLine->GetStartPortion() ) + { + --nTmpPortion; + const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) ) + nX -= rPrevTextPortion.GetSize().Width(); + else + break; + } + } + } + if ( bR2LPara ) + { + // Switch X positions... + OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" ); + OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" ); + nX = GetPaperSize().Width() - nX; + nX -= rDestPortion.GetSize().Width(); + } + + return nX; +} + +tools::Long ImpEditEngine::GetXPos( + const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const +{ + assert(pLine); + OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" ); + + bool bDoPreferPortionStart = bPreferPortionStart; + // Assure that the portion belongs to this line: + if ( nIndex == pLine->GetStart() ) + bDoPreferPortionStart = true; + else if ( nIndex == pLine->GetEnd() ) + bDoPreferPortionStart = false; + + sal_Int32 nTextPortionStart = 0; + sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart ); + + OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " ); + + const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion]; + + tools::Long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion ); + + // calc text width, portion size may include CJK/CTL spacing... + // But the array might not be init yet, if using text ranger this method is called within CreateLines()... + tools::Long nPortionTextWidth = rPortion.GetSize().Width(); + if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() ) + nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()]; + + if ( nTextPortionStart != nIndex ) + { + // Search within portion... + if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) ) + { + // End of Portion + if ( rPortion.GetKind() == PortionKind::TAB ) + { + if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() ) + { + const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1]; + if ( rNextPortion.GetKind() != PortionKind::TAB ) + { + if ( !bPreferPortionStart ) + nX = GetXPos( pParaPortion, pLine, nIndex, true ); + else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + nX += nPortionTextWidth; + } + } + else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) ) + { + nX += nPortionTextWidth; + } + } + else if ( !rPortion.IsRightToLeft() ) + { + nX += nPortionTextWidth; + } + } + else if ( rPortion.GetKind() == PortionKind::TEXT ) + { + OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" ); + OSL_ENSURE( !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" ); + + if( !pLine->GetCharPosArray().empty() ) + { + sal_Int32 nPos = nIndex - 1 - pLine->GetStart(); + if (nPos < 0 || o3tl::make_unsigned(nPos) >= pLine->GetCharPosArray().size()) + { + nPos = pLine->GetCharPosArray().size()-1; + OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!"); + } + + // old code restored see #i112788 (which leaves #i74188 unfixed again) + tools::Long nPosInPortion = pLine->GetCharPosArray()[nPos]; + + if ( !rPortion.IsRightToLeft() ) + { + nX += nPosInPortion; + } + else + { + nX += nPortionTextWidth - nPosInPortion; + } + + if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed ) + { + nX += rPortion.GetExtraInfos()->nPortionOffsetX; + if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight ) + { + AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) ); + if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() ) + { + sal_Int32 n = nIndex - nTextPortionStart; + const sal_Int32* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() ); + sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() ) + - ( n ? pDXArray[n-1] : 0 ); + if ( (n+1) < rPortion.GetLen() ) + { + // smaller, when char behind is AsianCompressionFlags::PunctuationRight also + nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) ); + if ( nType == AsianCompressionFlags::PunctuationRight ) + { + sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() ) + - pDXArray[n]; + sal_Int32 nCompressed = nNextCharWidth/2; + nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent; + nCompressed /= 10000; + nCharWidth += nCompressed; + } + } + else + { + nCharWidth *= 2; // last char pos to portion end is only compressed size + } + nX += nCharWidth/2; // 50% compression + } + } + } + } + } + } + else // if ( nIndex == pLine->GetStart() ) + { + if ( rPortion.IsRightToLeft() ) + { + nX += nPortionTextWidth; + } + } + + return nX; +} + +void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) +{ + pPortion->nHeight = 0; + pPortion->nFirstLineOffset = 0; + + if ( !pPortion->IsVisible() ) + return; + + OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" ); + for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine) + pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight(); + + if (maStatus.IsOutliner()) + return; + + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; + + if ( nSBL ) + { + if ( pPortion->GetLines().Count() > 1 ) + pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL; + if (maStatus.ULSpaceSummation()) + pPortion->nHeight += nSBL; + } + + sal_Int32 nPortion = GetParaPortions().GetPos( pPortion ); + if ( nPortion ) + { + sal_uInt16 nUpper = scaleYSpacingValue(rULItem.GetUpper()); + pPortion->nHeight += nUpper; + pPortion->nFirstLineOffset = nUpper; + } + + if ( nPortion != (GetParaPortions().Count()-1) ) + { + pPortion->nHeight += scaleYSpacingValue(rULItem.GetLower()); // not in the last + } + + + if ( !nPortion || maStatus.ULSpaceSummation() ) + return; + + ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 ); + if (!pPrev) + return; + + const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + + // In relation between WinWord6/Writer3: + // With a proportional line spacing the paragraph spacing is + // also manipulated. + // Only Writer3: Do not add up, but minimum distance. + + // check if distance by LineSpacing > Upper: + sal_uInt16 nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rLSItem)); + if ( nExtraSpace > pPortion->nFirstLineOffset ) + { + // Paragraph becomes 'bigger': + pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset ); + pPortion->nFirstLineOffset = nExtraSpace; + } + + // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev): + sal_uInt16 nPrevLower = scaleYSpacingValue(rPrevULItem.GetLower()); + + // This PrevLower is still in the height of PrevPortion ... + if ( nPrevLower > pPortion->nFirstLineOffset ) + { + // Paragraph is 'small': + pPortion->nHeight -= pPortion->nFirstLineOffset; + pPortion->nFirstLineOffset = 0; + } + else if ( nPrevLower ) + { + // Paragraph becomes 'somewhat smaller': + pPortion->nHeight -= nPrevLower; + pPortion->nFirstLineOffset = + pPortion->nFirstLineOffset - nPrevLower; + } + // I find it not so good, but Writer3 feature: + // Check if distance by LineSpacing > Lower: this value is not + // stuck in the height of PrevPortion. + if ( pPrev->IsInvalid() ) + return; + + nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rPrevLSItem)); + if ( nExtraSpace > nPrevLower ) + { + sal_uInt16 nMoreLower = nExtraSpace - nPrevLower; + // Paragraph becomes 'bigger', 'grows' downwards: + if ( nMoreLower > pPortion->nFirstLineOffset ) + { + pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset ); + pPortion->nFirstLineOffset = nMoreLower; + } + } +} + +void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) +{ + maPaperSize = rNewSz; + + tools::Long nMinWidth = maStatus.AutoPageWidth() ? maMinAutoPaperSize.Width() : 0; + tools::Long nMaxWidth = maStatus.AutoPageWidth() ? maMaxAutoPaperSize.Width() : 0x7FFFFFFF; + tools::Long nMinHeight = maStatus.AutoPageHeight() ? maMinAutoPaperSize.Height() : 0; + tools::Long nMaxHeight = maStatus.AutoPageHeight() ? maMaxAutoPaperSize.Height() : 0x7FFFFFFF; + + // Minimum/Maximum width: + if ( maPaperSize.Width() < nMinWidth ) + maPaperSize.setWidth( nMinWidth ); + else if ( maPaperSize.Width() > nMaxWidth ) + maPaperSize.setWidth( nMaxWidth ); + + // Minimum/Maximum height: + if ( maPaperSize.Height() < nMinHeight ) + maPaperSize.setHeight( nMinHeight ); + else if ( maPaperSize.Height() > nMaxHeight ) + maPaperSize.setHeight( nMaxHeight ); +} + +std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable() +{ + return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable(); +} + +void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars ); +} + +bool ImpEditEngine::IsVisualCursorTravelingEnabled() +{ + bool bVisualCursorTravaling = false; + + if ( SvtCTLOptions::IsCTLFontEnabled() && ( SvtCTLOptions::GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) ) + { + bVisualCursorTravaling = true; + } + + return bVisualCursorTravaling; + +} + +bool ImpEditEngine::DoVisualCursorTraveling() +{ + // Don't check if it's necessary, because we also need it when leaving the paragraph + return IsVisualCursorTravelingEnabled(); +} + +IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void) +{ + aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx new file mode 100644 index 0000000000..b24cc00401 --- /dev/null +++ b/editeng/source/editeng/impedit3.cxx @@ -0,0 +1,4910 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> + +#include <editeng/outliner.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/txtrange.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/numitem.hxx> +#include <outleeng.hxx> + +#include <svtools/colorcfg.hxx> +#include <svl/ctloptions.hxx> +#include <svl/asiancfg.hxx> + +#include <svx/compatflags.hxx> +#include <sfx2/viewsh.hxx> + +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddencharacterstable.hxx> + +#include <unotools/configmgr.hxx> + +#include <math.h> +#include <vcl/metric.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/InputSequenceChecker.hpp> +#include <vcl/pdfextoutdevdata.hxx> +#include <i18nlangtag/mslangid.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/lok.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/sorted_vector.hxx> +#include <osl/diagnose.h> +#include <comphelper/string.hxx> +#include <cstddef> +#include <memory> +#include <set> + +#include <vcl/outdev/ScopedStates.hxx> + +#include <unicode/uchar.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + +constexpr OUString CH_HYPH = u"-"_ustr; + +constexpr tools::Long WRONG_SHOW_MIN = 5; + +namespace { + +struct TabInfo +{ + bool bValid; + + SvxTabStop aTabStop; + sal_Int32 nTabPortion; + tools::Long nStartPosX; + tools::Long nTabPos; + + TabInfo() + : bValid(false) + , nTabPortion(0) + , nStartPosX(0) + , nTabPos(0) + { } + +}; + +} + +AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ) +{ + switch ( cChar ) + { + case 0x3008: case 0x300A: case 0x300C: case 0x300E: + case 0x3010: case 0x3014: case 0x3016: case 0x3018: + case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D: + case 0xFF5D: + { + return AsianCompressionFlags::PunctuationRight; + } + case 0x3001: case 0x3002: case 0x3009: case 0x300B: + case 0x300D: case 0x300F: case 0x3011: case 0x3015: + case 0x3017: case 0x3019: case 0x301B: case 0x301E: + case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E: + case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B: + { + return AsianCompressionFlags::PunctuationLeft; + } + default: + { + return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal; + } + } +} + +static void lcl_DrawRedLines( OutputDevice& rOutDev, + tools::Long nFontHeight, + const Point& rPoint, + size_t nIndex, + size_t nMaxEnd, + std::span<const sal_Int32> pDXArray, + WrongList const * pWrongs, + Degree10 nOrientation, + const Point& rOrigin, + bool bVertical, + bool bIsRightToLeft ) +{ + // But only if font is not too small... + tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height(); + if (WRONG_SHOW_MIN >= nHeight) + return; + + size_t nEnd, nStart = nIndex; + bool bWrong = pWrongs->NextWrong(nStart, nEnd); + + while (bWrong) + { + if (nStart >= nMaxEnd) + break; + + if (nStart < nIndex) // Corrected + nStart = nIndex; + + if (nEnd > nMaxEnd) + nEnd = nMaxEnd; + + Point aPoint1(rPoint); + if (bVertical) + { + // VCL doesn't know that the text is vertical, and is manipulating + // the positions a little bit in y direction... + tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height(); + tools::Long nCorrect = 2 * nOnePixel; + aPoint1.AdjustY(-nCorrect); + aPoint1.AdjustX(-nCorrect); + } + if (nStart > nIndex) + { + if (!bVertical) + { + // since for RTL portions rPoint is on the visual right end of the portion + // (i.e. at the start of the first RTL char) we need to subtract the offset + // for RTL portions... + aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]); + } + else + aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]); + } + Point aPoint2(rPoint); + assert(nEnd > nIndex && "RedLine: aPnt2?"); + if (!bVertical) + { + // since for RTL portions rPoint is on the visual right end of the portion + // (i.e. at the start of the first RTL char) we need to subtract the offset + // for RTL portions... + aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]); + } + else + { + aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]); + } + + if (nOrientation) + { + rOrigin.RotateAround(aPoint1, nOrientation); + rOrigin.RotateAround(aPoint2, nOrientation); + } + + { + vcl::ScopedAntialiasing a(rOutDev, true); + rOutDev.DrawWaveLine(aPoint1, aPoint2); + } + + nStart = nEnd + 1; + if (nEnd < nMaxEnd) + bWrong = pWrongs->NextWrong(nStart, nEnd); + else + bWrong = false; + } +} + +// For Kashidas from sw/source/core/text/porlay.cxx + +#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g ) +#define isAinChar(c) IS_JOINING_GROUP((c), AIN) +#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF) +#define isDalChar(c) IS_JOINING_GROUP((c), DAL) +#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH)) +#define isGafChar(c) IS_JOINING_GROUP((c), GAF) +#define isHehChar(c) IS_JOINING_GROUP((c), HEH) +#define isKafChar(c) IS_JOINING_GROUP((c), KAF) +#define isLamChar(c) IS_JOINING_GROUP((c), LAM) +#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF)) +#define isRehChar(c) IS_JOINING_GROUP((c), REH) +#define isTahChar(c) IS_JOINING_GROUP((c), TAH) +#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA) +#define isWawChar(c) IS_JOINING_GROUP((c), WAW) +#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN)) + +// Beh and characters that behave like Beh in medial form. +static bool isBehChar(sal_Unicode cCh) +{ + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_BEH: + case U_JG_NOON: + case U_JG_AFRICAN_NOON: + case U_JG_NYA: + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_BURUSHASKI_YEH_BARREE: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; +} + +// Yeh and characters that behave like Yeh in final form. +static bool isYehChar(sal_Unicode cCh) +{ + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_YEH_BARREE: + case U_JG_BURUSHASKI_YEH_BARREE: + case U_JG_YEH_WITH_TAIL: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; +} + +static bool isTransparentChar ( sal_Unicode cCh ) +{ + return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT; +} + +static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) +{ + // Lam + Alef + return ( isLamChar ( cCh ) && isAlefChar ( cNextCh )); +} + +static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) +{ + const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE ); + bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING; + + // check for ligatures cPrevChar + cChar + if ( bRet ) + bRet = ! lcl_IsLigature( cPrevCh, cCh ); + + return bRet; +} + + + +void ImpEditEngine::UpdateViews( EditView* pCurView ) +{ + if ( !IsUpdateLayout() || IsFormatting() || aInvalidRect.IsEmpty() ) + return; + + DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" ); + + for (EditView* pView : aEditViews) + { + pView->HideCursor(); + + tools::Rectangle aClipRect( aInvalidRect ); + tools::Rectangle aVisArea( pView->GetVisArea() ); + aClipRect.Intersection( aVisArea ); + + if ( !aClipRect.IsEmpty() ) + { + // convert to window coordinates... + aClipRect = pView->pImpEditView->GetWindowPos( aClipRect ); + + // moved to one executing method to allow finer control + pView->InvalidateWindow(aClipRect); + + pView->InvalidateOtherViewWindows( aClipRect ); + } + } + + if ( pCurView ) + { + bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll(); + pCurView->ShowCursor( bGotoCursor ); + } + + aInvalidRect = tools::Rectangle(); + CallStatusHdl(); +} + +IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void) +{ + if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() ) + DoOnlineSpelling(); + else + aOnlineSpellTimer.Start(); +} + +IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void) +{ + aIdleFormatter.ResetRestarts(); + + // #i97146# check if that view is still available + // else probably the idle format timer fired while we're already + // downing + EditView* pView = aIdleFormatter.GetView(); + for (EditView* aEditView : aEditViews) + { + if( aEditView == pView ) + { + FormatAndLayout( pView ); + break; + } + } +} + +void ImpEditEngine::CheckIdleFormatter() +{ + aIdleFormatter.ForceTimeout(); + // If not idle, but still not formatted: + if ( !IsFormatted() ) + FormatDoc(); +} + +bool ImpEditEngine::IsPageOverflow( ) const +{ + return mbNeedsChainingHandling; +} + + +void ImpEditEngine::FormatFullDoc() +{ + for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) + GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 ); + FormatDoc(); +} + +void ImpEditEngine::FormatDoc() +{ + if (!IsUpdateLayout() || IsFormatting()) + return; + + mbIsFormatting = true; + + // Then I can also start the spell-timer... + if ( GetStatus().DoOnlineSpelling() ) + StartOnlineSpellTimer(); + + tools::Long nY = 0; + bool bGrow = false; + + // Here already, so that not always in CreateLines... + bool bMapChanged = ImpCheckRefMapMode(); + sal_Int32 nParaCount = GetParaPortions().Count(); + o3tl::sorted_vector<sal_Int32> aRepaintParas; + aRepaintParas.reserve(nParaCount); + + for ( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) ) + { + // No formatting should be necessary for MustRepaint()! + if ( !pParaPortion->IsInvalid() || CreateLines( nPara, nY ) ) + { + if ( !bGrow && GetTextRanger() ) + { + // For a change in height all below must be reformatted... + for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ ) + { + ParaPortion* pPP = GetParaPortions()[n]; + pPP->MarkSelectionInvalid( 0 ); + pPP->GetLines().Reset(); + } + } + bGrow = true; + if ( IsCallParaInsertedOrDeleted() ) + { + GetEditEnginePtr()->ParagraphHeightChanged( nPara ); + + for (EditView* pView : aEditViews) + { + ImpEditView* pImpView = pView->pImpEditView.get(); + pImpView->ScrollStateChange(); + } + + } + pParaPortion->SetMustRepaint( false ); + } + + aRepaintParas.insert(nPara); + } + nY += pParaPortion->GetHeight(); + } + + aInvalidRect = tools::Rectangle(); // make empty + + // One can also get into the formatting through UpdateMode ON=>OFF=>ON... + // enable optimization first after Vobis delivery... + { + tools::Long nNewHeightNTP; + tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP); + tools::Long nDiff = nNewHeight - nCurTextHeight; + if ( nDiff ) + { + aInvalidRect.Union(tools::Rectangle::Normalize( + { 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), nCurTextHeight })); + maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; + } + + nCurTextHeight = nNewHeight; + nCurTextHeightNTP = nNewHeightNTP; + + if ( maStatus.AutoPageSize() ) + CheckAutoPageSize(); + else if ( nDiff ) + { + for (EditView* pView : aEditViews) + { + ImpEditView* pImpView = pView->pImpEditView.get(); + if ( pImpView->DoAutoHeight() ) + { + Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight ); + if ( aSz.Height() > maMaxAutoPaperSize.Height() ) + aSz.setHeight( maMaxAutoPaperSize.Height() ); + else if ( aSz.Height() < maMinAutoPaperSize.Height() ) + aSz.setHeight( maMinAutoPaperSize.Height() ); + pImpView->ResetOutputArea( tools::Rectangle( + pImpView->GetOutputArea().TopLeft(), aSz ) ); + } + } + } + + if (!aRepaintParas.empty()) + { + auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) { + if (aRepaintParas.count(rInfo.nPortion)) + aInvalidRect.Union(rInfo.aArea); + return CallbackResult::Continue; + }; + IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS); + } + } + + mbIsFormatting = false; + mbFormatted = true; + + if ( bMapChanged ) + GetRefDevice()->Pop(); + + CallStatusHdl(); // If Modified... +} + +bool ImpEditEngine::ImpCheckRefMapMode() +{ + bool bChange = false; + + if ( maStatus.DoFormat100() ) + { + MapMode aMapMode( GetRefDevice()->GetMapMode() ); + if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() ) + bChange = true; + else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() ) + bChange = true; + + if ( bChange ) + { + Fraction Scale1( 1, 1 ); + aMapMode.SetScaleX( Scale1 ); + aMapMode.SetScaleY( Scale1 ); + GetRefDevice()->Push(); + GetRefDevice()->SetMapMode( aMapMode ); + } + } + + return bChange; +} + +void ImpEditEngine::CheckAutoPageSize() +{ + Size aPrevPaperSize( GetPaperSize() ); + if ( GetStatus().AutoPageWidth() ) + maPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() ); + if ( GetStatus().AutoPageHeight() ) + maPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) ); + + SetValidPaperSize( maPaperSize ); // consider Min, Max + + if ( maPaperSize == aPrevPaperSize ) + return; + + if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) ) + || ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) ) + { + // If ahead is centered / right or tabs... + maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged; + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + { + // Only paragraphs which are not aligned to the left need to be + // reformatted, the height can not be changed here anymore. + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + SvxAdjust eJustification = GetJustification( nPara ); + if ( eJustification != SvxAdjust::Left ) + { + pParaPortion->MarkSelectionInvalid( 0 ); + CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange! + } + } + } + + Size aInvSize = maPaperSize; + if ( maPaperSize.Width() < aPrevPaperSize.Width() ) + aInvSize.setWidth( aPrevPaperSize.Width() ); + if ( maPaperSize.Height() < aPrevPaperSize.Height() ) + aInvSize.setHeight( aPrevPaperSize.Height() ); + + Size aSz( aInvSize ); + if ( IsEffectivelyVertical() ) + { + aSz.setWidth( aInvSize.Height() ); + aSz.setHeight( aInvSize.Width() ); + } + aInvalidRect = tools::Rectangle( Point(), aSz ); + + + for (EditView* pView : aEditViews) + { + pView->pImpEditView->RecalcOutputArea(); + } +} + +void ImpEditEngine::CheckPageOverflow() +{ + SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( maStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); + + tools::Long nBoxHeight = GetMaxAutoPaperSize().Height(); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight); + + tools::Long nTxtHeight = CalcTextHeight(nullptr); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight); + + sal_uInt32 nParaCount = GetParaPortions().Count(); + sal_uInt32 nFirstLineCount = GetLineCount(0); + bool bOnlyOneEmptyPara = (nParaCount == 1) && + (nFirstLineCount == 1) && + (GetLineLen(0,0) == 0); + + if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara) + { + // which paragraph is the first to cause higher size of the box? + ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text + //maStatus.SetPageOverflow(true); + mbNeedsChainingHandling = true; + } else + { + // No overflow if within box boundaries + //maStatus.SetPageOverflow(false); + mbNeedsChainingHandling = false; + } + +} + +static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight ) +{ + constexpr const double f120Percent = 12.0 / 10.0; + return basegfx::fround(nFontHeight * f120Percent); // + 20% +} + +tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const +{ + assert(mnColumns >= 1); + tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width(); + return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns; +} + +bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) +{ + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + + // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False + assert( pParaPortion->GetNode() && "Portion without Node in CreateLines" ); + DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" ); + DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" ); + + bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 ); + bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger(); + + + // Fast special treatment for empty paragraphs... + + if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() ) + { + // fast special treatment... + if ( pParaPortion->GetTextPortions().Count() ) + pParaPortion->GetTextPortions().Reset(); + if ( pParaPortion->GetLines().Count() ) + pParaPortion->GetLines().Reset(); + CreateAndInsertEmptyLine( pParaPortion ); + return FinishCreateLines( pParaPortion ); + } + + sal_Int64 nCurrentPosY = nStartPosY; + // If we're allowed to skip parts outside and this cannot possibly fit in the given height, + // bail out to avoid possibly formatting a lot of text that will not be used. For the first + // paragraph still format at least a bit. + if( mbSkipOutsideFormat && nPara != 0 + && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY ) + { + return false; + } + + // Initialization... + + // Always format for 100%: + bool bMapChanged = ImpCheckRefMapMode(); + + if ( pParaPortion->GetLines().Count() == 0 ) + { + EditLine* pL = new EditLine; + pParaPortion->GetLines().Append(pL); + } + + + // Get Paragraph attributes... + + ContentNode* const pNode = pParaPortion->GetNode(); + + bool bRightToLeftPara = IsRightToLeft( nPara ); + + SvxAdjust eJustification = GetJustification( nPara ); + bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue(); + sal_Int32 nSpaceBefore = 0; + sal_Int32 nMinLabelWidth = 0; + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth ); + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode ); + const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL ); + const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue(); + + const short nInvalidDiff = pParaPortion->GetInvalidDiff(); + const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart(); + const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff ); + + bool bQuickFormat = false; + if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) ) + { + if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) && + ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) ) + { + bQuickFormat = true; + } + else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) ) + { + // check if delete over the portion boundaries was done... + sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!! + sal_Int32 nEnd = nStart - nInvalidDiff; // negative + bQuickFormat = true; + sal_Int32 nPos = 0; + sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); + for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ ) + { + // There must be no start / end in the deleted area. + const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; + nPos = nPos + rTP.GetLen(); + if ( ( nPos > nStart ) && ( nPos < nEnd ) ) + { + bQuickFormat = false; + break; + } + } + } + } + + // Saving both layout mode and language (since I'm potentially changing both) + GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + + ImplInitLayoutMode(*GetRefDevice(), nPara, -1); + + sal_Int32 nRealInvalidStart = nInvalidStart; + + if ( bEmptyNodeWithPolygon ) + { + TextPortion* pDummyPortion = new TextPortion( 0 ); + pParaPortion->GetTextPortions().Reset(); + pParaPortion->GetTextPortions().Append(pDummyPortion); + } + else if ( bQuickFormat ) + { + // faster Method: + RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff ); + } + else // nRealInvalidStart can be before InvalidStart, since Portions were deleted... + { + CreateTextPortions( pParaPortion, nRealInvalidStart ); + } + + + // Search for line with InvalidPos, start one line before + // Flag the line => do not remove it ! + + + sal_Int32 nLine = pParaPortion->GetLines().Count()-1; + for ( sal_Int32 nL = 0; nL <= nLine; nL++ ) + { + EditLine& rLine = pParaPortion->GetLines()[nL]; + if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart! + { + nLine = nL; + break; + } + rLine.SetValid(); + } + // Begin one line before... + // If it is typed at the end, the line in front cannot change. + if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) ) + nLine--; + + EditLine* pLine = &pParaPortion->GetLines()[nLine]; + + static const tools::Rectangle aZeroArea { Point(), Point() }; + tools::Rectangle aBulletArea( aZeroArea ); + if ( !nLine ) + { + aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); + if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 ) + pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right()))); + else + pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly + } + + + // Reformat all lines from here... + + sal_Int32 nDelFromLine = -1; + bool bLineBreak = false; + + sal_Int32 nIndex = pLine->GetStart(); + EditLine aSaveLine( *pLine ); + SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() ); + + KernArray aCharPositionArray; + + bool bSameLineAgain = false; // For TextRanger, if the height changes. + TabInfo aCurrentTab; + + bool bForceOneRun = bEmptyNodeWithPolygon; + bool bCompressedChars = false; + + while ( ( nIndex < pNode->Len() ) || bForceOneRun ) + { + assert(pLine); + + bForceOneRun = false; + + bool bEOL = false; + bool bEOC = false; + sal_Int32 nPortionStart = 0; + sal_Int32 nPortionEnd = 0; + + tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth); + if ( nIndex == 0 ) + { + tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset()); + nStartX += nFI; + + if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) ) + { + nStartX = pParaPortion->GetBulletX(); + } + } + + const bool bAutoSize = IsEffectivelyVertical() ? maStatus.AutoPageHeight() : maStatus.AutoPageWidth(); + tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? maMaxAutoPaperSize : maPaperSize); + + nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight()); + nMaxLineWidth -= nStartX; + + // If PaperSize == long_max, one cannot take away any negative + // first line indent. (Overflow) + if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) ) + nMaxLineWidth = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()); + + // If still less than 0, it may be just the right edge. + if ( nMaxLineWidth <= 0 ) + nMaxLineWidth = 1; + + // Problem: + // Since formatting starts a line _before_ the invalid position, + // the positions unfortunately have to be redefined... + // Solution: + // The line before can only become longer, not smaller + // =>... + pLine->GetCharPosArray().clear(); + + sal_Int32 nTmpPos = nIndex; + sal_Int32 nTmpPortion = pLine->GetStartPortion(); + tools::Long nTmpWidth = 0; + tools::Long nXWidth = nMaxLineWidth; + + std::deque<tools::Long>* pTextRanges = nullptr; + tools::Long nTextExtraYOffset = 0; + tools::Long nTextXOffset = 0; + tools::Long nTextLineHeight = 0; + if ( GetTextRanger() ) + { + GetTextRanger()->SetVertical( IsEffectivelyVertical() ); + + tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top(); + if ( !bSameLineAgain ) + { + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + if ( IsFixedCellHeight() ) + nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ); + else + nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height(); + // Metrics can be greater + FormatterFontMetric aTempFormatterMetrics; + RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont ); + sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight(); + if ( nLineHeight > nTextLineHeight ) + nTextLineHeight = nLineHeight; + } + else + nTextLineHeight = pLine->GetHeight(); + + nXWidth = 0; + while ( !nXWidth ) + { + tools::Long nYOff = nTextY + nTextExtraYOffset; + tools::Long nYDiff = nTextLineHeight; + if ( IsEffectivelyVertical() ) + { + tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right(); + nYOff = nMaxPolygonX-nYOff; + nYDiff = -nTextLineHeight; + } + pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) ); + assert( pTextRanges && "GetTextRanges?!" ); + tools::Long nMaxRangeWidth = 0; + // Use the widest range... + // The widest range could be a bit confusing, so normally it + // is the first one. Best with gaps. + assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs"); + if (!pTextRanges->empty()) + { + tools::Long nA = pTextRanges->at(0); + tools::Long nB = pTextRanges->at(1); + DBG_ASSERT( nA <= nB, "TextRange distorted?" ); + tools::Long nW = nB - nA; + if ( nW > nMaxRangeWidth ) + { + nMaxRangeWidth = nW; + nTextXOffset = nA; + } + } + nXWidth = nMaxRangeWidth; + if ( nXWidth ) + nMaxLineWidth = nXWidth - nStartX - scaleXSpacingValue(rLRItem.GetRight()); + else + { + // Try further down in the polygon. + // Below the polygon use the Paper Width. + nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) ); + if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() ) + { + nXWidth = getWidthDirectionAware(GetPaperSize()); + if ( !nXWidth ) // AutoPaperSize + nXWidth = 0x7FFFFFFF; + } + } + } + } + + // search for Portion that no longer fits in line... + TextPortion* pPortion = nullptr; + sal_Int32 nPortionLen = 0; + bool bContinueLastPortion = false; + bool bBrokenLine = false; + bLineBreak = false; + const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() ); + while ( ( nTmpWidth < nXWidth ) && !bEOL ) + { + const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count(); + assert(nTextPortions > 0); + bContinueLastPortion = (nTmpPortion >= nTextPortions); + if (bContinueLastPortion) + { + if (nTmpPos >= pNode->Len()) + break; // while + + // Continue with remainder. This only to have *some* valid + // X-values and not endlessly create new lines until DOOM... + // Happened in the scenario of tdf#104152 where inserting a + // paragraph lead to a11y attempting to format the doc to + // obtain content when notified. + nTmpPortion = nTextPortions - 1; + SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion"); + } + + nPortionStart = nTmpPos; + pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; + if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR ) + { + // Throw away a Portion, if necessary correct the one before, + // if the Hyph portion has swallowed a character... + sal_Int32 nTmpLen = pPortion->GetLen(); + pParaPortion->GetTextPortions().Remove( nTmpPortion ); + if (nTmpPortion && nTmpLen) + { + nTmpPortion--; + TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion]; + DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); + nTmpWidth -= rPrev.GetSize().Width(); + nTmpPos = nTmpPos - rPrev.GetLen(); + rPrev.SetLen(rPrev.GetLen() + nTmpLen); + rPrev.setWidth(-1); + } + + assert( nTmpPortion < pParaPortion->GetTextPortions().Count() && "No more Portions left!" ); + pPortion = &pParaPortion->GetTextPortions()[nTmpPortion]; + } + + if (bContinueLastPortion) + { + // Note that this may point behind the portion and is only to + // be used with the node's string offsets to generate X-values. + nPortionLen = pNode->Len() - nPortionStart; + } + else + { + nPortionLen = pPortion->GetLen(); + } + + DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" ); + DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" ); + if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) ) + { + SAL_WARN_IF( bContinueLastPortion, + "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong"); + sal_uInt16 nWhich = pNextFeature->GetItem()->Which(); + switch ( nWhich ) + { + case EE_FEATURE_TAB: + { + tools::Long nOldTmpWidth = nTmpWidth; + + // Search for Tab-Pos... + tools::Long nCurPos = nTmpWidth + nStartX; + // consider scaling + if (maStatus.DoStretch() && (mfFontScaleX != 100.0)) + nCurPos = basegfx::fround(double(nCurPos) * 100.0 / std::max(mfFontScaleX, 1.0)); + + short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth); + aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, maEditDoc.GetDefTab() ); + aCurrentTab.nTabPos = scaleXFontValue(tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText/*rLRItem.GetTextLeft()*/)); + aCurrentTab.bValid = false; + + // Switch direction in R2L para... + if ( bRightToLeftPara ) + { + if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) + aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left; + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left ) + aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right; + } + + if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) || + ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) || + ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) ) + { + // For LEFT / DEFAULT this tab is not considered. + aCurrentTab.bValid = true; + aCurrentTab.nStartPosX = nTmpWidth; + aCurrentTab.nTabPortion = nTmpPortion; + } + + pPortion->SetKind(PortionKind::TAB); + pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() ); + pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) ); + + // Height needed... + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + pPortion->setHeight( GetRefDevice()->GetTextHeight() ); + + DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" ); + + nTmpWidth = aCurrentTab.nTabPos-nStartX; + + // If this is the first token on the line, + // and nTmpWidth > maPaperSize.Width, => infinite loop! + if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) + { + // What now? + // make the tab fitting + pPortion->setWidth( nXWidth-nOldTmpWidth ); + nTmpWidth = nXWidth-1; + bEOL = true; + bBrokenLine = true; + } + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + bCompressedChars = false; + } + break; + case EE_FEATURE_LINEBR: + { + assert( pPortion ); + pPortion->setWidth(0); + bEOL = true; + bLineBreak = true; + pPortion->SetKind( PortionKind::LINEBREAK ); + bCompressedChars = false; + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + } + break; + case EE_FEATURE_FIELD: + { + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue(); + // get size, but also DXArray to allow length information in line breaking below + KernArray aTmpDXArray; + pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(), + aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray)); + + // So no scrolling for oversized fields + if ( pPortion->GetSize().Width() > nXWidth ) + { + // create ExtraPortionInfo on-demand, flush lineBreaksList + ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos(); + + if(nullptr == pExtraInfo) + { + pExtraInfo = new ExtraPortionInfo(); + pExtraInfo->nOrgWidth = nXWidth; + pPortion->SetExtraInfos(pExtraInfo); + } + else + { + pExtraInfo->lineBreaksList.clear(); + } + + // iterate over CellBreaks using XBreakIterator to be on the + // safe side with international texts/charSets + Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator()); + const sal_Int32 nTextLength(aFieldValue.getLength()); + const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart))); + sal_Int32 nDone(0); + sal_Int32 nNextCellBreak( + xBreakIterator->nextCharacters( + aFieldValue, + 0, + aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, + 0, + nDone)); + sal_Int32 nLastCellBreak(0); + sal_Int32 nLineStartX(0); + + // always add 1st line break (safe, we already know we are larger than nXWidth) + pExtraInfo->lineBreaksList.push_back(0); + + for(sal_Int32 a(0); a < nTextLength; a++) + { + if(a == nNextCellBreak) + { + // check width + if(aTmpDXArray[a] - nLineStartX > nXWidth) + { + // new CellBreak does not fit in current line, need to + // create a break at LastCellBreak - but do not add 1st + // line break twice for very tall frames + if(0 != a) + { + pExtraInfo->lineBreaksList.push_back(a); + } + + // moveLineStart forward in X + nLineStartX = aTmpDXArray[nLastCellBreak]; + } + + // update CellBreak iteration values + nLastCellBreak = a; + nNextCellBreak = xBreakIterator->nextCharacters( + aFieldValue, + a, + aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, + 1, + nDone); + } + } + } + nTmpWidth += pPortion->GetSize().Width(); + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nPos = nTmpPos - pLine->GetStart(); + rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); + pPortion->SetKind(PortionKind::FIELD); + // If this is the first token on the line, + // and nTmpWidth > maPaperSize.Width, => infinite loop! + if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) + { + nTmpWidth = nXWidth-1; + bEOL = true; + bBrokenLine = true; + } + // Compression in Fields???? + // I think this could be a little bit difficult and is not very useful + bCompressedChars = false; + } + break; + default: OSL_FAIL( "What feature?" ); + } + pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 ); + } + else + { + DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" ); + SeekCursor( pNode, nTmpPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + + if (!bContinueLastPortion) + pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) ); + + if (bContinueLastPortion) + { + Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(), + pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray )); + pPortion->adjustSize(aSize.Width(), 0); + if (pPortion->GetSize().Height() < aSize.Height()) + pPortion->setHeight(aSize.Height()); + } + else + { + auto aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray); + pPortion->SetSize(aSize); + } + + // #i9050# Do Kerning also behind portions... + if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) ) + pPortion->adjustSize(aTmpFont.GetFixKerning(), 0); + if ( IsFixedCellHeight() ) + { + pPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + } + // The array is generally flattened at the beginning + // => Always simply quick inserts. + size_t nPos = nTmpPos - pLine->GetStart(); + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + assert(aCharPositionArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aCharPositionArray.get_subunit_array(); + rArray.insert( rArray.begin() + nPos, rKernArray.data(), rKernArray.data() + nPortionLen); + + // And now check for Compression: + if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE ) + { + sal_Int32* pDXArray = rArray.data() + nTmpPos - pLine->GetStart(); + bCompressedChars |= ImplCalcAsianCompression( + pNode, pPortion, nTmpPos, pDXArray, 10000, false); + } + + nTmpWidth += pPortion->GetSize().Width(); + + sal_Int32 _nPortionEnd = nTmpPos + nPortionLen; + if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) ) + { + bool bAllow = false; + sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) ); + sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) ); + if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) ) + bAllow = true; + + // No spacing within L2R/R2L nesting + if ( bAllow ) + { + tools::Long nExtraSpace = pPortion->GetSize().Height() / 5; + nExtraSpace = scaleXSpacingValue(nExtraSpace); + pPortion->adjustSize(nExtraSpace, 0); + nTmpWidth += nExtraSpace; + } + } + } + + if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) ) + { + tools::Long nWidthAfterTab = 0; + for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ ) + { + const TextPortion& rTP = pParaPortion->GetTextPortions()[n]; + nWidthAfterTab += rTP.GetSize().Width(); + } + tools::Long nW = nWidthAfterTab; // Length before tab position + if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) + { + } + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) + { + nW = nWidthAfterTab/2; + } + else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) + { + OUString aText = GetSelected( EditSelection( EditPaM( pParaPortion->GetNode(), nTmpPos ), + EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) ); + sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() ); + if ( nDecPos != -1 ) + { + nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width(); + nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(), + nTmpPos, nDecPos, nullptr ).Width(); + aCurrentTab.bValid = false; + } + } + else + { + OSL_FAIL( "CreateLines: Tab not handled!" ); + } + tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX; + if ( nW >= nMaxW ) + { + nW = nMaxW; + aCurrentTab.bValid = false; + } + TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion]; + rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX ); + nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab; + } + + nTmpPos = nTmpPos + nPortionLen; + nPortionEnd = nTmpPos; + nTmpPortion++; + if (maStatus.OneCharPerLine()) + bEOL = true; + } + + DBG_ASSERT( pPortion, "no portion!?" ); + + aCurrentTab.bValid = false; + + assert(pLine); + + // this was possibly a portion too far: + bool bFixedEnd = false; + if (maStatus.OneCharPerLine()) + { + // State before Portion (apart from nTmpWidth): + nTmpPos -= pPortion ? nPortionLen : 0; + nPortionStart = nTmpPos; + nTmpPortion--; + + bEOL = true; + bEOC = false; + + // And now just one character: + nTmpPos++; + nTmpPortion++; + nPortionEnd = nTmpPortion; + // one Non-Feature-Portion has to be wrapped + if ( pPortion && nPortionLen > 1 ) + { + DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" ); + nTmpWidth -= pPortion->GetSize().Width(); + sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine ); + nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width(); + } + } + else if ( nTmpWidth >= nXWidth ) + { + nPortionEnd = nTmpPos; + nTmpPos -= pPortion ? nPortionLen : 0; + nPortionStart = nTmpPos; + nTmpPortion--; + bEOL = false; + bEOC = false; + if( pPortion ) switch ( pPortion->GetKind() ) + { + case PortionKind::TEXT: + { + nTmpWidth -= pPortion->GetSize().Width(); + } + break; + case PortionKind::FIELD: + case PortionKind::TAB: + { + nTmpWidth -= pPortion->GetSize().Width(); + bEOL = true; + bFixedEnd = true; + } + break; + default: + { + // A feature is not wrapped: + DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" ); + bEOL = true; + bFixedEnd = true; + } + } + } + else + { + bEOL = true; + bEOC = true; + pLine->SetEnd( nPortionEnd ); + assert( pParaPortion->GetTextPortions().Count() && "No TextPortions?" ); + pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 ); + } + + if (maStatus.OneCharPerLine()) + { + pLine->SetEnd( nPortionEnd ); + pLine->SetEndPortion( nTmpPortion-1 ); + } + else if ( bFixedEnd ) + { + pLine->SetEnd( nPortionStart ); + pLine->SetEndPortion( nTmpPortion-1 ); + } + else if ( bLineBreak || bBrokenLine ) + { + pLine->SetEnd( nPortionStart+1 ); + pLine->SetEndPortion( nTmpPortion-1 ); + bEOC = false; // was set above, maybe change the sequence of the if's? + } + else if ( !bEOL && !bContinueLastPortion ) + { + DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" ); + tools::Long nRemainingWidth = !maStatus.IsSingleLine() ? + nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1; + bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ); + if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed ) + { + // I need the manipulated DXArray for determining the break position... + sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart()); + ImplCalcAsianCompression( + pNode, pPortion, nPortionStart, pDXArray, 10000, true); + } + if( pPortion ) + ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart, + nRemainingWidth, bCanHyphenate && bHyphenatePara ); + } + + + // Line finished => adjust + + + // CalcTextSize should be replaced by a continuous registering! + Size aTextSize = pLine->CalcTextSize( *pParaPortion ); + + if ( aTextSize.Height() == 0 ) + { + SeekCursor( pNode, pLine->GetStart()+1, aTmpFont ); + aTmpFont.SetPhysFont(*pRefDev); + ImplInitDigitMode(*pRefDev, aTmpFont.GetLanguage()); + + if ( IsFixedCellHeight() ) + aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + else + aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() ); + pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) ); + } + + // The font metrics can not be calculated continuously, if the font is + // set anyway, because a large font only after wrapping suddenly ends + // up in the next line => Font metrics too big. + FormatterFontMetric aFormatterMetrics; + sal_Int32 nTPos = pLine->GetStart(); + for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ ) + { + const TextPortion& rTP = pParaPortion->GetTextPortions()[nP]; + // problem with hard font height attribute, when everything but the line break has this attribute + if ( rTP.GetKind() != PortionKind::LINEBREAK ) + { + SeekCursor( pNode, nTPos+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); + } + nTPos = nTPos + rTP.GetLen(); + } + sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); + if ( nLineHeight > pLine->GetHeight() ) + pLine->SetHeight( nLineHeight ); + pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); + + bSameLineAgain = false; + if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) ) + { + // put down with the other size! + bSameLineAgain = true; + } + + if (!bSameLineAgain && !maStatus.IsOutliner()) + { + if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + { + double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight()); + sal_uInt16 nMinHeight = basegfx::fround(fMinHeight); + + sal_uInt16 nTxtHeight = pLine->GetHeight(); + if ( nTxtHeight < nMinHeight ) + { + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = nMinHeight - nTxtHeight; + pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) ); + pLine->SetHeight( nMinHeight, nTxtHeight ); + } + } + else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) + { + double fFixHeight = scaleYSpacingValue(rLSItem.GetLineHeight()); + sal_uInt16 nFixHeight = basegfx::fround(fFixHeight); + + sal_uInt16 nTxtHeight = pLine->GetHeight(); + pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); + pLine->SetHeight( nFixHeight, nTxtHeight ); + } + else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + // There are documents with PropLineSpace 0, why? + // (cmc: re above question :-) such documents can be seen by importing a .ppt + sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); + double fProportionalScale = double(nPropLineSpace) / 100.0; + constexpr const double f80Percent = 8.0 / 10.0; + double fSpacingFactor = mfSpacingScaleY / 100.0; + if (nPropLineSpace && nPropLineSpace < 100) + { + // Adapted code from sw/source/core/text/itrform2.cxx + sal_uInt16 nAscent = pLine->GetMaxAscent(); + sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor * fProportionalScale * f80Percent); + if (!nAscent || nAscent > nNewAscent) + pLine->SetMaxAscent(nNewAscent); + sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fProportionalScale * fSpacingFactor); + + pLine->SetHeight(nHeight, pLine->GetTxtHeight()); + } + else if (nPropLineSpace && nPropLineSpace != 100) + { + sal_uInt16 nTxtHeight = pLine->GetHeight(); + sal_Int32 nPropTextHeight = nTxtHeight * fProportionalScale * fSpacingFactor; + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = pLine->GetHeight() - nPropTextHeight; + pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) ); + pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight ); + } + } + else if (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Off) + { + if (mfSpacingScaleY < 100.0) + { + double fSpacingFactor = mfSpacingScaleY / 100.0; + sal_uInt16 nPropLineSpace = basegfx::fround(100.0 * fSpacingFactor); + if (nPropLineSpace && nPropLineSpace < 100) + { + // Adapted code from sw/source/core/text/itrform2.cxx + sal_uInt16 nAscent = pLine->GetMaxAscent(); + sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor); + if (!nAscent || nAscent > nNewAscent) + pLine->SetMaxAscent(nNewAscent); + sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fSpacingFactor); + + pLine->SetHeight(nHeight, pLine->GetTxtHeight()); + } + + } + } + } + + if ( ( !IsEffectivelyVertical() && maStatus.AutoPageWidth() ) || + ( IsEffectivelyVertical() && maStatus.AutoPageHeight() ) ) + { + // If the row fits within the current paper width, then this width + // has to be used for the Alignment. If it does not fit or if it + // will change the paper width, it will be formatted again for + // Justification! = LEFT anyway. + tools::Long nMaxLineWidthFix = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()) - nStartX; + if ( aTextSize.Width() < nMaxLineWidthFix ) + nMaxLineWidth = nMaxLineWidthFix; + } + + if ( bCompressedChars ) + { + tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width(); + if ( nRemainingWidth > 0 ) + { + ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth ); + aTextSize = pLine->CalcTextSize( *pParaPortion ); + } + } + + if ( pLine->IsHangingPunctuation() ) + { + // Width from HangingPunctuation was set to 0 in ImpBreakLine, + // check for rel width now, maybe create compression... + tools::Long n = nMaxLineWidth - aTextSize.Width(); + TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()]; + sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart(); + tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n; + if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size()) + { + pLine->GetCharPosArray()[ nPosInArray ] = nNewValue; + } + rTP.adjustSize(n, 0); + } + + pLine->SetTextWidth( aTextSize.Width() ); + switch ( eJustification ) + { + case SvxAdjust::Center: + { + tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2; + n += nStartX; // Indentation is kept. + pLine->SetStartPosX( n ); + } + break; + case SvxAdjust::Right: + { + // For automatically wrapped lines, which has a blank at the end + // the blank must not be displayed! + tools::Long n = nMaxLineWidth - aTextSize.Width(); + n += nStartX; // Indentation is kept. + pLine->SetStartPosX( n ); + } + break; + case SvxAdjust::Block: + { + bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute); + tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width(); + pLine->SetStartPosX( nStartX ); + if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) ) + ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace ); + } + break; + default: + { + pLine->SetStartPosX( nStartX ); // FI, LI + } + break; + } + + + // Check whether the line must be re-issued... + + pLine->SetInvalid(); + + // If a portion was wrapped there may be far too many positions in + // CharPosArray: + EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); + size_t nLen = pLine->GetLen(); + if (rArray.size() > nLen) + rArray.erase(rArray.begin()+nLen, rArray.end()); + + if ( GetTextRanger() ) + { + if ( nTextXOffset ) + pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset ); + if ( nTextExtraYOffset ) + { + pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 ); + pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) ); + } + } + + // for <0 think over ! + if ( pParaPortion->IsSimpleInvalid() ) + { + // Change through simple Text changes... + // Do not cancel formatting since Portions possibly have to be split + // again! If at some point cancelable, then validate the following + // line! But if applicable, mark as valid, so there is less output... + if ( pLine->GetEnd() < nInvalidStart ) + { + if ( *pLine == aSaveLine ) + { + pLine->SetValid(); + } + } + else + { + sal_Int32 nStart = pLine->GetStart(); + sal_Int32 nEnd = pLine->GetEnd(); + + if ( nStart > nInvalidEnd ) + { + if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) && + ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) ) + { + pLine->SetValid(); + if (bQuickFormat) + { + bLineBreak = false; + pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); + break; + } + } + } + else if (bQuickFormat && (nEnd > nInvalidEnd)) + { + // If the invalid line ends so that the next begins on the + // 'same' passage as before, i.e. not wrapped differently, + // then the text width does not have to be determined anew: + if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) ) + { + bLineBreak = false; + pParaPortion->CorrectValuesBehindLastFormattedLine( nLine ); + break; + } + } + } + } + + if ( !bSameLineAgain ) + { + nIndex = pLine->GetEnd(); // next line start = last line end + // as nEnd points to the last character! + + sal_Int32 nEndPortion = pLine->GetEndPortion(); + nCurrentPosY += pLine->GetHeight(); + + // Next line or maybe a new line... + pLine = nullptr; + if ( nLine < pParaPortion->GetLines().Count()-1 ) + pLine = &pParaPortion->GetLines()[++nLine]; + if ( pLine && ( nIndex >= pNode->Len() ) ) + { + nDelFromLine = nLine; + break; + } + // Stop processing if allowed and this is outside of the paper size height. + // Format at least two lines though, in case something detects whether + // the text has been wrapped or something similar. + if( mbSkipOutsideFormat && nLine > 2 + && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY ) + { + if ( pLine && ( nIndex >= pNode->Len()) ) + nDelFromLine = nLine; + break; + } + if ( !pLine ) + { + if ( nIndex < pNode->Len() ) + { + pLine = new EditLine; + pParaPortion->GetLines().Insert(++nLine, pLine); + } + else if ( nIndex && bLineBreak && GetTextRanger() ) + { + // normally CreateAndInsertEmptyLine would be called, but I want to use + // CreateLines, so I need Polygon code only here... + TextPortion* pDummyPortion = new TextPortion( 0 ); + pParaPortion->GetTextPortions().Append(pDummyPortion); + pLine = new EditLine; + pParaPortion->GetLines().Insert(++nLine, pLine); + bForceOneRun = true; + bProcessingEmptyLine = true; + } + } + if ( pLine ) + { + aSaveLine = *pLine; + pLine->SetStart( nIndex ); + pLine->SetEnd( nIndex ); + pLine->SetStartPortion( nEndPortion+1 ); + pLine->SetEndPortion( nEndPortion+1 ); + } + } + } // while ( Index < Len ) + + if ( nDelFromLine >= 0 ) + pParaPortion->GetLines().DeleteFromLine( nDelFromLine ); + + DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" ); + + if ( bLineBreak ) + CreateAndInsertEmptyLine( pParaPortion ); + + bool bHeightChanged = FinishCreateLines( pParaPortion ); + + if ( bMapChanged ) + GetRefDevice()->Pop(); + + GetRefDevice()->Pop(); + + return bHeightChanged; +} + +void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) +{ + DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" ); + + EditLine* pTmpLine = new EditLine; + pTmpLine->SetStart( pParaPortion->GetNode()->Len() ); + pTmpLine->SetEnd( pParaPortion->GetNode()->Len() ); + pParaPortion->GetLines().Append(pTmpLine); + + bool bLineBreak = pParaPortion->GetNode()->Len() > 0; + sal_Int32 nSpaceBefore = 0; + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore ); + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() ); + const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore); + + tools::Rectangle aBulletArea { Point(), Point() }; + if ( bLineBreak ) + { + nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth); + } + else + { + aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); + if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 ) + pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right()))); + else + pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly. + if ( pParaPortion->GetBulletX() > nStartX ) + { + nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth); + if ( pParaPortion->GetBulletX() > nStartX ) + nStartX = pParaPortion->GetBulletX(); + } + } + + SvxFont aTmpFont; + SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont ); + aTmpFont.SetPhysFont(*pRefDev); + + TextPortion* pDummyPortion = new TextPortion( 0 ); + pDummyPortion->SetSize(aTmpFont.GetPhysTxtSize(pRefDev)); + if ( IsFixedCellHeight() ) + pDummyPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + pParaPortion->GetTextPortions().Append(pDummyPortion); + FormatterFontMetric aFormatterMetrics; + RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); + pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) ); + sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight(); + if ( nLineHeight > pTmpLine->GetHeight() ) + pTmpLine->SetHeight( nLineHeight ); + + if (!maStatus.IsOutliner()) + { + sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + SvxAdjust eJustification = GetJustification( nPara ); + tools::Long nMaxLineWidth = GetColumnWidth(maPaperSize); + nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight()); + if ( nMaxLineWidth < 0 ) + nMaxLineWidth = 1; + if ( eJustification == SvxAdjust::Center ) + nStartX = nMaxLineWidth / 2; + else if ( eJustification == SvxAdjust::Right ) + nStartX = nMaxLineWidth; + } + + pTmpLine->SetStartPosX( nStartX ); + + if (!maStatus.IsOutliner()) + { + if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + { + sal_uInt16 nMinHeight = rLSItem.GetLineHeight(); + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + if ( nTxtHeight < nMinHeight ) + { + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = nMinHeight - nTxtHeight; + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) ); + pTmpLine->SetHeight( nMinHeight, nTxtHeight ); + } + } + else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) + { + sal_uInt16 nFixHeight = rLSItem.GetLineHeight(); + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); + pTmpLine->SetHeight( nFixHeight, nTxtHeight ); + } + else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line + { + // There are documents with PropLineSpace 0, why? + // (cmc: re above question :-) such documents can be seen by importing a .ppt + if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) + { + sal_uInt16 nTxtHeight = pTmpLine->GetHeight(); + sal_Int32 nH = nTxtHeight; + nH *= rLSItem.GetPropLineSpace(); + nH /= 100; + // The Ascent has to be adjusted for the difference: + tools::Long nDiff = pTmpLine->GetHeight() - nH; + if ( nDiff > pTmpLine->GetMaxAscent() ) + nDiff = pTmpLine->GetMaxAscent(); + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight ); + } + } + } + } + + if ( !bLineBreak ) + { + tools::Long nMinHeight = aBulletArea.GetHeight(); + if ( nMinHeight > static_cast<tools::Long>(pTmpLine->GetHeight()) ) + { + tools::Long nDiff = nMinHeight - static_cast<tools::Long>(pTmpLine->GetHeight()); + // distribute nDiff upwards and downwards + pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) ); + pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) ); + } + } + else + { + // -2: The new one is already inserted. +#ifdef DBG_UTIL + EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2]; + DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" ); +#endif + sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ; + pTmpLine->SetStartPortion( nPos ); + pTmpLine->SetEndPortion( nPos ); + } +} + +bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion ) +{ +// CalcCharPositions( pParaPortion ); + pParaPortion->SetValid(); + tools::Long nOldHeight = pParaPortion->GetHeight(); + CalcHeight( pParaPortion ); + + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" ); + bool bRet = ( pParaPortion->GetHeight() != nOldHeight ); + return bRet; +} + +void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate ) +{ + ContentNode* const pNode = pParaPortion->GetNode(); + + sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart(); + sal_Int32 nMax = nBreakInLine + pPortion->GetLen(); + while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) ) + nBreakInLine++; + + sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart(); + sal_Int32 nBreakPos = SAL_MAX_INT32; + + bool bCompressBlank = false; + bool bHyphenated = false; + bool bHangingPunctuation = false; + sal_Unicode cAlternateReplChar = 0; + sal_Unicode cAlternateExtraChar = 0; + bool bAltFullLeft = false; + bool bAltFullRight = false; + sal_uInt32 nAltDelChar = 0; + + if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) ) + { + // Break behind the blank, blank will be compressed... + nBreakPos = nMaxBreakPos + 1; + bCompressBlank = true; + } + else + { + sal_Int32 nMinBreakPos = pLine->GetStart(); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (size_t nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos) + { + nMinBreakPos = rAttr.GetEnd(); + break; + } + } + assert(nMinBreakPos <= nMaxBreakPos); + + lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) ); + + Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>( + pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue(); + + if (nMinBreakPos == nMaxBreakPos) + { + nBreakPos = nMinBreakPos; + } + else + { + Reference< XHyphenator > xHyph; + if ( bCanHyphenate ) + xHyph = GetHyphenator(); + i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 ); + i18n::LineBreakUserOptions aUserOptions; + + const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true ); + aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine; + aUserOptions.forbiddenEndCharacters = pForbidden->endLine; + aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue(); + aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin; + aUserOptions.allowHyphenateEnglish = false; + + if (!maStatus.IsSingleLine()) + { + i18n::LineBreakResults aLBR = _xBI->getLineBreak( + pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); + nBreakPos = aLBR.breakIndex; + + // show soft hyphen + if ( nBreakPos && CH_SOFTHYPHEN == pNode->GetString()[ sal_Int32(nBreakPos) - 1 ] ) + bHyphenated = true; + } + else + { + nBreakPos = nMaxBreakPos; + } + + // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos + if ( nBreakPos < nMinBreakPos ) + { + nBreakPos = nMinBreakPos; + } + else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin ) + { + OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" ); + nBreakPos = nMaxBreakPos; + } + // Hanging punctuation is the only case that increases nBreakPos and makes + // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over + // the border of the object. + } + + // BUG in I18N - the japanese dot is in the next line! + // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos ) + { + sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0; + if ( cFirstInNextLine == 12290 ) + nBreakPos++; + } + + bHangingPunctuation = nBreakPos > nMaxBreakPos; + pLine->SetHangingPunctuation( bHangingPunctuation ); + + // Whether a separator or not, push the word after the separator through + // hyphenation... NMaxBreakPos is the last character that fits into + // the line, nBreakPos is the beginning of the word. + // There is a problem if the Doc is so narrow that a word is broken + // into more than two lines... + if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() ) + { + i18n::Boundary aBoundary = _xBI->getWordBoundary( + pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true); + sal_Int32 nWordStart = nBreakPos; + sal_Int32 nWordEnd = aBoundary.endPos; + DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" ); + + sal_Int32 nWordLen = nWordEnd - nWordStart; + if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) ) + { + // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD + const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen); + sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter + Reference< XHyphenatedWord > xHyphWord; + if (xHyphenator.is()) + xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() ); + if (xHyphWord.is()) + { + bool bAlternate = xHyphWord->isAlternativeSpelling(); + sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); + + if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) ) + { + if ( !bAlternate ) + { + bHyphenated = true; + nBreakPos = nWordStart + _nWordLen; + } + else + { + // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*) + OUString aAlt( xHyphWord->getHyphenatedWord() ); + std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen)); + std::u16string_view aAltRight(aAlt.subView(_nWordLen)); + bAltFullLeft = aWord.startsWith(aAltLeft); + bAltFullRight = aWord.endsWith(aAltRight); + nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight); + + // NOTE: improved for other cases, see fdo#63711 + + // We expect[ed] the two cases: + // 1) packen becomes pak-ken + // 2) Schiffahrt becomes Schiff-fahrt + // In case 1, a character has to be replaced + // in case 2 a character is added. + // The identification is complicated by long + // compound words because the Hyphenator separates + // all position of the word. [This is not true for libhyphen.] + // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln" + // We can thus actually not directly connect the index of the + // AlternativeWord to aWord. The whole issue will be simplified + // by a function in the Hyphenator as soon as AMA builds this in... + sal_Int32 nAltStart = _nWordLen - 1; + sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); + sal_Int32 nTxtEnd = nTxtStart; + sal_Int32 nAltEnd = nAltStart; + + // The regions between the nStart and nEnd is the + // difference between alternative and original string. + while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && + aWord[nTxtEnd] != aAlt[nAltEnd] ) + { + ++nTxtEnd; + ++nAltEnd; + } + + // If a character is added, then we notice it now: + if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && + aWord[ nTxtEnd ] == aAlt[nAltEnd] ) + { + ++nAltEnd; + ++nTxtStart; + ++nTxtEnd; + } + + DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" ); + + if ( nTxtEnd > nTxtStart ) + cAlternateReplChar = aAlt[nAltStart]; + else + cAlternateExtraChar = aAlt[nAltStart]; + + bHyphenated = true; + nBreakPos = nWordStart + nTxtStart; + if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel" + nBreakPos++; + } + } + } + } + } + + if ( nBreakPos <= pLine->GetStart() ) + { + // No separator in line => Chop! + nBreakPos = nMaxBreakPos; + // I18N nextCharacters ! + if ( nBreakPos <= pLine->GetStart() ) + nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop! + } + } + + // the dickey portion is the end portion + pLine->SetEnd( nBreakPos ); + + sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine ); + + if ( !bCompressBlank && !bHangingPunctuation ) + { + // When justification is not SvxAdjust::Left, it's important to compress + // the trailing space even if there is enough room for the space... + // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too... + assert( nBreakPos > pLine->GetStart() && "ImpBreakLines - BreakPos not expected!" ); + if ( pNode->GetChar( nBreakPos-1 ) == ' ' ) + bCompressBlank = true; + } + + if ( bCompressBlank || bHangingPunctuation ) + { + TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion]; + DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" ); + DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" ); + sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart(); + rTP.setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ); + if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size()) + { + pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width(); + } + } + else if ( bHyphenated ) + { + // A portion for inserting the separator... + TextPortion* pHyphPortion = new TextPortion( 0 ); + pHyphPortion->SetKind( PortionKind::HYPHENATOR ); + if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported + { + TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion]; + DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" ); + rPrev.SetLen( rPrev.GetLen() - nAltDelChar ); + pHyphPortion->SetLen( nAltDelChar ); + if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar ); + // Correct width of the portion above: + rPrev.setWidth( + pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] ); + } + + // Determine the width of the Hyph-Portion: + SvxFont aFont; + SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont ); + aFont.SetPhysFont(*GetRefDevice()); + pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight())); + + pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion); + } + pLine->SetEndPortion( nEndPortion ); +} + +void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace ) +{ + DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." ); + assert( pLine && "AdjustBlocks: Line ?!" ); + if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() ) + return ; + + const sal_Int32 nFirstChar = pLine->GetStart(); + const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind + ContentNode* pNode = pParaPortion->GetNode(); + + DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" ); + + // Search blanks or Kashidas... + std::vector<sal_Int32> aPositions; + + // Kashidas ? + ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions ); + auto nKashidas = aPositions.size(); + + sal_uInt16 nLastScript = i18n::ScriptType::LATIN; + for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ ) + { + EditPaM aPaM( pNode, nChar+1 ); + LanguageType eLang = GetLanguage(aPaM).nLang; + sal_uInt16 nScript = GetI18NScriptType(aPaM); + // Arabic script is handled above, but if no Kashida positions are found, use blanks. + if (MsLangId::getPrimaryLanguage(eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY && nKashidas) + continue; + + if ( pNode->GetChar(nChar) == ' ' ) + { + // Normal latin script. + aPositions.push_back( nChar ); + } + else if (nChar > nFirstChar) + { + if (nLastScript == i18n::ScriptType::ASIAN) + { + // Set break position between this and the last character if + // the last character is asian script. + aPositions.push_back( nChar-1 ); + } + else if (nScript == i18n::ScriptType::ASIAN) + { + // Set break position between a latin script and asian script. + aPositions.push_back( nChar-1 ); + } + } + + nLastScript = nScript; + } + + if ( aPositions.empty() ) + return; + + // If the last character is a blank, it is rejected! + // The width must be distributed to the blockers in front... + // But not if it is the only one. + if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) && + ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) ) + { + aPositions.pop_back(); + sal_Int32 nPortionStart, nPortion; + nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart ); + TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; + tools::Long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar]; + tools::Long nBlankWidth = nRealWidth; + if ( nLastChar > nPortionStart ) + nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1]; + // Possibly the blank has already been deducted in ImpBreakLine: + if ( nRealWidth == rLastPortion.GetSize().Width() ) + { + // For the last character the portion must stop behind the blank + // => Simplify correction: + DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?"); + rLastPortion.adjustSize(-nBlankWidth, 0); + nRemainingSpace += nBlankWidth; + } + pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth; + } + + size_t nGaps = aPositions.size(); + const tools::Long nMore4Everyone = nRemainingSpace / nGaps; + tools::Long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps; + + DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" ); + DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " ); + + // Mark Kashida positions, so that VCL knows where to insert Kashida and + // where to only expand the width. + if (nKashidas) + { + pLine->GetKashidaArray().resize(pLine->GetCharPosArray().size(), false); + for (size_t i = 0; i < nKashidas; i++) + { + auto nChar = aPositions[i]; + if ( nChar < nLastChar ) + pLine->GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/; + } + } + + // Correct the positions in the Array and the portion widths: + // Last character won't be considered... + for (auto const& nChar : aPositions) + { + if ( nChar < nLastChar ) + { + sal_Int32 nPortionStart, nPortion; + nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true ); + TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; + + // The width of the portion: + rLastPortion.adjustSize(nMore4Everyone, 0); + if (nSomeExtraSpace) + { + rLastPortion.adjustSize(1, 0); + } + + // Correct positions in array + sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen(); + for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ ) + { + pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone; + if ( nSomeExtraSpace ) + pLine->GetCharPosArray()[_n-nFirstChar]++; + } + + if ( nSomeExtraSpace ) + nSomeExtraSpace--; + } + } + + // Now the text width contains the extra width... + pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace ); +} + +// For Kashidas from sw/source/core/text/porlay.cxx +void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ) +{ + // Kashida glyph looks suspicious, skip Kashida justification + if (GetRefDevice()->GetMinKashida() <= 0) + return; + + std::vector<sal_Int32> aKashidaArray; + + // the search has to be performed on a per word base + + EditSelection aWordSel( EditPaM( pNode, nStart ) ); + aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); + if ( aWordSel.Min().GetIndex() < nStart ) + aWordSel.Min().SetIndex( nStart ); + + while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) ) + { + const sal_Int32 nSavPos = aWordSel.Max().GetIndex(); + if ( aWordSel.Max().GetIndex() > nEnd ) + aWordSel.Max().SetIndex( nEnd ); + + OUString aWord = GetSelected( aWordSel ); + + // restore selection for proper iteration at the end of the function + aWordSel.Max().SetIndex( nSavPos ); + + sal_Int32 nIdx = 0, nPrevIdx = 0; + sal_Int32 nKashidaPos = -1; + sal_Unicode cCh, cPrevCh = 0; + + int nPriorityLevel = 7; // 0..6 = level found + // 7 not found + + sal_Int32 nWordLen = aWord.getLength(); + + // ignore trailing vowel chars + while( nWordLen && isTransparentChar( aWord[ nWordLen - 1 ] )) + --nWordLen; + + while ( nIdx < nWordLen ) + { + cCh = aWord[ nIdx ]; + + // 1. Priority: + // after user inserted kashida + if ( 0x640 == cCh ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx; + nPriorityLevel = 0; + } + + // 2. Priority: + // after a Seen or Sad + if (nPriorityLevel >= 1 && nIdx < nWordLen - 1) + { + if( isSeenOrSadChar( cCh ) + && (aWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx; + nPriorityLevel = 1; + } + } + + // 3. Priority: + // before final form of Teh Marbuta, Heh, Dal + if ( nPriorityLevel >= 2 && nIdx > 0 ) + { + if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining) + isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word + ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word + { + + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 2; + } + } + } + + // 4. Priority: + // before final form of Alef, Tah, Lam, Kaf or Gaf + if ( nPriorityLevel >= 3 && nIdx > 0 ) + { + if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word + (( isLamChar ( cCh ) || // Lam, + isTahChar ( cCh ) || // Tah, + isKafChar ( cCh ) || // Kaf (all dual joining) + isGafChar ( cCh ) ) + && nIdx == nWordLen - 1)) // only at end of word + { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 3; + } + } + } + + // 5. Priority: + // before medial Beh-like + if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 ) + { + if ( isBehChar ( cCh ) ) + { + // check if next character is Reh or Yeh-like + sal_Unicode cNextCh = aWord[ nIdx + 1 ]; + if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh )) + { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 4; + } + } + } + } + + // 6. Priority: + // before the final form of Waw, Ain, Qaf and Feh + if ( nPriorityLevel >= 5 && nIdx > 0 ) + { + if ( isWawChar ( cCh ) || // Wav (right joining) + // final form may appear in the middle of word + (( isAinChar ( cCh ) || // Ain (dual joining) + isQafChar ( cCh ) || // Qaf (dual joining) + isFehChar ( cCh ) ) // Feh (dual joining) + && nIdx == nWordLen - 1)) // only at end of word + { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 5; + } + } + } + + // other connecting possibilities + if ( nPriorityLevel >= 6 && nIdx > 0 ) + { + // Reh, Zain + if ( isRehChar ( cCh ) ) + { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 6; + } + } + } + + // Do not consider vowel marks when checking if a character + // can be connected to previous character. + if ( !isTransparentChar ( cCh) ) + { + cPrevCh = cCh; + nPrevIdx = nIdx; + } + + ++nIdx; + } // end of current word + + if ( nKashidaPos>=0 ) + aKashidaArray.push_back( nKashidaPos ); + + aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); + aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); + } + + // Validate + std::vector<sal_Int32> aDropped(aKashidaArray.size()); + auto nOldLayout = GetRefDevice()->GetLayoutMode(); + GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl); + GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart, + aKashidaArray.size(), aKashidaArray.data(), aDropped.data()); + GetRefDevice()->SetLayoutMode(nOldLayout); + + for (auto const& pos : aKashidaArray) + if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end()) + rArray.push_back(pos); +} + +sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine ) +{ + // The portion at nPos is split, if there is not a transition at nPos anyway + if ( nPos == 0 ) + return 0; + + assert( pPortion && "SplitTextPortion: Which ?" ); + + sal_Int32 nSplitPortion; + sal_Int32 nTmpPos = 0; + TextPortion* pTextPortion = nullptr; + sal_Int32 nPortions = pPortion->GetTextPortions().Count(); + for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ ) + { + TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion]; + nTmpPos = nTmpPos + rTP.GetLen(); + if ( nTmpPos >= nPos ) + { + if ( nTmpPos == nPos ) // then nothing needs to be split + { + return nSplitPortion; + } + pTextPortion = &rTP; + break; + } + } + + DBG_ASSERT( pTextPortion, "Position outside the area!" ); + + if (!pTextPortion) + return 0; + + DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" ); + + sal_Int32 nOverlapp = nTmpPos - nPos; + pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp ); + TextPortion* pNewPortion = new TextPortion( nOverlapp ); + pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion); + // Set sizes + if ( pCurLine ) + { + // No new GetTextSize, instead use values from the Array: + assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" ); + pTextPortion->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]); + + if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed ) + { + // We need the original size from the portion + sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion ); + SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); + SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont ); + aTmpFont.SetPhysFont(*GetRefDevice()); + GetRefDevice()->Push( vcl::PushFlags::TEXTLANGUAGE ); + ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); + Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), + nTxtPortionStart, pTextPortion->GetLen(), nullptr ); + GetRefDevice()->Pop(); + pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width(); + } + } + else + pTextPortion->setWidth(-1); + + return nSplitPortion; +} + +void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart ) +{ + sal_Int32 nStartPos = rStart; + ContentNode* pNode = pParaPortion->GetNode(); + DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" ); + + o3tl::sorted_vector< sal_Int32 > aPositions; + aPositions.insert( 0 ); + + for (std::size_t nAttr = 0;; ++nAttr) + { + // Insert Start and End into the Array... + // The Insert method does not allow for duplicate values... + EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr); + if (!pAttrib) + break; + aPositions.insert( pAttrib->GetStart() ); + aPositions.insert( pAttrib->GetEnd() ); + } + aPositions.insert( pNode->Len() ); + + if ( pParaPortion->aScriptInfos.empty() ) + InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) ); + + const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; + for (const ScriptTypePosInfo& rType : rTypes) + aPositions.insert( rType.nStartPos ); + + const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos; + for (const WritingDirectionInfo & rWritingDirection : rWritingDirections) + aPositions.insert( rWritingDirection.nStartPos ); + + if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) ) + { + ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF); + for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ ) + { + if ( mpIMEInfos->pAttribs[n] != nLastAttr ) + { + aPositions.insert( mpIMEInfos->aPos.GetIndex() + n ); + nLastAttr = mpIMEInfos->pAttribs[n]; + } + } + aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ); + } + + // From ... Delete: + // Unfortunately, the number of text portions does not have to match + // aPositions.Count(), since there might be line breaks... + sal_Int32 nPortionStart = 0; + sal_Int32 nInvPortion = 0; + sal_Int32 nP; + for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ ) + { + const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP]; + nPortionStart = nPortionStart + rTmpPortion.GetLen(); + if ( nPortionStart >= nStartPos ) + { + nPortionStart = nPortionStart - rTmpPortion.GetLen(); + rStart = nPortionStart; + nInvPortion = nP; + break; + } + } + DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" ); + if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) ) + { + // prefer one in front... + // But only if it was in the middle of the portion of, otherwise it + // might be the only one in the row in front! + nInvPortion--; + nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen(); + } + pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion ); + + // A portion may also have been formed by a line break: + aPositions.insert( nPortionStart ); + + auto nInvPos = aPositions.find( nPortionStart ); + DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" ); + + auto i = nInvPos; + ++i; + while ( i != aPositions.end() ) + { + TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ ); + pParaPortion->GetTextPortions().Append(pNew); + } + + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" ); +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" ); +#endif +} + +void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ) +{ + DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" ); + DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" ); + + ContentNode* const pNode = pParaPortion->GetNode(); + if ( nNewChars > 0 ) + { + // If an Attribute begins/ends at nStartPos, then a new portion starts + // otherwise the portion is extended at nStartPos. + if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) ) + { + sal_Int32 nNewPortionPos = 0; + if ( nStartPos ) + nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1; + + // A blank portion may be here, if the paragraph was empty, + // or if a line was created by a hard line break. + if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) && + !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() ) + { + TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos]; + DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" ); + rTP.SetLen( rTP.GetLen() + nNewChars ); + } + else + { + TextPortion* pNewPortion = new TextPortion( nNewChars ); + pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion); + } + } + else + { + sal_Int32 nPortionStart; + const sal_Int32 nTP = pParaPortion->GetTextPortions(). + FindPortion( nStartPos, nPortionStart ); + TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; + rTP.SetLen( rTP.GetLen() + nNewChars ); + rTP.setWidth(-1); + } + } + else + { + // Shrink or remove portion if necessary. + // Before calling this method it must be ensured that no portions were + // in the deleted area! + + // There must be no portions extending into the area or portions starting in + // the area, so it must be: + // nStartPos <= nPos <= nStartPos - nNewChars(neg.) + sal_Int32 nPortion = 0; + sal_Int32 nPos = 0; + sal_Int32 nEnd = nStartPos-nNewChars; + sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); + TextPortion* pTP = nullptr; + for ( nPortion = 0; nPortion < nPortions; nPortion++ ) + { + pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + if ( ( nPos+pTP->GetLen() ) > nStartPos ) + { + DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" ); + DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" ); + break; + } + nPos = nPos + pTP->GetLen(); + } + assert( pTP && "RecalcTextPortion: Portion not found" ); + if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) ) + { + // Remove portion; + PortionKind nType = pTP->GetKind(); + pParaPortion->GetTextPortions().Remove( nPortion ); + if ( nType == PortionKind::LINEBREAK ) + { + TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ]; + if ( !rNext.GetLen() ) + { + // Remove dummy portion + pParaPortion->GetTextPortions().Remove( nPortion ); + } + } + } + else + { + DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! "); + pTP->SetLen( pTP->GetLen() + nNewChars ); + } + + sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count(); + assert( nPortionCount ); + if (nPortionCount) + { + // No HYPHENATOR portion is allowed to get stuck right at the end... + sal_Int32 nLastPortion = nPortionCount - 1; + pTP = &pParaPortion->GetTextPortions()[nLastPortion]; + if ( pTP->GetKind() == PortionKind::HYPHENATOR ) + { + // Discard portion; if possible, correct the ones before, + // if the Hyphenator portion has swallowed one character... + if ( nLastPortion && pTP->GetLen() ) + { + TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1]; + DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); + rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() ); + rPrev.setWidth(-1); + } + pParaPortion->GetTextPortions().Remove( nLastPortion ); + } + } + } +#if OSL_DEBUG_LEVEL > 0 + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" ); +#endif +} + +void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger ) +{ + pTextRanger = std::move(pRanger); + + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; + pParaPortion->MarkSelectionInvalid( 0 ); + pParaPortion->GetLines().Reset(); + } + + FormatFullDoc(); + UpdateViews( GetActiveView() ); + if ( IsUpdateLayout() && GetActiveView() ) + pActiveView->ShowCursor(false, false); +} + +void ImpEditEngine::SetVertical( bool bVertical) +{ + if ( IsEffectivelyVertical() != bVertical) + { + GetEditDoc().SetVertical(bVertical); + bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + GetEditDoc().CreateDefFont( bUseCharAttribs ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } + } +} + +void ImpEditEngine::SetRotation(TextRotation nRotation) +{ + if (GetEditDoc().GetRotation() == nRotation) + return; // not modified + GetEditDoc().SetRotation(nRotation); + bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + GetEditDoc().CreateDefFont( bUseCharAttribs ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } +} + +void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + assert(nColumns >= 1); + if (mnColumns != nColumns || mnColumnSpacing != nSpacing) + { + if (nColumns == 0) + { + SAL_WARN("editeng", "bad nColumns value, ignoring"); + nColumns = 1; + } + mnColumns = nColumns; + mnColumnSpacing = nSpacing; + if (IsFormatted()) + { + FormatFullDoc(); + UpdateViews(GetActiveView()); + } + } +} + +void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + if ( IsFixedCellHeight() != bUseFixedCellHeight ) + { + GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight ); + if ( IsFormatted() ) + { + FormatFullDoc(); + UpdateViews( GetActiveView() ); + } + } +} + +void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut ) +{ + // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would + // only be searched anew at the StartPosition. + // Problem: There would be two lists to consider/handle: + // OrderedByStart,OrderedByEnd. + + if ( nPos > pNode->Len() ) + nPos = pNode->Len(); + + rFont = pNode->GetCharAttribs().GetDefFont(); + + /* + * Set attributes for script types Asian and Complex + */ + short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) ); + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) ) + { + const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) )); + rFont.SetFamilyName( rFontItem.GetFamilyName() ); + rFont.SetFamily( rFontItem.GetFamily() ); + rFont.SetPitch( rFontItem.GetPitch() ); + rFont.SetCharSet( rFontItem.GetCharSet() ); + Size aSz( rFont.GetFontSize() ); + aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() ); + rFont.SetFontSize( aSz ); + rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() ); + rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() ); + rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() ); + } + + sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue(); + + /* + * Set output device's line and overline colors + */ + if ( pOut ) + { + const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE ); + if ( rTextLineColor.GetColor() != COL_TRANSPARENT ) + pOut->SetTextLineColor( rTextLineColor.GetColor() ); + else + pOut->SetTextLineColor(); + + const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE ); + if ( rOverlineColor.GetColor() != COL_TRANSPARENT ) + pOut->SetOverlineColor( rOverlineColor.GetColor() ); + else + pOut->SetOverlineColor(); + } + + const SvxLanguageItem* pCJKLanguageItem = nullptr; + + /* + * Scan through char attributes of pNode + */ + if (maStatus.UseCharAttribs()) + { + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + size_t nAttr = 0; + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib && ( pAttrib->GetStart() <= nPos ) ) + { + // when seeking, ignore attributes which start there! Empty attributes + // are considered (used) as these are just set. But do not use empty + // attributes: When just set and empty => no effect on font + // In a blank paragraph, set characters take effect immediately. + if ( ( pAttrib->Which() != 0 ) && + ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) ) + || ( !pNode->Len() ) ) ) + { + DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " ); + if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) ) + { + pAttrib->SetFont( rFont, pOut ); + // #i1550# hard color attrib should win over text color from field + if ( pAttrib->Which() == EE_FEATURE_FIELD ) + { + EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos ); + if ( pColorAttr ) + pColorAttr->SetFont( rFont, pOut ); + } + } + if ( pAttrib->Which() == EE_CHAR_FONTWIDTH ) + nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue(); + if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK ) + pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() ); + } + pAttrib = GetAttrib( rAttribs, ++nAttr ); + } + } + + if ( !pCJKLanguageItem ) + pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK ); + + rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() ); + + if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) ) + rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian ); + + if (maStatus.DoNotUseColors()) + { + rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK ); + } + + if (maStatus.DoStretch() || ( nRelWidth != 100 )) + { + // For the current Output device, because otherwise if RefDev=Printer its looks + // ugly on the screen! + OutputDevice* pDev = pOut ? pOut : GetRefDevice(); + rFont.SetPhysFont(*pDev); + FontMetric aMetric( pDev->GetFontMetric() ); + + // before forcing nPropr to 100%, calculate a new escapement relative to this fake size. + sal_uInt8 nPropr = rFont.GetPropr(); + sal_Int16 nEsc = rFont.GetEscapement(); + if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER ) + rFont.SetEscapement( 100.0/nPropr * nEsc ); + + // Set the font as we want it to look like & reset the Propr attribute + // so that it is not counted twice. + Size aRealSz( aMetric.GetFontSize() ); + rFont.SetPropr( 100 ); + + if (maStatus.DoStretch()) + { + if (mfFontScaleY != 100.0) + { + double fHeightRounded = roundToNearestPt(aRealSz.Height()); + double fNewHeight = fHeightRounded * (mfFontScaleY / 100.0); + fNewHeight = roundToNearestPt(fNewHeight); + aRealSz.setHeight(basegfx::fround(fNewHeight)); + } + if (mfFontScaleX != 100.0) + { + if (mfFontScaleX == mfFontScaleY && nRelWidth == 100 ) + { + aRealSz.setWidth( 0 ); + } + else + { + double fWidthRounded = roundToNearestPt(aRealSz.Width()); + double fNewWidth = fWidthRounded * (mfFontScaleX / 100.0); + fNewWidth = roundToNearestPt(fNewWidth); + aRealSz.setWidth(basegfx::fround(fNewWidth)); + + // Also the Kerning: (long due to handle Interim results) + tools::Long nKerning = rFont.GetFixKerning(); +/* + The consideration was: If negative kerning, but StretchX = 200 + => Do not double the kerning, thus pull the letters closer together + --------------------------- + Kern StretchX =>Kern + --------------------------- + >0 <100 < (Proportional) + <0 <100 < (Proportional) + >0 >100 > (Proportional) + <0 >100 < (The amount, thus disproportional) +*/ + if (nKerning < 0 && mfFontScaleX > 100.0) + { + // disproportional + nKerning = basegfx::fround((double(nKerning) * 100.0) / mfFontScaleX); + } + else if ( nKerning ) + { + // Proportional + nKerning = basegfx::fround((double(nKerning) * mfFontScaleX) / 100.0); + } + rFont.SetFixKerning( static_cast<short>(nKerning) ); + } + } + } + if ( nRelWidth != 100 ) + { + aRealSz.setWidth( aRealSz.Width() * nRelWidth ); + aRealSz.setWidth( aRealSz.Width() / 100 ); + } + rFont.SetFontSize( aRealSz ); + // Font is not restored... + } + + if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut ) + { + // #i75566# Do not use AutoColor when printing OR Pdf export + const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType()); + const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType()); + + if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting) + { + // Never use WindowTextColor on the printer + rFont.SetColor( GetAutoColor() ); + } + else + { + if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() ) + rFont.SetColor( COL_WHITE ); + else + rFont.SetColor( COL_BLACK ); + } + } + + if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) && + ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) ) + return; + + ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ]; + if ( nAttr & ExtTextInputAttr::Underline ) + rFont.SetUnderline( LINESTYLE_SINGLE ); + else if ( nAttr & ExtTextInputAttr::DoubleUnderline ) + rFont.SetUnderline( LINESTYLE_DOUBLE ); + else if ( nAttr & ExtTextInputAttr::BoldUnderline ) + rFont.SetUnderline( LINESTYLE_BOLD ); + else if ( nAttr & ExtTextInputAttr::DottedUnderline ) + rFont.SetUnderline( LINESTYLE_DOTTED ); + else if ( nAttr & ExtTextInputAttr::DashDotUnderline ) + rFont.SetUnderline( LINESTYLE_DOTTED ); + else if ( nAttr & ExtTextInputAttr::RedText ) + rFont.SetColor( COL_RED ); + else if ( nAttr & ExtTextInputAttr::HalfToneText ) + rFont.SetColor( COL_LIGHTGRAY ); + if ( nAttr & ExtTextInputAttr::Highlight ) + { + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + rFont.SetColor( rStyleSettings.GetHighlightTextColor() ); + rFont.SetFillColor( rStyleSettings.GetHighlightColor() ); + rFont.SetTransparent( false ); + } + else if ( nAttr & ExtTextInputAttr::GrayWaveline ) + { + rFont.SetUnderline( LINESTYLE_WAVE ); + if( pOut ) + pOut->SetTextLineColor( COL_LIGHTGRAY ); + } +} + +void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont ) +{ + // for line height at high / low first without Propr! + sal_uInt16 nPropr = rFont.GetPropr(); + DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" ); + if ( nPropr != 100 ) + { + rFont.SetPropr( 100 ); + rFont.SetPhysFont(*pRefDev); + } + sal_uInt16 nAscent, nDescent; + + FontMetric aMetric( pRefDev->GetFontMetric() ); + nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); + if ( IsAddExtLeading() ) + nAscent = sal::static_int_cast< sal_uInt16 >( + nAscent + aMetric.GetExternalLeading() ); + nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); + + if ( IsFixedCellHeight() ) + { + nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() ); + nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent ); + } + else + { + sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0; + // Fonts without leading cause problems + if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) ) + { + // Lets see what Leading one gets on the screen + VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() ); + rFont.SetPhysFont(*pVDev); + aMetric = pVDev->GetFontMetric(); + + // This is so that the Leading does not count itself out again, + // if the whole line has the font, nTmpLeading. + nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); + nDescent = static_cast<sal_uInt16>(aMetric.GetDescent()); + } + } + if ( nAscent > rCurMetrics.nMaxAscent ) + rCurMetrics.nMaxAscent = nAscent; + if ( nDescent > rCurMetrics.nMaxDescent ) + rCurMetrics.nMaxDescent= nDescent; + // Special treatment of high/low: + if ( !rFont.GetEscapement() ) + return; + + // Now in consideration of Escape/Propr + // possibly enlarge Ascent or Descent + short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100); + if ( rFont.GetEscapement() > 0 ) + { + nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff); + if ( nAscent > rCurMetrics.nMaxAscent ) + rCurMetrics.nMaxAscent = nAscent; + } + else // has to be < 0 + { + nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff); + if ( nDescent > rCurMetrics.nMaxDescent ) + rCurMetrics.nMaxDescent= nDescent; + } +} + +tools::Long ImpEditEngine::getWidthDirectionAware(const Size& sz) const +{ + return !IsEffectivelyVertical() ? sz.Width() : sz.Height(); +} + +tools::Long ImpEditEngine::getHeightDirectionAware(const Size& sz) const +{ + return !IsEffectivelyVertical() ? sz.Height() : sz.Width(); +} + +void ImpEditEngine::adjustXDirectionAware(Point& pt, tools::Long x) const +{ + if (!IsEffectivelyVertical()) + pt.AdjustX(x); + else + pt.AdjustY(IsTopToBottom() ? x : -x); +} + +void ImpEditEngine::adjustYDirectionAware(Point& pt, tools::Long y) const +{ + if (!IsEffectivelyVertical()) + pt.AdjustY(y); + else + pt.AdjustX(IsTopToBottom() ? -y : y); +} + +void ImpEditEngine::setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const +{ + if (!IsEffectivelyVertical()) + ptDest.setX(ptSrc.X()); + else + ptDest.setY(ptSrc.Y()); +} + +void ImpEditEngine::setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const +{ + if (!IsEffectivelyVertical()) + ptDest.setY(ptSrc.Y()); + else + ptDest.setX(ptSrc.Y()); +} + +tools::Long ImpEditEngine::getYOverflowDirectionAware(const Point& pt, + const tools::Rectangle& rectMax) const +{ + tools::Long nRes; + if (!IsEffectivelyVertical()) + nRes = pt.Y() - rectMax.Bottom(); + else if (IsTopToBottom()) + nRes = rectMax.Left() - pt.X(); + else + nRes = pt.X() - rectMax.Right(); + return std::max(nRes, tools::Long(0)); +} + +bool ImpEditEngine::isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const +{ + if (!IsEffectivelyVertical()) + return pt.X() > rectMax.Right(); + + if (IsTopToBottom()) + return pt.Y() > rectMax.Bottom(); + else + return pt.Y() < rectMax.Top(); +} + +tools::Long ImpEditEngine::getBottomDocOffset(const tools::Rectangle& rect) const +{ + if (!IsEffectivelyVertical()) + return rect.Bottom(); + + if (IsTopToBottom()) + return -rect.Left(); + else + return rect.Right(); +} + +Size ImpEditEngine::getTopLeftDocOffset(const tools::Rectangle& rect) const +{ + if (!IsEffectivelyVertical()) + return { rect.Left(), rect.Top() }; + + if (IsTopToBottom()) + return { rect.Top(), -rect.Right() }; + else + return { -rect.Bottom(), rect.Left() }; +} + +// Returns the resulting shift for the point; allows to apply the same shift to other points +Point ImpEditEngine::MoveToNextLine( + Point& rMovePos, // [in, out] Point that will move to the next line + tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware) + sal_Int16& rColumn, // [in, out] current column number + Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column + tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed +) const +{ + const Point aOld = rMovePos; + + // Move the point by the requested distance in Y direction + adjustYDirectionAware(rMovePos, nLineHeight); + // Check if the resulting position has moved beyond the limits, and more columns left. + // The limits are defined by a rectangle starting from aOrigin with width of maPaperSize + // and height of nCurTextHeight + Point aOtherCorner = aOrigin; + adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(maPaperSize)); + adjustYDirectionAware(aOtherCorner, nCurTextHeight); + tools::Long nNeeded + = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner)); + if (pnHeightNeededToNotWrap) + *pnHeightNeededToNotWrap = nNeeded; + if (nNeeded && rColumn < mnColumns) + { + ++rColumn; + // If we didn't fit into the last column, indicate that only by setting the column number + // to the total number of columns; do not adjust + if (rColumn < mnColumns) + { + // Set Y position of the point to that of aOrigin + setYDirectionAwareFrom(rMovePos, aOrigin); + // Move the point by the requested distance in Y direction + adjustYDirectionAware(rMovePos, nLineHeight); + // Move the point by the column+spacing distance in X direction + adjustXDirectionAware(rMovePos, GetColumnWidth(maPaperSize) + mnColumnSpacing); + } + } + + return rMovePos - aOld; +} + +// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication + +void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation ) +{ + if ( !IsUpdateLayout() && !bStripOnly ) + return; + + if ( !IsFormatted() ) + FormatDoc(); + + tools::Long nFirstVisXPos = - rOutDev.GetMapMode().GetOrigin().X(); + tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y(); + + DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" ); + SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() ); + vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() ); + + // In the case of rotated text is aStartPos considered TopLeft because + // other information is missing, and since the whole object is shown anyway + // un-scrolled. + // The rectangle is infinite. + const Point aOrigin( aStartPos ); + + // #110496# Added some more optional metafile comments. This + // change: factored out some duplicated code. + GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile(); + const bool bMetafileValid( pMtf != nullptr ); + + const tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos); + + sal_Int16 nColumn = 0; + + // Over all the paragraphs... + + for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ ) + { + const ParaPortion* const pPortion = GetParaPortions()[n]; + assert( pPortion && "NULL-Pointer in TokenList in Paint" ); + // if when typing idle formatting, asynchronous Paint. + // Invisible Portions may be invalid. + if ( pPortion->IsVisible() && pPortion->IsInvalid() ) + return; + + if ( pPDFExtOutDevData ) + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); + + const tools::Long nParaHeight = pPortion->GetHeight(); + if ( pPortion->IsVisible() && ( + ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) || + ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) || + ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) ) + + { + Point aTmpPos; + + // Over the lines of the paragraph... + + const sal_Int32 nLines = pPortion->GetLines().Count(); + const sal_Int32 nLastLine = nLines-1; + + bool bEndOfParagraphWritten(false); + + adjustYDirectionAware(aStartPos, pPortion->GetFirstLineOffset()); + + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; + bool bPaintBullet (false); + + for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) + { + const EditLine* const pLine = &pPortion->GetLines()[nLine]; + assert( pLine && "NULL-Pointer in the line iterator in UpdateViews" ); + sal_Int32 nIndex = pLine->GetStart(); + tools::Long nLineHeight = pLine->GetHeight(); + if (nLine != nLastLine) + nLineHeight += nVertLineSpacing; + MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin); + aTmpPos = aStartPos; + adjustXDirectionAware(aTmpPos, pLine->GetStartPosX()); + adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight); + + if ( ( !IsEffectivelyVertical() && ( aStartPos.Y() > aClipRect.Top() ) ) + || ( IsEffectivelyVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() ) + || ( IsEffectivelyVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) ) + { + bPaintBullet = false; + + // Why not just also call when stripping portions? This will give the correct values + // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call + // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine + // does, too. No change for not-layouting (painting). + if(0 == nLine) // && !bStripOnly) + { + Point aLineStart(aStartPos); + adjustYDirectionAware(aLineStart, -nLineHeight); + GetEditEnginePtr()->PaintingFirstLine(n, aLineStart, aOrigin, nOrientation, rOutDev); + + // Remember whether a bullet was painted. + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE); + bPaintBullet = rBulletState.GetValue(); + } + + + // Over the Portions of the line... + + bool bParsingFields = false; + std::vector< sal_Int32 >::iterator itSubLines; + + for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ ) + { + DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" ); + const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion]; + + const tools::Long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion ); + setXDirectionAwareFrom(aTmpPos, aStartPos); + adjustXDirectionAware(aTmpPos, nPortionXOffset); + if (isXOverflowDirectionAware(aTmpPos, aClipRect)) + break; // No further output in line necessary + + switch ( rTextPortion.GetKind() ) + { + case PortionKind::TEXT: + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + { + SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev ); + + bool bDrawFrame = false; + + if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() && + ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() && + ( IsAutoColorEnabled() && ( rOutDev.GetOutDevType() != OUTDEV_PRINTER ) ) ) + { + aTmpFont.SetTransparent( true ); + rOutDev.SetFillColor(); + rOutDev.SetLineColor( GetAutoColor() ); + bDrawFrame = true; + } + +#if OSL_DEBUG_LEVEL > 2 + // Do we really need this if statement? + if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) + { + aTmpFont.SetFillColor( COL_LIGHTGRAY ); + aTmpFont.SetTransparent( sal_False ); + } + if ( rTextPortion.IsRightToLeft() ) + { + aTmpFont.SetFillColor( COL_LIGHTGRAY ); + aTmpFont.SetTransparent( sal_False ); + } + else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX ) + { + aTmpFont.SetFillColor( COL_LIGHTCYAN ); + aTmpFont.SetTransparent( sal_False ); + } +#endif + aTmpFont.SetPhysFont(rOutDev); + + // #114278# Saving both layout mode and language (since I'm + // potentially changing both) + rOutDev.Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); + ImplInitLayoutMode(rOutDev, n, nIndex); + ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage()); + + OUString aText; + sal_Int32 nTextStart = 0; + sal_Int32 nTextLen = 0; + std::span<const sal_Int32> pDXArray; + std::span<const sal_Bool> pKashidaArray; + KernArray aTmpDXArray; + + if ( rTextPortion.GetKind() == PortionKind::TEXT ) + { + aText = pPortion->GetNode()->GetString(); + nTextStart = nIndex; + nTextLen = rTextPortion.GetLen(); + pDXArray = std::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()), + pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart())); + + if (!pLine->GetKashidaArray().empty()) + { + pKashidaArray = std::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()), + pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart())); + } + + // Paint control characters (#i55716#) + /* XXX: Given that there's special handling + * only for some specific characters + * (U+200B ZERO WIDTH SPACE and U+2060 WORD + * JOINER) it is assumed to be not relevant + * for MarkUrlFields(). */ + if (maStatus.MarkNonUrlFields()) + { + sal_Int32 nTmpIdx; + const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen(); + + for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx ) + { + const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ? + aText[nTmpIdx] : + 0; + + if ( 0x200B == cChar || 0x2060 == cChar ) + { + tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev, + " ", 0, 1, nullptr ).Width() / 2; + + const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ? + rTextPortion.GetSize().Width() : + pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth; + const tools::Long nAdvanceY = -pLine->GetMaxAscent(); + + Point aTopLeftRectPos( aTmpPos ); + adjustXDirectionAware(aTopLeftRectPos, nAdvanceX); + adjustYDirectionAware(aTopLeftRectPos, nAdvanceY); + + Point aBottomRightRectPos( aTopLeftRectPos ); + adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth); + adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight()); + + rOutDev.Push( vcl::PushFlags::FILLCOLOR ); + rOutDev.Push( vcl::PushFlags::LINECOLOR ); + rOutDev.SetFillColor( COL_LIGHTGRAY ); + rOutDev.SetLineColor( COL_LIGHTGRAY ); + + const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos ); + rOutDev.DrawRect( aBackRect ); + + rOutDev.Pop(); + rOutDev.Pop(); + + if ( 0x200B == cChar ) + { + const OUString aSlash( '/' ); + const short nOldEscapement = aTmpFont.GetEscapement(); + const sal_uInt8 nOldPropr = aTmpFont.GetPropr(); + + aTmpFont.SetEscapement( -20 ); + aTmpFont.SetPropr( 25 ); + aTmpFont.SetPhysFont(rOutDev); + + const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev, + aSlash, 0, 1, nullptr ); + Point aSlashPos( aTmpPos ); + const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2; + setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos); + adjustXDirectionAware(aSlashPos, nAddX); + + aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} ); + + aTmpFont.SetEscapement( nOldEscapement ); + aTmpFont.SetPropr( nOldPropr ); + aTmpFont.SetPhysFont(rOutDev); + } + } + } + } + } + else if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + assert( pAttr && "Field not found"); + DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! "); + aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue(); + nTextStart = 0; + nTextLen = aText.getLength(); + ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos(); + // Do not split the Fields into different lines while editing + // With EditView on Overlay bStripOnly is now set for stripping to + // primitives. To stay compatible in EditMode use pActiveView to detect + // when we are in EditMode. For whatever reason URLs are drawn as single + // line in edit mode, originally clipped against edit area (which is no + // longer done in Overlay mode and allows to *read* the URL). + // It would be difficult to change this due to needed adaptations in + // EditEngine (look for lineBreaksList creation) + if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() ) + { + bParsingFields = true; + itSubLines = pExtraInfo->lineBreaksList.begin(); + } + + if( bParsingFields ) + { + if( itSubLines != pExtraInfo->lineBreaksList.begin() ) + { + // only use GetMaxAscent(), pLine->GetHeight() will not + // proceed as needed (see PortionKind::TEXT above and nAdvanceY) + // what will lead to a compressed look with multiple lines + const sal_uInt16 nMaxAscent(pLine->GetMaxAscent()); + + aTmpPos += MoveToNextLine(aStartPos, nMaxAscent, + nColumn, aOrigin); + } + std::vector< sal_Int32 >::iterator curIt = itSubLines; + ++itSubLines; + if( itSubLines != pExtraInfo->lineBreaksList.end() ) + { + nTextStart = *curIt; + nTextLen = *itSubLines - nTextStart; + } + else + { + nTextStart = *curIt; + nTextLen = nTextLen - nTextStart; + bParsingFields = false; + + if (nLine + 1 < nLines) + { + // tdf#148966 don't paint the line break following a + // multiline field based on a compat flag + OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(pEditEngine) }; + if (pOutlEditEng + && pOutlEditEng->GetCompatFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField) + .value_or(false)) + { + int nStartNextLine = pPortion->GetLines()[nLine + 1].GetStartPortion(); + const TextPortion& rNextTextPortion = pPortion->GetTextPortions()[nStartNextLine]; + if (rNextTextPortion.GetKind() == PortionKind::LINEBREAK) + ++nLine; //ignore the following linebreak + } + } + } + } + + aTmpFont.SetPhysFont(*GetRefDevice()); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, + &aTmpDXArray ); + assert(aTmpDXArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array(); + pDXArray = rKernArray; + + // add a meta file comment if we record to a metafile + if( bMetafileValid ) + { + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if( pFieldData ) + pMtf->AddAction( pFieldData->createBeginComment() ); + } + } + + } + else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR ) + { + if ( rTextPortion.GetExtraValue() ) + aText = OUString(rTextPortion.GetExtraValue()); + aText += CH_HYPH; + nTextStart = 0; + nTextLen = aText.getLength(); + + // crash when accessing 0 pointer in pDXArray + aTmpFont.SetPhysFont(*GetRefDevice()); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), + &aTmpDXArray ); + assert(aTmpDXArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array(); + pDXArray = rKernArray; + } + + tools::Long nTxtWidth = rTextPortion.GetSize().Width(); + + Point aOutPos( aTmpPos ); + Point aRedLineTmpPos = aTmpPos; + // In RTL portions spell markup pos should be at the start of the + // first chara as well. That is on the right end of the portion + if (rTextPortion.IsRightToLeft()) + aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() ); + + if ( bStripOnly ) + { + EEngineData::WrongSpellVector aWrongSpellVector; + + if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen()) + { + WrongList* pWrongs = pPortion->GetNode()->GetWrongList(); + + if(pWrongs && !pWrongs->empty()) + { + size_t nStart = nIndex, nEnd = 0; + bool bWrong = pWrongs->NextWrong(nStart, nEnd); + const size_t nMaxEnd(nIndex + rTextPortion.GetLen()); + + while(bWrong) + { + if(nStart >= nMaxEnd) + { + break; + } + + if(nStart < o3tl::make_unsigned(nIndex)) + { + nStart = nIndex; + } + + if(nEnd > nMaxEnd) + { + nEnd = nMaxEnd; + } + + // add to vector + aWrongSpellVector.emplace_back(nStart, nEnd); + + // goto next index + nStart = nEnd + 1; + + if(nEnd < nMaxEnd) + { + bWrong = pWrongs->NextWrong(nStart, nEnd); + } + else + { + bWrong = false; + } + } + } + } + + const SvxFieldData* pFieldData = nullptr; + + if(PortionKind::FIELD == rTextPortion.GetKind()) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + + if(pFieldItem) + { + pFieldData = pFieldItem->GetField(); + } + } + + // support for EOC, EOW, EOS TEXT comments. To support that, + // the locale is needed. With the locale and a XBreakIterator it is + // possible to re-create the text marking info on primitive level + const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1))); + + // create EOL and EOP bools + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in + // consequence, but also already set at rOutDev) + const Color aOverlineColor(rOutDev.GetOverlineColor()); + + // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in + // consequence, but also already set at rOutDev) + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + // Unicode code points conversion according to ctl text numeral setting + aText = convertDigits(aText, nTextStart, nTextLen, + ImplCalcDigitLang(aTmpFont.GetLanguage())); + + // StripPortions() data callback + GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray, + aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr, + pFieldData, + bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments + &aLocale, + aOverlineColor, + aTextLineColor); + + // #108052# remember that EOP is written already for this ParaPortion + if(bEndOfParagraph) + { + bEndOfParagraphWritten = true; + } + } + else + { + short nEsc = aTmpFont.GetEscapement(); + if ( nOrientation ) + { + // In case of high/low do it yourself: + if ( aTmpFont.GetEscapement() ) + { + tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L; + adjustYDirectionAware(aOutPos, -nDiff); + aRedLineTmpPos = aOutPos; + aTmpFont.SetEscapement( 0 ); + } + + aOrigin.RotateAround(aOutPos, nOrientation); + aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation ); + aTmpFont.SetPhysFont(rOutDev); + + } + + // Take only what begins in the visible range: + // Important, because of a bug in some graphic cards + // when transparent font, output when negative + if ( nOrientation || ( !IsEffectivelyVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) ) + || ( IsEffectivelyVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) ) + { + if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) ) + { + // Paint the high/low without underline, + // Display the Underline on the + // base line of the original font height... + // But only if there was something underlined before! + bool bSpecialUnderline = false; + EditCharAttrib* pPrev = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex ); + if ( pPrev ) + { + SvxFont aDummy; + // Underscore in front? + if ( pPrev->GetStart() ) + { + SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy ); + if ( aDummy.GetUnderline() != LINESTYLE_NONE ) + bSpecialUnderline = true; + } + if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) ) + { + SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy ); + if ( aDummy.GetUnderline() != LINESTYLE_NONE ) + bSpecialUnderline = true; + } + } + if ( bSpecialUnderline ) + { + Size aSz = aTmpFont.GetPhysTxtSize( &rOutDev, aText, nTextStart, nTextLen ); + sal_uInt8 nProp = aTmpFont.GetPropr(); + aTmpFont.SetEscapement( 0 ); + aTmpFont.SetPropr( 100 ); + aTmpFont.SetPhysFont(rOutDev); + OUStringBuffer aBlanks(nTextLen); + comphelper::string::padToLength( aBlanks, nTextLen, ' ' ); + Point aUnderlinePos( aOutPos ); + if ( nOrientation ) + { + aUnderlinePos = aTmpPos; + aOrigin.RotateAround(aUnderlinePos, nOrientation); + } + rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen ); + + aTmpFont.SetUnderline( LINESTYLE_NONE ); + if ( !nOrientation ) + aTmpFont.SetEscapement( nEsc ); + aTmpFont.SetPropr( nProp ); + aTmpFont.SetPhysFont(rOutDev); + } + } + Point aRealOutPos( aOutPos ); + if ( ( rTextPortion.GetKind() == PortionKind::TEXT ) + && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed + && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation ) + { + aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX ); + } + + // RTL portions with (#i37132#) + // compressed blank should not paint this blank: + if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 && + pDXArray[ nTextLen - 1 ] == + pDXArray[ nTextLen - 2 ] && + ' ' == aText[nTextStart + nTextLen - 1] ) + --nTextLen; + + // output directly + aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray ); + + if ( bDrawFrame ) + { + Point aTopLeft( aTmpPos ); + aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); + if ( nOrientation ) + aOrigin.RotateAround(aTopLeft, nOrientation); + tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); + rOutDev.DrawRect( aRect ); + } + + // PDF export: + if ( pPDFExtOutDevData ) + { + if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) ) + { + Point aTopLeft( aTmpPos ); + aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); + + tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation()); + aBookmark.aBookmark = pUrlField->GetURL(); + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); + rBookmarks.push_back( aBookmark ); + } + } + } + } + } + + const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList(); + if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() ) + { + {//#105750# adjust LinePos for superscript or subscript text + short _nEsc = aTmpFont.GetEscapement(); + if( _nEsc ) + { + tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L; + adjustYDirectionAware(aRedLineTmpPos, -nShift); + } + } + Color aOldColor( rOutDev.GetLineColor() ); + rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor ); + lcl_DrawRedLines( rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft() ); + rOutDev.SetLineColor( aOldColor ); + } + } + + rOutDev.Pop(); + + if ( rTextPortion.GetKind() == PortionKind::FIELD ) + { + // add a meta file comment if we record to a metafile + if( bMetafileValid ) + { + const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex); + assert( pAttr && "Field not found" ); + + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + DBG_ASSERT( pFieldItem != nullptr, "Wrong type of field!" ); + + if( pFieldItem ) + { + const SvxFieldData* pFieldData = pFieldItem->GetField(); + if( pFieldData ) + pMtf->AddAction( SvxFieldData::createEndComment() ); + } + } + + } + + } + break; + case PortionKind::TAB: + { + if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) ) + { + SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev ); + aTmpFont.SetTransparent( false ); + aTmpFont.SetEscapement( 0 ); + aTmpFont.SetPhysFont(rOutDev); + tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev, + OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width(); + sal_Int32 nChars = 2; + if( nCharWidth ) + nChars = rTextPortion.GetSize().Width() / nCharWidth; + if ( nChars < 2 ) + nChars = 2; // is compressed by DrawStretchText. + else if ( nChars == 2 ) + nChars = 3; // looks better + + OUStringBuffer aBuf(nChars); + comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue()); + OUString aText(aBuf.makeStringAndClear()); + aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} ); + rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText ); + + if ( bStripOnly ) + { + // create EOL and EOP bools + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + // StripPortions() data callback + GetEditEnginePtr()->DrawingTab( aTmpPos, + rTextPortion.GetSize().Width(), + OUString(rTextPortion.GetExtraValue()), + aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + bEndOfLine, bEndOfParagraph, + aOverlineColor, aTextLineColor); + } + } + else if ( bStripOnly ) + { + // #i108052# When stripping, a callback for _empty_ paragraphs is also needed. + // This was optimized away (by not rendering the space-only tab portion), so do + // it manually here. + const bool bEndOfLine(nPortion == pLine->GetEndPortion()); + const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines); + + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + GetEditEnginePtr()->DrawingText( + aTmpPos, OUString(), 0, 0, {}, {}, + aTmpFont, n, 0, + nullptr, + nullptr, + bEndOfLine, bEndOfParagraph, + nullptr, + aOverlineColor, + aTextLineColor); + } + } + break; + case PortionKind::LINEBREAK: break; + } + if( bParsingFields ) + nPortion--; + else + nIndex = nIndex + rTextPortion.GetLen(); + + } + } + + if ((nLine != nLastLine ) && !maStatus.IsOutliner()) + { + adjustYDirectionAware(aStartPos, nSBL); + } + + // no more visible actions? + if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } + + if (!maStatus.IsOutliner()) + { + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); + adjustYDirectionAware(aStartPos, nUL); + } + + // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion + // EOP is not written, do it now. This will be safer than before. It has shown + // that the reason for #i108052# was fixed/removed again, so this is a try to fix + // the number of paragraphs (and counting empty ones) now independent from the + // changes in EditEngine behaviour. + if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly) + { + const Color aOverlineColor(rOutDev.GetOverlineColor()); + const Color aTextLineColor(rOutDev.GetTextLineColor()); + + GetEditEnginePtr()->DrawingText( + aTmpPos, OUString(), 0, 0, {}, {}, + aTmpFont, n, 0, + nullptr, + nullptr, + false, true, // support for EOL/EOP TEXT comments + nullptr, + aOverlineColor, + aTextLineColor); + } + } + else + { + adjustYDirectionAware(aStartPos, nParaHeight); + } + + if ( pPDFExtOutDevData ) + pPDFExtOutDevData->EndStructureElement(); + + // no more visible actions? + if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } +} + +void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + if ( !IsUpdateLayout() || IsInUndo() ) + return; + + assert( pView && "No View - No Paint!" ); + + // Intersection of paint area and output area. + tools::Rectangle aClipRect( pView->GetOutputArea() ); + aClipRect.Intersection( rRect ); + + OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev(); + + Point aStartPos; + if ( !IsEffectivelyVertical() ) + aStartPos = pView->GetOutputArea().TopLeft(); + else + { + if( IsTopToBottom() ) + aStartPos = pView->GetOutputArea().TopRight(); + else + aStartPos = pView->GetOutputArea().BottomLeft(); + } + adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft())); + adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop())); + + // If Doc-width < Output Area,Width and not wrapped fields, + // the fields usually protrude if > line. + // (Not at the top, since there the Doc-width from formatting is already + // there) + if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) ) + { + tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width(); + if ( aClipRect.Left() > nMaxX ) + return; + if ( aClipRect.Right() > nMaxX ) + aClipRect.SetRight( nMaxX ); + } + + bool bClipRegion = rTarget.IsClipRegion(); + vcl::Region aOldRegion = rTarget.GetClipRegion(); + rTarget.IntersectClipRegion( aClipRect ); + + Paint(rTarget, aClipRect, aStartPos); + + if ( bClipRegion ) + rTarget.SetClipRegion( aOldRegion ); + else + rTarget.SetClipRegion(); + + pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget); +} + +void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos ) +{ + DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " ); + DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" ); + GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode )); + maEditDoc.Insert(nPos, pNode); + if ( IsCallParaInsertedOrDeleted() ) + GetEditEnginePtr()->ParagraphInserted( nPos ); +} + +EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ) +{ + ContentNode* pNode = maEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Invalid Node in SplitContent" ); + DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" ); + DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" ); + EditPaM aPaM( pNode, nSepPos ); + return ImpInsertParaBreak( aPaM ); +} + +EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward ) +{ + ContentNode* pLeftNode = maEditDoc.GetObject( nLeftNode ); + ContentNode* pRightNode = maEditDoc.GetObject( nLeftNode+1 ); + DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents "); + DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents "); + return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward ); +} + +bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate ) +{ + const bool bPrevUpdateLayout = mbUpdateLayout; + const bool mbChanged = (mbUpdateLayout != bUp); + + // When switching from true to false, all selections were visible, + // => paint over + // the other hand, were all invisible => paint + // If !bFormatted, e.g. after SetText, then if UpdateMode=true + // formatting is not needed immediately, probably because more text is coming. + // At latest it is formatted at a Paint/CalcTextWidth. + mbUpdateLayout = bUp; + if ( mbUpdateLayout && ( mbChanged || bForceUpdate ) ) + FormatAndLayout( pCurView ); + return bPrevUpdateLayout; +} + +void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) +{ + ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph ); + DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! "); + if ( !(pPPortion && ( pPPortion->IsVisible() != bShow )) ) + return; + + pPPortion->SetVisible( bShow ); + + if ( !bShow ) + { + // Mark as deleted, so that no selection will end or begin at + // this paragraph... + aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph )); + UpdateSelections(); + // The region below will not be invalidated if UpdateMode = sal_False! + // If anyway, then save as sal_False before SetVisible ! + } + + if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) ) + { + if ( !GetTextRanger() ) + { + if ( pPPortion->IsInvalid() ) + { + CreateLines( nParagraph, 0 ); // 0: No TextRanger + } + else + { + CalcHeight( pPPortion ); + } + nCurTextHeight += pPPortion->GetHeight(); + } + else + { + nCurTextHeight = 0x7fffffff; + } + } + + pPPortion->SetMustRepaint( true ); + if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() ) + { + aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ), + Point( GetPaperSize().Width(), nCurTextHeight ) ); + UpdateViews( GetActiveView() ); + } +} + +EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView ) +{ + DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" ); + if ( GetParaPortions().Count() == 0 ) + return EditSelection(); + aOldPositions.Normalize(); + + EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) ); + + if ( nNewPos >= GetParaPortions().Count() ) + nNewPos = GetParaPortions().Count() - 1; + + // Where the paragraph was inserted it has to be properly redrawn: + // Where the paragraph was removed it has to be properly redrawn: + // ( and correspondingly in between as well...) + if ( pCurView && IsUpdateLayout() ) + { + // in this case one can redraw directly without invalidating the + // Portions + sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); + sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos ); + + ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion ); + ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion ); + if (pUpperPortion && pLowerPortion) + { + aInvalidRect = tools::Rectangle(); // make empty + aInvalidRect.SetLeft( 0 ); + aInvalidRect.SetRight(GetColumnWidth(maPaperSize)); + aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); + aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); + + UpdateViews( pCurView ); + } + } + else + { + // redraw from the upper invalid position + sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos ); + InvalidateFromParagraph( nFirstInvPara ); + } + return aSel; +} + +void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara ) +{ + // The following paragraphs are not invalidated, since ResetHeight() + // => size change => all the following are re-issued anyway. + ParaPortion* pTmpPortion; + if ( nFirstInvPara != 0 ) + { + pTmpPortion = GetParaPortions()[nFirstInvPara-1]; + pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 ); + } + else + { + pTmpPortion = GetParaPortions()[0]; + pTmpPortion->MarkSelectionInvalid( 0 ); + } + pTmpPortion->ResetHeight(); +} + +IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void) +{ + CallStatusHdl(); +} + +void ImpEditEngine::CallStatusHdl() +{ + if ( aStatusHdlLink.IsSet() && bool(maStatus.GetStatusWord()) ) + { + // The Status has to be reset before the Call, + // since other Flags might be set in the handler... + EditStatus aTmpStatus( maStatus ); + maStatus.Clear(); + aStatusHdlLink.Call( aTmpStatus ); + aStatusTimer.Stop(); // If called by hand... + } +} + +ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode ) +{ + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" ); + pPortion = GetPrevVisPortion( pPortion ); + if ( pPortion ) + return pPortion->GetNode(); + return nullptr; +} + +ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode ) +{ + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" ); + pPortion = GetNextVisPortion( pPortion ); + if ( pPortion ) + return pPortion->GetNode(); + return nullptr; +} + +const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const +{ + sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); + DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" ); + const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; + while ( pPortion && !pPortion->IsVisible() ) + pPortion = nPara ? GetParaPortions()[--nPara] : nullptr; + + return pPortion; +} + +const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const +{ + sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); + DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" ); + const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara ); + while ( pPortion && !pPortion->IsVisible() ) + pPortion = GetParaPortions().SafeGetObject( ++nPara ); + + return pPortion; +} + +tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const +{ + tools::Long nTotalOccupiedHeight = 0; + sal_Int32 nTotalLineCount = 0; + const ParaPortionList& rParaPortions = GetParaPortions(); + sal_Int32 nParaCount = rParaPortions.Count(); + + for (sal_Int32 i = 0; i < nParaCount; ++i) + { + if (GetVerJustification(i) != SvxCellVerJustify::Block) + // All paragraphs must have the block justification set. + return 0; + + const ParaPortion* pPortion = rParaPortions[i]; + nTotalOccupiedHeight += pPortion->GetFirstLineOffset(); + + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; + + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); + + const EditLineList& rLines = pPortion->GetLines(); + sal_Int32 nLineCount = rLines.Count(); + nTotalLineCount += nLineCount; + for (sal_Int32 j = 0; j < nLineCount; ++j) + { + const EditLine& rLine = rLines[j]; + nTotalOccupiedHeight += rLine.GetHeight(); + if (j < nLineCount-1) + nTotalOccupiedHeight += nSBL; + nTotalOccupiedHeight += nUL; + } + } + + tools::Long nTotalSpace = getHeightDirectionAware(maPaperSize); + nTotalSpace -= nTotalOccupiedHeight; + if (nTotalSpace <= 0 || nTotalLineCount <= 1) + return 0; + + // Shift the text to the right for the asian layout mode. + if (IsEffectivelyVertical()) + adjustYDirectionAware(rStartPos, -nTotalSpace); + + return nTotalSpace / (nTotalLineCount-1); +} + +EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara ) +{ + EditPaM aPaM; + if ( nPara != 0 ) + { + ContentNode* pNode = GetEditDoc().GetObject( nPara-1 ); + if ( !pNode ) + pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 ); + assert(pNode && "Not a single paragraph in InsertParagraph ?"); + aPaM = EditPaM( pNode, pNode->Len() ); + } + else + { + ContentNode* pNode = GetEditDoc().GetObject( 0 ); + aPaM = EditPaM( pNode, 0 ); + } + + return ImpInsertParaBreak( aPaM ); +} + +std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara ) +{ + std::optional<EditSelection> pSel; + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" ); + if ( pNode ) + pSel.emplace( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) ); + + return pSel; +} + +void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo ) +{ + if (mbDowning) + return; + + if ( IsInUndo() ) + IdleFormatAndLayout( pCurView ); + else + { + if (bCalledFromUndo) + // in order to make bullet points that have had their styles changed, redraw themselves + for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) + GetParaPortions()[nPortion]->MarkInvalid( 0, 0 ); + FormatDoc(); + UpdateViews( pCurView ); + } + + EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); + GetNotifyHdl().Call(aNotify); +} + +void ImpEditEngine::SetFlatMode( bool bFlat ) +{ + if ( bFlat != maStatus.UseCharAttribs() ) + return; + + if ( !bFlat ) + maStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS ); + else + maStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS ); + + maEditDoc.CreateDefFont( !bFlat ); + + FormatFullDoc(); + UpdateViews(); + if ( pActiveView ) + pActiveView->ShowCursor(); +} + +void ImpEditEngine::setScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY) +{ + bool bChanged; + + if (!IsEffectivelyVertical()) + { + bChanged = mfFontScaleX != fFontScaleX || mfFontScaleY != fFontScaleY || + mfSpacingScaleX != fSpacingScaleX || mfSpacingScaleY != fSpacingScaleY; + mfFontScaleX = fFontScaleX; + mfFontScaleY = fFontScaleY; + mfSpacingScaleX = fSpacingScaleX; + mfSpacingScaleY = fSpacingScaleY; + } + else + { + bChanged = mfFontScaleX != fFontScaleY || mfFontScaleY != fFontScaleX || + mfSpacingScaleX != fSpacingScaleY || mfSpacingScaleY != fSpacingScaleX; + mfFontScaleX = fFontScaleY; + mfFontScaleY = fFontScaleX; + mfSpacingScaleX = fSpacingScaleY; + mfSpacingScaleY = fSpacingScaleX; + } + + if (bChanged && maStatus.DoStretch()) + { + FormatFullDoc(); + // (potentially) need everything redrawn + aInvalidRect = tools::Rectangle(0, 0, 1000000, 1000000); + UpdateViews(GetActiveView()); + } +} + +const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const +{ + const SvxNumberFormat *pRes = nullptr; + + if (pNode) + { + // get index of paragraph + sal_Int32 nPara = GetEditDoc().GetPos( pNode ); + DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" ); + if (nPara < EE_PARA_NOT_FOUND) + { + // the called function may be overridden by an OutlinerEditEng + // object to provide + // access to the SvxNumberFormat of the Outliner. + // The EditEngine implementation will just return 0. + pRes = pEditEngine->GetNumberFormat( nPara ); + } + } + + return pRes; +} + +sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth( + const ContentNode *pNode, + sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const +{ + // nSpaceBefore matches the ODF attribute text:space-before + // nMinLabelWidth matches the ODF attribute text:min-label-width + + const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode ); + + // if no number format was found we have no Outliner or the numbering level + // within the Outliner is -1 which means no number format should be applied. + // Thus the default values to be returned are 0. + sal_Int32 nSpaceBefore = 0; + sal_Int32 nMinLabelWidth = 0; + + if (pNumFmt) + { + nMinLabelWidth = -pNumFmt->GetFirstLineOffset(); + nSpaceBefore = pNumFmt->GetAbsLSpace() - nMinLabelWidth; + DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" ); + } + if (pnSpaceBefore) + *pnSpaceBefore = nSpaceBefore; + if (pnMinLabelWidth) + *pnMinLabelWidth = nMinLabelWidth; + + return nSpaceBefore + nMinLabelWidth; +} + +const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode ) +{ + return pNode->GetContentAttribs().GetItem( maStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); +} + +// select a representative text language for the digit type according to the +// text numeral setting: +LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) +{ + if (utl::ConfigManager::IsFuzzing()) + return LANGUAGE_ENGLISH_US; + + // #114278# Also setting up digit language from Svt options + // (cannot reliably inherit the outdev's setting) + + LanguageType eLang = eCurLang; + const SvtCTLOptions::TextNumerals nCTLTextNumerals = SvtCTLOptions::GetCTLTextNumerals(); + + if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals ) + eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; + else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals ) + eLang = LANGUAGE_ENGLISH; + else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals ) + eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + + return eLang; +} + +OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang) +{ + OUStringBuffer aBuf(rString); + for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx) + { + sal_Unicode cChar = aBuf[nIdx]; + if (cChar >= '0' && cChar <= '9') + aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang); + } + return aBuf.makeStringAndClear(); +} + +// Either sets the digit mode at the output device +void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang) +{ + rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang)); +} + +void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex) +{ + bool bCTL = false; + bool bR2L = false; + if ( nIndex == -1 ) + { + bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX ); + bR2L = IsRightToLeft( nPara ); + } + else + { + ContentNode* pNode = GetEditDoc().GetObject( nPara ); + short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) ); + bCTL = nScriptType == i18n::ScriptType::COMPLEX; + // this change was discussed in issue 37190 + bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0; + // it also works for issue 55927 + } + + vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode(); + + // We always use the left position for DrawText() + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl; + + if ( !bCTL && !bR2L) + { + // No Bidi checking necessary + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong; + } + else + { + // Bidi checking necessary + // Don't use BIDI_STRONG, VCL must do some checks. + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong; + + if ( bR2L ) + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + } + + rOutDev.SetLayoutMode( nLayoutMode ); + + // #114278# Also setting up digit language from Svt options + // (cannot reliably inherit the outdev's setting) + LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); + ImplInitDigitMode(rOutDev, eLang); +} + +Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const +{ + if ( !xBI.is() ) + { + Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xBI = i18n::BreakIterator::create( xContext ); + } + return xBI; +} + +Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const +{ + if ( !xISC.is() ) + { + Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xISC = i18n::InputSequenceChecker::create( xContext ); + } + return xISC; +} + +Color ImpEditEngine::GetAutoColor() const +{ + Color aColor; + + if (comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current()) + { + // Get document background color from current view instead + aColor = SfxViewShell::Current()->GetColorConfigColor(svtools::DOCCOLOR); + if (aColor.IsDark()) + aColor = COL_WHITE; + else + aColor = COL_BLACK; + } + else + { + aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + + if ( GetBackgroundColor() != COL_AUTO ) + { + if ( GetBackgroundColor().IsDark() && aColor.IsDark() ) + aColor = COL_WHITE; + else if ( GetBackgroundColor().IsBright() && aColor.IsBright() ) + aColor = COL_BLACK; + } + } + + return aColor; +} + +bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, + TextPortion* pTextPortion, sal_Int32 nStartPos, + sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, + bool bManipulateDXArray) +{ + DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" ); + DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" ); + + // Percent is 1/100 Percent... + if ( n100thPercentFromMax == 10000 ) + pTextPortion->SetExtraInfos( nullptr ); + + bool bCompressed = false; + + if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN ) + { + tools::Long nNewPortionWidth = pTextPortion->GetSize().Width(); + sal_Int32 nPortionLen = pTextPortion->GetLen(); + for ( sal_Int32 n = 0; n < nPortionLen; n++ ) + { + AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) ); + + bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight ); + bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana ); + + // create Extra infos only if needed... + if ( bCompressPunctuation || bCompressKana ) + { + if ( !pTextPortion->GetExtraInfos() ) + { + ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo; + pTextPortion->SetExtraInfos( pExtraInfos ); + pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width(); + pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal; + } + pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax; + pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType; + + tools::Long nOldCharWidth; + if ( (n+1) < nPortionLen ) + { + nOldCharWidth = pDXArray[n]; + } + else + { + if ( bManipulateDXArray ) + nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX; + else + nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth; + } + nOldCharWidth -= ( n ? pDXArray[n-1] : 0 ); + + tools::Long nCompress = 0; + + if ( bCompressPunctuation ) + { + nCompress = nOldCharWidth / 2; + } + else // Kana + { + nCompress = nOldCharWidth / 10; + } + + if ( n100thPercentFromMax != 10000 ) + { + nCompress *= n100thPercentFromMax; + nCompress /= 10000; + } + + if ( nCompress ) + { + bCompressed = true; + nNewPortionWidth -= nCompress; + pTextPortion->GetExtraInfos()->bCompressed = true; + + + // Special handling for rightpunctuation: For the 'compression' we must + // start the output before the normal char position... + if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) ) + { + if ( !pTextPortion->GetExtraInfos()->pOrgDXArray ) + pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 ); + + if ( nType == AsianCompressionFlags::PunctuationRight ) + { + // If it's the first char, I must handle it in Paint()... + if ( n ) + { + // -1: No entry for the last character + for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ ) + pDXArray[i] -= nCompress; + } + else + { + pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true; + pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress; + } + } + else + { + // -1: No entry for the last character + for ( sal_Int32 i = n; i < (nPortionLen-1); i++ ) + pDXArray[i] -= nCompress; + } + } + } + } + } + + if ( bCompressed && ( n100thPercentFromMax == 10000 ) ) + pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth; + + pTextPortion->setWidth(nNewPortionWidth); + + if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) ) + { + // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected + tools::Long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression; + nShrink *= n100thPercentFromMax; + nShrink /= 10000; + tools::Long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink; + if ( nNewWidth < pTextPortion->GetSize().Width() ) + pTextPortion->setWidth(nNewWidth); + } + } + return bCompressed; +} + + +void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth ) +{ + bool bFoundCompressedPortion = false; + tools::Long nCompressed = 0; + std::vector<TextPortion*> aCompressedPortions; + + sal_Int32 nPortion = pLine->GetEndPortion(); + TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) ) + { + if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed ) + { + bFoundCompressedPortion = true; + nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width(); + aCompressedPortions.push_back(pTP); + } + pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr; + } + + if ( !bFoundCompressedPortion ) + return; + + tools::Long nCompressPercent = 0; + if ( nCompressed > nRemainingWidth ) + { + nCompressPercent = nCompressed - nRemainingWidth; + DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" ); + nCompressPercent *= 10000; + nCompressPercent /= nCompressed; + } + + for (TextPortion* pTP2 : aCompressedPortions) + { + pTP = pTP2; + pTP->GetExtraInfos()->bCompressed = false; + pTP->setWidth(pTP->GetExtraInfos()->nOrgWidth); + if ( nCompressPercent ) + { + sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP ); + sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion ); + DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" ); + sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart()); + if ( pTP->GetExtraInfos()->pOrgDXArray ) + memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) ); + ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true ); + } + } +} + +void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight) +{ + tools::Long nY = 0; + tools::Long nPH; + + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { + ParaPortion* pPara = GetParaPortions()[nPara]; + nPH = pPara->GetHeight(); + nY += nPH; + if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing + { + mnOverflowingPara = nPara; + SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara); + ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH); + return; + } + } +} + +void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight, + sal_uInt32 nOverflowingPara, + tools::Long nHeightBeforeOverflowingPara) +{ + tools::Long nY = nHeightBeforeOverflowingPara; + tools::Long nLH; + + ParaPortion *pPara = GetParaPortions()[nOverflowingPara]; + + // Like UpdateOverflowingParaNum but for each line in the first + // overflowing paragraph. + for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) { + // XXX: We must use a reference here because the copy constructor resets the height + EditLine &aLine = pPara->GetLines()[nLine]; + nLH = aLine.GetHeight(); + nY += nLH; + + // Debugging output + if (nLine == 0) { + SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH); + } + + if ( nY > nPaperHeight ) // found first line overflowing + { + mnOverflowingLine = nLine; + SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine); + return; + } + } + + assert(false && "You should never get here"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx new file mode 100644 index 0000000000..57b3d65c54 --- /dev/null +++ b/editeng/source/editeng/impedit4.cxx @@ -0,0 +1,3141 @@ +/* -*- 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 <svl/srchitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/tstpitem.hxx> + +#include "eertfpar.hxx" +#include <editeng/editeng.hxx> +#include "impedit.hxx" +#include <editeng/editview.hxx> +#include "eehtml.hxx" +#include "editobj2.hxx" +#include <i18nlangtag/lang.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> + +#include <editxml.hxx> + +#include <editeng/autokernitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include "textconv.hxx" +#include <rtl/tencinfo.h> +#include <svtools/rtfout.hxx> +#include <tools/stream.hxx> +#include <edtspell.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/linguistic2/XThesaurus.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/textsearch.hxx> +#include <comphelper/processfactory.hxx> +#include <vcl/help.hxx> +#include <vcl/metric.hxx> +#include <svtools/rtfkeywd.hxx> +#include <editeng/edtdlg.hxx> + +#include <cstddef> +#include <memory> +#include <unordered_map> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + + +EditPaM ImpEditEngine::Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs) +{ + bool _bUpdate = SetUpdateLayout( false ); + EditPaM aPaM; + if ( eFormat == EETextFormat::Text ) + aPaM = ReadText( rInput, rSel ); + else if ( eFormat == EETextFormat::Rtf ) + aPaM = ReadRTF( rInput, rSel ); + else if ( eFormat == EETextFormat::Xml ) + aPaM = ReadXML( rInput, rSel ); + else if ( eFormat == EETextFormat::Html ) + aPaM = ReadHTML( rInput, rBaseURL, rSel, pHTTPHeaderAttrs ); + else + { + OSL_FAIL( "Read: Unknown Format" ); + } + + FormatFullDoc(); // perhaps a simple format is enough? + SetUpdateLayout( _bUpdate ); + + return aPaM; +} + +EditPaM ImpEditEngine::ReadText( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + EditPaM aPaM = aSel.Max(); + + OUString aTmpStr; + bool bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() ); + while ( bDone ) + { + aPaM = ImpInsertText( EditSelection( aPaM, aPaM ), aTmpStr ); + aPaM = ImpInsertParaBreak( aPaM ); + bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() ); + } + return aPaM; +} + +EditPaM ImpEditEngine::ReadXML( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + ESelection aESel = CreateESel( aSel ); + + return ::SvxReadXML( *GetEditEnginePtr(), rInput, aESel ); +} + +EditPaM ImpEditEngine::ReadRTF( SvStream& rInput, EditSelection aSel ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + // The SvRTF parser expects the Which-mapping passed on in the pool, not + // dependent on a secondary. + SfxItemPool* pPool = &maEditDoc.GetItemPool(); + while (pPool->GetSecondaryPool() && pPool->GetName() != "EditEngineItemPool") + { + pPool = pPool->GetSecondaryPool(); + } + + DBG_ASSERT(pPool && pPool->GetName() == "EditEngineItemPool", + "ReadRTF: no EditEnginePool!"); + + EditRTFParserRef xPrsr = new EditRTFParser(rInput, aSel, *pPool, pEditEngine); + SvParserState eState = xPrsr->CallParser(); + if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) ) + { + rInput.SetError( EE_READWRITE_WRONGFORMAT ); + return aSel.Min(); + } + return xPrsr->GetCurPaM(); +} + +EditPaM ImpEditEngine::ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + + EditHTMLParserRef xPrsr = new EditHTMLParser( rInput, rBaseURL, pHTTPHeaderAttrs ); + SvParserState eState = xPrsr->CallParser(pEditEngine, aSel.Max()); + if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) ) + { + rInput.SetError( EE_READWRITE_WRONGFORMAT ); + return aSel.Min(); + } + return xPrsr->GetCurSelection().Max(); +} + +void ImpEditEngine::Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel) +{ + if ( !rOutput.IsWritable() ) + rOutput.SetError( SVSTREAM_WRITE_ERROR ); + + if ( rOutput.GetError() ) + return; + + if ( eFormat == EETextFormat::Text ) + WriteText( rOutput, rSel ); + else if ( eFormat == EETextFormat::Rtf ) + WriteRTF( rOutput, rSel ); + else if ( eFormat == EETextFormat::Xml ) + WriteXML( rOutput, rSel ); + else if ( eFormat == EETextFormat::Html ) + ; + else + { + OSL_FAIL( "Write: Unknown Format" ); + } +} + +ErrCode ImpEditEngine::WriteText( SvStream& rOutput, EditSelection aSel ) +{ + sal_Int32 nStartNode, nEndNode; + bool bRange = aSel.HasRange(); + if ( bRange ) + { + aSel.Adjust( maEditDoc ); + nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + } + else + { + nStartNode = 0; + nEndNode = maEditDoc.Count()-1; + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + if ( bRange ) + { + if ( nNode == nStartNode ) + nStartPos = aSel.Min().GetIndex(); + if ( nNode == nEndNode ) // can also be == nStart! + nEndPos = aSel.Max().GetIndex(); + } + OUString aTmpStr = EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ); + rOutput.WriteByteStringLine( aTmpStr, rOutput.GetStreamCharSet() ); + } + + return rOutput.GetError(); +} + +bool ImpEditEngine::WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ) +{ + const SfxPoolItem* pAttrItem = rLst.First(); + while ( pAttrItem ) + { + WriteItemAsRTF( *pAttrItem, rOutput, nPara, nPos,rFontTable, rColorList ); + pAttrItem = rLst.Next(); + } + return rLst.Count() != 0; +} + +static void lcl_FindValidAttribs( ItemList& rLst, ContentNode* pNode, sal_Int32 nIndex, sal_uInt16 nScriptType ) +{ + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + while ( pAttr && ( pAttr->GetStart() <= nIndex ) ) + { + // Start is checked in while ... + if ( pAttr->GetEnd() > nIndex ) + { + if ( IsScriptItemValid( pAttr->GetItem()->Which(), nScriptType ) ) + rLst.Insert( pAttr->GetItem() ); + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } +} + +void ImpEditEngine::WriteXML(SvStream& rOutput, const EditSelection& rSel) +{ + ESelection aESel = CreateESel(rSel); + + SvxWriteXML( *GetEditEnginePtr(), rOutput, aESel ); +} + +ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel ) +{ + assert( IsUpdateLayout() && "WriteRTF for UpdateMode = sal_False!" ); + CheckIdleFormatter(); + if ( !IsFormatted() ) + FormatDoc(); + + sal_Int32 nStartNode, nEndNode; + aSel.Adjust( maEditDoc ); + + nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + // RTF header ... + rOutput.WriteChar( '{' ) ; + + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RTF ); + + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ANSI ); + rtl_TextEncoding eDestEnc = RTL_TEXTENCODING_MS_1252; + + // Generate and write out Font table ... + std::vector<std::unique_ptr<SvxFontItem>> aFontTable; + // default font must be up front, so DEF font in RTF + aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO ) ) ); + aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CJK ) ) ); + aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CTL ) ) ); + for ( sal_uInt16 nScriptType = 0; nScriptType < 3; nScriptType++ ) + { + sal_uInt16 nWhich = EE_CHAR_FONTINFO; + if ( nScriptType == 1 ) + nWhich = EE_CHAR_FONTINFO_CJK; + else if ( nScriptType == 2 ) + nWhich = EE_CHAR_FONTINFO_CTL; + + for (const SfxPoolItem* pItem : maEditDoc.GetItemPool().GetItemSurrogates(nWhich)) + { + SvxFontItem const*const pFontItem = static_cast<const SvxFontItem*>(pItem); + bool bAlreadyExist = false; + size_t nTestMax = nScriptType ? aFontTable.size() : 1; + for ( size_t nTest = 0; !bAlreadyExist && ( nTest < nTestMax ); nTest++ ) + { + bAlreadyExist = *aFontTable[ nTest ] == *pFontItem; + } + + if ( !bAlreadyExist ) + aFontTable.emplace_back( new SvxFontItem( *pFontItem ) ); + } + } + + rOutput << endl; + rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_FONTTBL ); + for ( std::vector<SvxFontItem*>::size_type j = 0; j < aFontTable.size(); j++ ) + { + SvxFontItem* pFontItem = aFontTable[ j ].get(); + rOutput.WriteChar( '{' ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_F ); + rOutput.WriteNumberAsString( j ); + switch ( pFontItem->GetFamily() ) + { + case FAMILY_DONTKNOW: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FNIL ); + break; + case FAMILY_DECORATIVE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FDECOR ); + break; + case FAMILY_MODERN: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FMODERN ); + break; + case FAMILY_ROMAN: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FROMAN ); + break; + case FAMILY_SCRIPT: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FSCRIPT ); + break; + case FAMILY_SWISS: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FSWISS ); + break; + default: + break; + } + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FPRQ ); + sal_uInt16 nVal = 0; + switch( pFontItem->GetPitch() ) + { + case PITCH_FIXED: nVal = 1; break; + case PITCH_VARIABLE: nVal = 2; break; + default: + break; + } + rOutput.WriteNumberAsString( nVal ); + + rtl_TextEncoding eChrSet = pFontItem->GetCharSet(); + // tdf#47679 OpenSymbol is not encoded in Symbol Encoding + // and anyway we always attempt to write as eDestEnc + // of RTL_TEXTENCODING_MS_1252 and pay no attention + // on export what encoding we claim to use for these + // fonts. + if (IsOpenSymbol(pFontItem->GetFamilyName())) + { + SAL_WARN_IF(eChrSet == RTL_TEXTENCODING_SYMBOL, "editeng", "OpenSymbol should not have charset of RTL_TEXTENCODING_SYMBOL in new documents"); + eChrSet = RTL_TEXTENCODING_UTF8; + } + DBG_ASSERT( eChrSet != 9, "SystemCharSet?!" ); + if( RTL_TEXTENCODING_DONTKNOW == eChrSet ) + eChrSet = osl_getThreadTextEncoding(); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FCHARSET ); + rOutput.WriteNumberAsString( rtl_getBestWindowsCharsetFromTextEncoding( eChrSet ) ); + + rOutput.WriteChar( ' ' ); + RTFOutFuncs::Out_String( rOutput, pFontItem->GetFamilyName(), eDestEnc ); + rOutput.WriteOString( ";}" ); + } + rOutput.WriteChar( '}' ); + rOutput << endl; + + // Write out ColorList ... + SvxColorList aColorList; + // COL_AUTO should be the default color, always put it first + aColorList.emplace_back(COL_AUTO); + SvxColorItem const& rDefault(maEditDoc.GetItemPool().GetDefaultItem(EE_CHAR_COLOR)); + if (rDefault.GetValue() != COL_AUTO) // is the default always AUTO? + { + aColorList.push_back(rDefault.GetValue()); + } + for (const SfxPoolItem* pItem : maEditDoc.GetItemPool().GetItemSurrogates(EE_CHAR_COLOR)) + { + auto pColorItem(dynamic_cast<SvxColorItem const*>(pItem)); + if (pColorItem && pColorItem->GetValue() != COL_AUTO) // may be null! + { + aColorList.push_back(pColorItem->GetValue()); + } + } + + rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_COLORTBL ); + for ( SvxColorList::size_type j = 0; j < aColorList.size(); j++ ) + { + Color const color = aColorList[j]; + if (color != COL_AUTO) // auto is represented by "empty" element + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RED ); + rOutput.WriteNumberAsString( color.GetRed() ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_GREEN ); + rOutput.WriteNumberAsString( color.GetGreen() ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_BLUE ); + rOutput.WriteNumberAsString( color.GetBlue() ); + } + rOutput.WriteChar( ';' ); + } + rOutput.WriteChar( '}' ); + rOutput << endl; + + std::unordered_map<SfxStyleSheetBase*, sal_uInt32> aStyleSheetToIdMap; + // StyleSheets... + if ( GetStyleSheetPool() ) + { + std::shared_ptr<SfxStyleSheetIterator> aSSSIterator = std::make_shared<SfxStyleSheetIterator>(GetStyleSheetPool(), + SfxStyleFamily::All); + // fill aStyleSheetToIdMap + sal_uInt32 nId = 1; + for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle; + pStyle = aSSSIterator->Next() ) + { + aStyleSheetToIdMap[pStyle] = nId; + nId++; + } + + if ( aSSSIterator->Count() ) + { + + sal_uInt32 nStyle = 0; + rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_STYLESHEET ); + + for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle; + pStyle = aSSSIterator->Next() ) + { + + rOutput << endl; + rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_S ); + sal_uInt32 nNumber = nStyle + 1; + rOutput.WriteNumberAsString( nNumber ); + + // Attribute, also from Parent! + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + if ( pStyle->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pStyle->GetItemSet().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList ); + } + } + + // Parent ... (only if necessary) + if ( !pStyle->GetParent().isEmpty() && ( pStyle->GetParent() != pStyle->GetName() ) ) + { + SfxStyleSheet* pParent = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetParent(), pStyle->GetFamily() )); + DBG_ASSERT( pParent, "Parent not found!" ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SBASEDON ); + nNumber = aStyleSheetToIdMap.find(pParent)->second; + rOutput.WriteNumberAsString( nNumber ); + } + + // Next Style... (more) + // we assume that we have only SfxStyleSheet in the pool + SfxStyleSheet* pNext = static_cast<SfxStyleSheet*>(pStyle); + if ( !pStyle->GetFollow().isEmpty() && ( pStyle->GetFollow() != pStyle->GetName() ) ) + pNext = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetFollow(), pStyle->GetFamily() )); + + DBG_ASSERT( pNext, "Next not found!" ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SNEXT ); + nNumber = aStyleSheetToIdMap.find(pNext)->second; + rOutput.WriteNumberAsString( nNumber ); + + // Name of the template... + rOutput.WriteOString( " " ); + RTFOutFuncs::Out_String( rOutput, pStyle->GetName(), eDestEnc ); + rOutput.WriteOString( ";}" ); + nStyle++; + } + rOutput.WriteChar( '}' ); + rOutput << endl; + } + } + + // Write the pool defaults in advance ... + rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_IGNORE ).WriteOString( "\\EditEnginePoolDefaults" ); + for ( sal_uInt16 nPoolDefItem = EE_PARA_START; nPoolDefItem <= EE_CHAR_END; nPoolDefItem++) + { + const SfxPoolItem& rItem = maEditDoc.GetItemPool().GetDefaultItem( nPoolDefItem ); + WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList ); + } + rOutput.WriteChar( '}' ) << endl; + + // DefTab: + MapMode aTwpMode( MapUnit::MapTwip ); + sal_uInt16 nDefTabTwps = static_cast<sal_uInt16>(GetRefDevice()->LogicToLogic( + Point( maEditDoc.GetDefTab(), 0 ), + &GetRefMapMode(), &aTwpMode ).X()); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_DEFTAB ); + rOutput.WriteNumberAsString( nDefTabTwps ); + rOutput << endl; + + // iterate over the paragraphs ... + rOutput.WriteChar( '{' ) << endl; + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + // The paragraph attributes in advance ... + bool bAttr = false; + + // Template? + if ( pNode->GetStyleSheet() ) + { + // Number of template + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_S ); + sal_uInt32 nNumber = aStyleSheetToIdMap.find(pNode->GetStyleSheet())->second; + rOutput.WriteNumberAsString( nNumber ); + + // All Attribute + // Attribute, also from Parent! + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + if ( pNode->GetStyleSheet()->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetStyleSheet()->GetItemSet().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList ); + bAttr = true; + } + } + } + + for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ ) + { + // Now where stylesheet processing, only hard paragraph attributes! + if ( pNode->GetContentAttribs().GetItems().GetItemState( nParAttr ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nParAttr ); + WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList ); + bAttr = true; + } + } + if ( bAttr ) + rOutput.WriteChar( ' ' ); // Separator + + ItemList aAttribItems; + ParaPortion* pParaPortion = FindParaPortion( pNode ); + DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" ); + + sal_Int32 nIndex = 0; + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + sal_Int32 nStartPortion = 0; + sal_Int32 nEndPortion = pParaPortion->GetTextPortions().Count() - 1; + bool bFinishPortion = false; + sal_Int32 nPortionStart; + + if ( nNode == nStartNode ) + { + nStartPos = aSel.Min().GetIndex(); + nStartPortion = pParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart ); + if ( nStartPos != 0 ) + { + aAttribItems.Clear(); + lcl_FindValidAttribs( aAttribItems, pNode, nStartPos, GetI18NScriptType( EditPaM( pNode, 0 ) ) ); + if ( aAttribItems.Count() ) + { + // These attributes may not apply to the entire paragraph: + rOutput.WriteChar( '{' ); + WriteItemListAsRTF( aAttribItems, rOutput, nNode, nStartPos, aFontTable, aColorList ); + bFinishPortion = true; + } + aAttribItems.Clear(); + } + } + if ( nNode == nEndNode ) // can also be == nStart! + { + nEndPos = aSel.Max().GetIndex(); + nEndPortion = pParaPortion->GetTextPortions().FindPortion( nEndPos, nPortionStart ); + } + + const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature(nIndex); + // start at 0, so the index is right ... + for ( sal_Int32 n = 0; n <= nEndPortion; n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + if ( n < nStartPortion ) + { + nIndex = nIndex + rTextPortion.GetLen(); + continue; + } + + if ( pNextFeature && ( pNextFeature->GetStart() == nIndex ) && ( pNextFeature->GetItem()->Which() != EE_FEATURE_FIELD ) ) + { + WriteItemAsRTF( *pNextFeature->GetItem(), rOutput, nNode, nIndex, aFontTable, aColorList ); + pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 ); + } + else + { + aAttribItems.Clear(); + sal_uInt16 nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) ); + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + if ( !n || IsScriptChange( EditPaM( pNode, nIndex ) ) ) + { + SfxItemSet aAttribs = GetAttribs( nNode, nIndex+1, nIndex+1 ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ) ) ); + aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ) ) ); + } + // Insert hard attribs AFTER CJK attribs... + lcl_FindValidAttribs( aAttribItems, pNode, nIndex, nScriptTypeI18N ); + + rOutput.WriteChar( '{' ); + if ( WriteItemListAsRTF( aAttribItems, rOutput, nNode, nIndex, aFontTable, aColorList ) ) + rOutput.WriteChar( ' ' ); + + sal_Int32 nS = nIndex; + sal_Int32 nE = nIndex + rTextPortion.GetLen(); + if ( n == nStartPortion ) + nS = nStartPos; + if ( n == nEndPortion ) + nE = nEndPos; + + OUString aRTFStr = EditDoc::GetParaAsString( pNode, nS, nE); + RTFOutFuncs::Out_String( rOutput, aRTFStr, eDestEnc ); + rOutput.WriteChar( '}' ); + } + if ( bFinishPortion ) + { + rOutput.WriteChar( '}' ); + bFinishPortion = false; + } + + nIndex = nIndex + rTextPortion.GetLen(); + } + + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_PAR ).WriteOString( OOO_STRING_SVTOOLS_RTF_PARD ).WriteOString( OOO_STRING_SVTOOLS_RTF_PLAIN ); + rOutput << endl; + } + // RTF-trailer ... + rOutput.WriteOString( "}}" ); // 1xparentheses paragraphs, 1xparentheses RTF document + + aFontTable.clear(); + + return rOutput.GetError(); +} + + +void ImpEditEngine::WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos, + std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList ) +{ + sal_uInt16 nWhich = rItem.Which(); + switch ( nWhich ) + { + case EE_PARA_WRITINGDIR: + { + const SvxFrameDirectionItem& rWritingMode = static_cast<const SvxFrameDirectionItem&>(rItem); + if ( rWritingMode.GetValue() == SvxFrameDirection::Horizontal_RL_TB ) + rOutput.WriteOString( "\\rtlpar" ); + else + rOutput.WriteOString( "\\ltrpar" ); + } + break; + case EE_PARA_OUTLLEVEL: + { + sal_Int32 nLevel = static_cast<const SfxInt16Item&>(rItem).GetValue(); + if( nLevel >= 0 ) + { + rOutput.WriteOString( "\\level" ); + rOutput.WriteNumberAsString( nLevel ); + } + } + break; + case EE_PARA_OUTLLRSPACE: + case EE_PARA_LRSPACE: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FI ); + sal_Int32 nTxtFirst = static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOffset(); + nTxtFirst = LogicToTwips( nTxtFirst ); + rOutput.WriteNumberAsString( nTxtFirst ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_LI ); + sal_uInt32 nTxtLeft = static_cast< sal_uInt32 >(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft()); + nTxtLeft = static_cast<sal_uInt32>(LogicToTwips( nTxtLeft )); + rOutput.WriteNumberAsString( nTxtLeft ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RI ); + sal_uInt32 nTxtRight = static_cast<const SvxLRSpaceItem&>(rItem).GetRight(); + nTxtRight = LogicToTwips( nTxtRight); + rOutput.WriteNumberAsString( nTxtRight ); + } + break; + case EE_PARA_ULSPACE: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SB ); + sal_uInt32 nUpper = static_cast<const SvxULSpaceItem&>(rItem).GetUpper(); + nUpper = static_cast<sal_uInt32>(LogicToTwips( nUpper )); + rOutput.WriteNumberAsString( nUpper ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SA ); + sal_uInt32 nLower = static_cast<const SvxULSpaceItem&>(rItem).GetLower(); + nLower = LogicToTwips( nLower ); + rOutput.WriteNumberAsString( nLower ); + } + break; + case EE_PARA_SBL: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SL ); + sal_Int32 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetLineHeight(); + char cMult = '0'; + if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) + { + // From where do I get the value now? + // The SwRTF parser is based on a 240 Font! + nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace(); + nVal *= 240; + nVal /= 100; + cMult = '1'; + } + rOutput.WriteNumberAsString( nVal ); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SLMULT ).WriteChar( cMult ); + } + break; + case EE_PARA_JUST: + { + SvxAdjust eJustification = static_cast<const SvxAdjustItem&>(rItem).GetAdjust(); + switch ( eJustification ) + { + case SvxAdjust::Center: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QC ); + break; + case SvxAdjust::Right: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QR ); + break; + default: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QL ); + break; + } + } + break; + case EE_PARA_TABS: + { + const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem); + for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ ) + { + const SvxTabStop& rTab = rTabs[i]; + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_TX ); + rOutput.WriteNumberAsString( LogicToTwips( rTab.GetTabPos() ) ); + } + } + break; + case EE_CHAR_COLOR: + { + SvxColorList::const_iterator const iter = std::find( + rColorList.begin(), rColorList.end(), + static_cast<SvxColorItem const&>(rItem).GetValue()); + assert(iter != rColorList.end()); + sal_uInt32 const n = iter - rColorList.begin(); + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_CF ); + rOutput.WriteNumberAsString( n ); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + sal_uInt32 n = 0; + for (size_t i = 0; i < rFontTable.size(); ++i) + { + if (*rFontTable[i] == rItem) + { + n = i; + break; + } + } + + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_F ); + rOutput.WriteNumberAsString( n ); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FS ); + sal_Int32 nHeight = static_cast<const SvxFontHeightItem&>(rItem).GetHeight(); + nHeight = LogicToTwips( nHeight ); + // Twips => HalfPoints + nHeight /= 10; + rOutput.WriteNumberAsString( nHeight ); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + FontWeight e = static_cast<const SvxWeightItem&>(rItem).GetWeight(); + switch ( e ) + { + case WEIGHT_BOLD: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_B ); break; + default: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_B ).WriteChar( '0' ); break; + } + } + break; + case EE_CHAR_UNDERLINE: + { + // Must underlined if in WordLineMode, but the information is + // missing here + FontLineStyle e = static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle(); + switch ( e ) + { + case LINESTYLE_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULNONE ); break; + case LINESTYLE_SINGLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_UL ); break; + case LINESTYLE_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULDB ); break; + case LINESTYLE_DOTTED: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULD ); break; + default: + break; + } + } + break; + case EE_CHAR_OVERLINE: + { + FontLineStyle e = static_cast<const SvxOverlineItem&>(rItem).GetLineStyle(); + switch ( e ) + { + case LINESTYLE_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLNONE ); break; + case LINESTYLE_SINGLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OL ); break; + case LINESTYLE_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLDB ); break; + case LINESTYLE_DOTTED: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLD ); break; + default: + break; + } + } + break; + case EE_CHAR_STRIKEOUT: + { + FontStrikeout e = static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout(); + switch ( e ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_STRIKE ); break; + case STRIKEOUT_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_STRIKE ).WriteChar( '0' ); break; + default: + break; + } + } + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + FontItalic e = static_cast<const SvxPostureItem&>(rItem).GetPosture(); + switch ( e ) + { + case ITALIC_OBLIQUE: + case ITALIC_NORMAL: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_I ); break; + case ITALIC_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_I ).WriteChar( '0' ); break; + default: + break; + } + } + break; + case EE_CHAR_OUTLINE: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OUTL ); + if ( !static_cast<const SvxContourItem&>(rItem).GetValue() ) + rOutput.WriteChar( '0' ); + } + break; + case EE_CHAR_RELIEF: + { + FontRelief nRelief = static_cast<const SvxCharReliefItem&>(rItem).GetValue(); + if ( nRelief == FontRelief::Embossed ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_EMBO ); + if ( nRelief == FontRelief::Engraved ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_IMPR ); + } + break; + case EE_CHAR_EMPHASISMARK: + { + FontEmphasisMark nMark = static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark(); + if ( nMark == FontEmphasisMark::NONE ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCNONE ); + else if ( nMark == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove) ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCCOMMA ); + else + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCDOT ); + } + break; + case EE_CHAR_SHADOW: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SHAD ); + if ( !static_cast<const SvxShadowedItem&>(rItem).GetValue() ) + rOutput.WriteChar( '0' ); + } + break; + case EE_FEATURE_TAB: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_TAB ); + } + break; + case EE_FEATURE_LINEBR: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SL ); + } + break; + case EE_CHAR_KERNING: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_EXPNDTW ); + rOutput.WriteNumberAsString( LogicToTwips( + static_cast<const SvxKerningItem&>(rItem).GetValue() ) ); + } + break; + case EE_CHAR_PAIRKERNING: + { + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_KERNING ); + rOutput.WriteNumberAsString( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ? 1 : 0 ); + } + break; + case EE_CHAR_ESCAPEMENT: + { + SvxFont aFont; + ContentNode* pNode = maEditDoc.GetObject( nPara ); + SeekCursor( pNode, nPos, aFont ); + MapMode aPntMode( MapUnit::MapPoint ); + tools::Long nFontHeight = GetRefDevice()->LogicToLogic( + aFont.GetFontSize(), &GetRefMapMode(), &aPntMode ).Height(); + nFontHeight *=2; // Half Points + sal_uInt16 const nProp = static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight(); + sal_uInt16 nProp100 = nProp*100; // For SWG-Token Prop in 100th percent. + short nEsc = static_cast<const SvxEscapementItem&>(rItem).GetEsc(); + const FontMetric& rFontMetric = GetRefDevice()->GetFontMetric(); + double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent(); + double fAutoAscent = .8; + double fAutoDescent = .2; + if ( fFontHeight ) + { + fAutoAscent = rFontMetric.GetAscent() / fFontHeight; + fAutoDescent = rFontMetric.GetDescent() / fFontHeight; + } + if ( nEsc == DFLT_ESC_AUTO_SUPER ) + { + nEsc = fAutoAscent * (100 - nProp); + nProp100++; // A 1 afterwards means 'automatic'. + } + else if ( nEsc == DFLT_ESC_AUTO_SUB ) + { + nEsc = fAutoDescent * -(100 - nProp); + nProp100++; + } + // SWG: + if ( nEsc ) + { + rOutput.WriteOString( "{\\*\\updnprop" ).WriteNumberAsString( + nProp100 ).WriteChar( '}' ); + } + tools::Long nUpDown = nFontHeight * std::abs( nEsc ) / 100; + if ( nEsc < 0 ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_DN ); + else if ( nEsc > 0 ) + rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_UP ); + rOutput.WriteNumberAsString(nUpDown); + } + break; + case EE_CHAR_CASEMAP: + { + const SvxCaseMapItem& rCaseMap = static_cast<const SvxCaseMapItem&>(rItem); + switch (rCaseMap.GetValue()) + { + case SvxCaseMap::SmallCaps: + rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_SCAPS); + break; + case SvxCaseMap::Uppercase: + rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_CAPS); + break; + default: // Something that rtf does not support + rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_SCAPS); + rOutput.WriteNumberAsString(0); + rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_CAPS); + rOutput.WriteNumberAsString(0); + break; + } + } + break; + } +} + +std::unique_ptr<EditTextObject> ImpEditEngine::GetEmptyTextObject() +{ + EditSelection aEmptySel; + aEmptySel.Min() = maEditDoc.GetStartPaM(); + aEmptySel.Max() = maEditDoc.GetStartPaM(); + + return CreateTextObject( aEmptySel ); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject() +{ + EditSelection aCompleteSelection; + aCompleteSelection.Min() = maEditDoc.GetStartPaM(); + aCompleteSelection.Max() = maEditDoc.GetEndPaM(); + + return CreateTextObject( aCompleteSelection ); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject(const EditSelection& rSel) +{ + return CreateTextObject(rSel, GetEditTextObjectPool(), maStatus.AllowBigObjects(), mnBigTextObjectStart); +} + +std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart ) +{ + sal_Int32 nStartNode, nEndNode; + sal_Int32 nTextPortions = 0; + + aSel.Adjust( maEditDoc ); + nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + bool bOnlyFullParagraphs = !( aSel.Min().GetIndex() || + ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) ); + + // Templates are not saved! + // (Only the name and family, template itself must be in App!) + + const MapUnit eMapUnit = maEditDoc.GetItemPool().GetMetric(DEF_METRIC); + auto pTxtObj(std::make_unique<EditTextObjectImpl>(pPool, eMapUnit, GetVertical(), GetRotation(), + GetItemScriptType(aSel))); + + // iterate over the paragraphs ... + sal_Int32 nNode; + for ( nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + DBG_ASSERT( pNode, "Node not found: Search&Replace" ); + + if ( bOnlyFullParagraphs ) + { + const ParaPortion* pParaPortion = GetParaPortions()[nNode]; + nTextPortions += pParaPortion->GetTextPortions().Count(); + } + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->Len(); + + bool bEmptyPara = nEndPos == 0; + + if ( ( nNode == nStartNode ) && !bOnlyFullParagraphs ) + nStartPos = aSel.Min().GetIndex(); + if ( ( nNode == nEndNode ) && !bOnlyFullParagraphs ) + nEndPos = aSel.Max().GetIndex(); + + + ContentInfo *pC = pTxtObj->CreateAndInsertContent(); + + // The paragraph attributes ... + pC->GetParaAttribs().Set( pNode->GetContentAttribs().GetItems() ); + + // The StyleSheet... + if ( pNode->GetStyleSheet() ) + { + pC->SetStyle(pNode->GetStyleSheet()->GetName()); + pC->SetFamily(pNode->GetStyleSheet()->GetFamily()); + } + + // The Text... + pC->SetText(pNode->Copy(nStartPos, nEndPos-nStartPos)); + auto& rCAttriblist = pC->GetCharAttribs(); + + // and the Attribute... + std::size_t nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + rCAttriblist.reserve(rCAttriblist.size() + pNode->GetCharAttribs().GetAttribs().size()); + while ( pAttr ) + { + // In a blank paragraph keep the attributes! + if ( bEmptyPara || + ( ( pAttr->GetEnd() > nStartPos ) && ( pAttr->GetStart() < nEndPos ) ) ) + { + XEditAttribute aX = pTxtObj->CreateAttrib(*pAttr->GetItem(), pAttr->GetStart(), pAttr->GetEnd()); + // Possibly Correct ... + if ( ( nNode == nStartNode ) && ( nStartPos != 0 ) ) + { + aX.GetStart() = ( aX.GetStart() > nStartPos ) ? aX.GetStart()-nStartPos : 0; + aX.GetEnd() = aX.GetEnd() - nStartPos; + + } + if ( nNode == nEndNode ) + { + if ( aX.GetEnd() > (nEndPos-nStartPos) ) + aX.GetEnd() = nEndPos-nStartPos; + } + DBG_ASSERT( aX.GetEnd() <= (nEndPos-nStartPos), "CreateTextObject: Attribute too long!" ); + if ( aX.GetLen() || bEmptyPara ) + rCAttriblist.push_back(std::move(aX)); + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + + // If possible online spelling + if ( bAllowBigObjects && bOnlyFullParagraphs && pNode->GetWrongList() ) + pC->SetWrongList( pNode->GetWrongList()->Clone() ); + + } + + // Remember the portions info in case of large text objects: + // sleeper set up when Olli paragraphs not hacked! + if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && IsUpdateLayout() && ( nTextPortions >= nBigObjectStart ) ) + { + XParaPortionList* pXList = new XParaPortionList(GetRefDevice(), GetColumnWidth(maPaperSize), mfFontScaleX, mfFontScaleY, mfSpacingScaleX, mfSpacingScaleY); + pTxtObj->SetPortionInfo(std::unique_ptr<XParaPortionList>(pXList)); + for ( nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + const ParaPortion* pParaPortion = GetParaPortions()[nNode]; + XParaPortion* pX = new XParaPortion; + pXList->push_back(pX); + + pX->nHeight = pParaPortion->GetHeight(); + pX->nFirstLineOffset = pParaPortion->GetFirstLineOffset(); + + // The TextPortions + sal_uInt16 nCount = pParaPortion->GetTextPortions().Count(); + sal_uInt16 n; + for ( n = 0; n < nCount; n++ ) + { + const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n]; + TextPortion* pNew = new TextPortion( rTextPortion ); + pX->aTextPortions.Append(pNew); + } + + // The lines + nCount = pParaPortion->GetLines().Count(); + for ( n = 0; n < nCount; n++ ) + { + const EditLine& rLine = pParaPortion->GetLines()[n]; + EditLine* pNew = rLine.Clone(); + pX->aLines.Append(pNew); + } +#ifdef DBG_UTIL + sal_uInt16 nTest; + int nTPLen = 0, nTxtLen = 0; + for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; ) + nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen(); + for ( nTest = pParaPortion->GetLines().Count(); nTest; ) + nTxtLen += pParaPortion->GetLines()[--nTest].GetLen(); + DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "CreateBinTextObject: ParaPortion not completely formatted!" ); +#endif + } + } + return pTxtObj; +} + +void ImpEditEngine::SetText( const EditTextObject& rTextObject ) +{ + // Since setting a text object is not undo-able! + ResetUndoManager(); + bool _bUpdate = IsUpdateLayout(); + bool _bUndo = IsUndoEnabled(); + + SetText( OUString() ); + EditPaM aPaM = maEditDoc.GetStartPaM(); + + SetUpdateLayout( false ); + EnableUndo( false ); + + InsertText( rTextObject, EditSelection( aPaM, aPaM ) ); + SetVertical(rTextObject.GetVertical()); + SetRotation(rTextObject.GetRotation()); + + DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" ); + SetUpdateLayout( _bUpdate ); + EnableUndo( _bUndo ); +} + +EditSelection ImpEditEngine::InsertText( const EditTextObject& rTextObject, EditSelection aSel ) +{ + aSel.Adjust( maEditDoc ); + if ( aSel.HasRange() ) + aSel = ImpDeleteSelection( aSel ); + EditSelection aNewSel = InsertTextObject( rTextObject, aSel.Max() ); + return aNewSel; +} + +EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject, EditPaM aPaM ) +{ + // Optimize: No getPos undFindParaportion, instead calculate index! + EditSelection aSel( aPaM, aPaM ); + DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "InsertBibTextObject: Selection broken!(1)" ); + + bool bUsePortionInfo = false; + const EditTextObjectImpl& rTextObjectImpl = toImpl(rTextObject); + XParaPortionList* pPortionInfo = rTextObjectImpl.GetPortionInfo(); + + if (pPortionInfo && ( static_cast<tools::Long>(pPortionInfo->GetPaperWidth()) == GetColumnWidth(maPaperSize)) + && pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode() + && pPortionInfo->getFontScaleX() == mfFontScaleX + && pPortionInfo->getFontScaleY() == mfFontScaleY + && pPortionInfo->getSpacingScaleX() == mfSpacingScaleX + && pPortionInfo->getSpacingScaleY() == mfSpacingScaleY) + { + if ( (pPortionInfo->GetRefDevPtr() == GetRefDevice()) || + (pPortionInfo->RefDevIsVirtual() && GetRefDevice()->IsVirtual()) ) + bUsePortionInfo = true; + } + + bool bConvertMetricOfItems = false; + MapUnit eSourceUnit = MapUnit(), eDestUnit = MapUnit(); + if (rTextObjectImpl.HasMetric()) + { + eSourceUnit = rTextObjectImpl.GetMetric(); + eDestUnit = maEditDoc.GetItemPool().GetMetric( DEF_METRIC ); + if ( eSourceUnit != eDestUnit ) + bConvertMetricOfItems = true; + } + + // Before, paragraph count was of type sal_uInt16 so if nContents exceeded + // 0xFFFF this wouldn't have worked anyway, given that nPara is used to + // number paragraphs and is fearlessly incremented. + sal_Int32 nContents = static_cast<sal_Int32>(rTextObjectImpl.GetContents().size()); + SAL_WARN_IF( nContents < 0, "editeng", "ImpEditEngine::InsertTextObject - contents overflow " << nContents); + sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() ); + + for (sal_Int32 n = 0; n < nContents; ++n, ++nPara) + { + const ContentInfo* pC = rTextObjectImpl.GetContents()[n].get(); + bool bNewContent = aPaM.GetNode()->Len() == 0; + const sal_Int32 nStartPos = aPaM.GetIndex(); + + aPaM = ImpFastInsertText( aPaM, pC->GetText() ); + + ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() ); + DBG_ASSERT( pPortion, "Blind Portion in FastInsertText" ); + pPortion->MarkInvalid( nStartPos, pC->GetText().getLength() ); + + // Character attributes ... + bool bAllreadyHasAttribs = aPaM.GetNode()->GetCharAttribs().Count() != 0; + size_t nNewAttribs = pC->GetCharAttribs().size(); + if ( nNewAttribs ) + { + bool bUpdateFields = false; + for (size_t nAttr = 0; nAttr < nNewAttribs; ++nAttr) + { + const XEditAttribute& rX = pC->GetCharAttribs()[nAttr]; + // Can happen when paragraphs > 16K, it is simply wrapped. + //TODO! Still true, still needed? + if ( rX.GetEnd() <= aPaM.GetNode()->Len() ) + { + if ( !bAllreadyHasAttribs || rX.IsFeature() ) + { + // Normal attributes then go faster ... + // Features shall not be inserted through + // EditDoc:: InsertAttrib, using FastInsertText they are + // already in the flow + DBG_ASSERT( rX.GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute too large!" ); + EditCharAttrib* pAttr; + if ( !bConvertMetricOfItems ) + pAttr = MakeCharAttrib( maEditDoc.GetItemPool(), *(rX.GetItem()), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos ); + else + { + std::unique_ptr<SfxPoolItem> pNew(rX.GetItem()->Clone()); + ConvertItem( pNew, eSourceUnit, eDestUnit ); + pAttr = MakeCharAttrib( maEditDoc.GetItemPool(), *pNew, rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos ); + } + DBG_ASSERT( pAttr->GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (1)" ); + aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttr ); + if ( pAttr->Which() == EE_FEATURE_FIELD ) + bUpdateFields = true; + } + else + { + DBG_ASSERT( rX.GetEnd()+nStartPos <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (2)" ); + // Tabs and other Features can not be inserted through InsertAttrib: + maEditDoc.InsertAttrib( aPaM.GetNode(), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos, *rX.GetItem() ); + } + } + } + if ( bUpdateFields ) + UpdateFields(); + + // Otherwise, quick format => no attributes! + pPortion->MarkSelectionInvalid( nStartPos ); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aPaM.GetNode()->GetCharAttribs()); +#endif + + bool bParaAttribs = false; + if ( bNewContent || ( ( n > 0 ) && ( n < (nContents-1) ) ) ) + { + // only style and ParaAttribs when new paragraph, or + // completely internal ... + bParaAttribs = pC->GetParaAttribs().Count() != 0; + if ( GetStyleSheetPool() && pC->GetStyle().getLength() ) + { + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pC->GetStyle(), pC->GetFamily() )); + DBG_ASSERT( pStyle, "InsertBinTextObject - Style not found!" ); + SetStyleSheet( nPara, pStyle ); + } + if ( !bConvertMetricOfItems ) + SetParaAttribs( maEditDoc.GetPos( aPaM.GetNode() ), pC->GetParaAttribs() ); + else + { + SfxItemSet aAttribs( GetEmptyItemSet() ); + ConvertAndPutItems( aAttribs, pC->GetParaAttribs(), &eSourceUnit, &eDestUnit ); + SetParaAttribs( maEditDoc.GetPos( aPaM.GetNode() ), aAttribs ); + } + if ( bNewContent && bUsePortionInfo ) + { + const XParaPortion& rXP = (*pPortionInfo)[n]; + ParaPortion* pParaPortion = GetParaPortions()[ nPara ]; + DBG_ASSERT( pParaPortion, "InsertBinTextObject: ParaPortion?" ); + pParaPortion->nHeight = rXP.nHeight; + pParaPortion->nFirstLineOffset = rXP.nFirstLineOffset; + pParaPortion->bForceRepaint = true; + pParaPortion->SetValid(); // Do not format + + // The Text Portions + pParaPortion->GetTextPortions().Reset(); + sal_uInt16 nCount = rXP.aTextPortions.Count(); + for ( sal_uInt16 _n = 0; _n < nCount; _n++ ) + { + const TextPortion& rTextPortion = rXP.aTextPortions[_n]; + TextPortion* pNew = new TextPortion( rTextPortion ); + pParaPortion->GetTextPortions().Append(pNew); + } + + // The lines + pParaPortion->GetLines().Reset(); + nCount = rXP.aLines.Count(); + for ( sal_uInt16 m = 0; m < nCount; m++ ) + { + const EditLine& rLine = rXP.aLines[m]; + EditLine* pNew = rLine.Clone(); + pNew->SetInvalid(); // Paint again! + pParaPortion->GetLines().Append(pNew); + } +#ifdef DBG_UTIL + sal_uInt16 nTest; + int nTPLen = 0, nTxtLen = 0; + for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; ) + nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen(); + for ( nTest = pParaPortion->GetLines().Count(); nTest; ) + nTxtLen += pParaPortion->GetLines()[--nTest].GetLen(); + DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "InsertTextObject: ParaPortion not completely formatted!" ); +#endif + } + } + if ( !bParaAttribs ) // DefFont is not calculated for FastInsertParagraph + { + aPaM.GetNode()->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont(); + if (maStatus.UseCharAttribs()) + aPaM.GetNode()->CreateDefFont(); + } + + if ( bNewContent && GetStatus().DoOnlineSpelling() && pC->GetWrongList() ) + { + aPaM.GetNode()->SetWrongList( pC->GetWrongList()->Clone() ); + } + + // Wrap when followed by other ... + if ( n < ( nContents-1) ) + { + if ( bNewContent ) + aPaM = ImpFastInsertParagraph( nPara+1 ); + else + aPaM = ImpInsertParaBreak( aPaM, false ); + } + } + + aSel.Max() = aPaM; + DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "InsertBibTextObject: Selection broken!(1)" ); + return aSel; +} + +void ImpEditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const +{ + std::vector<editeng::MisspellRanges> aRanges; + const EditDoc& rDoc = GetEditDoc(); + for (sal_Int32 i = 0, n = rDoc.Count(); i < n; ++i) + { + const ContentNode* pNode = rDoc.GetObject(i); + const WrongList* pWrongList = pNode->GetWrongList(); + if (!pWrongList) + continue; + + aRanges.emplace_back(i, std::vector(pWrongList->GetRanges())); + } + + aRanges.swap(rRanges); +} + +void ImpEditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges ) +{ + EditDoc& rDoc = GetEditDoc(); + for (auto const& rParaRanges : rRanges) + { + ContentNode* pNode = rDoc.GetObject(rParaRanges.mnParagraph); + if (!pNode) + continue; + + pNode->CreateWrongList(); + WrongList* pWrongList = pNode->GetWrongList(); + pWrongList->SetRanges(std::vector(rParaRanges.maRanges)); + } +} + +editeng::LanguageSpan ImpEditEngine::GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos ) const +{ + short nScriptTypeI18N = GetI18NScriptType( rPaM, pEndPos ); // pEndPos will be valid now, pointing to ScriptChange or NodeLen + SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N); + sal_uInt16 nLangId = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); + const SvxLanguageItem* pLangItem = &static_cast<const SvxLanguageItem&>(rPaM.GetNode()->GetContentAttribs().GetItem( nLangId )); + const EditCharAttrib* pAttr = rPaM.GetNode()->GetCharAttribs().FindAttrib( nLangId, rPaM.GetIndex() ); + + editeng::LanguageSpan aLang; + + if ( pAttr ) + { + pLangItem = static_cast<const SvxLanguageItem*>(pAttr->GetItem()); + aLang.nStart = pAttr->GetStart(); + aLang.nEnd = pAttr->GetEnd(); + } + + if ( pEndPos && pAttr && ( pAttr->GetEnd() < *pEndPos ) ) + *pEndPos = pAttr->GetEnd(); + + aLang.nLang = pLangItem->GetLanguage(); + + return aLang; +} + +css::lang::Locale ImpEditEngine::GetLocale( const EditPaM& rPaM ) const +{ + return LanguageTag( GetLanguage( rPaM ).nLang ).getLocale(); +} + +Reference< XSpellChecker1 > const & ImpEditEngine::GetSpeller() +{ + if ( !xSpeller.is() ) + xSpeller = LinguMgr::GetSpellChecker(); + return xSpeller; +} + + +void ImpEditEngine::CreateSpellInfo( bool bMultipleDocs ) +{ + if (!pSpellInfo) + pSpellInfo.reset( new SpellInfo ); + else + *pSpellInfo = SpellInfo(); // reset to default values + + pSpellInfo->bMultipleDoc = bMultipleDocs; + // always spell draw objects completely, starting at the top. + // (spelling in only a selection or not starting with the top requires + // further changes elsewhere to work properly) + pSpellInfo->aSpellStart = EPaM(); + pSpellInfo->aSpellTo = EPaM( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND ); +} + + +EESpellState ImpEditEngine::Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc) +{ + SAL_WARN_IF( !xSpeller.is(), "editeng", "No Spell checker set!" ); + + if ( !xSpeller.is() ) + return EESpellState::NoSpeller; + + aOnlineSpellTimer.Stop(); + + // In MultipleDoc always from the front / rear ... + if ( bMultipleDoc ) + { + pEditView->pImpEditView->SetEditSelection( maEditDoc.GetStartPaM() ); + } + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + CreateSpellInfo( bMultipleDoc ); + + bool bIsStart = false; + if ( bMultipleDoc ) + bIsStart = true; // Accessible from the front or from behind ... + else if ( CreateEPaM( maEditDoc.GetStartPaM() ) == pSpellInfo->aSpellStart ) + bIsStart = true; + + { + EditSpellWrapper aWrp(pDialogParent, bIsStart, pEditView ); + aWrp.SpellDocument(); + } + + if ( !bMultipleDoc ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + aCurSel.Min() = aCurSel.Max(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + EESpellState eState = pSpellInfo->eState; + pSpellInfo.reset(); + return eState; +} + + +bool ImpEditEngine::HasConvertibleTextPortion( LanguageType nSrcLang ) +{ + bool bHasConvTxt = false; + + sal_Int32 nParas = pEditEngine->GetParagraphCount(); + for (sal_Int32 k = 0; k < nParas; ++k) + { + std::vector<sal_Int32> aPortions; + pEditEngine->GetPortions( k, aPortions ); + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + sal_Int32 nEnd = aPortions[ nPos ]; + sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + // if the paragraph is not empty we need to increase the index + // by one since the attribute of the character left to the + // specified position is evaluated. + if (nEnd > nStart) // empty para? + ++nStart; + LanguageType nLangFound = pEditEngine->GetLanguage( k, nStart ).nLang; +#ifdef DEBUG + lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) ); +#endif + bHasConvTxt = (nSrcLang == nLangFound) || + (editeng::HangulHanjaConversion::IsChinese( nLangFound ) && + editeng::HangulHanjaConversion::IsChinese( nSrcLang )); + if (bHasConvTxt) + return bHasConvTxt; + } + } + + return bHasConvTxt; +} + +void ImpEditEngine::Convert( EditView* pEditView, weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + // modified version of ImpEditEngine::Spell + + // In MultipleDoc always from the front / rear ... + if ( bMultipleDoc ) + pEditView->pImpEditView->SetEditSelection( maEditDoc.GetStartPaM() ); + + + // initialize pConvInfo + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + aCurSel.Adjust( maEditDoc ); + pConvInfo.reset(new ConvInfo); + pConvInfo->bMultipleDoc = bMultipleDoc; + pConvInfo->aConvStart = CreateEPaM( aCurSel.Min() ); + + // if it is not just a selection and we are about to begin + // with the current conversion for the very first time + // we need to find the start of the current (initial) + // convertible unit in order for the text conversion to give + // the correct result for that. Since it is easier to obtain + // the start of the word we use that though. + if (!aCurSel.HasRange() && ImplGetBreakIterator().is()) + { + EditPaM aWordStartPaM( SelectWord( aCurSel, i18n::WordType::DICTIONARY_WORD ).Min() ); + + // since #118246 / #117803 still occurs if the cursor is placed + // between the two chinese characters to be converted (because both + // of them are words on their own!) using the word boundary here does + // not work. Thus since chinese conversion is not interactive we start + // at the begin of the paragraph to solve the problem, i.e. have the + // TextConversion service get those characters together in the same call. + pConvInfo->aConvStart.nIndex = editeng::HangulHanjaConversion::IsChinese( nSrcLang ) + ? 0 : aWordStartPaM.GetIndex(); + } + + pConvInfo->aConvContinue = pConvInfo->aConvStart; + + bool bIsStart = false; + if ( bMultipleDoc ) + bIsStart = true; // Accessible from the front or from behind ... + else if ( CreateEPaM( maEditDoc.GetStartPaM() ) == pConvInfo->aConvStart ) + bIsStart = true; + + TextConvWrapper aWrp( pDialogParent, + ::comphelper::getProcessComponentContext(), + LanguageTag::convertToLocale( nSrcLang ), + LanguageTag::convertToLocale( nDestLang ), + pDestFont, + nOptions, bIsInteractive, + bIsStart, pEditView ); + + + //!! optimization does not work since when update mode is false + //!! the object is 'lying' about it portions, paragraphs, + //!! EndPaM... later on. + //!! Should not be a great problem since text boxes or cells in + //!! Calc usually have only a rather short text. + // + // disallow formatting, updating the view, ... while + // non-interactively converting the document. (saves time) + //if (!bIsInteractive) + // SetUpdateMode( sal_False ); + + aWrp.Convert(); + + //if (!bIsInteractive) + //SetUpdateMode( sal_True, 0, sal_True ); + + if ( !bMultipleDoc ) + { + pEditView->pImpEditView->DrawSelectionXOR(); + if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() ) + aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() ); + aCurSel.Min() = aCurSel.Max(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + pConvInfo.reset(); +} + + +void ImpEditEngine::SetLanguageAndFont( + const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ) +{ + ESelection aOldSel = pActiveView->GetSelection(); + pActiveView->SetSelection( rESel ); + + // set new language attribute + SfxItemSet aNewSet( pActiveView->GetEmptyItemSet() ); + aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) ); + + // new font to be set? + DBG_ASSERT( pFont, "target font missing?" ); + if (pFont) + { + // set new font attribute + SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) ); + aFontItem.SetFamilyName( pFont->GetFamilyName()); + aFontItem.SetFamily( pFont->GetFamilyType()); + aFontItem.SetStyleName( pFont->GetStyleName()); + aFontItem.SetPitch( pFont->GetPitch()); + aFontItem.SetCharSet( pFont->GetCharSet() ); + aNewSet.Put( aFontItem ); + } + + // apply new attributes + pActiveView->SetAttribs( aNewSet ); + + pActiveView->SetSelection( aOldSel ); +} + + +void ImpEditEngine::ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang, + EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange, + bool bAllowImplicitChangesForNotConvertibleText, + LanguageType nTargetLang, const vcl::Font *pTargetFont ) +{ + // modified version of ImpEditEngine::ImpSpell + + // looks for next convertible text portion to be passed on to the wrapper + + OUString aRes; + LanguageType nResLang = LANGUAGE_NONE; + + EditPaM aPos( CreateEditPaM( pConvInfo->aConvContinue ) ); + EditSelection aCurSel( aPos, aPos ); + + OUString aWord; + + while (aRes.isEmpty()) + { + // empty paragraph found that needs to have language and font set? + if (bAllowImplicitChangesForNotConvertibleText && + pEditEngine->GetText( pConvInfo->aConvContinue.nPara ).isEmpty()) + { + sal_Int32 nPara = pConvInfo->aConvContinue.nPara; + ESelection aESel( nPara, 0, nPara, 0 ); + // see comment for below same function call + SetLanguageAndFont( aESel, + nTargetLang, EE_CHAR_LANGUAGE_CJK, + pTargetFont, EE_CHAR_FONTINFO_CJK ); + } + + + if (pConvInfo->aConvContinue.nPara == pConvInfo->aConvTo.nPara && + pConvInfo->aConvContinue.nIndex >= pConvInfo->aConvTo.nIndex) + break; + + sal_Int32 nAttribStart = -1; + sal_Int32 nAttribEnd = -1; + sal_Int32 nCurPos = -1; + EPaM aCurStart = CreateEPaM( aCurSel.Min() ); + std::vector<sal_Int32> aPortions; + pEditEngine->GetPortions( aCurStart.nPara, aPortions ); + for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos ) + { + const sal_Int32 nEnd = aPortions[ nPos ]; + const sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0; + + // the language attribute is obtained from the left character + // (like usually all other attributes) + // thus we usually have to add 1 in order to get the language + // of the text right to the cursor position + const sal_Int32 nLangIdx = nEnd > nStart ? nStart + 1 : nStart; + LanguageType nLangFound = pEditEngine->GetLanguage( aCurStart.nPara, nLangIdx ).nLang; +#ifdef DEBUG + lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) ); +#endif + bool bLangOk = (nLangFound == nSrcLang) || + (editeng::HangulHanjaConversion::IsChinese( nLangFound ) && + editeng::HangulHanjaConversion::IsChinese( nSrcLang )); + + if (nAttribEnd>=0) // start already found? + { + DBG_ASSERT(nEnd >= aCurStart.nIndex, "error while scanning attributes (a)" ); + DBG_ASSERT(nEnd >= nAttribEnd, "error while scanning attributes (b)" ); + if (/*nEnd >= aCurStart.nIndex &&*/ nLangFound == nResLang) + nAttribEnd = nEnd; + else // language attrib has changed + break; + } + if (nAttribStart<0 && // start not yet found? + nEnd > aCurStart.nIndex && bLangOk) + { + nAttribStart = nStart; + nAttribEnd = nEnd; + nResLang = nLangFound; + } + //! the list of portions may have changed compared to the previous + //! call to this function (because of possibly changed language + //! attribute!) + //! But since we don't want to start in the already processed part + //! we clip the start accordingly. + if (nAttribStart >= 0 && nAttribStart < aCurStart.nIndex) + { + nAttribStart = aCurStart.nIndex; + } + + // check script type to the right of the start of the current portion + EditPaM aPaM( CreateEditPaM( EPaM(aCurStart.nPara, nLangIdx) ) ); + bool bIsAsianScript = (i18n::ScriptType::ASIAN == GetI18NScriptType( aPaM )); + // not yet processed text part with for conversion + // not suitable language found that needs to be changed? + if (bAllowImplicitChangesForNotConvertibleText && + !bLangOk && !bIsAsianScript && nEnd > aCurStart.nIndex) + { + ESelection aESel( aCurStart.nPara, nStart, aCurStart.nPara, nEnd ); + // set language and font to target language and font of conversion + //! Now this especially includes all non convertible text e.g. + //! spaces, empty paragraphs and western text. + // This is in order for every *new* text entered at *any* position to + // have the correct language and font attributes set. + SetLanguageAndFont( aESel, + nTargetLang, EE_CHAR_LANGUAGE_CJK, + pTargetFont, EE_CHAR_FONTINFO_CJK ); + } + + nCurPos = nEnd; + } + + if (nAttribStart>=0 && nAttribEnd>=0) + { + aCurSel.Min().SetIndex( nAttribStart ); + aCurSel.Max().SetIndex( nAttribEnd ); + } + else if (nCurPos>=0) + { + // set selection to end of scanned text + // (used to set the position where to continue from later on) + aCurSel.Min().SetIndex( nCurPos ); + aCurSel.Max().SetIndex( nCurPos ); + } + + if ( !pConvInfo->bConvToEnd ) + { + EPaM aEPaM( CreateEPaM( aCurSel.Min() ) ); + if ( !( aEPaM < pConvInfo->aConvTo ) ) + break; + } + + // clip selected word to the converted area + // (main use when conversion starts/ends **within** a word) + EditPaM aPaM( CreateEditPaM( pConvInfo->aConvStart ) ); + if (pConvInfo->bConvToEnd && + aCurSel.Min().GetNode() == aPaM.GetNode() && + aCurSel.Min().GetIndex() < aPaM.GetIndex()) + aCurSel.Min().SetIndex( aPaM.GetIndex() ); + aPaM = CreateEditPaM( pConvInfo->aConvContinue ); + if (aCurSel.Min().GetNode() == aPaM.GetNode() && + aCurSel.Min().GetIndex() < aPaM.GetIndex()) + aCurSel.Min().SetIndex( aPaM.GetIndex() ); + aPaM = CreateEditPaM( pConvInfo->aConvTo ); + if ((!pConvInfo->bConvToEnd || rConvRange.HasRange())&& + aCurSel.Max().GetNode() == aPaM.GetNode() && + aCurSel.Max().GetIndex() > aPaM.GetIndex()) + aCurSel.Max().SetIndex( aPaM.GetIndex() ); + + aWord = GetSelected( aCurSel ); + + if ( !aWord.isEmpty() /* && bLangOk */) + aRes = aWord; + + // move to next word/paragraph if necessary + if ( aRes.isEmpty() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + + pConvInfo->aConvContinue = CreateEPaM( aCurSel.Max() ); + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + + rConvTxt = aRes; + if ( !rConvTxt.isEmpty() ) + rConvTxtLang = nResLang; +} + + +Reference< XSpellAlternatives > ImpEditEngine::ImpSpell( EditView* pEditView ) +{ + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 ); + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + aCurSel.Min() = aCurSel.Max(); + + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while (!xSpellAlt.is()) + { + // Known (most likely) bug: If SpellToCurrent, the current has to be + // corrected at each replacement, otherwise it may not fit exactly in + // the end ... + if ( pSpellInfo->bSpellToEnd || pSpellInfo->bMultipleDoc ) + { + if ( aCurSel.Max().GetNode() == pLastNode ) + { + if ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) + break; + } + } + else if ( !pSpellInfo->bSpellToEnd ) + { + EPaM aEPaM( CreateEPaM( aCurSel.Max() ) ); + if ( !( aEPaM < pSpellInfo->aSpellTo ) ) + break; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord = GetSelected( aCurSel ); + + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) ) + { + sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 ); + aWord += OUStringChar(cNext); + } + } + + if ( !aWord.isEmpty() ) + { + LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang; + SvxSpellWrapper::CheckSpellLang( xSpeller, eLang ); + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq ); + } + + if ( !xSpellAlt.is() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + else + pSpellInfo->eState = EESpellState::ErrorFound; + } + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + return xSpellAlt; +} + +Reference< XSpellAlternatives > ImpEditEngine::ImpFindNextError(EditSelection& rSelection) +{ + EditSelection aCurSel( rSelection.Min() ); + + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while (!xSpellAlt.is()) + { + //check if the end of the selection has been reached + { + EPaM aEPaM( CreateEPaM( aCurSel.Max() ) ); + if ( !( aEPaM < CreateEPaM( rSelection.Max()) ) ) + break; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord = GetSelected( aCurSel ); + + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) ) + { + sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 ); + aWord += OUStringChar(cNext); + } + } + + if ( !aWord.isEmpty() ) + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(GetLanguage( aCurSel.Max() ).nLang), aEmptySeq ); + + if ( !xSpellAlt.is() ) + aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD ); + else + { + pSpellInfo->eState = EESpellState::ErrorFound; + rSelection = aCurSel; + } + } + return xSpellAlt; +} + +bool ImpEditEngine::SpellSentence(EditView const & rEditView, + svx::SpellPortions& rToFill ) +{ + bool bRet = false; + EditSelection aCurSel( rEditView.pImpEditView->GetEditSelection() ); + if(!pSpellInfo) + CreateSpellInfo( true ); + pSpellInfo->aCurSentenceStart = aCurSel.Min(); + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + pSpellInfo->aLastSpellPortions.clear(); + pSpellInfo->aLastSpellContentSelections.clear(); + rToFill.clear(); + //if no selection previously exists the range is extended to the end of the object + if (!aCurSel.HasRange()) + { + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1); + aCurSel.Max() = EditPaM(pLastNode, pLastNode->Len()); + } + // check for next error in aCurSel and set aCurSel to that one if any was found + Reference< XSpellAlternatives > xAlt = ImpFindNextError(aCurSel); + if (xAlt.is()) + { + bRet = true; + //find the sentence boundaries + EditSelection aSentencePaM = SelectSentence(aCurSel); + //make sure that the sentence is never smaller than the error range! + if(aSentencePaM.Max().GetIndex() < aCurSel.Max().GetIndex()) + aSentencePaM.Max() = aCurSel.Max(); + //add the portion preceding the error + EditSelection aStartSelection(aSentencePaM.Min(), aCurSel.Min()); + if(aStartSelection.HasRange()) + AddPortionIterated(rEditView, aStartSelection, nullptr, rToFill); + //add the error portion + AddPortionIterated(rEditView, aCurSel, xAlt, rToFill); + //find the end of the sentence + //search for all errors in the rest of the sentence and add all the portions + do + { + EditSelection aNextSel(aCurSel.Max(), aSentencePaM.Max()); + xAlt = ImpFindNextError(aNextSel); + if(xAlt.is()) + { + //add the part between the previous and the current error + AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aNextSel.Min()), nullptr, rToFill); + //add the current error + AddPortionIterated(rEditView, aNextSel, xAlt, rToFill); + } + else + AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aSentencePaM.Max()), xAlt, rToFill); + aCurSel = aNextSel; + } + while( xAlt.is() ); + + //set the selection to the end of the current sentence + rEditView.pImpEditView->SetEditSelection(aSentencePaM.Max()); + } + return bRet; +} + +// Adds one portion to the SpellPortions +void ImpEditEngine::AddPortion( + const EditSelection& rSel, + const uno::Reference< XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill, + bool bIsField) +{ + if(!rSel.HasRange()) + return; + + svx::SpellPortion aPortion; + aPortion.sText = GetSelected( rSel ); + aPortion.eLanguage = GetLanguage( rSel.Min() ).nLang; + aPortion.xAlternatives = xAlt; + aPortion.bIsField = bIsField; + rToFill.push_back(aPortion); + + //save the spelled portions for later use + pSpellInfo->aLastSpellPortions.push_back(aPortion); + pSpellInfo->aLastSpellContentSelections.push_back(rSel); +} + +// Adds one or more portions of text to the SpellPortions depending on language changes +void ImpEditEngine::AddPortionIterated( + EditView const & rEditView, + const EditSelection& rSel, + const Reference< XSpellAlternatives >& xAlt, + svx::SpellPortions& rToFill) +{ + if (!rSel.HasRange()) + return; + + if(xAlt.is()) + { + AddPortion(rSel, xAlt, rToFill, false); + } + else + { + //iterate and search for language attribute changes + //save the start and end positions + bool bTest = rSel.Min().GetIndex() <= rSel.Max().GetIndex(); + EditPaM aStart(bTest ? rSel.Min() : rSel.Max()); + EditPaM aEnd(bTest ? rSel.Max() : rSel.Min()); + //iterate over the text to find changes in language + //set the mark equal to the point + EditPaM aCursor(aStart); + rEditView.pImpEditView->SetEditSelection( aCursor ); + LanguageType eStartLanguage = GetLanguage( aCursor ).nLang; + //search for a field attribute at the beginning - only the end position + //of this field is kept to end a portion at that position + const EditCharAttrib* pFieldAttr = aCursor.GetNode()->GetCharAttribs(). + FindFeature( aCursor.GetIndex() ); + bool bIsField = pFieldAttr && + pFieldAttr->GetStart() == aCursor.GetIndex() && + pFieldAttr->GetStart() != pFieldAttr->GetEnd() && + pFieldAttr->Which() == EE_FEATURE_FIELD; + sal_Int32 nEndField = bIsField ? pFieldAttr->GetEnd() : -1; + do + { + aCursor = CursorRight( aCursor); + //determine whether a field and has been reached + bool bIsEndField = nEndField == aCursor.GetIndex(); + //search for a new field attribute + const EditCharAttrib* _pFieldAttr = aCursor.GetNode()->GetCharAttribs(). + FindFeature( aCursor.GetIndex() ); + bIsField = _pFieldAttr && + _pFieldAttr->GetStart() == aCursor.GetIndex() && + _pFieldAttr->GetStart() != _pFieldAttr->GetEnd() && + _pFieldAttr->Which() == EE_FEATURE_FIELD; + //on every new field move the end position + if (bIsField) + nEndField = _pFieldAttr->GetEnd(); + + LanguageType eCurLanguage = GetLanguage( aCursor ).nLang; + if(eCurLanguage != eStartLanguage || bIsField || bIsEndField) + { + eStartLanguage = eCurLanguage; + //go one step back - the cursor currently selects the first character + //with a different language + //create a selection from start to the current Cursor + EditSelection aSelection(aStart, aCursor); + AddPortion(aSelection, xAlt, rToFill, bIsEndField); + aStart = aCursor; + } + } + while(aCursor.GetIndex() < aEnd.GetIndex()); + EditSelection aSelection(aStart, aCursor); + AddPortion(aSelection, xAlt, rToFill, bIsField); + } +} + +void ImpEditEngine::ApplyChangedSentence(EditView const & rEditView, + const svx::SpellPortions& rNewPortions, + bool bRecheck ) +{ + // Note: rNewPortions.size() == 0 is valid and happens when the whole + // sentence got removed in the dialog + + DBG_ASSERT(pSpellInfo, "pSpellInfo not initialized"); + if (!pSpellInfo || pSpellInfo->aLastSpellPortions.empty()) // no portions -> no text to be changed + return; + + // get current paragraph length to calculate later on how the sentence length changed, + // in order to place the cursor at the end of the sentence again + EditSelection aOldSel( rEditView.pImpEditView->GetEditSelection() ); + sal_Int32 nOldLen = aOldSel.Max().GetNode()->Len(); + + UndoActionStart( EDITUNDO_INSERT ); + if(pSpellInfo->aLastSpellPortions.size() == rNewPortions.size()) + { + DBG_ASSERT( !rNewPortions.empty(), "rNewPortions should not be empty here" ); + DBG_ASSERT( pSpellInfo->aLastSpellPortions.size() == pSpellInfo->aLastSpellContentSelections.size(), + "aLastSpellPortions and aLastSpellContentSelections size mismatch" ); + + //the simple case: the same number of elements on both sides + //each changed element has to be applied to the corresponding source element + svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end(); + svx::SpellPortions::const_iterator aCurrentOldPortion = pSpellInfo->aLastSpellPortions.end(); + SpellContentSelections::const_iterator aCurrentOldPosition = pSpellInfo->aLastSpellContentSelections.end(); + bool bSetToEnd = false; + do + { + --aCurrentNewPortion; + --aCurrentOldPortion; + --aCurrentOldPosition; + //set the cursor to the end of the sentence - necessary to + //resume there at the next step + if(!bSetToEnd) + { + bSetToEnd = true; + rEditView.pImpEditView->SetEditSelection( aCurrentOldPosition->Max() ); + } + + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage ); + sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: break; + } + if(aCurrentNewPortion->sText != aCurrentOldPortion->sText) + { + //change text and apply language + SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId ); + aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId)); + SetAttribs( *aCurrentOldPosition, aSet ); + ImpInsertText( *aCurrentOldPosition, aCurrentNewPortion->sText ); + } + else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + { + //apply language + SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId); + aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId)); + SetAttribs( *aCurrentOldPosition, aSet ); + } + } + while(aCurrentNewPortion != rNewPortions.begin()); + } + else + { + DBG_ASSERT( !pSpellInfo->aLastSpellContentSelections.empty(), "aLastSpellContentSelections should not be empty here" ); + + //select the complete sentence + SpellContentSelections::const_iterator aCurrentEndPosition = pSpellInfo->aLastSpellContentSelections.end(); + --aCurrentEndPosition; + SpellContentSelections::const_iterator aCurrentStartPosition = pSpellInfo->aLastSpellContentSelections.begin(); + EditSelection aAllSentence(aCurrentStartPosition->Min(), aCurrentEndPosition->Max()); + + //delete the sentence completely + ImpDeleteSelection( aAllSentence ); + EditPaM aCurrentPaM = aAllSentence.Min(); + for(const auto& rCurrentNewPortion : rNewPortions) + { + //set the language attribute + LanguageType eCurLanguage = GetLanguage( aCurrentPaM ).nLang; + if(eCurLanguage != rCurrentNewPortion.eLanguage) + { + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( rCurrentNewPortion.eLanguage ); + sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: break; + } + SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId); + aSet.Put(SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId)); + SetAttribs( aCurrentPaM, aSet ); + } + //insert the new string and set the cursor to the end of the inserted string + aCurrentPaM = ImpInsertText( aCurrentPaM , rCurrentNewPortion.sText ); + } + } + UndoActionEnd(); + + EditPaM aNext; + if (bRecheck) + aNext = pSpellInfo->aCurSentenceStart; + else + { + // restore cursor position to the end of the modified sentence. + // (This will define the continuation position for spell/grammar checking) + // First: check if the sentence/para length changed + const sal_Int32 nDelta = rEditView.pImpEditView->GetEditSelection().Max().GetNode()->Len() - nOldLen; + const sal_Int32 nEndOfSentence = aOldSel.Max().GetIndex() + nDelta; + aNext = EditPaM( aOldSel.Max().GetNode(), nEndOfSentence ); + } + rEditView.pImpEditView->SetEditSelection( aNext ); + + if (IsUpdateLayout()) + FormatAndLayout(); + maEditDoc.SetModified(true); +} + +void ImpEditEngine::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + if( pSpellInfo && !pSpellInfo->aLastSpellContentSelections.empty() ) + { + rEditView.pImpEditView->SetEditSelection( pSpellInfo->aLastSpellContentSelections.begin()->Min() ); + } +} + + +void ImpEditEngine::DoOnlineSpelling( ContentNode* pThisNodeOnly, bool bSpellAtCursorPos, bool bInterruptible ) +{ + /* + It will iterate over all the paragraphs, paragraphs with only + invalidated wrong list will be checked ... + + All the words are checked in the invalidated region. Is a word wrong, + but not in the wrong list, or vice versa, the range of the word will be + invalidated + (no Invalidate, but if only transitions wrong from right =>, simple Paint, + even out properly with VDev on transitions from wrong => right) + */ + + if ( !xSpeller.is() ) + return; + + EditPaM aCursorPos; + if( pActiveView && !bSpellAtCursorPos ) + { + aCursorPos = pActiveView->pImpEditView->GetEditSelection().Max(); + } + + bool bRestartTimer = false; + + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count() - 1 ); + sal_Int32 nNodes = GetEditDoc().Count(); + sal_Int32 nInvalids = 0; + Sequence< PropertyValue > aEmptySeq; + for ( sal_Int32 n = 0; n < nNodes; n++ ) + { + ContentNode* pNode = GetEditDoc().GetObject( n ); + if ( pThisNodeOnly ) + pNode = pThisNodeOnly; + + pNode->EnsureWrongList(); + if (!pNode->GetWrongList()->IsValid()) + { + WrongList* pWrongList = pNode->GetWrongList(); + const size_t nInvStart = pWrongList->GetInvalidStart(); + const size_t nInvEnd = pWrongList->GetInvalidEnd(); + + sal_Int32 nPaintFrom = -1; + sal_Int32 nPaintTo = 0; + bool bSimpleRepaint = true; + + pWrongList->SetValid(); + + EditPaM aPaM( pNode, nInvStart ); + EditSelection aSel( aPaM, aPaM ); + while ( aSel.Max().GetNode() == pNode ) + { + if ( ( o3tl::make_unsigned(aSel.Min().GetIndex()) > nInvEnd ) + || ( ( aSel.Max().GetNode() == pLastNode ) && ( aSel.Max().GetIndex() >= pLastNode->Len() ) ) ) + break; // Document end or end of invalid region + + aSel = SelectWord( aSel, i18n::WordType::DICTIONARY_WORD ); + // If afterwards a dot, this must be handed over! + // If an abbreviation ... + bool bDottAdded = false; + if ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) + { + sal_Unicode cNext = aSel.Max().GetNode()->GetChar( aSel.Max().GetIndex() ); + if ( cNext == '.' ) + { + aSel.Max().SetIndex( aSel.Max().GetIndex()+1 ); + bDottAdded = true; + } + } + OUString aWord = GetSelected(aSel); + + bool bChanged = false; + if (!aWord.isEmpty()) + { + const sal_Int32 nWStart = aSel.Min().GetIndex(); + const sal_Int32 nWEnd = aSel.Max().GetIndex(); + if ( !xSpeller->isValid( aWord, static_cast<sal_uInt16>(GetLanguage( EditPaM( aSel.Min().GetNode(), nWStart+1 ) ).nLang), aEmptySeq ) ) + { + // Check if already marked correctly... + const sal_Int32 nXEnd = bDottAdded ? nWEnd -1 : nWEnd; + if ( !pWrongList->HasWrong( nWStart, nXEnd ) ) + { + // Mark Word as wrong... + // But only when not at Cursor-Position... + bool bCursorPos = false; + if ( aCursorPos.GetNode() == pNode ) + { + if ( ( nWStart <= aCursorPos.GetIndex() ) && nWEnd >= aCursorPos.GetIndex() ) + bCursorPos = true; + } + if ( bCursorPos ) + { + // Then continue to mark as invalid ... + pWrongList->ResetInvalidRange(nWStart, nWEnd); + bRestartTimer = true; + } + else + { + // It may be that the Wrongs in the list are not + // spanning exactly over words because the + // WordDelimiters during expansion are not + // evaluated. + pWrongList->InsertWrong(nWStart, nXEnd); + bChanged = true; + } + } + } + else + { + // Check if not marked as wrong + if ( pWrongList->HasAnyWrong( nWStart, nWEnd ) ) + { + pWrongList->ClearWrongs( nWStart, nWEnd, pNode ); + bSimpleRepaint = false; + bChanged = true; + } + } + if ( bChanged ) + { + if ( nPaintFrom<0 ) + nPaintFrom = nWStart; + nPaintTo = nWEnd; + } + } + + EditPaM aLastEnd( aSel.Max() ); + aSel = WordRight( aSel.Max(), i18n::WordType::DICTIONARY_WORD ); + if ( bChanged && ( aSel.Min().GetNode() == pNode ) && + ( aSel.Min().GetIndex()-aLastEnd.GetIndex() > 1 ) ) + { + // If two words are separated by more than one blank, it + // can happen that when splitting a Wrongs the start of + // the second word is before the actually word + pWrongList->ClearWrongs( aLastEnd.GetIndex(), aSel.Min().GetIndex(), pNode ); + } + } + + // Invalidate? + if ( nPaintFrom>=0 ) + { + maStatus.GetStatusWord() |= EditStatusFlags::WRONGWORDCHANGED; + CallStatusHdl(); + + if (!aEditViews.empty()) + { + // For SimpleRepaint one was painted over a range without + // reaching VDEV, but then one would have to intersect, c + // clipping, ... over all views. Probably not worthwhile. + EditPaM aStartPaM( pNode, nPaintFrom ); + EditPaM aEndPaM( pNode, nPaintTo ); + tools::Rectangle aStartCursor( PaMtoEditCursor( aStartPaM ) ); + tools::Rectangle aEndCursor( PaMtoEditCursor( aEndPaM ) ); + DBG_ASSERT( aInvalidRect.IsEmpty(), "InvalidRect set!" ); + aInvalidRect.SetLeft( 0 ); + aInvalidRect.SetRight( GetPaperSize().Width() ); + aInvalidRect.SetTop( aStartCursor.Top() ); + aInvalidRect.SetBottom( aEndCursor.Bottom() ); + if ( pActiveView && pActiveView->HasSelection() ) + { + // Then no output through VDev. + UpdateViews(); + } + else if ( bSimpleRepaint ) + { + for (EditView* pView : aEditViews) + { + tools::Rectangle aClipRect( aInvalidRect ); + aClipRect.Intersection( pView->GetVisArea() ); + if ( !aClipRect.IsEmpty() ) + { + // convert to window coordinates... + aClipRect.SetPos( pView->pImpEditView->GetWindowPos( aClipRect.TopLeft() ) ); + pView->pImpEditView->InvalidateAtWindow(aClipRect); + } + } + } + else + { + UpdateViews( pActiveView ); + } + aInvalidRect = tools::Rectangle(); + } + } + // After two corrected nodes give up the control... + nInvalids++; + if ( bInterruptible && ( nInvalids >= 2 ) ) + { + bRestartTimer = true; + break; + } + } + + if ( pThisNodeOnly ) + break; + } + if ( bRestartTimer ) + aOnlineSpellTimer.Start(); +} + + +EESpellState ImpEditEngine::HasSpellErrors() +{ + DBG_ASSERT( xSpeller.is(), "No spell checker set!" ); + + ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count() - 1 ); + EditSelection aCurSel( maEditDoc.GetStartPaM() ); + + OUString aWord; + Reference< XSpellAlternatives > xSpellAlt; + Sequence< PropertyValue > aEmptySeq; + while ( !xSpellAlt.is() ) + { + if ( ( aCurSel.Max().GetNode() == pLastNode ) && + ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) ) + { + return EESpellState::Ok; + } + + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + aWord = GetSelected( aCurSel ); + if ( !aWord.isEmpty() ) + { + LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang; + SvxSpellWrapper::CheckSpellLang( xSpeller, eLang ); + xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq ); + } + aCurSel = WordRight( aCurSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); + } + + return EESpellState::ErrorFound; +} + +void ImpEditEngine::ClearSpellErrors() +{ + maEditDoc.ClearSpellErrors(); +} + +EESpellState ImpEditEngine::StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent) +{ + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + if ( !aCurSel.HasRange() ) + aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD ); + OUString aWord( GetSelected( aCurSel ) ); + + Reference< XThesaurus > xThes( LinguMgr::GetThesaurus() ); + if (!xThes.is()) + return EESpellState::ErrorFound; + + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractThesaurusDialog> xDlg(pFact->CreateThesaurusDialog(pDialogParent, xThes, + aWord, GetLanguage( aCurSel.Max() ).nLang )); + if (xDlg->Execute() == RET_OK) + { + // Replace Word... + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->pImpEditView->SetEditSelection( aCurSel ); + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->InsertText(xDlg->GetWord()); + pEditView->ShowCursor(true, false); + } + + return EESpellState::Ok; +} + +sal_Int32 ImpEditEngine::StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem ) +{ + sal_Int32 nFound = 0; + + EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() ); + + // FIND_ALL is not possible without multiple selection. + if ( ( rSearchItem.GetCommand() == SvxSearchCmd::FIND ) || + ( rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL ) ) + { + if ( Search( rSearchItem, pEditView ) ) + nFound++; + } + else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE ) + { + // The word is selected if the user not altered the selection + // in between: + if ( aCurSel.HasRange() ) + { + pEditView->InsertText( rSearchItem.GetReplaceString() ); + nFound = 1; + } + else + if( Search( rSearchItem, pEditView ) ) + nFound = 1; + } + else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL ) + { + // The Writer replaces all front beginning to end ... + SvxSearchItem aTmpItem( rSearchItem ); + aTmpItem.SetBackward( false ); + + pEditView->pImpEditView->DrawSelectionXOR(); + + aCurSel.Adjust( maEditDoc ); + EditPaM aStartPaM = aTmpItem.GetSelection() ? aCurSel.Min() : maEditDoc.GetStartPaM(); + EditSelection aFoundSel( aCurSel.Max() ); + bool bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel ); + if ( bFound ) + UndoActionStart( EDITUNDO_REPLACEALL ); + while ( bFound ) + { + nFound++; + aStartPaM = ImpInsertText( aFoundSel, rSearchItem.GetReplaceString() ); + bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel ); + } + if ( nFound ) + { + EditPaM aNewPaM( aFoundSel.Max() ); + if ( aNewPaM.GetIndex() > aNewPaM.GetNode()->Len() ) + aNewPaM.SetIndex( aNewPaM.GetNode()->Len() ); + pEditView->pImpEditView->SetEditSelection( aNewPaM ); + FormatAndLayout( pEditView ); + UndoActionEnd(); + } + else + { + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + } + } + return nFound; +} + +bool ImpEditEngine::Search( const SvxSearchItem& rSearchItem, EditView* pEditView ) +{ + EditSelection aSel( pEditView->pImpEditView->GetEditSelection() ); + aSel.Adjust( maEditDoc ); + EditPaM aStartPaM( aSel.Max() ); + if ( rSearchItem.GetSelection() && !rSearchItem.GetBackward() ) + aStartPaM = aSel.Min(); + + EditSelection aFoundSel; + bool bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel ); + if ( bFound && ( aFoundSel == aSel ) ) // For backwards-search + { + aStartPaM = aSel.Min(); + bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel ); + } + + pEditView->pImpEditView->DrawSelectionXOR(); + if ( bFound ) + { + // First, set the minimum, so the whole word is in the visible range. + pEditView->pImpEditView->SetEditSelection( aFoundSel.Min() ); + pEditView->ShowCursor( true, false ); + pEditView->pImpEditView->SetEditSelection( aFoundSel ); + } + else + pEditView->pImpEditView->SetEditSelection( aSel.Max() ); + + pEditView->pImpEditView->DrawSelectionXOR(); + pEditView->ShowCursor( true, false ); + return bFound; +} + +bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem, + const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel ) +{ + i18nutil::SearchOptions2 aSearchOptions( rSearchItem.GetSearchOptions() ); + aSearchOptions.Locale = GetLocale( rStartPos ); + + bool bBack = rSearchItem.GetBackward(); + bool bSearchInSelection = rSearchItem.GetSelection(); + sal_Int32 nStartNode = maEditDoc.GetPos( rStartPos.GetNode() ); + sal_Int32 nEndNode; + if ( bSearchInSelection ) + { + nEndNode = maEditDoc.GetPos( bBack ? rSearchSelection.Min().GetNode() : rSearchSelection.Max().GetNode() ); + } + else + { + nEndNode = bBack ? 0 : maEditDoc.Count()-1; + } + + utl::TextSearch aSearcher( aSearchOptions ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; + bBack ? ( nNode >= nEndNode ) : ( nNode <= nEndNode) ; + bBack ? nNode-- : nNode++ ) + { + // For backwards-search if nEndNode = 0: + if ( nNode < 0 ) + return false; + + ContentNode* pNode = maEditDoc.GetObject( nNode ); + + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pNode->GetExpandedLen(); + if ( nNode == nStartNode ) + { + if ( bBack ) + nEndPos = rStartPos.GetIndex(); + else + nStartPos = rStartPos.GetIndex(); + } + if ( ( nNode == nEndNode ) && bSearchInSelection ) + { + if ( bBack ) + nStartPos = rSearchSelection.Min().GetIndex(); + else + nEndPos = rSearchSelection.Max().GetIndex(); + } + + // Searching ... + OUString aParaStr( pNode->GetExpandedText() ); + bool bFound = false; + if ( bBack ) + { + sal_Int32 nTemp; + nTemp = nStartPos; + nStartPos = nEndPos; + nEndPos = nTemp; + + bFound = aSearcher.SearchBackward( aParaStr, &nStartPos, &nEndPos); + } + else + { + bFound = aSearcher.SearchForward( aParaStr, &nStartPos, &nEndPos); + } + if ( bFound ) + { + pNode->UnExpandPositions( nStartPos, nEndPos ); + + rFoundSel.Min().SetNode( pNode ); + rFoundSel.Min().SetIndex( nStartPos ); + rFoundSel.Max().SetNode( pNode ); + rFoundSel.Max().SetIndex( nEndPos ); + return true; + } + } + return false; +} + +bool ImpEditEngine::HasText( const SvxSearchItem& rSearchItem ) +{ + SvxSearchItem aTmpItem( rSearchItem ); + aTmpItem.SetBackward( false ); + aTmpItem.SetSelection( false ); + + EditPaM aStartPaM( maEditDoc.GetStartPaM() ); + EditSelection aDummySel( aStartPaM ); + EditSelection aFoundSel; + return ImpSearch( aTmpItem, aDummySel, aStartPaM, aFoundSel ); +} + +void ImpEditEngine::SetAutoCompleteText(const OUString& rStr, bool bClearTipWindow) +{ + maAutoCompleteText = rStr; + if ( bClearTipWindow && pActiveView ) + Help::ShowQuickHelp( pActiveView->GetWindow(), tools::Rectangle(), OUString() ); +} + +namespace +{ + struct eeTransliterationChgData + { + sal_Int32 nStart; + sal_Int32 nLen; + EditSelection aSelection; + OUString aNewText; + uno::Sequence< sal_Int32 > aOffsets; + }; +} + +EditSelection ImpEditEngine::TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode ) +{ + uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() ); + if (!_xBI.is()) + return rSelection; + + EditSelection aSel( rSelection ); + aSel.Adjust( maEditDoc ); + + if ( !aSel.HasRange() ) + { + /* Cursor is inside of a word */ + if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE) + aSel = SelectSentence( aSel ); + else + aSel = SelectWord( aSel ); + } + + // tdf#107176: if there's still no range, just return aSel + if ( !aSel.HasRange() ) + return aSel; + + EditSelection aNewSel( aSel ); + + const sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + const sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + bool bChanges = false; + bool bLenChanged = false; + std::unique_ptr<EditUndoTransliteration> pUndo; + + utl::TransliterationWrapper aTransliterationWrapper( ::comphelper::getProcessComponentContext(), nTransliterationMode ); + bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode(); + + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + const OUString& aNodeStr = pNode->GetString(); + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : aNodeStr.getLength(); // can also be == nStart! + + sal_Int32 nCurrentStart = nStartPos; + sal_Int32 nCurrentEnd = nEndPos; + LanguageType nLanguage = LANGUAGE_SYSTEM; + + // since we don't use Hiragana/Katakana or half-width/full-width transliterations here + // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will + // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES + // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the + // proper thing to do. + const sal_Int16 nWordType = i18n::WordType::ANYWORD_IGNOREWHITESPACES; + + //! In order to have less trouble with changing text size, e.g. because + //! of ligatures or German small sz being resolved, we need to process + //! the text replacements from end to start. + //! This way the offsets for the yet to be changed words will be + //! left unchanged by the already replaced text. + //! For this we temporarily save the changes to be done in this vector + std::vector< eeTransliterationChgData > aChanges; + eeTransliterationChgData aChgData; + + if (nTransliterationMode == TransliterationFlags::TITLE_CASE) + { + // for 'capitalize every word' we need to iterate over each word + + i18n::Boundary aSttBndry; + i18n::Boundary aEndBndry; + aSttBndry = _xBI->getWordBoundary( + aNodeStr, nStartPos, + GetLocale( EditPaM( pNode, nStartPos + 1 ) ), + nWordType, true /*prefer forward direction*/); + aEndBndry = _xBI->getWordBoundary( + aNodeStr, nEndPos, + GetLocale( EditPaM( pNode, nEndPos + 1 ) ), + nWordType, false /*prefer backward direction*/); + + // prevent backtracking to the previous word if selection is at word boundary + if (aSttBndry.endPos <= nStartPos) + { + aSttBndry = _xBI->nextWord( + aNodeStr, aSttBndry.endPos, + GetLocale( EditPaM( pNode, aSttBndry.endPos + 1 ) ), + nWordType); + } + // prevent advancing to the next word if selection is at word boundary + if (aEndBndry.startPos >= nEndPos) + { + aEndBndry = _xBI->previousWord( + aNodeStr, aEndBndry.startPos, + GetLocale( EditPaM( pNode, aEndBndry.startPos + 1 ) ), + nWordType); + } + + /* Nothing to do if user selection lies entirely outside of word start and end boundary computed above. + * Skip this node, because otherwise the below logic for constraining to the selection will fail */ + if (aSttBndry.startPos >= aSel.Max().GetIndex() || aEndBndry.endPos <= aSel.Min().GetIndex()) { + continue; + } + + // prevent going outside of the user's selection, which may + // start or end in the middle of a word + if (nNode == nStartNode) { + aSttBndry.startPos = std::max(aSttBndry.startPos, aSel.Min().GetIndex()); + aSttBndry.endPos = std::min(aSttBndry.endPos, aSel.Max().GetIndex()); + aEndBndry.startPos = std::max(aEndBndry.startPos, aSttBndry.startPos); + aEndBndry.endPos = std::min(aEndBndry.endPos, aSel.Max().GetIndex()); + } + + i18n::Boundary aCurWordBndry( aSttBndry ); + while (aCurWordBndry.endPos && aCurWordBndry.startPos <= aEndBndry.startPos) + { + nCurrentStart = aCurWordBndry.startPos; + nCurrentEnd = aCurWordBndry.endPos; + sal_Int32 nLen = nCurrentEnd - nCurrentStart; + DBG_ASSERT( nLen > 0, "invalid word length of 0" ); + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate(aNodeStr, + GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang, + nCurrentStart, nLen, &aOffsets )); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } +#if OSL_DEBUG_LEVEL > 1 + OUString aSelTxt ( GetSelected( aChgData.aSelection ) ); + (void) aSelTxt; +#endif + + aCurWordBndry = _xBI->nextWord(aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ), + nWordType); + } + DBG_ASSERT( nCurrentEnd >= aEndBndry.endPos, "failed to reach end of transliteration" ); + } + else if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE) + { + // for 'sentence case' we need to iterate sentence by sentence + + sal_Int32 nLastStart = _xBI->beginOfSentence( + aNodeStr, nEndPos, + GetLocale( EditPaM( pNode, nEndPos + 1 ) ) ); + sal_Int32 nLastEnd = _xBI->endOfSentence( + aNodeStr, nLastStart, + GetLocale( EditPaM( pNode, nLastStart + 1 ) ) ); + + // extend nCurrentStart, nCurrentEnd to the current sentence boundaries + nCurrentStart = _xBI->beginOfSentence( + aNodeStr, nStartPos, + GetLocale( EditPaM( pNode, nStartPos + 1 ) ) ); + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + + // prevent backtracking to the previous sentence if selection starts at end of a sentence + if (nCurrentEnd <= nStartPos) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the next real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = _xBI->nextWord( aNodeStr, nCurrentEnd, + GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ), + i18n::WordType::DICTIONARY_WORD); + + // now get new current sentence boundaries + nCurrentStart = _xBI->beginOfSentence( + aNodeStr, aBndry.startPos, + GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) ); + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + } + // prevent advancing to the next sentence if selection ends at start of a sentence + if (nLastStart >= nEndPos) + { + // now nCurrentStart is probably located on a non-letter word. (unless we + // are in Asian text with no spaces...) + // Thus to get the real sentence start we should locate the previous real word, + // that is one found by DICTIONARY_WORD + i18n::Boundary aBndry = _xBI->previousWord( aNodeStr, nLastStart, + GetLocale( EditPaM( pNode, nLastStart + 1 ) ), + i18n::WordType::DICTIONARY_WORD); + nLastEnd = _xBI->endOfSentence( + aNodeStr, aBndry.startPos, + GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) ); + if (nCurrentEnd > nLastEnd) + nCurrentEnd = nLastEnd; + } + + // prevent making any change outside of the user's selection + nCurrentStart = std::max(aSel.Min().GetIndex(), nCurrentStart); + nCurrentEnd = std::min(aSel.Max().GetIndex(), nCurrentEnd); + nLastStart = std::max(aSel.Min().GetIndex(), nLastStart); + nLastEnd = std::min(aSel.Max().GetIndex(), nLastEnd); + + while (nCurrentStart < nLastEnd) + { + const sal_Int32 nLen = nCurrentEnd - nCurrentStart; + DBG_ASSERT( nLen > 0, "invalid word length of 0" ); + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, + GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang, + nCurrentStart, nLen, &aOffsets )); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + i18n::Boundary aFirstWordBndry = _xBI->nextWord( + aNodeStr, nCurrentEnd, + GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ), + nWordType); + nCurrentStart = aFirstWordBndry.startPos; + nCurrentEnd = _xBI->endOfSentence( + aNodeStr, nCurrentStart, + GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) ); + } + DBG_ASSERT( nCurrentEnd >= nLastEnd, "failed to reach end of transliteration" ); + } + else + { + do + { + if ( bConsiderLanguage ) + { + nLanguage = GetLanguage( EditPaM( pNode, nCurrentStart+1 ), &nCurrentEnd ).nLang; + if ( nCurrentEnd > nEndPos ) + nCurrentEnd = nEndPos; + } + + const sal_Int32 nLen = nCurrentEnd - nCurrentStart; + + Sequence< sal_Int32 > aOffsets; + OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, nLanguage, nCurrentStart, nLen, &aOffsets ) ); + + if (aNodeStr != aNewText) + { + aChgData.nStart = nCurrentStart; + aChgData.nLen = nLen; + aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) ); + aChgData.aNewText = aNewText; + aChgData.aOffsets = aOffsets; + aChanges.push_back( aChgData ); + } + + nCurrentStart = nCurrentEnd; + } while( nCurrentEnd < nEndPos ); + } + + if (!aChanges.empty()) + { + // Create a single UndoAction on Demand for all the changes ... + if ( !pUndo && IsUndoEnabled() && !IsInUndo() ) + { + // adjust selection to include all changes + for (const eeTransliterationChgData & aChange : aChanges) + { + const EditSelection &rSel = aChange.aSelection; + if (aSel.Min().GetNode() == rSel.Min().GetNode() && + aSel.Min().GetIndex() > rSel.Min().GetIndex()) + aSel.Min().SetIndex( rSel.Min().GetIndex() ); + if (aSel.Max().GetNode() == rSel.Max().GetNode() && + aSel.Max().GetIndex() < rSel.Max().GetIndex()) + aSel.Max().SetIndex( rSel.Max().GetIndex() ); + } + aNewSel = aSel; + + ESelection aESel( CreateESel( aSel ) ); + pUndo.reset(new EditUndoTransliteration(pEditEngine, aESel, nTransliterationMode)); + + const bool bSingleNode = aSel.Min().GetNode()== aSel.Max().GetNode(); + const bool bHasAttribs = aSel.Min().GetNode()->GetCharAttribs().HasAttrib( aSel.Min().GetIndex(), aSel.Max().GetIndex() ); + if (bSingleNode && !bHasAttribs) + pUndo->SetText( aSel.Min().GetNode()->Copy( aSel.Min().GetIndex(), aSel.Max().GetIndex()-aSel.Min().GetIndex() ) ); + else + pUndo->SetText( CreateTextObject( aSel, nullptr ) ); + } + + // now apply the changes from end to start to leave the offsets of the + // yet unchanged text parts remain the same. + for (size_t i = 0; i < aChanges.size(); ++i) + { + eeTransliterationChgData& rData = aChanges[ aChanges.size() - 1 - i ]; + + bChanges = true; + if (rData.nLen != rData.aNewText.getLength()) + bLenChanged = true; + + // Change text without losing the attributes + const sal_Int32 nDiffs = + ReplaceTextOnly( rData.aSelection.Min().GetNode(), + rData.nStart, rData.aNewText, rData.aOffsets ); + + // adjust selection in end node to possibly changed size + if (aSel.Max().GetNode() == rData.aSelection.Max().GetNode()) + aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + nDiffs ); + + sal_Int32 nSelNode = maEditDoc.GetPos( rData.aSelection.Min().GetNode() ); + ParaPortion* pParaPortion = GetParaPortions()[nSelNode]; + pParaPortion->MarkSelectionInvalid( rData.nStart ); + } + } + } + + if ( pUndo ) + { + ESelection aESel( CreateESel( aNewSel ) ); + pUndo->SetNewSelection( aESel ); + InsertUndo( std::move(pUndo) ); + } + + if ( bChanges ) + { + TextModified(); + SetModifyFlag( true ); + if ( bLenChanged ) + UpdateSelections(); + if (IsUpdateLayout()) + FormatAndLayout(); + } + + return aNewSel; +} + + +short ImpEditEngine::ReplaceTextOnly( + ContentNode* pNode, + sal_Int32 nCurrentStart, + std::u16string_view rNewText, + const uno::Sequence< sal_Int32 >& rOffsets ) +{ + // Change text without losing the attributes + sal_Int32 nCharsAfterTransliteration = rOffsets.getLength(); + const sal_Int32* pOffsets = rOffsets.getConstArray(); + short nDiffs = 0; + for ( sal_Int32 n = 0; n < nCharsAfterTransliteration; n++ ) + { + sal_Int32 nCurrentPos = nCurrentStart+n; + sal_Int32 nDiff = (nCurrentPos-nDiffs) - pOffsets[n]; + + if ( !nDiff ) + { + DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" ); + pNode->SetChar( nCurrentPos, rNewText[n] ); + } + else if ( nDiff < 0 ) + { + // Replace first char, delete the rest... + DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" ); + pNode->SetChar( nCurrentPos, rNewText[n] ); + + DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" ); + GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff); + } + else + { + DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." ); + GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUStringChar(rNewText[n]) ); + + } + nDiffs = sal::static_int_cast< short >(nDiffs + nDiff); + } + + return nDiffs; +} + + +void ImpEditEngine::SetAsianCompressionMode( CharCompressType n ) +{ + if (n != mnAsianCompressionMode) + { + mnAsianCompressionMode = n; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +} + +void ImpEditEngine::SetKernAsianPunctuation( bool b ) +{ + if ( b != mbKernAsianPunctuation ) + { + mbKernAsianPunctuation = b; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +} + +void ImpEditEngine::SetAddExtLeading( bool bExtLeading ) +{ + if ( IsAddExtLeading() != bExtLeading ) + { + mbAddExtLeading = bExtLeading; + if ( ImplHasText() ) + { + FormatFullDoc(); + UpdateViews(); + } + } +}; + + +bool ImpEditEngine::ImplHasText() const +{ + return ( ( GetEditDoc().Count() > 1 ) || GetEditDoc().GetObject(0)->Len() ); +} + +sal_Int32 ImpEditEngine::LogicToTwips(sal_Int32 n) +{ + Size aSz(n, 0); + MapMode aTwipsMode( MapUnit::MapTwip ); + aSz = pRefDev->LogicToLogic( aSz, nullptr, &aTwipsMode ); + return aSz.Width(); +} + +double ImpEditEngine::roundToNearestPt(double fInput) const +{ + if (mbRoundToNearestPt) + { + double fInputPt = o3tl::convert(fInput, o3tl::Length::mm100, o3tl::Length::pt); + auto nInputRounded = basegfx::fround(fInputPt); + return o3tl::convert(double(nInputRounded), o3tl::Length::pt, o3tl::Length::mm100); + } + else + { + return fInput; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/impedit5.cxx b/editeng/source/editeng/impedit5.cxx new file mode 100644 index 0000000000..0f5af2f75d --- /dev/null +++ b/editeng/source/editeng/impedit5.cxx @@ -0,0 +1,845 @@ +/* -*- 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 <memory> +#include "impedit.hxx" +#include <editeng/editeng.hxx> +#include <svl/hint.hxx> +#include <sfx2/app.hxx> +#include <utility> + +void ImpEditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + if ( pStylePool != pSPool ) + { + pStylePool = pSPool; + } +} + +const SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara ) const +{ + const ContentNode* pNode = maEditDoc.GetObject( nPara ); + return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr; +} + +SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara ) +{ + ContentNode* pNode = maEditDoc.GetObject( nPara ); + return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr; +} + +void ImpEditEngine::SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle ) +{ + aSel.Adjust( maEditDoc ); + + sal_Int32 nStartPara = maEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndPara = maEditDoc.GetPos( aSel.Max().GetNode() ); + + bool _bUpdate = SetUpdateLayout( false ); + + for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ ) + SetStyleSheet( n, pStyle ); + + SetUpdateLayout( _bUpdate ); +} + +void ImpEditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + DBG_ASSERT( GetStyleSheetPool() || !pStyle, "SetStyleSheet: No StyleSheetPool registered!" ); + ContentNode* pNode = maEditDoc.GetObject( nPara ); + SfxStyleSheet* pCurStyle = pNode->GetStyleSheet(); + if ( pStyle != pCurStyle ) + { + if ( IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs() ) + { + OUString aPrevStyleName; + if ( pCurStyle ) + aPrevStyleName = pCurStyle->GetName(); + + OUString aNewStyleName; + if ( pStyle ) + aNewStyleName = pStyle->GetName(); + + InsertUndo( + std::make_unique<EditUndoSetStyleSheet>(pEditEngine, maEditDoc.GetPos( pNode ), + aPrevStyleName, pCurStyle ? pCurStyle->GetFamily() : SfxStyleFamily::Para, + aNewStyleName, pStyle ? pStyle->GetFamily() : SfxStyleFamily::Para, + pNode->GetContentAttribs().GetItems() ) ); + } + if ( pCurStyle ) + EndListening( *pCurStyle ); + pNode->SetStyleSheet( pStyle, maStatus.UseCharAttribs() ); + if ( pStyle ) + StartListening(*pStyle, DuplicateHandling::Allow); + + if (pNode->GetWrongList()) + pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len()); + ParaAttribsChanged( pNode ); + } + if (IsUpdateLayout()) + FormatAndLayout(); +} + +void ImpEditEngine::UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle ) +{ + SvxFont aFontFromStyle; + CreateFont( aFontFromStyle, pStyle->GetItemSet() ); + + bool bUsed = false; + for ( sal_Int32 nNode = 0; nNode < maEditDoc.Count(); nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + if ( pNode->GetStyleSheet() == pStyle ) + { + bUsed = true; + if (maStatus.UseCharAttribs()) + pNode->SetStyleSheet( pStyle, aFontFromStyle ); + else + pNode->SetStyleSheet( pStyle, false ); + + if (pNode->GetWrongList()) + pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len()); + ParaAttribsChanged( pNode ); + } + } + if ( bUsed ) + { + GetEditEnginePtr()->StyleSheetChanged( pStyle ); + if (IsUpdateLayout()) + FormatAndLayout(); + } +} + +void ImpEditEngine::RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle ) +{ + for ( sal_Int32 nNode = 0; nNode < maEditDoc.Count(); nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject(nNode); + if ( pNode->GetStyleSheet() == pStyle ) + { + pNode->SetStyleSheet( nullptr ); + ParaAttribsChanged( pNode ); + } + } + if (IsUpdateLayout()) + FormatAndLayout(); +} + +void ImpEditEngine::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + // So that not a lot of unnecessary formatting is done when destructing: + if (!mbDowning) + { + SfxHintId nId = rHint.GetId(); + if ( ( nId == SfxHintId::StyleSheetInDestruction ) || + ( nId == SfxHintId::StyleSheetErased ) ) + { + const SfxStyleSheetHint* pStyleSheetHint = static_cast<const SfxStyleSheetHint*>(&rHint); + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>( pStyleSheetHint->GetStyleSheet() ); + RemoveStyleFromParagraphs( pStyle ); + } + else if ( nId == SfxHintId::StyleSheetModified ) + { + const SfxStyleSheetHint* pStyleSheetHint = static_cast<const SfxStyleSheetHint*>(&rHint); + SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>( pStyleSheetHint->GetStyleSheet() ); + UpdateParagraphsWithStyleSheet( pStyle ); + } + else if ( nId == SfxHintId::Dying ) + { + if ( auto pStyle = dynamic_cast< SfxStyleSheet* >(&rBC) ) + RemoveStyleFromParagraphs( pStyle ); + } + else if ( nId == SfxHintId::DataChanged ) + { + if ( auto pStyle = dynamic_cast< SfxStyleSheet* >(&rBC) ) + UpdateParagraphsWithStyleSheet( pStyle ); + } + } + if (rHint.GetId() == SfxHintId::Dying && dynamic_cast<const SfxApplication*>(&rBC)) + Dispose(); +} + +std::unique_ptr<EditUndoSetAttribs> ImpEditEngine::CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet ) +{ + DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "CreateAttribUndo: Incorrect selection "); + aSel.Adjust( maEditDoc ); + + ESelection aESel( CreateESel( aSel ) ); + + sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + DBG_ASSERT( nStartNode <= nEndNode, "CreateAttribUndo: Start > End ?!" ); + + std::unique_ptr<EditUndoSetAttribs> pUndo; + if ( rSet.GetPool() != &maEditDoc.GetItemPool() ) + { + SfxItemSet aTmpSet( GetEmptyItemSet() ); + aTmpSet.Put( rSet ); + pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, std::move(aTmpSet)) ); + } + else + { + pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, rSet) ); + } + + SfxItemPool* pPool = pUndo->GetNewAttribs().GetPool(); + + for ( sal_Int32 nPara = nStartNode; nPara <= nEndNode; nPara++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nPara ); + DBG_ASSERT( maEditDoc.GetObject( nPara ), "Node not found: CreateAttribUndo" ); + ContentAttribsInfo* pInf = new ContentAttribsInfo( pNode->GetContentAttribs().GetItems() ); + pUndo->AppendContentInfo(pInf); + + for ( sal_Int32 nAttr = 0; nAttr < pNode->GetCharAttribs().Count(); nAttr++ ) + { + const EditCharAttrib& rAttr = *pNode->GetCharAttribs().GetAttribs()[nAttr]; + if (rAttr.GetLen()) + { + EditCharAttrib* pNew = MakeCharAttrib(*pPool, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd()); + pInf->AppendCharAttrib(pNew); + } + } + } + return pUndo; +} + +ViewShellId ImpEditEngine::CreateViewShellId() +{ + ViewShellId nRet(-1); + + const EditView* pEditView = pEditEngine ? pEditEngine->GetActiveView() : nullptr; + const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr; + if (pViewShell) + nRet = pViewShell->GetViewShellId(); + + return nRet; +} + +void ImpEditEngine::UndoActionStart( sal_uInt16 nId, const ESelection& aSel ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() ); + DBG_ASSERT( !moUndoMarkSelection, "UndoAction SelectionMarker?" ); + moUndoMarkSelection = aSel; + } +} + +void ImpEditEngine::UndoActionStart( sal_uInt16 nId ) +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() ); + DBG_ASSERT( !moUndoMarkSelection, "UndoAction SelectionMarker?" ); + } +} + +void ImpEditEngine::UndoActionEnd() +{ + if ( IsUndoEnabled() && !IsInUndo() ) + { + GetUndoManager().LeaveListAction(); + moUndoMarkSelection.reset(); + } +} + +void ImpEditEngine::InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge ) +{ + DBG_ASSERT( !IsInUndo(), "InsertUndo in Undo mode!" ); + if ( moUndoMarkSelection ) + { + GetUndoManager().AddUndoAction( std::make_unique<EditUndoMarkSelection>(pEditEngine, *moUndoMarkSelection) ); + moUndoMarkSelection.reset(); + } + GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge ); + + mbLastTryMerge = bTryMerge; +} + +void ImpEditEngine::ResetUndoManager() +{ + if ( HasUndoManager() ) + GetUndoManager().Clear(); +} + +void ImpEditEngine::EnableUndo( bool bEnable ) +{ + // When switching the mode Delete list: + if ( bEnable != IsUndoEnabled() ) + ResetUndoManager(); + + mbUndoEnabled = bEnable; +} + +void ImpEditEngine::Undo( EditView* pView ) +{ + if ( HasUndoManager() && GetUndoManager().GetUndoActionCount() ) + { + SetActiveView( pView ); + GetUndoManager().Undo(); + } +} + +void ImpEditEngine::Redo( EditView* pView ) +{ + if ( HasUndoManager() && GetUndoManager().GetRedoActionCount() ) + { + SetActiveView( pView ); + GetUndoManager().Redo(); + } +} + +SfxItemSet ImpEditEngine::GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib ) +{ + + aSel.Adjust( maEditDoc ); + + SfxItemSet aCurSet( GetEmptyItemSet() ); + + sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + assert( pNode && "Node not found: GetAttrib" ); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // Can also be == nStart! + + // Problem: Templates... + // => Other way: + // 1) Hard character attributes, as usual... + // 2) Examine Style and paragraph attributes only when OFF... + + // First the very hard formatting... + if (pNode) + EditDoc::FindAttribs( pNode, nStartPos, nEndPos, aCurSet ); + + if( nOnlyHardAttrib != EditEngineAttribs::OnlyHard ) + { + // and then paragraph formatting and template... + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich ); + aCurSet.Put( rItem ); + } + else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nWhich ); + aCurSet.Put( rItem ); + } + } + else if ( aCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem* pItem = nullptr; + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + pItem = &pNode->GetContentAttribs().GetItem( nWhich ); + } + else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET ) + { + pItem = &pNode->GetContentAttribs().GetItems().Get( nWhich ); + } + // pItem can only be NULL when nOnlyHardAttrib... + if ( !pItem || ( *pItem != aCurSet.Get( nWhich ) ) ) + { + // Problem: When Paragraph style with for example font, + // but the Font is hard and completely different, + // wrong in selection if invalidated.... + // => better not invalidate, instead CHANGE! + // It would be better to fill each paragraph with + // an itemset and compare this in large. + if ( nWhich <= EE_PARA_END ) + aCurSet.InvalidateItem( nWhich ); + } + } + } + } + } + + // fill empty slots with defaults ... + if ( nOnlyHardAttrib == EditEngineAttribs::All ) + { + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + aCurSet.Put( maEditDoc.GetItemPool().GetDefaultItem( nWhich ) ); + } + } + } + return aCurSet; +} + + +SfxItemSet ImpEditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const +{ + // Optimized function with fewer Puts(), which cause unnecessary cloning from default items. + // If this works, change GetAttribs( EditSelection ) to use this for each paragraph and merge the results! + + + ContentNode* pNode = const_cast<ContentNode*>(maEditDoc.GetObject(nPara)); + DBG_ASSERT( pNode, "GetAttribs - unknown paragraph!" ); + DBG_ASSERT( nStart <= nEnd, "getAttribs: Start > End not supported!" ); + + SfxItemSet aAttribs(GetEmptyItemSet()); + + if ( pNode ) + { + if ( nEnd > pNode->Len() ) + nEnd = pNode->Len(); + + if ( nStart > nEnd ) + nStart = nEnd; + + // StyleSheet / Parattribs... + + if ( pNode->GetStyleSheet() && ( nFlags & GetAttribsFlags::STYLESHEET ) ) + aAttribs.Set(pNode->GetStyleSheet()->GetItemSet()); + + if ( nFlags & GetAttribsFlags::PARAATTRIBS ) + aAttribs.Put( pNode->GetContentAttribs().GetItems() ); + + // CharAttribs... + + if ( nFlags & GetAttribsFlags::CHARATTRIBS ) + { + // Make testing easier... + pNode->GetCharAttribs().OptimizeRanges(); + + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (const auto & nAttr : rAttrs) + { + const EditCharAttrib& rAttr = *nAttr; + + if ( nStart == nEnd ) + { + sal_Int32 nCursorPos = nStart; + if ( ( rAttr.GetStart() <= nCursorPos ) && ( rAttr.GetEnd() >= nCursorPos ) ) + { + // To be used the attribute has to start BEFORE the position, or it must be a + // new empty attr AT the position, or we are on position 0. + if ( ( rAttr.GetStart() < nCursorPos ) || rAttr.IsEmpty() || !nCursorPos ) + { + // maybe this attrib ends here and a new attrib with 0 Len may follow and be valid here, + // but that s no problem, the empty item will come later and win. + aAttribs.Put( *rAttr.GetItem() ); + } + } + } + else + { + // Check every attribute covering the area, partial or full. + if ( ( rAttr.GetStart() < nEnd ) && ( rAttr.GetEnd() > nStart ) ) + { + if ( ( rAttr.GetStart() <= nStart ) && ( rAttr.GetEnd() >= nEnd ) ) + { + // full coverage + aAttribs.Put( *rAttr.GetItem() ); + } + else + { + // OptimizeRanges() assures that not the same attr can follow for full coverage + // only partial, check with current, when using para/style, otherwise invalid. + if ( !( nFlags & (GetAttribsFlags::PARAATTRIBS|GetAttribsFlags::STYLESHEET) ) || + ( *rAttr.GetItem() != aAttribs.Get( rAttr.Which() ) ) ) + { + aAttribs.InvalidateItem( rAttr.Which() ); + } + } + } + } + + if ( rAttr.GetStart() > nEnd ) + { + break; + } + } + } + } + + return aAttribs; +} + + +void ImpEditEngine::SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial, bool bSetSelection ) +{ + aSel.Adjust( maEditDoc ); + + // When no selection => use the Attribute on the word. + // ( the RTF-parser should actually never call the Method without a Range ) + if ( nSpecial == SetAttribsMode::WholeWord && !aSel.HasRange() ) + aSel = SelectWord( aSel, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false ); + + sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + + if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs()) + { + std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, rSet ); + pUndo->SetSpecial( nSpecial ); + pUndo->SetUpdateSelection(bSetSelection); + InsertUndo( std::move(pUndo) ); + } + + bool bCheckLanguage = false; + if ( GetStatus().DoOnlineSpelling() ) + { + bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET ); + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + bool bParaAttribFound = false; + bool bCharAttribFound = false; + + DBG_ASSERT( maEditDoc.GetObject( nNode ), "Node not found: SetAttribs" ); + DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" ); + + ContentNode* pNode = maEditDoc.GetObject( nNode ); + ParaPortion* pPortion = GetParaPortions()[nNode]; + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + // Iterate over the Items... + for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++) + { + if ( rSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rSet.Get( nWhich ); + if ( nWhich <= EE_PARA_END ) + { + pNode->GetContentAttribs().GetItems().Put( rItem ); + bParaAttribFound = true; + } + else + { + maEditDoc.InsertAttrib( pNode, nStartPos, nEndPos, rItem ); + bCharAttribFound = true; + if ( nSpecial == SetAttribsMode::Edge ) + { + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs) + { + EditCharAttrib& rAttr = *rAttrib; + if (rAttr.GetStart() > nEndPos) + break; + + if (rAttr.GetEnd() == nEndPos && rAttr.Which() == nWhich) + { + rAttr.SetEdge(true); + break; + } + } + } + } + } + } + + if ( bParaAttribFound ) + { + ParaAttribsChanged( pPortion->GetNode() ); + } + else if ( bCharAttribFound ) + { + mbFormatted = false; + if ( !pNode->Len() || ( nStartPos != nEndPos ) ) + { + pPortion->MarkSelectionInvalid( nStartPos ); + if ( bCheckLanguage ) + pNode->GetWrongList()->SetInvalidRange(nStartPos, nEndPos); + } + } + } +} + +void ImpEditEngine::RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich ) +{ + aSel.Adjust( maEditDoc ); + + sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() ); + sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() ); + bool bRemoveParaAttribs = eMode == EERemoveParaAttribsMode::RemoveAll; + const SfxItemSet* _pEmptyItemSet = bRemoveParaAttribs ? &GetEmptyItemSet() : nullptr; + + if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs()) + { + // Possibly a special Undo, or itemset* + std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, GetEmptyItemSet() ); + pUndo->SetRemoveAttribs( true ); + pUndo->SetRemoveParaAttribs( bRemoveParaAttribs ); + pUndo->SetRemoveWhich( nWhich ); + InsertUndo( std::move(pUndo) ); + } + + // iterate over the paragraphs ... + for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { + ContentNode* pNode = maEditDoc.GetObject( nNode ); + ParaPortion* pPortion = GetParaPortions()[nNode]; + + DBG_ASSERT( maEditDoc.GetObject( nNode ), "Node not found: SetAttribs" ); + DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" ); + + const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0; + const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart! + + // Optimize: If whole paragraph, then RemoveCharAttribs (nPara)? + bool bChanged = maEditDoc.RemoveAttribs( pNode, nStartPos, nEndPos, nWhich ); + if ( bRemoveParaAttribs ) + { + SetParaAttribs( nNode, *_pEmptyItemSet ); // Invalidated + } + else if (eMode == EERemoveParaAttribsMode::RemoveCharItems) + { + // For 'Format-Standard' also the character attributes should + // disappear, which were set as paragraph attributes by the + // DrawingEngine. These could not have been set by the user anyway. + + // #106871# Not when nWhich + // Would have been better to offer a separate method for format/standard... + if ( !nWhich ) + { + SfxItemSet aAttribs( GetParaAttribs( nNode ) ); + for ( sal_uInt16 nW = EE_CHAR_START; nW <= EE_CHAR_END; nW++ ) + aAttribs.ClearItem( nW ); + SetParaAttribs( nNode, aAttribs ); + } + } + + if ( bChanged && !bRemoveParaAttribs ) + { + mbFormatted = false; + pPortion->MarkSelectionInvalid( nStartPos ); + } + } +} + +void ImpEditEngine::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures ) +{ + ContentNode* pNode = maEditDoc.GetObject( nPara ); + ParaPortion* pPortion = GetParaPortions().SafeGetObject( nPara ); + + DBG_ASSERT( pNode, "Node not found: RemoveCharAttribs" ); + DBG_ASSERT( pPortion, "Portion not found: RemoveCharAttribs" ); + + if ( !pNode || !pPortion ) + return; + + size_t nAttr = 0; + CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttr = GetAttrib(rAttrs, nAttr); + while ( pAttr ) + { + if ( ( !pAttr->IsFeature() || bRemoveFeatures ) && + ( !nWhich || ( pAttr->GetItem()->Which() == nWhich ) ) ) + { + pNode->GetCharAttribs().Remove(nAttr); + nAttr--; + } + nAttr++; + pAttr = GetAttrib(rAttrs, nAttr); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + pPortion->MarkSelectionInvalid( 0 ); +} + +void ImpEditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + ContentNode* pNode = maEditDoc.GetObject( nPara ); + + if ( !pNode ) + return; + + if ( pNode->GetContentAttribs().GetItems() == rSet ) + return; + + if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs()) + { + if ( rSet.GetPool() != &maEditDoc.GetItemPool() ) + { + SfxItemSet aTmpSet( GetEmptyItemSet() ); + aTmpSet.Put( rSet ); + InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), aTmpSet)); + } + else + { + InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), rSet)); + } + } + + bool bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) || + ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET ); + + pNode->GetContentAttribs().GetItems().Set( rSet ); + + if ( bCheckLanguage && pNode->GetWrongList() ) + pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len()); + + if (maStatus.UseCharAttribs()) + pNode->CreateDefFont(); + + ParaAttribsChanged( pNode ); +} + +const SfxItemSet& ImpEditEngine::GetParaAttribs( sal_Int32 nPara ) const +{ + const ContentNode* pNode = maEditDoc.GetObject( nPara ); + assert(pNode && "Node not found: GetParaAttribs"); + return pNode->GetContentAttribs().GetItems(); +} + +bool ImpEditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const ContentNode* pNode = maEditDoc.GetObject( nPara ); + assert(pNode && "Node not found: HasParaAttrib"); + return pNode->GetContentAttribs().HasItem( nWhich ); +} + +const SfxPoolItem& ImpEditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const ContentNode* pNode = maEditDoc.GetObject(nPara); + assert(pNode && "Node not found: GetParaAttrib"); + return pNode->GetContentAttribs().GetItem(nWhich); +} + +void ImpEditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const +{ + rLst.clear(); + const ContentNode* pNode = maEditDoc.GetObject( nPara ); + if ( !pNode ) + return; + + rLst.reserve(pNode->GetCharAttribs().Count()); + const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); + for (const auto & i : rAttrs) + { + const EditCharAttrib& rAttr = *i; + EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem()); + rLst.push_back(aEEAttr); + } +} + +void ImpEditEngine::ParaAttribsToCharAttribs( ContentNode* pNode ) +{ + pNode->GetCharAttribs().DeleteEmptyAttribs(); + sal_Int32 nEndPos = pNode->Len(); + for ( sal_uInt16 nWhich = EE_CHAR_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + if ( pNode->GetContentAttribs().HasItem( nWhich ) ) + { + const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich ); + // Fill the gap: + sal_Int32 nLastEnd = 0; + const EditCharAttrib* pAttr = pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd ); + while ( pAttr ) + { + nLastEnd = pAttr->GetEnd(); + if ( pAttr->GetStart() > nLastEnd ) + maEditDoc.InsertAttrib( pNode, nLastEnd, pAttr->GetStart(), rItem ); + // #112831# Last Attr might go from 0xffff to 0x0000 + pAttr = nLastEnd ? pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd ) : nullptr; + } + + // And the Rest: + if ( nLastEnd < nEndPos ) + maEditDoc.InsertAttrib( pNode, nLastEnd, nEndPos, rItem ); + } + } + mbFormatted = false; + // Portion does not need to be invalidated here, happens elsewhere. +} + +IdleFormattter::IdleFormattter() + : Idle("editeng::ImpEditEngine aIdleFormatter") +{ + pView = nullptr; + nRestarts = 0; +} + +IdleFormattter::~IdleFormattter() +{ + pView = nullptr; +} + +void IdleFormattter::DoIdleFormat( EditView* pV ) +{ + pView = pV; + + if ( IsActive() ) + nRestarts++; + + if ( nRestarts > 4 ) + ForceTimeout(); + else + Start(); +} + +void IdleFormattter::ForceTimeout() +{ + if ( IsActive() ) + { + Stop(); + Invoke(); + } +} + +ImplIMEInfos::ImplIMEInfos( const EditPaM& rPos, OUString _aOldTextAfterStartPos ) + : aOldTextAfterStartPos(std::move( _aOldTextAfterStartPos )), + aPos(rPos), + nLen(0), + bWasCursorOverwrite(false) + { + } + +ImplIMEInfos::~ImplIMEInfos() +{ +} + +void ImplIMEInfos::CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL ) +{ + nLen = nL; + pAttribs.reset( new ExtTextInputAttr[ nL ] ); + memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) ); +} + +void ImplIMEInfos::DestroyAttribs() +{ + pAttribs.reset(); + nLen = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/misspellrange.cxx b/editeng/source/editeng/misspellrange.cxx new file mode 100644 index 0000000000..562a9905c2 --- /dev/null +++ b/editeng/source/editeng/misspellrange.cxx @@ -0,0 +1,21 @@ +/* -*- 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/. + */ + +#include <editeng/misspellrange.hxx> + +namespace editeng { + +MisspellRange::MisspellRange(size_t nStart, size_t nEnd) : mnStart(nStart), mnEnd(nEnd) {} + +MisspellRanges::MisspellRanges(sal_Int32 nParagraph, std::vector<MisspellRange>&& rRanges) : + mnParagraph(nParagraph), maRanges(std::move(rRanges)) {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/section.cxx b/editeng/source/editeng/section.cxx new file mode 100644 index 0000000000..f65b0158a9 --- /dev/null +++ b/editeng/source/editeng/section.cxx @@ -0,0 +1,19 @@ +/* -*- 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/. + */ + +#include <editeng/section.hxx> + +namespace editeng { + +Section::Section(sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd) : + mnParagraph(nPara), mnStart(nStart), mnEnd(nEnd){} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/textconv.cxx b/editeng/source/editeng/textconv.cxx new file mode 100644 index 0000000000..e97be254d6 --- /dev/null +++ b/editeng/source/editeng/textconv.cxx @@ -0,0 +1,543 @@ +/* -*- 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 "impedit.hxx" +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <editeng/langitem.hxx> +#include <editeng/fontitem.hxx> +#include "textconv.hxx" +#include <osl/diagnose.h> +#include <vcl/weld.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::linguistic2; + +TextConvWrapper::TextConvWrapper( weld::Widget* pWindow, + const Reference< XComponentContext >& rxContext, + const lang::Locale& rSourceLocale, + const lang::Locale& rTargetLocale, + const vcl::Font* pTargetFont, + sal_Int32 nOptions, + bool bIsInteractive, + bool bIsStart, + EditView* pView ) : + HangulHanjaConversion( pWindow, rxContext, rSourceLocale, rTargetLocale, pTargetFont, nOptions, bIsInteractive ) + , m_nConvTextLang(LANGUAGE_NONE) + , m_nUnitOffset(0) + , m_nLastPos(0) + , m_aConvSel(pView->GetSelection()) + , m_pEditView(pView) + , m_pWin(pWindow) + , m_bStartChk(false) + , m_bStartDone(bIsStart) + , m_bEndDone(false) + , m_bAllowChange(false) +{ + DBG_ASSERT( pWindow, "TextConvWrapper: window missing" ); + + m_aConvSel.Adjust(); // make Start <= End +} + + +TextConvWrapper::~TextConvWrapper() +{ +} + + +bool TextConvWrapper::ConvNext_impl() +{ + // modified version of SvxSpellWrapper::SpellNext + + if( m_bStartChk ) + m_bStartDone = true; + else + m_bEndDone = true; + + if ( m_bStartDone && m_bEndDone ) + { + if ( ConvMore_impl() ) // examine another document? + { + m_bStartDone = true; + m_bEndDone = false; + ConvStart_impl( SvxSpellArea::Body ); + return true; + } + return false; + + } + + if ( m_bStartDone && m_bEndDone ) + { + if ( ConvMore_impl() ) // examine another document? + { + m_bStartDone = true; + m_bEndDone = false; + ConvStart_impl( SvxSpellArea::Body ); + return true; + } + } + else if (!m_aConvSel.HasRange()) + { + m_bStartChk = !m_bStartDone; + ConvStart_impl( m_bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + return true; + } + + return false; +} + +void TextConvWrapper::FindConvText_impl() +{ + // modified version of SvxSpellWrapper::FindSpellError + weld::WaitObject aWait(m_pWin); + while ( true ) + { + if (ConvContinue_impl() || !ConvNext_impl()) + break; + } +} + +bool TextConvWrapper::ConvMore_impl() +{ + // modified version of SvxSpellWrapper::SpellMore + + bool bMore = false; + EditEngine* pEE = m_pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + if ( pConvInfo->bMultipleDoc ) + { + bMore = pEE->ConvertNextDocument(); + if ( bMore ) + { + // The text has been entered in this engine ... + m_pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + } + return bMore; +} + + +void TextConvWrapper::ConvStart_impl( SvxSpellArea eArea ) +{ + // modified version of EditSpellWrapper::SpellStart + + EditEngine* pEE = m_pEditView->GetEditEngine(); + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + + if ( eArea == SvxSpellArea::BodyStart ) + { + // Is called when Spell-forward has reached the end, and to start over + if ( m_bEndDone ) + { + pConvInfo->bConvToEnd = false; + pConvInfo->aConvTo = pConvInfo->aConvStart; + pConvInfo->aConvContinue = EPaM( 0, 0 ); + m_pEditView->GetImpEditView()->SetEditSelection( + pEE->GetEditDoc().GetStartPaM() ); + } + else + { + pConvInfo->bConvToEnd = true; + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetStartPaM() ); + } + } + else if ( eArea == SvxSpellArea::BodyEnd ) + { + // Is called when Spell-forward starts + pConvInfo->bConvToEnd = true; + if (m_aConvSel.HasRange()) + { + // user selection: convert to end of selection + pConvInfo->aConvTo.nPara = m_aConvSel.nEndPara; + pConvInfo->aConvTo.nIndex = m_aConvSel.nEndPos; + pConvInfo->bConvToEnd = false; + } + else + { + // nothing selected: convert to end of document + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + } + else if ( eArea == SvxSpellArea::Body ) + { + // called by ConvNext_impl... + pConvInfo->aConvContinue = pConvInfo->aConvStart; + pConvInfo->aConvTo = pImpEE->CreateEPaM( + pEE->GetEditDoc().GetEndPaM() ); + } + else + { + OSL_FAIL( "ConvStart_impl: Unknown Area!" ); + } +} + + +bool TextConvWrapper::ConvContinue_impl() +{ + // modified version of EditSpellWrapper::SpellContinue + + // get next convertible text portion and its language + m_aConvText.clear(); + m_nConvTextLang = LANGUAGE_NONE; + m_pEditView->GetImpEditEngine()->ImpConvert( m_aConvText, m_nConvTextLang, + m_pEditView, GetSourceLanguage(), m_aConvSel, + m_bAllowChange, GetTargetLanguage(), GetTargetFont() ); + return !m_aConvText.isEmpty(); +} + + +void TextConvWrapper::SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ) +{ + ESelection aOldSel = m_pEditView->GetSelection(); + m_pEditView->SetSelection( rESel ); + + // set new language attribute + SfxItemSet aNewSet( m_pEditView->GetEmptyItemSet() ); + aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) ); + + // new font to be set? + DBG_ASSERT( pFont, "target font missing?" ); + if (pFont) + { + // set new font attribute + SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) ); + aFontItem.SetFamilyName( pFont->GetFamilyName()); + aFontItem.SetFamily( pFont->GetFamilyType()); + aFontItem.SetStyleName( pFont->GetStyleName()); + aFontItem.SetPitch( pFont->GetPitch()); + aFontItem.SetCharSet(pFont->GetCharSet()); + aNewSet.Put( aFontItem ); + } + + // apply new attributes + m_pEditView->SetAttribs( aNewSet ); + + m_pEditView->SetSelection( aOldSel ); +} + + +void TextConvWrapper::SelectNewUnit_impl( + const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) +{ + const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd; + DBG_ASSERT( bOK, "invalid arguments" ); + if (!bOK) + return; + + ESelection aSelection = m_pEditView->GetSelection(); + DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara, + "paragraph mismatch in selection" ); + aSelection.nStartPos = (m_nLastPos + m_nUnitOffset + nUnitStart); + aSelection.nEndPos = (m_nLastPos + m_nUnitOffset + nUnitEnd); + m_pEditView->SetSelection( aSelection ); +} + + +void TextConvWrapper::GetNextPortion( + OUString& /* [out] */ rNextPortion, + LanguageType& /* [out] */ rLangOfPortion, + bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText ) +{ + m_bAllowChange = _bAllowImplicitChangesForNotConvertibleText; + + FindConvText_impl(); + rNextPortion = m_aConvText; + rLangOfPortion = m_nConvTextLang; + m_nUnitOffset = 0; + + ESelection aSelection = m_pEditView->GetSelection(); + DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara, + "paragraph mismatch in selection" ); + DBG_ASSERT( aSelection.nStartPos <= aSelection.nEndPos, + "start pos > end pos" ); + m_nLastPos = aSelection.nStartPos; +} + + +void TextConvWrapper::HandleNewUnit( + const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) +{ + SelectNewUnit_impl( nUnitStart, nUnitEnd ); +} + +#ifdef DBG_UTIL +namespace +{ + bool IsSimilarChinese( LanguageType nLang1, LanguageType nLang2 ) + { + using namespace editeng; + return (HangulHanjaConversion::IsTraditional(nLang1) && HangulHanjaConversion::IsTraditional(nLang2)) || + (HangulHanjaConversion::IsSimplified(nLang1) && HangulHanjaConversion::IsSimplified(nLang2)); + } +} +#endif + +void TextConvWrapper::ReplaceUnit( + const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd, + const OUString& rOrigText, + const OUString& rReplaceWith, + const css::uno::Sequence< sal_Int32 > &rOffsets, + ReplacementAction eAction, + LanguageType *pNewUnitLanguage ) +{ + const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd; + DBG_ASSERT( bOK, "invalid arguments" ); + if (!bOK) + return; + + // select current unit + SelectNewUnit_impl( nUnitStart, nUnitEnd ); + + OUString aOrigTxt( m_pEditView->GetSelected() ); + OUString aNewTxt( rReplaceWith ); + switch (eAction) + { + case eExchange : + break; + case eReplacementBracketed : + aNewTxt = aOrigTxt + "(" + rReplaceWith + ")"; + break; + case eOriginalBracketed : + aNewTxt = rReplaceWith + "(" + aOrigTxt + ")"; + break; + case eReplacementAbove : + case eOriginalAbove : + case eReplacementBelow : + case eOriginalBelow : + OSL_FAIL( "Rubies not supported" ); + break; + default: + OSL_FAIL( "unexpected case" ); + } + m_nUnitOffset = m_nUnitOffset + nUnitStart + aNewTxt.getLength(); + + // remember current original language for later use + ImpEditEngine *pImpEditEng = m_pEditView->GetImpEditEngine(); + ESelection aOldSel = m_pEditView->GetSelection(); + //EditSelection aOldEditSel = pEditView->GetImpEditView()->GetEditSelection(); + +#ifdef DBG_UTIL + LanguageType nOldLang = pImpEditEng->GetLanguage( pImpEditEng->CreateSel( aOldSel ).Min() ).nLang; +#endif + + pImpEditEng->UndoActionStart( EDITUNDO_INSERT ); + + // according to FT we should currently not bother about keeping + // attributes in Hangul/Hanja conversion and leave that untouched. + // Thus we do this only for Chinese translation... + bool bIsChineseConversion = IsChinese( GetSourceLanguage() ); + if (bIsChineseConversion) + ChangeText( aNewTxt, rOrigText, &rOffsets, &aOldSel ); + else + ChangeText( aNewTxt, rOrigText, nullptr, nullptr ); + + // change language and font if necessary + if (bIsChineseConversion) + { + DBG_ASSERT( GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED || GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL, + "TextConvWrapper::ReplaceUnit : unexpected target language" ); + + ESelection aNewSel( aOldSel ); + aNewSel.nStartPos = aNewSel.nStartPos - aNewTxt.getLength(); + + if (pNewUnitLanguage) + { +#ifdef DBG_UTIL + DBG_ASSERT(!IsSimilarChinese( *pNewUnitLanguage, nOldLang ), + "similar language should not be changed!"); +#endif + SetLanguageAndFont( aNewSel, *pNewUnitLanguage, EE_CHAR_LANGUAGE_CJK, + GetTargetFont(), EE_CHAR_FONTINFO_CJK ); + } + } + + pImpEditEng->UndoActionEnd(); + + // adjust ConvContinue / ConvTo if necessary + ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine(); + ConvInfo* pConvInfo = pImpEE->GetConvInfo(); + sal_Int32 nDelta = aNewTxt.getLength() - aOrigTxt.getLength(); + if (nDelta != 0) + { + // Note: replacement is always done in the current paragraph + // which is the one ConvContinue points to + pConvInfo->aConvContinue.nIndex = pConvInfo->aConvContinue.nIndex + nDelta; + + // if that is the same as the one where the conversions ends + // the end needs to be updated also + if (pConvInfo->aConvTo.nPara == pConvInfo->aConvContinue.nPara) + pConvInfo->aConvTo.nIndex = pConvInfo->aConvTo.nIndex + nDelta; + } +} + + +void TextConvWrapper::ChangeText( const OUString &rNewText, + std::u16string_view rOrigText, + const uno::Sequence< sal_Int32 > *pOffsets, + ESelection *pESelection ) +{ + //!! code is a modified copy of SwHHCWrapper::ChangeText from sw !! + + DBG_ASSERT( !rNewText.isEmpty(), "unexpected empty string" ); + if (rNewText.isEmpty()) + return; + + if (pOffsets && pESelection) // try to keep as much attributation as possible ? + { + pESelection->Adjust(); + + // remember cursor start position for later setting of the cursor + const sal_Int32 nStartIndex = pESelection->nStartPos; + + const sal_Int32 nIndices = pOffsets->getLength(); + const sal_Int32 *pIndices = pOffsets->getConstArray(); + const sal_Int32 nConvTextLen = rNewText.getLength(); + sal_Int32 nPos = 0; + sal_Int32 nChgPos = -1; + sal_Int32 nConvChgPos = -1; + + // offset to calculate the position in the text taking into + // account that text may have been replaced with new text of + // different length. Negative values allowed! + sal_Int32 nCorrectionOffset = 0; + + DBG_ASSERT(nIndices == 0 || nIndices == nConvTextLen, + "mismatch between string length and sequence length!" ); + + // find all substrings that need to be replaced (and only those) + while (true) + { + // get index in original text that matches nPos in new text + sal_Int32 nIndex; + if (nPos < nConvTextLen) + nIndex = nPos < nIndices ? pIndices[nPos] : nPos; + else + { + nPos = nConvTextLen; + nIndex = rOrigText.size(); + } + + // end of string also terminates non-matching char sequence + if (nPos == nConvTextLen || rOrigText[nIndex] == rNewText[nPos]) + { + // substring that needs to be replaced found? + if (nChgPos>=0 && nConvChgPos>=0) + { + const sal_Int32 nChgLen = nIndex - nChgPos; + const sal_Int32 nConvChgLen = nPos - nConvChgPos; + OUString aInNew( rNewText.copy( nConvChgPos, nConvChgLen ) ); + + // set selection to sub string to be replaced in original text + ESelection aSel( *pESelection ); + sal_Int32 nChgInNodeStartIndex = nStartIndex + nCorrectionOffset + nChgPos; + aSel.nStartPos = nChgInNodeStartIndex; + aSel.nEndPos = nChgInNodeStartIndex + nChgLen; + m_pEditView->SetSelection( aSel ); + + // replace selected sub string with the corresponding + // sub string from the new text while keeping as + // much from the attributes as possible + ChangeText_impl( aInNew, true ); + + nCorrectionOffset += nConvChgLen - nChgLen; + + nChgPos = -1; + nConvChgPos = -1; + } + } + else + { + // begin of non-matching char sequence found ? + if (nChgPos<0 && nConvChgPos<0) + { + nChgPos = nIndex; + nConvChgPos = nPos; + } + } + if (nPos >= nConvTextLen) + break; + ++nPos; + } + + // set cursor to the end of the inserted text + // (as it would happen after ChangeText_impl (Delete and Insert) + // of the whole text in the 'else' branch below) + pESelection->nStartPos = pESelection->nEndPos = nStartIndex + nConvTextLen; + } + else + { + ChangeText_impl( rNewText, false ); + } +} + + +void TextConvWrapper::ChangeText_impl( const OUString &rNewText, bool bKeepAttributes ) +{ + if (bKeepAttributes) + { + // save attributes to be restored + SfxItemSet aSet( m_pEditView->GetAttribs() ); + + // replace old text and select new text + m_pEditView->InsertText( rNewText, true ); + + // since 'SetAttribs' below function like merging with the attributes + // from the itemset with any existing ones we have to get rid of all + // all attributes now. (Those attributes that may take effect left + // to the position where the new text gets inserted after the old text + // was deleted) + m_pEditView->RemoveAttribs(EERemoveParaAttribsMode::RemoveNone, 0); + // apply saved attributes to new inserted text + m_pEditView->SetAttribs( aSet ); + } + else + { + m_pEditView->InsertText( rNewText ); + } +} + + +void TextConvWrapper::Convert() +{ + m_bStartChk = false; + ConvStart_impl( SvxSpellArea::BodyEnd ); + ConvertDocument(); +} + + +bool TextConvWrapper::HasRubySupport() const +{ + return false; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/editeng/textconv.hxx b/editeng/source/editeng/textconv.hxx new file mode 100644 index 0000000000..96525a98f5 --- /dev/null +++ b/editeng/source/editeng/textconv.hxx @@ -0,0 +1,107 @@ +/* -*- 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 . + */ +#pragma once + +#include <editeng/editdata.hxx> +#include <editeng/svxenum.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <editeng/hangulhanja.hxx> + +class EditView; + +class TextConvWrapper final : public editeng::HangulHanjaConversion +{ + OUString m_aConvText; // convertible text part found last time + LanguageType m_nConvTextLang; // language of aConvText + sal_uInt16 m_nUnitOffset; // offset of current unit in the current text portion (word) + sal_uInt16 m_nLastPos; // starting position of the last found text portion (word) + + ESelection m_aConvSel; // selection to be converted if + // 'HasRange' is true, other conversion + // starts from the cursor position + + EditView * m_pEditView; + weld::Widget* m_pWin; + + bool m_bStartChk; + bool m_bStartDone; + bool m_bEndDone; + bool m_bAllowChange; // storage for _bAllowImplicitChangesForNotConvertibleText + // parameters value of function GetNextPortion. + // used to transport the value to where it is needed. + + + // from SvxSpellWrapper copied and modified + bool ConvNext_impl(); // former SpellNext + void FindConvText_impl(); // former FindSpellError + bool ConvMore_impl(); // former SpellMore + + // from EditSpellWrapper copied and modified + void ConvStart_impl( SvxSpellArea eSpell ); // former SpellStart + bool ConvContinue_impl(); // former SpellContinue + + void SelectNewUnit_impl( const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ); + + void ChangeText( const OUString &rNewText, + std::u16string_view rOrigText, + const css::uno::Sequence< sal_Int32 > *pOffsets, + ESelection *pESelection ); + void ChangeText_impl( const OUString &rNewText, bool bKeepAttributes ); + + TextConvWrapper (const TextConvWrapper &) = delete; + TextConvWrapper & operator= (const TextConvWrapper &) = delete; + + virtual void GetNextPortion( OUString& /* [out] */ rNextPortion, + LanguageType& /* [out] */ rLangOfPortion, + bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText ) override; + virtual void HandleNewUnit( const sal_Int32 nUnitStart, + const sal_Int32 nUnitEnd ) override; + virtual void ReplaceUnit( + const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd, + const OUString& rOrigText, + const OUString& rReplaceWith, + const css::uno::Sequence< sal_Int32 > &rOffsets, + ReplacementAction eAction, + LanguageType *pNewUnitLanguage ) override; + + virtual bool HasRubySupport() const override; + + void SetLanguageAndFont( const ESelection &rESel, + LanguageType nLang, sal_uInt16 nLangWhichId, + const vcl::Font *pFont, sal_uInt16 nFontWhichId ); + + +public: + TextConvWrapper(weld::Widget* pWindow, + const css::uno::Reference< css::uno::XComponentContext >& rxContext, + const css::lang::Locale& rSourceLocale, + const css::lang::Locale& rTargetLocale, + const vcl::Font* pTargetFont, + sal_Int32 nOptions, + bool bIsInteractive, + bool bIsStart, EditView* pView ); + + virtual ~TextConvWrapper() override; + + void Convert(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/CustomPropertyField.cxx b/editeng/source/items/CustomPropertyField.cxx new file mode 100644 index 0000000000..eaad4c4c4d --- /dev/null +++ b/editeng/source/items/CustomPropertyField.cxx @@ -0,0 +1,72 @@ +/* -*- 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/. + * + */ + +#include <editeng/CustomPropertyField.hxx> +#include <utility> +#include <vcl/metaact.hxx> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +using namespace css; + +namespace editeng +{ + +CustomPropertyField::CustomPropertyField(OUString aName, OUString aCurrentPresentation) + : msName(std::move(aName)) + , msCurrentPresentation(std::move(aCurrentPresentation)) +{} + +CustomPropertyField::~CustomPropertyField() +{} + +std::unique_ptr<SvxFieldData> CustomPropertyField::Clone() const +{ + return std::make_unique<CustomPropertyField>(msName, msCurrentPresentation); +} + +bool CustomPropertyField::operator==(const SvxFieldData& rOther) const +{ + if (typeid(rOther) != typeid(*this)) + return false; + + const CustomPropertyField& rOtherField = static_cast<const CustomPropertyField&>(rOther); + return (msName == rOtherField.msName && + msCurrentPresentation == rOtherField.msCurrentPresentation); +} + +MetaAction* CustomPropertyField::createBeginComment() const +{ + return new MetaCommentAction("FIELD_SEQ_BEGIN"_ostr); +} + +OUString CustomPropertyField::GetFormatted(uno::Reference<document::XDocumentProperties> const & xDocumentProperties) +{ + if (msName.isEmpty()) + return OUString(); + if (!xDocumentProperties.is()) + return OUString(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + if (!xPropertyContainer.is()) + return OUString(); + uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY); + if (!xPropertySet.is()) + return OUString(); + uno::Any aAny = xPropertySet->getPropertyValue(msName); + if (!aAny.has<OUString>()) + return OUString(); + msCurrentPresentation = aAny.get<OUString>(); + return msCurrentPresentation; +} + +} // end editeng namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/borderline.cxx b/editeng/source/items/borderline.cxx new file mode 100644 index 0000000000..05742eb951 --- /dev/null +++ b/editeng/source/items/borderline.cxx @@ -0,0 +1,719 @@ +/* -*- 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 <algorithm> + +#include <basegfx/color/bcolor.hxx> +#include <basegfx/color/bcolortools.hxx> + +#include <editeng/borderline.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <tools/bigint.hxx> + +#include <docmodel/uno/UnoComplexColor.hxx> +#include <com/sun/star/util/XComplexColor.hpp> + +using namespace ::com::sun::star::table::BorderLineStyle; +using namespace css; + +// class SvxBorderLine -------------------------------------------------- + +namespace { + + Color lcl_compute3DColor( Color aMain, int nLight, int nMedium, int nDark ) + { + basegfx::BColor color = aMain.getBColor( ); + basegfx::BColor hsl = basegfx::utils::rgb2hsl( color ); + + int nCoef = 0; + if ( hsl.getZ( ) >= 0.5 ) + nCoef = nLight; + else if ( 0.5 > hsl.getZ() && hsl.getZ() >= 0.25 ) + nCoef = nMedium; + else + nCoef = nDark; + + double L = std::min(hsl.getZ() * 255.0 + nCoef, 255.0); + hsl.setZ( L / 255.0 ); + color = basegfx::utils::hsl2rgb( hsl ); + + return Color( color ); + } +} // Anonymous namespace + +namespace editeng +{ + +bool SvxBorderLine::setComplexColorFromAny(css::uno::Any const& rValue) +{ + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rValue >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + { + auto aComplexColor = model::color::getFromXComplexColor(xComplexColor); + setComplexColor(aComplexColor); + } + return true; +} + +Color SvxBorderLine::darkColor( Color aMain ) +{ + return aMain; +} + +Color SvxBorderLine::lightColor( Color aMain ) +{ + + // Divide Luminance by 2 + basegfx::BColor color = aMain.getBColor( ); + basegfx::BColor hsl = basegfx::utils::rgb2hsl( color ); + hsl.setZ( hsl.getZ() * 0.5 ); + color = basegfx::utils::hsl2rgb( hsl ); + + return Color( color ); +} + + +Color SvxBorderLine::threeDLightColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, 3, 40, 83 ); +} + +Color SvxBorderLine::threeDDarkColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, -85, -43, -1 ); +} + +Color SvxBorderLine::threeDMediumColor( Color aMain ) +{ + // These values have been defined in an empirical way + return lcl_compute3DColor( aMain, -42, -0, 42 ); +} + +SvxBorderLine::SvxBorderLine( const Color *pCol, tools::Long nWidth, + SvxBorderLineStyle nStyle, + Color (*pColorOutFn)( Color ), Color (*pColorInFn)( Color ) ) + : m_nWidth(nWidth) + , m_nMult(1) + , m_nDiv(1) + , m_pColorOutFn(pColorOutFn) + , m_pColorInFn(pColorInFn) + , m_pColorGapFn(nullptr) + , m_aWidthImpl(SvxBorderLine::getWidthImpl(nStyle)) + , m_nStyle(nStyle) + , m_bMirrorWidths(false) + , m_bUseLeftTop(false) +{ + if (pCol) + m_aColor = *pCol; +} + +SvxBorderLineStyle +ConvertBorderStyleFromWord(int const nWordLineStyle) +{ + switch (nWordLineStyle) + { + // First the single lines + case 1: + case 2: // thick line + case 5: // hairline + // and the unsupported special cases which we map to a single line + case 20: + return SvxBorderLineStyle::SOLID; + case 6: + return SvxBorderLineStyle::DOTTED; + case 7: + return SvxBorderLineStyle::DASHED; + case 22: + return SvxBorderLineStyle::FINE_DASHED; + case 8: + return SvxBorderLineStyle::DASH_DOT; + case 9: + return SvxBorderLineStyle::DASH_DOT_DOT; + // then the shading beams which we represent by a double line + case 23: + return SvxBorderLineStyle::DOUBLE; + // then the double lines, for which we have good matches + case 3: + case 10: // Don't have triple so use double + case 21: // Don't have double wave: use double instead + return SvxBorderLineStyle::DOUBLE; + case 11: + return SvxBorderLineStyle::THINTHICK_SMALLGAP; + case 12: + case 13: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_SMALLGAP; + case 14: + return SvxBorderLineStyle::THINTHICK_MEDIUMGAP; + case 15: + case 16: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_MEDIUMGAP; + case 17: + return SvxBorderLineStyle::THINTHICK_LARGEGAP; + case 18: + case 19: // Don't have thin thick thin, so use thick thin + return SvxBorderLineStyle::THICKTHIN_LARGEGAP; + case 24: + return SvxBorderLineStyle::EMBOSSED; + case 25: + return SvxBorderLineStyle::ENGRAVED; + case 26: + return SvxBorderLineStyle::OUTSET; + case 27: + return SvxBorderLineStyle::INSET; + default: + return SvxBorderLineStyle::NONE; + } +} + +const double THINTHICK_SMALLGAP_line2 = 15.0; +const double THINTHICK_SMALLGAP_gap = 15.0; +const double THINTHICK_LARGEGAP_line1 = 30.0; +const double THINTHICK_LARGEGAP_line2 = 15.0; +const double THICKTHIN_SMALLGAP_line1 = 15.0; +const double THICKTHIN_SMALLGAP_gap = 15.0; +const double THICKTHIN_LARGEGAP_line1 = 15.0; +const double THICKTHIN_LARGEGAP_line2 = 30.0; +const double OUTSET_line1 = 15.0; +const double INSET_line2 = 15.0; + +double +ConvertBorderWidthFromWord(SvxBorderLineStyle const eStyle, double const i_fWidth, + int const nWordLineStyle) +{ + // fdo#68779: at least for RTF, 0.75pt is the default if width is missing + double const fWidth((i_fWidth == 0.0) ? 15.0 : i_fWidth); + switch (eStyle) + { + // Single lines + case SvxBorderLineStyle::SOLID: + switch (nWordLineStyle) + { + case 2: + return (fWidth * 2.0); // thick + case 5: // fdo#55526: map 0 hairline width to > 0 + return std::max(fWidth, 1.0); + default: + return fWidth; + } + break; + + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + return fWidth; + + // Display a minimum effective border width of 1pt + case SvxBorderLineStyle::FINE_DASHED: + return (fWidth > 0 && fWidth < 20) ? 20 : fWidth; + + // Double lines + case SvxBorderLineStyle::DOUBLE: + return fWidth * 3.0; + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + return fWidth * 2.0; + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + return fWidth + THINTHICK_SMALLGAP_line2 + THINTHICK_SMALLGAP_gap; + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + return fWidth + THINTHICK_LARGEGAP_line1 + THINTHICK_LARGEGAP_line2; + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + return fWidth + THICKTHIN_SMALLGAP_line1 + THICKTHIN_SMALLGAP_gap; + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + return fWidth + THICKTHIN_LARGEGAP_line1 + THICKTHIN_LARGEGAP_line2; + + case SvxBorderLineStyle::OUTSET: + return (fWidth * 2.0) + OUTSET_line1; + + case SvxBorderLineStyle::INSET: + return (fWidth * 2.0) + INSET_line2; + + default: + assert(false); // should only be called for known border style + } + return 0; +} + +double +ConvertBorderWidthToWord(SvxBorderLineStyle const eStyle, double const fWidth) +{ + if ( !fWidth ) + return 0; + + switch (eStyle) + { + // Single lines + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + return fWidth; + + // Double lines + case SvxBorderLineStyle::DOUBLE: + case SvxBorderLineStyle::DOUBLE_THIN: + return std::max(1.0, fWidth / 3.0); + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + return std::max(1.0, fWidth / 2.0); + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + return std::max(1.0, fWidth - THINTHICK_SMALLGAP_line2 - THINTHICK_SMALLGAP_gap); + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + return std::max(1.0, fWidth - THINTHICK_LARGEGAP_line1 - THINTHICK_LARGEGAP_line2); + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + return std::max(1.0, fWidth - THICKTHIN_SMALLGAP_line1 - THICKTHIN_SMALLGAP_gap); + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + return std::max(1.0, fWidth - THICKTHIN_LARGEGAP_line1 - THICKTHIN_LARGEGAP_line2); + + case SvxBorderLineStyle::OUTSET: + return std::max(1.0, (fWidth - OUTSET_line1) / 2.0); + + case SvxBorderLineStyle::INSET: + return std::max(1.0, (fWidth - INSET_line2) / 2.0); + + case SvxBorderLineStyle::NONE: + return 0; + + default: + assert(false); // should only be called for known border style + return 0; + } +} + +/** Get the BorderWithImpl object corresponding to the given #nStyle, all the + units handled by the resulting object are Twips and the + BorderWidthImpl::GetLine1() corresponds to the Outer Line. + */ +BorderWidthImpl SvxBorderLine::getWidthImpl( SvxBorderLineStyle nStyle ) +{ + BorderWidthImpl aImpl; + + switch ( nStyle ) + { + // No line: no width + case SvxBorderLineStyle::NONE: + aImpl = BorderWidthImpl( BorderWidthImplFlags::FIXED, 0.0 ); + break; + + // Single lines + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE1, 1.0 ); + break; + + // Double lines + + case SvxBorderLineStyle::DOUBLE: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + // fdo#46112 fdo#38542 fdo#43249: + // non-constant widths must sum to 1 + 1.0/3.0, 1.0/3.0, 1.0/3.0 ); + break; + + case SvxBorderLineStyle::DOUBLE_THIN: + aImpl = BorderWidthImpl(BorderWidthImplFlags::CHANGE_DIST, 10.0, 10.0, 1.0); + break; + + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE1, 1.0, + THINTHICK_SMALLGAP_line2, THINTHICK_SMALLGAP_gap ); + break; + + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.5, 0.25, 0.25 ); + break; + + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_DIST, + THINTHICK_LARGEGAP_line1, THINTHICK_LARGEGAP_line2, 1.0 ); + break; + + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_LINE2, THICKTHIN_SMALLGAP_line1, + 1.0, THICKTHIN_SMALLGAP_gap ); + break; + + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.25, 0.5, 0.25 ); + break; + + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + aImpl = BorderWidthImpl( BorderWidthImplFlags::CHANGE_DIST, THICKTHIN_LARGEGAP_line1, + THICKTHIN_LARGEGAP_line2, 1.0 ); + break; + + // Engraved / Embossed + /* + * Word compat: the lines widths are exactly following this rule, should be: + * 0.75pt up to 3pt and then 3pt + */ + + case SvxBorderLineStyle::EMBOSSED: + case SvxBorderLineStyle::ENGRAVED: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + 0.25, 0.25, 0.5 ); + break; + + // Inset / Outset + /* + * Word compat: the gap width should be measured relatively to the biggest width for the + * row or column. + */ + case SvxBorderLineStyle::OUTSET: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + OUTSET_line1, 0.5, 0.5 ); + break; + + case SvxBorderLineStyle::INSET: + aImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_DIST, + 0.5, INSET_line2, 0.5 ); + break; + } + + return aImpl; +} + +void SvxBorderLine::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + m_nMult = nMult; + m_nDiv = nDiv; +} + +void SvxBorderLine::GuessLinesWidths( SvxBorderLineStyle nStyle, sal_uInt16 nOut, sal_uInt16 nIn, sal_uInt16 nDist ) +{ + if (SvxBorderLineStyle::NONE == nStyle) + { + nStyle = SvxBorderLineStyle::SOLID; + if ( nOut > 0 && nIn > 0 ) + nStyle = SvxBorderLineStyle::DOUBLE; + } + + if ( nStyle == SvxBorderLineStyle::DOUBLE ) + { + static const SvxBorderLineStyle aDoubleStyles[] = + { + SvxBorderLineStyle::DOUBLE, + SvxBorderLineStyle::DOUBLE_THIN, + SvxBorderLineStyle::THINTHICK_SMALLGAP, + SvxBorderLineStyle::THINTHICK_MEDIUMGAP, + SvxBorderLineStyle::THINTHICK_LARGEGAP, + SvxBorderLineStyle::THICKTHIN_SMALLGAP, + SvxBorderLineStyle::THICKTHIN_MEDIUMGAP, + SvxBorderLineStyle::THICKTHIN_LARGEGAP + }; + + static size_t const len = SAL_N_ELEMENTS(aDoubleStyles); + tools::Long nWidth = 0; + SvxBorderLineStyle nTestStyle(SvxBorderLineStyle::NONE); + for (size_t i = 0; i < len && nWidth == 0; ++i) + { + nTestStyle = aDoubleStyles[i]; + BorderWidthImpl aWidthImpl = getWidthImpl( nTestStyle ); + nWidth = aWidthImpl.GuessWidth( nOut, nIn, nDist ); + } + + // If anything matched, then set it + if ( nWidth > 0 ) + { + nStyle = nTestStyle; + SetBorderLineStyle(nStyle); + m_nWidth = nWidth; + } + else + { + // fdo#38542: not a known double, default to something custom... + SetBorderLineStyle(nStyle); + m_nWidth = nOut + nIn + nDist; + if (m_nWidth) + { + m_aWidthImpl = BorderWidthImpl( + BorderWidthImplFlags::CHANGE_LINE1 | BorderWidthImplFlags::CHANGE_LINE2 | BorderWidthImplFlags::CHANGE_DIST, + static_cast<double>(nOut ) / static_cast<double>(m_nWidth), + static_cast<double>(nIn ) / static_cast<double>(m_nWidth), + static_cast<double>(nDist) / static_cast<double>(m_nWidth)); + } + } + } + else + { + SetBorderLineStyle(nStyle); + if (nOut == 0 && nIn > 0) + { + // If only inner width is given swap inner and outer widths for + // single line styles, otherwise GuessWidth() marks this as invalid + // and returns a 0 width. + switch (nStyle) + { + case SvxBorderLineStyle::SOLID: + case SvxBorderLineStyle::DOTTED: + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::FINE_DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + std::swap( nOut, nIn); + break; + default: + ; // nothing + } + } + m_nWidth = m_aWidthImpl.GuessWidth( nOut, nIn, nDist ); + } +} + +sal_uInt16 SvxBorderLine::GetOutWidth() const +{ + sal_uInt16 nOut = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine1( m_nWidth ), m_nMult, m_nDiv )); + if ( m_bMirrorWidths ) + nOut = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine2( m_nWidth ), m_nMult, m_nDiv )); + return nOut; +} + +sal_uInt16 SvxBorderLine::GetInWidth() const +{ + sal_uInt16 nIn = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine2( m_nWidth ), m_nMult, m_nDiv )); + if ( m_bMirrorWidths ) + nIn = static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetLine1( m_nWidth ), m_nMult, m_nDiv )); + return nIn; +} + +sal_uInt16 SvxBorderLine::GetDistance() const +{ + return static_cast<sal_uInt16>(BigInt::Scale( m_aWidthImpl.GetGap( m_nWidth ), m_nMult, m_nDiv )); +} + + +bool SvxBorderLine::operator==( const SvxBorderLine& rCmp ) const +{ + return (m_aColor == rCmp.m_aColor && + m_aComplexColor == rCmp.m_aComplexColor && + m_nWidth == rCmp.m_nWidth && + m_bMirrorWidths == rCmp.m_bMirrorWidths && + m_aWidthImpl == rCmp.m_aWidthImpl && + m_nStyle == rCmp.GetBorderLineStyle() && + m_bUseLeftTop == rCmp.m_bUseLeftTop && + m_pColorOutFn == rCmp.m_pColorOutFn && + m_pColorInFn == rCmp.m_pColorInFn && + m_pColorGapFn == rCmp.m_pColorGapFn); +} + +void SvxBorderLine::SetBorderLineStyle( SvxBorderLineStyle nNew ) +{ + m_nStyle = nNew; + m_aWidthImpl = getWidthImpl( m_nStyle ); + + switch ( nNew ) + { + case SvxBorderLineStyle::EMBOSSED: + m_pColorOutFn = threeDLightColor; + m_pColorInFn = threeDDarkColor; + m_pColorGapFn = threeDMediumColor; + m_bUseLeftTop = true; + break; + case SvxBorderLineStyle::ENGRAVED: + m_pColorOutFn = threeDDarkColor; + m_pColorInFn = threeDLightColor; + m_pColorGapFn = threeDMediumColor; + m_bUseLeftTop = true; + break; + case SvxBorderLineStyle::OUTSET: + m_pColorOutFn = lightColor; + m_pColorInFn = darkColor; + m_bUseLeftTop = true; + m_pColorGapFn = nullptr; + break; + case SvxBorderLineStyle::INSET: + m_pColorOutFn = darkColor; + m_pColorInFn = lightColor; + m_bUseLeftTop = true; + m_pColorGapFn = nullptr; + break; + default: + m_pColorOutFn = darkColor; + m_pColorInFn = darkColor; + m_bUseLeftTop = false; + m_pColorGapFn = nullptr; + break; + } +} + +Color SvxBorderLine::GetColorOut( bool bLeftOrTop ) const +{ + Color aResult = m_aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorOutFn != nullptr ) + { + if ( !bLeftOrTop && m_bUseLeftTop ) + aResult = (*m_pColorInFn)(m_aColor); + else + aResult = (*m_pColorOutFn)(m_aColor); + } + + return aResult; +} + +Color SvxBorderLine::GetColorIn( bool bLeftOrTop ) const +{ + Color aResult = m_aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorInFn != nullptr ) + { + if ( !bLeftOrTop && m_bUseLeftTop ) + aResult = (*m_pColorOutFn)(m_aColor); + else + aResult = (*m_pColorInFn)(m_aColor); + } + + return aResult; +} + +Color SvxBorderLine::GetColorGap( ) const +{ + Color aResult = m_aColor; + + if ( m_aWidthImpl.IsDouble() && m_pColorGapFn != nullptr ) + { + aResult = (*m_pColorGapFn)(m_aColor); + } + + return aResult; +} + +void SvxBorderLine::SetWidth( tools::Long nWidth ) +{ + m_nWidth = nWidth; +} + +OUString SvxBorderLine::GetValueString(MapUnit eSrcUnit, + MapUnit eDestUnit, + const IntlWrapper* pIntl, + bool bMetricStr) const +{ + static TranslateId aStyleIds[] = + { + RID_SOLID, + RID_DOTTED, + RID_DASHED, + RID_DOUBLE, + RID_THINTHICK_SMALLGAP, + RID_THINTHICK_MEDIUMGAP, + RID_THINTHICK_LARGEGAP, + RID_THICKTHIN_SMALLGAP, + RID_THICKTHIN_MEDIUMGAP, + RID_THICKTHIN_LARGEGAP, + RID_EMBOSSED, + RID_ENGRAVED, + RID_OUTSET, + RID_INSET, + RID_FINE_DASHED, + RID_DOUBLE_THIN, + RID_DASH_DOT, + RID_DASH_DOT_DOT + }; + OUString aStr = "(" + ::GetColorString(m_aColor) + cpDelim; + + if ( static_cast<int>(m_nStyle) < int(SAL_N_ELEMENTS(aStyleIds)) ) + { + TranslateId pResId = aStyleIds[static_cast<int>(m_nStyle)]; + aStr += EditResId(pResId); + } + else + { + OUString sMetric = EditResId(GetMetricId( eDestUnit )); + aStr += GetMetricText( static_cast<tools::Long>(GetInWidth()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + aStr += cpDelim + + GetMetricText( static_cast<tools::Long>(GetOutWidth()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + aStr += cpDelim + + GetMetricText( static_cast<tools::Long>(GetDistance()), eSrcUnit, eDestUnit, pIntl ); + if ( bMetricStr ) + aStr += sMetric; + } + aStr += ")"; + return aStr; +} + +bool SvxBorderLine::HasPriority( const SvxBorderLine& rOtherLine ) const +{ + const sal_uInt16 nThisSize = GetScaledWidth(); + const sal_uInt16 nOtherSize = rOtherLine.GetScaledWidth(); + + if ( nThisSize > nOtherSize ) + { + return true; + } + else if ( nThisSize < nOtherSize ) + { + return false; + } + else if ( rOtherLine.GetInWidth() && !GetInWidth() ) + { + return true; + } + + return false; +} + +bool operator!=( const SvxBorderLine& rLeft, const SvxBorderLine& rRight ) +{ + return !(rLeft == rRight); +} + +} // namespace editeng + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/bulitem.cxx b/editeng/source/items/bulitem.cxx new file mode 100644 index 0000000000..769179748b --- /dev/null +++ b/editeng/source/items/bulitem.cxx @@ -0,0 +1,159 @@ +/* -*- 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 <vcl/outdev.hxx> + +#include <editeng/bulletitem.hxx> + +SvxBulletItem::SvxBulletItem( sal_uInt16 _nWhich ) + : SfxPoolItem(_nWhich) + , aFont(OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE )) + , nStart(1) + , nStyle(SvxBulletStyle::N123) + , nWidth(1200) // 1.2cm + , nScale(75) + , cSymbol(' ') +{ + aFont.SetAlignment(ALIGN_BOTTOM); + aFont.SetTransparent( true ); +} + + +SvxBulletItem::SvxBulletItem( const SvxBulletItem& rItem ) + : SfxPoolItem(rItem) + , aFont(rItem.aFont) + , pGraphicObject(rItem.pGraphicObject ? new GraphicObject( *rItem.pGraphicObject ) : nullptr) + , aPrevText(rItem.aPrevText) + , aFollowText(rItem.aFollowText) + , nStart(rItem.nStart) + , nStyle(rItem.nStyle) + , nWidth(rItem.nWidth) + , nScale(rItem.nScale) + , cSymbol(rItem.cSymbol) +{ +} + + +SvxBulletItem::~SvxBulletItem() +{ +} + +SvxBulletItem* SvxBulletItem::Clone( SfxItemPool * /*pPool*/ ) const +{ + return new SvxBulletItem( *this ); +} + +void SvxBulletItem::CopyValidProperties( const SvxBulletItem& rCopyFrom ) +{ + vcl::Font _aFont = GetFont(); + vcl::Font aNewFont = rCopyFrom.GetFont(); + _aFont.SetFamilyName( aNewFont.GetFamilyName() ); + _aFont.SetFamily( aNewFont.GetFamilyType() ); + _aFont.SetStyleName( aNewFont.GetStyleName() ); + _aFont.SetColor( aNewFont.GetColor() ); + SetSymbol( rCopyFrom.cSymbol ); + SetGraphicObject( rCopyFrom.GetGraphicObject() ); + SetScale( rCopyFrom.nScale ); + SetStart( rCopyFrom.nStart ); + SetStyle( rCopyFrom.nStyle ); + aPrevText = rCopyFrom.aPrevText; + aFollowText = rCopyFrom.aFollowText; + SetFont( _aFont ); +} + + +bool SvxBulletItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + const SvxBulletItem& rBullet = static_cast<const SvxBulletItem&>(rItem); + // Compare with ValidMask, otherwise no put possible in an AttrSet if the + // item differs only in terms of the ValidMask from an existing one. + if( nStyle != rBullet.nStyle || + nScale != rBullet.nScale || + nWidth != rBullet.nWidth || + nStart != rBullet.nStart || + cSymbol != rBullet.cSymbol || + aPrevText != rBullet.aPrevText || + aFollowText != rBullet.aFollowText ) + return false; + + if( ( nStyle != SvxBulletStyle::BMP ) && ( aFont != rBullet.aFont ) ) + return false; + + if( nStyle == SvxBulletStyle::BMP ) + { + if( ( pGraphicObject && !rBullet.pGraphicObject ) || ( !pGraphicObject && rBullet.pGraphicObject ) ) + return false; + + if( ( pGraphicObject && rBullet.pGraphicObject ) && + ( ( *pGraphicObject != *rBullet.pGraphicObject ) || + ( pGraphicObject->GetPrefSize() != rBullet.pGraphicObject->GetPrefSize() ) ) ) + { + return false; + } + } + + return true; +} + + +OUString SvxBulletItem::GetFullText() const +{ + return aPrevText + OUStringChar(cSymbol) + aFollowText; +} + + +bool SvxBulletItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText = GetFullText(); + return true; +} + + +const GraphicObject& SvxBulletItem::GetGraphicObject() const +{ + if( pGraphicObject ) + return *pGraphicObject; + else + { + static const GraphicObject aDefaultObject; + return aDefaultObject; + } +} + + +void SvxBulletItem::SetGraphicObject( const GraphicObject& rGraphicObject ) +{ + if( ( GraphicType::NONE == rGraphicObject.GetType() ) || ( GraphicType::Default == rGraphicObject.GetType() ) ) + { + pGraphicObject.reset(); + } + else + { + pGraphicObject.reset( new GraphicObject( rGraphicObject ) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/charhiddenitem.cxx b/editeng/source/items/charhiddenitem.cxx new file mode 100644 index 0000000000..ec2a0af3c7 --- /dev/null +++ b/editeng/source/items/charhiddenitem.cxx @@ -0,0 +1,53 @@ +/* -*- 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 <editeng/charhiddenitem.hxx> +#include <editeng/editrids.hrc> +#include <editeng/eerdll.hxx> +#include <unotools/resmgr.hxx> + + +SvxCharHiddenItem::SvxCharHiddenItem( const bool bHidden, const sal_uInt16 nId ) : + SfxBoolItem( nId, bHidden ) +{ +} + +SvxCharHiddenItem* SvxCharHiddenItem::Clone( SfxItemPool * ) const +{ + return new SvxCharHiddenItem( *this ); +} + +bool SvxCharHiddenItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper & /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_CHARHIDDEN_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_CHARHIDDEN_TRUE; + rText = EditResId(pId); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/flditem.cxx b/editeng/source/items/flditem.cxx new file mode 100644 index 0000000000..b501d40ba9 --- /dev/null +++ b/editeng/source/items/flditem.cxx @@ -0,0 +1,935 @@ +/* -*- 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 <osl/file.hxx> +#include <utility> +#include <vcl/metaact.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <tools/urlobj.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/CustomPropertyField.hxx> +#include <editeng/measfld.hxx> +#include <editeng/unonames.hxx> + +#include <tools/debug.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <com/sun/star/util/DateTime.hpp> + +using namespace com::sun::star; + +SvxFieldData* SvxFieldData::Create(const uno::Reference<text::XTextContent>& xTextContent) +{ + uno::Reference<beans::XPropertySet> xPropSet(xTextContent, uno::UNO_QUERY); + if (!xPropSet.is()) + return nullptr; + + // we do not support these fields from Writer, so make sure we do not throw + // here - see fdo#63436 how to possibly extend Writer to make use of this + uno::Any aAny; + try { + aAny = xPropSet->getPropertyValue(UNO_TC_PROP_TEXTFIELD_TYPE); + if ( !aAny.has<sal_Int32>() ) + return nullptr; + + sal_Int32 nFieldType = aAny.get<sal_Int32>(); + + switch (nFieldType) + { + case text::textfield::Type::TIME: + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + { + bool bIsDate = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_DATE) >>= bIsDate; + + if (bIsDate) + { + util::DateTime aDateTime = xPropSet->getPropertyValue(UNO_TC_PROP_DATE_TIME).get<util::DateTime>(); + Date aDate(aDateTime.Day, aDateTime.Month, aDateTime.Year); + bool bIsFixed = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + + SvxDateField* pData = new SvxDateField(aDate, bIsFixed ? SvxDateType::Fix : SvxDateType::Var); + sal_Int32 nNumFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + if (static_cast<SvxDateFormat>(nNumFmt) >= SvxDateFormat::AppDefault && + static_cast<SvxDateFormat>(nNumFmt) <= SvxDateFormat::F) + pData->SetFormat(static_cast<SvxDateFormat>(nNumFmt)); + + return pData; + } + + if (nFieldType != text::textfield::Type::TIME) + { + util::DateTime aDateTime = xPropSet->getPropertyValue(UNO_TC_PROP_DATE_TIME).get<util::DateTime>(); + tools::Time aTime(aDateTime); + + bool bIsFixed = false; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + + SvxExtTimeField* pData = new SvxExtTimeField(aTime, bIsFixed ? SvxTimeType::Fix : SvxTimeType::Var); + + sal_Int32 nNumFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + if (static_cast<SvxTimeFormat>(nNumFmt) >= SvxTimeFormat::AppDefault && + static_cast<SvxTimeFormat>(nNumFmt) <= SvxTimeFormat::HH12_MM_SS_00_AMPM) + pData->SetFormat(static_cast<SvxTimeFormat>(nNumFmt)); + + return pData; + } + + return new SvxTimeField(); + } + case text::textfield::Type::URL: + { + OUString aRep, aTarget, aURL; + sal_Int16 nFmt = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_REPRESENTATION) >>= aRep; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_TARGET) >>= aTarget; + xPropSet->getPropertyValue(UNO_TC_PROP_URL) >>= aURL; + xPropSet->getPropertyValue(UNO_TC_PROP_URL_FORMAT) >>= nFmt; + SvxURLField* pData = new SvxURLField(aURL, aRep, aRep.isEmpty() ? SvxURLFormat::Url : SvxURLFormat::Repr); + pData->SetTargetFrame(aTarget); + if (static_cast<SvxURLFormat>(nFmt) >= SvxURLFormat::AppDefault && + static_cast<SvxURLFormat>(nFmt) <= SvxURLFormat::Repr) + pData->SetFormat(static_cast<SvxURLFormat>(nFmt)); + + return pData; + } + case text::textfield::Type::PAGE: + return new SvxPageField(); + case text::textfield::Type::PAGES: + return new SvxPagesField(); + case text::textfield::Type::PAGE_NAME: + return new SvxPageTitleField(); + case text::textfield::Type::DOCINFO_TITLE: + return new SvxFileField(); + case text::textfield::Type::TABLE: + { + sal_Int32 nTab = 0; + xPropSet->getPropertyValue(UNO_TC_PROP_TABLE_POSITION) >>= nTab; + return new SvxTableField(nTab); + } + case text::textfield::Type::EXTENDED_FILE: + { + OUString aPresentation; + bool bIsFixed = false; + sal_Int16 nFmt = text::FilenameDisplayFormat::FULL; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= aPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nFmt; + + SvxFileFormat eFmt = SvxFileFormat::NameAndExt; + switch (nFmt) + { + case text::FilenameDisplayFormat::FULL: eFmt = SvxFileFormat::PathFull; break; + case text::FilenameDisplayFormat::PATH: eFmt = SvxFileFormat::PathOnly; break; + case text::FilenameDisplayFormat::NAME: eFmt = SvxFileFormat::NameOnly; break; + default:; + } + + // pass fixed attribute to constructor + return new SvxExtFileField( + aPresentation, bIsFixed ? SvxFileType::Fix : SvxFileType::Var, eFmt); + } + case text::textfield::Type::AUTHOR: + { + bool bIsFixed = false; + bool bFullName = false; + sal_Int16 nFmt = -1; + OUString aPresentation, aContent, aFirstName, aLastName; + xPropSet->getPropertyValue(UNO_TC_PROP_IS_FIXED) >>= bIsFixed; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_FULLNAME) >>= bFullName; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= aPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_CONTENT) >>= aContent; + xPropSet->getPropertyValue(UNO_TC_PROP_AUTHOR_FORMAT) >>= nFmt; + + // do we have CurrentPresentation given? Mimic behaviour of + // writer, which means: prefer CurrentPresentation over Content + // if both are given. + if (!aPresentation.isEmpty()) + aContent = aPresentation; + + sal_Int32 nPos = aContent.lastIndexOf(' ', 0); + if (nPos > 0) + { + aFirstName = aContent.copy(0, nPos); + aLastName = aContent.copy(nPos + 1); + } + else + { + aLastName = aContent; + } + + // #92009# pass fixed attribute to constructor + SvxAuthorField* pData = new SvxAuthorField( + aFirstName, aLastName, OUString(), bIsFixed ? SvxAuthorType::Fix : SvxAuthorType::Var); + + if (!bIsFixed) + { + if (!bFullName) + { + pData->SetFormat(SvxAuthorFormat::ShortName); + } + else if (static_cast<SvxAuthorFormat>(nFmt) >= SvxAuthorFormat::FullName && + static_cast<SvxAuthorFormat>(nFmt) <= SvxAuthorFormat::ShortName) + { + pData->SetFormat(static_cast<SvxAuthorFormat>(nFmt)); + } + } + + return pData; + } + case text::textfield::Type::MEASURE: + { + SdrMeasureFieldKind eKind = SdrMeasureFieldKind::Value; + sal_Int16 nTmp = -1; + xPropSet->getPropertyValue(UNO_TC_PROP_MEASURE_KIND) >>= nTmp; + if (nTmp == static_cast<sal_Int16>(SdrMeasureFieldKind::Unit) || + nTmp == static_cast<sal_Int16>(SdrMeasureFieldKind::Rotate90Blanks)) + eKind = static_cast<SdrMeasureFieldKind>(nTmp); + + return new SdrMeasureField(eKind); + } + case text::textfield::Type::PRESENTATION_HEADER: + return new SvxHeaderField(); + case text::textfield::Type::PRESENTATION_FOOTER: + return new SvxFooterField(); + case text::textfield::Type::PRESENTATION_DATE_TIME: + return new SvxDateTimeField(); + case text::textfield::Type::DOCINFO_CUSTOM: + { + OUString sName; + xPropSet->getPropertyValue(UNO_TC_PROP_NAME) >>= sName; + + OUString sCurrentPresentation; + xPropSet->getPropertyValue(UNO_TC_PROP_CURRENT_PRESENTATION) >>= sCurrentPresentation; + + return new editeng::CustomPropertyField(sName, sCurrentPresentation); + } + default: + ; + }; + } catch ( const beans::UnknownPropertyException& ) + { + return nullptr; + } + + return nullptr; +} + + +SvxFieldData::SvxFieldData() +{ +} + + +SvxFieldData::~SvxFieldData() +{ +} + + +std::unique_ptr<SvxFieldData> SvxFieldData::Clone() const +{ + return std::make_unique<SvxFieldData>(); +} + + +bool SvxFieldData::operator==( const SvxFieldData& rFld ) const +{ + DBG_ASSERT( typeid(*this) == typeid(rFld), "==: Different Types" ); + (void)rFld; + return true; // Basic class is always the same. +} + + +MetaAction* SvxFieldData::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN"_ostr ); +} + +MetaAction* SvxFieldData::createEndComment() +{ + return new MetaCommentAction( "FIELD_SEQ_END"_ostr ); +} + + +SvxFieldItem::SvxFieldItem( std::unique_ptr<SvxFieldData> pField, const sal_uInt16 nId ) : + SfxPoolItem( nId ) + , mpField( std::move(pField) ) +{ +} + +SvxFieldItem::SvxFieldItem( const SvxFieldData& rField, const sal_uInt16 nId ) : + SfxPoolItem( nId ) + , mpField( rField.Clone() ) +{ +} + + +SvxFieldItem::SvxFieldItem( const SvxFieldItem& rItem ) : + SfxPoolItem ( rItem ) + , mpField( rItem.mpField ? rItem.mpField->Clone() : nullptr ) +{ +} + +SvxFieldItem::~SvxFieldItem() +{ +} + +SvxFieldItem* SvxFieldItem::Clone( SfxItemPool* ) const +{ + return new SvxFieldItem(*this); +} + +bool SvxFieldItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + + const SvxFieldData* pOtherFld = static_cast<const SvxFieldItem&>(rItem).GetField(); + if( mpField.get() == pOtherFld ) + return true; + if( mpField == nullptr || pOtherFld == nullptr ) + return false; + return ( typeid(*mpField) == typeid(*pOtherFld) ) + && ( *mpField == *pOtherFld ); +} + + +// The following are the derivatives of SvxFieldData ... + + +SvxDateField::SvxDateField() +{ + nFixDate = Date( Date::SYSTEM ).GetDate(); + eType = SvxDateType::Var; + eFormat = SvxDateFormat::StdSmall; +} + + +SvxDateField::SvxDateField( const Date& rDate, SvxDateType eT, SvxDateFormat eF ) +{ + nFixDate = rDate.GetDate(); + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxDateField::Clone() const +{ + return std::make_unique<SvxDateField>( *this ); +} + + +bool SvxDateField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxDateField& rOtherFld = static_cast<const SvxDateField&>(rOther); + return ( ( nFixDate == rOtherFld.nFixDate ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + + +OUString SvxDateField::GetFormatted( SvNumberFormatter& rFormatter, LanguageType eLang ) const +{ + Date aDate( Date::EMPTY ); + if ( eType == SvxDateType::Fix ) + aDate.SetDate( nFixDate ); + else + aDate = Date( Date::SYSTEM ); // current date + + return GetFormatted( aDate, eFormat, rFormatter, eLang ); +} + +OUString SvxDateField::GetFormatted( Date const & aDate, SvxDateFormat eFormat, SvNumberFormatter& rFormatter, LanguageType eLang ) +{ + if ( eFormat == SvxDateFormat::System ) + { + OSL_FAIL( "SvxDateFormat::System not implemented!" ); + eFormat = SvxDateFormat::StdSmall; + } + else if ( eFormat == SvxDateFormat::AppDefault ) + { + OSL_FAIL( "SvxDateFormat::AppDefault: take them from where? "); + eFormat = SvxDateFormat::StdSmall; + } + + sal_uInt32 nFormatKey; + + switch( eFormat ) + { + case SvxDateFormat::StdSmall: + // short + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYSTEM_SHORT, eLang ); + break; + case SvxDateFormat::StdBig: + // long + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYSTEM_LONG, eLang ); + break; + case SvxDateFormat::A: + // 13.02.96 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYY, eLang ); + break; + case SvxDateFormat::B: + // 13.02.1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eLang ); + break; + case SvxDateFormat::C: + // 13. Feb 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DMMMYYYY, eLang ); + break; + case SvxDateFormat::D: + // 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_DMMMMYYYY, eLang ); + break; + case SvxDateFormat::E: + // The, 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_NNDMMMMYYYY, eLang ); + break; + case SvxDateFormat::F: + // Tuesday, 13. February 1996 + nFormatKey = rFormatter.GetFormatIndex( NF_DATE_SYS_NNNNDMMMMYYYY, eLang ); + break; + default: + nFormatKey = rFormatter.GetStandardFormat( SvNumFormatType::DATE, eLang ); + } + + double fDiffDate = aDate - rFormatter.GetNullDate(); + OUString aStr; + const Color* pColor = nullptr; + rFormatter.GetOutputString( fDiffDate, nFormatKey, aStr, &pColor ); + return aStr; +} + +MetaAction* SvxDateField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN"_ostr ); +} + +SvxURLField::SvxURLField() +{ + eFormat = SvxURLFormat::Url; +} + + +SvxURLField::SvxURLField( OUString _aURL, OUString aRepres, SvxURLFormat eFmt ) + : aURL(std::move( _aURL )), aRepresentation(std::move( aRepres )) +{ + eFormat = eFmt; +} + + +std::unique_ptr<SvxFieldData> SvxURLField::Clone() const +{ + return std::make_unique<SvxURLField>( *this ); +} + + +bool SvxURLField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxURLField& rOtherFld = static_cast<const SvxURLField&>(rOther); + return ( ( eFormat == rOtherFld.eFormat ) && + ( aURL == rOtherFld.aURL ) && + ( aRepresentation == rOtherFld.aRepresentation ) && + ( aTargetFrame == rOtherFld.aTargetFrame ) ); +} + + +MetaAction* SvxURLField::createBeginComment() const +{ + // #i46618# Adding target URL to metafile comment + return new MetaCommentAction( "FIELD_SEQ_BEGIN"_ostr, + 0, + reinterpret_cast<const sal_uInt8*>(aURL.getStr()), + 2*aURL.getLength() ); +} + +// +// SvxPageTitleField methods +// + +SvxPageTitleField::SvxPageTitleField() {} + +std::unique_ptr<SvxFieldData> SvxPageTitleField::Clone() const +{ + return std::make_unique<SvxPageTitleField>(); +} + +bool SvxPageTitleField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPageTitleField *>(&rCmp) != nullptr ); +} + +MetaAction* SvxPageTitleField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN;PageTitleField"_ostr ); +} + +// +// SvxPagesField +// +// The fields that were removed from Calc: + + +SvxPageField::SvxPageField() {} + +std::unique_ptr<SvxFieldData> SvxPageField::Clone() const +{ + return std::make_unique<SvxPageField>(); // empty +} + +bool SvxPageField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPageField *>(&rCmp) != nullptr ); +} + +MetaAction* SvxPageField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN;PageField"_ostr ); +} + + +SvxPagesField::SvxPagesField() {} + +std::unique_ptr<SvxFieldData> SvxPagesField::Clone() const +{ + return std::make_unique<SvxPagesField>(); // empty +} + +bool SvxPagesField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxPagesField *>(&rCmp) != nullptr); +} + +SvxTimeField::SvxTimeField() {} + +std::unique_ptr<SvxFieldData> SvxTimeField::Clone() const +{ + return std::make_unique<SvxTimeField>(); // empty +} + +bool SvxTimeField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxTimeField *>(&rCmp) != nullptr); +} + +MetaAction* SvxTimeField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN"_ostr ); +} + +SvxFileField::SvxFileField() {} + +std::unique_ptr<SvxFieldData> SvxFileField::Clone() const +{ + return std::make_unique<SvxFileField>(); // empty +} + +bool SvxFileField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxFileField *>(&rCmp) != nullptr ); +} + +SvxTableField::SvxTableField() : mnTab(0) {} + +SvxTableField::SvxTableField(int nTab) : mnTab(nTab) {} + +void SvxTableField::SetTab(int nTab) +{ + mnTab = nTab; +} + + +std::unique_ptr<SvxFieldData> SvxTableField::Clone() const +{ + return std::make_unique<SvxTableField>(mnTab); +} + +bool SvxTableField::operator==( const SvxFieldData& rCmp ) const +{ + if (dynamic_cast<const SvxTableField *>(&rCmp) == nullptr) + return false; + + return mnTab == static_cast<const SvxTableField&>(rCmp).mnTab; +} + +// SvxExtTimeField + + +SvxExtTimeField::SvxExtTimeField() + : m_nFixTime( tools::Time(tools::Time::SYSTEM).GetTime() ) +{ + eType = SvxTimeType::Var; + eFormat = SvxTimeFormat::Standard; +} + + +SvxExtTimeField::SvxExtTimeField( const tools::Time& rTime, SvxTimeType eT, SvxTimeFormat eF ) + : m_nFixTime( rTime.GetTime() ) +{ + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxExtTimeField::Clone() const +{ + return std::make_unique<SvxExtTimeField>( *this ); +} + + +bool SvxExtTimeField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxExtTimeField& rOtherFld = static_cast<const SvxExtTimeField&>(rOther); + return ((m_nFixTime == rOtherFld.m_nFixTime) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxExtTimeField::GetFormatted( SvNumberFormatter& rFormatter, LanguageType eLang ) const +{ + tools::Time aTime( tools::Time::EMPTY ); + if ( eType == SvxTimeType::Fix ) + aTime.SetTime(m_nFixTime); + else + aTime = tools::Time( tools::Time::SYSTEM ); // current time + return GetFormatted( aTime, eFormat, rFormatter, eLang ); +} + +OUString SvxExtTimeField::GetFormatted( tools::Time const & aTime, SvxTimeFormat eFormat, SvNumberFormatter& rFormatter, LanguageType eLang ) +{ + switch( eFormat ) + { + case SvxTimeFormat::System : + OSL_FAIL( "SvxTimeFormat::System: not implemented" ); + eFormat = SvxTimeFormat::Standard; + break; + case SvxTimeFormat::AppDefault : + OSL_FAIL( "SvxTimeFormat::AppDefault: not implemented" ); + eFormat = SvxTimeFormat::Standard; + break; + default: ;//prevent warning + } + + sal_uInt32 nFormatKey; + + switch( eFormat ) + { + case SvxTimeFormat::HH12_MM: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMAMPM, eLang ); + break; + case SvxTimeFormat::HH12_MM_SS_00: + { + // no builtin format available, try to insert or reuse + OUString aFormatCode( "HH:MM:SS.00 AM/PM" ); + sal_Int32 nCheckPos; + SvNumFormatType nType; + rFormatter.PutandConvertEntry( aFormatCode, nCheckPos, nType, + nFormatKey, LANGUAGE_ENGLISH_US, eLang, true); + DBG_ASSERT( nCheckPos == 0, "SvxTimeFormat::HH12_MM_SS_00: could not insert format code" ); + if ( nCheckPos ) + { + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HH_MMSS00, eLang ); + } + break; + } + case SvxTimeFormat::HH24_MM: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMM, eLang ); + break; + case SvxTimeFormat::HH24_MM_SS_00: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HH_MMSS00, eLang ); + break; + case SvxTimeFormat::HH12_MM_SS: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMSSAMPM, eLang ); + break; + case SvxTimeFormat::HH24_MM_SS: + nFormatKey = rFormatter.GetFormatIndex( NF_TIME_HHMMSS, eLang ); + break; + case SvxTimeFormat::Standard: + default: + nFormatKey = rFormatter.GetStandardFormat( SvNumFormatType::TIME, eLang ); + } + + double fFracTime = aTime.GetTimeInDays(); + OUString aStr; + const Color* pColor = nullptr; + rFormatter.GetOutputString( fFracTime, nFormatKey, aStr, &pColor ); + return aStr; +} + +MetaAction* SvxExtTimeField::createBeginComment() const +{ + return new MetaCommentAction( "FIELD_SEQ_BEGIN"_ostr ); +} + + +// SvxExtFileField + + +SvxExtFileField::SvxExtFileField() +{ + eType = SvxFileType::Var; + eFormat = SvxFileFormat::PathFull; +} + + +SvxExtFileField::SvxExtFileField( const OUString& rStr, SvxFileType eT, SvxFileFormat eF ) +{ + aFile = rStr; + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxExtFileField::Clone() const +{ + return std::make_unique<SvxExtFileField>( *this ); +} + + +bool SvxExtFileField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxExtFileField& rOtherFld = static_cast<const SvxExtFileField&>(rOther); + return ( ( aFile == rOtherFld.aFile ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxExtFileField::GetFormatted() const +{ + OUString aString; + + INetURLObject aURLObj( aFile ); + + if( INetProtocol::NotValid == aURLObj.GetProtocol() ) + { + // invalid? try to interpret string as system file name + OUString aURLStr; + + osl::FileBase::getFileURLFromSystemPath( aFile, aURLStr ); + + aURLObj.SetURL( aURLStr ); + } + + // #92009# Be somewhat liberate when trying to + // get formatted content out of the FileField + if( INetProtocol::NotValid == aURLObj.GetProtocol() ) + { + // still not valid? Then output as is + aString = aFile; + } + else if( INetProtocol::File == aURLObj.GetProtocol() ) + { + switch( eFormat ) + { + case SvxFileFormat::PathFull: + aString = aURLObj.getFSysPath(FSysStyle::Detect); + break; + + case SvxFileFormat::PathOnly: + aURLObj.removeSegment(INetURLObject::LAST_SEGMENT, false); + // #101742# Leave trailing slash at the pathname + aURLObj.setFinalSlash(); + aString = aURLObj.getFSysPath(FSysStyle::Detect); + break; + + case SvxFileFormat::NameOnly: + aString = aURLObj.getBase(INetURLObject::LAST_SEGMENT,true,INetURLObject::DecodeMechanism::Unambiguous); + break; + + case SvxFileFormat::NameAndExt: + aString = aURLObj.getName(INetURLObject::LAST_SEGMENT,true,INetURLObject::DecodeMechanism::Unambiguous); + break; + } + } + else + { + switch( eFormat ) + { + case SvxFileFormat::PathFull: + aString = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + break; + + case SvxFileFormat::PathOnly: + aURLObj.removeSegment(INetURLObject::LAST_SEGMENT, false); + // #101742# Leave trailing slash at the pathname + aURLObj.setFinalSlash(); + aString = aURLObj.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + break; + + case SvxFileFormat::NameOnly: + aString = aURLObj.getBase(); + break; + + case SvxFileFormat::NameAndExt: + aString = aURLObj.getName(); + break; + } + } + + return aString; +} + + +// SvxAuthorField + + +SvxAuthorField::SvxAuthorField( const OUString& rFirstName, + const OUString& rLastName, + const OUString& rShortName, + SvxAuthorType eT, SvxAuthorFormat eF ) +{ + aName = rLastName; + aFirstName = rFirstName; + aShortName = rShortName; + eType = eT; + eFormat = eF; +} + + +std::unique_ptr<SvxFieldData> SvxAuthorField::Clone() const +{ + return std::make_unique<SvxAuthorField>( *this ); +} + + +bool SvxAuthorField::operator==( const SvxFieldData& rOther ) const +{ + if ( typeid(rOther) != typeid(*this) ) + return false; + + const SvxAuthorField& rOtherFld = static_cast<const SvxAuthorField&>(rOther); + return ( ( aName == rOtherFld.aName ) && + ( aFirstName == rOtherFld.aFirstName ) && + ( aShortName == rOtherFld.aShortName ) && + ( eType == rOtherFld.eType ) && + ( eFormat == rOtherFld.eFormat ) ); +} + + +OUString SvxAuthorField::GetFormatted() const +{ + OUString aString; + + switch( eFormat ) + { + case SvxAuthorFormat::FullName: + aString = aFirstName + " " + aName; + break; + case SvxAuthorFormat::LastName: + aString = aName; + break; + + case SvxAuthorFormat::FirstName: + aString = aFirstName; + break; + + case SvxAuthorFormat::ShortName: + aString = aShortName; + break; + } + + return aString; +} + +SvxHeaderField::SvxHeaderField() {} + +std::unique_ptr<SvxFieldData> SvxHeaderField::Clone() const +{ + return std::make_unique<SvxHeaderField>(); // empty +} + +bool SvxHeaderField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxHeaderField *>(&rCmp) != nullptr ); +} + +SvxFooterField::SvxFooterField() {} + +std::unique_ptr<SvxFieldData> SvxFooterField::Clone() const +{ + return std::make_unique<SvxFooterField>(); // empty +} + +bool SvxFooterField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxFooterField *>(&rCmp) != nullptr ); +} + +std::unique_ptr<SvxFieldData> SvxDateTimeField::Clone() const +{ + return std::make_unique<SvxDateTimeField>(); // empty +} + +bool SvxDateTimeField::operator==( const SvxFieldData& rCmp ) const +{ + return ( dynamic_cast< const SvxDateTimeField *>(&rCmp) != nullptr ); +} + +SvxDateTimeField::SvxDateTimeField() {} + +OUString SvxDateTimeField::GetFormatted( + Date const & rDate, tools::Time const & rTime, + SvxDateFormat eDateFormat, SvxTimeFormat eTimeFormat, + SvNumberFormatter& rFormatter, LanguageType eLanguage ) +{ + OUString aRet; + + if(eDateFormat != SvxDateFormat::AppDefault) + { + aRet = SvxDateField::GetFormatted( rDate, eDateFormat, rFormatter, eLanguage ); + } + + if(eTimeFormat != SvxTimeFormat::AppDefault) + { + OUStringBuffer aBuf(aRet); + + if (!aRet.isEmpty()) + aBuf.append(' '); + + aBuf.append( + SvxExtTimeField::GetFormatted(rTime, eTimeFormat, rFormatter, eLanguage)); + + aRet = aBuf.makeStringAndClear(); + } + + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/frmitems.cxx b/editeng/source/items/frmitems.cxx new file mode 100644 index 0000000000..94b7704303 --- /dev/null +++ b/editeng/source/items/frmitems.cxx @@ -0,0 +1,4709 @@ +/* -*- 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 <memory> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/script/Converter.hpp> +#include <com/sun/star/table/ShadowLocation.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/table/BorderLineStyle.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/GraphicLocation.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/frame/status/UpperLowerMarginScale.hpp> +#include <com/sun/star/frame/status/LeftRightMarginScale.hpp> +#include <com/sun/star/drawing/ShadingPattern.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/util/XComplexColor.hpp> + +#include <osl/diagnose.h> +#include <i18nutil/unicode.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <utility> +#include <vcl/GraphicObject.hxx> +#include <tools/urlobj.hxx> +#include <tools/bigint.hxx> +#include <svl/memberid.h> +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/graphicfilter.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <editeng/editrids.hrc> +#include <editeng/pbinitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/prntitem.hxx> +#include <editeng/opaqitem.hxx> +#include <editeng/protitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/memberids.h> +#include <libxml/xmlwriter.h> +#include <o3tl/enumrange.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> +#include <vcl/GraphicLoader.hxx> +#include <unotools/securityoptions.hxx> +#include <docmodel/uno/UnoComplexColor.hxx> + +#include <boost/property_tree/ptree.hpp> + +using namespace ::editeng; +using namespace ::com::sun::star; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::table::BorderLineStyle; + + +SfxPoolItem* SvxPaperBinItem::CreateDefault() { return new SvxPaperBinItem(0);} +SfxPoolItem* SvxSizeItem::CreateDefault() { return new SvxSizeItem(0);} +SfxPoolItem* SvxLRSpaceItem::CreateDefault() { return new SvxLRSpaceItem(0);} +SfxPoolItem* SvxULSpaceItem::CreateDefault() { return new SvxULSpaceItem(0);} +SfxPoolItem* SvxProtectItem::CreateDefault() { return new SvxProtectItem(0);} +SfxPoolItem* SvxBrushItem::CreateDefault() { return new SvxBrushItem(0);} +SfxPoolItem* SvxShadowItem::CreateDefault() { return new SvxShadowItem(0);} +SfxPoolItem* SvxBoxItem::CreateDefault() { return new SvxBoxItem(0);} +SfxPoolItem* SvxBoxInfoItem::CreateDefault() { return new SvxBoxInfoItem(0);} +SfxPoolItem* SvxFormatBreakItem::CreateDefault() { return new SvxFormatBreakItem(SvxBreak::NONE, 0);} +SfxPoolItem* SvxFormatKeepItem::CreateDefault() { return new SvxFormatKeepItem(false, 0);} +SfxPoolItem* SvxLineItem::CreateDefault() { return new SvxLineItem(0);} + +SvxPaperBinItem* SvxPaperBinItem::Clone( SfxItemPool* ) const +{ + return new SvxPaperBinItem( *this ); +} + +bool SvxPaperBinItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = OUString::number( GetValue() ); + return true; + + case SfxItemPresentation::Complete: + { + sal_uInt8 nValue = GetValue(); + + if ( PAPERBIN_PRINTER_SETTINGS == nValue ) + rText = EditResId(RID_SVXSTR_PAPERBIN_SETTINGS); + else + { + rText = EditResId(RID_SVXSTR_PAPERBIN) + " " + OUString::number( nValue ); + } + return true; + } + //no break necessary + default: ; //prevent warning + } + + return false; +} + + +SvxSizeItem::SvxSizeItem( const sal_uInt16 nId, const Size& rSize ) : + + SfxPoolItem( nId ), + + m_aSize( rSize ) +{ +} + + +bool SvxSizeItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + awt::Size aTmp(m_aSize.Width(), m_aSize.Height()); + if( bConvert ) + { + aTmp.Height = convertTwipToMm100(aTmp.Height); + aTmp.Width = convertTwipToMm100(aTmp.Width); + } + + switch( nMemberId ) + { + case MID_SIZE_SIZE: rVal <<= aTmp; break; + case MID_SIZE_WIDTH: rVal <<= aTmp.Width; break; + case MID_SIZE_HEIGHT: rVal <<= aTmp.Height; break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + + +bool SvxSizeItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch( nMemberId ) + { + case MID_SIZE_SIZE: + { + awt::Size aTmp; + if( rVal >>= aTmp ) + { + if(bConvert) + { + aTmp.Height = o3tl::toTwips(aTmp.Height, o3tl::Length::mm100); + aTmp.Width = o3tl::toTwips(aTmp.Width, o3tl::Length::mm100); + } + m_aSize = Size( aTmp.Width, aTmp.Height ); + } + else + { + return false; + } + } + break; + case MID_SIZE_WIDTH: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal )) + return false; + + m_aSize.setWidth( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + } + break; + case MID_SIZE_HEIGHT: + { + sal_Int32 nVal = 0; + if(!(rVal >>= nVal)) + return true; + + m_aSize.setHeight( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + } + break; + default: OSL_FAIL("Wrong MemberId!"); + return false; + } + return true; +} + + +SvxSizeItem::SvxSizeItem( const sal_uInt16 nId ) : + + SfxPoolItem( nId ) +{ +} + + +bool SvxSizeItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return ( m_aSize == static_cast<const SvxSizeItem&>( rAttr ).GetSize() ); +} + +SvxSizeItem* SvxSizeItem::Clone( SfxItemPool* ) const +{ + return new SvxSizeItem( *this ); +} + +bool SvxSizeItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = GetMetricText( m_aSize.Width(), eCoreUnit, ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( m_aSize.Height(), eCoreUnit, ePresUnit, &rIntl ); + return true; + + case SfxItemPresentation::Complete: + rText = EditResId(RID_SVXITEMS_SIZE_WIDTH) + + GetMetricText( m_aSize.Width(), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_SIZE_HEIGHT) + + GetMetricText( m_aSize.Height(), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + return true; + // no break necessary + default: ; // prevent warning + + } + return false; +} + + +void SvxSizeItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + m_aSize.setWidth( BigInt::Scale( m_aSize.Width(), nMult, nDiv ) ); + m_aSize.setHeight( BigInt::Scale( m_aSize.Height(), nMult, nDiv ) ); +} + + +bool SvxSizeItem::HasMetrics() const +{ + return true; +} + + +SvxLRSpaceItem::SvxLRSpaceItem(const sal_uInt16 nId) + : SfxPoolItem(nId) + , nFirstLineOffset(0) + , nLeftMargin(0) + , nRightMargin(0) + , m_nGutterMargin(0) + , m_nRightGutterMargin(0), + nPropFirstLineOffset( 100 ), + nPropLeftMargin( 100 ), + nPropRightMargin( 100 ), + bAutoFirst ( false ), + bExplicitZeroMarginValRight(false), + bExplicitZeroMarginValLeft(false) +{ +} + + +SvxLRSpaceItem::SvxLRSpaceItem( const tools::Long nLeft, const tools::Long nRight, + const short nOfset, + const sal_uInt16 nId ) + : SfxPoolItem(nId) + , nFirstLineOffset(nOfset) + , nLeftMargin(nLeft) + , nRightMargin(nRight) + , m_nGutterMargin(0) + , m_nRightGutterMargin(0), + nPropFirstLineOffset( 100 ), + nPropLeftMargin( 100 ), + nPropRightMargin( 100 ), + bAutoFirst ( false ), + bExplicitZeroMarginValRight(false), + bExplicitZeroMarginValLeft(false) +{ +} + + +bool SvxLRSpaceItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bRet = true; + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + // now all signed + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + aLRSpace.Left = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLeftMargin) : nLeftMargin); + aLRSpace.TextLeft = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetTextLeft()) : GetTextLeft()); + aLRSpace.Right = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nRightMargin) : nRightMargin); + aLRSpace.ScaleLeft = static_cast<sal_Int16>(nPropLeftMargin); + aLRSpace.ScaleRight = static_cast<sal_Int16>(nPropRightMargin); + aLRSpace.FirstLine = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nFirstLineOffset) : nFirstLineOffset); + aLRSpace.ScaleFirstLine = static_cast<sal_Int16>(nPropFirstLineOffset); + aLRSpace.AutoFirstLine = IsAutoFirst(); + rVal <<= aLRSpace; + break; + } + case MID_L_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLeftMargin) : nLeftMargin); + break; + + case MID_TXT_LMARGIN : + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetTextLeft()) : GetTextLeft()); + break; + case MID_R_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nRightMargin) : nRightMargin); + break; + case MID_L_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(nPropLeftMargin); + break; + case MID_R_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(nPropRightMargin); + break; + + case MID_FIRST_LINE_INDENT: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nFirstLineOffset) : nFirstLineOffset); + break; + + case MID_FIRST_LINE_REL_INDENT: + rVal <<= static_cast<sal_Int16>(nPropFirstLineOffset); + break; + + case MID_FIRST_AUTO: + rVal <<= IsAutoFirst(); + break; + + case MID_GUTTER_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nGutterMargin) + : m_nGutterMargin); + break; + + default: + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + + +bool SvxLRSpaceItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0 != (nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + if( nMemberId != 0 && nMemberId != MID_FIRST_AUTO && + nMemberId != MID_L_REL_MARGIN && nMemberId != MID_R_REL_MARGIN) + if(!(rVal >>= nVal)) + return false; + + switch( nMemberId ) + { + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + if(!(rVal >>= aLRSpace)) + return false; + + SetLeft( bConvert ? o3tl::toTwips(aLRSpace.Left, o3tl::Length::mm100) : aLRSpace.Left ); + SetTextLeft( bConvert ? o3tl::toTwips(aLRSpace.TextLeft, o3tl::Length::mm100) : aLRSpace.TextLeft ); + SetRight(bConvert ? o3tl::toTwips(aLRSpace.Right, o3tl::Length::mm100) : aLRSpace.Right); + nPropLeftMargin = aLRSpace.ScaleLeft; + nPropRightMargin = aLRSpace.ScaleRight; + SetTextFirstLineOffset(bConvert ? o3tl::toTwips(aLRSpace.FirstLine, o3tl::Length::mm100) : aLRSpace.FirstLine); + SetPropTextFirstLineOffset ( aLRSpace.ScaleFirstLine ); + SetAutoFirst( aLRSpace.AutoFirstLine ); + break; + } + case MID_L_MARGIN: + SetLeft( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + break; + + case MID_TXT_LMARGIN : + SetTextLeft( bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal ); + break; + + case MID_R_MARGIN: + SetRight(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_L_REL_MARGIN: + case MID_R_REL_MARGIN: + { + sal_Int32 nRel = 0; + if((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + if(MID_L_REL_MARGIN== nMemberId) + nPropLeftMargin = static_cast<sal_uInt16>(nRel); + else + nPropRightMargin = static_cast<sal_uInt16>(nRel); + } + else + return false; + } + break; + case MID_FIRST_LINE_INDENT : + SetTextFirstLineOffset(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + + case MID_FIRST_LINE_REL_INDENT: + SetPropTextFirstLineOffset ( nVal ); + break; + + case MID_FIRST_AUTO: + SetAutoFirst( Any2Bool(rVal) ); + break; + + case MID_GUTTER_MARGIN: + SetGutterMargin(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + + default: + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +void SvxLeftMarginItem::SetLeft(const tools::Long nL, const sal_uInt16 nProp) +{ + m_nLeftMargin = (nL * nProp) / 100; + m_nPropLeftMargin = nProp; +} + +void SvxLRSpaceItem::SetLeft(const tools::Long nL, const sal_uInt16 nProp) +{ + nLeftMargin = (nL * nProp) / 100; + SAL_WARN_IF(nFirstLineOffset != 0, "editeng", "probably call SetTextLeft instead? looks inconsistent otherwise"); + nPropLeftMargin = nProp; +} + +void SvxRightMarginItem::SetRight(const tools::Long nR, const sal_uInt16 nProp) +{ + m_nRightMargin = (nR * nProp) / 100; + m_nPropRightMargin = nProp; +} + +void SvxLRSpaceItem::SetRight(const tools::Long nR, const sal_uInt16 nProp) +{ + if (0 == nR) + { + SetExplicitZeroMarginValRight(true); + } + nRightMargin = (nR * nProp) / 100; + nPropRightMargin = nProp; +} + +void SvxFirstLineIndentItem::SetTextFirstLineOffset( + const short nF, const sal_uInt16 nProp) +{ + m_nFirstLineOffset = short((tools::Long(nF) * nProp ) / 100); + m_nPropFirstLineOffset = nProp; +} + +void SvxLRSpaceItem::SetTextFirstLineOffset(const short nF, const sal_uInt16 nProp) +{ + // note: left margin contains any negative first line offset - preserve it! + if (nFirstLineOffset < 0) + { + nLeftMargin -= nFirstLineOffset; + } + nFirstLineOffset = short((tools::Long(nF) * nProp ) / 100); + nPropFirstLineOffset = nProp; + if (nFirstLineOffset < 0) + { + nLeftMargin += nFirstLineOffset; + } +} + +#if 0 +void SvxTextLeftMarginItem::SetLeft(SvxFirstLineIndentItem const& rFirstLine, + const tools::Long nL, const sal_uInt16 nProp) +{ + m_nTextLeftMargin = (nL * nProp) / 100; + m_nPropLeftMargin = nProp; + // note: text left margin contains any negative first line offset + if (rFirstLine.GetTextFirstLineOffset() < 0) + { + m_nTextLeftMargin += rFirstLine.GetTextFirstLineOffset(); + } +} +#endif + +void SvxTextLeftMarginItem::SetTextLeft(const tools::Long nL, const sal_uInt16 nProp) +{ + m_nTextLeftMargin = (nL * nProp) / 100; + m_nPropLeftMargin = nProp; +} + +void SvxLRSpaceItem::SetTextLeft(const tools::Long nL, const sal_uInt16 nProp) +{ + if (0 == nL) + { + SetExplicitZeroMarginValLeft(true); + } + auto const nTxtLeft = (nL * nProp) / 100; + nPropLeftMargin = nProp; + // note: left margin contains any negative first line offset + if ( 0 > nFirstLineOffset ) + nLeftMargin = nTxtLeft + nFirstLineOffset; + else + nLeftMargin = nTxtLeft; +} + +tools::Long SvxTextLeftMarginItem::GetTextLeft() const +{ + return m_nTextLeftMargin; +} + +tools::Long SvxTextLeftMarginItem::GetLeft(SvxFirstLineIndentItem const& rFirstLine) const +{ + // add any negative first line offset to text left margin to get left + return (rFirstLine.GetTextFirstLineOffset() < 0) + ? m_nTextLeftMargin + rFirstLine.GetTextFirstLineOffset() + : m_nTextLeftMargin; +} + +tools::Long SvxLRSpaceItem::GetTextLeft() const +{ + // remove any negative first line offset from left margin to get text-left + return (nFirstLineOffset < 0) + ? nLeftMargin - nFirstLineOffset + : nLeftMargin; +} + +SvxLeftMarginItem::SvxLeftMarginItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +SvxLeftMarginItem::SvxLeftMarginItem(const tools::Long nLeft, const sal_uInt16 nId) + : SfxPoolItem(nId) + , m_nLeftMargin(nLeft) +{ +} + +bool SvxLeftMarginItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + bool bRet = true; + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_L_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nLeftMargin) : m_nLeftMargin); + break; + case MID_L_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(m_nPropLeftMargin); + break; + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + +bool SvxLeftMarginItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch (nMemberId) + { + case MID_L_MARGIN: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + { + return false; + } + SetLeft(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + } + case MID_L_REL_MARGIN: + { + sal_Int32 nRel = 0; + if ((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + m_nPropLeftMargin = static_cast<sal_uInt16>(nRel); + } + else + { + return false; + } + } + break; + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +bool SvxLeftMarginItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxLeftMarginItem& rOther = static_cast<const SvxLeftMarginItem&>(rAttr); + + return (m_nLeftMargin == rOther.GetLeft() + && m_nPropLeftMargin == rOther.GetPropLeft()); +} + +SvxLeftMarginItem* SvxLeftMarginItem::Clone(SfxItemPool *) const +{ + return new SvxLeftMarginItem(*this); +} + +bool SvxLeftMarginItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch (ePres) + { + case SfxItemPresentation::Nameless: + { + if (100 != m_nPropLeftMargin) + { + rText = unicode::formatPercent(m_nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText = GetMetricText(m_nLeftMargin, + eCoreUnit, ePresUnit, &rIntl); + } + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_LRSPACE_LEFT); + if (100 != m_nPropLeftMargin) + { + rText += unicode::formatPercent(m_nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(m_nLeftMargin, eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + +void SvxLeftMarginItem::ScaleMetrics(tools::Long const nMult, tools::Long const nDiv) +{ + m_nLeftMargin = BigInt::Scale(m_nLeftMargin, nMult, nDiv); +} + +bool SvxLeftMarginItem::HasMetrics() const +{ + return true; +} + +void SvxLeftMarginItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxLeftMarginItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nLeftMargin"), BAD_CAST(OString::number(m_nLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPropLeftMargin"), BAD_CAST(OString::number(m_nPropLeftMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxLeftMarginItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sLeft = GetMetricText(GetLeft(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("left", sLeft); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxTextLeftMarginItem::SvxTextLeftMarginItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +SvxTextLeftMarginItem::SvxTextLeftMarginItem(const tools::Long nLeft, const sal_uInt16 nId) + : SfxPoolItem(nId) + , m_nTextLeftMargin(nLeft) +{ +} + +bool SvxTextLeftMarginItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + bool bRet = true; + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + // tdf#154282 - return both values for the hardcoded 0 in SfxDispatchController_Impl::StateChanged + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + aLRSpace.TextLeft = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetTextLeft()) : GetTextLeft()); + aLRSpace.ScaleLeft = static_cast<sal_Int16>(m_nPropLeftMargin); + rVal <<= aLRSpace; + break; + } + case MID_TXT_LMARGIN : + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetTextLeft()) : GetTextLeft()); + break; + case MID_L_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(m_nPropLeftMargin); + break; + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + +bool SvxTextLeftMarginItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch (nMemberId) + { + case MID_TXT_LMARGIN: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + { + return false; + } + SetTextLeft(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + } + break; + case MID_L_REL_MARGIN: + { + sal_Int32 nRel = 0; + if ((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + m_nPropLeftMargin = static_cast<sal_uInt16>(nRel); + } + else + { + return false; + } + } + break; + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +bool SvxTextLeftMarginItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxTextLeftMarginItem& rOther = static_cast<const SvxTextLeftMarginItem&>(rAttr); + + return (m_nTextLeftMargin == rOther.GetTextLeft() + && m_nPropLeftMargin == rOther.GetPropLeft()); +} + +SvxTextLeftMarginItem* SvxTextLeftMarginItem::Clone(SfxItemPool *) const +{ + return new SvxTextLeftMarginItem(*this); +} + +bool SvxTextLeftMarginItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch (ePres) + { + case SfxItemPresentation::Nameless: + { + if (100 != m_nPropLeftMargin) + { + rText = unicode::formatPercent(m_nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText = GetMetricText(m_nTextLeftMargin, + eCoreUnit, ePresUnit, &rIntl); + } + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_LRSPACE_LEFT); + if (100 != m_nPropLeftMargin) + { + rText += unicode::formatPercent(m_nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(m_nTextLeftMargin, eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + +void SvxTextLeftMarginItem::ScaleMetrics(tools::Long const nMult, tools::Long const nDiv) +{ + m_nTextLeftMargin = BigInt::Scale(m_nTextLeftMargin, nMult, nDiv); +} + +bool SvxTextLeftMarginItem::HasMetrics() const +{ + return true; +} + +void SvxTextLeftMarginItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTextLeftMarginItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nTextLeftMargin"), BAD_CAST(OString::number(m_nTextLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPropLeftMargin"), BAD_CAST(OString::number(m_nPropLeftMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxTextLeftMarginItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sLeft = GetMetricText(GetTextLeft(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("left", sLeft); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxFirstLineIndentItem::SvxFirstLineIndentItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +SvxFirstLineIndentItem::SvxFirstLineIndentItem(const short nFirst, const sal_uInt16 nId) + : SfxPoolItem(nId) + , m_nFirstLineOffset(nFirst) +{ +} + +bool SvxFirstLineIndentItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + bool bRet = true; + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_FIRST_LINE_INDENT: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nFirstLineOffset) : m_nFirstLineOffset); + break; + case MID_FIRST_LINE_REL_INDENT: + rVal <<= static_cast<sal_Int16>(m_nPropFirstLineOffset); + break; + case MID_FIRST_AUTO: + rVal <<= IsAutoFirst(); + break; + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + +bool SvxFirstLineIndentItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch (nMemberId) + { + case MID_FIRST_LINE_INDENT: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + { + return false; + } + m_nFirstLineOffset = bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal; + m_nPropFirstLineOffset = 100; + break; + } + case MID_FIRST_LINE_REL_INDENT: + { + sal_Int32 nRel = 0; + if ((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + SetPropTextFirstLineOffset(nRel); + } + else + { + return false; + } + break; + } + case MID_FIRST_AUTO: + SetAutoFirst(Any2Bool(rVal)); + break; + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +bool SvxFirstLineIndentItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxFirstLineIndentItem& rOther = static_cast<const SvxFirstLineIndentItem&>(rAttr); + + return (m_nFirstLineOffset == rOther.GetTextFirstLineOffset() + && m_nPropFirstLineOffset == rOther.GetPropTextFirstLineOffset() + && m_bAutoFirst == rOther.IsAutoFirst()); +} + +SvxFirstLineIndentItem* SvxFirstLineIndentItem::Clone(SfxItemPool *) const +{ + return new SvxFirstLineIndentItem(*this); +} + +bool SvxFirstLineIndentItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch (ePres) + { + case SfxItemPresentation::Nameless: + { + if (100 != m_nPropFirstLineOffset) + { + rText += unicode::formatPercent(m_nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(static_cast<tools::Long>(m_nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl); + } + return true; + } + case SfxItemPresentation::Complete: + { + rText += EditResId(RID_SVXITEMS_LRSPACE_FLINE); + if (100 != m_nPropFirstLineOffset) + { + rText += unicode::formatPercent(m_nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(static_cast<tools::Long>(m_nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + +void SvxFirstLineIndentItem::ScaleMetrics(tools::Long const nMult, tools::Long const nDiv) +{ + m_nFirstLineOffset = static_cast<short>(BigInt::Scale(m_nFirstLineOffset, nMult, nDiv)); +} + +bool SvxFirstLineIndentItem::HasMetrics() const +{ + return true; +} + +void SvxFirstLineIndentItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFirstLineIndentItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nFirstLineOffset"), BAD_CAST(OString::number(m_nFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPropFirstLineOffset"), BAD_CAST(OString::number(m_nPropFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_bAutoFirst"), BAD_CAST(OString::number(int(m_bAutoFirst)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxFirstLineIndentItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sFirstline = GetMetricText(GetTextFirstLineOffset(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("firstline", sFirstline); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxRightMarginItem::SvxRightMarginItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +SvxRightMarginItem::SvxRightMarginItem(const tools::Long nRight, const sal_uInt16 nId) + : SfxPoolItem(nId) + , m_nRightMargin(nRight) +{ +} + +bool SvxRightMarginItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + bool bRet = true; + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + // tdf#154282 - return both values for the hardcoded 0 in SfxDispatchController_Impl::StateChanged + case 0: + { + css::frame::status::LeftRightMarginScale aLRSpace; + aLRSpace.Right = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nRightMargin) : m_nRightMargin); + aLRSpace.ScaleRight = static_cast<sal_Int16>(m_nPropRightMargin); + rVal <<= aLRSpace; + break; + } + case MID_R_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nRightMargin) : m_nRightMargin); + break; + case MID_R_REL_MARGIN: + rVal <<= static_cast<sal_Int16>(m_nPropRightMargin); + break; + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + +bool SvxRightMarginItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch (nMemberId) + { + case MID_R_MARGIN: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + { + return false; + } + SetRight(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + } + case MID_R_REL_MARGIN: + { + sal_Int32 nRel = 0; + if ((rVal >>= nRel) && nRel >= 0 && nRel < SAL_MAX_UINT16) + { + m_nPropRightMargin = static_cast<sal_uInt16>(nRel); + } + else + { + return false; + } + } + break; + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +bool SvxRightMarginItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxRightMarginItem& rOther = static_cast<const SvxRightMarginItem&>(rAttr); + + return (m_nRightMargin == rOther.GetRight() + && m_nPropRightMargin == rOther.GetPropRight()); +} + +SvxRightMarginItem* SvxRightMarginItem::Clone(SfxItemPool *) const +{ + return new SvxRightMarginItem(*this); +} + +bool SvxRightMarginItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch (ePres) + { + case SfxItemPresentation::Nameless: + { + if (100 != m_nRightMargin) + { + rText += unicode::formatPercent(m_nRightMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(m_nRightMargin, + eCoreUnit, ePresUnit, &rIntl); + } + return true; + } + case SfxItemPresentation::Complete: + { + rText += EditResId(RID_SVXITEMS_LRSPACE_RIGHT); + if (100 != m_nPropRightMargin) + { + rText += unicode::formatPercent(m_nPropRightMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText(m_nRightMargin, + eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + +void SvxRightMarginItem::ScaleMetrics(tools::Long const nMult, tools::Long const nDiv) +{ + m_nRightMargin = BigInt::Scale(m_nRightMargin, nMult, nDiv); +} + +bool SvxRightMarginItem::HasMetrics() const +{ + return true; +} + +void SvxRightMarginItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxRightMarginItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nRightMargin"), BAD_CAST(OString::number(m_nRightMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nPropRightMargin"), BAD_CAST(OString::number(m_nPropRightMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxRightMarginItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sRight = GetMetricText(GetRight(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("right", sRight); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxGutterLeftMarginItem::SvxGutterLeftMarginItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +bool SvxGutterLeftMarginItem::QueryValue(uno::Any& rVal, sal_uInt8 nMemberId) const +{ + bool bRet = true; + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_GUTTER_MARGIN: + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(m_nGutterMargin) + : m_nGutterMargin); + break; + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } + return bRet; +} + +bool SvxGutterLeftMarginItem::PutValue(const uno::Any& rVal, sal_uInt8 nMemberId) +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + switch (nMemberId) + { + case MID_GUTTER_MARGIN: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + { + return false; + } + SetGutterMargin(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + } + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + +bool SvxGutterLeftMarginItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxGutterLeftMarginItem& rOther = static_cast<const SvxGutterLeftMarginItem&>(rAttr); + + return (m_nGutterMargin == rOther.GetGutterMargin()); +} + +SvxGutterLeftMarginItem* SvxGutterLeftMarginItem::Clone(SfxItemPool * ) const +{ + return new SvxGutterLeftMarginItem(*this); +} + +bool SvxGutterLeftMarginItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& /*rText*/, const IntlWrapper& /*rIntl*/ +) const +{ + // TODO? + return false; +} + +void SvxGutterLeftMarginItem::ScaleMetrics(tools::Long const /*nMult*/, tools::Long const /*nDiv*/) +{ + // TODO? +} + +bool SvxGutterLeftMarginItem::HasMetrics() const +{ + return true; +} + +void SvxGutterLeftMarginItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxGutterLeftMarginItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nGutterMargin"), + BAD_CAST(OString::number(m_nGutterMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxGutterLeftMarginItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + // TODO? + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxGutterRightMarginItem::SvxGutterRightMarginItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + +bool SvxGutterRightMarginItem::QueryValue(uno::Any& /*rVal*/, sal_uInt8 nMemberId) const +{ + bool bRet = true; + //bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; +#ifndef _MSC_VER + switch (nMemberId) + { + // TODO? + default: + assert(false); + bRet = false; + // SfxDispatchController_Impl::StateChanged calls this with hardcoded 0 triggering this; there used to be a MID_LR_MARGIN 0 but what type would it have? + OSL_FAIL("unknown MemberId"); + } +#else + (void) nMemberId; +#endif + return bRet; +} + +bool SvxGutterRightMarginItem::PutValue(const uno::Any& /*rVal*/, sal_uInt8 nMemberId) +{ + //bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + +#ifndef _MSC_VER + switch (nMemberId) + { + // TODO? + default: + assert(false); + OSL_FAIL("unknown MemberId"); + return false; + } +#else + (void) nMemberId; +#endif + return true; +} + + +bool SvxGutterRightMarginItem::operator==(const SfxPoolItem& rAttr) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxGutterRightMarginItem& rOther = static_cast<const SvxGutterRightMarginItem&>(rAttr); + + return (m_nRightGutterMargin == rOther.GetRightGutterMargin()); +} + +SvxGutterRightMarginItem* SvxGutterRightMarginItem::Clone(SfxItemPool *) const +{ + return new SvxGutterRightMarginItem(*this); +} + +bool SvxGutterRightMarginItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& /*rText*/, const IntlWrapper& /*rIntl*/ +) const +{ + // TODO? + return false; +} + +void SvxGutterRightMarginItem::ScaleMetrics(tools::Long const /*nMult*/, tools::Long const /*nDiv*/) +{ + // TODO? +} + +bool SvxGutterRightMarginItem::HasMetrics() const +{ + return true; +} + +void SvxGutterRightMarginItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxGutterRightMarginItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nRightGutterMargin"), + BAD_CAST(OString::number(m_nRightGutterMargin).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxGutterRightMarginItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + // TODO? + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + + +bool SvxLRSpaceItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxLRSpaceItem& rOther = static_cast<const SvxLRSpaceItem&>(rAttr); + + return ( + nFirstLineOffset == rOther.GetTextFirstLineOffset() && + m_nGutterMargin == rOther.GetGutterMargin() && + m_nRightGutterMargin == rOther.GetRightGutterMargin() && + nLeftMargin == rOther.GetLeft() && + nRightMargin == rOther.GetRight() && + nPropFirstLineOffset == rOther.GetPropTextFirstLineOffset() && + nPropLeftMargin == rOther.GetPropLeft() && + nPropRightMargin == rOther.GetPropRight() && + bAutoFirst == rOther.IsAutoFirst() && + bExplicitZeroMarginValRight == rOther.IsExplicitZeroMarginValRight() && + bExplicitZeroMarginValLeft == rOther.IsExplicitZeroMarginValLeft() ); +} + +SvxLRSpaceItem* SvxLRSpaceItem::Clone( SfxItemPool* ) const +{ + return new SvxLRSpaceItem( *this ); +} + +bool SvxLRSpaceItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + if ( 100 != nPropLeftMargin ) + { + rText = unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + rText = GetMetricText( nLeftMargin, + eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nPropFirstLineOffset ) + { + rText += unicode::formatPercent(nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( static_cast<tools::Long>(nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nRightMargin ) + { + rText += unicode::formatPercent(nRightMargin, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( nRightMargin, + eCoreUnit, ePresUnit, &rIntl ); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_LRSPACE_LEFT); + if ( 100 != nPropLeftMargin ) + rText += unicode::formatPercent(nPropLeftMargin, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( nLeftMargin, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim; + if ( 100 != nPropFirstLineOffset || nFirstLineOffset ) + { + rText += EditResId(RID_SVXITEMS_LRSPACE_FLINE); + if ( 100 != nPropFirstLineOffset ) + rText += unicode::formatPercent(nPropFirstLineOffset, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( static_cast<tools::Long>(nFirstLineOffset), + eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim; + } + rText += EditResId(RID_SVXITEMS_LRSPACE_RIGHT); + if ( 100 != nPropRightMargin ) + rText += unicode::formatPercent(nPropRightMargin, + Application::GetSettings().GetUILanguageTag()); + else + { + rText += GetMetricText( nRightMargin, + eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxLRSpaceItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nFirstLineOffset = static_cast<short>(BigInt::Scale( nFirstLineOffset, nMult, nDiv )); + nLeftMargin = BigInt::Scale( nLeftMargin, nMult, nDiv ); + nRightMargin = BigInt::Scale( nRightMargin, nMult, nDiv ); +} + + +bool SvxLRSpaceItem::HasMetrics() const +{ + return true; +} + + +void SvxLRSpaceItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxLRSpaceItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nFirstLineOffset"), BAD_CAST(OString::number(nFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLeftMargin"), BAD_CAST(OString::number(nLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRightMargin"), BAD_CAST(OString::number(nRightMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nGutterMargin"), + BAD_CAST(OString::number(m_nGutterMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nRightGutterMargin"), + BAD_CAST(OString::number(m_nRightGutterMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropFirstLineOffset"), BAD_CAST(OString::number(nPropFirstLineOffset).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropLeftMargin"), BAD_CAST(OString::number(nPropLeftMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropRightMargin"), BAD_CAST(OString::number(nPropRightMargin).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bAutoFirst"), BAD_CAST(OString::number(int(bAutoFirst)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bExplicitZeroMarginValRight"), BAD_CAST(OString::number(int(bExplicitZeroMarginValRight)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bExplicitZeroMarginValLeft"), BAD_CAST(OString::number(int(bExplicitZeroMarginValLeft)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +boost::property_tree::ptree SvxLRSpaceItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sLeft = GetMetricText(GetLeft(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sRight = GetMetricText(GetRight(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sFirstline = GetMetricText(GetTextFirstLineOffset(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("left", sLeft); + aState.put("right", sRight); + aState.put("firstline", sFirstline); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + + +SvxULSpaceItem::SvxULSpaceItem( const sal_uInt16 nId ) + : SfxPoolItem(nId) + , nUpper(0) + , nLower(0) + , bContext(false) + , nPropUpper(100) + , nPropLower(100) +{ +} + + +SvxULSpaceItem::SvxULSpaceItem( const sal_uInt16 nUp, const sal_uInt16 nLow, + const sal_uInt16 nId ) + : SfxPoolItem(nId) + , nUpper(nUp) + , nLower(nLow) + , bContext(false) + , nPropUpper(100) + , nPropLower(100) +{ +} + + +bool SvxULSpaceItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + // now all signed + case 0: + { + css::frame::status::UpperLowerMarginScale aUpperLowerMarginScale; + aUpperLowerMarginScale.Upper = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nUpper) : nUpper); + aUpperLowerMarginScale.Lower = static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLower) : nPropUpper); + aUpperLowerMarginScale.ScaleUpper = static_cast<sal_Int16>(nPropUpper); + aUpperLowerMarginScale.ScaleLower = static_cast<sal_Int16>(nPropLower); + rVal <<= aUpperLowerMarginScale; + break; + } + case MID_UP_MARGIN: rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nUpper) : nUpper); break; + case MID_LO_MARGIN: rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nLower) : nLower); break; + case MID_CTX_MARGIN: rVal <<= bContext; break; + case MID_UP_REL_MARGIN: rVal <<= static_cast<sal_Int16>(nPropUpper); break; + case MID_LO_REL_MARGIN: rVal <<= static_cast<sal_Int16>(nPropLower); break; + } + return true; +} + + +bool SvxULSpaceItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + bool bVal = false; + switch( nMemberId ) + { + case 0: + { + css::frame::status::UpperLowerMarginScale aUpperLowerMarginScale; + if ( !(rVal >>= aUpperLowerMarginScale )) + return false; + { + SetUpper(bConvert ? o3tl::toTwips(aUpperLowerMarginScale.Upper, o3tl::Length::mm100) : aUpperLowerMarginScale.Upper); + SetLower(bConvert ? o3tl::toTwips(aUpperLowerMarginScale.Lower, o3tl::Length::mm100) : aUpperLowerMarginScale.Lower); + if( aUpperLowerMarginScale.ScaleUpper > 1 ) + nPropUpper = aUpperLowerMarginScale.ScaleUpper; + if( aUpperLowerMarginScale.ScaleLower > 1 ) + nPropUpper = aUpperLowerMarginScale.ScaleLower; + } + } + break; + case MID_UP_MARGIN : + if(!(rVal >>= nVal)) + return false; + SetUpper(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_LO_MARGIN : + if(!(rVal >>= nVal) || nVal < 0) + return false; + SetLower(bConvert ? o3tl::toTwips(nVal, o3tl::Length::mm100) : nVal); + break; + case MID_CTX_MARGIN : + if (!(rVal >>= bVal)) + return false; + SetContextValue(bVal); + break; + case MID_UP_REL_MARGIN: + case MID_LO_REL_MARGIN: + { + sal_Int32 nRel = 0; + if((rVal >>= nRel) && nRel > 1 ) + { + if(MID_UP_REL_MARGIN == nMemberId) + nPropUpper = static_cast<sal_uInt16>(nRel); + else + nPropLower = static_cast<sal_uInt16>(nRel); + } + else + return false; + } + break; + + default: + OSL_FAIL("unknown MemberId"); + return false; + } + return true; +} + + +bool SvxULSpaceItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxULSpaceItem& rSpaceItem = static_cast<const SvxULSpaceItem&>( rAttr ); + return ( nUpper == rSpaceItem.nUpper && + nLower == rSpaceItem.nLower && + bContext == rSpaceItem.bContext && + nPropUpper == rSpaceItem.nPropUpper && + nPropLower == rSpaceItem.nPropLower ); +} + +SvxULSpaceItem* SvxULSpaceItem::Clone( SfxItemPool* ) const +{ + return new SvxULSpaceItem( *this ); +} + +bool SvxULSpaceItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, + const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + if ( 100 != nPropUpper ) + { + rText = unicode::formatPercent(nPropUpper, + Application::GetSettings().GetUILanguageTag()); + } + else + rText = GetMetricText( static_cast<tools::Long>(nUpper), eCoreUnit, ePresUnit, &rIntl ); + rText += cpDelim; + if ( 100 != nPropLower ) + { + rText += unicode::formatPercent(nPropLower, + Application::GetSettings().GetUILanguageTag()); + } + else + rText += GetMetricText( static_cast<tools::Long>(nLower), eCoreUnit, ePresUnit, &rIntl ); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_ULSPACE_UPPER); + if ( 100 != nPropUpper ) + { + rText += unicode::formatPercent(nPropUpper, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText( static_cast<tools::Long>(nUpper), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + rText += cpDelim + EditResId(RID_SVXITEMS_ULSPACE_LOWER); + if ( 100 != nPropLower ) + { + rText += unicode::formatPercent(nPropLower, + Application::GetSettings().GetUILanguageTag()); + } + else + { + rText += GetMetricText( static_cast<tools::Long>(nLower), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxULSpaceItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nUpper = static_cast<sal_uInt16>(BigInt::Scale( nUpper, nMult, nDiv )); + nLower = static_cast<sal_uInt16>(BigInt::Scale( nLower, nMult, nDiv )); +} + + +bool SvxULSpaceItem::HasMetrics() const +{ + return true; +} + + +void SvxULSpaceItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxULSpaceItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUpper"), BAD_CAST(OString::number(nUpper).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nLower"), BAD_CAST(OString::number(nLower).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bContext"), BAD_CAST(OString::boolean(bContext).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropUpper"), BAD_CAST(OString::number(nPropUpper).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nPropLower"), BAD_CAST(OString::number(nPropLower).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxULSpaceItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON(); + + boost::property_tree::ptree aState; + + MapUnit eTargetUnit = MapUnit::MapInch; + + OUString sUpper = GetMetricText(GetUpper(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + OUString sLower = GetMetricText(GetLower(), + MapUnit::MapTwip, eTargetUnit, nullptr); + + aState.put("upper", sUpper); + aState.put("lower", sLower); + aState.put("unit", "inch"); + + aTree.push_back(std::make_pair("state", aState)); + + return aTree; +} + +SvxPrintItem* SvxPrintItem::Clone( SfxItemPool* ) const +{ + return new SvxPrintItem( *this ); +} + +bool SvxPrintItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_PRINT_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_PRINT_TRUE; + rText = EditResId(pId); + return true; +} + +SvxOpaqueItem* SvxOpaqueItem::Clone( SfxItemPool* ) const +{ + return new SvxOpaqueItem( *this ); +} + +bool SvxOpaqueItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_OPAQUE_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_OPAQUE_TRUE; + rText = EditResId(pId); + return true; +} + + +bool SvxProtectItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxProtectItem& rItem = static_cast<const SvxProtectItem&>(rAttr); + return ( bCntnt == rItem.bCntnt && + bSize == rItem.bSize && + bPos == rItem.bPos ); +} + + +bool SvxProtectItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bValue; + switch(nMemberId) + { + case MID_PROTECT_CONTENT : bValue = bCntnt; break; + case MID_PROTECT_SIZE : bValue = bSize; break; + case MID_PROTECT_POSITION: bValue = bPos; break; + default: + OSL_FAIL("Wrong MemberId"); + return false; + } + + rVal <<= bValue; + return true; +} + + +bool SvxProtectItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bVal( Any2Bool(rVal) ); + switch(nMemberId) + { + case MID_PROTECT_CONTENT : bCntnt = bVal; break; + case MID_PROTECT_SIZE : bSize = bVal; break; + case MID_PROTECT_POSITION: bPos = bVal; break; + default: + OSL_FAIL("Wrong MemberId"); + return false; + } + return true; +} + +SvxProtectItem* SvxProtectItem::Clone( SfxItemPool* ) const +{ + return new SvxProtectItem( *this ); +} + +bool SvxProtectItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_PROT_CONTENT_FALSE; + + if ( bCntnt ) + pId = RID_SVXITEMS_PROT_CONTENT_TRUE; + rText = EditResId(pId) + cpDelim; + pId = RID_SVXITEMS_PROT_SIZE_FALSE; + + if ( bSize ) + pId = RID_SVXITEMS_PROT_SIZE_TRUE; + rText += EditResId(pId) + cpDelim; + pId = RID_SVXITEMS_PROT_POS_FALSE; + + if ( bPos ) + pId = RID_SVXITEMS_PROT_POS_TRUE; + rText += EditResId(pId); + return true; +} + + +void SvxProtectItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxProtectItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("content"), BAD_CAST(OString::boolean(bCntnt).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::boolean(bSize).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("position"), BAD_CAST(OString::boolean(bPos).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxShadowItem::SvxShadowItem( const sal_uInt16 nId, + const Color *pColor, const sal_uInt16 nW, + const SvxShadowLocation eLoc ) : + SfxEnumItemInterface( nId ), + aShadowColor(COL_GRAY), + nWidth ( nW ), + eLocation ( eLoc ) +{ + if ( pColor ) + aShadowColor = *pColor; +} + + +bool SvxShadowItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + table::ShadowFormat aShadow; + table::ShadowLocation eSet = table::ShadowLocation_NONE; + switch( eLocation ) + { + case SvxShadowLocation::TopLeft : eSet = table::ShadowLocation_TOP_LEFT ; break; + case SvxShadowLocation::TopRight : eSet = table::ShadowLocation_TOP_RIGHT ; break; + case SvxShadowLocation::BottomLeft : eSet = table::ShadowLocation_BOTTOM_LEFT ; break; + case SvxShadowLocation::BottomRight: eSet = table::ShadowLocation_BOTTOM_RIGHT; break; + default: ; // prevent warning + } + aShadow.Location = eSet; + aShadow.ShadowWidth = bConvert ? convertTwipToMm100(nWidth) : nWidth; + aShadow.IsTransparent = aShadowColor.IsTransparent(); + aShadow.Color = sal_Int32(aShadowColor); + + sal_Int8 nTransparence = rtl::math::round((float(255 - aShadowColor.GetAlpha()) * 100) / 255); + + switch ( nMemberId ) + { + case MID_LOCATION: rVal <<= aShadow.Location; break; + case MID_WIDTH: rVal <<= aShadow.ShadowWidth; break; + case MID_TRANSPARENT: rVal <<= aShadow.IsTransparent; break; + case MID_BG_COLOR: rVal <<= aShadow.Color; break; + case 0: rVal <<= aShadow; break; + case MID_SHADOW_TRANSPARENCE: rVal <<= nTransparence; break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + +bool SvxShadowItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + table::ShadowFormat aShadow; + uno::Any aAny; + bool bRet = QueryValue( aAny, bConvert ? CONVERT_TWIPS : 0 ) && ( aAny >>= aShadow ); + switch ( nMemberId ) + { + case MID_LOCATION: + { + bRet = (rVal >>= aShadow.Location); + if ( !bRet ) + { + sal_Int16 nVal = 0; + bRet = (rVal >>= nVal); + aShadow.Location = static_cast<table::ShadowLocation>(nVal); + } + + break; + } + + case MID_WIDTH: rVal >>= aShadow.ShadowWidth; break; + case MID_TRANSPARENT: rVal >>= aShadow.IsTransparent; break; + case MID_BG_COLOR: rVal >>= aShadow.Color; break; + case 0: rVal >>= aShadow; break; + case MID_SHADOW_TRANSPARENCE: + { + sal_Int32 nTransparence = 0; + if ((rVal >>= nTransparence) && !o3tl::checked_multiply<sal_Int32>(nTransparence, 255, nTransparence)) + { + Color aColor(ColorTransparency, aShadow.Color); + aColor.SetAlpha(255 - rtl::math::round(float(nTransparence) / 100)); + aShadow.Color = sal_Int32(aColor); + } + break; + } + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + if ( bRet ) + { + switch( aShadow.Location ) + { + case table::ShadowLocation_NONE : eLocation = SvxShadowLocation::NONE; break; + case table::ShadowLocation_TOP_LEFT : eLocation = SvxShadowLocation::TopLeft; break; + case table::ShadowLocation_TOP_RIGHT : eLocation = SvxShadowLocation::TopRight; break; + case table::ShadowLocation_BOTTOM_LEFT : eLocation = SvxShadowLocation::BottomLeft ; break; + case table::ShadowLocation_BOTTOM_RIGHT: eLocation = SvxShadowLocation::BottomRight; break; + default: ; // prevent warning + } + + nWidth = bConvert ? o3tl::toTwips(aShadow.ShadowWidth, o3tl::Length::mm100) : aShadow.ShadowWidth; + Color aSet(ColorTransparency, aShadow.Color); + aShadowColor = aSet; + } + + return bRet; +} + + +bool SvxShadowItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxShadowItem& rItem = static_cast<const SvxShadowItem&>(rAttr); + return ( ( aShadowColor == rItem.aShadowColor ) && + ( nWidth == rItem.GetWidth() ) && + ( eLocation == rItem.GetLocation() ) ); +} + +SvxShadowItem* SvxShadowItem::Clone( SfxItemPool* ) const +{ + return new SvxShadowItem( *this ); +} + +sal_uInt16 SvxShadowItem::CalcShadowSpace( SvxShadowItemSide nShadow ) const +{ + sal_uInt16 nSpace = 0; + + switch ( nShadow ) + { + case SvxShadowItemSide::TOP: + if ( eLocation == SvxShadowLocation::TopLeft || + eLocation == SvxShadowLocation::TopRight ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::BOTTOM: + if ( eLocation == SvxShadowLocation::BottomLeft || + eLocation == SvxShadowLocation::BottomRight ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::LEFT: + if ( eLocation == SvxShadowLocation::TopLeft || + eLocation == SvxShadowLocation::BottomLeft ) + nSpace = nWidth; + break; + + case SvxShadowItemSide::RIGHT: + if ( eLocation == SvxShadowLocation::TopRight || + eLocation == SvxShadowLocation::BottomRight ) + nSpace = nWidth; + break; + + default: + OSL_FAIL( "wrong shadow" ); + } + return nSpace; +} + +static TranslateId RID_SVXITEMS_SHADOW[] = +{ + RID_SVXITEMS_SHADOW_NONE, + RID_SVXITEMS_SHADOW_TOPLEFT, + RID_SVXITEMS_SHADOW_TOPRIGHT, + RID_SVXITEMS_SHADOW_BOTTOMLEFT, + RID_SVXITEMS_SHADOW_BOTTOMRIGHT +}; + +bool SvxShadowItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = ::GetColorString( aShadowColor ) + cpDelim; + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + + if ( aShadowColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId) + + cpDelim + + GetMetricText( static_cast<tools::Long>(nWidth), eCoreUnit, ePresUnit, &rIntl ) + + cpDelim + + EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]); + return true; + } + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_SHADOW_COMPLETE) + + ::GetColorString( aShadowColor ) + + cpDelim; + + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + if ( aShadowColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId) + + cpDelim + + GetMetricText( static_cast<tools::Long>(nWidth), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelim + + EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]); + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxShadowItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nWidth = static_cast<sal_uInt16>(BigInt::Scale( nWidth, nMult, nDiv )); +} + + +bool SvxShadowItem::HasMetrics() const +{ + return true; +} + + +sal_uInt16 SvxShadowItem::GetValueCount() const +{ + return sal_uInt16(SvxShadowLocation::End); // SvxShadowLocation::BottomRight + 1 +} + +sal_uInt16 SvxShadowItem::GetEnumValue() const +{ + return static_cast<sal_uInt16>(GetLocation()); +} + + +void SvxShadowItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetLocation( static_cast<SvxShadowLocation>(nVal) ); +} + +void SvxShadowItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxShadowItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("aShadowColor"), BAD_CAST(aShadowColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nWidth"), BAD_CAST(OString::number(nWidth).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eLocation"), BAD_CAST(OString::number(static_cast<int>(eLocation)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(EditResId(RID_SVXITEMS_SHADOW[static_cast<int>(eLocation)]).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxBoxItem ------------------------------------------------------ + +SvxBoxItem::SvxBoxItem(const SvxBoxItem& rCopy) + : SfxPoolItem (rCopy) + , mpTopBorderLine(rCopy.mpTopBorderLine ? new SvxBorderLine(*rCopy.mpTopBorderLine) : nullptr) + , mpBottomBorderLine(rCopy.mpBottomBorderLine ? new SvxBorderLine(*rCopy.mpBottomBorderLine) : nullptr) + , mpLeftBorderLine(rCopy.mpLeftBorderLine ? new SvxBorderLine(*rCopy.mpLeftBorderLine) : nullptr) + , mpRightBorderLine(rCopy.mpRightBorderLine ? new SvxBorderLine(*rCopy.mpRightBorderLine) : nullptr) + , mnTopDistance(rCopy.mnTopDistance) + , mnBottomDistance(rCopy.mnBottomDistance) + , mnLeftDistance(rCopy.mnLeftDistance) + , mnRightDistance(rCopy.mnRightDistance) + , maTempComplexColors(rCopy.maTempComplexColors) + , mbRemoveAdjCellBorder(rCopy.mbRemoveAdjCellBorder) +{ +} + + +SvxBoxItem::SvxBoxItem(const sal_uInt16 nId) + : SfxPoolItem(nId) +{ +} + + +SvxBoxItem::~SvxBoxItem() +{ +} + +void SvxBoxItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxBoxItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("top-dist"), + BAD_CAST(OString::number(mnTopDistance).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("bottom-dist"), + BAD_CAST(OString::number(mnBottomDistance).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("left-dist"), + BAD_CAST(OString::number(mnLeftDistance).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("right-dist"), + BAD_CAST(OString::number(mnRightDistance).getStr())); + SfxPoolItem::dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +boost::property_tree::ptree SvxBoxItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree; + + boost::property_tree::ptree aState; + aState.put("top", GetTop() && !GetTop()->isEmpty()); + aState.put("bottom", GetBottom() && !GetBottom()->isEmpty()); + aState.put("left", GetLeft() && !GetLeft()->isEmpty()); + aState.put("right", GetRight() && !GetRight()->isEmpty()); + + aTree.push_back(std::make_pair("state", aState)); + aTree.put("commandName", ".uno:BorderOuter"); + + return aTree; +} + + +static bool CompareBorderLine(const std::unique_ptr<SvxBorderLine> & pBrd1, const SvxBorderLine* pBrd2) +{ + if( pBrd1.get() == pBrd2 ) + return true; + if( pBrd1 == nullptr || pBrd2 == nullptr) + return false; + return *pBrd1 == *pBrd2; +} + + +bool SvxBoxItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBoxItem& rBoxItem = static_cast<const SvxBoxItem&>(rAttr); + return ( + (mnTopDistance == rBoxItem.mnTopDistance) && + (mnBottomDistance == rBoxItem.mnBottomDistance) && + (mnLeftDistance == rBoxItem.mnLeftDistance) && + (mnRightDistance == rBoxItem.mnRightDistance) && + (mbRemoveAdjCellBorder == rBoxItem.mbRemoveAdjCellBorder ) && + (maTempComplexColors == rBoxItem.maTempComplexColors) && + CompareBorderLine(mpTopBorderLine, rBoxItem.GetTop()) && + CompareBorderLine(mpBottomBorderLine, rBoxItem.GetBottom()) && + CompareBorderLine(mpLeftBorderLine, rBoxItem.GetLeft()) && + CompareBorderLine(mpRightBorderLine, rBoxItem.GetRight())); +} + + +table::BorderLine2 SvxBoxItem::SvxLineToLine(const SvxBorderLine* pLine, bool bConvert) +{ + table::BorderLine2 aLine; + if(pLine) + { + aLine.Color = sal_Int32(pLine->GetColor()); + aLine.InnerLineWidth = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetInWidth() ): pLine->GetInWidth() ); + aLine.OuterLineWidth = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetOutWidth()): pLine->GetOutWidth() ); + aLine.LineDistance = sal_uInt16( bConvert ? convertTwipToMm100(pLine->GetDistance()): pLine->GetDistance() ); + aLine.LineStyle = sal_Int16(pLine->GetBorderLineStyle()); + aLine.LineWidth = sal_uInt32( bConvert ? convertTwipToMm100( pLine->GetWidth( ) ) : pLine->GetWidth( ) ); + } + else + { + aLine.Color = aLine.InnerLineWidth = aLine.OuterLineWidth = aLine.LineDistance = 0; + aLine.LineStyle = table::BorderLineStyle::NONE; // 0 is SOLID! + } + return aLine; +} + +bool SvxBoxItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + table::BorderLine2 aRetLine; + sal_Int16 nDist = 0; + bool bDistMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + // 4 Borders and 5 distances + uno::Sequence< uno::Any > aSeq{ + uno::Any(SvxBoxItem::SvxLineToLine(GetLeft(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetRight(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetBottom(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(GetTop(), bConvert)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetSmallestDistance()) : GetSmallestDistance())), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(mnTopDistance) : mnTopDistance)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(mnBottomDistance) : mnBottomDistance)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(mnLeftDistance) : mnLeftDistance)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(mnRightDistance) : mnRightDistance)) + }; + rVal <<= aSeq; + return true; + } + case MID_LEFT_BORDER: + case LEFT_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetLeft(), bConvert); + break; + case MID_RIGHT_BORDER: + case RIGHT_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetRight(), bConvert); + break; + case MID_BOTTOM_BORDER: + case BOTTOM_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetBottom(), bConvert); + break; + case MID_TOP_BORDER: + case TOP_BORDER: + aRetLine = SvxBoxItem::SvxLineToLine(GetTop(), bConvert); + break; + case BORDER_DISTANCE: + nDist = GetSmallestDistance(); + bDistMember = true; + break; + case TOP_BORDER_DISTANCE: + nDist = mnTopDistance; + bDistMember = true; + break; + case BOTTOM_BORDER_DISTANCE: + nDist = mnBottomDistance; + bDistMember = true; + break; + case LEFT_BORDER_DISTANCE: + nDist = mnLeftDistance; + bDistMember = true; + break; + case RIGHT_BORDER_DISTANCE: + nDist = mnRightDistance; + bDistMember = true; + break; + case MID_BORDER_BOTTOM_COLOR: + { + if (mpBottomBorderLine) + { + rVal <<= model::color::createXComplexColor(mpBottomBorderLine->getComplexColor()); + } + else if (maTempComplexColors[size_t(SvxBoxItemLine::BOTTOM)].getType() != model::ColorType::Unused) + { + rVal <<= model::color::createXComplexColor(maTempComplexColors[size_t(SvxBoxItemLine::BOTTOM)]); + } + return true; + } + case MID_BORDER_LEFT_COLOR: + { + if (mpLeftBorderLine) + { + rVal <<= model::color::createXComplexColor(mpLeftBorderLine->getComplexColor()); + } + else if (maTempComplexColors[size_t(SvxBoxItemLine::LEFT)].getType() != model::ColorType::Unused) + { + rVal <<= model::color::createXComplexColor(maTempComplexColors[size_t(SvxBoxItemLine::LEFT)]); + } + return true; + } + case MID_BORDER_RIGHT_COLOR: + { + if (mpRightBorderLine) + { + rVal <<= model::color::createXComplexColor(mpRightBorderLine->getComplexColor()); + } + else if (maTempComplexColors[size_t(SvxBoxItemLine::RIGHT)].getType() != model::ColorType::Unused) + { + rVal <<= model::color::createXComplexColor(maTempComplexColors[size_t(SvxBoxItemLine::RIGHT)]); + } + return true; + } + case MID_BORDER_TOP_COLOR: + { + if (mpTopBorderLine) + { + rVal <<= model::color::createXComplexColor(mpTopBorderLine->getComplexColor()); + } + else if (maTempComplexColors[size_t(SvxBoxItemLine::TOP)].getType() != model::ColorType::Unused) + { + rVal <<= model::color::createXComplexColor(maTempComplexColors[size_t(SvxBoxItemLine::TOP)]); + } + return true; + } + case LINE_STYLE: + case LINE_WIDTH: + // it doesn't make sense to return a value for these since it's + // probably ambiguous + return true; + } + + if( bDistMember ) + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(nDist) : nDist); + else + rVal <<= aRetLine; + + return true; +} + +namespace +{ + +bool +lcl_lineToSvxLine(const table::BorderLine& rLine, SvxBorderLine& rSvxLine, bool bConvert, bool bGuessWidth) +{ + rSvxLine.SetColor( Color(ColorTransparency, rLine.Color)); + if ( bGuessWidth ) + { + rSvxLine.GuessLinesWidths( rSvxLine.GetBorderLineStyle(), + bConvert ? o3tl::toTwips(rLine.OuterLineWidth, o3tl::Length::mm100) : rLine.OuterLineWidth, + bConvert ? o3tl::toTwips(rLine.InnerLineWidth, o3tl::Length::mm100) : rLine.InnerLineWidth, + bConvert ? o3tl::toTwips(rLine.LineDistance, o3tl::Length::mm100) : rLine.LineDistance ); + } + + bool bRet = !rSvxLine.isEmpty(); + return bRet; +} + +} + + +bool SvxBoxItem::LineToSvxLine(const css::table::BorderLine& rLine, SvxBorderLine& rSvxLine, bool bConvert) +{ + return lcl_lineToSvxLine(rLine, rSvxLine, bConvert, true); +} + +bool +SvxBoxItem::LineToSvxLine(const css::table::BorderLine2& rLine, SvxBorderLine& rSvxLine, bool bConvert) +{ + SvxBorderLineStyle const nStyle = + (rLine.LineStyle < 0 || BORDER_LINE_STYLE_MAX < rLine.LineStyle) + ? SvxBorderLineStyle::SOLID // default + : static_cast<SvxBorderLineStyle>(rLine.LineStyle); + + rSvxLine.SetBorderLineStyle( nStyle ); + + bool bGuessWidth = true; + if ( rLine.LineWidth ) + { + rSvxLine.SetWidth( bConvert? o3tl::toTwips(rLine.LineWidth, o3tl::Length::mm100) : rLine.LineWidth ); + // fdo#46112: double does not necessarily mean symmetric + // for backwards compatibility + bGuessWidth = (SvxBorderLineStyle::DOUBLE == nStyle || SvxBorderLineStyle::DOUBLE_THIN == nStyle) && + (rLine.InnerLineWidth > 0) && (rLine.OuterLineWidth > 0); + } + + return lcl_lineToSvxLine(rLine, rSvxLine, bConvert, bGuessWidth); +} + + +namespace +{ + +bool +lcl_extractBorderLine(const uno::Any& rAny, table::BorderLine2& rLine) +{ + if (rAny >>= rLine) + return true; + + table::BorderLine aBorderLine; + if (rAny >>= aBorderLine) + { + rLine.Color = aBorderLine.Color; + rLine.InnerLineWidth = aBorderLine.InnerLineWidth; + rLine.OuterLineWidth = aBorderLine.OuterLineWidth; + rLine.LineDistance = aBorderLine.LineDistance; + rLine.LineStyle = table::BorderLineStyle::SOLID; + return true; + } + + return false; +} + +template<typename Item, typename Line> +bool +lcl_setLine(const uno::Any& rAny, Item& rItem, Line nLine, const bool bConvert) +{ + bool bDone = false; + table::BorderLine2 aBorderLine; + if (lcl_extractBorderLine(rAny, aBorderLine)) + { + SvxBorderLine aLine; + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + rItem.SetLine( bSet ? &aLine : nullptr, nLine); + bDone = true; + } + return bDone; +} + +} + +bool SvxBoxItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + SvxBoxItemLine nLine = SvxBoxItemLine::TOP; + bool bDistMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + uno::Sequence< uno::Any > aSeq; + if (( rVal >>= aSeq ) && ( aSeq.getLength() == 9 )) + { + // 4 Borders and 5 distances + const SvxBoxItemLine aBorders[] = { SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::TOP }; + for (size_t n(0); n != std::size(aBorders); ++n) + { + if (!lcl_setLine(aSeq[n], *this, aBorders[n], bConvert)) + return false; + tryMigrateComplexColor(aBorders[n]); + } + + // WTH are the borders and the distances saved in different order? + SvxBoxItemLine const nLines[4] = { SvxBoxItemLine::TOP, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::LEFT, SvxBoxItemLine::RIGHT }; + for ( sal_Int32 n = 4; n < 9; n++ ) + { + sal_Int32 nDist = 0; + if ( aSeq[n] >>= nDist ) + { + if( bConvert ) + nDist = o3tl::toTwips(nDist, o3tl::Length::mm100); + if ( n == 4 ) + SetAllDistances(nDist); + else + SetDistance( nDist, nLines[n-5] ); + } + else + return false; + } + + return true; + } + else + return false; + } + case LEFT_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case LEFT_BORDER: + case MID_LEFT_BORDER: + nLine = SvxBoxItemLine::LEFT; + break; + case RIGHT_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case RIGHT_BORDER: + case MID_RIGHT_BORDER: + nLine = SvxBoxItemLine::RIGHT; + break; + case BOTTOM_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case BOTTOM_BORDER: + case MID_BOTTOM_BORDER: + nLine = SvxBoxItemLine::BOTTOM; + break; + case TOP_BORDER_DISTANCE: + bDistMember = true; + [[fallthrough]]; + case TOP_BORDER: + case MID_TOP_BORDER: + nLine = SvxBoxItemLine::TOP; + break; + case LINE_STYLE: + { + drawing::LineStyle eDrawingStyle; + rVal >>= eDrawingStyle; + SvxBorderLineStyle eBorderStyle = SvxBorderLineStyle::NONE; + switch ( eDrawingStyle ) + { + default: + case drawing::LineStyle_NONE: + break; + case drawing::LineStyle_SOLID: + eBorderStyle = SvxBorderLineStyle::SOLID; + break; + case drawing::LineStyle_DASH: + eBorderStyle = SvxBorderLineStyle::DASHED; + break; + } + + // Set the line style on all borders + for( SvxBoxItemLine n : o3tl::enumrange<SvxBoxItemLine>() ) + { + editeng::SvxBorderLine* pLine = const_cast< editeng::SvxBorderLine* >( GetLine( n ) ); + if( pLine ) + pLine->SetBorderLineStyle( eBorderStyle ); + } + return true; + } + break; + case LINE_WIDTH: + { + // Set the line width on all borders + tools::Long nWidth(0); + rVal >>= nWidth; + if( bConvert ) + nWidth = o3tl::toTwips(nWidth, o3tl::Length::mm100); + + // Set the line Width on all borders + for( SvxBoxItemLine n : o3tl::enumrange<SvxBoxItemLine>() ) + { + editeng::SvxBorderLine* pLine = const_cast< editeng::SvxBorderLine* >( GetLine( n ) ); + if( pLine ) + pLine->SetWidth( nWidth ); + } + } + return true; + case MID_BORDER_BOTTOM_COLOR: + { + if (mpBottomBorderLine) + return mpBottomBorderLine->setComplexColorFromAny(rVal); + else + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maTempComplexColors[size_t(SvxBoxItemLine::BOTTOM)] = model::color::getFromXComplexColor(xComplexColor); + } + return true; + } + case MID_BORDER_LEFT_COLOR: + { + if (mpLeftBorderLine) + return mpLeftBorderLine->setComplexColorFromAny(rVal); + else + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maTempComplexColors[size_t(SvxBoxItemLine::LEFT)] = model::color::getFromXComplexColor(xComplexColor); + } + return true; + } + case MID_BORDER_RIGHT_COLOR: + { + if (mpRightBorderLine) + return mpRightBorderLine->setComplexColorFromAny(rVal); + else + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maTempComplexColors[size_t(SvxBoxItemLine::RIGHT)] = model::color::getFromXComplexColor(xComplexColor); + } + return true; + } + case MID_BORDER_TOP_COLOR: + { + if (mpTopBorderLine) + return mpTopBorderLine->setComplexColorFromAny(rVal); + else + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maTempComplexColors[size_t(SvxBoxItemLine::TOP)] = model::color::getFromXComplexColor(xComplexColor); + } + return true; + } + } + + if( bDistMember || nMemberId == BORDER_DISTANCE ) + { + sal_Int32 nDist = 0; + if(!(rVal >>= nDist)) + return false; + + { + if( bConvert ) + nDist = o3tl::toTwips(nDist, o3tl::Length::mm100); + if( nMemberId == BORDER_DISTANCE ) + SetAllDistances(nDist); + else + SetDistance( nDist, nLine ); + } + } + else + { + SvxBorderLine aLine; + if( !rVal.hasValue() ) + return false; + + table::BorderLine2 aBorderLine; + if( lcl_extractBorderLine(rVal, aBorderLine) ) + { + // usual struct + } + else if (rVal.getValueTypeClass() == uno::TypeClass_SEQUENCE ) + { + // serialization for basic macro recording + uno::Reference < script::XTypeConverter > xConverter + ( script::Converter::create(::comphelper::getProcessComponentContext()) ); + uno::Sequence < uno::Any > aSeq; + uno::Any aNew; + try { aNew = xConverter->convertTo( rVal, cppu::UnoType<uno::Sequence < uno::Any >>::get() ); } + catch (const uno::Exception&) {} + + aNew >>= aSeq; + if (aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + sal_Int32 nVal = 0; + if ( aSeq[0] >>= nVal ) + aBorderLine.Color = nVal; + if ( aSeq[1] >>= nVal ) + aBorderLine.InnerLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[2] >>= nVal ) + aBorderLine.OuterLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[3] >>= nVal ) + aBorderLine.LineDistance = static_cast<sal_Int16>(nVal); + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + if (aSeq[4] >>= nVal) + { + aBorderLine.LineStyle = nVal; + } + if (aSeq.getLength() >= 6) + { + if (aSeq[5] >>= nVal) + { + aBorderLine.LineWidth = nVal; + } + } + } + } + else + return false; + } + else + return false; + + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + SetLine(bSet ? &aLine : nullptr, nLine); + tryMigrateComplexColor(nLine); + } + + return true; +} + +SvxBoxItem* SvxBoxItem::Clone( SfxItemPool* ) const +{ + return new SvxBoxItem( *this ); +} + +bool SvxBoxItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText.clear(); + + if (mpTopBorderLine) + { + rText = mpTopBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if ( !(mpTopBorderLine && mpBottomBorderLine && mpLeftBorderLine && mpRightBorderLine && + *mpTopBorderLine == *mpBottomBorderLine && + *mpTopBorderLine == *mpLeftBorderLine && + *mpTopBorderLine == *mpRightBorderLine)) + { + if (mpBottomBorderLine) + { + rText += mpBottomBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if (mpLeftBorderLine) + { + rText += mpLeftBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + if (mpRightBorderLine) + { + rText += mpRightBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl ) + cpDelimTmp; + } + } + rText += GetMetricText( static_cast<tools::Long>(mnTopDistance), eCoreUnit, ePresUnit, &rIntl ); + if (mnTopDistance != mnBottomDistance || + mnTopDistance != mnLeftDistance || + mnTopDistance != mnRightDistance) + { + rText += cpDelimTmp + + GetMetricText( tools::Long(mnBottomDistance), eCoreUnit, ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( tools::Long(mnLeftDistance), eCoreUnit, ePresUnit, &rIntl ) + + cpDelimTmp + + GetMetricText( tools::Long(mnRightDistance), eCoreUnit, ePresUnit, &rIntl ); + } + return true; + } + case SfxItemPresentation::Complete: + { + if (!(mpTopBorderLine || mpBottomBorderLine || mpLeftBorderLine || mpRightBorderLine)) + { + rText = EditResId(RID_SVXITEMS_BORDER_NONE) + cpDelimTmp; + } + else + { + rText = EditResId(RID_SVXITEMS_BORDER_COMPLETE); + if (mpTopBorderLine && mpBottomBorderLine && mpLeftBorderLine && mpRightBorderLine && + *mpTopBorderLine == *mpBottomBorderLine && + *mpTopBorderLine == *mpLeftBorderLine && + *mpTopBorderLine == *mpRightBorderLine) + { + rText += mpTopBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + cpDelimTmp; + } + else + { + if (mpTopBorderLine) + { + rText += EditResId(RID_SVXITEMS_BORDER_TOP) + + mpTopBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if (mpBottomBorderLine) + { + rText += EditResId(RID_SVXITEMS_BORDER_BOTTOM) + + mpBottomBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if (mpLeftBorderLine) + { + rText += EditResId(RID_SVXITEMS_BORDER_LEFT) + + mpLeftBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + if (mpRightBorderLine) + { + rText += EditResId(RID_SVXITEMS_BORDER_RIGHT) + + mpRightBorderLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, true ) + + cpDelimTmp; + } + } + } + + rText += EditResId(RID_SVXITEMS_BORDER_DISTANCE); + if (mnTopDistance == mnBottomDistance && + mnTopDistance == mnLeftDistance && + mnTopDistance == mnRightDistance) + { + rText += GetMetricText(tools::Long(mnTopDistance), eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + else + { + rText += EditResId(RID_SVXITEMS_BORDER_TOP) + + GetMetricText(tools::Long(mnTopDistance), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_BOTTOM) + + GetMetricText(tools::Long(mnBottomDistance), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_LEFT) + + GetMetricText(tools::Long(mnLeftDistance), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_BORDER_RIGHT) + + GetMetricText(tools::Long(mnRightDistance), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + } + return true; + } + default: ; // prevent warning + } + return false; +} + + +void SvxBoxItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if (mpTopBorderLine) + mpTopBorderLine->ScaleMetrics( nMult, nDiv ); + if (mpBottomBorderLine) + mpBottomBorderLine->ScaleMetrics( nMult, nDiv ); + if (mpLeftBorderLine) + mpLeftBorderLine->ScaleMetrics( nMult, nDiv ); + if (mpRightBorderLine) + mpRightBorderLine->ScaleMetrics( nMult, nDiv ); + + mnTopDistance = static_cast<sal_Int16>(BigInt::Scale(mnTopDistance, nMult, nDiv)); + mnBottomDistance = static_cast<sal_Int16>(BigInt::Scale(mnBottomDistance, nMult, nDiv)); + mnLeftDistance = static_cast<sal_Int16>(BigInt::Scale(mnLeftDistance, nMult, nDiv)); + mnRightDistance = static_cast<sal_Int16>(BigInt::Scale(mnRightDistance, nMult, nDiv)); +} + + +bool SvxBoxItem::HasMetrics() const +{ + return true; +} + + +const SvxBorderLine *SvxBoxItem::GetLine( SvxBoxItemLine nLine ) const +{ + const SvxBorderLine *pRet = nullptr; + + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pRet = mpTopBorderLine.get(); + break; + case SvxBoxItemLine::BOTTOM: + pRet = mpBottomBorderLine.get(); + break; + case SvxBoxItemLine::LEFT: + pRet = mpLeftBorderLine.get(); + break; + case SvxBoxItemLine::RIGHT: + pRet = mpRightBorderLine.get(); + break; + default: + OSL_FAIL( "wrong line" ); + break; + } + + return pRet; +} + + +void SvxBoxItem::SetLine( const SvxBorderLine* pNew, SvxBoxItemLine nLine ) +{ + std::unique_ptr<SvxBorderLine> pTmp( pNew ? new SvxBorderLine( *pNew ) : nullptr ); + + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + mpTopBorderLine = std::move(pTmp); + break; + case SvxBoxItemLine::BOTTOM: + mpBottomBorderLine = std::move(pTmp); + break; + case SvxBoxItemLine::LEFT: + mpLeftBorderLine = std::move(pTmp); + break; + case SvxBoxItemLine::RIGHT: + mpRightBorderLine = std::move(pTmp); + break; + default: + OSL_FAIL( "wrong line" ); + } +} + + +sal_uInt16 SvxBoxItem::GetSmallestDistance() const +{ + // The smallest distance that is not 0 will be returned. + sal_uInt16 nDist = mnTopDistance; + if (mnBottomDistance && (!nDist || mnBottomDistance < nDist)) + nDist = mnBottomDistance; + if (mnLeftDistance && (!nDist || mnLeftDistance < nDist)) + nDist = mnLeftDistance; + if (mnRightDistance && (!nDist || mnRightDistance < nDist)) + nDist = mnRightDistance; + + return nDist; +} + + +sal_Int16 SvxBoxItem::GetDistance( SvxBoxItemLine nLine, bool bAllowNegative ) const +{ + sal_Int16 nDist = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + nDist = mnTopDistance; + break; + case SvxBoxItemLine::BOTTOM: + nDist = mnBottomDistance; + break; + case SvxBoxItemLine::LEFT: + nDist = mnLeftDistance; + break; + case SvxBoxItemLine::RIGHT: + nDist = mnRightDistance; + break; + default: + OSL_FAIL( "wrong line" ); + } + + if (!bAllowNegative && nDist < 0) + { + nDist = 0; + } + return nDist; +} + + +void SvxBoxItem::SetDistance( sal_Int16 nNew, SvxBoxItemLine nLine ) +{ + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + mnTopDistance = nNew; + break; + case SvxBoxItemLine::BOTTOM: + mnBottomDistance = nNew; + break; + case SvxBoxItemLine::LEFT: + mnLeftDistance = nNew; + break; + case SvxBoxItemLine::RIGHT: + mnRightDistance = nNew; + break; + default: + OSL_FAIL( "wrong line" ); + } +} + +sal_uInt16 SvxBoxItem::CalcLineWidth( SvxBoxItemLine nLine ) const +{ + SvxBorderLine* pTmp = nullptr; + sal_uInt16 nWidth = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pTmp = mpTopBorderLine.get(); + break; + case SvxBoxItemLine::BOTTOM: + pTmp = mpBottomBorderLine.get(); + break; + case SvxBoxItemLine::LEFT: + pTmp = mpLeftBorderLine.get(); + break; + case SvxBoxItemLine::RIGHT: + pTmp = mpRightBorderLine.get(); + break; + default: + OSL_FAIL( "wrong line" ); + } + + if( pTmp ) + nWidth = pTmp->GetScaledWidth(); + + return nWidth; +} + +sal_Int16 SvxBoxItem::CalcLineSpace( SvxBoxItemLine nLine, bool bEvenIfNoLine, bool bAllowNegative ) const +{ + SvxBorderLine* pTmp = nullptr; + sal_Int16 nDist = 0; + switch ( nLine ) + { + case SvxBoxItemLine::TOP: + pTmp = mpTopBorderLine.get(); + nDist = mnTopDistance; + break; + case SvxBoxItemLine::BOTTOM: + pTmp = mpBottomBorderLine.get(); + nDist = mnBottomDistance; + break; + case SvxBoxItemLine::LEFT: + pTmp = mpLeftBorderLine.get(); + nDist = mnLeftDistance; + break; + case SvxBoxItemLine::RIGHT: + pTmp = mpRightBorderLine.get(); + nDist = mnRightDistance; + break; + default: + OSL_FAIL( "wrong line" ); + } + + if( pTmp ) + { + nDist = nDist + pTmp->GetScaledWidth(); + } + else if( !bEvenIfNoLine ) + nDist = 0; + + if (!bAllowNegative && nDist < 0) + { + nDist = 0; + } + + return nDist; +} + +void SvxBoxItem::tryMigrateComplexColor(SvxBoxItemLine eLine) +{ + if (!GetLine(eLine)) + return; + + auto nIndex = size_t(eLine); + + if (maTempComplexColors[nIndex].getType() == model::ColorType::Unused) + return; + + switch (eLine) + { + case SvxBoxItemLine::TOP: + mpTopBorderLine->setComplexColor(maTempComplexColors[nIndex]); + break; + case SvxBoxItemLine::BOTTOM: + mpBottomBorderLine->setComplexColor(maTempComplexColors[nIndex]); + break; + case SvxBoxItemLine::LEFT: + mpLeftBorderLine->setComplexColor(maTempComplexColors[nIndex]); + break; + case SvxBoxItemLine::RIGHT: + mpRightBorderLine->setComplexColor(maTempComplexColors[nIndex]); + break; + } + + maTempComplexColors[nIndex] = model::ComplexColor(); +} + +bool SvxBoxItem::HasBorder( bool bTreatPaddingAsBorder ) const +{ + return CalcLineSpace( SvxBoxItemLine::BOTTOM, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::RIGHT, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::TOP, bTreatPaddingAsBorder ) + || CalcLineSpace( SvxBoxItemLine::LEFT, bTreatPaddingAsBorder ); +} + +// class SvxBoxInfoItem -------------------------------------------------- + +SvxBoxInfoItem::SvxBoxInfoItem(const sal_uInt16 nId) + : SfxPoolItem(nId) + , mbDistance(false) + , mbMinimumDistance(false) +{ + ResetFlags(); +} + + +SvxBoxInfoItem::SvxBoxInfoItem( const SvxBoxInfoItem& rCopy ) + : SfxPoolItem(rCopy) + , mpHorizontalLine(rCopy.mpHorizontalLine ? new SvxBorderLine(*rCopy.mpHorizontalLine) : nullptr) + , mpVerticalLine(rCopy.mpVerticalLine ? new SvxBorderLine(*rCopy.mpVerticalLine) : nullptr) + , mbEnableHorizontalLine(rCopy.mbEnableHorizontalLine) + , mbEnableVerticalLine(rCopy.mbEnableVerticalLine) + , mbDistance(rCopy.mbDistance) + , mbMinimumDistance (rCopy.mbMinimumDistance) + , mnValidFlags(rCopy.mnValidFlags) + , mnDefaultMinimumDistance(rCopy.mnDefaultMinimumDistance) +{ +} + +SvxBoxInfoItem::~SvxBoxInfoItem() +{ +} + + +boost::property_tree::ptree SvxBoxInfoItem::dumpAsJSON() const +{ + boost::property_tree::ptree aTree; + + boost::property_tree::ptree aState; + aState.put("vertical", GetVert() && !GetVert()->isEmpty()); + aState.put("horizontal", GetHori() && !GetHori()->isEmpty()); + + aTree.push_back(std::make_pair("state", aState)); + aTree.put("commandName", ".uno:BorderInner"); + + return aTree; +} + + +bool SvxBoxInfoItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBoxInfoItem& rBoxInfo = static_cast<const SvxBoxInfoItem&>(rAttr); + + return (mbEnableHorizontalLine == rBoxInfo.mbEnableHorizontalLine + && mbEnableVerticalLine == rBoxInfo.mbEnableVerticalLine + && mbDistance == rBoxInfo.mbDistance + && mbMinimumDistance == rBoxInfo.mbMinimumDistance + && mnValidFlags == rBoxInfo.mnValidFlags + && mnDefaultMinimumDistance == rBoxInfo.mnDefaultMinimumDistance + && CompareBorderLine(mpHorizontalLine, rBoxInfo.GetHori()) + && CompareBorderLine(mpVerticalLine, rBoxInfo.GetVert())); +} + + +void SvxBoxInfoItem::SetLine( const SvxBorderLine* pNew, SvxBoxInfoItemLine nLine ) +{ + std::unique_ptr<SvxBorderLine> pCopy(pNew ? new SvxBorderLine(*pNew) : nullptr); + + if ( SvxBoxInfoItemLine::HORI == nLine ) + { + mpHorizontalLine = std::move(pCopy); + } + else if ( SvxBoxInfoItemLine::VERT == nLine ) + { + mpVerticalLine = std::move(pCopy); + } + else + { + OSL_FAIL( "wrong line" ); + } +} + +SvxBoxInfoItem* SvxBoxInfoItem::Clone( SfxItemPool* ) const +{ + return new SvxBoxInfoItem( *this ); +} + +bool SvxBoxInfoItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText.clear(); + return false; +} + + +void SvxBoxInfoItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if (mpHorizontalLine) + mpHorizontalLine->ScaleMetrics(nMult, nDiv); + if (mpVerticalLine) + mpVerticalLine->ScaleMetrics(nMult, nDiv); + mnDefaultMinimumDistance = sal_uInt16(BigInt::Scale(mnDefaultMinimumDistance, nMult, nDiv)); +} + + +bool SvxBoxInfoItem::HasMetrics() const +{ + return true; +} + + +void SvxBoxInfoItem::ResetFlags() +{ + mnValidFlags = static_cast<SvxBoxInfoItemValidFlags>(0x7F); // all valid except Disable +} + +bool SvxBoxInfoItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0 != (nMemberId & CONVERT_TWIPS); + table::BorderLine2 aRetLine; + sal_Int16 nVal=0; + bool bIntMember = false; + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + // 2 BorderLines, flags, valid flags and distance + if ( IsTable() ) + nVal |= 0x01; + if ( IsDist() ) + nVal |= 0x02; + if ( IsMinDist() ) + nVal |= 0x04; + css::uno::Sequence< css::uno::Any > aSeq{ + uno::Any(SvxBoxItem::SvxLineToLine(mpHorizontalLine.get(), bConvert)), + uno::Any(SvxBoxItem::SvxLineToLine(mpVerticalLine.get(), bConvert)), + uno::Any(nVal), + uno::Any(static_cast<sal_Int16>(mnValidFlags)), + uno::Any(static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetDefDist()) : GetDefDist())) + }; + rVal <<= aSeq; + return true; + } + + case MID_HORIZONTAL: + aRetLine = SvxBoxItem::SvxLineToLine(mpHorizontalLine.get(), bConvert); + break; + case MID_VERTICAL: + aRetLine = SvxBoxItem::SvxLineToLine(mpVerticalLine.get(), bConvert); + break; + case MID_FLAGS: + bIntMember = true; + if ( IsTable() ) + nVal |= 0x01; + if ( IsDist() ) + nVal |= 0x02; + if ( IsMinDist() ) + nVal |= 0x04; + rVal <<= nVal; + break; + case MID_VALIDFLAGS: + bIntMember = true; + rVal <<= static_cast<sal_Int16>(mnValidFlags); + break; + case MID_DISTANCE: + bIntMember = true; + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(GetDefDist()) : GetDefDist()); + break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + if( !bIntMember ) + rVal <<= aRetLine; + + return true; +} + + +bool SvxBoxInfoItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + bool bRet; + switch(nMemberId) + { + case 0: + { + css::uno::Sequence< css::uno::Any > aSeq; + if (( rVal >>= aSeq ) && ( aSeq.getLength() == 5 )) + { + // 2 BorderLines, flags, valid flags and distance + if (!lcl_setLine(aSeq[0], *this, SvxBoxInfoItemLine::HORI, bConvert)) + return false; + if (!lcl_setLine(aSeq[1], *this, SvxBoxInfoItemLine::VERT, bConvert)) + return false; + + sal_Int16 nFlags( 0 ); + sal_Int32 nVal( 0 ); + if ( aSeq[2] >>= nFlags ) + { + SetTable ( ( nFlags & 0x01 ) != 0 ); + SetDist ( ( nFlags & 0x02 ) != 0 ); + SetMinDist( ( nFlags & 0x04 ) != 0 ); + } + else + return false; + if ( aSeq[3] >>= nFlags ) + mnValidFlags = static_cast<SvxBoxInfoItemValidFlags>(nFlags); + else + return false; + if (( aSeq[4] >>= nVal ) && ( nVal >= 0 )) + { + if( bConvert ) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetDefDist( nVal ); + } + } + return true; + } + + case MID_HORIZONTAL: + case MID_VERTICAL: + { + if( !rVal.hasValue() ) + return false; + + table::BorderLine2 aBorderLine; + if( lcl_extractBorderLine(rVal, aBorderLine) ) + { + // usual struct + } + else if (rVal.getValueTypeClass() == uno::TypeClass_SEQUENCE ) + { + // serialization for basic macro recording + uno::Reference < script::XTypeConverter > xConverter( script::Converter::create(::comphelper::getProcessComponentContext()) ); + uno::Any aNew; + uno::Sequence < uno::Any > aSeq; + try { aNew = xConverter->convertTo( rVal, cppu::UnoType<uno::Sequence < uno::Any >>::get() ); } + catch (const uno::Exception&) {} + + if ((aNew >>= aSeq) && + aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + sal_Int32 nVal = 0; + if ( aSeq[0] >>= nVal ) + aBorderLine.Color = nVal; + if ( aSeq[1] >>= nVal ) + aBorderLine.InnerLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[2] >>= nVal ) + aBorderLine.OuterLineWidth = static_cast<sal_Int16>(nVal); + if ( aSeq[3] >>= nVal ) + aBorderLine.LineDistance = static_cast<sal_Int16>(nVal); + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + if (aSeq[4] >>= nVal) + { + aBorderLine.LineStyle = nVal; + } + if (aSeq.getLength() >= 6) + { + if (aSeq[5] >>= nVal) + { + aBorderLine.LineWidth = nVal; + } + } + } + } + else + return false; + } + else if (rVal.getValueType() == cppu::UnoType<css::uno::Sequence < sal_Int16 >>::get() ) + { + // serialization for basic macro recording + css::uno::Sequence < sal_Int16 > aSeq; + rVal >>= aSeq; + if (aSeq.getLength() >= 4 && aSeq.getLength() <= 6) + { + aBorderLine.Color = aSeq[0]; + aBorderLine.InnerLineWidth = aSeq[1]; + aBorderLine.OuterLineWidth = aSeq[2]; + aBorderLine.LineDistance = aSeq[3]; + if (aSeq.getLength() >= 5) // fdo#40874 added fields + { + aBorderLine.LineStyle = aSeq[4]; + if (aSeq.getLength() >= 6) + { + aBorderLine.LineWidth = aSeq[5]; + } + } + } + else + return false; + } + else + return false; + + SvxBorderLine aLine; + bool bSet = SvxBoxItem::LineToSvxLine(aBorderLine, aLine, bConvert); + if ( bSet ) + SetLine( &aLine, nMemberId == MID_HORIZONTAL ? SvxBoxInfoItemLine::HORI : SvxBoxInfoItemLine::VERT ); + break; + } + case MID_FLAGS: + { + sal_Int16 nFlags = sal_Int16(); + bRet = (rVal >>= nFlags); + if ( bRet ) + { + SetTable ( ( nFlags & 0x01 ) != 0 ); + SetDist ( ( nFlags & 0x02 ) != 0 ); + SetMinDist( ( nFlags & 0x04 ) != 0 ); + } + + break; + } + case MID_VALIDFLAGS: + { + sal_Int16 nFlags = sal_Int16(); + bRet = (rVal >>= nFlags); + if ( bRet ) + mnValidFlags = static_cast<SvxBoxInfoItemValidFlags>(nFlags); + break; + } + case MID_DISTANCE: + { + sal_Int32 nVal = 0; + bRet = (rVal >>= nVal); + if ( bRet && nVal>=0 ) + { + if( bConvert ) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetDefDist( static_cast<sal_uInt16>(nVal) ); + } + break; + } + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + + +namespace editeng +{ + +void BorderDistanceFromWord(bool bFromEdge, sal_Int32& nMargin, sal_Int32& nBorderDistance, + sal_Int32 nBorderWidth) +{ + // See https://wiki.openoffice.org/wiki/Writer/MSInteroperability/PageBorder + + sal_Int32 nNewMargin = nMargin; + sal_Int32 nNewBorderDistance = nBorderDistance; + + if (bFromEdge) + { + nNewMargin = nBorderDistance; + nNewBorderDistance = nMargin - nBorderDistance - nBorderWidth; + } + else + { + nNewMargin -= nBorderDistance + nBorderWidth; + } + + // Ensure correct distance from page edge to text in cases not supported by us: + // when border is outside entire page area (!bFromEdge && BorderDistance > Margin), + // and when border is inside page body area (bFromEdge && BorderDistance > Margin) + if (nNewMargin < 0) + { + nNewMargin = 0; + nNewBorderDistance = std::max<sal_Int32>(nMargin - nBorderWidth, 0); + } + else if (nNewBorderDistance < 0) + { + nNewMargin = nMargin; + } + + nMargin = nNewMargin; + nBorderDistance = nNewBorderDistance; +} + +// Heuristics to decide if we need to use "from edge" offset of borders +// +// There are two cases when we can safely use "from text" or "from edge" offset without distorting +// border position (modulo rounding errors): +// 1. When distance of all borders from text is no greater than 31 pt, we use "from text" +// 2. Otherwise, if distance of all borders from edge is no greater than 31 pt, we use "from edge" +// In all other cases, the position of borders would be distorted on export, because Word doesn't +// support the offset of >31 pts (https://msdn.microsoft.com/en-us/library/ff533820), and we need +// to decide which type of offset would provide less wrong result (i.e., the result would look +// closer to original). Here, we just check sum of distances from text to borders, and if it is +// less than sum of distances from borders to edges. The alternative would be to compare total areas +// between text-and-borders and between borders-and-edges (taking into account different lengths of +// borders, and visual impact of that). +void BorderDistancesToWord(const SvxBoxItem& rBox, const WordPageMargins& rMargins, + WordBorderDistances& rDistances) +{ + // Use signed sal_Int32 that can hold sal_uInt16, to prevent overflow at subtraction below + const sal_Int32 nT = rBox.GetDistance(SvxBoxItemLine::TOP, /*bAllowNegative=*/true); + const sal_Int32 nL = rBox.GetDistance(SvxBoxItemLine::LEFT, /*bAllowNegative=*/true); + const sal_Int32 nB = rBox.GetDistance(SvxBoxItemLine::BOTTOM, /*bAllowNegative=*/true); + const sal_Int32 nR = rBox.GetDistance(SvxBoxItemLine::RIGHT, /*bAllowNegative=*/true); + + // Only take into account existing borders + const SvxBorderLine* pLnT = rBox.GetLine(SvxBoxItemLine::TOP); + const SvxBorderLine* pLnL = rBox.GetLine(SvxBoxItemLine::LEFT); + const SvxBorderLine* pLnB = rBox.GetLine(SvxBoxItemLine::BOTTOM); + const SvxBorderLine* pLnR = rBox.GetLine(SvxBoxItemLine::RIGHT); + + // We need to take border widths into account + const tools::Long nWidthT = pLnT ? pLnT->GetScaledWidth() : 0; + const tools::Long nWidthL = pLnL ? pLnL->GetScaledWidth() : 0; + const tools::Long nWidthB = pLnB ? pLnB->GetScaledWidth() : 0; + const tools::Long nWidthR = pLnR ? pLnR->GetScaledWidth() : 0; + + // Resulting distances from text to borders + const sal_Int32 nT2BT = pLnT ? nT : 0; + const sal_Int32 nT2BL = pLnL ? nL : 0; + const sal_Int32 nT2BB = pLnB ? nB : 0; + const sal_Int32 nT2BR = pLnR ? nR : 0; + + // Resulting distances from edge to borders + const sal_Int32 nE2BT = pLnT ? std::max<sal_Int32>(rMargins.nTop - nT - nWidthT, 0) : 0; + const sal_Int32 nE2BL = pLnL ? std::max<sal_Int32>(rMargins.nLeft - nL - nWidthL, 0) : 0; + const sal_Int32 nE2BB = pLnB ? std::max<sal_Int32>(rMargins.nBottom - nB - nWidthB, 0) : 0; + const sal_Int32 nE2BR = pLnR ? std::max<sal_Int32>(rMargins.nRight - nR - nWidthR, 0) : 0; + + const sal_Int32 n32pt = 32 * 20; + // 1. If all borders are in range of 31 pts from text + if (nT2BT >= 0 && nT2BT < n32pt && nT2BL >= 0 && nT2BL < n32pt && nT2BB >= 0 && nT2BB < n32pt && nT2BR >= 0 && nT2BR < n32pt) + { + rDistances.bFromEdge = false; + } + else + { + // 2. If all borders are in range of 31 pts from edge + if (nE2BT < n32pt && nE2BL < n32pt && nE2BB < n32pt && nE2BR < n32pt) + { + rDistances.bFromEdge = true; + } + else + { + // Let's try to guess which would be the best approximation + rDistances.bFromEdge = + (nT2BT + nT2BL + nT2BB + nT2BR) > (nE2BT + nE2BL + nE2BB + nE2BR); + } + } + + if (rDistances.bFromEdge) + { + rDistances.nTop = sal::static_int_cast<sal_uInt16>(nE2BT); + rDistances.nLeft = sal::static_int_cast<sal_uInt16>(nE2BL); + rDistances.nBottom = sal::static_int_cast<sal_uInt16>(nE2BB); + rDistances.nRight = sal::static_int_cast<sal_uInt16>(nE2BR); + } + else + { + rDistances.nTop = sal::static_int_cast<sal_uInt16>(nT2BT); + rDistances.nLeft = sal::static_int_cast<sal_uInt16>(nT2BL); + rDistances.nBottom = sal::static_int_cast<sal_uInt16>(nT2BB); + rDistances.nRight = sal::static_int_cast<sal_uInt16>(nT2BR); + } +} + +} + +// class SvxFormatBreakItem ------------------------------------------------- + +bool SvxFormatBreakItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return GetValue() == static_cast<const SvxFormatBreakItem&>( rAttr ).GetValue(); +} + + +bool SvxFormatBreakItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText = GetValueTextByPos( GetEnumValue() ); + return true; +} + +OUString SvxFormatBreakItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_BREAK[] = + { + RID_SVXITEMS_BREAK_NONE, + RID_SVXITEMS_BREAK_COLUMN_BEFORE, + RID_SVXITEMS_BREAK_COLUMN_AFTER, + RID_SVXITEMS_BREAK_COLUMN_BOTH, + RID_SVXITEMS_BREAK_PAGE_BEFORE, + RID_SVXITEMS_BREAK_PAGE_AFTER, + RID_SVXITEMS_BREAK_PAGE_BOTH + }; + static_assert(std::size(RID_SVXITEMS_BREAK) == size_t(SvxBreak::End), "unexpected size"); + assert(nPos < sal_uInt16(SvxBreak::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_BREAK[nPos]); +} + +bool SvxFormatBreakItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + style::BreakType eBreak = style::BreakType_NONE; + switch ( GetBreak() ) + { + case SvxBreak::ColumnBefore: eBreak = style::BreakType_COLUMN_BEFORE; break; + case SvxBreak::ColumnAfter: eBreak = style::BreakType_COLUMN_AFTER ; break; + case SvxBreak::ColumnBoth: eBreak = style::BreakType_COLUMN_BOTH ; break; + case SvxBreak::PageBefore: eBreak = style::BreakType_PAGE_BEFORE ; break; + case SvxBreak::PageAfter: eBreak = style::BreakType_PAGE_AFTER ; break; + case SvxBreak::PageBoth: eBreak = style::BreakType_PAGE_BOTH ; break; + default: ; // prevent warning + } + rVal <<= eBreak; + return true; +} + +bool SvxFormatBreakItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + style::BreakType nBreak; + + if(!(rVal >>= nBreak)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + nBreak = static_cast<style::BreakType>(nValue); + } + + SvxBreak eBreak = SvxBreak::NONE; + switch( nBreak ) + { + case style::BreakType_COLUMN_BEFORE: eBreak = SvxBreak::ColumnBefore; break; + case style::BreakType_COLUMN_AFTER: eBreak = SvxBreak::ColumnAfter; break; + case style::BreakType_COLUMN_BOTH: eBreak = SvxBreak::ColumnBoth; break; + case style::BreakType_PAGE_BEFORE: eBreak = SvxBreak::PageBefore; break; + case style::BreakType_PAGE_AFTER: eBreak = SvxBreak::PageAfter; break; + case style::BreakType_PAGE_BOTH: eBreak = SvxBreak::PageBoth; break; + default: ; // prevent warning + } + SetValue(eBreak); + + return true; +} + +SvxFormatBreakItem* SvxFormatBreakItem::Clone( SfxItemPool* ) const +{ + return new SvxFormatBreakItem( *this ); +} + +sal_uInt16 SvxFormatBreakItem::GetValueCount() const +{ + return sal_uInt16(SvxBreak::End); // SvxBreak::PageBoth + 1 +} + +SvxFormatKeepItem* SvxFormatKeepItem::Clone( SfxItemPool* ) const +{ + return new SvxFormatKeepItem( *this ); +} + +bool SvxFormatKeepItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& + ) const +{ + TranslateId pId = RID_SVXITEMS_FMTKEEP_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_FMTKEEP_TRUE; + rText = EditResId(pId); + return true; +} + +void SvxFormatKeepItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFormatKeepItem")); + + SfxBoolItem::dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SvxLineItem::SvxLineItem( const sal_uInt16 nId ) : + SfxPoolItem ( nId ) +{ +} + + +SvxLineItem::SvxLineItem( const SvxLineItem& rCpy ) : + SfxPoolItem ( rCpy ), + pLine(rCpy.pLine ? new SvxBorderLine( *rCpy.pLine ) : nullptr) +{ +} + + +SvxLineItem::~SvxLineItem() +{ +} + + +bool SvxLineItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return CompareBorderLine(pLine, static_cast<const SvxLineItem&>(rAttr).GetLine()); +} + +SvxLineItem* SvxLineItem::Clone( SfxItemPool* ) const +{ + return new SvxLineItem( *this ); +} + +bool SvxLineItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemId ) const +{ + bool bConvert = 0!=(nMemId&CONVERT_TWIPS); + nMemId &= ~CONVERT_TWIPS; + if ( nMemId == 0 ) + { + rVal <<= SvxBoxItem::SvxLineToLine(pLine.get(), bConvert); + return true; + } + else if ( pLine ) + { + switch ( nMemId ) + { + case MID_FG_COLOR: rVal <<= pLine->GetColor(); break; + case MID_OUTER_WIDTH: rVal <<= sal_Int32(pLine->GetOutWidth()); break; + case MID_INNER_WIDTH: rVal <<= sal_Int32(pLine->GetInWidth( )); break; + case MID_DISTANCE: rVal <<= sal_Int32(pLine->GetDistance()); break; + default: + OSL_FAIL( "Wrong MemberId" ); + return false; + } + } + + return true; +} + + +bool SvxLineItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemId ) +{ + bool bConvert = 0!=(nMemId&CONVERT_TWIPS); + nMemId &= ~CONVERT_TWIPS; + sal_Int32 nVal = 0; + if ( nMemId == 0 ) + { + table::BorderLine2 aLine; + if ( lcl_extractBorderLine(rVal, aLine) ) + { + if ( !pLine ) + pLine.reset( new SvxBorderLine ); + if( !SvxBoxItem::LineToSvxLine(aLine, *pLine, bConvert) ) + pLine.reset(); + return true; + } + return false; + } + else if ( rVal >>= nVal ) + { + if ( !pLine ) + pLine.reset( new SvxBorderLine ); + + switch ( nMemId ) + { + case MID_FG_COLOR: pLine->SetColor( Color(ColorTransparency, nVal) ); break; + case MID_LINE_STYLE: + pLine->SetBorderLineStyle(static_cast<SvxBorderLineStyle>(nVal)); + break; + default: + OSL_FAIL( "Wrong MemberId" ); + return false; + } + + return true; + } + + return false; +} + + +bool SvxLineItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + rText.clear(); + + if ( pLine ) + rText = pLine->GetValueString( eCoreUnit, ePresUnit, &rIntl, + (SfxItemPresentation::Complete == ePres) ); + return true; +} + + +void SvxLineItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + if ( pLine ) pLine->ScaleMetrics( nMult, nDiv ); +} + + +bool SvxLineItem::HasMetrics() const +{ + return true; +} + + +void SvxLineItem::SetLine( const SvxBorderLine* pNew ) +{ + pLine.reset( pNew ? new SvxBorderLine( *pNew ) : nullptr ); +} + +SvxBrushItem::SvxBrushItem(sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , eGraphicPos(GPOS_NONE) + , bLoadAgain(true) +{ +} + +SvxBrushItem::SvxBrushItem(const Color& rColor, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(rColor) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , eGraphicPos(GPOS_NONE) + , bLoadAgain(true) +{ +} + +SvxBrushItem::SvxBrushItem(Color const& rColor, model::ComplexColor const& rComplexColor, sal_uInt16 nWhich) + : SfxPoolItem(nWhich) + , aColor(rColor) + , maComplexColor(rComplexColor) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , eGraphicPos(GPOS_NONE) + , bLoadAgain(true) +{ +} + +SvxBrushItem::SvxBrushItem(const Graphic& rGraphic, SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , xGraphicObject(new GraphicObject(rGraphic)) + , nGraphicTransparency(0) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(const GraphicObject& rGraphicObj, SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , xGraphicObject(new GraphicObject(rGraphicObj)) + , nGraphicTransparency(0) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(OUString aLink, OUString aFilter, + SvxGraphicPosition ePos, sal_uInt16 _nWhich) + : SfxPoolItem(_nWhich) + , aColor(COL_TRANSPARENT) + , aFilterColor(COL_TRANSPARENT) + , nShadingValue(ShadingPattern::CLEAR) + , nGraphicTransparency(0) + , maStrLink(std::move(aLink)) + , maStrFilter(std::move(aFilter)) + , eGraphicPos((GPOS_NONE != ePos) ? ePos : GPOS_MM) + , bLoadAgain(true) +{ + DBG_ASSERT( GPOS_NONE != ePos, "SvxBrushItem-Ctor with GPOS_NONE == ePos" ); +} + +SvxBrushItem::SvxBrushItem(const SvxBrushItem& rItem) + : SfxPoolItem(rItem) + , aColor(rItem.aColor) + , maComplexColor(rItem.maComplexColor) + , aFilterColor(rItem.aFilterColor) + , nShadingValue(rItem.nShadingValue) + , xGraphicObject(rItem.xGraphicObject ? new GraphicObject(*rItem.xGraphicObject) : nullptr) + , nGraphicTransparency(rItem.nGraphicTransparency) + , maStrLink(rItem.maStrLink) + , maStrFilter(rItem.maStrFilter) + , eGraphicPos(rItem.eGraphicPos) + , bLoadAgain(rItem.bLoadAgain) +{ +} + +SvxBrushItem::SvxBrushItem(SvxBrushItem&& rItem) + : SfxPoolItem(std::move(rItem)) + , aColor(std::move(rItem.aColor)) + , maComplexColor(std::move(rItem.maComplexColor)) + , aFilterColor(std::move(rItem.aFilterColor)) + , nShadingValue(std::move(rItem.nShadingValue)) + , xGraphicObject(std::move(rItem.xGraphicObject)) + , nGraphicTransparency(std::move(rItem.nGraphicTransparency)) + , maStrLink(std::move(rItem.maStrLink)) + , maStrFilter(std::move(rItem.maStrFilter)) + , eGraphicPos(std::move(rItem.eGraphicPos)) + , bLoadAgain(std::move(rItem.bLoadAgain)) +{ +} + +SvxBrushItem::~SvxBrushItem() +{ +} + +bool SvxBrushItem::isUsed() const +{ + if (GPOS_NONE != GetGraphicPos()) + { + // graphic used + return true; + } + else if (!GetColor().IsFullyTransparent()) + { + // color used + return true; + } + + return false; +} + + +static sal_Int8 lcl_PercentToTransparency(tools::Long nPercent) +{ + // 0xff must not be returned! + return sal_Int8(nPercent ? (50 + 0xfe * nPercent) / 100 : 0); +} + + +sal_Int8 SvxBrushItem::TransparencyToPercent(sal_Int32 nTrans) +{ + return static_cast<sal_Int8>((nTrans * 100 + 127) / 254); +} + + +bool SvxBrushItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId) + { + case MID_BACK_COLOR: + rVal <<= aColor; + break; + case MID_BACK_COLOR_R_G_B: + rVal <<= aColor.GetRGBColor(); + break; + case MID_BACK_COLOR_TRANSPARENCY: + rVal <<= SvxBrushItem::TransparencyToPercent(255 - aColor.GetAlpha()); + break; + + case MID_BACKGROUND_COMPLEX_COLOR: + { + auto xComplexColor = model::color::createXComplexColor(maComplexColor); + rVal <<= xComplexColor; + break; + } + break; + + case MID_GRAPHIC_POSITION: + rVal <<= static_cast<style::GraphicLocation>(static_cast<sal_Int16>(eGraphicPos)); + break; + + case MID_GRAPHIC_TRANSPARENT: + rVal <<= ( aColor.GetAlpha() == 0 ); + break; + + case MID_GRAPHIC_URL: + case MID_GRAPHIC: + { + uno::Reference<graphic::XGraphic> xGraphic; + if (!maStrLink.isEmpty()) + { + Graphic aGraphic(vcl::graphic::loadFromURL(maStrLink)); + xGraphic = aGraphic.GetXGraphic(); + } + else if (xGraphicObject) + { + xGraphic = xGraphicObject->GetGraphic().GetXGraphic(); + } + rVal <<= xGraphic; + } + break; + + case MID_GRAPHIC_FILTER: + { + rVal <<= maStrFilter; + } + break; + + case MID_GRAPHIC_TRANSPARENCY: + rVal <<= nGraphicTransparency; + break; + + case MID_SHADING_VALUE: + { + rVal <<= nShadingValue; + } + break; + } + + return true; +} + + +bool SvxBrushItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId) + { + case MID_BACK_COLOR: + case MID_BACK_COLOR_R_G_B: + { + Color aNewCol; + if ( !( rVal >>= aNewCol ) ) + return false; + if(MID_BACK_COLOR_R_G_B == nMemberId) + { + aNewCol.SetAlpha(aColor.GetAlpha()); + } + aColor = aNewCol; + } + break; + case MID_BACK_COLOR_TRANSPARENCY: + { + sal_Int32 nTrans = 0; + if ( !( rVal >>= nTrans ) || nTrans < 0 || nTrans > 100 ) + return false; + aColor.SetAlpha(255 - lcl_PercentToTransparency(nTrans)); + } + break; + + case MID_BACKGROUND_COMPLEX_COLOR: + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maComplexColor = model::color::getFromXComplexColor(xComplexColor); + } + break; + + case MID_GRAPHIC_POSITION: + { + style::GraphicLocation eLocation; + if ( !( rVal>>=eLocation ) ) + { + sal_Int32 nValue = 0; + if ( !( rVal >>= nValue ) ) + return false; + eLocation = static_cast<style::GraphicLocation>(nValue); + } + SetGraphicPos( static_cast<SvxGraphicPosition>(static_cast<sal_uInt16>(eLocation)) ); + } + break; + + case MID_GRAPHIC_TRANSPARENT: + aColor.SetAlpha( Any2Bool( rVal ) ? 0 : 255 ); + break; + + case MID_GRAPHIC_URL: + case MID_GRAPHIC: + { + Graphic aGraphic; + + if (rVal.getValueType() == ::cppu::UnoType<OUString>::get()) + { + OUString aURL = rVal.get<OUString>(); + aGraphic = vcl::graphic::loadFromURL(aURL); + } + else if (rVal.getValueType() == cppu::UnoType<graphic::XGraphic>::get()) + { + auto xGraphic = rVal.get<uno::Reference<graphic::XGraphic>>(); + aGraphic = Graphic(xGraphic); + } + + if (!aGraphic.IsNone()) + { + maStrLink.clear(); + + std::unique_ptr<GraphicObject> xOldGrfObj(std::move(xGraphicObject)); + xGraphicObject.reset(new GraphicObject(aGraphic)); + ApplyGraphicTransparency_Impl(); + xOldGrfObj.reset(); + + if (!aGraphic.IsNone() && eGraphicPos == GPOS_NONE) + { + eGraphicPos = GPOS_MM; + } + else if (aGraphic.IsNone()) + { + eGraphicPos = GPOS_NONE; + } + } + } + break; + + case MID_GRAPHIC_FILTER: + { + if( rVal.getValueType() == ::cppu::UnoType<OUString>::get() ) + { + OUString sLink; + rVal >>= sLink; + SetGraphicFilter( sLink ); + } + } + break; + case MID_GRAPHIC_TRANSPARENCY : + { + sal_Int32 nTmp = 0; + rVal >>= nTmp; + if(nTmp >= 0 && nTmp <= 100) + { + nGraphicTransparency = sal_Int8(nTmp); + if (xGraphicObject) + ApplyGraphicTransparency_Impl(); + } + } + break; + + case MID_SHADING_VALUE: + { + sal_Int32 nVal = 0; + if (!(rVal >>= nVal)) + return false; + + nShadingValue = nVal; + } + break; + } + + return true; +} + + +bool SvxBrushItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& + ) const +{ + if ( GPOS_NONE == eGraphicPos ) + { + rText = ::GetColorString( aColor ) + cpDelim; + TranslateId pId = RID_SVXITEMS_TRANSPARENT_FALSE; + + if ( aColor.IsTransparent() ) + pId = RID_SVXITEMS_TRANSPARENT_TRUE; + rText += EditResId(pId); + } + else + { + rText = EditResId(RID_SVXITEMS_GRAPHIC); + } + + return true; +} + +bool SvxBrushItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxBrushItem& rCmp = static_cast<const SvxBrushItem&>(rAttr); + bool bEqual = + aColor == rCmp.aColor && + maComplexColor == rCmp.maComplexColor && + aFilterColor == rCmp.aFilterColor && + eGraphicPos == rCmp.eGraphicPos && + nGraphicTransparency == rCmp.nGraphicTransparency; + + if ( bEqual ) + { + if ( GPOS_NONE != eGraphicPos ) + { + bEqual = maStrLink == rCmp.maStrLink; + + if ( bEqual ) + { + bEqual = maStrFilter == rCmp.maStrFilter; + } + + if ( bEqual ) + { + if (!rCmp.xGraphicObject) + bEqual = !xGraphicObject; + else + bEqual = xGraphicObject && + (*xGraphicObject == *rCmp.xGraphicObject); + } + } + + if (bEqual) + { + bEqual = nShadingValue == rCmp.nShadingValue; + } + } + + return bEqual; +} + +SvxBrushItem* SvxBrushItem::Clone( SfxItemPool* ) const +{ + return new SvxBrushItem( *this ); +} + +const GraphicObject* SvxBrushItem::GetGraphicObject(OUString const & referer) const +{ + if (bLoadAgain && !maStrLink.isEmpty() && !xGraphicObject) + // when graphics already loaded, use as a cache + { + if (SvtSecurityOptions::isUntrustedReferer(referer)) { + return nullptr; + } + + // tdf#94088 prepare graphic and state + Graphic aGraphic; + bool bGraphicLoaded = false; + + // try to create stream directly from given URL + std::unique_ptr<SvStream> xStream(utl::UcbStreamHelper::CreateStream(maStrLink, StreamMode::STD_READ)); + // tdf#94088 if we have a stream, try to load it directly as graphic + if (xStream && !xStream->GetError()) + { + if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, maStrLink, *xStream, + GRFILTER_FORMAT_DONTKNOW, nullptr, GraphicFilterImportFlags::DontSetLogsizeForJpeg)) + { + bGraphicLoaded = true; + } + } + + // tdf#94088 if no succeeded, try if the string (which is not empty) contains + // a 'data:' scheme url and try to load that (embedded graphics) + if(!bGraphicLoaded) + { + INetURLObject aGraphicURL( maStrLink ); + + if( INetProtocol::Data == aGraphicURL.GetProtocol() ) + { + std::unique_ptr<SvMemoryStream> const xMemStream(aGraphicURL.getData()); + if (xMemStream) + { + if (ERRCODE_NONE == GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, u"", *xMemStream)) + { + bGraphicLoaded = true; + + // tdf#94088 delete the no longer needed data scheme URL which + // is potentially pretty // large, containing a base64 encoded copy of the graphic + const_cast< SvxBrushItem* >(this)->maStrLink.clear(); + } + } + } + } + + // tdf#94088 when we got a graphic, set it + if(bGraphicLoaded && GraphicType::NONE != aGraphic.GetType()) + { + xGraphicObject.reset(new GraphicObject); + xGraphicObject->SetGraphic(aGraphic); + const_cast < SvxBrushItem*> (this)->ApplyGraphicTransparency_Impl(); + } + else + { + bLoadAgain = false; + } + } + + return xGraphicObject.get(); +} + +void SvxBrushItem::setGraphicTransparency(sal_Int8 nNew) +{ + if (nNew != nGraphicTransparency) + { + nGraphicTransparency = nNew; + ApplyGraphicTransparency_Impl(); + } +} + +const Graphic* SvxBrushItem::GetGraphic(OUString const & referer) const +{ + const GraphicObject* pGrafObj = GetGraphicObject(referer); + return( pGrafObj ? &( pGrafObj->GetGraphic() ) : nullptr ); +} + +void SvxBrushItem::SetGraphicPos( SvxGraphicPosition eNew ) +{ + eGraphicPos = eNew; + + if ( GPOS_NONE == eGraphicPos ) + { + xGraphicObject.reset(); + maStrLink.clear(); + maStrFilter.clear(); + } + else + { + if (!xGraphicObject && maStrLink.isEmpty()) + { + xGraphicObject.reset(new GraphicObject); // Creating a dummy + } + } +} + +void SvxBrushItem::SetGraphic( const Graphic& rNew ) +{ + if ( maStrLink.isEmpty() ) + { + if (xGraphicObject) + xGraphicObject->SetGraphic(rNew); + else + xGraphicObject.reset(new GraphicObject(rNew)); + + ApplyGraphicTransparency_Impl(); + + if ( GPOS_NONE == eGraphicPos ) + eGraphicPos = GPOS_MM; // None would be brush, then Default: middle + } + else + { + OSL_FAIL( "SetGraphic() on linked graphic! :-/" ); + } +} + +void SvxBrushItem::SetGraphicObject( const GraphicObject& rNewObj ) +{ + if ( maStrLink.isEmpty() ) + { + if (xGraphicObject) + *xGraphicObject = rNewObj; + else + xGraphicObject.reset(new GraphicObject(rNewObj)); + + ApplyGraphicTransparency_Impl(); + + if ( GPOS_NONE == eGraphicPos ) + eGraphicPos = GPOS_MM; // None would be brush, then Default: middle + } + else + { + OSL_FAIL( "SetGraphic() on linked graphic! :-/" ); + } +} + +void SvxBrushItem::SetGraphicLink( const OUString& rNew ) +{ + if ( rNew.isEmpty() ) + maStrLink.clear(); + else + { + maStrLink = rNew; + xGraphicObject.reset(); + } +} + +void SvxBrushItem::SetGraphicFilter( const OUString& rNew ) +{ + maStrFilter = rNew; +} + +void SvxBrushItem::ApplyGraphicTransparency_Impl() +{ + DBG_ASSERT(xGraphicObject, "no GraphicObject available" ); + if (xGraphicObject) + { + GraphicAttr aAttr(xGraphicObject->GetAttr()); + aAttr.SetAlpha(255 - lcl_PercentToTransparency( + nGraphicTransparency)); + xGraphicObject->SetAttr(aAttr); + } +} + +void SvxBrushItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxBrushItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("color"), BAD_CAST(aColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("filtercolor"), BAD_CAST(aFilterColor.AsRGBHexString().toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("shadingValue"), BAD_CAST(OString::number(nShadingValue).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("link"), BAD_CAST(maStrLink.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("filter"), BAD_CAST(maStrFilter.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("graphicPos"), BAD_CAST(OString::number(eGraphicPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("loadAgain"), BAD_CAST(OString::boolean(bLoadAgain).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxFrameDirectionItem::SvxFrameDirectionItem( SvxFrameDirection nValue , + sal_uInt16 _nWhich ) + : SfxEnumItem<SvxFrameDirection>( _nWhich, nValue ) +{ +} + + +SvxFrameDirectionItem::~SvxFrameDirectionItem() +{ +} + +SvxFrameDirectionItem* SvxFrameDirectionItem::Clone( SfxItemPool * ) const +{ + return new SvxFrameDirectionItem( *this ); +} + +TranslateId getFrmDirResId(size_t nIndex) +{ + TranslateId const RID_SVXITEMS_FRMDIR[] = + { + RID_SVXITEMS_FRMDIR_HORI_LEFT_TOP, + RID_SVXITEMS_FRMDIR_HORI_RIGHT_TOP, + RID_SVXITEMS_FRMDIR_VERT_TOP_RIGHT, + RID_SVXITEMS_FRMDIR_VERT_TOP_LEFT, + RID_SVXITEMS_FRMDIR_ENVIRONMENT, + RID_SVXITEMS_FRMDIR_VERT_BOT_LEFT, + RID_SVXITEMS_FRMDIR_VERT_TOP_RIGHT90 + }; + return RID_SVXITEMS_FRMDIR[nIndex]; +} + +bool SvxFrameDirectionItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper&) const +{ + rText = EditResId(getFrmDirResId(GetEnumValue())); + return true; +} + +bool SvxFrameDirectionItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 ) +{ + sal_Int16 nVal = sal_Int16(); + bool bRet = ( rVal >>= nVal ); + if( bRet ) + { + // translate WritingDirection2 constants into SvxFrameDirection + switch( nVal ) + { + case text::WritingMode2::LR_TB: + SetValue( SvxFrameDirection::Horizontal_LR_TB ); + break; + case text::WritingMode2::RL_TB: + SetValue( SvxFrameDirection::Horizontal_RL_TB ); + break; + case text::WritingMode2::TB_RL: + SetValue( SvxFrameDirection::Vertical_RL_TB ); + break; + case text::WritingMode2::TB_LR: + SetValue( SvxFrameDirection::Vertical_LR_TB ); + break; + case text::WritingMode2::BT_LR: + SetValue( SvxFrameDirection::Vertical_LR_BT ); + break; + case text::WritingMode2::TB_RL90: + SetValue(SvxFrameDirection::Vertical_RL_TB90); + break; + case text::WritingMode2::PAGE: + SetValue( SvxFrameDirection::Environment ); + break; + default: + bRet = false; + break; + } + } + + return bRet; +} + + +bool SvxFrameDirectionItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 ) const +{ + // translate SvxFrameDirection into WritingDirection2 + sal_Int16 nVal; + bool bRet = true; + switch( GetValue() ) + { + case SvxFrameDirection::Horizontal_LR_TB: + nVal = text::WritingMode2::LR_TB; + break; + case SvxFrameDirection::Horizontal_RL_TB: + nVal = text::WritingMode2::RL_TB; + break; + case SvxFrameDirection::Vertical_RL_TB: + nVal = text::WritingMode2::TB_RL; + break; + case SvxFrameDirection::Vertical_LR_TB: + nVal = text::WritingMode2::TB_LR; + break; + case SvxFrameDirection::Vertical_LR_BT: + nVal = text::WritingMode2::BT_LR; + break; + case SvxFrameDirection::Vertical_RL_TB90: + nVal = text::WritingMode2::TB_RL90; + break; + case SvxFrameDirection::Environment: + nVal = text::WritingMode2::PAGE; + break; + default: + OSL_FAIL("Unknown SvxFrameDirection value!"); + bRet = false; + break; + } + + // return value + error state + if( bRet ) + { + rVal <<= nVal; + } + return bRet; +} + +void SvxFrameDirectionItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFrameDirectionItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nWhich"), + BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute( + pWriter, BAD_CAST("m_nValue"), + BAD_CAST(OString::number(static_cast<sal_Int16>(GetValue())).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/itemtype.cxx b/editeng/source/items/itemtype.cxx new file mode 100644 index 0000000000..cbb83c83be --- /dev/null +++ b/editeng/source/items/itemtype.cxx @@ -0,0 +1,232 @@ +/* -*- 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 <osl/diagnose.h> +#include <vcl/outdev.hxx> +#include <editeng/editrids.hrc> +#include <unotools/intlwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <rtl/ustrbuf.hxx> + + +OUString GetMetricText( tools::Long nVal, MapUnit eSrcUnit, MapUnit eDestUnit, const IntlWrapper* pIntl ) +{ + bool bNeg = false; + bool bShowAtLeastOneDecimalDigit = true; + sal_Int32 nRet = 0; + + if ( nVal < 0 ) + { + bNeg = true; + nVal *= -1; + } + + switch ( eDestUnit ) + { + case MapUnit::Map100thMM: + case MapUnit::Map10thMM: + case MapUnit::MapMM: + case MapUnit::MapCM: + { + nRet = OutputDevice::LogicToLogic( nVal, eSrcUnit, MapUnit::Map100thMM ); + + switch ( eDestUnit ) + { + case MapUnit::Map100thMM: nRet *= 1000; break; + case MapUnit::Map10thMM: nRet *= 100; break; + case MapUnit::MapMM: nRet *= 10; break; + default: ;//prevent warning + } + break; + } + + case MapUnit::Map1000thInch: + case MapUnit::Map100thInch: + case MapUnit::Map10thInch: + case MapUnit::MapInch: + { + nRet = OutputDevice::LogicToLogic( nVal, eSrcUnit, MapUnit::Map1000thInch ); + + switch ( eDestUnit ) + { + case MapUnit::Map1000thInch: nRet *= 1000; break; + case MapUnit::Map100thInch: nRet *= 100; break; + case MapUnit::Map10thInch: nRet *= 10; break; + default: ;//prevent warning + } + break; + } + + case MapUnit::MapPoint: + // fractions of a point are used, e.g., for font size + nRet = OutputDevice::LogicToLogic(nVal, eSrcUnit, MapUnit::MapTwip) * 50; + bShowAtLeastOneDecimalDigit = false; + break; + + case MapUnit::MapTwip: + case MapUnit::MapPixel: + return OUString::number( OutputDevice::LogicToLogic( + nVal, eSrcUnit, eDestUnit )); + + default: + OSL_FAIL( "not supported mapunit" ); + return OUString(); + } + + if ( MapUnit::MapCM == eDestUnit || MapUnit::MapInch == eDestUnit ) + { + sal_Int32 nMod = nRet % 10; + + if ( nMod > 4 ) + nRet += 10 - nMod; + else if ( nMod > 0 ) + nRet -= nMod; + } + + OUStringBuffer sRet; + + if ( bNeg ) + sRet.append('-'); + + tools::Long nDiff = 1000; + for( int nDigits = 4; nDigits; --nDigits, nDiff /= 10 ) + { + if ( nRet < nDiff ) + sRet.append('0'); + else + sRet.append(nRet / nDiff); + nRet %= nDiff; + if( 4 == nDigits && (bShowAtLeastOneDecimalDigit || nRet) ) + { + if(pIntl) + sRet.append(pIntl->getLocaleData()->getNumDecimalSep()); + else + sRet.append(','); + if( !nRet ) + { + sRet.append('0'); + break; + } + } + else if( !nRet ) + break; + } + return sRet.makeStringAndClear(); +} + +OUString GetColorString( const Color& rCol ) +{ + if (rCol == COL_AUTO) + return EditResId(RID_SVXSTR_AUTOMATIC); + + static const Color aColAry[] = { + COL_BLACK, COL_BLUE, COL_GREEN, COL_CYAN, + COL_RED, COL_MAGENTA, COL_BROWN, COL_GRAY, + COL_LIGHTGRAY, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, + COL_LIGHTRED, COL_LIGHTMAGENTA, COL_YELLOW, COL_WHITE }; + + sal_uInt16 nColor = 0; + while ( nColor < SAL_N_ELEMENTS(aColAry) && + aColAry[nColor] != rCol.GetRGBColor() ) + { + nColor += 1; + } + + static TranslateId RID_SVXITEMS_COLORS[] = + { + RID_SVXITEMS_COLOR_BLACK, + RID_SVXITEMS_COLOR_BLUE, + RID_SVXITEMS_COLOR_GREEN, + RID_SVXITEMS_COLOR_CYAN, + RID_SVXITEMS_COLOR_RED, + RID_SVXITEMS_COLOR_MAGENTA, + RID_SVXITEMS_COLOR_BROWN, + RID_SVXITEMS_COLOR_GRAY, + RID_SVXITEMS_COLOR_LIGHTGRAY, + RID_SVXITEMS_COLOR_LIGHTBLUE, + RID_SVXITEMS_COLOR_LIGHTGREEN, + RID_SVXITEMS_COLOR_LIGHTCYAN, + RID_SVXITEMS_COLOR_LIGHTRED, + RID_SVXITEMS_COLOR_LIGHTMAGENTA, + RID_SVXITEMS_COLOR_YELLOW, + RID_SVXITEMS_COLOR_WHITE + }; + + static_assert(SAL_N_ELEMENTS(aColAry) == SAL_N_ELEMENTS(RID_SVXITEMS_COLORS), "must match"); + + OUString sStr; + if ( nColor < SAL_N_ELEMENTS(aColAry) ) + sStr = EditResId(RID_SVXITEMS_COLORS[nColor]); + + if ( sStr.isEmpty() ) + { + sStr += "RGB(" + + OUString::number( rCol.GetRed() ) + cpDelim + + OUString::number( rCol.GetGreen() ) + cpDelim + + OUString::number( rCol.GetBlue() ) + ")"; + } + return sStr; +} + +TranslateId GetMetricId( MapUnit eUnit ) +{ + TranslateId pId = RID_SVXITEMS_METRIC_MM; + + switch ( eUnit ) + { + case MapUnit::Map100thMM: + case MapUnit::Map10thMM: + case MapUnit::MapMM: + pId = RID_SVXITEMS_METRIC_MM; + break; + + case MapUnit::MapCM: + pId = RID_SVXITEMS_METRIC_CM; + break; + + case MapUnit::Map1000thInch: + case MapUnit::Map100thInch: + case MapUnit::Map10thInch: + case MapUnit::MapInch: + pId = RID_SVXITEMS_METRIC_INCH; + break; + + case MapUnit::MapPoint: + pId = RID_SVXITEMS_METRIC_POINT; + break; + + case MapUnit::MapTwip: + pId = RID_SVXITEMS_METRIC_TWIP; + break; + + case MapUnit::MapPixel: + pId = RID_SVXITEMS_METRIC_PIXEL; + break; + + default: + OSL_FAIL( "not supported mapunit" ); + } + return pId; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/justifyitem.cxx b/editeng/source/items/justifyitem.cxx new file mode 100644 index 0000000000..7fe699cb2c --- /dev/null +++ b/editeng/source/items/justifyitem.cxx @@ -0,0 +1,368 @@ +/* -*- 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 <editeng/justifyitem.hxx> +#include <editeng/memberids.h> +#include <editeng/eerdll.hxx> + +#include <com/sun/star/table/CellHoriJustify.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/table/CellJustifyMethod.hpp> +#include <com/sun/star/table/CellVertJustify2.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> + +#include <strings.hrc> + +SfxPoolItem* SvxHorJustifyItem::CreateDefault() { return new SvxHorJustifyItem(SvxCellHorJustify::Standard, 0) ;} +SfxPoolItem* SvxVerJustifyItem::CreateDefault() { return new SvxVerJustifyItem(SvxCellVerJustify::Standard, 0) ;} + +using namespace ::com::sun::star; + + +SvxHorJustifyItem::SvxHorJustifyItem( const sal_uInt16 nId ) : + SfxEnumItem( nId, SvxCellHorJustify::Standard ) +{ +} + +SvxHorJustifyItem::SvxHorJustifyItem( const SvxCellHorJustify eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + + +bool SvxHorJustifyItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper&) const +{ + rText = GetValueText(GetValue()); + return true; +} + + +bool SvxHorJustifyItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_HORJUST: + { + table::CellHoriJustify eUno = table::CellHoriJustify_STANDARD; + switch ( GetValue() ) + { + case SvxCellHorJustify::Standard: eUno = table::CellHoriJustify_STANDARD; break; + case SvxCellHorJustify::Left: eUno = table::CellHoriJustify_LEFT; break; + case SvxCellHorJustify::Center: eUno = table::CellHoriJustify_CENTER; break; + case SvxCellHorJustify::Right: eUno = table::CellHoriJustify_RIGHT; break; + case SvxCellHorJustify::Block: eUno = table::CellHoriJustify_BLOCK; break; + case SvxCellHorJustify::Repeat: eUno = table::CellHoriJustify_REPEAT; break; + } + rVal <<= eUno; + } + break; + case MID_HORJUST_ADJUST: + { + // ParagraphAdjust values, as in SvxAdjustItem + // (same value for ParaAdjust and ParaLastLineAdjust) + + style::ParagraphAdjust nAdjust = style::ParagraphAdjust_LEFT; + switch ( GetValue() ) + { + // ParagraphAdjust_LEFT is used for STANDARD and REPEAT + case SvxCellHorJustify::Standard: + case SvxCellHorJustify::Repeat: + case SvxCellHorJustify::Left: nAdjust = style::ParagraphAdjust_LEFT; break; + case SvxCellHorJustify::Center: nAdjust = style::ParagraphAdjust_CENTER; break; + case SvxCellHorJustify::Right: nAdjust = style::ParagraphAdjust_RIGHT; break; + case SvxCellHorJustify::Block: nAdjust = style::ParagraphAdjust_BLOCK; break; + } + rVal <<= static_cast<sal_Int16>(nAdjust); // as sal_Int16 + } + break; + } + return true; +} + +bool SvxHorJustifyItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_HORJUST: + { + table::CellHoriJustify eUno; + if(!(rVal >>= eUno)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + eUno = static_cast<table::CellHoriJustify>(nValue); + } + SvxCellHorJustify eSvx = SvxCellHorJustify::Standard; + switch (eUno) + { + case table::CellHoriJustify_STANDARD: eSvx = SvxCellHorJustify::Standard; break; + case table::CellHoriJustify_LEFT: eSvx = SvxCellHorJustify::Left; break; + case table::CellHoriJustify_CENTER: eSvx = SvxCellHorJustify::Center; break; + case table::CellHoriJustify_RIGHT: eSvx = SvxCellHorJustify::Right; break; + case table::CellHoriJustify_BLOCK: eSvx = SvxCellHorJustify::Block; break; + case table::CellHoriJustify_REPEAT: eSvx = SvxCellHorJustify::Repeat; break; + default: ; //prevent warning + } + SetValue( eSvx ); + } + break; + case MID_HORJUST_ADJUST: + { + // property contains ParagraphAdjust values as sal_Int16 + sal_Int16 nVal = sal_Int16(); + if(!(rVal >>= nVal)) + return false; + + SvxCellHorJustify eSvx = SvxCellHorJustify::Standard; + switch (static_cast<style::ParagraphAdjust>(nVal)) + { + // STRETCH is treated as BLOCK + case style::ParagraphAdjust_LEFT: eSvx = SvxCellHorJustify::Left; break; + case style::ParagraphAdjust_RIGHT: eSvx = SvxCellHorJustify::Right; break; + case style::ParagraphAdjust_STRETCH: + case style::ParagraphAdjust_BLOCK: eSvx = SvxCellHorJustify::Block; break; + case style::ParagraphAdjust_CENTER: eSvx = SvxCellHorJustify::Center; break; + default: break; + } + SetValue( eSvx ); + } + } + return true; +} + +OUString SvxHorJustifyItem::GetValueText(SvxCellHorJustify nVal) +{ + assert(nVal <= SvxCellHorJustify::Repeat && "enum overflow!"); + return EditResId(RID_SVXITEMS_HORJUST[static_cast<size_t>(nVal)]); +} + +SvxHorJustifyItem* SvxHorJustifyItem::Clone( SfxItemPool* ) const +{ + return new SvxHorJustifyItem( *this ); +} + +sal_uInt16 SvxHorJustifyItem::GetValueCount() const +{ + return sal_uInt16(SvxCellHorJustify::Repeat) + 1; // Last Enum value + 1 +} + + +SvxVerJustifyItem::SvxVerJustifyItem( const sal_uInt16 nId ) : + SfxEnumItem( nId, SvxCellVerJustify::Standard ) +{ +} + +SvxVerJustifyItem::SvxVerJustifyItem( const SvxCellVerJustify eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + + +bool SvxVerJustifyItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& ) const +{ + rText = GetValueText( GetValue() ); + return true; +} + + +bool SvxVerJustifyItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_ADJUST: + { + style::VerticalAlignment eUno = style::VerticalAlignment_TOP; + switch ( GetValue() ) + { + case SvxCellVerJustify::Top: eUno = style::VerticalAlignment_TOP; break; + case SvxCellVerJustify::Center: eUno = style::VerticalAlignment_MIDDLE; break; + case SvxCellVerJustify::Bottom: eUno = style::VerticalAlignment_BOTTOM; break; + default: ; //prevent warning + } + rVal <<= eUno; + break; + } + default: + { + sal_Int32 nUno = table::CellVertJustify2::STANDARD; + switch ( GetValue() ) + { + case SvxCellVerJustify::Standard: nUno = table::CellVertJustify2::STANDARD; break; + case SvxCellVerJustify::Top: nUno = table::CellVertJustify2::TOP; break; + case SvxCellVerJustify::Center: nUno = table::CellVertJustify2::CENTER; break; + case SvxCellVerJustify::Bottom: nUno = table::CellVertJustify2::BOTTOM; break; + case SvxCellVerJustify::Block: nUno = table::CellVertJustify2::BLOCK; break; + default: ; //prevent warning + } + rVal <<= nUno; + break; + } + } + return true; +} + +bool SvxVerJustifyItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_HORJUST_ADJUST: + { + // property contains ParagraphAdjust values as sal_Int16 + style::VerticalAlignment nVal = style::VerticalAlignment_TOP; + if(!(rVal >>= nVal)) + return false; + + SvxCellVerJustify eSvx = SvxCellVerJustify::Standard; + switch (nVal) + { + case style::VerticalAlignment_TOP: eSvx = SvxCellVerJustify::Top; break; + case style::VerticalAlignment_MIDDLE: eSvx = SvxCellVerJustify::Center; break; + case style::VerticalAlignment_BOTTOM: eSvx = SvxCellVerJustify::Bottom; break; + default:; + } + SetValue( eSvx ); + break; + } + default: + { + sal_Int32 eUno = table::CellVertJustify2::STANDARD; + rVal >>= eUno; + + SvxCellVerJustify eSvx = SvxCellVerJustify::Standard; + switch (eUno) + { + case table::CellVertJustify2::STANDARD: eSvx = SvxCellVerJustify::Standard; break; + case table::CellVertJustify2::TOP: eSvx = SvxCellVerJustify::Top; break; + case table::CellVertJustify2::CENTER: eSvx = SvxCellVerJustify::Center; break; + case table::CellVertJustify2::BOTTOM: eSvx = SvxCellVerJustify::Bottom; break; + case table::CellVertJustify2::BLOCK: eSvx = SvxCellVerJustify::Block; break; + default: ; //prevent warning + } + SetValue( eSvx ); + break; + } + } + + return true; +} + +OUString SvxVerJustifyItem::GetValueText( SvxCellVerJustify nVal ) +{ + assert(nVal <= SvxCellVerJustify::Block && "enum overflow!"); + return EditResId(RID_SVXITEMS_VERJUST[static_cast<size_t>(nVal)]); +} + +SvxVerJustifyItem* SvxVerJustifyItem::Clone( SfxItemPool* ) const +{ + return new SvxVerJustifyItem( *this ); +} + +sal_uInt16 SvxVerJustifyItem::GetValueCount() const +{ + return static_cast<sal_uInt16>(SvxCellVerJustify::Bottom) + 1; // Last Enum value + 1 +} + +SvxJustifyMethodItem::SvxJustifyMethodItem( const SvxCellJustifyMethod eJustify, + const sal_uInt16 nId ) : + SfxEnumItem( nId, eJustify ) +{ +} + +bool SvxJustifyMethodItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& ) const +{ + rText = GetValueText( GetValue() ); + return true; +} + + +bool SvxJustifyMethodItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + sal_Int32 nUno = table::CellJustifyMethod::AUTO; + switch (GetValue()) + { + case SvxCellJustifyMethod::Auto: nUno = table::CellJustifyMethod::AUTO; break; + case SvxCellJustifyMethod::Distribute: nUno = table::CellJustifyMethod::DISTRIBUTE; break; + default:; + } + rVal <<= nUno; + return true; +} + +bool SvxJustifyMethodItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + sal_Int32 nVal = table::CellJustifyMethod::AUTO; + if (!(rVal >>= nVal)) + return false; + + SvxCellJustifyMethod eSvx = SvxCellJustifyMethod::Auto; + switch (nVal) + { + case table::CellJustifyMethod::AUTO: + eSvx = SvxCellJustifyMethod::Auto; + break; + case table::CellJustifyMethod::DISTRIBUTE: + eSvx = SvxCellJustifyMethod::Distribute; + break; + default:; + } + SetValue(eSvx); + return true; +} + +OUString SvxJustifyMethodItem::GetValueText( SvxCellJustifyMethod nVal ) +{ + assert(nVal <= SvxCellJustifyMethod::Distribute && "enum overflow!"); + return EditResId(RID_SVXITEMS_JUSTMETHOD[static_cast<size_t>(nVal)]); +} + +SvxJustifyMethodItem* SvxJustifyMethodItem::Clone( SfxItemPool* ) const +{ + return new SvxJustifyMethodItem( *this ); +} + +sal_uInt16 SvxJustifyMethodItem::GetValueCount() const +{ + return static_cast<sal_uInt16>(SvxCellJustifyMethod::Distribute) + 1; // Last Enum value + 1 +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/legacyitem.cxx b/editeng/source/items/legacyitem.cxx new file mode 100644 index 0000000000..96742f46fc --- /dev/null +++ b/editeng/source/items/legacyitem.cxx @@ -0,0 +1,826 @@ +/* -*- 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 <editeng/legacyitem.hxx> +#include <unotools/fontdefs.hxx> +#include <tools/tenccvt.hxx> +#include <tools/stream.hxx> +#include <comphelper/fileformat.h> +#include <vcl/graph.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/TypeSerializer.hxx> +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/editerr.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/shaditem.hxx> +#include <tools/GenericTypeSerializer.hxx> + + +void Create_legacy_direct_set(SvxFontHeightItem& rItem, sal_uInt32 nH, sal_uInt16 nP, MapUnit eP) +{ + rItem.legacy_direct_set(nH, nP, eP); +} + +namespace legacy +{ + namespace SvxFont + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxFontItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 _eFamily, eFontPitch, eFontTextEncoding; + OUString aName, aStyle; + rStrm.ReadUChar( _eFamily ); + rStrm.ReadUChar( eFontPitch ); + rStrm.ReadUChar( eFontTextEncoding ); + + // UNICODE: rStrm >> aName; + aName = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // UNICODE: rStrm >> aStyle; + aStyle = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // Set the "correct" textencoding + eFontTextEncoding = static_cast<sal_uInt8>(GetSOLoadTextEncoding( eFontTextEncoding )); + + // at some point, the StarBats changes from ANSI font to SYMBOL font + if ( RTL_TEXTENCODING_SYMBOL != eFontTextEncoding && aName == "StarBats" ) + eFontTextEncoding = RTL_TEXTENCODING_SYMBOL; + + // Check if we have stored unicode + sal_uInt64 const nStreamPos = rStrm.Tell(); + // #define STORE_UNICODE_MAGIC_MARKER 0xFE331188 + sal_uInt32 nMagic = 0xFE331188; + rStrm.ReadUInt32( nMagic ); + if ( nMagic == 0xFE331188 ) + { + aName = rStrm.ReadUniOrByteString( RTL_TEXTENCODING_UNICODE ); + aStyle = rStrm.ReadUniOrByteString( RTL_TEXTENCODING_UNICODE ); + } + else + { + rStrm.Seek( nStreamPos ); + } + + rItem.SetFamilyName(aName); + rItem.SetStyleName(aStyle); + rItem.SetFamily(static_cast<FontFamily>(_eFamily)); + rItem.SetPitch(static_cast<FontPitch>(eFontPitch)); + rItem.SetCharSet(static_cast<rtl_TextEncoding>(eFontTextEncoding)); + } + + SvStream& Store(const SvxFontItem& rItem, SvStream& rStrm, sal_uInt16) + { + const bool bToBats(IsOpenSymbol(rItem.GetFamilyName())); + + rStrm.WriteUChar(rItem.GetFamily()).WriteUChar(rItem.GetPitch()).WriteUChar(bToBats ? + RTL_TEXTENCODING_SYMBOL : + GetSOStoreTextEncoding(rItem.GetCharSet())); + + const OUString aStoreFamilyName(bToBats ? "StarBats" : rItem.GetFamilyName()); + + rStrm.WriteUniOrByteString(aStoreFamilyName, rStrm.GetStreamCharSet()); + rStrm.WriteUniOrByteString(rItem.GetStyleName(), rStrm.GetStreamCharSet()); + + return rStrm; + } + } + + namespace SvxFontHeight + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return (nFileFormatVersion <= SOFFICE_FILEFORMAT_40) + ? FONTHEIGHT_16_VERSION + : FONTHEIGHT_UNIT_VERSION; + } + + void Create(SvxFontHeightItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_uInt16 nsize, nprop = 0; + MapUnit nPropUnit = MapUnit::MapRelative; + + rStrm.ReadUInt16( nsize ); + + if( FONTHEIGHT_16_VERSION <= nItemVersion ) + rStrm.ReadUInt16( nprop ); + else + { + sal_uInt8 nP; + rStrm .ReadUChar( nP ); + nprop = static_cast<sal_uInt16>(nP); + } + + if( FONTHEIGHT_UNIT_VERSION <= nItemVersion ) + { + sal_uInt16 nTmp; + rStrm.ReadUInt16( nTmp ); + nPropUnit = static_cast<MapUnit>(nTmp); + } + + Create_legacy_direct_set(rItem, nsize, nprop, nPropUnit); + } + + SvStream& Store(const SvxFontHeightItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteUInt16( rItem.GetHeight() ); + + if( FONTHEIGHT_UNIT_VERSION <= nItemVersion ) + rStrm.WriteUInt16( rItem.GetProp() ).WriteUInt16( static_cast<sal_uInt16>(rItem.GetPropUnit()) ); + else + { + // When exporting to the old versions the relative information is lost + // when there is no percentage + sal_uInt16 _nProp = rItem.GetProp(); + if( MapUnit::MapRelative != rItem.GetPropUnit() ) + _nProp = 100; + rStrm.WriteUInt16( _nProp ); + } + return rStrm; + } + } + + namespace SvxWeight + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxWeightItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nWeight(0); + rStrm.ReadUChar(nWeight); + rItem.SetValue(static_cast<FontWeight>(nWeight)); + } + + SvStream& Store(const SvxWeightItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxPosture + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxPostureItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nPosture(0); + rStrm.ReadUChar(nPosture); + rItem.SetValue(static_cast<FontItalic>(nPosture)); + } + + SvStream& Store(const SvxPostureItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar( rItem.GetValue() ); + return rStrm; + } + } + + namespace SvxTextLine // SvxUnderlineItem, SvxOverlineItem -> SvxTextLineItem + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxTextLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 nState(0); + rStrm.ReadUChar(nState); + rItem.SetValue(static_cast<FontLineStyle>(nState)); + // GetColor() is *not* saved/loaded ?!? + } + + SvStream& Store(const SvxTextLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + // GetColor() is *not* saved/loaded ?!? + return rStrm; + } + } + + namespace SvxCrossedOut + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxCrossedOutItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt8 eCross(0); + rStrm.ReadUChar(eCross); + rItem.SetValue(static_cast<FontStrikeout>(eCross)); + } + + SvStream& Store(const SvxCrossedOutItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUChar(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxColor + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxColorItem: Is there a new file format? "); + return SOFFICE_FILEFORMAT_50 >= nFileFormatVersion ? VERSION_USEAUTOCOLOR : 0; + } + + void Create(SvxColorItem& rItem, SvStream& rStrm, sal_uInt16) + { + Color aColor(COL_AUTO); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + rItem.SetValue(aColor); + } + + SvStream& Store(const SvxColorItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + tools::GenericTypeSerializer aSerializer(rStrm); + if( VERSION_USEAUTOCOLOR == nItemVersion && COL_AUTO == rItem.GetValue() ) + aSerializer.writeColor(COL_BLACK); + else + aSerializer.writeColor(rItem.GetValue()); + return rStrm; + } + } + + namespace SvxBox + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxBoxItem: Is there a new file format?" ); + return SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion ? 0 : BOX_BORDER_STYLE_VERSION; + } + + /// Item version for saved border lines. The old version saves the line without style information. + const int BORDER_LINE_OLD_VERSION = 0; + /// Item version for saved border lies. The new version includes line style. + const int BORDER_LINE_WITH_STYLE_VERSION = 1; + + /// Creates a border line from a stream. + static ::editeng::SvxBorderLine CreateBorderLine(SvStream &stream, sal_uInt16 version) + { + sal_uInt16 nOutline, nInline, nDistance; + sal_uInt16 nStyle = css::table::BorderLineStyle::NONE; + Color aColor; + tools::GenericTypeSerializer aSerializer(stream); + aSerializer.readColor(aColor); + stream.ReadUInt16( nOutline ).ReadUInt16( nInline ).ReadUInt16( nDistance ); + + if (version >= BORDER_LINE_WITH_STYLE_VERSION) + stream.ReadUInt16( nStyle ); + + ::editeng::SvxBorderLine border(&aColor); + border.GuessLinesWidths(static_cast<SvxBorderLineStyle>(nStyle), nOutline, nInline, nDistance); + return border; + } + + /// Retrieves a BORDER_LINE_* version from a BOX_BORDER_* version. + static sal_uInt16 BorderLineVersionFromBoxVersion(sal_uInt16 boxVersion) + { + return (boxVersion >= BOX_BORDER_STYLE_VERSION)? BORDER_LINE_WITH_STYLE_VERSION : BORDER_LINE_OLD_VERSION; + } + + void Create(SvxBoxItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_uInt16 nDistance(0); + rStrm.ReadUInt16( nDistance ); + SvxBoxItemLine aLineMap[4] = { SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, + SvxBoxItemLine::RIGHT, SvxBoxItemLine::BOTTOM }; + sal_Int8 cLine(0); + + while (rStrm.good()) + { + rStrm.ReadSChar( cLine ); + + if( cLine > 3 ) + break; + + ::editeng::SvxBorderLine aBorder = CreateBorderLine(rStrm, BorderLineVersionFromBoxVersion(nItemVersion)); + rItem.SetLine( &aBorder, aLineMap[cLine] ); + } + + if( nItemVersion >= BOX_4DISTS_VERSION && (cLine&0x10) != 0 ) + { + for(const SvxBoxItemLine & i : aLineMap) + { + sal_uInt16 nDist; + rStrm.ReadUInt16( nDist ); + rItem.SetDistance( nDist, i ); + } + } + else + { + rItem.SetAllDistances(nDistance); + } + } + + /// Store a border line to a stream. + static SvStream& StoreBorderLine(SvStream &stream, const ::editeng::SvxBorderLine &l, sal_uInt16 version) + { + tools::GenericTypeSerializer aSerializer(stream); + aSerializer.writeColor(l.GetColor()); + + stream.WriteUInt16( l.GetOutWidth() ) + .WriteUInt16( l.GetInWidth() ) + .WriteUInt16( l.GetDistance() ); + + if (version >= BORDER_LINE_WITH_STYLE_VERSION) + stream.WriteUInt16( static_cast<sal_uInt16>(l.GetBorderLineStyle()) ); + + return stream; + } + + SvStream& Store(const SvxBoxItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteUInt16( rItem.GetSmallestDistance() ); + const ::editeng::SvxBorderLine* pLine[ 4 ]; // top, left, right, bottom + pLine[ 0 ] = rItem.GetTop(); + pLine[ 1 ] = rItem.GetLeft(); + pLine[ 2 ] = rItem.GetRight(); + pLine[ 3 ] = rItem.GetBottom(); + + for( int i = 0; i < 4; i++ ) + { + const ::editeng::SvxBorderLine* l = pLine[ i ]; + if( l ) + { + rStrm.WriteSChar(i); + StoreBorderLine(rStrm, *l, BorderLineVersionFromBoxVersion(nItemVersion)); + } + } + sal_Int8 cLine = 4; + const sal_uInt16 nTopDist(rItem.GetDistance(SvxBoxItemLine::TOP)); + const sal_uInt16 nLeftDist(rItem.GetDistance(SvxBoxItemLine::LEFT)); + const sal_uInt16 nRightDist(rItem.GetDistance(SvxBoxItemLine::RIGHT)); + const sal_uInt16 nBottomDist(rItem.GetDistance(SvxBoxItemLine::BOTTOM)); + + if( nItemVersion >= BOX_4DISTS_VERSION && + !(nTopDist == nLeftDist && + nTopDist == nRightDist && + nTopDist == nBottomDist) ) + { + cLine |= 0x10; + } + + rStrm.WriteSChar( cLine ); + + if( nItemVersion >= BOX_4DISTS_VERSION && (cLine & 0x10) != 0 ) + { + rStrm.WriteUInt16( nTopDist ) + .WriteUInt16( nLeftDist ) + .WriteUInt16( nRightDist ) + .WriteUInt16( nBottomDist ); + } + + return rStrm; + } + } + + namespace SvxLine + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + short nOutline, nInline, nDistance; + Color aColor; + + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + rStrm.ReadInt16( nOutline ).ReadInt16( nInline ).ReadInt16( nDistance ); + if( nOutline ) + { + ::editeng::SvxBorderLine aLine( &aColor ); + aLine.GuessLinesWidths(SvxBorderLineStyle::NONE, nOutline, nInline, nDistance); + rItem.SetLine( &aLine ); + } + } + + SvStream& Store(const SvxLineItem& rItem, SvStream& rStrm, sal_uInt16) + { + const ::editeng::SvxBorderLine* pLine(rItem.GetLine()); + + if(nullptr != pLine) + { + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(pLine->GetColor()); + rStrm.WriteInt16( pLine->GetOutWidth() ) + .WriteInt16( pLine->GetInWidth() ) + .WriteInt16( pLine->GetDistance() ); + } + else + { + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(Color()); + rStrm.WriteInt16( 0 ).WriteInt16( 0 ).WriteInt16( 0 ); + } + + return rStrm; + } + } + + namespace SvxBrush + { + sal_uInt16 GetVersion(sal_uInt16) + { + return BRUSH_GRAPHIC_VERSION; + } + + const sal_uInt16 LOAD_GRAPHIC = (sal_uInt16(0x0001)); + const sal_uInt16 LOAD_LINK = (sal_uInt16(0x0002)); + const sal_uInt16 LOAD_FILTER = (sal_uInt16(0x0004)); + + void Create(SvxBrushItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + bool bTrans; + Color aTempColor; + Color aTempFillColor; + sal_Int8 nStyle; + + rStrm.ReadCharAsBool( bTrans ); + TypeSerializer aSerializer(rStrm); + aSerializer.readColor(aTempColor); + aSerializer.readColor(aTempFillColor); + rStrm.ReadSChar( nStyle ); + + switch ( nStyle ) + { + case 8: // BRUSH_25: + { + sal_uInt32 nRed = aTempColor.GetRed(); + sal_uInt32 nGreen = aTempColor.GetGreen(); + sal_uInt32 nBlue = aTempColor.GetBlue(); + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed())*2; + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen())*2; + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue())*2; + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/3), static_cast<sal_Int8>(nGreen/3), static_cast<sal_Int8>(nBlue/3) )); + } + break; + + case 9: // BRUSH_50: + { + sal_uInt32 nRed = aTempColor.GetRed(); + sal_uInt32 nGreen = aTempColor.GetGreen(); + sal_uInt32 nBlue = aTempColor.GetBlue(); + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed()); + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen()); + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue()); + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/2), static_cast<sal_Int8>(nGreen/2), static_cast<sal_Int8>(nBlue/2) )); + } + break; + + case 10: // BRUSH_75: + { + sal_uInt32 nRed = aTempColor.GetRed()*2; + sal_uInt32 nGreen = aTempColor.GetGreen()*2; + sal_uInt32 nBlue = aTempColor.GetBlue()*2; + nRed += static_cast<sal_uInt32>(aTempFillColor.GetRed()); + nGreen += static_cast<sal_uInt32>(aTempFillColor.GetGreen()); + nBlue += static_cast<sal_uInt32>(aTempFillColor.GetBlue()); + rItem.SetColor(Color( static_cast<sal_Int8>(nRed/3), static_cast<sal_Int8>(nGreen/3), static_cast<sal_Int8>(nBlue/3) )); + } + break; + + case 0: // BRUSH_NULL: + rItem.SetColor(COL_TRANSPARENT); + break; + + default: + rItem.SetColor(aTempColor); + } + + if ( nItemVersion < BRUSH_GRAPHIC_VERSION ) + return; + + sal_uInt16 nDoLoad = 0; + sal_Int8 nPos; + + rStrm.ReadUInt16( nDoLoad ); + + if ( nDoLoad & LOAD_GRAPHIC ) + { + Graphic aGraphic; + aSerializer.readGraphic(aGraphic); + rItem.SetGraphicObject(GraphicObject(std::move(aGraphic))); + + if( SVSTREAM_FILEFORMAT_ERROR == rStrm.GetError() ) + { + rStrm.ResetError(); + rStrm.SetError( ERRCODE_SVX_GRAPHIC_WRONG_FILEFORMAT.MakeWarning() ); + } + } + + if ( nDoLoad & LOAD_LINK ) + { + // UNICODE: rStrm >> aRel; + OUString aRel = rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet()); + + // TODO/MBA: how can we get a BaseURL here?! + OSL_FAIL("No BaseURL!"); + OUString aAbs = INetURLObject::GetAbsURL( u"", aRel ); + DBG_ASSERT( !aAbs.isEmpty(), "Invalid URL!" ); + rItem.SetGraphicLink(aAbs); + } + + if ( nDoLoad & LOAD_FILTER ) + { + // UNICODE: rStrm >> maStrFilter; + rItem.SetGraphicFilter(rStrm.ReadUniOrByteString(rStrm.GetStreamCharSet())); + } + + rStrm.ReadSChar( nPos ); + + rItem.SetGraphicPos(static_cast<SvxGraphicPosition>(nPos)); + } + + SvStream& Store(const SvxBrushItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteBool( false ); + TypeSerializer aSerializer(rStrm); + aSerializer.writeColor(rItem.GetColor()); + aSerializer.writeColor(rItem.GetColor()); + rStrm.WriteSChar( rItem.GetColor().IsTransparent() ? 0 : 1 ); //BRUSH_NULL : BRUSH_SOLID + + sal_uInt16 nDoLoad = 0; + const GraphicObject* pGraphicObject(rItem.GetGraphicObject()); + + if (nullptr != pGraphicObject && rItem.GetGraphicLink().isEmpty()) + nDoLoad |= LOAD_GRAPHIC; + if ( !rItem.GetGraphicLink().isEmpty() ) + nDoLoad |= LOAD_LINK; + if ( !rItem.GetGraphicFilter().isEmpty() ) + nDoLoad |= LOAD_FILTER; + rStrm.WriteUInt16( nDoLoad ); + + if (nullptr != pGraphicObject && rItem.GetGraphicLink().isEmpty()) + { + aSerializer.writeGraphic(pGraphicObject->GetGraphic()); + } + if ( !rItem.GetGraphicLink().isEmpty() ) + { + OSL_FAIL("No BaseURL!"); + // TODO/MBA: how to get a BaseURL?! + OUString aRel = INetURLObject::GetRelURL( u"", rItem.GetGraphicLink() ); + // UNICODE: rStrm << aRel; + rStrm.WriteUniOrByteString(aRel, rStrm.GetStreamCharSet()); + } + if ( !rItem.GetGraphicFilter().isEmpty() ) + { + // UNICODE: rStrm << rItem.GetGraphicFilter(); + rStrm.WriteUniOrByteString(rItem.GetGraphicFilter(), rStrm.GetStreamCharSet()); + } + rStrm.WriteSChar( rItem.GetGraphicPos() ); + return rStrm; + } + } + + namespace SvxAdjust + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return (nFileFormatVersion == SOFFICE_FILEFORMAT_31) + ? 0 : ADJUST_LASTBLOCK_VERSION; + } + + void Create(SvxAdjustItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + char eAdjustment; + rStrm.ReadChar(eAdjustment); + rItem.SetAdjust(static_cast<::SvxAdjust>(eAdjustment)); + + if( nItemVersion >= ADJUST_LASTBLOCK_VERSION ) + { + sal_Int8 nFlags; + rStrm.ReadSChar( nFlags ); + rItem.SetAsFlags(nFlags); + } + } + + SvStream& Store(const SvxAdjustItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteChar( static_cast<char>(rItem.GetAdjust()) ); + if ( nItemVersion >= ADJUST_LASTBLOCK_VERSION ) + { + const sal_Int8 nFlags(rItem.GetAsFlags()); + rStrm.WriteSChar( nFlags ); + } + return rStrm; + } + } + + namespace SvxHorJustify + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxHorJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxCellHorJustify>(nVal)); + } + + SvStream& Store(const SvxHorJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxVerJustify + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxVerJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxCellVerJustify>(nVal)); + } + + SvStream& Store(const SvxVerJustifyItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxFrameDirection + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + return SOFFICE_FILEFORMAT_50 > nFileFormatVersion ? USHRT_MAX : 0; + } + + void Create(SvxFrameDirectionItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_uInt16 nVal(0); + rStrm.ReadUInt16( nVal ); + rItem.SetValue(static_cast<::SvxFrameDirection>(nVal)); + } + + SvStream& Store(const SvxFrameDirectionItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteUInt16( static_cast<sal_uInt16>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxFormatBreak + { + sal_uInt16 GetVersion(sal_uInt16 nFileFormatVersion) + { + DBG_ASSERT( SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion || + SOFFICE_FILEFORMAT_50==nFileFormatVersion, + "SvxFormatBreakItem: Is there a new file format? "); + return SOFFICE_FILEFORMAT_31==nFileFormatVersion || + SOFFICE_FILEFORMAT_40==nFileFormatVersion ? 0 : FMTBREAK_NOAUTO; + } + + void Create(SvxFormatBreakItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + sal_Int8 eBreak, bDummy; + rStrm.ReadSChar( eBreak ); + if( FMTBREAK_NOAUTO > nItemVersion ) + rStrm.ReadSChar( bDummy ); + rItem.SetValue(static_cast<::SvxBreak>(eBreak)); + } + + SvStream& Store(const SvxFormatBreakItem& rItem, SvStream& rStrm, sal_uInt16 nItemVersion) + { + rStrm.WriteSChar( rItem.GetEnumValue() ); + if( FMTBREAK_NOAUTO > nItemVersion ) + rStrm.WriteSChar( 0x01 ); + return rStrm; + } + } + + namespace SvxFormatKeep + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxFormatKeepItem& rItem, SvStream& rStrm, sal_uInt16) + { + // derived from SfxBoolItem, but that uses + // rStream.ReadCharAsBool( tmp ); + sal_Int8 bIsKeep; + rStrm.ReadSChar( bIsKeep ); + rItem.SetValue(static_cast<bool>(bIsKeep)); + } + + SvStream& Store(const SvxFormatKeepItem& rItem, SvStream& rStrm, sal_uInt16) + { + // derived from SfxBoolItem, but that uses + // rStream.WriteBool( m_bValue ); // not bool for serialization! + rStrm.WriteSChar( static_cast<sal_Int8>(rItem.GetValue()) ); + return rStrm; + } + } + + namespace SvxShadow + { + sal_uInt16 GetVersion(sal_uInt16) + { + return 0; + } + + void Create(SvxShadowItem& rItem, SvStream& rStrm, sal_uInt16) + { + sal_Int8 cLoc; + sal_uInt16 _nWidth; + bool bTrans; + Color aColor; + Color aFillColor; + sal_Int8 nStyle; + rStrm.ReadSChar( cLoc ).ReadUInt16( _nWidth ).ReadCharAsBool( bTrans ); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.readColor(aColor); + aSerializer.readColor(aFillColor); + rStrm.ReadSChar(nStyle); + aColor.SetAlpha(bTrans ? 0 : 255); + + rItem.SetLocation(static_cast<SvxShadowLocation>(cLoc)); + rItem.SetWidth(_nWidth); + rItem.SetColor(aColor); + } + + SvStream& Store(const SvxShadowItem& rItem, SvStream& rStrm, sal_uInt16) + { + rStrm.WriteSChar( static_cast<sal_uInt8>(rItem.GetLocation()) ) + .WriteUInt16( rItem.GetWidth() ) + .WriteBool( rItem.GetColor().IsTransparent() ); + tools::GenericTypeSerializer aSerializer(rStrm); + aSerializer.writeColor(rItem.GetColor()); + aSerializer.writeColor(rItem.GetColor()); + rStrm.WriteSChar( rItem.GetColor().IsTransparent() ? 0 : 1 ); //BRUSH_NULL : BRUSH_SOLID + return rStrm; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/numitem.cxx b/editeng/source/items/numitem.cxx new file mode 100644 index 0000000000..983eff2779 --- /dev/null +++ b/editeng/source/items/numitem.cxx @@ -0,0 +1,1156 @@ +/* -*- 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 <algorithm> + +#include <editeng/numitem.hxx> + +#include <com/sun/star/text/VertOrientation.hpp> +#include <comphelper/propertyvalue.hxx> +#include <editeng/brushitem.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/font.hxx> +#include <vcl/settings.hxx> +#include <editeng/editids.hrc> +#include <editeng/numdef.hxx> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/text/XNumberingFormatter.hpp> +#include <com/sun/star/text/DefaultNumberingProvider.hpp> +#include <com/sun/star/text/XDefaultNumberingProvider.hpp> +#include <com/sun/star/style/NumberingType.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/fileformat.h> +#include <comphelper/processfactory.hxx> +#include <tools/mapunit.hxx> +#include <tools/stream.hxx> +#include <tools/debug.hxx> +#include <tools/GenericTypeSerializer.hxx> +#include <unotools/configmgr.hxx> +#include <libxml/xmlwriter.h> +#include <editeng/unonrule.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <editeng/legacyitem.hxx> + +constexpr sal_Int32 DEF_WRITER_LSPACE = 500; //Standard Indentation +constexpr sal_Int32 DEF_DRAW_LSPACE = 800; //Standard Indentation + +constexpr sal_uInt16 NUMITEM_VERSION_03 = 0x03; +constexpr sal_uInt16 NUMITEM_VERSION_04 = 0x04; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::style; + +sal_Int32 SvxNumberType::nRefCount = 0; +css::uno::Reference<css::text::XNumberingFormatter> SvxNumberType::xFormatter; +static void lcl_getFormatter(css::uno::Reference<css::text::XNumberingFormatter>& _xFormatter) +{ + if(_xFormatter.is()) + return; + + try + { + Reference<XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + Reference<XDefaultNumberingProvider> xRet = text::DefaultNumberingProvider::create(xContext); + _xFormatter.set(xRet, UNO_QUERY); + } + catch(const Exception&) + { + SAL_WARN("editeng", "service missing: \"com.sun.star.text.DefaultNumberingProvider\""); + } +} + +SvxNumberType::SvxNumberType(SvxNumType nType) : + nNumType(nType), + bShowSymbol(true) +{ + nRefCount++; +} + +SvxNumberType::SvxNumberType(const SvxNumberType& rType) : + nNumType(rType.nNumType), + bShowSymbol(rType.bShowSymbol) +{ + nRefCount++; +} + +SvxNumberType::~SvxNumberType() +{ + if(!--nRefCount) + xFormatter = nullptr; +} + +OUString SvxNumberType::GetNumStr( sal_Int32 nNo ) const +{ + LanguageTag aLang = utl::ConfigManager::IsFuzzing() ? + LanguageTag("en-US") : + Application::GetSettings().GetLanguageTag(); + return GetNumStr( nNo, aLang.getLocale() ); +} + +static bool isArabicNumberingType(SvxNumType t) +{ + return t == SVX_NUM_ARABIC || t == SVX_NUM_ARABIC_ZERO || t == SVX_NUM_ARABIC_ZERO3 + || t == SVX_NUM_ARABIC_ZERO4 || t == SVX_NUM_ARABIC_ZERO5; +} + +OUString SvxNumberType::GetNumStr( sal_Int32 nNo, const css::lang::Locale& rLocale, bool bIsLegal ) const +{ + lcl_getFormatter(xFormatter); + if(!xFormatter.is()) + return OUString(); + + if(bShowSymbol) + { + switch(nNumType) + { + case NumberingType::CHAR_SPECIAL: + case NumberingType::BITMAP: + break; + default: + { + // '0' allowed for ARABIC numberings + if(NumberingType::ARABIC == nNumType && 0 == nNo ) + return OUString('0'); + else + { + SvxNumType nActType = !bIsLegal || isArabicNumberingType(nNumType) ? nNumType : SVX_NUM_ARABIC; + static constexpr OUString sNumberingType = u"NumberingType"_ustr; + static constexpr OUString sValue = u"Value"_ustr; + Sequence< PropertyValue > aProperties + { + comphelper::makePropertyValue(sNumberingType, static_cast<sal_uInt16>(nActType)), + comphelper::makePropertyValue(sValue, nNo) + }; + + try + { + return xFormatter->makeNumberingString( aProperties, rLocale ); + } + catch(const Exception&) + { + } + } + } + } + } + return OUString(); +} + +void SvxNumberType::dumpAsXml( xmlTextWriterPtr pWriter ) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumberType")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("NumType"), BAD_CAST(OString::number(nNumType).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SvxNumberFormat::SvxNumberFormat( SvxNumType eType ) + : SvxNumberType(eType), + eNumAdjust(SvxAdjust::Left), + nInclUpperLevels(1), + nStart(1), + cBullet(SVX_DEF_BULLET), + nBulletRelSize(100), + nBulletColor(COL_BLACK), + mePositionAndSpaceMode( LABEL_WIDTH_AND_POSITION ), + nFirstLineOffset(0), + nAbsLSpace(0), + nCharTextDistance(0), + meLabelFollowedBy( LISTTAB ), + mnListtabPos( 0 ), + mnFirstLineIndent( 0 ), + mnIndentAt( 0 ), + eVertOrient(text::VertOrientation::NONE) +{ +} + +SvxNumberFormat::SvxNumberFormat(const SvxNumberFormat& rFormat) : + SvxNumberType(rFormat), + mePositionAndSpaceMode( rFormat.mePositionAndSpaceMode ) +{ + *this = rFormat; +} + +SvxNumberFormat::SvxNumberFormat( SvStream &rStream ) + : nStart(0) + , nBulletRelSize(100) + , nFirstLineOffset(0) + , nAbsLSpace(0) + , nCharTextDistance(0) +{ + sal_uInt16 nTmp16(0); + sal_Int32 nTmp32(0); + rStream.ReadUInt16( nTmp16 ); // Version number + + rStream.ReadUInt16( nTmp16 ); SetNumberingType( static_cast<SvxNumType>(nTmp16) ); + rStream.ReadUInt16( nTmp16 ); eNumAdjust = static_cast<SvxAdjust>(nTmp16); + rStream.ReadUInt16( nTmp16 ); nInclUpperLevels = nTmp16; + rStream.ReadUInt16( nStart ); + rStream.ReadUInt16( nTmp16 ); cBullet = static_cast<sal_Unicode>(nTmp16); + + sal_Int16 temp = 0; + rStream.ReadInt16( temp ); + nFirstLineOffset = temp; + temp = 0; + rStream.ReadInt16( temp ); + nAbsLSpace = temp; + rStream.SeekRel(2); //skip old now unused nLSpace; + + rStream.ReadInt16( nCharTextDistance ); + + sPrefix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + sSuffix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + sCharStyleName = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() ); + + sal_uInt16 hasGraphicBrush = 0; + rStream.ReadUInt16( hasGraphicBrush ); + if ( hasGraphicBrush ) + { + pGraphicBrush.reset(new SvxBrushItem(SID_ATTR_BRUSH)); + legacy::SvxBrush::Create(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION); + } + else pGraphicBrush = nullptr; + rStream.ReadUInt16( nTmp16 ); eVertOrient = nTmp16; + + sal_uInt16 hasBulletFont = 0; + rStream.ReadUInt16( hasBulletFont ); + if ( hasBulletFont ) + { + pBulletFont.emplace(); + ReadFont( rStream, *pBulletFont ); + } + else pBulletFont.reset(); + + tools::GenericTypeSerializer aSerializer(rStream); + aSerializer.readSize(aGraphicSize); + aSerializer.readColor(nBulletColor); + + rStream.ReadUInt16( nBulletRelSize ); + rStream.ReadUInt16( nTmp16 ); SetShowSymbol( nTmp16 != 0 ); + + rStream.ReadUInt16( nTmp16 ); mePositionAndSpaceMode = static_cast<SvxNumPositionAndSpaceMode>(nTmp16); + rStream.ReadUInt16( nTmp16 ); meLabelFollowedBy = static_cast<LabelFollowedBy>(nTmp16); + rStream.ReadInt32( nTmp32 ); mnListtabPos = nTmp32; + rStream.ReadInt32( nTmp32 ); mnFirstLineIndent = nTmp32; + rStream.ReadInt32( nTmp32 ); mnIndentAt = nTmp32; +} + +SvxNumberFormat::~SvxNumberFormat() +{ +} + +void SvxNumberFormat::Store(SvStream &rStream, FontToSubsFontConverter pConverter) +{ + if(pConverter && pBulletFont) + { + cBullet = ConvertFontToSubsFontChar(pConverter, cBullet); + OUString sFontName = GetFontToSubsFontName(pConverter); + pBulletFont->SetFamilyName(sFontName); + } + + tools::GenericTypeSerializer aSerializer(rStream); + + rStream.WriteUInt16( NUMITEM_VERSION_04 ); + + rStream.WriteUInt16( GetNumberingType() ); + rStream.WriteUInt16( static_cast<sal_uInt16>(eNumAdjust) ); + rStream.WriteUInt16( nInclUpperLevels ); + rStream.WriteUInt16( nStart ); + rStream.WriteUInt16( cBullet ); + + rStream.WriteInt16( + sal_Int16(std::clamp<sal_Int32>(nFirstLineOffset, SAL_MIN_INT16, SAL_MAX_INT16)) ); + //TODO: better way to handle out-of-bounds value? + rStream.WriteInt16( + sal_Int16(std::clamp<sal_Int32>(nAbsLSpace, SAL_MIN_INT16, SAL_MAX_INT16)) ); + //TODO: better way to handle out-of-bounds value? + rStream.WriteInt16( 0 ); // write a dummy for old now unused nLSpace + + rStream.WriteInt16( nCharTextDistance ); + rtl_TextEncoding eEnc = osl_getThreadTextEncoding(); + rStream.WriteUniOrByteString(sPrefix, eEnc); + rStream.WriteUniOrByteString(sSuffix, eEnc); + rStream.WriteUniOrByteString(sCharStyleName, eEnc); + if(pGraphicBrush) + { + rStream.WriteUInt16( 1 ); + + // in SD or SI force bullet itself to be stored, + // for that purpose throw away link when link and graphic + // are present, so Brush save is forced + if(!pGraphicBrush->GetGraphicLink().isEmpty() && pGraphicBrush->GetGraphic()) + { + pGraphicBrush->SetGraphicLink(""); + } + + legacy::SvxBrush::Store(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION); + } + else + rStream.WriteUInt16( 0 ); + + rStream.WriteUInt16( eVertOrient ); + if(pBulletFont) + { + rStream.WriteUInt16( 1 ); + WriteFont( rStream, *pBulletFont ); + } + else + rStream.WriteUInt16( 0 ); + + aSerializer.writeSize(aGraphicSize); + + Color nTempColor = nBulletColor; + if(COL_AUTO == nBulletColor) + nTempColor = COL_BLACK; + + aSerializer.writeColor(nTempColor); + rStream.WriteUInt16( nBulletRelSize ); + rStream.WriteUInt16( sal_uInt16(IsShowSymbol()) ); + + rStream.WriteUInt16( mePositionAndSpaceMode ); + rStream.WriteUInt16( meLabelFollowedBy ); + rStream.WriteInt32( mnListtabPos ); + rStream.WriteInt32( mnFirstLineIndent ); + rStream.WriteInt32( mnIndentAt ); +} + +SvxNumberFormat& SvxNumberFormat::operator=( const SvxNumberFormat& rFormat ) +{ + if (& rFormat == this) { return *this; } + + SvxNumberType::SetNumberingType(rFormat.GetNumberingType()); + eNumAdjust = rFormat.eNumAdjust ; + nInclUpperLevels = rFormat.nInclUpperLevels ; + nStart = rFormat.nStart ; + cBullet = rFormat.cBullet ; + mePositionAndSpaceMode = rFormat.mePositionAndSpaceMode; + nFirstLineOffset = rFormat.nFirstLineOffset; + nAbsLSpace = rFormat.nAbsLSpace ; + nCharTextDistance = rFormat.nCharTextDistance ; + meLabelFollowedBy = rFormat.meLabelFollowedBy; + mnListtabPos = rFormat.mnListtabPos; + mnFirstLineIndent = rFormat.mnFirstLineIndent; + mnIndentAt = rFormat.mnIndentAt; + eVertOrient = rFormat.eVertOrient; + sPrefix = rFormat.sPrefix; + sSuffix = rFormat.sSuffix; + sListFormat = rFormat.sListFormat; + aGraphicSize = rFormat.aGraphicSize ; + nBulletColor = rFormat.nBulletColor ; + nBulletRelSize = rFormat.nBulletRelSize; + SetShowSymbol(rFormat.IsShowSymbol()); + sCharStyleName = rFormat.sCharStyleName; + pGraphicBrush.reset(); + if(rFormat.pGraphicBrush) + { + pGraphicBrush.reset( new SvxBrushItem(*rFormat.pGraphicBrush) ); + } + pBulletFont.reset(); + if(rFormat.pBulletFont) + pBulletFont = *rFormat.pBulletFont; + mbIsLegal = rFormat.mbIsLegal; + return *this; +} + +bool SvxNumberFormat::operator==( const SvxNumberFormat& rFormat) const +{ + if( GetNumberingType() != rFormat.GetNumberingType() || + eNumAdjust != rFormat.eNumAdjust || + nInclUpperLevels != rFormat.nInclUpperLevels || + nStart != rFormat.nStart || + cBullet != rFormat.cBullet || + mePositionAndSpaceMode != rFormat.mePositionAndSpaceMode || + nFirstLineOffset != rFormat.nFirstLineOffset || + nAbsLSpace != rFormat.nAbsLSpace || + nCharTextDistance != rFormat.nCharTextDistance || + meLabelFollowedBy != rFormat.meLabelFollowedBy || + mnListtabPos != rFormat.mnListtabPos || + mnFirstLineIndent != rFormat.mnFirstLineIndent || + mnIndentAt != rFormat.mnIndentAt || + eVertOrient != rFormat.eVertOrient || + sPrefix != rFormat.sPrefix || + sSuffix != rFormat.sSuffix || + sListFormat != rFormat.sListFormat || + aGraphicSize != rFormat.aGraphicSize || + nBulletColor != rFormat.nBulletColor || + nBulletRelSize != rFormat.nBulletRelSize || + IsShowSymbol() != rFormat.IsShowSymbol() || + sCharStyleName != rFormat.sCharStyleName || + mbIsLegal != rFormat.mbIsLegal + ) + return false; + if ( + (pGraphicBrush && !rFormat.pGraphicBrush) || + (!pGraphicBrush && rFormat.pGraphicBrush) || + (pGraphicBrush && *pGraphicBrush != *rFormat.pGraphicBrush) + ) + { + return false; + } + if ( + (pBulletFont && !rFormat.pBulletFont) || + (!pBulletFont && rFormat.pBulletFont) || + (pBulletFont && *pBulletFont != *rFormat.pBulletFont) + ) + { + return false; + } + return true; +} + +void SvxNumberFormat::SetGraphicBrush( const SvxBrushItem* pBrushItem, + const Size* pSize, const sal_Int16* pOrient) +{ + if (!pBrushItem) + pGraphicBrush.reset(); + else if ( !pGraphicBrush || (*pBrushItem != *pGraphicBrush) ) + pGraphicBrush.reset(pBrushItem->Clone()); + + if(pOrient) + eVertOrient = *pOrient; + else + eVertOrient = text::VertOrientation::NONE; + if(pSize) + aGraphicSize = *pSize; + else + { + aGraphicSize.setWidth(0); + aGraphicSize.setHeight(0); + } +} + +void SvxNumberFormat::SetGraphic( const OUString& rName ) +{ + if( pGraphicBrush && pGraphicBrush->GetGraphicLink() == rName ) + return ; + + pGraphicBrush.reset( new SvxBrushItem( rName, "", GPOS_AREA, 0 ) ); + if( eVertOrient == text::VertOrientation::NONE ) + eVertOrient = text::VertOrientation::TOP; + + aGraphicSize.setWidth(0); + aGraphicSize.setHeight(0); +} + +sal_Int16 SvxNumberFormat::GetVertOrient() const +{ + return eVertOrient; +} + +void SvxNumberFormat::SetBulletFont(const vcl::Font* pFont) +{ + if (pFont) + pBulletFont = *pFont; + else + pBulletFont.reset(); +} + +void SvxNumberFormat::SetPositionAndSpaceMode( SvxNumPositionAndSpaceMode ePositionAndSpaceMode ) +{ + mePositionAndSpaceMode = ePositionAndSpaceMode; +} + +sal_Int32 SvxNumberFormat::GetAbsLSpace() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION + ? nAbsLSpace + : static_cast<sal_Int32>( GetFirstLineIndent() + GetIndentAt() ); +} +sal_Int32 SvxNumberFormat::GetFirstLineOffset() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION + ? nFirstLineOffset + : static_cast<sal_Int32>( GetFirstLineIndent() ); +} +short SvxNumberFormat::GetCharTextDistance() const +{ + return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION ? nCharTextDistance : 0; +} + +void SvxNumberFormat::SetLabelFollowedBy( const LabelFollowedBy eLabelFollowedBy ) +{ + meLabelFollowedBy = eLabelFollowedBy; +} + +OUString SvxNumberFormat::GetLabelFollowedByAsString() const +{ + switch (meLabelFollowedBy) + { + case LISTTAB: + return "\t"; + case SPACE: + return " "; + case NEWLINE: + return "\n"; + case NOTHING: + // intentionally left blank. + return OUString(); + default: + SAL_WARN("editeng", "Unknown SvxNumberFormat::GetLabelFollowedBy() return value"); + assert(false); + } + return OUString(); +} + +void SvxNumberFormat::SetListtabPos( const tools::Long nListtabPos ) +{ + mnListtabPos = nListtabPos; +} +void SvxNumberFormat::SetFirstLineIndent( const tools::Long nFirstLineIndent ) +{ + mnFirstLineIndent = nFirstLineIndent; +} +void SvxNumberFormat::SetIndentAt( const tools::Long nIndentAt ) +{ + mnIndentAt = nIndentAt; +} + +Size SvxNumberFormat::GetGraphicSizeMM100(const Graphic* pGraphic) +{ + const MapMode aMapMM100( MapUnit::Map100thMM ); + const Size& rSize = pGraphic->GetPrefSize(); + Size aRetSize; + if ( pGraphic->GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel ) + { + OutputDevice* pOutDev = Application::GetDefaultDevice(); + MapMode aOldMap( pOutDev->GetMapMode() ); + pOutDev->SetMapMode( aMapMM100 ); + aRetSize = pOutDev->PixelToLogic( rSize ); + pOutDev->SetMapMode( aOldMap ); + } + else + aRetSize = OutputDevice::LogicToLogic( rSize, pGraphic->GetPrefMapMode(), aMapMM100 ); + return aRetSize; +} + +OUString SvxNumberFormat::CreateRomanString( sal_Int32 nNo, bool bUpper ) +{ + OUStringBuffer sRet; + + constexpr char romans[][13] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + constexpr sal_Int32 values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + + for (size_t i = 0; i < std::size(romans); ++i) + { + while(nNo - values[i] >= 0) + { + sRet.appendAscii(romans[i]); + nNo -= values[i]; + } + } + + return bUpper ? sRet.makeStringAndClear() + : sRet.makeStringAndClear().toAsciiLowerCase(); +} + +void SvxNumberFormat::SetPrefix(const OUString& rSet) +{ + // ListFormat manages the prefix. If badly changed via this function, sListFormat is invalidated + if (sListFormat && rSet.getLength() != sPrefix.getLength()) + sListFormat.reset(); + + sPrefix = rSet; +} + +void SvxNumberFormat::SetSuffix(const OUString& rSet) +{ + // ListFormat manages the suffix. If badly changed via this function, sListFormat is invalidated + if (sListFormat && rSet.getLength() != sSuffix.getLength()) + sListFormat.reset(); + + sSuffix = rSet; +} + +void SvxNumberFormat::SetListFormat(const OUString& rPrefix, const OUString& rSuffix, int nLevel) +{ + sPrefix = rPrefix; + sSuffix = rSuffix; + + // Generate list format + sListFormat = std::make_optional(sPrefix); + + for (int i = 1; i <= nInclUpperLevels; i++) + { + int nLevelId = nLevel - nInclUpperLevels + i; + if (nLevelId < 0) + // There can be cases with current level 1, but request to show 10 upper levels. Trim it + continue; + + *sListFormat += "%"; + *sListFormat += OUString::number(nLevelId + 1); + *sListFormat += "%"; + if (i != nInclUpperLevels) + *sListFormat += "."; // Default separator for older ODT + } + + *sListFormat += sSuffix; +} + +void SvxNumberFormat::SetListFormat(std::optional<OUString> oSet) +{ + sPrefix.clear(); + sSuffix.clear(); + + sListFormat = oSet; + + if (!oSet.has_value()) + { + return; + } + + // For backward compatibility and UI we should create something looking like + // a prefix, suffix and included levels also. This is not possible in general case + // since level format string is much more flexible. But for most cases is okay + sal_Int32 nFirstReplacement = sListFormat->indexOf('%'); + sal_Int32 nLastReplacement = sListFormat->lastIndexOf('%') + 1; + if (nFirstReplacement > 0) + // Everything before first '%' will be prefix + sPrefix = sListFormat->copy(0, nFirstReplacement); + if (nLastReplacement >= 0 && nLastReplacement < sListFormat->getLength()) + // Everything beyond last '%' is a suffix + sSuffix = sListFormat->copy(nLastReplacement); + + sal_uInt8 nPercents = 0; + for (sal_Int32 i = 0; i < sListFormat->getLength(); i++) + { + if ((*sListFormat)[i] == '%') + nPercents++; + } + nInclUpperLevels = nPercents/2; + if (nInclUpperLevels < 1) + { + // There should be always at least one level. This will be not required + // in future (when we get rid of prefix/suffix), but nowadays there + // are too many conversions "list format" <-> "prefix/suffix/inclUpperLevel" + nInclUpperLevels = 1; + } +} + +OUString SvxNumberFormat::GetListFormat(bool bIncludePrefixSuffix /*= true*/) const +{ + assert(sListFormat.has_value()); + + if (bIncludePrefixSuffix) + return *sListFormat; + + // Strip prefix & suffix from string + return sListFormat->copy(sPrefix.getLength(), sListFormat->getLength() - sPrefix.getLength() - sSuffix.getLength()); +} + +OUString SvxNumberFormat::GetCharFormatName()const +{ + return sCharStyleName; +} + +sal_Int32 SvxNumRule::nRefCount = 0; +static SvxNumberFormat* pStdNumFmt = nullptr; +static SvxNumberFormat* pStdOutlineNumFmt = nullptr; +SvxNumRule::SvxNumRule( SvxNumRuleFlags nFeatures, + sal_uInt16 nLevels, + bool bCont, + SvxNumRuleType eType, + SvxNumberFormat::SvxNumPositionAndSpaceMode + eDefaultNumberFormatPositionAndSpaceMode ) + : nLevelCount(nLevels), + nFeatureFlags(nFeatures), + eNumberingType(eType), + bContinuousNumbering(bCont) +{ + ++nRefCount; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(i < nLevels) + { + aFmts[i].reset( new SvxNumberFormat(SVX_NUM_CHARS_UPPER_LETTER) ); + // It is a distinction between writer and draw + if(nFeatures & SvxNumRuleFlags::CONTINUOUS) + { + if ( eDefaultNumberFormatPositionAndSpaceMode == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + aFmts[i]->SetAbsLSpace(o3tl::toTwips(DEF_WRITER_LSPACE * (i+1), o3tl::Length::mm100)); + aFmts[i]->SetFirstLineOffset(o3tl::toTwips(-DEF_WRITER_LSPACE, o3tl::Length::mm100)); + } + else if ( eDefaultNumberFormatPositionAndSpaceMode == + SvxNumberFormat::LABEL_ALIGNMENT ) + { + // first line indent of general numbering in inch: -0,25 inch + constexpr tools::Long cFirstLineIndent = o3tl::toTwips(-0.25, o3tl::Length::in); + // indent values of general numbering in inch: + // 0,5 0,75 1,0 1,25 1,5 + // 1,75 2,0 2,25 2,5 2,75 + constexpr tools::Long cIndentAt = o3tl::toTwips(0.25, o3tl::Length::in); + aFmts[i]->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT ); + aFmts[i]->SetLabelFollowedBy( SvxNumberFormat::LISTTAB ); + aFmts[i]->SetListtabPos( cIndentAt * (i+2) ); + aFmts[i]->SetFirstLineIndent( cFirstLineIndent ); + aFmts[i]->SetIndentAt( cIndentAt * (i+2) ); + } + } + else + { + aFmts[i]->SetAbsLSpace( DEF_DRAW_LSPACE * i ); + } + } + else + aFmts[i] = nullptr; + aFmtsSet[i] = false; + } +} + +SvxNumRule::SvxNumRule(const SvxNumRule& rCopy) +{ + ++nRefCount; + nLevelCount = rCopy.nLevelCount ; + nFeatureFlags = rCopy.nFeatureFlags ; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) ); + else + aFmts[i].reset(); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } +} + +SvxNumRule::SvxNumRule(SvxNumRule&& rCopy) noexcept +{ + ++nRefCount; + nLevelCount = rCopy.nLevelCount ; + nFeatureFlags = rCopy.nFeatureFlags ; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i] = std::move(rCopy.aFmts[i]); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } +} + +SvxNumRule::SvxNumRule( SvStream &rStream ) + : nLevelCount(0) +{ + sal_uInt16 nTmp16(0); + rStream.ReadUInt16( nTmp16 ); // NUM_ITEM_VERSION + rStream.ReadUInt16( nLevelCount ); + + if (nLevelCount > SVX_MAX_NUM) + { + SAL_WARN("editeng", "nLevelCount: " << nLevelCount << " greater than max of: " << SVX_MAX_NUM); + nLevelCount = SVX_MAX_NUM; + } + + // first nFeatureFlags of old Versions + rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16); + rStream.ReadUInt16( nTmp16 ); bContinuousNumbering = nTmp16; + rStream.ReadUInt16( nTmp16 ); eNumberingType = static_cast<SvxNumRuleType>(nTmp16); + + for (sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + rStream.ReadUInt16( nTmp16 ); + bool hasNumberingFormat = nTmp16 & 1; + aFmtsSet[i] = nTmp16 & 2; // fdo#68648 reset flag + if ( hasNumberingFormat ){ + aFmts[i].reset( new SvxNumberFormat( rStream ) ); + } + else + { + aFmts[i].reset(); + aFmtsSet[i] = false; // actually only false is valid + } + } + //second nFeatureFlags for new versions + rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16); +} + +void SvxNumRule::Store( SvStream &rStream ) +{ + rStream.WriteUInt16( NUMITEM_VERSION_03 ); + rStream.WriteUInt16( nLevelCount ); + //first save of nFeatureFlags for old versions + rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) ); + rStream.WriteUInt16( sal_uInt16(bContinuousNumbering) ); + rStream.WriteUInt16( static_cast<sal_uInt16>(eNumberingType) ); + + FontToSubsFontConverter pConverter = nullptr; + bool bConvertBulletFont = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_50 ) && ( rStream.GetVersion() ); + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + sal_uInt16 nSetFlag(aFmtsSet[i] ? 2 : 0); // fdo#68648 store that too + if(aFmts[i]) + { + rStream.WriteUInt16( 1 | nSetFlag ); + if(bConvertBulletFont && aFmts[i]->GetBulletFont()) + { + if(!pConverter) + pConverter = + CreateFontToSubsFontConverter(aFmts[i]->GetBulletFont()->GetFamilyName(), + FontToSubsFontFlags::EXPORT); + } + aFmts[i]->Store(rStream, pConverter); + } + else + rStream.WriteUInt16( 0 | nSetFlag ); + } + //second save of nFeatureFlags for new versions + rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) ); +} + +void SvxNumRule::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumRule")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("levelCount"), BAD_CAST(OString::number(nLevelCount).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("continuousNumbering"), BAD_CAST(OString::boolean(bContinuousNumbering).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("numberingType"), BAD_CAST(OString::number(static_cast<int>(eNumberingType)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("featureFlags"), BAD_CAST(OString::number(static_cast<int>(nFeatureFlags)).getStr())); + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(aFmts[i]) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("aFmts")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("i"), BAD_CAST(OString::number(i).getStr())); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", aFmts[i].get()); + (void)xmlTextWriterEndElement(pWriter); + } + } + (void)xmlTextWriterEndElement(pWriter); +} + + +SvxNumRule::~SvxNumRule() +{ + if(!--nRefCount) + { + delete pStdNumFmt; + pStdNumFmt = nullptr; + delete pStdOutlineNumFmt; + pStdOutlineNumFmt = nullptr; + } +} + +SvxNumRule& SvxNumRule::operator=( const SvxNumRule& rCopy ) +{ + if (this != &rCopy) + { + nLevelCount = rCopy.nLevelCount; + nFeatureFlags = rCopy.nFeatureFlags; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) ); + else + aFmts[i].reset(); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } + } + return *this; +} + +SvxNumRule& SvxNumRule::operator=( SvxNumRule&& rCopy ) noexcept +{ + if (this != &rCopy) + { + nLevelCount = rCopy.nLevelCount; + nFeatureFlags = rCopy.nFeatureFlags; + bContinuousNumbering = rCopy.bContinuousNumbering; + eNumberingType = rCopy.eNumberingType; + for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++) + { + if(rCopy.aFmts[i]) + aFmts[i] = std::move(rCopy.aFmts[i]); + aFmtsSet[i] = rCopy.aFmtsSet[i]; + } + } + return *this; +} + +bool SvxNumRule::operator==( const SvxNumRule& rCopy) const +{ + if(nLevelCount != rCopy.nLevelCount || + nFeatureFlags != rCopy.nFeatureFlags || + bContinuousNumbering != rCopy.bContinuousNumbering || + eNumberingType != rCopy.eNumberingType) + return false; + for(sal_uInt16 i = 0; i < nLevelCount; i++) + { + if ( + (aFmtsSet[i] != rCopy.aFmtsSet[i]) || + (!aFmts[i] && rCopy.aFmts[i]) || + (aFmts[i] && !rCopy.aFmts[i]) || + (aFmts[i] && *aFmts[i] != *rCopy.aFmts[i]) + ) + { + return false; + } + } + return true; +} + +const SvxNumberFormat* SvxNumRule::Get(sal_uInt16 nLevel)const +{ + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + if( nLevel < SVX_MAX_NUM ) + return aFmtsSet[nLevel] ? aFmts[nLevel].get() : nullptr; + else + return nullptr; +} + +const SvxNumberFormat& SvxNumRule::GetLevel(sal_uInt16 nLevel)const +{ + if(!pStdNumFmt) + { + pStdNumFmt = new SvxNumberFormat(SVX_NUM_ARABIC); + pStdOutlineNumFmt = new SvxNumberFormat(SVX_NUM_NUMBER_NONE); + } + + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + + return ( ( nLevel < SVX_MAX_NUM ) && aFmts[nLevel] ) ? + *aFmts[nLevel] : eNumberingType == SvxNumRuleType::NUMBERING ? + *pStdNumFmt : *pStdOutlineNumFmt; +} + +void SvxNumRule::SetLevel( sal_uInt16 i, const SvxNumberFormat& rNumFmt, bool bIsValid ) +{ + DBG_ASSERT(i < SVX_MAX_NUM, "Wrong Level" ); + + if( i >= SVX_MAX_NUM ) + return; + + bool bReplace = !aFmtsSet[i]; + if (!bReplace) + { + const SvxNumberFormat *pFmt = Get(i); + bReplace = pFmt == nullptr || rNumFmt != *pFmt; + } + + if (bReplace) + { + aFmts[i].reset( new SvxNumberFormat(rNumFmt) ); + aFmtsSet[i] = bIsValid; + } +} + +void SvxNumRule::SetLevel(sal_uInt16 nLevel, const SvxNumberFormat* pFmt) +{ + DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" ); + + if( nLevel < SVX_MAX_NUM ) + { + aFmtsSet[nLevel] = nullptr != pFmt; + if(pFmt) + SetLevel(nLevel, *pFmt); + else + { + aFmts[nLevel].reset(); + } + } +} + +OUString SvxNumRule::MakeNumString( const SvxNodeNum& rNum ) const +{ + OUStringBuffer aStr; + if( SVX_NO_NUM > rNum.GetLevel() && !( SVX_NO_NUMLEVEL & rNum.GetLevel() ) ) + { + const SvxNumberFormat& rMyNFmt = GetLevel( rNum.GetLevel() ); + aStr.append(rMyNFmt.GetPrefix()); + if( SVX_NUM_NUMBER_NONE != rMyNFmt.GetNumberingType() ) + { + sal_uInt8 i = rNum.GetLevel(); + + if( !IsContinuousNumbering() && + 1 < rMyNFmt.GetIncludeUpperLevels() ) // only on own level? + { + sal_uInt8 n = rMyNFmt.GetIncludeUpperLevels(); + if( 1 < n ) + { + if( i+1 >= n ) + i -= n - 1; + else + i = 0; + } + } + + for( ; i <= rNum.GetLevel(); ++i ) + { + const SvxNumberFormat& rNFmt = GetLevel( i ); + if( SVX_NUM_NUMBER_NONE == rNFmt.GetNumberingType() ) + { + continue; + } + + bool bDot = true; + if( rNum.GetLevelVal()[ i ] ) + { + if(SVX_NUM_BITMAP != rNFmt.GetNumberingType()) + { + const LanguageTag& rLang = Application::GetSettings().GetLanguageTag(); + aStr.append(rNFmt.GetNumStr( rNum.GetLevelVal()[ i ], rLang.getLocale(), rMyNFmt.GetIsLegal() )); + } + else + bDot = false; + } + else + aStr.append("0"); // all 0-levels are a 0 + if( i != rNum.GetLevel() && bDot) + aStr.append("."); + } + } + + aStr.append(rMyNFmt.GetSuffix()); + } + return aStr.makeStringAndClear(); +} + +// changes linked to embedded bitmaps +void SvxNumRule::UnLinkGraphics() +{ + for(sal_uInt16 i = 0; i < GetLevelCount(); i++) + { + SvxNumberFormat aFmt(GetLevel(i)); + const SvxBrushItem* pBrush = aFmt.GetBrush(); + if(SVX_NUM_BITMAP == aFmt.GetNumberingType()) + { + if(pBrush && !pBrush->GetGraphicLink().isEmpty()) + { + const Graphic* pGraphic = pBrush->GetGraphic(); + if (pGraphic) + { + SvxBrushItem aTempItem(*pBrush); + aTempItem.SetGraphicLink(""); + aTempItem.SetGraphic(*pGraphic); + sal_Int16 eOrient = aFmt.GetVertOrient(); + aFmt.SetGraphicBrush( &aTempItem, &aFmt.GetGraphicSize(), &eOrient ); + } + } + } + else if((SVX_NUM_BITMAP|LINK_TOKEN) == static_cast<int>(aFmt.GetNumberingType())) + aFmt.SetNumberingType(SVX_NUM_BITMAP); + SetLevel(i, aFmt); + } +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule) : + SfxPoolItem(SID_ATTR_NUMBERING_RULE), + maNumRule(rRule) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule) : + SfxPoolItem(SID_ATTR_NUMBERING_RULE), + maNumRule(std::move(rRule)) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule, sal_uInt16 _nWhich ) : + SfxPoolItem(_nWhich), + maNumRule(rRule) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule, sal_uInt16 _nWhich ) : + SfxPoolItem(_nWhich), + maNumRule(std::move(rRule)) +{ +} + +SvxNumBulletItem::SvxNumBulletItem(const SvxNumBulletItem& rCopy) : + SfxPoolItem(rCopy), + maNumRule(rCopy.maNumRule) +{ +} + +SvxNumBulletItem::~SvxNumBulletItem() +{ +} + +bool SvxNumBulletItem::operator==( const SfxPoolItem& rCopy) const +{ + return SfxPoolItem::operator==(rCopy) && + maNumRule == static_cast<const SvxNumBulletItem&>(rCopy).maNumRule; +} + +SvxNumBulletItem* SvxNumBulletItem::Clone( SfxItemPool * ) const +{ + return new SvxNumBulletItem(*this); +} + +bool SvxNumBulletItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= SvxCreateNumRule( maNumRule ); + return true; +} + +bool SvxNumBulletItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + uno::Reference< container::XIndexReplace > xRule; + if( rVal >>= xRule ) + { + try + { + SvxNumRule aNewRule( SvxGetNumRule( xRule ) ); + if( aNewRule.GetLevelCount() != maNumRule.GetLevelCount() || + aNewRule.GetNumRuleType() != maNumRule.GetNumRuleType() ) + { + aNewRule = SvxConvertNumRule( aNewRule, maNumRule.GetLevelCount(), maNumRule.GetNumRuleType() ); + } + maNumRule = std::move( aNewRule ); + return true; + } + catch(const lang::IllegalArgumentException&) + { + } + } + return false; +} + +void SvxNumBulletItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumBulletItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + maNumRule.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +SvxNumRule SvxConvertNumRule( const SvxNumRule& rRule, sal_uInt16 nLevels, SvxNumRuleType eType ) +{ + const sal_uInt16 nSrcLevels = rRule.GetLevelCount(); + SvxNumRule aNewRule(rRule.GetFeatureFlags(), nLevels, rRule.IsContinuousNumbering(), eType ); + + for( sal_uInt16 nLevel = 0; (nLevel < nLevels) && (nLevel < nSrcLevels); nLevel++ ) + aNewRule.SetLevel( nLevel, rRule.GetLevel( nLevel ) ); + + return aNewRule; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/optitems.cxx b/editeng/source/items/optitems.cxx new file mode 100644 index 0000000000..254da79d91 --- /dev/null +++ b/editeng/source/items/optitems.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <editeng/optitems.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> + + +// class SfxHyphenRegionItem ----------------------------------------------- + +SfxHyphenRegionItem::SfxHyphenRegionItem( const sal_uInt16 nId ) : + + SfxPoolItem( nId ) +{ + nMinLead = nMinTrail = 0; +} + +bool SfxHyphenRegionItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return ( ( static_cast<const SfxHyphenRegionItem&>( rAttr ).nMinLead == nMinLead ) && + ( static_cast<const SfxHyphenRegionItem&>( rAttr ).nMinTrail == nMinTrail ) ); +} + +SfxHyphenRegionItem* SfxHyphenRegionItem::Clone( SfxItemPool* ) const +{ + return new SfxHyphenRegionItem( *this ); +} + +bool SfxHyphenRegionItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit , + MapUnit , + OUString& rText, + const IntlWrapper& +) const +{ + rText += EditResId(RID_SVXITEMS_HYPHEN_MINLEAD).replaceAll("%1", OUString::number(nMinLead)) + + "," + + EditResId(RID_SVXITEMS_HYPHEN_MINTRAIL).replaceAll("%1", OUString::number(nMinTrail)); + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/paperinf.cxx b/editeng/source/items/paperinf.cxx new file mode 100644 index 0000000000..86401e63f3 --- /dev/null +++ b/editeng/source/items/paperinf.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <vcl/print.hxx> +#include <editeng/paperinf.hxx> + +/*-------------------------------------------------------------------- + Description: Is the printer valid + --------------------------------------------------------------------*/ + +static bool IsValidPrinter( const Printer* pPtr ) +{ + return !pPtr->GetName().isEmpty(); +} + + +Size SvxPaperInfo::GetPaperSize( Paper ePaper, MapUnit eUnit ) +{ + PaperInfo aInfo(ePaper); + Size aRet(aInfo.getWidth(), aInfo.getHeight()); // in 100thMM + return eUnit == MapUnit::Map100thMM + ? aRet + : OutputDevice::LogicToLogic(aRet, MapMode(MapUnit::Map100thMM), MapMode(eUnit)); +} + +/*------------------------------------------------------------------------ + Description: Return the paper size of the printer, aligned to our + own sizes. If no Printer is set in the system, A4 portrait + will be delivered as the default paper size. +------------------------------------------------------------------------*/ + +//Is this method may be confused about the units it returns ? +//Always returns TWIPS for known paper sizes or on failure. +//But in the case of PAPER_USER paper and with a Printer with a mapmode set +//will return in those printer units ? +Size SvxPaperInfo::GetPaperSize( const Printer* pPrinter ) +{ + if ( !IsValidPrinter(pPrinter) ) + return GetPaperSize( PAPER_A4 ); + const Paper ePaper = pPrinter->GetPaper(); + + if ( ePaper == PAPER_USER ) + { + // Orientation not take into account, as the right size has + // been already set by SV + Size aPaperSize = pPrinter->GetPaperSize(); + const Size aInvalidSize; + + if ( aPaperSize == aInvalidSize ) + return GetPaperSize(PAPER_A4); + const MapMode& aMap1 = pPrinter->GetMapMode(); + MapMode aMap2; + + if ( aMap1 == aMap2 ) + aPaperSize = + pPrinter->PixelToLogic( aPaperSize, MapMode( MapUnit::MapTwip ) ); + return aPaperSize; + } + + const Orientation eOrient = pPrinter->GetOrientation(); + Size aSize( GetPaperSize( ePaper ) ); + // for Landscape exchange the pages, has already been done by SV + if ( eOrient == Orientation::Landscape ) + Swap( aSize ); + return aSize; +} + + +Paper SvxPaperInfo::GetSvxPaper( const Size &rSize, MapUnit eUnit ) +{ + Size aSize(eUnit == MapUnit::Map100thMM ? rSize : OutputDevice::LogicToLogic(rSize, MapMode(eUnit), MapMode(MapUnit::Map100thMM))); + PaperInfo aInfo(aSize.Width(), aSize.Height()); + aInfo.doSloppyFit(); + return aInfo.getPaper(); +} + + +tools::Long SvxPaperInfo::GetSloppyPaperDimension( tools::Long nSize ) +{ + nSize = o3tl::convert(nSize, o3tl::Length::twip, o3tl::Length::mm100); + nSize = PaperInfo::sloppyFitPageDimension(nSize); + return o3tl::convert(nSize, o3tl::Length::mm100, o3tl::Length::twip); +} + + +Size SvxPaperInfo::GetDefaultPaperSize( MapUnit eUnit ) +{ + PaperInfo aInfo(PaperInfo::getSystemDefaultPaper()); + Size aRet(aInfo.getWidth(), aInfo.getHeight()); + return eUnit == MapUnit::Map100thMM + ? aRet + : OutputDevice::LogicToLogic(aRet, MapMode(MapUnit::Map100thMM), MapMode(eUnit)); +} + +/*------------------------------------------------------------------------ + Description: String representation for the SV-defines of paper size +------------------------------------------------------------------------*/ + +OUString SvxPaperInfo::GetName( Paper ePaper ) +{ + return Printer::GetPaperName( ePaper ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/paraitem.cxx b/editeng/source/items/paraitem.cxx new file mode 100644 index 0000000000..e10c323bcd --- /dev/null +++ b/editeng/source/items/paraitem.cxx @@ -0,0 +1,1317 @@ +/* -*- 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 <com/sun/star/style/TabStop.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <libxml/xmlwriter.h> +#include <comphelper/extract.hxx> +#include <osl/diagnose.h> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> +#include <svl/itempool.hxx> +#include <svl/memberid.h> +#include <editeng/editrids.hrc> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/orphitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/pmdlitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/paravertalignitem.hxx> +#include <editeng/pgrditem.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <editeng/memberids.h> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> + +using namespace ::com::sun::star; + + +SfxPoolItem* SvxLineSpacingItem::CreateDefault() { return new SvxLineSpacingItem(LINE_SPACE_DEFAULT_HEIGHT, 0);} +SfxPoolItem* SvxAdjustItem::CreateDefault() { return new SvxAdjustItem(SvxAdjust::Left, 0);} +SfxPoolItem* SvxWidowsItem::CreateDefault() { return new SvxWidowsItem(0, 0);} +SfxPoolItem* SvxOrphansItem::CreateDefault() { return new SvxOrphansItem(0, 0);} +SfxPoolItem* SvxHyphenZoneItem::CreateDefault() { return new SvxHyphenZoneItem(false, 0);} +SfxPoolItem* SvxTabStopItem::CreateDefault() { return new SvxTabStopItem(0);} +SfxPoolItem* SvxFormatSplitItem::CreateDefault() { return new SvxFormatSplitItem(false, 0);} +SfxPoolItem* SvxPageModelItem::CreateDefault() { return new SvxPageModelItem(TypedWhichId<SvxPageModelItem>(0));} +SfxPoolItem* SvxParaVertAlignItem::CreateDefault() { return new SvxParaVertAlignItem(Align::Automatic, TypedWhichId<SvxParaVertAlignItem>(0));} + +namespace { + +enum class SvxSpecialLineSpace +{ + User, + OneLine, + OnePointFiveLines, + TwoLines, + End +}; + +} + +SvxLineSpacingItem::SvxLineSpacingItem( sal_uInt16 nHeight, const sal_uInt16 nId ) + : SfxEnumItemInterface( nId ) +{ + nPropLineSpace = 100; + nInterLineSpace = 0; + nLineHeight = nHeight; + eLineSpaceRule = SvxLineSpaceRule::Auto; + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; +} + + +bool SvxLineSpacingItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxLineSpacingItem& rLineSpace = static_cast<const SvxLineSpacingItem&>(rAttr); + return + // Same Linespacing Rule? + (eLineSpaceRule == rLineSpace.eLineSpaceRule) + // For maximum and minimum Linespacing be the size must coincide. + && (eLineSpaceRule == SvxLineSpaceRule::Auto || + nLineHeight == rLineSpace.nLineHeight) + // Same Linespacing Rule? + && ( eInterLineSpaceRule == rLineSpace.eInterLineSpaceRule ) + // Either set proportional or additive. + && (( eInterLineSpaceRule == SvxInterLineSpaceRule::Off) + || (eInterLineSpaceRule == SvxInterLineSpaceRule::Prop + && nPropLineSpace == rLineSpace.nPropLineSpace) + || (eInterLineSpaceRule == SvxInterLineSpaceRule::Fix + && (nInterLineSpace == rLineSpace.nInterLineSpace))); +} + +/* Who does still know why the LineSpacingItem is so complicated? + We can not use it for UNO since there are only two values: + - a sal_uInt16 for the mode + - a sal_uInt32 for all values (distance, height, rel. detail) +*/ +bool SvxLineSpacingItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + style::LineSpacing aLSp; + switch( eLineSpaceRule ) + { + case SvxLineSpaceRule::Auto: + if(eInterLineSpaceRule == SvxInterLineSpaceRule::Fix) + { + aLSp.Mode = style::LineSpacingMode::LEADING; + aLSp.Height = ( bConvert ? static_cast<short>(convertTwipToMm100(nInterLineSpace)) : nInterLineSpace); + } + else if(eInterLineSpaceRule == SvxInterLineSpaceRule::Off) + { + aLSp.Mode = style::LineSpacingMode::PROP; + aLSp.Height = 100; + } + else + { + aLSp.Mode = style::LineSpacingMode::PROP; + aLSp.Height = nPropLineSpace; + } + break; + case SvxLineSpaceRule::Fix : + case SvxLineSpaceRule::Min : + aLSp.Mode = eLineSpaceRule == SvxLineSpaceRule::Fix ? style::LineSpacingMode::FIX : style::LineSpacingMode::MINIMUM; + aLSp.Height = ( bConvert ? static_cast<short>(convertTwipToMm100(nLineHeight)) : nLineHeight ); + break; + default: + ;//prevent warning about SvxLineSpaceRule::End + } + + switch ( nMemberId ) + { + case 0 : rVal <<= aLSp; break; + case MID_LINESPACE : rVal <<= aLSp.Mode; break; + case MID_HEIGHT : rVal <<= aLSp.Height; break; + default: OSL_FAIL("Wrong MemberId!"); break; + } + + return true; +} + +bool SvxLineSpacingItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + + // fill with current data + style::LineSpacing aLSp; + uno::Any aAny; + bool bRet = QueryValue( aAny, bConvert ? CONVERT_TWIPS : 0 ) && ( aAny >>= aLSp ); + + // get new data + switch ( nMemberId ) + { + case 0 : bRet = (rVal >>= aLSp); break; + case MID_LINESPACE : bRet = (rVal >>= aLSp.Mode); break; + case MID_HEIGHT : bRet = (rVal >>= aLSp.Height); break; + default: OSL_FAIL("Wrong MemberId!"); break; + } + + if( bRet ) + { + nLineHeight = aLSp.Height; + switch( aLSp.Mode ) + { + case style::LineSpacingMode::LEADING: + { + eInterLineSpaceRule = SvxInterLineSpaceRule::Fix; + eLineSpaceRule = SvxLineSpaceRule::Auto; + nInterLineSpace = aLSp.Height; + if(bConvert) + nInterLineSpace = o3tl::toTwips(nInterLineSpace, o3tl::Length::mm100); + + } + break; + case style::LineSpacingMode::PROP: + { + eLineSpaceRule = SvxLineSpaceRule::Auto; + nPropLineSpace = aLSp.Height; + if(100 == aLSp.Height) + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; + else + eInterLineSpaceRule = SvxInterLineSpaceRule::Prop; + } + break; + case style::LineSpacingMode::FIX: + case style::LineSpacingMode::MINIMUM: + { + eInterLineSpaceRule = SvxInterLineSpaceRule::Off; + eLineSpaceRule = aLSp.Mode == style::LineSpacingMode::FIX ? SvxLineSpaceRule::Fix : SvxLineSpaceRule::Min; + nLineHeight = aLSp.Height; + if(bConvert) + nLineHeight = o3tl::toTwips(nLineHeight, o3tl::Length::mm100); + } + break; + } + } + + return bRet; +} + +SvxLineSpacingItem* SvxLineSpacingItem::Clone( SfxItemPool * ) const +{ + return new SvxLineSpacingItem( *this ); +} + +bool SvxLineSpacingItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + case SfxItemPresentation::Complete: + { + switch( GetLineSpaceRule() ) + { + case SvxLineSpaceRule::Auto: + { + SvxInterLineSpaceRule eInter = GetInterLineSpaceRule(); + + switch( eInter ) + { + // Default single line spacing + case SvxInterLineSpaceRule::Off: + rText = EditResId(RID_SVXITEMS_LINESPACING_SINGLE); + break; + + // Default single line spacing + case SvxInterLineSpaceRule::Prop: + if ( 100 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_SINGLE); + break; + } + // 1.15 line spacing + if ( 115 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_115); + break; + } + // 1.5 line spacing + if ( 150 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_15); + break; + } + // double line spacing + if ( 200 == GetPropLineSpace() ) + { + rText = EditResId(RID_SVXITEMS_LINESPACING_DOUBLE); + break; + } + // the set per cent value + rText = EditResId(RID_SVXITEMS_LINESPACING_PROPORTIONAL) + " " + OUString::number(GetPropLineSpace()) + "%"; + break; + + case SvxInterLineSpaceRule::Fix: + rText = EditResId(RID_SVXITEMS_LINESPACING_LEADING) + + " " + GetMetricText(GetInterLineSpace(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + default: ;//prevent warning + } + } + break; + case SvxLineSpaceRule::Fix: + rText = EditResId(RID_SVXITEMS_LINESPACING_FIXED) + + " " + GetMetricText(GetLineHeight(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + + case SvxLineSpaceRule::Min: + rText = EditResId(RID_SVXITEMS_LINESPACING_MIN) + + " " + GetMetricText(GetLineHeight(), eCoreUnit, ePresUnit, &rIntl) + + " " + EditResId(GetMetricId(ePresUnit)); + break; + default: ;//prevent warning + } + } + } + return true; +} + +sal_uInt16 SvxLineSpacingItem::GetValueCount() const +{ + return sal_uInt16(SvxSpecialLineSpace::End); // SvxSpecialLineSpace::TwoLines + 1 +} + + +sal_uInt16 SvxLineSpacingItem::GetEnumValue() const +{ + SvxSpecialLineSpace nVal; + switch ( nPropLineSpace ) + { + case 100: nVal = SvxSpecialLineSpace::OneLine; break; + case 150: nVal = SvxSpecialLineSpace::OnePointFiveLines; break; + case 200: nVal = SvxSpecialLineSpace::TwoLines; break; + default: nVal = SvxSpecialLineSpace::User; break; + } + return static_cast<sal_uInt16>(nVal); +} + + +void SvxLineSpacingItem::SetEnumValue( sal_uInt16 nVal ) +{ + switch ( static_cast<SvxSpecialLineSpace>(nVal) ) + { + case SvxSpecialLineSpace::OneLine: nPropLineSpace = 100; break; + case SvxSpecialLineSpace::OnePointFiveLines: nPropLineSpace = 150; break; + case SvxSpecialLineSpace::TwoLines: nPropLineSpace = 200; break; + default: break; + } +} + +// class SvxAdjustItem --------------------------------------------------- + +SvxAdjustItem::SvxAdjustItem(const SvxAdjust eAdjst, const sal_uInt16 nId ) + : SfxEnumItemInterface( nId ), + bOneBlock( false ), bLastCenter( false ), bLastBlock( false ) +{ + SetAdjust( eAdjst ); +} + + +bool SvxAdjustItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxAdjustItem& rItem = static_cast<const SvxAdjustItem&>(rAttr); + return GetAdjust() == rItem.GetAdjust() && + bOneBlock == rItem.bOneBlock && + bLastCenter == rItem.bLastCenter && + bLastBlock == rItem.bLastBlock; +} + +bool SvxAdjustItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_PARA_ADJUST : rVal <<= static_cast<sal_Int16>(GetAdjust()); break; + case MID_LAST_LINE_ADJUST : rVal <<= static_cast<sal_Int16>(GetLastBlock()); break; + case MID_EXPAND_SINGLE : + { + rVal <<= bOneBlock; + break; + } + default: ;//prevent warning + } + return true; +} + +bool SvxAdjustItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_PARA_ADJUST : + case MID_LAST_LINE_ADJUST : + { + sal_Int32 eVal = - 1; + ::cppu::enum2int(eVal,rVal); + if(eVal >= 0 && eVal <= 4) + { + SvxAdjust eAdjust = static_cast<SvxAdjust>(eVal); + if(MID_LAST_LINE_ADJUST == nMemberId && + eAdjust != SvxAdjust::Left && + eAdjust != SvxAdjust::Block && + eAdjust != SvxAdjust::Center) + return false; + nMemberId == MID_PARA_ADJUST ? SetAdjust(eAdjust) : SetLastBlock(eAdjust); + } + } + break; + case MID_EXPAND_SINGLE : + bOneBlock = Any2Bool(rVal); + break; + } + return true; +} + +SvxAdjustItem* SvxAdjustItem::Clone( SfxItemPool * ) const +{ + return new SvxAdjustItem( *this ); +} + +bool SvxAdjustItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + case SfxItemPresentation::Complete: + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetAdjust()) ); + return true; + default: ;//prevent warning + } + return false; +} + + +sal_uInt16 SvxAdjustItem::GetValueCount() const +{ + return sal_uInt16(SvxAdjust::End); // SvxAdjust::BlockLine + 1 +} + +OUString SvxAdjustItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_ADJUST[] = + { + RID_SVXITEMS_ADJUST_LEFT, + RID_SVXITEMS_ADJUST_RIGHT, + RID_SVXITEMS_ADJUST_BLOCK, + RID_SVXITEMS_ADJUST_CENTER, + RID_SVXITEMS_ADJUST_BLOCKLINE + }; + static_assert(SAL_N_ELEMENTS(RID_SVXITEMS_ADJUST) - 1 == size_t(SvxAdjust::BlockLine), "unexpected size"); + assert(nPos <= sal_uInt16(SvxAdjust::BlockLine) && "enum overflow!"); + return EditResId(RID_SVXITEMS_ADJUST[nPos]); +} + +sal_uInt16 SvxAdjustItem::GetEnumValue() const +{ + return static_cast<sal_uInt16>(GetAdjust()); +} + + +void SvxAdjustItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetAdjust( static_cast<SvxAdjust>(nVal) ); +} + + +// class SvxWidowsItem --------------------------------------------------- + +SvxWidowsItem::SvxWidowsItem(const sal_uInt8 nL, const sal_uInt16 nId ) : + SfxByteItem( nId, nL ) +{ +} + +SvxWidowsItem* SvxWidowsItem::Clone( SfxItemPool * ) const +{ + return new SvxWidowsItem( *this ); +} + +bool SvxWidowsItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = EditResId(RID_SVXITEMS_LINES); + break; + } + + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_WIDOWS_COMPLETE) + " " + EditResId(RID_SVXITEMS_LINES); + break; + } + + default: + { + SAL_WARN( "editeng.items", "SvxWidowsItem::GetPresentation(): unknown SfxItemPresentation" ); + } + } + + rText = rText.replaceFirst( "%1", OUString::number( GetValue() ) ); + return true; +} + +// class SvxOrphansItem -------------------------------------------------- + +SvxOrphansItem::SvxOrphansItem(const sal_uInt8 nL, const sal_uInt16 nId ) : + SfxByteItem( nId, nL ) +{ +} + +SvxOrphansItem* SvxOrphansItem::Clone( SfxItemPool * ) const +{ + return new SvxOrphansItem( *this ); +} + +bool SvxOrphansItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + rText = EditResId(RID_SVXITEMS_LINES); + break; + } + + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_ORPHANS_COMPLETE) + " " + EditResId(RID_SVXITEMS_LINES); + break; + } + + default: + { + SAL_WARN( "editeng.items", "SvxOrphansItem::GetPresentation(): unknown SfxItemPresentation" ); + } + } + + rText = rText.replaceFirst( "%1", OUString::number( GetValue() ) ); + return true; +} + +// class SvxHyphenZoneItem ----------------------------------------------- + +SvxHyphenZoneItem::SvxHyphenZoneItem( const bool bHyph, const sal_uInt16 nId ) : + SfxPoolItem( nId ), + bHyphen(bHyph), + bPageEnd(true), + bNoCapsHyphenation(false), + bNoLastWordHyphenation(false), + nMinLead(0), + nMinTrail(0), + nMaxHyphens(255), + nMinWordLength(0), + nTextHyphenZone(0) +{ +} + + +bool SvxHyphenZoneItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_IS_HYPHEN: + rVal <<= bHyphen; + break; + case MID_HYPHEN_MIN_LEAD: + rVal <<= static_cast<sal_Int16>(nMinLead); + break; + case MID_HYPHEN_MIN_TRAIL: + rVal <<= static_cast<sal_Int16>(nMinTrail); + break; + case MID_HYPHEN_MAX_HYPHENS: + rVal <<= static_cast<sal_Int16>(nMaxHyphens); + break; + case MID_HYPHEN_NO_CAPS: + rVal <<= bNoCapsHyphenation; + break; + case MID_HYPHEN_NO_LAST_WORD: + rVal <<= bNoLastWordHyphenation; + break; + case MID_HYPHEN_MIN_WORD_LENGTH: + rVal <<= static_cast<sal_Int16>(nMinWordLength); + break; + case MID_HYPHEN_ZONE: + rVal <<= static_cast<sal_Int16>(nTextHyphenZone); + break; + } + return true; +} + +bool SvxHyphenZoneItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + sal_Int16 nNewVal = 0; + + if( nMemberId != MID_IS_HYPHEN && nMemberId != MID_HYPHEN_NO_CAPS && + nMemberId != MID_HYPHEN_NO_LAST_WORD ) + { + if(!(rVal >>= nNewVal)) + return false; + } + + switch(nMemberId) + { + case MID_IS_HYPHEN: + bHyphen = Any2Bool(rVal); + break; + case MID_HYPHEN_MIN_LEAD: + nMinLead = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_MIN_TRAIL: + nMinTrail = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_MAX_HYPHENS: + nMaxHyphens = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_NO_CAPS: + bNoCapsHyphenation = Any2Bool(rVal); + break; + case MID_HYPHEN_NO_LAST_WORD: + bNoLastWordHyphenation = Any2Bool(rVal); + break; + case MID_HYPHEN_MIN_WORD_LENGTH: + nMinWordLength = static_cast<sal_uInt8>(nNewVal); + break; + case MID_HYPHEN_ZONE: + nTextHyphenZone = nNewVal; + break; + } + return true; +} + + +bool SvxHyphenZoneItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxHyphenZoneItem& rItem = static_cast<const SvxHyphenZoneItem&>(rAttr); + return ( rItem.bHyphen == bHyphen + && rItem.bNoCapsHyphenation == bNoCapsHyphenation + && rItem.bNoLastWordHyphenation == bNoLastWordHyphenation + && rItem.bPageEnd == bPageEnd + && rItem.nMinLead == nMinLead + && rItem.nMinTrail == nMinTrail + && rItem.nMaxHyphens == nMaxHyphens + && rItem.nMinWordLength == nMinWordLength + && rItem.nTextHyphenZone == nTextHyphenZone ); +} + +SvxHyphenZoneItem* SvxHyphenZoneItem::Clone( SfxItemPool * ) const +{ + return new SvxHyphenZoneItem( *this ); +} + +bool SvxHyphenZoneItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + OUString cpDelimTmp(cpDelim); + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + { + TranslateId pId = RID_SVXITEMS_HYPHEN_FALSE; + + if ( bHyphen ) + pId = RID_SVXITEMS_HYPHEN_TRUE; + rText = EditResId(pId) + cpDelimTmp; + pId = RID_SVXITEMS_PAGE_END_FALSE; + + if ( bPageEnd ) + pId = RID_SVXITEMS_PAGE_END_TRUE; + rText += EditResId(pId) + cpDelimTmp + + OUString::number( nMinLead ) + cpDelimTmp + + OUString::number( nMinTrail ) + cpDelimTmp + + OUString::number( nMaxHyphens ) + cpDelimTmp + + OUString::number( nMinWordLength ) + cpDelimTmp + + GetMetricText( nTextHyphenZone, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + + if ( bNoCapsHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_NO_CAPS_TRUE); + + if ( bNoLastWordHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_LAST_WORD_TRUE); + + return true; + } + case SfxItemPresentation::Complete: + { + TranslateId pId = RID_SVXITEMS_HYPHEN_FALSE; + + if ( bHyphen ) + pId = RID_SVXITEMS_HYPHEN_TRUE; + rText = EditResId(pId) + cpDelimTmp; + pId = RID_SVXITEMS_PAGE_END_FALSE; + + if ( bPageEnd ) + pId = RID_SVXITEMS_PAGE_END_TRUE; + rText += EditResId(pId) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINLEAD).replaceAll("%1", OUString::number(nMinLead)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINTRAIL).replaceAll("%1", OUString::number(nMinTrail)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MAX).replaceAll("%1", OUString::number(nMaxHyphens)) + + cpDelimTmp + + EditResId(RID_SVXITEMS_HYPHEN_MINWORDLEN).replaceAll("%1", OUString::number(nMinWordLength)); + + if ( nTextHyphenZone > 0 ) + { + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_ZONE) + + GetMetricText( nTextHyphenZone, eCoreUnit, ePresUnit, &rIntl ) + + " " + EditResId(GetMetricId(ePresUnit)); + } + + if ( bNoCapsHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_NO_CAPS_TRUE); + + if ( bNoLastWordHyphenation ) + rText += cpDelimTmp + EditResId(RID_SVXITEMS_HYPHEN_LAST_WORD_TRUE); + + return true; + } + default: ;//prevent warning + } + return false; +} + + +// class SvxTabStop ------------------------------------------------------ + +SvxTabStop::SvxTabStop() +{ + nTabPos = 0; + eAdjustment = SvxTabAdjust::Left; + m_cDecimal = cDfltDecimalChar; + cFill = cDfltFillChar; +} + + +SvxTabStop::SvxTabStop( const sal_Int32 nPos, const SvxTabAdjust eAdjst, + const sal_Unicode cDec, const sal_Unicode cFil ) +{ + nTabPos = nPos; + eAdjustment = eAdjst; + m_cDecimal = cDec; + cFill = cFil; +} + +void SvxTabStop::fillDecimal() const +{ + if ( cDfltDecimalChar == m_cDecimal ) + m_cDecimal = SvtSysLocale().GetLocaleData().getNumDecimalSep()[0]; +} + +void SvxTabStop::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTabStop")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nTabPos"), + BAD_CAST(OString::number(nTabPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("eAdjustment"), + BAD_CAST(OString::number(static_cast<int>(eAdjustment)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxTabStopItem -------------------------------------------------- + +SvxTabStopItem::SvxTabStopItem( sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ + const sal_uInt16 nTabs = SVX_TAB_DEFCOUNT, nDist = SVX_TAB_DEFDIST; + const SvxTabAdjust eAdjst= SvxTabAdjust::Default; + + for (sal_uInt16 i = 0; i < nTabs; ++i) + { + SvxTabStop aTab( (i + 1) * nDist, eAdjst ); + maTabStops.insert( aTab ); + } +} + + +SvxTabStopItem::SvxTabStopItem( const sal_uInt16 nTabs, + const sal_uInt16 nDist, + const SvxTabAdjust eAdjst, + sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ + for ( sal_uInt16 i = 0; i < nTabs; ++i ) + { + SvxTabStop aTab( (i + 1) * nDist, eAdjst ); + maTabStops.insert( aTab ); + } +} + + +sal_uInt16 SvxTabStopItem::GetPos( const SvxTabStop& rTab ) const +{ + SvxTabStopArr::const_iterator it = maTabStops.find( rTab ); + return it != maTabStops.end() ? it - maTabStops.begin() : SVX_TAB_NOTFOUND; +} + + +sal_uInt16 SvxTabStopItem::GetPos( const sal_Int32 nPos ) const +{ + SvxTabStopArr::const_iterator it = maTabStops.find( SvxTabStop( nPos ) ); + return it != maTabStops.end() ? it - maTabStops.begin() : SVX_TAB_NOTFOUND; +} + +void SvxTabStopItem::SetDefaultDistance(sal_Int32 nDefaultDistance) +{ + mnDefaultDistance = nDefaultDistance; +} + +sal_Int32 SvxTabStopItem::GetDefaultDistance() const +{ + return mnDefaultDistance; +} + +bool SvxTabStopItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_TABSTOPS: + { + sal_uInt16 nCount = Count(); + uno::Sequence< style::TabStop> aSeq(nCount); + style::TabStop* pArr = aSeq.getArray(); + for(sal_uInt16 i = 0; i < nCount; i++) + { + const SvxTabStop& rTab = (*this)[i]; + pArr[i].Position = bConvert ? convertTwipToMm100(rTab.GetTabPos()) : rTab.GetTabPos(); + switch(rTab.GetAdjustment()) + { + case SvxTabAdjust::Left : pArr[i].Alignment = style::TabAlign_LEFT; break; + case SvxTabAdjust::Right : pArr[i].Alignment = style::TabAlign_RIGHT; break; + case SvxTabAdjust::Decimal: pArr[i].Alignment = style::TabAlign_DECIMAL; break; + case SvxTabAdjust::Center : pArr[i].Alignment = style::TabAlign_CENTER; break; + default: //SvxTabAdjust::Default + pArr[i].Alignment = style::TabAlign_DEFAULT; + + } + pArr[i].DecimalChar = rTab.GetDecimal(); + pArr[i].FillChar = rTab.GetFill(); + } + rVal <<= aSeq; + break; + } + case MID_STD_TAB: + { + const SvxTabStop &rTab = maTabStops.front(); + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(rTab.GetTabPos()) : rTab.GetTabPos()); + break; + } + case MID_TABSTOP_DEFAULT_DISTANCE: + { + rVal <<= static_cast<sal_Int32>(bConvert ? convertTwipToMm100(mnDefaultDistance) : mnDefaultDistance); + break; + } + } + return true; +} + +bool SvxTabStopItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch ( nMemberId ) + { + case MID_TABSTOPS: + { + uno::Sequence< style::TabStop> aSeq; + if(!(rVal >>= aSeq)) + { + uno::Sequence < uno::Sequence < uno::Any > > aAnySeq; + if (!(rVal >>= aAnySeq)) + return false; + auto aAnySeqRange = asNonConstRange(aAnySeq); + sal_Int32 nLength = aAnySeq.getLength(); + aSeq.realloc( nLength ); + auto pSeq = aSeq.getArray(); + for ( sal_Int32 n=0; n<nLength; n++ ) + { + uno::Sequence < uno::Any >& rAnySeq = aAnySeqRange[n]; + if ( rAnySeq.getLength() == 4 ) + { + if (!(rAnySeq[0] >>= pSeq[n].Position)) return false; + if (!(rAnySeq[1] >>= pSeq[n].Alignment)) + { + sal_Int32 nVal = 0; + if (rAnySeq[1] >>= nVal) + pSeq[n].Alignment = static_cast<css::style::TabAlign>(nVal); + else + return false; + } + if (!(rAnySeq[2] >>= pSeq[n].DecimalChar)) + { + OUString aVal; + if ( (rAnySeq[2] >>= aVal) && aVal.getLength() == 1 ) + pSeq[n].DecimalChar = aVal.toChar(); + else + return false; + } + if (!(rAnySeq[3] >>= pSeq[n].FillChar)) + { + OUString aVal; + if ( (rAnySeq[3] >>= aVal) && aVal.getLength() == 1 ) + pSeq[n].FillChar = aVal.toChar(); + else + return false; + } + } + else + return false; + } + } + + maTabStops.clear(); + const style::TabStop* pArr = aSeq.getConstArray(); + const sal_uInt16 nCount = static_cast<sal_uInt16>(aSeq.getLength()); + for(sal_uInt16 i = 0; i < nCount ; i++) + { + SvxTabAdjust eAdjust = SvxTabAdjust::Default; + switch(pArr[i].Alignment) + { + case style::TabAlign_LEFT : eAdjust = SvxTabAdjust::Left; break; + case style::TabAlign_CENTER : eAdjust = SvxTabAdjust::Center; break; + case style::TabAlign_RIGHT : eAdjust = SvxTabAdjust::Right; break; + case style::TabAlign_DECIMAL: eAdjust = SvxTabAdjust::Decimal; break; + default: ;//prevent warning + } + sal_Unicode cFill = pArr[i].FillChar; + sal_Unicode cDecimal = pArr[i].DecimalChar; + SvxTabStop aTab( bConvert ? o3tl::toTwips(pArr[i].Position, o3tl::Length::mm100) : pArr[i].Position, + eAdjust, + cDecimal, + cFill ); + Insert(aTab); + } + break; + } + case MID_STD_TAB: + { + sal_Int32 nNewPos = 0; + if (!(rVal >>= nNewPos) ) + return false; + if (bConvert) + nNewPos = o3tl::toTwips(nNewPos, o3tl::Length::mm100); + if (nNewPos <= 0) + return false; + const SvxTabStop& rTab = maTabStops.front(); + SvxTabStop aNewTab ( nNewPos, rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() ); + Remove( 0 ); + Insert( aNewTab ); + break; + } + case MID_TABSTOP_DEFAULT_DISTANCE: + { + sal_Int32 nNewDefaultDistance = 0; + if (!(rVal >>= nNewDefaultDistance)) + return false; + if (bConvert) + nNewDefaultDistance = o3tl::toTwips(nNewDefaultDistance, o3tl::Length::mm100); + if (nNewDefaultDistance < 0) + return false; + mnDefaultDistance = nNewDefaultDistance; + break; + } + } + return true; +} + + +bool SvxTabStopItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxTabStopItem& rTSI = static_cast<const SvxTabStopItem&>(rAttr); + + if ( mnDefaultDistance != rTSI.GetDefaultDistance() ) + return false; + + if ( Count() != rTSI.Count() ) + return false; + + for ( sal_uInt16 i = 0; i < Count(); ++i ) + if( (*this)[i] != rTSI[i] ) + return false; + return true; +} + +SvxTabStopItem* SvxTabStopItem::Clone( SfxItemPool * ) const +{ + return new SvxTabStopItem( *this ); +} + +bool SvxTabStopItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit ePresUnit, + OUString& rText, const IntlWrapper& rIntl +) const +{ + rText.clear(); + // TODO also consider mnDefaultTabDistance here + + bool bComma = false; + + for ( sal_uInt16 i = 0; i < Count(); ++i ) + { + if ( SvxTabAdjust::Default != ((*this)[i]).GetAdjustment() ) + { + if ( bComma ) + rText += ","; + rText += GetMetricText( + ((*this)[i]).GetTabPos(), eCoreUnit, ePresUnit, &rIntl ); + if ( SfxItemPresentation::Complete == ePres ) + { + rText += " " + EditResId(GetMetricId(ePresUnit)); + } + bComma = true; + } + } + return true; +} + + +bool SvxTabStopItem::Insert( const SvxTabStop& rTab ) +{ + sal_uInt16 nTabPos = GetPos(rTab); + if(SVX_TAB_NOTFOUND != nTabPos ) + Remove(nTabPos); + return maTabStops.insert( rTab ).second; +} + +void SvxTabStopItem::Insert( const SvxTabStopItem* pTabs ) +{ + for( sal_uInt16 i = 0; i < pTabs->Count(); i++ ) + { + const SvxTabStop& rTab = (*pTabs)[i]; + sal_uInt16 nTabPos = GetPos(rTab); + if(SVX_TAB_NOTFOUND != nTabPos) + Remove(nTabPos); + } + for( sal_uInt16 i = 0; i < pTabs->Count(); i++ ) + { + maTabStops.insert( (*pTabs)[i] ); + } +} + +void SvxTabStopItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTabStopItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("mnDefaultDistance"), + BAD_CAST(OString::number(mnDefaultDistance).getStr())); + for (const auto& rTabStop : maTabStops) + rTabStop.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxFormatSplitItem ------------------------------------------------- +SvxFormatSplitItem::~SvxFormatSplitItem() +{ +} + +SvxFormatSplitItem* SvxFormatSplitItem::Clone( SfxItemPool * ) const +{ + return new SvxFormatSplitItem( *this ); +} + +bool SvxFormatSplitItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + TranslateId pId = RID_SVXITEMS_FMTSPLIT_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_FMTSPLIT_TRUE; + rText = EditResId(pId); + return true; +} + +SvxPageModelItem* SvxPageModelItem::Clone( SfxItemPool* ) const +{ + return new SvxPageModelItem( *this ); +} + +bool SvxPageModelItem::QueryValue( css::uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + + switch ( nMemberId ) + { + case MID_AUTO: rVal <<= bAuto; break; + case MID_NAME: rVal <<= GetValue(); break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return true; +} + +bool SvxPageModelItem::PutValue( const css::uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet; + OUString aStr; + switch ( nMemberId ) + { + case MID_AUTO: bRet = ( rVal >>= bAuto ); break; + case MID_NAME: bRet = ( rVal >>= aStr ); if ( bRet ) SetValue(aStr); break; + default: OSL_FAIL("Wrong MemberId!"); return false; + } + + return bRet; +} + +bool SvxPageModelItem::operator==( const SfxPoolItem& rAttr ) const +{ + return SfxStringItem::operator==(rAttr) && + bAuto == static_cast<const SvxPageModelItem&>( rAttr ).bAuto; +} + +bool SvxPageModelItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& +) const +{ + rText.clear(); + bool bSet = !GetValue().isEmpty(); + + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + if ( bSet ) + rText = GetValue(); + return true; + + case SfxItemPresentation::Complete: + if ( bSet ) + { + rText = EditResId(RID_SVXITEMS_PAGEMODEL_COMPLETE) + GetValue(); + } + return true; + default: ;//prevent warning + } + return false; +} + + +SvxScriptSpaceItem::SvxScriptSpaceItem( bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxScriptSpaceItem* SvxScriptSpaceItem::Clone( SfxItemPool * ) const +{ + return new SvxScriptSpaceItem( *this ); +} + +bool SvxScriptSpaceItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_SCRPTSPC_OFF + : RID_SVXITEMS_SCRPTSPC_ON ); + return true; +} + + +SvxHangingPunctuationItem::SvxHangingPunctuationItem( + bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxHangingPunctuationItem* SvxHangingPunctuationItem::Clone( SfxItemPool * ) const +{ + return new SvxHangingPunctuationItem( *this ); +} + +bool SvxHangingPunctuationItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_HNGPNCT_OFF + : RID_SVXITEMS_HNGPNCT_ON ); + return true; +} + + +SvxForbiddenRuleItem::SvxForbiddenRuleItem( + bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxForbiddenRuleItem* SvxForbiddenRuleItem::Clone( SfxItemPool * ) const +{ + return new SvxForbiddenRuleItem( *this ); +} + +bool SvxForbiddenRuleItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = EditResId( !GetValue() + ? RID_SVXITEMS_FORBIDDEN_RULE_OFF + : RID_SVXITEMS_FORBIDDEN_RULE_ON ); + return true; +} + +/************************************************************************* +|* class SvxParaVertAlignItem +*************************************************************************/ + +SvxParaVertAlignItem::SvxParaVertAlignItem( Align nValue, + TypedWhichId<SvxParaVertAlignItem> nW ) + : SfxUInt16Item( nW, static_cast<sal_uInt16>(nValue) ) +{ +} + +SvxParaVertAlignItem* SvxParaVertAlignItem::Clone( SfxItemPool* ) const +{ + return new SvxParaVertAlignItem( *this ); +} + +bool SvxParaVertAlignItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& ) const +{ + TranslateId pTmp; + switch( GetValue() ) + { + case Align::Automatic: pTmp = RID_SVXITEMS_PARAVERTALIGN_AUTO; break; + case Align::Top: pTmp = RID_SVXITEMS_PARAVERTALIGN_TOP; break; + case Align::Center: pTmp = RID_SVXITEMS_PARAVERTALIGN_CENTER; break; + case Align::Bottom: pTmp = RID_SVXITEMS_PARAVERTALIGN_BOTTOM; break; + default: pTmp = RID_SVXITEMS_PARAVERTALIGN_BASELINE; break; + } + rText = EditResId(pTmp); + return true; +} + +bool SvxParaVertAlignItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= static_cast<sal_Int16>(GetValue()); + return true; +} + +bool SvxParaVertAlignItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 /*nMemberId*/ ) +{ + sal_Int16 nVal = sal_Int16(); + if((rVal >>= nVal) && nVal >=0 && nVal <= sal_uInt16(Align::Bottom) ) + { + SetValue( static_cast<Align>(nVal) ); + return true; + } + else + return false; +} + +SvxParaGridItem::SvxParaGridItem( bool bOn, const sal_uInt16 nId ) + : SfxBoolItem( nId, bOn ) +{ +} + +SvxParaGridItem* SvxParaGridItem::Clone( SfxItemPool * ) const +{ + return new SvxParaGridItem( *this ); +} + +bool SvxParaGridItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + rText = GetValue() ? + EditResId( RID_SVXITEMS_PARASNAPTOGRID_ON ) : + EditResId( RID_SVXITEMS_PARASNAPTOGRID_OFF ); + + return true; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/svdfield.cxx b/editeng/source/items/svdfield.cxx new file mode 100644 index 0000000000..a9b78148c0 --- /dev/null +++ b/editeng/source/items/svdfield.cxx @@ -0,0 +1,34 @@ +/* -*- 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 <editeng/measfld.hxx> + +SdrMeasureField::~SdrMeasureField() {} + +std::unique_ptr<SvxFieldData> SdrMeasureField::Clone() const +{ + return std::make_unique<SdrMeasureField>(*this); +} + +bool SdrMeasureField::operator==(const SvxFieldData& rSrc) const +{ + return eMeasureFieldKind == static_cast<const SdrMeasureField&>(rSrc).GetMeasureFieldKind(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/svxfont.cxx b/editeng/source/items/svxfont.cxx new file mode 100644 index 0000000000..876bc06868 --- /dev/null +++ b/editeng/source/items/svxfont.cxx @@ -0,0 +1,906 @@ +/* -*- 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 <editeng/svxfont.hxx> + +#include <vcl/glyphitemcache.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <vcl/print.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <tools/poly.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/i18n/KCharacterType.hpp> +#include <editeng/escapementitem.hxx> +#include <editeng/smallcaps.hxx> +#include <sal/log.hxx> +#include <limits> + +static tools::Long GetTextArray( const OutputDevice* pOut, const OUString& rStr, KernArray* pDXAry, + sal_Int32 nIndex, sal_Int32 nLen ) + +{ + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen); + return pOut->GetTextArray( rStr, pDXAry, nIndex, nLen, true, nullptr, layoutGlyphs); +} + +SvxFont::SvxFont() +{ + nEsc = 0; + nPropr = 100; + eCaseMap = SvxCaseMap::NotMapped; + SetLanguage(LANGUAGE_SYSTEM); +} + +SvxFont::SvxFont( const vcl::Font &rFont ) + : Font( rFont ) +{ + nEsc = 0; + nPropr = 100; + eCaseMap = SvxCaseMap::NotMapped; + SetLanguage(LANGUAGE_SYSTEM); +} + +SvxFont::SvxFont( const SvxFont &rFont ) + : Font( rFont ) +{ + nEsc = rFont.GetEscapement(); + nPropr = rFont.GetPropr(); + eCaseMap = rFont.GetCaseMap(); + SetLanguage(rFont.GetLanguage()); +} + +void SvxFont::SetNonAutoEscapement(short nNewEsc, const OutputDevice* pOutDev) +{ + nEsc = nNewEsc; + if ( abs(nEsc) == DFLT_ESC_AUTO_SUPER ) + { + double fAutoAscent = .8; + double fAutoDescent = .2; + if ( pOutDev ) + { + const FontMetric& rFontMetric = pOutDev->GetFontMetric(); + double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent(); + if ( fFontHeight ) + { + fAutoAscent = rFontMetric.GetAscent() / fFontHeight; + fAutoDescent = rFontMetric.GetDescent() / fFontHeight; + } + } + + if ( nEsc == DFLT_ESC_AUTO_SUPER ) + nEsc = fAutoAscent * (100 - nPropr); + else //DFLT_ESC_AUTO_SUB + nEsc = fAutoDescent * -(100 - nPropr); + } + + if ( nEsc > MAX_ESC_POS ) + nEsc = MAX_ESC_POS; + else if ( nEsc < -MAX_ESC_POS ) + nEsc = -MAX_ESC_POS; +} + +tools::Polygon SvxFont::DrawArrow( OutputDevice &rOut, const tools::Rectangle& rRect, + const Size& rSize, const Color& rCol, bool bLeftOrTop, bool bVertical ) +{ + tools::Polygon aPoly; + Point aTmp; + Point aNxt; + if (bVertical) + { + tools::Long nLeft = ((rRect.Left() + rRect.Right()) / 2) - (rSize.Height() / 2); + tools::Long nRight = ((rRect.Left() + rRect.Right()) / 2) + (rSize.Height() / 2); + tools::Long nMid = (rRect.Left() + rRect.Right()) / 2; + tools::Long nTop = ((rRect.Top() + rRect.Bottom()) / 2) - (rSize.Height() / 2); + tools::Long nBottom = nTop + rSize.Height(); + if (nTop < rRect.Top()) + { + if (bLeftOrTop) + { + nTop = rRect.Top(); + nBottom = rRect.Bottom(); + } + else + { + nTop = rRect.Bottom(); + nBottom = rRect.Bottom() - (rSize.Height() / 2); + } + } + aTmp.setX(nRight); + aTmp.setY(nBottom); + aNxt.setX(nMid); + aNxt.setY(nTop); + aPoly.Insert(0, aTmp); + aPoly.Insert(0, aNxt); + aTmp.setX(nLeft); + aPoly.Insert(0, aTmp); + } + else + { + tools::Long nLeft = (rRect.Left() + rRect.Right() - rSize.Width()) / 2; + tools::Long nRight = nLeft + rSize.Width(); + tools::Long nMid = (rRect.Top() + rRect.Bottom()) / 2; + tools::Long nTop = nMid - rSize.Height() / 2; + tools::Long nBottom = nTop + rSize.Height(); + if (nLeft < rRect.Left()) + { + nLeft = rRect.Left(); + nRight = rRect.Right(); + } + aTmp.setX(bLeftOrTop ? nLeft : nRight); + aTmp.setY(nMid); + aNxt.setX(bLeftOrTop ? nRight : nLeft); + aNxt.setY(nTop); + aPoly.Insert(0, aTmp); + aPoly.Insert(0, aNxt); + aNxt.setY(nBottom); + aPoly.Insert(0, aNxt); + } + Color aOldLineColor = rOut.GetLineColor(); + Color aOldFillColor = rOut.GetFillColor(); + rOut.SetFillColor( rCol ); + rOut.SetLineColor( COL_BLACK ); + rOut.DrawPolygon( aPoly ); + rOut.DrawLine( aTmp, aNxt ); + rOut.SetLineColor( aOldLineColor ); + rOut.SetFillColor( aOldFillColor ); + return aPoly; +} + +OUString SvxFont::CalcCaseMap(const OUString &rTxt) const +{ + if (!IsCaseMap() || rTxt.isEmpty()) + return rTxt; + OUString aTxt(rTxt); + // I still have to get the language + const LanguageType eLang = LANGUAGE_DONTKNOW == GetLanguage() + ? LANGUAGE_SYSTEM : GetLanguage(); + + CharClass aCharClass(( LanguageTag(eLang) )); + + switch( eCaseMap ) + { + case SvxCaseMap::SmallCaps: + case SvxCaseMap::Uppercase: + { + aTxt = aCharClass.uppercase( aTxt ); + break; + } + + case SvxCaseMap::Lowercase: + { + aTxt = aCharClass.lowercase( aTxt ); + break; + } + case SvxCaseMap::Capitalize: + { + // Every beginning of a word is capitalized, the rest of the word + // is taken over as is. + // Bug: if the attribute starts in the middle of the word. + bool bBlank = true; + + for (sal_Int32 i = 0; i < aTxt.getLength(); ++i) + { + if( aTxt[i] == ' ' || aTxt[i] == '\t') + bBlank = true; + else + { + if (bBlank) + { + OUString sTitle(aCharClass.uppercase(OUString(aTxt[i]))); + aTxt = aTxt.replaceAt(i, 1, sTitle); + } + bBlank = false; + } + } + break; + } + default: + { + SAL_WARN( "editeng", "SvxFont::CaseMapTxt: unknown casemap"); + break; + } + } + return aTxt; +} + +void SvxDoCapitals::DoSpace( const bool /*bDraw*/ ) { } + +void SvxDoCapitals::SetSpace() { } + +/************************************************************************* + * SvxFont::DoOnCapitals() const + * Decomposes the String into uppercase and lowercase letters and then + * calls the method SvxDoCapitals::Do( ). + *************************************************************************/ + +void SvxFont::DoOnCapitals(SvxDoCapitals &rDo) const +{ + const OUString &rTxt = rDo.GetTxt(); + const sal_Int32 nIdx = rDo.GetIdx(); + const sal_Int32 nLen = rDo.GetLen(); + + const OUString aTxt( CalcCaseMap( rTxt ) ); + const sal_Int32 nTxtLen = std::min( rTxt.getLength(), nLen ); + sal_Int32 nPos = 0; + sal_Int32 nOldPos = nPos; + + // Test if string length differ between original and CaseMapped + bool bCaseMapLengthDiffers(aTxt.getLength() != rTxt.getLength()); + + const LanguageType eLang = LANGUAGE_DONTKNOW == GetLanguage() + ? LANGUAGE_SYSTEM : GetLanguage(); + + CharClass aCharClass(( LanguageTag(eLang) )); + OUString aCharString; + + while( nPos < nTxtLen ) + { + // first in turn are the uppercase letters + + // There are characters that are both upper- and lower-case L (eg blank) + // Such ambiguities lead to chaos, this is why these characters are + // allocated to the lowercase characters! + + while( nPos < nTxtLen ) + { + aCharString = rTxt.copy( nPos + nIdx, 1 ); + sal_Int32 nCharacterType = aCharClass.getCharacterType( aCharString, 0 ); + if ( nCharacterType & css::i18n::KCharacterType::LOWER ) + break; + if ( ! ( nCharacterType & css::i18n::KCharacterType::UPPER ) ) + break; + ++nPos; + } + if( nOldPos != nPos ) + { + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos-nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), true ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos-nOldPos, true ); + } + + nOldPos = nPos; + } + // Now the lowercase are processed (without blanks) + while( nPos < nTxtLen ) + { + sal_uInt32 nCharacterType = aCharClass.getCharacterType( aCharString, 0 ); + if ( nCharacterType & css::i18n::KCharacterType::UPPER ) + break; + if ( aCharString == " " ) + break; + if( ++nPos < nTxtLen ) + aCharString = rTxt.copy( nPos + nIdx, 1 ); + } + if( nOldPos != nPos ) + { + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos - nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), false ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos-nOldPos, false ); + } + + nOldPos = nPos; + } + // Now the blanks are<processed + while( nPos < nTxtLen && aCharString == " " && ++nPos < nTxtLen ) + aCharString = rTxt.copy( nPos + nIdx, 1 ); + + if( nOldPos != nPos ) + { + rDo.DoSpace( false ); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx + nOldPos, nPos - nOldPos); + OUString aNewText = CalcCaseMap(aSnippet); + + rDo.Do( aNewText, 0, aNewText.getLength(), false ); + } + else + { + rDo.Do( aTxt, nIdx + nOldPos, nPos - nOldPos, false ); + } + + nOldPos = nPos; + rDo.SetSpace(); + } + } + rDo.DoSpace( true ); +} + + +void SvxFont::SetPhysFont(OutputDevice& rOut) const +{ + const vcl::Font& rCurrentFont = rOut.GetFont(); + if ( nPropr == 100 ) + { + if ( !rCurrentFont.IsSameInstance( *this ) ) + rOut.SetFont( *this ); + } + else + { + Font aNewFont( *this ); + Size aSize( aNewFont.GetFontSize() ); + aNewFont.SetFontSize( Size( aSize.Width() * nPropr / 100, + aSize.Height() * nPropr / 100 ) ); + if ( !rCurrentFont.IsSameInstance( aNewFont ) ) + rOut.SetFont( aNewFont ); + } +} + +vcl::Font SvxFont::ChgPhysFont(OutputDevice& rOut) const +{ + vcl::Font aOldFont(rOut.GetFont()); + SetPhysFont(rOut); + return aOldFont; +} + +Size SvxFont::GetPhysTxtSize( const OutputDevice *pOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + if ( !IsCaseMap() && !IsFixKerning() ) + return Size( pOut->GetTextWidth( rTxt, nIdx, nLen ), + pOut->GetTextHeight() ); + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + if ( !IsCaseMap() ) + aTxtSize.setWidth( pOut->GetTextWidth( rTxt, nIdx, nLen ) ); + else + { + const OUString aNewText = CalcCaseMap(rTxt); + bool bCaseMapLengthDiffers(aNewText.getLength() != rTxt.getLength()); + sal_Int32 nWidth(0); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet = rTxt.copy(nIdx, nLen); + OUString _aNewText = CalcCaseMap(aSnippet); + nWidth = pOut->GetTextWidth( _aNewText, 0, _aNewText.getLength() ); + } + else + { + nWidth = pOut->GetTextWidth( aNewText, nIdx, nLen ); + } + + aTxtSize.setWidth(nWidth); + } + + if( IsFixKerning() && ( nLen > 1 ) ) + { + auto nKern = GetFixKerning(); + KernArray aDXArray; + GetTextArray(pOut, rTxt, &aDXArray, nIdx, nLen); + tools::Long nOldValue = aDXArray[0]; + sal_Int32 nSpaceCount = 0; + for(sal_Int32 i = 1; i < nLen; ++i) + { + if (aDXArray[i] != nOldValue) + { + nOldValue = aDXArray[i]; + ++nSpaceCount; + } + } + aTxtSize.AdjustWidth( nSpaceCount * tools::Long( nKern ) ); + } + + return aTxtSize; +} + +Size SvxFont::GetPhysTxtSize( const OutputDevice *pOut ) +{ + if ( !IsCaseMap() && !IsFixKerning() ) + return Size( pOut->GetTextWidth( "" ), pOut->GetTextHeight() ); + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + if ( !IsCaseMap() ) + aTxtSize.setWidth( pOut->GetTextWidth( "" ) ); + else + aTxtSize.setWidth( pOut->GetTextWidth( CalcCaseMap( "" ) ) ); + + return aTxtSize; +} + +Size SvxFont::QuickGetTextSize( const OutputDevice *pOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen, KernArray* pDXArray ) const +{ + if ( !IsCaseMap() && !IsFixKerning() ) + { + SAL_INFO( "editeng.quicktextsize", "SvxFont::QuickGetTextSize before GetTextArray(): Case map: " << IsCaseMap() << " Fix kerning: " << IsFixKerning()); + Size aTxtSize( GetTextArray( pOut, rTxt, pDXArray, nIdx, nLen ), + pOut->GetTextHeight() ); + SAL_INFO( "editeng.quicktextsize", "SvxFont::QuickGetTextSize after GetTextArray(): Text length: " << nLen << " Text size: " << aTxtSize.Width() << "x" << aTxtSize.Height()); + return aTxtSize; + } + + KernArray aDXArray; + + // We always need pDXArray to count the number of kern spaces + if (!pDXArray && IsFixKerning() && nLen > 1) + { + pDXArray = &aDXArray; + aDXArray.reserve(nLen); + } + + Size aTxtSize; + aTxtSize.setHeight( pOut->GetTextHeight() ); + SAL_INFO( "editeng.quicktextsize", "SvxFont::QuickGetTextSize before GetTextArray(): Case map: " << IsCaseMap() << " Fix kerning: " << IsFixKerning()); + if ( !IsCaseMap() ) + aTxtSize.setWidth( GetTextArray( pOut, rTxt, pDXArray, nIdx, nLen ) ); + else + { + if (IsCapital() && !rTxt.isEmpty()) + aTxtSize = GetCapitalSize(pOut, rTxt, pDXArray, nIdx, nLen); + else + aTxtSize.setWidth( GetTextArray( pOut, CalcCaseMap( rTxt ), + pDXArray, nIdx, nLen ) ); + } + SAL_INFO( "editeng.quicktextsize", "SvxFont::QuickGetTextSize after GetTextArray(): Text length: " << nLen << " Text size: " << aTxtSize.Width() << "x" << aTxtSize.Height()); + + if( IsFixKerning() && ( nLen > 1 ) ) + { + auto nKern = GetFixKerning(); + tools::Long nOldValue = (*pDXArray)[0]; + tools::Long nSpaceSum = nKern; + pDXArray->adjust(0, nSpaceSum); + + for ( sal_Int32 i = 1; i < nLen; i++ ) + { + if ( (*pDXArray)[i] != nOldValue ) + { + nOldValue = (*pDXArray)[i]; + nSpaceSum += nKern; + } + pDXArray->adjust(i, nSpaceSum); + } + + // The last one is a nKern too big: + nOldValue = (*pDXArray)[nLen - 1]; + tools::Long nNewValue = nOldValue - nKern; + for ( sal_Int32 i = nLen - 1; i >= 0 && (*pDXArray)[i] == nOldValue; --i) + pDXArray->set(i, nNewValue); + + aTxtSize.AdjustWidth(nSpaceSum - nKern); + } + + return aTxtSize; +} + +Size SvxFont::GetTextSize(const OutputDevice& rOut, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen) const +{ + sal_Int32 nTmp = nLen; + if ( nTmp == SAL_MAX_INT32 ) // already initialized? + nTmp = rTxt.getLength(); + Font aOldFont( ChgPhysFont(const_cast<OutputDevice&>(rOut))); + Size aTxtSize; + if( IsCapital() && !rTxt.isEmpty() ) + { + aTxtSize = GetCapitalSize(&rOut, rTxt, nullptr, nIdx, nTmp); + } + else aTxtSize = GetPhysTxtSize(&rOut,rTxt,nIdx,nTmp); + const_cast<OutputDevice&>(rOut).SetFont(aOldFont); + return aTxtSize; +} + +static void DrawTextArray( OutputDevice* pOut, const Point& rStartPt, const OUString& rStr, + std::span<const sal_Int32> pDXAry, + std::span<const sal_Bool> pKashidaAry, + sal_Int32 nIndex, sal_Int32 nLen ) +{ + const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen); + pOut->DrawTextArray(rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, SalLayoutFlags::NONE, layoutGlyphs); +} + +void SvxFont::QuickDrawText( OutputDevice *pOut, + const Point &rPos, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen, + std::span<const sal_Int32> pDXArray, + std::span<const sal_Bool> pKashidaArray) const +{ + + // Font has to be selected in OutputDevice... + if ( !IsCaseMap() && !IsCapital() && !IsFixKerning() && !IsEsc() ) + { + DrawTextArray( pOut, rPos, rTxt, pDXArray, pKashidaArray, nIdx, nLen ); + return; + } + + Point aPos( rPos ); + + if ( nEsc ) + { + tools::Long nDiff = GetFontSize().Height(); + nDiff *= nEsc; + nDiff /= 100; + + if ( !IsVertical() ) + aPos.AdjustY( -nDiff ); + else + aPos.AdjustX(nDiff ); + } + + if( IsCapital() ) + { + DrawCapital( pOut, aPos, rTxt, pDXArray, pKashidaArray, nIdx, nLen ); + } + else + { + if ( IsFixKerning() && pDXArray.empty() ) + { + Size aSize = GetPhysTxtSize( pOut, rTxt, nIdx, nLen ); + + if ( !IsCaseMap() ) + pOut->DrawStretchText( aPos, aSize.Width(), rTxt, nIdx, nLen ); + else + pOut->DrawStretchText( aPos, aSize.Width(), CalcCaseMap( rTxt ), nIdx, nLen ); + } + else + { + if ( !IsCaseMap() ) + DrawTextArray( pOut, aPos, rTxt, pDXArray, pKashidaArray, nIdx, nLen ); + else + DrawTextArray( pOut, aPos, CalcCaseMap( rTxt ), pDXArray, pKashidaArray, nIdx, nLen ); + } + } +} + + +void SvxFont::DrawPrev( OutputDevice *pOut, Printer* pPrinter, + const Point &rPos, const OUString &rTxt, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + if ( !nLen || rTxt.isEmpty() ) + return; + sal_Int32 nTmp = nLen; + + if ( nTmp == SAL_MAX_INT32 ) // already initialized? + nTmp = rTxt.getLength(); + Point aPos( rPos ); + + if ( nEsc ) + { + short nTmpEsc; + if( DFLT_ESC_AUTO_SUPER == nEsc ) + { + nTmpEsc = .8 * (100 - nPropr); + assert (nTmpEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???"); + nTmpEsc = DFLT_ESC_SUPER; + } + else if( DFLT_ESC_AUTO_SUB == nEsc ) + { + nTmpEsc = .2 * -(100 - nPropr); + assert (nTmpEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???"); + nTmpEsc = -20; + } + else + nTmpEsc = nEsc; + Size aSize = GetFontSize(); + aPos.AdjustY( -(( nTmpEsc * aSize.Height() ) / 100) ); + } + Font aOldFont( ChgPhysFont(*pOut) ); + Font aOldPrnFont( ChgPhysFont(*pPrinter) ); + + if ( IsCapital() ) + DrawCapital( pOut, aPos, rTxt, {}, {}, nIdx, nTmp ); + else + { + Size aSize = GetPhysTxtSize( pPrinter, rTxt, nIdx, nTmp ); + + if ( !IsCaseMap() ) + pOut->DrawStretchText( aPos, aSize.Width(), rTxt, nIdx, nTmp ); + else + { + const OUString aNewText = CalcCaseMap(rTxt); + bool bCaseMapLengthDiffers(aNewText.getLength() != rTxt.getLength()); + + if(bCaseMapLengthDiffers) + { + // If strings differ work preparing the necessary snippet to address that + // potential difference + const OUString aSnippet(rTxt.copy( nIdx, nTmp)); + OUString _aNewText = CalcCaseMap(aSnippet); + + pOut->DrawStretchText( aPos, aSize.Width(), _aNewText, 0, _aNewText.getLength() ); + } + else + { + pOut->DrawStretchText( aPos, aSize.Width(), CalcCaseMap( rTxt ), nIdx, nTmp ); + } + } + } + pOut->SetFont(aOldFont); + pPrinter->SetFont( aOldPrnFont ); +} + + +SvxFont& SvxFont::operator=( const vcl::Font& rFont ) +{ + Font::operator=( rFont ); + return *this; +} + +SvxFont& SvxFont::operator=( const SvxFont& rFont ) +{ + Font::operator=( rFont ); + eCaseMap = rFont.eCaseMap; + nEsc = rFont.nEsc; + nPropr = rFont.nPropr; + return *this; +} + +namespace { + +class SvxDoGetCapitalSize : public SvxDoCapitals +{ +protected: + VclPtr<OutputDevice> pOut; + SvxFont* pFont; + Size aTxtSize; + short nKern; + KernArray* pDXAry; +public: + SvxDoGetCapitalSize( SvxFont *_pFnt, const OutputDevice *_pOut, + const OUString &_rTxt, KernArray* _pDXAry, const sal_Int32 _nIdx, + const sal_Int32 _nLen, const short _nKrn ) + : SvxDoCapitals( _rTxt, _nIdx, _nLen ), + pOut( const_cast<OutputDevice*>(_pOut) ), + pFont( _pFnt ), + nKern( _nKrn ), + pDXAry( _pDXAry ) + { + if (pDXAry) + { + pDXAry->clear(); + pDXAry->reserve(_nLen); + } + } + + virtual void Do( const OUString &rTxt, const sal_Int32 nIdx, + const sal_Int32 nLen, const bool bUpper ) override; + + const Size &GetSize() const { return aTxtSize; }; +}; + +} + +void SvxDoGetCapitalSize::Do( const OUString &_rTxt, const sal_Int32 _nIdx, + const sal_Int32 _nLen, const bool bUpper ) +{ + Size aPartSize; + sal_uInt8 nProp(0); + if ( !bUpper ) + { + nProp = pFont->GetPropr(); + pFont->SetProprRel( SMALL_CAPS_PERCENTAGE ); + pFont->SetPhysFont( *pOut ); + } + + if (pDXAry) + { + KernArray aKernArray; + aPartSize.setWidth(pOut->GetTextArray(_rTxt, &aKernArray, _nIdx, _nLen)); + assert(pDXAry->get_factor() == aKernArray.get_factor()); + auto& dest = pDXAry->get_subunit_array(); + sal_Int32 nStart = dest.empty() ? 0 : dest.back(); + size_t nSrcLen = aKernArray.size(); + dest.reserve(dest.size() + nSrcLen); + const auto& src = aKernArray.get_subunit_array(); + for (size_t i = 0; i < nSrcLen; ++i) + dest.push_back(src[i] + nStart); + } + else + { + aPartSize.setWidth( pOut->GetTextWidth( _rTxt, _nIdx, _nLen ) ); + } + + aPartSize.setHeight( pOut->GetTextHeight() ); + + if ( !bUpper ) + { + aTxtSize.setHeight( aPartSize.Height() ); + pFont->SetPropr( nProp ); + pFont->SetPhysFont( *pOut ); + } + + aTxtSize.AdjustWidth(aPartSize.Width() ); + aTxtSize.AdjustWidth( _nLen * tools::Long( nKern ) ); +} + +Size SvxFont::GetCapitalSize( const OutputDevice *pOut, const OUString &rTxt, KernArray* pDXAry, + const sal_Int32 nIdx, const sal_Int32 nLen) const +{ + // Start: + SvxDoGetCapitalSize aDo( const_cast<SvxFont *>(this), pOut, rTxt, pDXAry, nIdx, nLen, GetFixKerning() ); + DoOnCapitals( aDo ); + Size aTxtSize( aDo.GetSize() ); + + // End: + if( !aTxtSize.Height() ) + { + aTxtSize.setWidth( 0 ); + aTxtSize.setHeight( pOut->GetTextHeight() ); + } + return aTxtSize; +} + +namespace { + +class SvxDoDrawCapital : public SvxDoCapitals +{ +protected: + VclPtr<OutputDevice> pOut; + SvxFont *pFont; + Point aPos; + Point aSpacePos; + short nKern; + std::span<const sal_Int32> pDXArray; + std::span<const sal_Bool> pKashidaArray; +public: + SvxDoDrawCapital( SvxFont *pFnt, OutputDevice *_pOut, const OUString &_rTxt, + std::span<const sal_Int32> _pDXArray, + std::span<const sal_Bool> _pKashidaArray, + const sal_Int32 _nIdx, const sal_Int32 _nLen, + const Point &rPos, const short nKrn ) + : SvxDoCapitals( _rTxt, _nIdx, _nLen ), + pOut( _pOut ), + pFont( pFnt ), + aPos( rPos ), + aSpacePos( rPos ), + nKern( nKrn ), + pDXArray(_pDXArray), + pKashidaArray(_pKashidaArray) + { } + virtual void DoSpace( const bool bDraw ) override; + virtual void SetSpace() override; + virtual void Do( const OUString &rTxt, const sal_Int32 nIdx, + const sal_Int32 nLen, const bool bUpper ) override; +}; + +} + +void SvxDoDrawCapital::DoSpace( const bool bDraw ) +{ + if ( !(bDraw || pFont->IsWordLineMode()) ) + return; + + sal_Int32 nDiff = static_cast<sal_Int32>(aPos.X() - aSpacePos.X()); + if ( nDiff ) + { + bool bWordWise = pFont->IsWordLineMode(); + bool bTrans = pFont->IsTransparent(); + pFont->SetWordLineMode( false ); + pFont->SetTransparent( true ); + pFont->SetPhysFont(*pOut); + pOut->DrawStretchText( aSpacePos, nDiff, " ", 0, 2 ); + pFont->SetWordLineMode( bWordWise ); + pFont->SetTransparent( bTrans ); + pFont->SetPhysFont(*pOut); + } +} + +void SvxDoDrawCapital::SetSpace() +{ + if ( pFont->IsWordLineMode() ) + aSpacePos.setX( aPos.X() ); +} + +void SvxDoDrawCapital::Do( const OUString &_rTxt, const sal_Int32 nSpanIdx, + const sal_Int32 nSpanLen, const bool bUpper) +{ + sal_uInt8 nProp = 0; + + // Set the desired font + FontLineStyle eUnder = pFont->GetUnderline(); + FontLineStyle eOver = pFont->GetOverline(); + FontStrikeout eStrike = pFont->GetStrikeout(); + pFont->SetUnderline( LINESTYLE_NONE ); + pFont->SetOverline( LINESTYLE_NONE ); + pFont->SetStrikeout( STRIKEOUT_NONE ); + if ( !bUpper ) + { + nProp = pFont->GetPropr(); + pFont->SetProprRel( SMALL_CAPS_PERCENTAGE ); + } + pFont->SetPhysFont(*pOut); + + if (pDXArray.empty()) + { + auto nWidth = pOut->GetTextWidth(_rTxt, nSpanIdx, nSpanLen); + if (nKern) + { + aPos.AdjustX(nKern/2); + if (nSpanLen) + nWidth += (nSpanLen * nKern); + } + pOut->DrawStretchText(aPos, nWidth-nKern, _rTxt, nSpanIdx, nSpanLen); + // in this case we move aPos along to be the start of each subspan + aPos.AdjustX(nWidth-(nKern/2) ); + } + else + { + const sal_Int32 nStartOffset = nSpanIdx - nIdx; + sal_Int32 nStartX = nStartOffset ? pDXArray[nStartOffset - 1] : 0; + + Point aStartPos(aPos.X() + nStartX, aPos.Y()); + + std::vector<sal_Int32> aDXArray; + aDXArray.reserve(nSpanLen); + for (sal_Int32 i = 0; i < nSpanLen; ++i) + aDXArray.push_back(pDXArray[nStartOffset + i] - nStartX); + + auto aKashidaArray = !pKashidaArray.empty() ? + std::span<const sal_Bool>(pKashidaArray.data() + nStartOffset, nSpanLen) : + std::span<const sal_Bool>(); + + DrawTextArray(pOut, aStartPos, _rTxt, aDXArray, aKashidaArray, nSpanIdx, nSpanLen); + // in this case we leave aPos at the start and use the DXArray to find the start + // of each subspan + } + + // Restore Font + pFont->SetUnderline( eUnder ); + pFont->SetOverline( eOver ); + pFont->SetStrikeout( eStrike ); + if ( !bUpper ) + pFont->SetPropr( nProp ); + pFont->SetPhysFont(*pOut); +} + +/************************************************************************* + * SvxFont::DrawCapital() draws the uppercase letter. + *************************************************************************/ + +void SvxFont::DrawCapital( OutputDevice *pOut, + const Point &rPos, const OUString &rTxt, + std::span<const sal_Int32> pDXArray, + std::span<const sal_Bool> pKashidaArray, + const sal_Int32 nIdx, const sal_Int32 nLen ) const +{ + SvxDoDrawCapital aDo(const_cast<SvxFont *>(this), pOut, + rTxt, pDXArray, pKashidaArray, + nIdx, nLen, rPos, GetFixKerning()); + DoOnCapitals( aDo ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/textitem.cxx b/editeng/source/items/textitem.cxx new file mode 100644 index 0000000000..e242566bd3 --- /dev/null +++ b/editeng/source/items/textitem.cxx @@ -0,0 +1,2808 @@ +/* -*- 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 <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/frame/status/FontHeight.hpp> +#include <math.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <unotools/configmgr.hxx> +#include <unotools/fontdefs.hxx> +#include <unotools/intlwrapper.hxx> +#include <unotools/syslocale.hxx> +#include <utility> +#include <vcl/outdev.hxx> +#include <vcl/unohelp.hxx> +#include <svtools/unitconv.hxx> + +#include <editeng/editids.hrc> +#include <editeng/editrids.hrc> +#include <tools/bigint.hxx> +#include <tools/mapunit.hxx> +#include <tools/UnitConversion.hxx> + +#include <rtl/math.hxx> +#include <rtl/ustring.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <svl/itemset.hxx> + +#include <svtools/langtab.hxx> +#include <svl/itempool.hxx> +#include <svtools/ctrltool.hxx> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/text/FontEmphasis.hpp> +#include <editeng/rsiditem.hxx> +#include <editeng/memberids.h> +#include <editeng/flstitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/nhypitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/itemtype.hxx> +#include <editeng/eerdll.hxx> +#include <docmodel/color/ComplexColorJSON.hxx> +#include <docmodel/uno/UnoComplexColor.hxx> +#include <docmodel/color/ComplexColor.hxx> +#include <libxml/xmlwriter.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::text; + +SfxPoolItem* SvxFontItem::CreateDefault() {return new SvxFontItem(0);} +SfxPoolItem* SvxPostureItem::CreateDefault() { return new SvxPostureItem(ITALIC_NONE, 0);} +SfxPoolItem* SvxWeightItem::CreateDefault() {return new SvxWeightItem(WEIGHT_NORMAL, 0);} +SfxPoolItem* SvxFontHeightItem::CreateDefault() {return new SvxFontHeightItem(240, 100, 0);} +SfxPoolItem* SvxUnderlineItem::CreateDefault() {return new SvxUnderlineItem(LINESTYLE_NONE, 0);} +SfxPoolItem* SvxOverlineItem::CreateDefault() {return new SvxOverlineItem(LINESTYLE_NONE, 0);} +SfxPoolItem* SvxCrossedOutItem::CreateDefault() {return new SvxCrossedOutItem(STRIKEOUT_NONE, 0);} +SfxPoolItem* SvxShadowedItem::CreateDefault() {return new SvxShadowedItem(false, 0);} +SfxPoolItem* SvxAutoKernItem::CreateDefault() {return new SvxAutoKernItem(false, 0);} +SfxPoolItem* SvxWordLineModeItem::CreateDefault() {return new SvxWordLineModeItem(false, 0);} +SfxPoolItem* SvxContourItem::CreateDefault() {return new SvxContourItem(false, 0);} +SfxPoolItem* SvxColorItem::CreateDefault() {return new SvxColorItem(0);} +SfxPoolItem* SvxKerningItem::CreateDefault() {return new SvxKerningItem(0, 0);} +SfxPoolItem* SvxCaseMapItem::CreateDefault() {return new SvxCaseMapItem(SvxCaseMap::NotMapped, 0);} +SfxPoolItem* SvxEscapementItem::CreateDefault() {return new SvxEscapementItem(0);} +SfxPoolItem* SvxLanguageItem::CreateDefault() {return new SvxLanguageItem(LANGUAGE_GERMAN, 0);} +SfxPoolItem* SvxEmphasisMarkItem::CreateDefault() {return new SvxEmphasisMarkItem(FontEmphasisMark::NONE, TypedWhichId<SvxEmphasisMarkItem>(0));} +SfxPoolItem* SvxCharRotateItem::CreateDefault() {return new SvxCharRotateItem(0_deg10, false, TypedWhichId<SvxCharRotateItem>(0));} +SfxPoolItem* SvxCharScaleWidthItem::CreateDefault() {return new SvxCharScaleWidthItem(100, TypedWhichId<SvxCharScaleWidthItem>(0));} +SfxPoolItem* SvxCharReliefItem::CreateDefault() {return new SvxCharReliefItem(FontRelief::NONE, 0);} + + +// class SvxFontListItem ------------------------------------------------- + +SvxFontListItem::SvxFontListItem( const FontList* pFontLst, + const sal_uInt16 nId ) : + SfxPoolItem( nId ), + pFontList( pFontLst ) +{ + if ( pFontList ) + { + sal_Int32 nCount = pFontList->GetFontNameCount(); + aFontNameSeq.realloc( nCount ); + auto pFontNameSeq = aFontNameSeq.getArray(); + + for ( sal_Int32 i = 0; i < nCount; i++ ) + pFontNameSeq[i] = pFontList->GetFontName(i).GetFamilyName(); + } +} + +SvxFontListItem* SvxFontListItem::Clone( SfxItemPool* ) const +{ + return new SvxFontListItem( *this ); +} + +bool SvxFontListItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return( pFontList == static_cast<const SvxFontListItem&>(rAttr).pFontList ); +} + +bool SvxFontListItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + rVal <<= aFontNameSeq; + return true; +} + + +bool SvxFontListItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +// class SvxFontItem ----------------------------------------------------- + +SvxFontItem::SvxFontItem( const sal_uInt16 nId ) : + SfxPoolItem( nId ) +{ + eFamily = FAMILY_SWISS; + ePitch = PITCH_VARIABLE; + eTextEncoding = RTL_TEXTENCODING_DONTKNOW; +} + + +SvxFontItem::SvxFontItem( const FontFamily eFam, OUString aName, + OUString aStName, const FontPitch eFontPitch, + const rtl_TextEncoding eFontTextEncoding, const sal_uInt16 nId ) : + + SfxPoolItem( nId ), + + aFamilyName(std::move(aName)), + aStyleName(std::move(aStName)) +{ + eFamily = eFam; + ePitch = eFontPitch; + eTextEncoding = eFontTextEncoding; +} + + +bool SvxFontItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + css::awt::FontDescriptor aFontDescriptor; + aFontDescriptor.Name = aFamilyName; + aFontDescriptor.StyleName = aStyleName; + aFontDescriptor.Family = static_cast<sal_Int16>(eFamily); + aFontDescriptor.CharSet = static_cast<sal_Int16>(eTextEncoding); + aFontDescriptor.Pitch = static_cast<sal_Int16>(ePitch); + rVal <<= aFontDescriptor; + } + break; + case MID_FONT_FAMILY_NAME: + rVal <<= aFamilyName; + break; + case MID_FONT_STYLE_NAME: + rVal <<= aStyleName; + break; + case MID_FONT_FAMILY : rVal <<= static_cast<sal_Int16>(eFamily); break; + case MID_FONT_CHAR_SET : rVal <<= static_cast<sal_Int16>(eTextEncoding); break; + case MID_FONT_PITCH : rVal <<= static_cast<sal_Int16>(ePitch); break; + } + return true; +} + +bool SvxFontItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case 0: + { + css::awt::FontDescriptor aFontDescriptor; + if ( !( rVal >>= aFontDescriptor )) + return false; + + aFamilyName = aFontDescriptor.Name; + aStyleName = aFontDescriptor.StyleName; + eFamily = static_cast<FontFamily>(aFontDescriptor.Family); + eTextEncoding = static_cast<rtl_TextEncoding>(aFontDescriptor.CharSet); + ePitch = static_cast<FontPitch>(aFontDescriptor.Pitch); + } + break; + case MID_FONT_FAMILY_NAME : + { + OUString aStr; + if(!(rVal >>= aStr)) + return false; + aFamilyName = aStr; + } + break; + case MID_FONT_STYLE_NAME: + { + OUString aStr; + if(!(rVal >>= aStr)) + return false; + aStyleName = aStr; + } + break; + case MID_FONT_FAMILY : + { + sal_Int16 nFamily = sal_Int16(); + if(!(rVal >>= nFamily)) + return false; + eFamily = static_cast<FontFamily>(nFamily); + } + break; + case MID_FONT_CHAR_SET : + { + sal_Int16 nSet = sal_Int16(); + if(!(rVal >>= nSet)) + return false; + eTextEncoding = static_cast<rtl_TextEncoding>(nSet); + } + break; + case MID_FONT_PITCH : + { + sal_Int16 nPitch = sal_Int16(); + if(!(rVal >>= nPitch)) + return false; + ePitch = static_cast<FontPitch>(nPitch); + } + break; + } + return true; +} + + +bool SvxFontItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + const SvxFontItem& rItem = static_cast<const SvxFontItem&>(rAttr); + + bool bRet = ( eFamily == rItem.eFamily && + aFamilyName == rItem.aFamilyName && + aStyleName == rItem.aStyleName ); + + if ( bRet ) + { + if ( ePitch != rItem.ePitch || eTextEncoding != rItem.eTextEncoding ) + { + bRet = false; + SAL_INFO( "editeng.items", "FontItem::operator==(): only pitch or rtl_TextEncoding different "); + } + } + return bRet; +} + +SvxFontItem* SvxFontItem::Clone( SfxItemPool * ) const +{ + return new SvxFontItem( *this ); +} + +bool SvxFontItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = aFamilyName; + return true; +} + + +void SvxFontItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFontItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("familyName"), BAD_CAST(aFamilyName.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("styleName"), BAD_CAST(aStyleName.toUtf8().getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("family"), BAD_CAST(OString::number(eFamily).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pitch"), BAD_CAST(OString::number(ePitch).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("textEncoding"), BAD_CAST(OString::number(eTextEncoding).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxPostureItem -------------------------------------------------- + +SvxPostureItem::SvxPostureItem( const FontItalic ePosture, const sal_uInt16 nId ) : + SfxEnumItem( nId, ePosture ) +{ +} + +SvxPostureItem* SvxPostureItem::Clone( SfxItemPool * ) const +{ + return new SvxPostureItem( *this ); +} + +sal_uInt16 SvxPostureItem::GetValueCount() const +{ + return ITALIC_NORMAL + 1; // ITALIC_NONE also belongs here +} + + +bool SvxPostureItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + + +OUString SvxPostureItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + DBG_ASSERT( nPos <= sal_uInt16(ITALIC_NORMAL), "enum overflow!" ); + + FontItalic eItalic = static_cast<FontItalic>(nPos); + TranslateId pId; + + switch ( eItalic ) + { + case ITALIC_NONE: pId = RID_SVXITEMS_ITALIC_NONE; break; + case ITALIC_OBLIQUE: pId = RID_SVXITEMS_ITALIC_OBLIQUE; break; + case ITALIC_NORMAL: pId = RID_SVXITEMS_ITALIC_NORMAL; break; + default: ;//prevent warning + } + + return pId ? EditResId(pId) : OUString(); +} + +bool SvxPostureItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_ITALIC: + rVal <<= GetBoolValue(); + break; + case MID_POSTURE: + rVal <<= vcl::unohelper::ConvertFontSlant(GetValue()); + break; + } + return true; +} + +bool SvxPostureItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_ITALIC: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_POSTURE: + { + awt::FontSlant eSlant; + if(!(rVal >>= eSlant)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + eSlant = static_cast<awt::FontSlant>(nValue); + } + SetValue(vcl::unohelper::ConvertFontSlant(eSlant)); + } + } + return true; +} + +bool SvxPostureItem::HasBoolValue() const +{ + return true; +} + +bool SvxPostureItem::GetBoolValue() const +{ + return ( GetValue() >= ITALIC_OBLIQUE ); +} + +void SvxPostureItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? ITALIC_NORMAL : ITALIC_NONE ); +} + +void SvxPostureItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxPostureItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%d", GetValue()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(GetValueTextByPos(GetValue()).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxWeightItem --------------------------------------------------- + +SvxWeightItem::SvxWeightItem( const FontWeight eWght, const sal_uInt16 nId ) : + SfxEnumItem( nId, eWght ) +{ +} + + +bool SvxWeightItem::HasBoolValue() const +{ + return true; +} + + +bool SvxWeightItem::GetBoolValue() const +{ + return GetValue() >= WEIGHT_BOLD; +} + + +void SvxWeightItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? WEIGHT_BOLD : WEIGHT_NORMAL ); +} + + +sal_uInt16 SvxWeightItem::GetValueCount() const +{ + return WEIGHT_BLACK; // WEIGHT_DONTKNOW does not belong +} + +SvxWeightItem* SvxWeightItem::Clone( SfxItemPool * ) const +{ + return new SvxWeightItem( *this ); +} + +bool SvxWeightItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + +OUString SvxWeightItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_WEIGHTS[] = + { + RID_SVXITEMS_WEIGHT_DONTKNOW, + RID_SVXITEMS_WEIGHT_THIN, + RID_SVXITEMS_WEIGHT_ULTRALIGHT, + RID_SVXITEMS_WEIGHT_LIGHT, + RID_SVXITEMS_WEIGHT_SEMILIGHT, + RID_SVXITEMS_WEIGHT_NORMAL, + RID_SVXITEMS_WEIGHT_MEDIUM, + RID_SVXITEMS_WEIGHT_SEMIBOLD, + RID_SVXITEMS_WEIGHT_BOLD, + RID_SVXITEMS_WEIGHT_ULTRABOLD, + RID_SVXITEMS_WEIGHT_BLACK + }; + + static_assert(std::size(RID_SVXITEMS_WEIGHTS) - 1 == WEIGHT_BLACK, "must match"); + assert(nPos <= sal_uInt16(WEIGHT_BLACK) && "enum overflow!" ); + return EditResId(RID_SVXITEMS_WEIGHTS[nPos]); +} + +bool SvxWeightItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_BOLD : + rVal <<= GetBoolValue(); + break; + case MID_WEIGHT: + { + rVal <<= vcl::unohelper::ConvertFontWeight( GetValue() ); + } + break; + } + return true; +} + +bool SvxWeightItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_BOLD : + SetBoolValue(Any2Bool(rVal)); + break; + case MID_WEIGHT: + { + double fValue = 0; + if(!(rVal >>= fValue)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fValue = static_cast<float>(nValue); + } + SetValue( vcl::unohelper::ConvertFontWeight(static_cast<float>(fValue)) ); + } + break; + } + return true; +} + +void SvxWeightItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxWeightItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%d", GetValue()); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(GetValueTextByPos(GetValue()).toUtf8().getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxFontHeightItem ----------------------------------------------- + +SvxFontHeightItem::SvxFontHeightItem( const sal_uInt32 nSz, + const sal_uInt16 nPrp, + const sal_uInt16 nId ) : + SfxPoolItem( nId ) +{ + SetHeight( nSz,nPrp ); // calculate in percentage +} + +SvxFontHeightItem* SvxFontHeightItem::Clone( SfxItemPool * ) const +{ + return new SvxFontHeightItem( *this ); +} + +bool SvxFontHeightItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + return GetHeight() == static_cast<const SvxFontHeightItem&>(rItem).GetHeight() && + GetProp() == static_cast<const SvxFontHeightItem&>(rItem).GetProp() && + GetPropUnit() == static_cast<const SvxFontHeightItem&>(rItem).GetPropUnit(); +} + +bool SvxFontHeightItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + // In StarOne is the uno::Any always 1/100mm. Through the MemberId it is + // controlled if the value in the Item should be 1/100mm or Twips. + + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case 0: + { + css::frame::status::FontHeight aFontHeight; + + // Point (i.e. Twips) is asked for, thus re-calculate if + // CONVERT_TWIPS is not set. + if( bConvert ) + { + aFontHeight.Height = o3tl::convert<double>(nHeight, o3tl::Length::twip, o3tl::Length::pt); + } + else + { + double fPoints = o3tl::convert<double>(nHeight, o3tl::Length::mm100, o3tl::Length::pt); + aFontHeight.Height = rtl::math::round(fPoints, 1); + } + + aFontHeight.Prop = MapUnit::MapRelative == ePropUnit ? nProp : 100; + + float fRet = nProp; + switch( ePropUnit ) + { + case MapUnit::MapRelative: + fRet = 0.; + break; + case MapUnit::Map100thMM: + fRet = o3tl::convert(fRet, o3tl::Length::mm100, o3tl::Length::pt); + break; + case MapUnit::MapPoint: + + break; + case MapUnit::MapTwip: + fRet = o3tl::convert(fRet, o3tl::Length::twip, o3tl::Length::pt); + break; + default: ;//prevent warning + } + aFontHeight.Diff = fRet; + rVal <<= aFontHeight; + } + break; + case MID_FONTHEIGHT: + { + // Point (i.e. Twips) is asked for, thus re-calculate if + // CONVERT_TWIPS is not set. + if( bConvert ) + { + rVal <<= static_cast<float>(o3tl::convert<double>(nHeight, o3tl::Length::twip, o3tl::Length::pt)); + } + else + { + double fPoints = o3tl::convert<double>(nHeight, o3tl::Length::mm100, o3tl::Length::pt); + rVal <<= static_cast<float>(::rtl::math::round(fPoints, 1)); + } + } + break; + case MID_FONTHEIGHT_PROP: + rVal <<= static_cast<sal_Int16>(MapUnit::MapRelative == ePropUnit ? nProp : 100); + break; + case MID_FONTHEIGHT_DIFF: + { + float fRet = nProp; + switch( ePropUnit ) + { + case MapUnit::MapRelative: + fRet = 0.; + break; + case MapUnit::Map100thMM: + fRet = o3tl::convert(fRet, o3tl::Length::mm100, o3tl::Length::pt); + break; + case MapUnit::MapPoint: + + break; + case MapUnit::MapTwip: + fRet = o3tl::convert(fRet, o3tl::Length::twip, o3tl::Length::pt); + break; + default: ;//prevent warning + } + rVal <<= fRet; + } + break; + } + return true; +} + +// Try to reconstruct the original height input value from the modified height +// and the prop data; this seems somewhat futile given the various ways how the +// modified height is calculated (with and without conversion between twips and +// 100th mm; with an additional eCoreMetric input in one of the SetHeight +// overloads), and indeed known to occasionally produce nRet values that would +// be negative, so just guard against negative results here and throw the hands +// up in despair: +static sal_uInt32 lcl_GetRealHeight_Impl(sal_uInt32 nHeight, sal_uInt16 nProp, MapUnit eProp, bool bCoreInTwip) +{ + sal_uInt32 nRet = nHeight; + short nDiff = 0; + switch( eProp ) + { + case MapUnit::MapRelative: + if (nProp) + { + nRet *= 100; + nRet /= nProp; + } + break; + case MapUnit::MapPoint: + { + short nTemp = static_cast<short>(nProp); + nDiff = nTemp * 20; + if(!bCoreInTwip) + nDiff = static_cast<short>(convertTwipToMm100(static_cast<tools::Long>(nDiff))); + break; + } + case MapUnit::Map100thMM: + //then the core is surely also in 1/100 mm + nDiff = static_cast<short>(nProp); + break; + case MapUnit::MapTwip: + // Here surely TWIP + nDiff = static_cast<short>(nProp); + break; + default: + break; + } + nRet = (nDiff < 0 || nRet >= o3tl::make_unsigned(nDiff)) + ? nRet - nDiff : 0; + //TODO: overflow in case nDiff < 0 and nRet - nDiff > SAL_MAX_UINT32 + + return nRet; +} + +bool SvxFontHeightItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + bool bConvert = 0!=(nMemberId&CONVERT_TWIPS); + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case 0: + { + css::frame::status::FontHeight aFontHeight; + if ( rVal >>= aFontHeight ) + { + // Height + ePropUnit = MapUnit::MapRelative; + nProp = 100; + double fPoint = aFontHeight.Height; + if( fPoint < 0. || fPoint > 10000. ) + return false; + + nHeight = static_cast<tools::Long>( fPoint * 20.0 + 0.5 ); // Twips + if (!bConvert) + nHeight = convertTwipToMm100(nHeight); // Convert, if the item contains 1/100mm + + nProp = aFontHeight.Prop; + } + else + return false; + } + break; + case MID_FONTHEIGHT: + { + ePropUnit = MapUnit::MapRelative; + nProp = 100; + double fPoint = 0; + if(!(rVal >>= fPoint)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fPoint = static_cast<float>(nValue); + } + + if (fPoint < 0. || fPoint > 10000.) + return false; + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing && fPoint > 240) + { + SAL_WARN("editeng.items", "SvxFontHeightItem ignoring font size of " << fPoint << " for performance"); + return false; + } + + nHeight = static_cast<tools::Long>( fPoint * 20.0 + 0.5 ); // Twips + if (!bConvert) + nHeight = convertTwipToMm100(nHeight); // Convert, if the item contains 1/100mm + } + break; + case MID_FONTHEIGHT_PROP: + { + sal_Int16 nNew = sal_Int16(); + if(!(rVal >>= nNew)) + return true; + + nHeight = lcl_GetRealHeight_Impl(nHeight, nProp, ePropUnit, bConvert); + + nHeight *= nNew; + nHeight /= 100; + nProp = nNew; + ePropUnit = MapUnit::MapRelative; + } + break; + case MID_FONTHEIGHT_DIFF: + { + nHeight = lcl_GetRealHeight_Impl(nHeight, nProp, ePropUnit, bConvert); + float fValue = 0; + if(!(rVal >>= fValue)) + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + fValue = static_cast<float>(nValue); + } + sal_Int16 nCoreDiffValue = static_cast<sal_Int16>(fValue * 20.); + nHeight += bConvert ? nCoreDiffValue : convertTwipToMm100(nCoreDiffValue); + nProp = static_cast<sal_uInt16>(static_cast<sal_Int16>(fValue)); + ePropUnit = MapUnit::MapPoint; + } + break; + } + return true; +} + + +bool SvxFontHeightItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit eCoreUnit, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& rIntl +) const +{ + if( MapUnit::MapRelative != ePropUnit ) + { + rText = OUString::number( static_cast<short>(nProp) ) + + " " + EditResId( GetMetricId( ePropUnit ) ); + if( 0 <= static_cast<short>(nProp) ) + rText = "+" + rText; + } + else if( 100 == nProp ) + { + rText = GetMetricText( static_cast<tools::Long>(nHeight), + eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + } + else + rText = OUString::number( nProp ) + "%"; + return true; +} + + +void SvxFontHeightItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + nHeight = static_cast<sal_uInt32>(BigInt::Scale( nHeight, nMult, nDiv )); +} + + +bool SvxFontHeightItem::HasMetrics() const +{ + return true; +} + +void SvxFontHeightItem::SetHeight( sal_uInt32 nNewHeight, const sal_uInt16 nNewProp, + MapUnit eUnit ) +{ + DBG_ASSERT( GetRefCount() == 0, "SetValue() with pooled item" ); + + if( MapUnit::MapRelative != eUnit ) + nHeight = nNewHeight + ::ItemToControl( short(nNewProp), eUnit, + FieldUnit::TWIP ); + else if( 100 != nNewProp ) + nHeight = sal_uInt32(( nNewHeight * nNewProp ) / 100 ); + else + nHeight = nNewHeight; + + nProp = nNewProp; + ePropUnit = eUnit; +} + +void SvxFontHeightItem::SetHeight( sal_uInt32 nNewHeight, sal_uInt16 nNewProp, + MapUnit eMetric, MapUnit eCoreMetric ) +{ + DBG_ASSERT( GetRefCount() == 0, "SetValue() with pooled item" ); + + if( MapUnit::MapRelative != eMetric ) + nHeight = nNewHeight + + ::ControlToItem( ::ItemToControl(static_cast<short>(nNewProp), eMetric, + FieldUnit::TWIP ), FieldUnit::TWIP, + eCoreMetric ); + else if( 100 != nNewProp ) + nHeight = sal_uInt32(( nNewHeight * nNewProp ) / 100 ); + else + nHeight = nNewHeight; + + nProp = nNewProp; + ePropUnit = eMetric; +} + +void SvxFontHeightItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxFontHeightItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("height"), BAD_CAST(OString::number(nHeight).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("prop"), BAD_CAST(OString::number(nProp).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("propUnit"), BAD_CAST(OString::number(static_cast<int>(ePropUnit)).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxTextLineItem ------------------------------------------------ + +SvxTextLineItem::SvxTextLineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SfxEnumItem(nId, eSt) + , maColor(COL_TRANSPARENT) +{ +} + + +bool SvxTextLineItem::HasBoolValue() const +{ + return true; +} + + +bool SvxTextLineItem::GetBoolValue() const +{ + return GetValue() != LINESTYLE_NONE; +} + + +void SvxTextLineItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? LINESTYLE_SINGLE : LINESTYLE_NONE ); +} + +SvxTextLineItem* SvxTextLineItem::Clone( SfxItemPool * ) const +{ + return new SvxTextLineItem( *this ); +} + +sal_uInt16 SvxTextLineItem::GetValueCount() const +{ + return LINESTYLE_DOTTED + 1; // LINESTYLE_NONE also belongs here +} + + +bool SvxTextLineItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + if( !maColor.IsTransparent() ) + rText += cpDelim + ::GetColorString(maColor); + return true; +} + + +OUString SvxTextLineItem::GetValueTextByPos( sal_uInt16 /*nPos*/ ) const +{ + OSL_FAIL("SvxTextLineItem::GetValueTextByPos: Pure virtual method"); + return OUString(); +} + +bool SvxTextLineItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_TEXTLINED: + rVal <<= GetBoolValue(); + break; + case MID_TL_STYLE: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + case MID_TL_COLOR: + rVal <<= maColor; + break; + case MID_TL_COMPLEX_COLOR: + { + auto xComplexColor = model::color::createXComplexColor(maComplexColor); + rVal <<= xComplexColor; + break; + } + case MID_TL_HASCOLOR: + rVal <<= maColor.GetAlpha() == 255; + break; + } + return true; +} + +bool SvxTextLineItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch(nMemberId) + { + case MID_TEXTLINED: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_TL_STYLE: + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + bRet = false; + else + SetValue(static_cast<FontLineStyle>(nValue)); + } + break; + case MID_TL_COLOR: + { + Color nCol; + if( !( rVal >>= nCol ) ) + bRet = false; + else + { + // Keep transparence, because it contains the information + // whether the font color or the stored color should be used + sal_uInt8 nAlpha = maColor.GetAlpha(); + maColor = nCol; + maColor.SetAlpha( nAlpha ); + } + } + break; + case MID_TL_COMPLEX_COLOR: + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maComplexColor = model::color::getFromXComplexColor(xComplexColor); + } + break; + case MID_TL_HASCOLOR: + maColor.SetAlpha( Any2Bool( rVal ) ? 255 : 0 ); + break; + } + return bRet; +} + +bool SvxTextLineItem::operator==( const SfxPoolItem& rItem ) const +{ + return SfxEnumItem::operator==( rItem ) && + maColor == static_cast<const SvxTextLineItem&>(rItem).maColor && + maComplexColor == static_cast<const SvxTextLineItem&>(rItem).maComplexColor; +} + +// class SvxUnderlineItem ------------------------------------------------ + + +SvxUnderlineItem::SvxUnderlineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SvxTextLineItem( eSt, nId ) +{ +} + +SvxUnderlineItem* SvxUnderlineItem::Clone( SfxItemPool * ) const +{ + return new SvxUnderlineItem( *this ); +} + +OUString SvxUnderlineItem::GetValueTextByPos( sal_uInt16 nPos ) const +{ + static TranslateId RID_SVXITEMS_UL[] = + { + RID_SVXITEMS_UL_NONE, + RID_SVXITEMS_UL_SINGLE, + RID_SVXITEMS_UL_DOUBLE, + RID_SVXITEMS_UL_DOTTED, + RID_SVXITEMS_UL_DONTKNOW, + RID_SVXITEMS_UL_DASH, + RID_SVXITEMS_UL_LONGDASH, + RID_SVXITEMS_UL_DASHDOT, + RID_SVXITEMS_UL_DASHDOTDOT, + RID_SVXITEMS_UL_SMALLWAVE, + RID_SVXITEMS_UL_WAVE, + RID_SVXITEMS_UL_DOUBLEWAVE, + RID_SVXITEMS_UL_BOLD, + RID_SVXITEMS_UL_BOLDDOTTED, + RID_SVXITEMS_UL_BOLDDASH, + RID_SVXITEMS_UL_BOLDLONGDASH, + RID_SVXITEMS_UL_BOLDDASHDOT, + RID_SVXITEMS_UL_BOLDDASHDOTDOT, + RID_SVXITEMS_UL_BOLDWAVE + }; + static_assert(std::size(RID_SVXITEMS_UL) - 1 == LINESTYLE_BOLDWAVE, "must match"); + assert(nPos <= sal_uInt16(LINESTYLE_BOLDWAVE) && "enum overflow!"); + return EditResId(RID_SVXITEMS_UL[nPos]); +} + +// class SvxOverlineItem ------------------------------------------------ + +SvxOverlineItem::SvxOverlineItem( const FontLineStyle eSt, const sal_uInt16 nId ) + : SvxTextLineItem( eSt, nId ) +{ +} + +SvxOverlineItem* SvxOverlineItem::Clone( SfxItemPool * ) const +{ + return new SvxOverlineItem( *this ); +} + +OUString SvxOverlineItem::GetValueTextByPos( sal_uInt16 nPos ) const +{ + static TranslateId RID_SVXITEMS_OL[] = + { + RID_SVXITEMS_OL_NONE, + RID_SVXITEMS_OL_SINGLE, + RID_SVXITEMS_OL_DOUBLE, + RID_SVXITEMS_OL_DOTTED, + RID_SVXITEMS_OL_DONTKNOW, + RID_SVXITEMS_OL_DASH, + RID_SVXITEMS_OL_LONGDASH, + RID_SVXITEMS_OL_DASHDOT, + RID_SVXITEMS_OL_DASHDOTDOT, + RID_SVXITEMS_OL_SMALLWAVE, + RID_SVXITEMS_OL_WAVE, + RID_SVXITEMS_OL_DOUBLEWAVE, + RID_SVXITEMS_OL_BOLD, + RID_SVXITEMS_OL_BOLDDOTTED, + RID_SVXITEMS_OL_BOLDDASH, + RID_SVXITEMS_OL_BOLDLONGDASH, + RID_SVXITEMS_OL_BOLDDASHDOT, + RID_SVXITEMS_OL_BOLDDASHDOTDOT, + RID_SVXITEMS_OL_BOLDWAVE + }; + static_assert(std::size(RID_SVXITEMS_OL) - 1 == LINESTYLE_BOLDWAVE, "must match"); + assert(nPos <= sal_uInt16(LINESTYLE_BOLDWAVE) && "enum overflow!"); + return EditResId(RID_SVXITEMS_OL[nPos]); +} + +// class SvxCrossedOutItem ----------------------------------------------- + +SvxCrossedOutItem::SvxCrossedOutItem( const FontStrikeout eSt, const sal_uInt16 nId ) + : SfxEnumItem( nId, eSt ) +{ +} + + +bool SvxCrossedOutItem::HasBoolValue() const +{ + return true; +} + + +bool SvxCrossedOutItem::GetBoolValue() const +{ + return GetValue() != STRIKEOUT_NONE; +} + + +void SvxCrossedOutItem::SetBoolValue( bool bVal ) +{ + SetValue( bVal ? STRIKEOUT_SINGLE : STRIKEOUT_NONE ); +} + + +sal_uInt16 SvxCrossedOutItem::GetValueCount() const +{ + return STRIKEOUT_DOUBLE + 1; // STRIKEOUT_NONE belongs also here +} + +SvxCrossedOutItem* SvxCrossedOutItem::Clone( SfxItemPool * ) const +{ + return new SvxCrossedOutItem( *this ); +} + +bool SvxCrossedOutItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetValue() ); + return true; +} + +OUString SvxCrossedOutItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_STRIKEOUT[] = + { + RID_SVXITEMS_STRIKEOUT_NONE, + RID_SVXITEMS_STRIKEOUT_SINGLE, + RID_SVXITEMS_STRIKEOUT_DOUBLE, + RID_SVXITEMS_STRIKEOUT_DONTKNOW, + RID_SVXITEMS_STRIKEOUT_BOLD, + RID_SVXITEMS_STRIKEOUT_SLASH, + RID_SVXITEMS_STRIKEOUT_X + }; + static_assert(std::size(RID_SVXITEMS_STRIKEOUT) - 1 == STRIKEOUT_X, "must match"); + assert(nPos <= sal_uInt16(STRIKEOUT_X) && "enum overflow!"); + return EditResId(RID_SVXITEMS_STRIKEOUT[nPos]); +} + +bool SvxCrossedOutItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_CROSSED_OUT: + rVal <<= GetBoolValue(); + break; + case MID_CROSS_OUT: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + } + return true; +} + +bool SvxCrossedOutItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_CROSSED_OUT: + SetBoolValue(Any2Bool(rVal)); + break; + case MID_CROSS_OUT: + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + SetValue(static_cast<FontStrikeout>(nValue)); + } + break; + } + return true; +} +// class SvxShadowedItem ------------------------------------------------- + +SvxShadowedItem::SvxShadowedItem( const bool bShadowed, const sal_uInt16 nId ) : + SfxBoolItem( nId, bShadowed ) +{ +} + +SvxShadowedItem* SvxShadowedItem::Clone( SfxItemPool * ) const +{ + return new SvxShadowedItem( *this ); +} + +bool SvxShadowedItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_SHADOWED_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_SHADOWED_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxAutoKernItem ------------------------------------------------- + +SvxAutoKernItem::SvxAutoKernItem( const bool bAutoKern, const sal_uInt16 nId ) : + SfxBoolItem( nId, bAutoKern ) +{ +} + +SvxAutoKernItem* SvxAutoKernItem::Clone( SfxItemPool * ) const +{ + return new SvxAutoKernItem( *this ); +} + +bool SvxAutoKernItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_AUTOKERN_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_AUTOKERN_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxWordLineModeItem --------------------------------------------- + +SvxWordLineModeItem::SvxWordLineModeItem( const bool bWordLineMode, + const sal_uInt16 nId ) : + SfxBoolItem( nId, bWordLineMode ) +{ +} + +SvxWordLineModeItem* SvxWordLineModeItem::Clone( SfxItemPool * ) const +{ + return new SvxWordLineModeItem( *this ); +} + +bool SvxWordLineModeItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_WORDLINE_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_WORDLINE_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxContourItem -------------------------------------------------- + +SvxContourItem::SvxContourItem( const bool bContoured, const sal_uInt16 nId ) : + SfxBoolItem( nId, bContoured ) +{ +} + +SvxContourItem* SvxContourItem::Clone( SfxItemPool * ) const +{ + return new SvxContourItem( *this ); +} + +bool SvxContourItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_CONTOUR_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_CONTOUR_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxColorItem ---------------------------------------------------- +SvxColorItem::SvxColorItem( const sal_uInt16 nId ) : + SfxPoolItem(nId), + mColor( COL_BLACK ) +{ +} + +SvxColorItem::SvxColorItem( const Color& rCol, const sal_uInt16 nId ) : + SfxPoolItem( nId ), + mColor( rCol ) +{ +} + +SvxColorItem::SvxColorItem(Color const& rColor, model::ComplexColor const& rComplexColor, const sal_uInt16 nId) + : SfxPoolItem(nId) + , mColor(rColor) + , maComplexColor(rComplexColor) +{ +} + +SvxColorItem::~SvxColorItem() +{ +} + +bool SvxColorItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + const SvxColorItem& rColorItem = static_cast<const SvxColorItem&>(rAttr); + + return mColor == rColorItem.mColor && + maComplexColor == rColorItem.maComplexColor; +} + +bool SvxColorItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch (nMemberId) + { + case MID_COLOR_ALPHA: + { + auto fTransparency = static_cast<double>(255 - mColor.GetAlpha()) * 100 / 255; + rVal <<= static_cast<sal_Int16>(basegfx::fround(fTransparency)); + break; + } + case MID_GRAPHIC_TRANSPARENT: + { + rVal <<= mColor.GetAlpha() == 0; + break; + } + case MID_COLOR_THEME_INDEX: + { + rVal <<= sal_Int16(maComplexColor.getThemeColorType()); + break; + } + case MID_COLOR_TINT_OR_SHADE: + { + sal_Int16 nValue = 0; + for (auto const& rTransform : maComplexColor.getTransformations()) + { + if (rTransform.meType == model::TransformationType::Tint) + nValue = rTransform.mnValue; + else if (rTransform.meType == model::TransformationType::Shade) + nValue = -rTransform.mnValue; + } + rVal <<= nValue; + break; + } + case MID_COLOR_LUM_MOD: + { + sal_Int16 nValue = 10000; + for (auto const& rTransform : maComplexColor.getTransformations()) + { + if (rTransform.meType == model::TransformationType::LumMod) + nValue = rTransform.mnValue; + } + rVal <<= nValue; + break; + } + case MID_COLOR_LUM_OFF: + { + sal_Int16 nValue = 0; + for (auto const& rTransform : maComplexColor.getTransformations()) + { + if (rTransform.meType == model::TransformationType::LumOff) + nValue = rTransform.mnValue; + } + rVal <<= nValue; + break; + } + case MID_COMPLEX_COLOR_JSON: + { + rVal <<= OStringToOUString(model::color::convertToJSON(maComplexColor), RTL_TEXTENCODING_UTF8); + break; + } + case MID_COMPLEX_COLOR: + { + auto xComplexColor = model::color::createXComplexColor(maComplexColor); + rVal <<= xComplexColor; + break; + } + case MID_COLOR_RGB: + default: + { + rVal <<= mColor; + break; + } + } + return true; +} + +bool SvxColorItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_COLOR_ALPHA: + { + sal_Int16 nTransparency = 0; + bool bRet = rVal >>= nTransparency; + if (bRet) + { + auto fTransparency = static_cast<double>(nTransparency) * 255 / 100; + mColor.SetAlpha(255 - static_cast<sal_uInt8>(basegfx::fround(fTransparency))); + } + return bRet; + } + case MID_GRAPHIC_TRANSPARENT: + { + mColor.SetAlpha( Any2Bool( rVal ) ? 0 : 255 ); + return true; + } + case MID_COLOR_THEME_INDEX: + { + sal_Int16 nIndex = -1; + if (!(rVal >>= nIndex)) + return false; + maComplexColor.setThemeColor(model::convertToThemeColorType(nIndex)); + } + break; + case MID_COLOR_TINT_OR_SHADE: + { + sal_Int16 nTintShade = 0; + if (!(rVal >>= nTintShade)) + return false; + + maComplexColor.removeTransformations(model::TransformationType::Tint); + maComplexColor.removeTransformations(model::TransformationType::Shade); + + if (nTintShade > 0) + maComplexColor.addTransformation({model::TransformationType::Tint, nTintShade}); + else if (nTintShade < 0) + { + sal_Int16 nShade = o3tl::narrowing<sal_Int16>(-nTintShade); + maComplexColor.addTransformation({model::TransformationType::Shade, nShade}); + } + } + break; + case MID_COLOR_LUM_MOD: + { + sal_Int16 nLumMod = 10000; + if (!(rVal >>= nLumMod)) + return false; + maComplexColor.removeTransformations(model::TransformationType::LumMod); + maComplexColor.addTransformation({model::TransformationType::LumMod, nLumMod}); + } + break; + case MID_COLOR_LUM_OFF: + { + sal_Int16 nLumOff = 0; + if (!(rVal >>= nLumOff)) + return false; + maComplexColor.removeTransformations(model::TransformationType::LumOff); + maComplexColor.addTransformation({model::TransformationType::LumOff, nLumOff}); + } + break; + case MID_COMPLEX_COLOR_JSON: + { + OUString sComplexColorJson; + if (!(rVal >>= sComplexColorJson)) + return false; + + if (sComplexColorJson.isEmpty()) + return false; + + OString aJSON = OUStringToOString(sComplexColorJson, RTL_TEXTENCODING_ASCII_US); + if (!model::color::convertFromJSON(aJSON, maComplexColor)) + return false; + } + break; + case MID_COMPLEX_COLOR: + { + css::uno::Reference<css::util::XComplexColor> xComplexColor; + if (!(rVal >>= xComplexColor)) + return false; + + if (xComplexColor.is()) + maComplexColor = model::color::getFromXComplexColor(xComplexColor); + } + break; + case MID_COLOR_RGB: + default: + { + if (!(rVal >>= mColor)) + return false; + } + break; + } + return true; +} + +SvxColorItem* SvxColorItem::Clone( SfxItemPool * ) const +{ + return new SvxColorItem( *this ); +} + +bool SvxColorItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = ::GetColorString( mColor ); + return true; +} + +void SvxColorItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxColorItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + + std::stringstream ss; + ss << mColor; + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(ss.str().c_str())); + + OUString aStr; + IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag()); + GetPresentation( SfxItemPresentation::Complete, MapUnit::Map100thMM, MapUnit::Map100thMM, aStr, aIntlWrapper); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"), BAD_CAST(OUStringToOString(aStr, RTL_TEXTENCODING_UTF8).getStr())); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("complex-color")); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), + BAD_CAST(OString::number(sal_Int16(maComplexColor.getType())).getStr())); + + for (auto const& rTransform : maComplexColor.getTransformations()) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("transformation")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("type"), + BAD_CAST(OString::number(sal_Int16(rTransform.meType)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::number(rTransform.mnValue).getStr())); + (void)xmlTextWriterEndElement(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +// class SvxKerningItem -------------------------------------------------- + +SvxKerningItem::SvxKerningItem( const short nKern, const sal_uInt16 nId ) : + SfxInt16Item( nId, nKern ) +{ +} + +SvxKerningItem* SvxKerningItem::Clone( SfxItemPool * ) const +{ + return new SvxKerningItem( *this ); +} + +void SvxKerningItem::ScaleMetrics( tools::Long nMult, tools::Long nDiv ) +{ + SetValue( static_cast<sal_Int16>(BigInt::Scale( GetValue(), nMult, nDiv )) ); +} + + +bool SvxKerningItem::HasMetrics() const +{ + return true; +} + + +bool SvxKerningItem::GetPresentation +( + SfxItemPresentation ePres, + MapUnit eCoreUnit, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& rIntl +) const +{ + switch ( ePres ) + { + case SfxItemPresentation::Nameless: + rText = GetMetricText( static_cast<tools::Long>(GetValue()), eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + return true; + case SfxItemPresentation::Complete: + { + rText = EditResId(RID_SVXITEMS_KERNING_COMPLETE); + TranslateId pId; + + if ( GetValue() > 0 ) + pId = RID_SVXITEMS_KERNING_EXPANDED; + else if ( GetValue() < 0 ) + pId = RID_SVXITEMS_KERNING_CONDENSED; + + if (pId) + rText += EditResId(pId); + rText += GetMetricText( static_cast<tools::Long>(GetValue()), eCoreUnit, MapUnit::MapPoint, &rIntl ) + + " " + EditResId(GetMetricId(MapUnit::MapPoint)); + return true; + } + default: ; //prevent warning + } + return false; +} + +bool SvxKerningItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + sal_Int16 nVal = GetValue(); + if(nMemberId & CONVERT_TWIPS) + nVal = static_cast<sal_Int16>(convertTwipToMm100(nVal)); + rVal <<= nVal; + return true; +} + +bool SvxKerningItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId) +{ + sal_Int16 nVal = sal_Int16(); + if(!(rVal >>= nVal)) + return false; + if(nMemberId & CONVERT_TWIPS) + nVal = o3tl::toTwips(nVal, o3tl::Length::mm100); + SetValue(nVal); + return true; +} + +// class SvxCaseMapItem -------------------------------------------------- + +SvxCaseMapItem::SvxCaseMapItem( const SvxCaseMap eMap, const sal_uInt16 nId ) : + SfxEnumItem( nId, eMap ) +{ +} + +sal_uInt16 SvxCaseMapItem::GetValueCount() const +{ + return sal_uInt16(SvxCaseMap::End); // SvxCaseMap::SmallCaps + 1 +} + +SvxCaseMapItem* SvxCaseMapItem::Clone( SfxItemPool * ) const +{ + return new SvxCaseMapItem( *this ); +} + +bool SvxCaseMapItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetValue()) ); + return true; +} + +OUString SvxCaseMapItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_CASEMAP[] = + { + RID_SVXITEMS_CASEMAP_NONE, + RID_SVXITEMS_CASEMAP_UPPERCASE, + RID_SVXITEMS_CASEMAP_LOWERCASE, + RID_SVXITEMS_CASEMAP_TITLE, + RID_SVXITEMS_CASEMAP_SMALLCAPS + }; + + static_assert(std::size(RID_SVXITEMS_CASEMAP) == size_t(SvxCaseMap::End), "must match"); + assert(nPos < sal_uInt16(SvxCaseMap::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_CASEMAP[nPos]); +} + +bool SvxCaseMapItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + sal_Int16 nRet = style::CaseMap::NONE; + switch( GetValue() ) + { + case SvxCaseMap::Uppercase : nRet = style::CaseMap::UPPERCASE; break; + case SvxCaseMap::Lowercase : nRet = style::CaseMap::LOWERCASE; break; + case SvxCaseMap::Capitalize : nRet = style::CaseMap::TITLE ; break; + case SvxCaseMap::SmallCaps: nRet = style::CaseMap::SMALLCAPS; break; + default: break; + } + rVal <<= nRet; + return true; +} + +bool SvxCaseMapItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + sal_uInt16 nVal = sal_uInt16(); + if(!(rVal >>= nVal)) + return false; + + SvxCaseMap eVal; + switch( nVal ) + { + case style::CaseMap::NONE : eVal = SvxCaseMap::NotMapped; break; + case style::CaseMap::UPPERCASE: eVal = SvxCaseMap::Uppercase; break; + case style::CaseMap::LOWERCASE: eVal = SvxCaseMap::Lowercase; break; + case style::CaseMap::TITLE : eVal = SvxCaseMap::Capitalize; break; + case style::CaseMap::SMALLCAPS: eVal = SvxCaseMap::SmallCaps; break; + default: return false; + } + SetValue(eVal); + return true; +} + +// class SvxEscapementItem ----------------------------------------------- + +SvxEscapementItem::SvxEscapementItem( const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + + nEsc ( 0 ), + nProp ( 100 ) +{ +} + + +SvxEscapementItem::SvxEscapementItem( const SvxEscapement eEscape, + const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + nProp( 100 ) +{ + SetEscapement( eEscape ); + if( nEsc ) + nProp = DFLT_ESC_PROP; +} + + +SvxEscapementItem::SvxEscapementItem( const short _nEsc, + const sal_uInt8 _nProp, + const sal_uInt16 nId ) : + SfxEnumItemInterface( nId ), + nEsc ( _nEsc ), + nProp ( _nProp ) +{ +} + + +bool SvxEscapementItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + + return( nEsc == static_cast<const SvxEscapementItem&>(rAttr).nEsc && + nProp == static_cast<const SvxEscapementItem&>(rAttr).nProp ); +} + +SvxEscapementItem* SvxEscapementItem::Clone( SfxItemPool * ) const +{ + return new SvxEscapementItem( *this ); +} + +sal_uInt16 SvxEscapementItem::GetValueCount() const +{ + return sal_uInt16(SvxEscapement::End); // SvxEscapement::Subscript + 1 +} + + +bool SvxEscapementItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( GetEnumValue() ); + + if ( nEsc != 0 ) + { + if( DFLT_ESC_AUTO_SUPER == nEsc || DFLT_ESC_AUTO_SUB == nEsc ) + rText += EditResId(RID_SVXITEMS_ESCAPEMENT_AUTO); + else + rText += OUString::number( nEsc ) + "%"; + } + return true; +} + +OUString SvxEscapementItem::GetValueTextByPos( sal_uInt16 nPos ) +{ + static TranslateId RID_SVXITEMS_ESCAPEMENT[] = + { + RID_SVXITEMS_ESCAPEMENT_OFF, + RID_SVXITEMS_ESCAPEMENT_SUPER, + RID_SVXITEMS_ESCAPEMENT_SUB + }; + + static_assert(std::size(RID_SVXITEMS_ESCAPEMENT) == size_t(SvxEscapement::End), "must match"); + assert(nPos < sal_uInt16(SvxEscapement::End) && "enum overflow!"); + return EditResId(RID_SVXITEMS_ESCAPEMENT[nPos]); +} + +sal_uInt16 SvxEscapementItem::GetEnumValue() const +{ + if ( nEsc < 0 ) + return sal_uInt16(SvxEscapement::Subscript); + else if ( nEsc > 0 ) + return sal_uInt16(SvxEscapement::Superscript); + return sal_uInt16(SvxEscapement::Off); +} + + +void SvxEscapementItem::SetEnumValue( sal_uInt16 nVal ) +{ + SetEscapement( static_cast<SvxEscapement>(nVal) ); +} + +bool SvxEscapementItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_ESC: + rVal <<= static_cast<sal_Int16>(nEsc); + break; + case MID_ESC_HEIGHT: + rVal <<= static_cast<sal_Int8>(nProp); + break; + case MID_AUTO_ESC: + rVal <<= (DFLT_ESC_AUTO_SUB == nEsc || DFLT_ESC_AUTO_SUPER == nEsc); + break; + } + return true; +} + +bool SvxEscapementItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_ESC: + { + sal_Int16 nVal = sal_Int16(); + if( (rVal >>= nVal) && (std::abs(nVal) <= MAX_ESC_POS+1)) + nEsc = nVal; + else + return false; + } + break; + case MID_ESC_HEIGHT: + { + sal_Int8 nVal = sal_Int8(); + if( (rVal >>= nVal) && (nVal <= 100)) + nProp = nVal; + else + return false; + } + break; + case MID_AUTO_ESC: + { + bool bVal = Any2Bool(rVal); + if(bVal) + { + if(nEsc < 0) + nEsc = DFLT_ESC_AUTO_SUB; + else + nEsc = DFLT_ESC_AUTO_SUPER; + } + else + if(DFLT_ESC_AUTO_SUPER == nEsc ) + --nEsc; + else if(DFLT_ESC_AUTO_SUB == nEsc) + ++nEsc; + } + break; + } + return true; +} + +// class SvxLanguageItem ------------------------------------------------- + +SvxLanguageItem::SvxLanguageItem( const LanguageType eLang, const sal_uInt16 nId ) + : SvxLanguageItem_Base( nId , eLang ) +{ +} + + +sal_uInt16 SvxLanguageItem::GetValueCount() const +{ + // #i50205# got rid of class International + SAL_WARN( "editeng.items", "SvxLanguageItem::GetValueCount: supposed to return a count of what?"); + // Could be SvtLanguageTable::GetEntryCount() (all locales with resource string)? + // Could be LocaleDataWrapper::getInstalledLanguageTypes() (all locales with locale data)? + return 0; +} + +SvxLanguageItem* SvxLanguageItem::Clone( SfxItemPool * ) const +{ + return new SvxLanguageItem( *this ); +} + +bool SvxLanguageItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = SvtLanguageTable::GetLanguageString( GetValue() ); + return true; +} + +bool SvxLanguageItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_LANG_INT: // for basic conversions! + rVal <<= static_cast<sal_Int16>(static_cast<sal_uInt16>(GetValue())); + break; + case MID_LANG_LOCALE: + lang::Locale aRet( LanguageTag::convertToLocale( GetValue(), false)); + rVal <<= aRet; + break; + } + return true; +} + +bool SvxLanguageItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch(nMemberId) + { + case MID_LANG_INT: // for basic conversions! + { + sal_Int32 nValue = 0; + if(!(rVal >>= nValue)) + return false; + + SetValue(LanguageType(nValue)); + } + break; + case MID_LANG_LOCALE: + { + lang::Locale aLocale; + if(!(rVal >>= aLocale)) + return false; + + SetValue( LanguageTag::convertToLanguageType( aLocale, false)); + } + break; + } + return true; +} + +// class SvxNoHyphenItem ------------------------------------------------- + +SvxNoHyphenItem::SvxNoHyphenItem( const sal_uInt16 nId ) : + SfxBoolItem( nId , true ) +{ +} + +SvxNoHyphenItem* SvxNoHyphenItem::Clone( SfxItemPool* ) const +{ + return new SvxNoHyphenItem( *this ); +} + +bool SvxNoHyphenItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +/* + * Dummy item for ToolBox controls: + * + */ + + +// class SvxBlinkItem ------------------------------------------------- + + +SvxBlinkItem::SvxBlinkItem( const bool bBlink, const sal_uInt16 nId ) : + SfxBoolItem( nId, bBlink ) +{ +} + +SvxBlinkItem* SvxBlinkItem::Clone( SfxItemPool * ) const +{ + return new SvxBlinkItem( *this ); +} + +bool SvxBlinkItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + TranslateId pId = RID_SVXITEMS_BLINK_FALSE; + + if ( GetValue() ) + pId = RID_SVXITEMS_BLINK_TRUE; + rText = EditResId(pId); + return true; +} + +// class SvxEmphaisMarkItem --------------------------------------------------- + +SvxEmphasisMarkItem::SvxEmphasisMarkItem( const FontEmphasisMark nValue, + TypedWhichId<SvxEmphasisMarkItem> nId ) + : SfxUInt16Item( nId, static_cast<sal_uInt16>(nValue) ) +{ +} + +SvxEmphasisMarkItem* SvxEmphasisMarkItem::Clone( SfxItemPool * ) const +{ + return new SvxEmphasisMarkItem( *this ); +} + +bool SvxEmphasisMarkItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, + const IntlWrapper& /*rIntl*/ +) const +{ + static TranslateId RID_SVXITEMS_EMPHASIS[] = + { + RID_SVXITEMS_EMPHASIS_NONE_STYLE, + RID_SVXITEMS_EMPHASIS_DOT_STYLE, + RID_SVXITEMS_EMPHASIS_CIRCLE_STYLE, + RID_SVXITEMS_EMPHASIS_DISC_STYLE, + RID_SVXITEMS_EMPHASIS_ACCENT_STYLE + }; + + FontEmphasisMark nVal = GetEmphasisMark(); + rText = EditResId(RID_SVXITEMS_EMPHASIS[ + static_cast<sal_uInt16>(static_cast<FontEmphasisMark>( nVal & FontEmphasisMark::Style ))]); + TranslateId pId = ( FontEmphasisMark::PosAbove & nVal ) + ? RID_SVXITEMS_EMPHASIS_ABOVE_POS + : ( FontEmphasisMark::PosBelow & nVal ) + ? RID_SVXITEMS_EMPHASIS_BELOW_POS + : TranslateId(); + if( pId ) + rText += EditResId( pId ); + return true; +} + +bool SvxEmphasisMarkItem::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_EMPHASIS: + { + FontEmphasisMark nValue = GetEmphasisMark(); + sal_Int16 nRet = 0; + switch(nValue & FontEmphasisMark::Style) + { + case FontEmphasisMark::NONE : nRet = FontEmphasis::NONE; break; + case FontEmphasisMark::Dot : nRet = FontEmphasis::DOT_ABOVE; break; + case FontEmphasisMark::Circle : nRet = FontEmphasis::CIRCLE_ABOVE; break; + case FontEmphasisMark::Disc : nRet = FontEmphasis::DISK_ABOVE; break; + case FontEmphasisMark::Accent : nRet = FontEmphasis::ACCENT_ABOVE; break; + default: break; + } + if(nRet && nValue & FontEmphasisMark::PosBelow) + nRet += 10; + rVal <<= nRet; + } + break; + } + return true; +} + +bool SvxEmphasisMarkItem::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + switch( nMemberId ) + { + case MID_EMPHASIS: + { + sal_Int32 nValue = -1; + rVal >>= nValue; + FontEmphasisMark nMark; + switch(nValue) + { + case FontEmphasis::NONE : nMark = FontEmphasisMark::NONE; break; + case FontEmphasis::DOT_ABOVE : nMark = FontEmphasisMark::Dot|FontEmphasisMark::PosAbove; break; + case FontEmphasis::CIRCLE_ABOVE: nMark = FontEmphasisMark::Circle|FontEmphasisMark::PosAbove; break; + case FontEmphasis::DISK_ABOVE : nMark = FontEmphasisMark::Disc|FontEmphasisMark::PosAbove; break; + case FontEmphasis::ACCENT_ABOVE: nMark = FontEmphasisMark::Accent|FontEmphasisMark::PosAbove; break; + case FontEmphasis::DOT_BELOW : nMark = FontEmphasisMark::Dot|FontEmphasisMark::PosBelow; break; + case FontEmphasis::CIRCLE_BELOW: nMark = FontEmphasisMark::Circle|FontEmphasisMark::PosBelow; break; + case FontEmphasis::DISK_BELOW : nMark = FontEmphasisMark::Disc|FontEmphasisMark::PosBelow; break; + case FontEmphasis::ACCENT_BELOW: nMark = FontEmphasisMark::Accent|FontEmphasisMark::PosBelow; break; + default: return false; + } + SetValue( static_cast<sal_Int16>(nMark) ); + } + break; + } + return true; +} + +/************************************************************************* +|* class SvxTwoLinesItem +*************************************************************************/ + +SvxTwoLinesItem::SvxTwoLinesItem( bool bFlag, sal_Unicode nStartBracket, + sal_Unicode nEndBracket, sal_uInt16 nW ) + : SfxPoolItem( nW ), + cStartBracket( nStartBracket ), cEndBracket( nEndBracket ), bOn( bFlag ) +{ +} + +SvxTwoLinesItem::~SvxTwoLinesItem() +{ +} + +bool SvxTwoLinesItem::operator==( const SfxPoolItem& rAttr ) const +{ + assert(SfxPoolItem::operator==(rAttr)); + return bOn == static_cast<const SvxTwoLinesItem&>(rAttr).bOn && + cStartBracket == static_cast<const SvxTwoLinesItem&>(rAttr).cStartBracket && + cEndBracket == static_cast<const SvxTwoLinesItem&>(rAttr).cEndBracket; +} + +SvxTwoLinesItem* SvxTwoLinesItem::Clone( SfxItemPool* ) const +{ + return new SvxTwoLinesItem( *this ); +} + +bool SvxTwoLinesItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_TWOLINES: + rVal <<= bOn; + break; + case MID_START_BRACKET: + { + OUString s; + if( cStartBracket ) + s = OUString( cStartBracket ); + rVal <<= s; + } + break; + case MID_END_BRACKET: + { + OUString s; + if( cEndBracket ) + s = OUString( cEndBracket ); + rVal <<= s; + } + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxTwoLinesItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = false; + OUString s; + switch( nMemberId ) + { + case MID_TWOLINES: + bOn = Any2Bool( rVal ); + bRet = true; + break; + case MID_START_BRACKET: + if( rVal >>= s ) + { + cStartBracket = s.isEmpty() ? 0 : s[ 0 ]; + bRet = true; + } + break; + case MID_END_BRACKET: + if( rVal >>= s ) + { + cEndBracket = s.isEmpty() ? 0 : s[ 0 ]; + bRet = true; + } + break; + } + return bRet; +} + +bool SvxTwoLinesItem::GetPresentation( SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper& /*rIntl*/ ) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_TWOLINES_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_TWOLINES ); + if( GetStartBracket() ) + rText = OUStringChar(GetStartBracket()) + rText; + if( GetEndBracket() ) + rText += OUStringChar(GetEndBracket()); + } + return true; +} + + +/************************************************************************* +|* class SvxTextRotateItem +*************************************************************************/ + +SvxTextRotateItem::SvxTextRotateItem(Degree10 nValue, TypedWhichId<SvxTextRotateItem> nW) + : SfxUInt16Item(nW, nValue.get()) +{ +} + +SvxTextRotateItem* SvxTextRotateItem::Clone(SfxItemPool*) const +{ + return new SvxTextRotateItem(*this); +} + +bool SvxTextRotateItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if (!GetValue()) + rText = EditResId(RID_SVXITEMS_TEXTROTATE_OFF); + else + { + rText = EditResId(RID_SVXITEMS_TEXTROTATE); + rText = rText.replaceFirst("$(ARG1)", + OUString::number(toDegrees(GetValue()))); + } + return true; +} + +bool SvxTextRotateItem::QueryValue(css::uno::Any& rVal, + sal_uInt8 nMemberId) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch (nMemberId) + { + case MID_ROTATE: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxTextRotateItem::PutValue(const css::uno::Any& rVal, sal_uInt8 nMemberId) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch (nMemberId) + { + case MID_ROTATE: + { + sal_Int16 nVal = 0; + if ((rVal >>= nVal) && (0 == nVal || 900 == nVal || 2700 == nVal)) + SetValue(Degree10(nVal)); + else + bRet = false; + break; + } + default: + bRet = false; + } + return bRet; +} + +void SvxTextRotateItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxTextRotateItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue().get()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + + +/************************************************************************* +|* class SvxCharRotateItem +*************************************************************************/ + +SvxCharRotateItem::SvxCharRotateItem( Degree10 nValue, + bool bFitIntoLine, + TypedWhichId<SvxCharRotateItem> nW ) + : SvxTextRotateItem(nValue, nW), bFitToLine( bFitIntoLine ) +{ +} + +SvxCharRotateItem* SvxCharRotateItem::Clone( SfxItemPool* ) const +{ + return new SvxCharRotateItem( *this ); +} + +bool SvxCharRotateItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_CHARROTATE_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_CHARROTATE ); + rText = rText.replaceFirst( "$(ARG1)", + OUString::number( toDegrees(GetValue()) )); + if( IsFitToLine() ) + rText += EditResId( RID_SVXITEMS_CHARROTATE_FITLINE ); + } + return true; +} + +bool SvxCharRotateItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + bool bRet = true; + switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: + SvxTextRotateItem::QueryValue(rVal, nMemberId); + break; + case MID_FITTOLINE: + rVal <<= IsFitToLine(); + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxCharRotateItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + bool bRet = true; + switch(nMemberId & ~CONVERT_TWIPS) + { + case MID_ROTATE: + { + bRet = SvxTextRotateItem::PutValue(rVal, nMemberId); + break; + } + + case MID_FITTOLINE: + SetFitToLine( Any2Bool( rVal ) ); + break; + default: + bRet = false; + } + return bRet; +} + +bool SvxCharRotateItem::operator==( const SfxPoolItem& rItem ) const +{ + assert(SfxPoolItem::operator==(rItem)); + return SvxTextRotateItem::operator==( rItem ) && + IsFitToLine() == static_cast<const SvxCharRotateItem&>(rItem).IsFitToLine(); +} + +void SvxCharRotateItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxCharRotateItem")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue().get()).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fitToLine"), BAD_CAST(OString::boolean(IsFitToLine()).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +/************************************************************************* +|* class SvxCharScaleItem +*************************************************************************/ + +SvxCharScaleWidthItem::SvxCharScaleWidthItem( sal_uInt16 nValue, + TypedWhichId<SvxCharScaleWidthItem> nW ) + : SfxUInt16Item( nW, nValue ) +{ +} + +SvxCharScaleWidthItem* SvxCharScaleWidthItem::Clone( SfxItemPool* ) const +{ + return new SvxCharScaleWidthItem( *this ); +} + +bool SvxCharScaleWidthItem::GetPresentation( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, MapUnit /*ePresMetric*/, + OUString &rText, const IntlWrapper&) const +{ + if( !GetValue() ) + rText = EditResId( RID_SVXITEMS_CHARSCALE_OFF ); + else + { + rText = EditResId( RID_SVXITEMS_CHARSCALE ); + rText = rText.replaceFirst( "$(ARG1)", + OUString::number( GetValue() )); + } + return true; +} + +bool SvxCharScaleWidthItem::PutValue( const uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + sal_Int16 nValue = sal_Int16(); + if (rVal >>= nValue) + { + SetValue( static_cast<sal_uInt16>(nValue) ); + return true; + } + + SAL_WARN("editeng.items", "SvxCharScaleWidthItem::PutValue - Wrong type!" ); + return false; +} + +bool SvxCharScaleWidthItem::QueryValue( uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + // SfxUInt16Item::QueryValue returns sal_Int32 in Any now... (srx642w) + // where we still want this to be a sal_Int16 + rVal <<= static_cast<sal_Int16>(GetValue()); + return true; +} + +/************************************************************************* +|* class SvxCharReliefItem +*************************************************************************/ + +SvxCharReliefItem::SvxCharReliefItem( FontRelief eValue, + const sal_uInt16 nId ) + : SfxEnumItem( nId, eValue ) +{ +} + +SvxCharReliefItem* SvxCharReliefItem::Clone( SfxItemPool * ) const +{ + return new SvxCharReliefItem( *this ); +} + +static TranslateId RID_SVXITEMS_RELIEF[] = +{ + RID_SVXITEMS_RELIEF_NONE, + RID_SVXITEMS_RELIEF_EMBOSSED, + RID_SVXITEMS_RELIEF_ENGRAVED +}; + +OUString SvxCharReliefItem::GetValueTextByPos(sal_uInt16 nPos) +{ + assert(nPos < std::size(RID_SVXITEMS_RELIEF) && "enum overflow"); + return EditResId(RID_SVXITEMS_RELIEF[nPos]); +} + +sal_uInt16 SvxCharReliefItem::GetValueCount() const +{ + return std::size(RID_SVXITEMS_RELIEF) - 1; +} + +bool SvxCharReliefItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText = GetValueTextByPos( static_cast<sal_uInt16>(GetValue()) ); + return true; +} + +bool SvxCharReliefItem::PutValue( const css::uno::Any& rVal, + sal_uInt8 nMemberId ) +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_RELIEF: + { + sal_Int16 nVal = -1; + rVal >>= nVal; + if(nVal >= 0 && nVal <= sal_Int16(FontRelief::Engraved)) + SetValue( static_cast<FontRelief>(nVal) ); + else + bRet = false; + } + break; + default: + bRet = false; + break; + } + return bRet; +} + +bool SvxCharReliefItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 nMemberId ) const +{ + nMemberId &= ~CONVERT_TWIPS; + bool bRet = true; + switch( nMemberId ) + { + case MID_RELIEF: + rVal <<= static_cast<sal_Int16>(GetValue()); + break; + default: + bRet = false; + break; + } + return bRet; +} + +/************************************************************************* +|* class SvxScriptSetItem +*************************************************************************/ + +SvxScriptSetItem::SvxScriptSetItem( sal_uInt16 nSlotId, SfxItemPool& rPool ) + : SfxSetItem( nSlotId, SfxItemSet( rPool, + svl::Items<SID_ATTR_CHAR_FONT, SID_ATTR_CHAR_FONT> )) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nLatin, nAsian, nComplex ); + GetItemSet().MergeRange( nLatin, nLatin ); + GetItemSet().MergeRange( nAsian, nAsian ); + GetItemSet().MergeRange( nComplex, nComplex ); +} + +SvxScriptSetItem* SvxScriptSetItem::Clone( SfxItemPool * ) const +{ + SvxScriptSetItem* p = new SvxScriptSetItem( Which(), *GetItemSet().GetPool() ); + p->GetItemSet().Put( GetItemSet(), false ); + return p; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScriptSet( + const SfxItemSet& rSet, sal_uInt16 nId ) +{ + const SfxPoolItem* pI; + SfxItemState eSt = rSet.GetItemState( nId, false, &pI ); + if( SfxItemState::SET != eSt ) + pI = SfxItemState::DEFAULT == eSt ? &rSet.Get( nId ) : nullptr; + return pI; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScript( sal_uInt16 nSlotId, const SfxItemSet& rSet, SvtScriptType nScript ) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nSlotId, rSet, nLatin, nAsian, nComplex ); + + const SfxPoolItem *pRet, *pAsn, *pCmplx; + if (nScript == SvtScriptType::ASIAN) + { + pRet = GetItemOfScriptSet( rSet, nAsian ); + } else if (nScript == SvtScriptType::COMPLEX) + { + pRet = GetItemOfScriptSet( rSet, nComplex ); + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::ASIAN)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pAsn = GetItemOfScriptSet( rSet, nAsian )) || + *pRet != *pAsn ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pCmplx ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::ASIAN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nAsian )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pCmplx ) + pRet = nullptr; + } else if (nScript == (SvtScriptType::LATIN|SvtScriptType::ASIAN|SvtScriptType::COMPLEX)) + { + if( nullptr == (pRet = GetItemOfScriptSet( rSet, nLatin )) || + nullptr == (pAsn = GetItemOfScriptSet( rSet, nAsian )) || + nullptr == (pCmplx = GetItemOfScriptSet( rSet, nComplex )) || + *pRet != *pAsn || *pRet != *pCmplx ) + pRet = nullptr; + } else { + //no one valid -> match to latin + pRet = GetItemOfScriptSet( rSet, nLatin ); + } + return pRet; +} + +const SfxPoolItem* SvxScriptSetItem::GetItemOfScript( SvtScriptType nScript ) const +{ + return GetItemOfScript( Which(), GetItemSet(), nScript ); +} + +void SvxScriptSetItem::PutItemForScriptType( SvtScriptType nScriptType, + const SfxPoolItem& rItem ) +{ + sal_uInt16 nLatin, nAsian, nComplex; + GetWhichIds( nLatin, nAsian, nComplex ); + + if( SvtScriptType::LATIN & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nLatin) ); + } + if( SvtScriptType::ASIAN & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nAsian) ); + } + if( SvtScriptType::COMPLEX & nScriptType ) + { + GetItemSet().Put( rItem.CloneSetWhich(nComplex) ); + } +} + +void SvxScriptSetItem::GetWhichIds( sal_uInt16 nSlotId, const SfxItemSet& rSet, sal_uInt16& rLatin, sal_uInt16& rAsian, sal_uInt16& rComplex ) +{ + const SfxItemPool& rPool = *rSet.GetPool(); + GetSlotIds( nSlotId, rLatin, rAsian, rComplex ); + rLatin = rPool.GetWhich( rLatin ); + rAsian = rPool.GetWhich( rAsian ); + rComplex = rPool.GetWhich( rComplex ); +} + +void SvxScriptSetItem::GetWhichIds( sal_uInt16& rLatin, sal_uInt16& rAsian, + sal_uInt16& rComplex ) const +{ + GetWhichIds( Which(), GetItemSet(), rLatin, rAsian, rComplex ); +} + +void SvxScriptSetItem::GetSlotIds( sal_uInt16 nSlotId, sal_uInt16& rLatin, + sal_uInt16& rAsian, sal_uInt16& rComplex ) +{ + switch( nSlotId ) + { + default: + SAL_WARN( "editeng.items", "wrong SlotId for class SvxScriptSetItem" ); + [[fallthrough]]; // default to font - Id Range !! + + case SID_ATTR_CHAR_FONT: + rLatin = SID_ATTR_CHAR_FONT; + rAsian = SID_ATTR_CHAR_CJK_FONT; + rComplex = SID_ATTR_CHAR_CTL_FONT; + break; + case SID_ATTR_CHAR_FONTHEIGHT: + rLatin = SID_ATTR_CHAR_FONTHEIGHT; + rAsian = SID_ATTR_CHAR_CJK_FONTHEIGHT; + rComplex = SID_ATTR_CHAR_CTL_FONTHEIGHT; + break; + case SID_ATTR_CHAR_WEIGHT: + rLatin = SID_ATTR_CHAR_WEIGHT; + rAsian = SID_ATTR_CHAR_CJK_WEIGHT; + rComplex = SID_ATTR_CHAR_CTL_WEIGHT; + break; + case SID_ATTR_CHAR_POSTURE: + rLatin = SID_ATTR_CHAR_POSTURE; + rAsian = SID_ATTR_CHAR_CJK_POSTURE; + rComplex = SID_ATTR_CHAR_CTL_POSTURE; + break; + case SID_ATTR_CHAR_LANGUAGE: + rLatin = SID_ATTR_CHAR_LANGUAGE; + rAsian = SID_ATTR_CHAR_CJK_LANGUAGE; + rComplex = SID_ATTR_CHAR_CTL_LANGUAGE; + break; + case SID_ATTR_CHAR_SHADOWED: + rLatin = SID_ATTR_CHAR_SHADOWED; + rAsian = SID_ATTR_CHAR_SHADOWED; + rComplex = SID_ATTR_CHAR_SHADOWED; + break; + case SID_ATTR_CHAR_STRIKEOUT: + rLatin = SID_ATTR_CHAR_STRIKEOUT; + rAsian = SID_ATTR_CHAR_STRIKEOUT; + rComplex = SID_ATTR_CHAR_STRIKEOUT; + break; + } +} + +void GetDefaultFonts( SvxFontItem& rLatin, SvxFontItem& rAsian, SvxFontItem& rComplex ) +{ + const sal_uInt16 nItemCnt = 3; + + static struct + { + DefaultFontType nFontType; + LanguageType nLanguage; + } + const aOutTypeArr[ nItemCnt ] = + { + { DefaultFontType::LATIN_TEXT, LANGUAGE_ENGLISH_US }, + { DefaultFontType::CJK_TEXT, LANGUAGE_ENGLISH_US }, + { DefaultFontType::CTL_TEXT, LANGUAGE_ARABIC_SAUDI_ARABIA } + }; + + SvxFontItem* aItemArr[ nItemCnt ] = { &rLatin, &rAsian, &rComplex }; + + for ( sal_uInt16 n = 0; n < nItemCnt; ++n ) + { + vcl::Font aFont( OutputDevice::GetDefaultFont( aOutTypeArr[ n ].nFontType, + aOutTypeArr[ n ].nLanguage, + GetDefaultFontFlags::OnlyOne ) ); + SvxFontItem* pItem = aItemArr[ n ]; + pItem->SetFamily( aFont.GetFamilyType() ); + pItem->SetFamilyName( aFont.GetFamilyName() ); + pItem->SetStyleName( OUString() ); + pItem->SetPitch( aFont.GetPitch()); + pItem->SetCharSet(aFont.GetCharSet()); + } +} + + +bool SvxRsidItem::QueryValue( uno::Any& rVal, sal_uInt8 ) const +{ + rVal <<= GetValue(); + return true; +} + +bool SvxRsidItem::PutValue( const uno::Any& rVal, sal_uInt8 ) +{ + sal_uInt32 nRsid = 0; + if( !( rVal >>= nRsid ) ) + return false; + + SetValue( nRsid ); + return true; +} + +SvxRsidItem* SvxRsidItem::Clone( SfxItemPool * ) const +{ + return new SvxRsidItem( *this ); +} + +bool SvxRsidItem::GetPresentation +( + SfxItemPresentation /*ePres*/, + MapUnit /*eCoreUnit*/, + MapUnit /*ePresUnit*/, + OUString& rText, const IntlWrapper& /*rIntl*/ +) const +{ + rText.clear(); + return false; +} + +void SvxRsidItem::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxRsidItem")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("whichId"), "%d", Which()); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("value"), "%" SAL_PRIuUINT32, GetValue()); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/writingmodeitem.cxx b/editeng/source/items/writingmodeitem.cxx new file mode 100644 index 0000000000..35dbddabab --- /dev/null +++ b/editeng/source/items/writingmodeitem.cxx @@ -0,0 +1,94 @@ +/* -*- 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 <editeng/writingmodeitem.hxx> +#include <editeng/frmdir.hxx> +#include <editeng/eerdll.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::text; + + +SvxWritingModeItem::SvxWritingModeItem( WritingMode eValue, TypedWhichId<SvxWritingModeItem> _nWhich ) + : SfxUInt16Item( _nWhich, static_cast<sal_uInt16>(eValue) ) +{ +} + +SvxWritingModeItem::~SvxWritingModeItem() +{ +} + +SvxWritingModeItem* SvxWritingModeItem::Clone( SfxItemPool * ) const +{ + return new SvxWritingModeItem( *this ); +} + +bool SvxWritingModeItem::GetPresentation( SfxItemPresentation /*ePres*/, + MapUnit /*eCoreMetric*/, + MapUnit /*ePresMetric*/, + OUString &rText, + const IntlWrapper& ) const +{ + rText = EditResId(getFrmDirResId(static_cast<int>(GetValue()))); + return true; +} + +bool SvxWritingModeItem::PutValue( const css::uno::Any& rVal, sal_uInt8 ) +{ + sal_Int32 nVal = 0; + bool bRet = ( rVal >>= nVal ); + + if( !bRet ) + { + WritingMode eMode; + bRet = rVal >>= eMode; + + if( bRet ) + { + nVal = static_cast<sal_Int32>(eMode); + } + } + + if( bRet ) + { + switch( static_cast<WritingMode>(nVal) ) + { + case WritingMode_LR_TB: + case WritingMode_RL_TB: + case WritingMode_TB_RL: + SetValue( static_cast<sal_uInt16>(nVal) ); + bRet = true; + break; + default: + bRet = false; + break; + } + } + + return bRet; +} + +bool SvxWritingModeItem::QueryValue( css::uno::Any& rVal, + sal_uInt8 ) const +{ + rVal <<= GetValue(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/items/xmlcnitm.cxx b/editeng/source/items/xmlcnitm.cxx new file mode 100644 index 0000000000..7507ed2afd --- /dev/null +++ b/editeng/source/items/xmlcnitm.cxx @@ -0,0 +1,206 @@ +/* -*- 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 <memory> + +#include <comphelper/servicehelper.hxx> +#include <com/sun/star/xml/AttributeData.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <o3tl/any.hxx> +#include <xmloff/xmlcnimp.hxx> +#include <xmloff/unoatrcn.hxx> +#include <editeng/xmlcnitm.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::xml; + + +SvXMLAttrContainerItem::SvXMLAttrContainerItem( sal_uInt16 _nWhich ) : + SfxPoolItem( _nWhich ) +{ +} + +SvXMLAttrContainerItem::SvXMLAttrContainerItem( + const SvXMLAttrContainerItem& rItem ) : + SfxPoolItem( rItem ), + maContainerData( rItem.maContainerData ) +{ +} + +SvXMLAttrContainerItem::~SvXMLAttrContainerItem() +{ +} + +bool SvXMLAttrContainerItem::operator==( const SfxPoolItem& rItem ) const +{ + return SfxPoolItem::operator==(rItem) && + maContainerData == static_cast<const SvXMLAttrContainerItem&>(rItem).maContainerData; +} + +bool SvXMLAttrContainerItem::GetPresentation( + SfxItemPresentation /*ePresentation*/, + MapUnit /*eCoreMetric*/, + MapUnit /*ePresentationMetric*/, + OUString & /*rText*/, + const IntlWrapper& /*rIntlWrapper*/ ) const +{ + return false; +} + +bool SvXMLAttrContainerItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const +{ + Reference<XNameContainer> xContainer + = new SvUnoAttributeContainer(std::make_unique<SvXMLAttrContainerData>(maContainerData)); + + rVal <<= xContainer; + return true; +} + +bool SvXMLAttrContainerItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) +{ + Reference<XInterface> xTunnel(rVal, UNO_QUERY); + if (auto pContainer = dynamic_cast<SvUnoAttributeContainer*>(xTunnel.get())) + { + maContainerData = *pContainer->GetContainerImpl(); + } + else + { + SvXMLAttrContainerData aNewImpl; + + try + { + Reference<XNameContainer> xContainer( rVal, UNO_QUERY ); + if( !xContainer.is() ) + return false; + + const Sequence< OUString > aNameSequence( xContainer->getElementNames() ); + const OUString* pNames = aNameSequence.getConstArray(); + const sal_Int32 nCount = aNameSequence.getLength(); + Any aAny; + sal_Int32 nAttr; + + for( nAttr = 0; nAttr < nCount; nAttr++ ) + { + const OUString aName( *pNames++ ); + + aAny = xContainer->getByName( aName ); + auto pData = o3tl::tryAccess<AttributeData>(aAny); + if( !pData ) + return false; + + sal_Int32 pos = aName.indexOf( ':' ); + if( pos != -1 ) + { + const OUString aPrefix( aName.copy( 0, pos )); + const OUString aLName( aName.copy( pos+1 )); + + if( pData->Namespace.isEmpty() ) + { + if( !aNewImpl.AddAttr( aPrefix, aLName, pData->Value ) ) + break; + } + else + { + if( !aNewImpl.AddAttr( aPrefix, pData->Namespace, aLName, pData->Value ) ) + break; + } + } + else + { + if( !aNewImpl.AddAttr( aName, pData->Value ) ) + break; + } + } + + if( nAttr == nCount ) + maContainerData = std::move(aNewImpl); + else + return false; + } + catch(...) + { + return false; + } + } + return true; +} + + +bool SvXMLAttrContainerItem::AddAttr( const OUString& rLName, + const OUString& rValue ) +{ + return maContainerData.AddAttr( rLName, rValue ); +} + +bool SvXMLAttrContainerItem::AddAttr( const OUString& rPrefix, + const OUString& rNamespace, const OUString& rLName, + const OUString& rValue ) +{ + return maContainerData.AddAttr( rPrefix, rNamespace, rLName, rValue ); +} + +sal_uInt16 SvXMLAttrContainerItem::GetAttrCount() const +{ + return static_cast<sal_uInt16>(maContainerData.GetAttrCount()); +} + +OUString SvXMLAttrContainerItem::GetAttrNamespace( sal_uInt16 i ) const +{ + return maContainerData.GetAttrNamespace( i ); +} + +OUString SvXMLAttrContainerItem::GetAttrPrefix( sal_uInt16 i ) const +{ + return maContainerData.GetAttrPrefix( i ); +} + +const OUString& SvXMLAttrContainerItem::GetAttrLName( sal_uInt16 i ) const +{ + return maContainerData.GetAttrLName( i ); +} + +const OUString& SvXMLAttrContainerItem::GetAttrValue( sal_uInt16 i ) const +{ + return maContainerData.GetAttrValue( i ); +} + + +sal_uInt16 SvXMLAttrContainerItem::GetFirstNamespaceIndex() const +{ + return maContainerData.GetFirstNamespaceIndex(); +} + +sal_uInt16 SvXMLAttrContainerItem::GetNextNamespaceIndex( sal_uInt16 nIdx ) const +{ + return maContainerData.GetNextNamespaceIndex( nIdx ); +} + +const OUString& SvXMLAttrContainerItem::GetNamespace( sal_uInt16 i ) const +{ + return maContainerData.GetNamespace( i ); +} + +const OUString& SvXMLAttrContainerItem::GetPrefix( sal_uInt16 i ) const +{ + return maContainerData.GetPrefix( i ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/lookuptree/Trie.cxx b/editeng/source/lookuptree/Trie.cxx new file mode 100644 index 0000000000..6505c00e43 --- /dev/null +++ b/editeng/source/lookuptree/Trie.cxx @@ -0,0 +1,186 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <editeng/Trie.hxx> + +namespace editeng +{ + +/* TrieNode */ + +struct TrieNode final +{ + static const int LATIN_ARRAY_SIZE = 26; + + sal_Unicode mCharacter; + bool mMarker; + std::vector<std::unique_ptr<TrieNode>> mChildren; + std::unique_ptr<TrieNode> mLatinArray[LATIN_ARRAY_SIZE]; + + explicit TrieNode(sal_Unicode aCharacter = '\0'); + + void markWord(); + TrieNode* findChild(sal_Unicode aCharacter); + TrieNode* traversePath(std::u16string_view sPath); + void addNewChild(TrieNode* pChild); + void collectSuggestions(std::u16string_view sPath, std::vector<OUString>& rSuggestionList); + static void collectSuggestionsForCurrentNode(TrieNode* pCurrent, std::u16string_view sPath, std::vector<OUString>& rSuggestionList); +}; + +TrieNode::TrieNode(sal_Unicode aCharacter) : + mCharacter(aCharacter), + mMarker(false) +{ + for (auto & i : mLatinArray) + { + i = nullptr; + } +} + +void TrieNode::markWord() +{ + mMarker = true; +} + +void TrieNode::addNewChild(TrieNode* pChild) +{ + if (pChild->mCharacter >= 'a' && + pChild->mCharacter <= 'z') + { + mLatinArray[pChild->mCharacter - u'a'].reset(pChild); + } + else + { + mChildren.push_back(std::unique_ptr<TrieNode>(pChild)); + } +} + +TrieNode* TrieNode::findChild(sal_Unicode aInputCharacter) +{ + if (aInputCharacter >= 'a' && + aInputCharacter <= 'z') + { + return mLatinArray[aInputCharacter - u'a'].get(); + } + + for(auto const & pCurrent : mChildren) + { + if ( pCurrent->mCharacter == aInputCharacter ) + return pCurrent.get(); + } + + return nullptr; +} + +void TrieNode::collectSuggestions(std::u16string_view sPath, std::vector<OUString>& rSuggestionList) +{ + // first traverse nodes for alphabet characters + for (auto const & pCurrent : mLatinArray) + { + if (pCurrent != nullptr) + collectSuggestionsForCurrentNode(pCurrent.get(), sPath, rSuggestionList); + } + + // traverse nodes for other characters + for(auto const & pCurrent : mChildren) + { + if (pCurrent != nullptr) + collectSuggestionsForCurrentNode(pCurrent.get(), sPath, rSuggestionList); + } +} + +void TrieNode::collectSuggestionsForCurrentNode(TrieNode* pCurrent, std::u16string_view sPath, std::vector<OUString>& rSuggestionList) +{ + OUString aStringPath = sPath + OUStringChar(pCurrent->mCharacter); + if(pCurrent->mMarker) + { + rSuggestionList.push_back(aStringPath); + } + // recursively descend tree + pCurrent->collectSuggestions(aStringPath, rSuggestionList); +} + +TrieNode* TrieNode::traversePath(std::u16string_view sPath) +{ + TrieNode* pCurrent = this; + + for ( const auto aCurrentChar : sPath ) + { + pCurrent = pCurrent->findChild(aCurrentChar); + if ( pCurrent == nullptr ) + return nullptr; + } + + return pCurrent; +} + +/* TRIE */ + +Trie::Trie() : + mRoot(new TrieNode()) +{} + +Trie::~Trie() +{} + +void Trie::insert(std::u16string_view sInputString) const +{ + // adding an empty word is not allowed + if ( sInputString.empty() ) + { + return; + } + + // traverse the input string and modify the tree with new nodes / characters + + TrieNode* pCurrent = mRoot.get(); + + for ( const auto aCurrentChar : sInputString ) + { + TrieNode* pChild = pCurrent->findChild(aCurrentChar); + if ( pChild == nullptr ) + { + TrieNode* pNewNode = new TrieNode(aCurrentChar); + pCurrent->addNewChild(pNewNode); + pCurrent = pNewNode; + } + else + { + pCurrent = pChild; + } + } + + pCurrent->markWord(); +} + +void Trie::findSuggestions(std::u16string_view sWordPart, std::vector<OUString>& rSuggestionList) const +{ + TrieNode* pNode = mRoot->traversePath(sWordPart); + + if (pNode != nullptr) + { + pNode->collectSuggestions(sWordPart, rSuggestionList); + } +} + +size_t Trie::size() const +{ + if (!mRoot) + return 0; + std::vector<OUString> entries; + mRoot->collectSuggestions(std::u16string_view(), entries); + return entries.size(); +} + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.cxx b/editeng/source/misc/SvXMLAutoCorrectExport.cxx new file mode 100644 index 0000000000..8faf434116 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.cxx @@ -0,0 +1,110 @@ +/* -*- 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 "SvXMLAutoCorrectExport.hxx" + +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/XDocumentHandler.hpp> +#include <xmloff/namespacemap.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +SvXMLAutoCorrectExport::SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + pAutocorr_List( pNewAutocorr_List ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLAutoCorrectExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + const SvxAutocorrWordList::AutocorrWordSetType& rContent = pAutocorr_List->getSortedContent(); + for (auto const& content : rContent) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + content.GetShort()); + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_NAME, + content.IsTextOnly() ? content.GetLong() : content.GetShort()); + + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +SvXMLExceptionListExport::SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler) +: SvXMLExport( xContext, "", rFileName, util::MeasureUnit::CM, rHandler ), + rList( rNewList ) +{ + GetNamespaceMap_().Add( GetXMLToken ( XML_NP_BLOCK_LIST ), + GetXMLToken ( XML_N_BLOCK_LIST ), + XML_NAMESPACE_BLOCKLIST ); +} + +ErrCode SvXMLExceptionListExport::exportDoc(enum XMLTokenEnum /*eClass*/) +{ + GetDocHandler()->startDocument(); + + addChaffWhenEncryptedStorage(); + + AddAttribute ( XML_NAMESPACE_NONE, + GetNamespaceMap_().GetAttrNameByKey ( XML_NAMESPACE_BLOCKLIST ), + GetNamespaceMap_().GetNameByKey ( XML_NAMESPACE_BLOCKLIST ) ); + { + SvXMLElementExport aRoot (*this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK_LIST, true, true); + sal_uInt16 nBlocks= rList.size(); + for ( sal_uInt16 i = 0; i < nBlocks; i++) + { + AddAttribute( XML_NAMESPACE_BLOCKLIST, + XML_ABBREVIATED_NAME, + rList[i] ); + SvXMLElementExport aBlock( *this, XML_NAMESPACE_BLOCKLIST, XML_BLOCK, true, true); + } + } + GetDocHandler()->endDocument(); + return ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectExport.hxx b/editeng/source/misc/SvXMLAutoCorrectExport.hxx new file mode 100644 index 0000000000..58570e3ad1 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectExport.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ +#pragma once + +#include <xmloff/xmlexp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectExport : public SvXMLExport +{ +private: + const SvxAutocorrWordList *pAutocorr_List; +public: + SvXMLAutoCorrectExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvxAutocorrWordList * pNewAutocorr_List, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +class SvStringsISortDtor; + +class SvXMLExceptionListExport : public SvXMLExport +{ +private: + const SvStringsISortDtor & rList; +public: + SvXMLExceptionListExport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const SvStringsISortDtor &rNewList, + const OUString &rFileName, + css::uno::Reference< css::xml::sax::XDocumentHandler> const &rHandler); + + ErrCode exportDoc(enum ::xmloff::token::XMLTokenEnum eClass = ::xmloff::token::XML_TOKEN_INVALID) override; + void ExportAutoStyles_() override {} + void ExportMasterStyles_ () override {} + void ExportContent_() override {} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.cxx b/editeng/source/misc/SvXMLAutoCorrectImport.cxx new file mode 100644 index 0000000000..baeef88612 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <utility> + +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" + +using namespace css; +using namespace css::xml::sax; + +SvXMLAutoCorrectImport::SvXMLAutoCorrectImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage) +: SvXMLImport( xContext, "" ), + pAutocorr_List (pNewAutocorr_List), + rAutoCorrect ( rNewAutoCorrect ), + xStorage (std::move( xNewStorage )) +{ +} + +SvXMLAutoCorrectImport::~SvXMLAutoCorrectImport() noexcept +{ +} + +SvXMLImportContext *SvXMLAutoCorrectImport::CreateFastContext( sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLWordListContext( *this ); + return nullptr; +} + +SvXMLWordListContext::SvXMLWordListContext( + SvXMLAutoCorrectImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ + rLocalRef.rAutoCorrect.refreshBlockList( rLocalRef.xStorage ); +} + +css::uno::Reference<XFastContextHandler> SAL_CALL SvXMLWordListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLWordContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLWordListContext::~SvXMLWordListContext() +{ +} + +SvXMLWordContext::SvXMLWordContext( + SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWrong, sRight; + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWrong = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if ( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::NAME ) ) + sRight = xAttrList->getValue( SvXMLAutoCorrectToken::NAME ); + + if ( sWrong.isEmpty() || sRight.isEmpty()) + return; + + bool bOnlyTxt = sRight != sWrong; + if( !bOnlyTxt ) + { + const OUString sLongSave( sRight ); + if( !rImport.rAutoCorrect.GetLongText( sWrong, sRight ) && + !sLongSave.isEmpty() ) + { + sRight = sLongSave; + bOnlyTxt = true; + } + } + rImport.pAutocorr_List->LoadEntry( sWrong, sRight, bOnlyTxt ); +} + +SvXMLWordContext::~SvXMLWordContext() +{ +} + +SvXMLExceptionListImport::SvXMLExceptionListImport( + const uno::Reference< uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ) +: SvXMLImport( xContext, "" ), + rList (rNewList) +{ +} + +SvXMLExceptionListImport::~SvXMLExceptionListImport() noexcept +{ +} + +SvXMLImportContext *SvXMLExceptionListImport::CreateFastContext(sal_Int32 Element, + const uno::Reference< xml::sax::XFastAttributeList > & /*xAttrList*/ ) +{ + if( Element == SvXMLAutoCorrectToken::BLOCKLIST ) + return new SvXMLExceptionListContext( *this ); + return nullptr; +} + +SvXMLExceptionListContext::SvXMLExceptionListContext( + SvXMLExceptionListImport& rImport ) : + SvXMLImportContext ( rImport ), + rLocalRef(rImport) +{ +} + +css::uno::Reference<xml::sax::XFastContextHandler> SAL_CALL SvXMLExceptionListContext::createFastChildContext( + sal_Int32 Element, const uno::Reference< xml::sax::XFastAttributeList > & xAttrList ) +{ + if ( Element == SvXMLAutoCorrectToken::BLOCK ) + return new SvXMLExceptionContext (rLocalRef, xAttrList); + return nullptr; +} + +SvXMLExceptionListContext::~SvXMLExceptionListContext() +{ +} + +SvXMLExceptionContext::SvXMLExceptionContext( + SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) : + SvXMLImportContext ( rImport ) +{ + OUString sWord; + if( xAttrList.is() && xAttrList->hasAttribute( SvXMLAutoCorrectToken::ABBREVIATED_NAME ) ) + sWord = xAttrList->getValue( SvXMLAutoCorrectToken::ABBREVIATED_NAME ); + + if (sWord.isEmpty()) + return; + + rImport.rList.insert( sWord ); +} + +SvXMLExceptionContext::~SvXMLExceptionContext() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectImport.hxx b/editeng/source/misc/SvXMLAutoCorrectImport.hxx new file mode 100644 index 0000000000..961e6963d7 --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectImport.hxx @@ -0,0 +1,113 @@ +/* -*- 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 . + */ +#pragma once + +#include <sot/storage.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/xmlimp.hxx> +#include <editeng/svxacorr.hxx> + +class SvXMLAutoCorrectImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + +public: + SvxAutocorrWordList *pAutocorr_List; + SvxAutoCorrect &rAutoCorrect; + css::uno::Reference < css::embed::XStorage > xStorage; + + SvXMLAutoCorrectImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvxAutocorrWordList *pNewAutocorr_List, + SvxAutoCorrect &rNewAutoCorrect, + css::uno::Reference < css::embed::XStorage > xNewStorage); + + virtual ~SvXMLAutoCorrectImport() noexcept override; +}; + +class SvXMLWordListContext : public SvXMLImportContext +{ +private: + SvXMLAutoCorrectImport & rLocalRef; +public: + SvXMLWordListContext ( SvXMLAutoCorrectImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLWordListContext() override; +}; + +class SvXMLWordContext : public SvXMLImportContext +{ +public: + SvXMLWordContext ( SvXMLAutoCorrectImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLWordContext() override; +}; + + +class SvXMLExceptionListImport : public SvXMLImport +{ +protected: + + // This method is called after the namespace map has been updated, but + // before a context for the current element has been pushed. + virtual SvXMLImportContext *CreateFastContext( sal_Int32 Element, const css::uno::Reference< + css::xml::sax::XFastAttributeList > & xAttrList ) override; +public: + SvStringsISortDtor &rList; + + SvXMLExceptionListImport( + const css::uno::Reference< css::uno::XComponentContext > & xContext, + SvStringsISortDtor & rNewList ); + + virtual ~SvXMLExceptionListImport() noexcept override; +}; + +class SvXMLExceptionListContext : public SvXMLImportContext +{ +private: + SvXMLExceptionListImport & rLocalRef; +public: + SvXMLExceptionListContext ( SvXMLExceptionListImport& rImport ); + + virtual css::uno::Reference<XFastContextHandler> SAL_CALL createFastChildContext( sal_Int32 Element, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ) override; + + virtual ~SvXMLExceptionListContext() override; +}; + +class SvXMLExceptionContext : public SvXMLImportContext +{ +public: + SvXMLExceptionContext ( SvXMLExceptionListImport& rImport, + const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList ); + + virtual ~SvXMLExceptionContext() override; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx new file mode 100644 index 0000000000..4bdadcdcde --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.cxx @@ -0,0 +1,54 @@ +/* -*- 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/. + */ + +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <xmloff/xmltoken.hxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-register" +#endif +#endif +#include <tokens.cxx> +#if defined __clang__ +#if __has_warning("-Wdeprecated-register") +#pragma GCC diagnostic pop +#endif +#endif + +using namespace css::uno; +using namespace ::xmloff::token; + +SvXMLAutoCorrectTokenHandler::SvXMLAutoCorrectTokenHandler() +{ +} + +SvXMLAutoCorrectTokenHandler::~SvXMLAutoCorrectTokenHandler() +{ +} + +sal_Int32 SAL_CALL SvXMLAutoCorrectTokenHandler::getTokenFromUTF8( const Sequence< sal_Int8 >& Identifier ) +{ + return getTokenDirect( reinterpret_cast< const char* >( Identifier.getConstArray() ), Identifier.getLength() ); +} + +Sequence< sal_Int8 > SAL_CALL SvXMLAutoCorrectTokenHandler::getUTF8Identifier( sal_Int32 ) +{ + return Sequence< sal_Int8 >(); +} + +sal_Int32 SvXMLAutoCorrectTokenHandler::getTokenDirect( const char *pTag, sal_Int32 nLength ) const +{ + if( !nLength ) + nLength = strlen( pTag ); + const struct xmltoken* pToken = Perfect_Hash::in_word_set( pTag, nLength ); + return pToken ? pToken->nToken : XML_TOKEN_INVALID; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx new file mode 100644 index 0000000000..df913dbe6b --- /dev/null +++ b/editeng/source/misc/SvXMLAutoCorrectTokenHandler.hxx @@ -0,0 +1,45 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/types.h> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <com/sun/star/xml/sax/FastToken.hpp> +#include <sax/fastattribs.hxx> + +using namespace css::xml::sax; +using namespace ::xmloff::token; + +enum SvXMLAutoCorrectToken : sal_Int32 +{ + NAMESPACE = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST, //65553 + ABBREVIATED_NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_ABBREVIATED_NAME, //65655 + BLOCK = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK, //65791 + BLOCKLIST = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_BLOCK_LIST, //65792 + NAME = FastToken::NAMESPACE | XML_NAMESPACE_BLOCKLIST | XML_NAME //66737 +}; + +class SvXMLAutoCorrectTokenHandler : + public sax_fastparser::FastTokenHandlerBase +{ +public: + explicit SvXMLAutoCorrectTokenHandler(); + virtual ~SvXMLAutoCorrectTokenHandler() override; + + //XFastTokenHandler + virtual sal_Int32 SAL_CALL getTokenFromUTF8( const css::uno::Sequence< sal_Int8 >& Identifier ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getUTF8Identifier( sal_Int32 Token ) override; + + // Much faster direct C++ shortcut to the method that matters + virtual sal_Int32 getTokenDirect( const char *pToken, sal_Int32 nLength ) const override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/acorrcfg.cxx b/editeng/source/misc/acorrcfg.cxx new file mode 100644 index 0000000000..616d75c696 --- /dev/null +++ b/editeng/source/misc/acorrcfg.cxx @@ -0,0 +1,698 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <editeng/acorrcfg.hxx> +#include <o3tl/any.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/ucbhelper.hxx> +#include <svtools/langtab.hxx> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> + +#include <editeng/svxacorr.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace utl; +using namespace com::sun::star; +using namespace com::sun::star::uno; + + +/** An autocorrection file dropped into such directory may create a language + list entry if one didn't exist already. + */ +static void scanAutoCorrectDirForLanguageTags( const OUString& rURL ) +{ + // Silently ignore all errors. + try + { + ::ucbhelper::Content aContent( rURL, + uno::Reference<ucb::XCommandEnvironment>(), comphelper::getProcessComponentContext()); + if (aContent.isFolder()) + { + // Title is file name here. + uno::Reference<sdbc::XResultSet> xResultSet = aContent.createCursor( + {"Title"}, ::ucbhelper::INCLUDE_DOCUMENTS_ONLY); + uno::Reference<sdbc::XRow> xRow( xResultSet, UNO_QUERY); + if (xResultSet.is() && xRow.is()) + { + while (xResultSet->next()) + { + try + { + const OUString aTitle( xRow->getString(1)); + if (aTitle.getLength() <= 9 || !(aTitle.startsWith("acor_") && aTitle.endsWith(".dat"))) + continue; + + const OUString aBcp47( aTitle.copy( 5, aTitle.getLength() - 9)); + OUString aCanonicalized; + // Ignore invalid langtags and canonicalize for good, + // allow private-use tags. + if (!LanguageTag::isValidBcp47( aBcp47, &aCanonicalized)) + continue; + + const LanguageTag aLanguageTag( aCanonicalized); + if (SvtLanguageTable::HasLanguageType( aLanguageTag.getLanguageType())) + continue; + + // Insert language(-script)-only tags only if there is + // no known matching fallback locale, otherwise we'd + // end up with unwanted entries where a language + // autocorrection file covers several locales. We do + // know a few art-x-... though so exclude those and any + // other private-use tag (which should not fallback, + // but avoid). + if (aLanguageTag.getCountry().isEmpty() + && LanguageTag::isValidBcp47( aCanonicalized, nullptr, + LanguageTag::PrivateUse::DISALLOW)) + { + LanguageTag aFallback( aLanguageTag); + aFallback.makeFallback(); + if (aFallback.getLanguageAndScript() == aLanguageTag.getLanguageAndScript()) + continue; + } + + // Finally add this one. + SvtLanguageTable::AddLanguageTag( aLanguageTag); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("editeng", "Unable to get a directory entry from '" << rURL << "'"); + } + } + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("editeng", "Unable to iterate directory '" << rURL << "'"); + } +} + +SvxAutoCorrCfg::SvxAutoCorrCfg() : + aBaseConfig(*this), + aSwConfig(*this), + bFileRel(true), + bNetRel(true), + bAutoTextTip(true), + bAutoTextPreview(false), + bAutoFmtByInput(true), + bSearchInAllCategories(false) +{ + SvtPathOptions aPathOpt; + OUString sSharePath, sUserPath; + OUString const & sAutoPath( aPathOpt.GetAutoCorrectPath() ); + + sSharePath = sAutoPath.getToken(0, ';'); + sUserPath = sAutoPath.getToken(1, ';'); + + //fdo#67743 ensure the userdir exists so that any later attempt to copy the + //shared autocorrect file into the user dir will succeed + ::ucbhelper::Content aContent; + Reference < ucb::XCommandEnvironment > xEnv; + ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, sUserPath, aContent); + + for( OUString* pS : { &sSharePath, &sUserPath } ) + { + INetURLObject aPath( *pS ); + scanAutoCorrectDirForLanguageTags( aPath.GetMainURL(INetURLObject::DecodeMechanism::ToIUri)); + aPath.insertName(u"acor"); + *pS = aPath.GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + } + pAutoCorrect.reset( new SvxAutoCorrect( sSharePath, sUserPath ) ); + + aBaseConfig.Load(true); + aSwConfig.Load(true); +} + +SvxAutoCorrCfg::~SvxAutoCorrCfg() +{ +} + +void SvxAutoCorrCfg::SetAutoCorrect(SvxAutoCorrect *const pNew) +{ + if (pNew != pAutoCorrect.get()) + { + if (pNew && (pAutoCorrect->GetFlags() != pNew->GetFlags())) + { + aBaseConfig.SetModified(); + aSwConfig.SetModified(); + } + pAutoCorrect.reset( pNew ); + } +} + +Sequence<OUString> SvxBaseAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Exceptions/TwoCapitalsAtStart", // 0 + "Exceptions/CapitalAtStartSentence", // 1 + "UseReplacementTable", // 2 + "TwoCapitalsAtStart", // 3 + "CapitalAtStartSentence", // 4 + "ChangeUnderlineWeight", // 5 + "SetInetAttribute", // 6 + "ChangeOrdinalNumber", // 7 + "AddNonBreakingSpace", // 8 + "ChangeDash", // 9 + "RemoveDoubleSpaces", // 10 + "ReplaceSingleQuote", // 11 + "SingleQuoteAtStart", // 12 + "SingleQuoteAtEnd", // 13 + "ReplaceDoubleQuote", // 14 + "DoubleQuoteAtStart", // 15 + "DoubleQuoteAtEnd", // 16 + "CorrectAccidentalCapsLock", // 17 + "TransliterateRTL", // 18 + "ChangeAngleQuotes", // 19 + "SetDOIAttribute", // 20 + }; + const int nCount = 21; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxBaseAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + ACFlags nFlags = ACFlags::NONE; // default all off + sal_Int32 nTemp = 0; + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordCplSttLst; + break;//"Exceptions/TwoCapitalsAtStart", + case 1: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SaveWordWordStartLst; + break;//"Exceptions/CapitalAtStartSentence", + case 2: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::Autocorrect; + break;//"UseReplacementTable", + case 3: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartWord; + break;//"TwoCapitalsAtStart", + case 4: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CapitalStartSentence; + break;//"CapitalAtStartSentence", + case 5: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgWeightUnderl; + break;//"ChangeUnderlineWeight", + case 6: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SetINetAttr; + break;//"SetInetAttribute", + case 7: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgOrdinalNumber; + break;//"ChangeOrdinalNumber", + case 8: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::AddNonBrkSpace; + break;//"AddNonBreakingSpace" + case 9: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgToEnEmDash; + break;//"ChangeDash", + case 10: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::IgnoreDoubleSpace; + break;//"RemoveDoubleSpaces", + case 11: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgSglQuotes; + break;//"ReplaceSingleQuote", + case 12: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtStart", + case 13: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndSingleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"SingleQuoteAtEnd", + case 14: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgQuotes; + break;//"ReplaceDoubleQuote", + case 15: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetStartDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtStart", + case 16: + pValues[nProp] >>= nTemp; + rParent.pAutoCorrect->SetEndDoubleQuote( + sal::static_int_cast< sal_Unicode >( nTemp ) ); + break;//"DoubleQuoteAtEnd" + case 17: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::CorrectCapsLock; + break;//"CorrectAccidentalCapsLock" + case 18: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::TransliterateRTL; + break;//"TransliterateRTL" + case 19: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::ChgAngleQuotes; + break;//"ChangeAngleQuotes" + case 20: + if(*o3tl::doAccess<bool>(pValues[nProp])) + nFlags |= ACFlags::SetDOIAttr; + break;//"SetDOIAttr", + } + } + } + if( nFlags != ACFlags::NONE ) + rParent.pAutoCorrect->SetAutoCorrFlag( nFlags ); + rParent.pAutoCorrect->SetAutoCorrFlag( ( static_cast<ACFlags>(0xffff) & ~nFlags ), false ); +} + +SvxBaseAutoCorrCfg::SvxBaseAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Common/AutoCorrect"), + rParent(rPar) +{ +} + +SvxBaseAutoCorrCfg::~SvxBaseAutoCorrCfg() +{ +} + +void SvxBaseAutoCorrCfg::ImplCommit() +{ + const ACFlags nFlags = rParent.pAutoCorrect->GetFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(bool(nFlags & ACFlags::SaveWordCplSttLst)), + // "Exceptions/TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::SaveWordWordStartLst)), + // "Exceptions/CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::Autocorrect)), // "UseReplacementTable" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartWord)), + // "TwoCapitalsAtStart" + css::uno::Any(bool(nFlags & ACFlags::CapitalStartSentence)), + // "CapitalAtStartSentence" + css::uno::Any(bool(nFlags & ACFlags::ChgWeightUnderl)), + // "ChangeUnderlineWeight" + css::uno::Any(bool(nFlags & ACFlags::SetINetAttr)), // "SetInetAttribute" + css::uno::Any(bool(nFlags & ACFlags::ChgOrdinalNumber)), + // "ChangeOrdinalNumber" + css::uno::Any(bool(nFlags & ACFlags::AddNonBrkSpace)), // "AddNonBreakingSpace" + css::uno::Any(bool(nFlags & ACFlags::ChgToEnEmDash)), // "ChangeDash" + css::uno::Any(bool(nFlags & ACFlags::IgnoreDoubleSpace)), + // "RemoveDoubleSpaces" + css::uno::Any(bool(nFlags & ACFlags::ChgSglQuotes)), // "ReplaceSingleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartSingleQuote())), + // "SingleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndSingleQuote())), + // "SingleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::ChgQuotes)), // "ReplaceDoubleQuote" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetStartDoubleQuote())), + // "DoubleQuoteAtStart" + css::uno::Any(sal_Int32(rParent.pAutoCorrect->GetEndDoubleQuote())), + // "DoubleQuoteAtEnd" + css::uno::Any(bool(nFlags & ACFlags::CorrectCapsLock)), + // "CorrectAccidentalCapsLock" + css::uno::Any(bool(nFlags & ACFlags::TransliterateRTL)), + // "TransliterateRTL" + css::uno::Any(bool(nFlags & ACFlags::ChgAngleQuotes)), + // "ChangeAngleQuotes" + css::uno::Any(bool(nFlags & ACFlags::SetDOIAttr)), // "SetDOIAttribute" + }); +} + +void SvxBaseAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */) +{ + Load(false); +} + +Sequence<OUString> SvxSwAutoCorrCfg::GetPropertyNames() +{ + static const char* aPropNames[] = + { + "Text/FileLinks", // 0 + "Text/InternetLinks", // 1 + "Text/ShowPreview", // 2 + "Text/ShowToolTip", // 3 + "Text/SearchInAllCategories", // 4 + "Format/Option/UseReplacementTable", // 5 + "Format/Option/TwoCapitalsAtStart", // 6 + "Format/Option/CapitalAtStartSentence", // 7 + "Format/Option/ChangeUnderlineWeight", // 8 + "Format/Option/SetInetAttribute", // 9 + "Format/Option/ChangeOrdinalNumber", //10 + "Format/Option/AddNonBreakingSpace", //11 + "Format/Option/ChangeDash", //12 + "Format/Option/DelEmptyParagraphs", //13 + "Format/Option/ReplaceUserStyle", //14 + "Format/Option/ChangeToBullets/Enable", //15 + "Format/Option/ChangeToBullets/SpecialCharacter/Char", //16 + "Format/Option/ChangeToBullets/SpecialCharacter/Font", //17 + "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", //18 + "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", //19 + "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", //20 + "Format/Option/CombineParagraphs", //21 + "Format/Option/CombineValue", //22 + "Format/Option/DelSpacesAtStartEnd", //23 + "Format/Option/DelSpacesBetween", //24 + "Format/ByInput/Enable", //25 + "Format/ByInput/ChangeDash", //26 + "Format/ByInput/ApplyNumbering/Enable", //27 + "Format/ByInput/ChangeToBorders", //28 + "Format/ByInput/ChangeToTable", //29 + "Format/ByInput/ReplaceStyle", //30 + "Format/ByInput/DelSpacesAtStartEnd", //31 + "Format/ByInput/DelSpacesBetween", //32 + "Completion/Enable", //33 + "Completion/MinWordLen", //34 + "Completion/MaxListLen", //35 + "Completion/CollectWords", //36 + "Completion/EndlessList", //37 + "Completion/AppendBlank", //38 + "Completion/ShowAsTip", //39 + "Completion/AcceptKey", //40 + "Completion/KeepList", //41 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", //42 + "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", //43 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", //44 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", //45 + "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", //46 + "Format/Option/SetDOIAttribute", //47 + "Format/ByInput/ApplyBulletsAfterSpace", //48 + }; + const int nCount = 49; + Sequence<OUString> aNames(nCount); + OUString* pNames = aNames.getArray(); + for(int i = 0; i < nCount; i++) + pNames[i] = OUString::createFromAscii(aPropNames[i]); + return aNames; +} + +void SvxSwAutoCorrCfg::Load(bool bInit) +{ + Sequence<OUString> aNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(aNames); + if(bInit) + EnableNotification(aNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == aNames.getLength(), "GetProperties failed"); + if(aValues.getLength() != aNames.getLength()) + return; + + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + switch(nProp) + { + case 0: rParent.bFileRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/FileLinks", + case 1: rParent.bNetRel = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/InternetLinks", + case 2: rParent.bAutoTextPreview = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowPreview", + case 3: rParent.bAutoTextTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Text/ShowToolTip", + case 4: rParent.bSearchInAllCategories = *o3tl::doAccess<bool>(pValues[nProp]); break; //"Text/SearchInAllCategories" + case 5: rSwFlags.bAutoCorrect = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/UseReplacementTable", + case 6: rSwFlags.bCapitalStartSentence = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/TwoCapitalsAtStart", + case 7: rSwFlags.bCapitalStartWord = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CapitalAtStartSentence", + case 8: rSwFlags.bChgWeightUnderl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeUnderlineWeight", + case 9: rSwFlags.bSetINetAttr = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/SetInetAttribute", + case 10: rSwFlags.bChgOrdinalNumber = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeOrdinalNumber", + case 11: rSwFlags.bAddNonBrkSpace = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/AddNonBreakingSpace", +// it doesn't exist here - the common flags are used for that -> LM +// case 12: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeDash", + case 13: rSwFlags.bDelEmptyNode = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelEmptyParagraphs", + case 14: rSwFlags.bChgUserColl = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ReplaceUserStyle", + case 15: rSwFlags.bChgEnumNum = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/ChangeToBullets/Enable", + case 16: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Char", + case 17: + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aBulletFont.SetFamilyName(sTemp); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/Font", + case 18: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetFamily(FontFamily(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily", + case 19: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset", + case 20: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aBulletFont.SetPitch(FontPitch(nVal)); + } + break; // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch", + case 21: rSwFlags.bRightMargin = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/CombineParagraphs", + case 22: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nRightMargin = + sal::static_int_cast< sal_uInt8 >(nVal); + } + break; // "Format/Option/CombineValue", + case 23: rSwFlags.bAFormatDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesAtStartEnd", + case 24: rSwFlags.bAFormatDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/DelSpacesBetween", + case 25: rParent.bAutoFmtByInput = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/Enable", + case 26: rSwFlags.bChgToEnEmDash = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeDash", + case 27: rSwFlags.bSetNumRule = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ApplyNumbering/Enable", + case 28: rSwFlags.bSetBorder = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToBorders", + case 29: rSwFlags.bCreateTable = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ChangeToTable", + case 30: rSwFlags.bReplaceStyles = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ReplaceStyle", + case 31: rSwFlags.bAFormatByInpDelSpacesAtSttEnd = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesAtStartEnd", + case 32: rSwFlags.bAFormatByInpDelSpacesBetweenLines = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/DelSpacesBetween", + case 33: rSwFlags.bAutoCompleteWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/Enable", + case 34: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltWordLen = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/MinWordLen", + case 35: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltListLen = + sal::static_int_cast< sal_uInt32 >(nVal); + } + break; // "Completion/MaxListLen", + case 36: rSwFlags.bAutoCmpltCollectWords = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/CollectWords", + case 37: rSwFlags.bAutoCmpltEndless = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/EndlessList", + case 38: rSwFlags.bAutoCmpltAppendBlank = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/AppendBlank", + case 39: rSwFlags.bAutoCmpltShowAsTip = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Completion/ShowAsTip", + case 40: + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.nAutoCmpltExpandKey = + sal::static_int_cast< sal_uInt16 >(nVal); + } + break; // "Completion/AcceptKey" + case 41 :rSwFlags.bAutoCmpltKeepList = *o3tl::doAccess<bool>(pValues[nProp]); break;//"Completion/KeepList" + case 42 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.cByInputBullet = + sal::static_int_cast< sal_Unicode >(nVal); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Char", + case 43 : + { + OUString sTemp; pValues[nProp] >>= sTemp; + rSwFlags.aByInputBulletFont.SetFamilyName(sTemp); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/Font", + case 44 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetFamily(FontFamily(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily", + case 45 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetCharSet(rtl_TextEncoding(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset", + case 46 : + { + sal_Int32 nVal = 0; pValues[nProp] >>= nVal; + rSwFlags.aByInputBulletFont.SetPitch(FontPitch(nVal)); + } + break;// "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch", + case 47: rSwFlags.bSetDOIAttr = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/Option/SetDOIAttribute", + case 48 : rSwFlags.bSetNumRuleAfterSpace = *o3tl::doAccess<bool>(pValues[nProp]); break; // "Format/ByInput/ApplyNumberingAfterSpace", + } + } + } +} + +SvxSwAutoCorrCfg::SvxSwAutoCorrCfg(SvxAutoCorrCfg& rPar) : + utl::ConfigItem("Office.Writer/AutoFunction"), + rParent(rPar) +{ +} + +SvxSwAutoCorrCfg::~SvxSwAutoCorrCfg() +{ +} + +void SvxSwAutoCorrCfg::ImplCommit() +{ + SvxSwAutoFormatFlags& rSwFlags = rParent.pAutoCorrect->GetSwFlags(); + PutProperties( + GetPropertyNames(), + {css::uno::Any(rParent.bFileRel), // "Text/FileLinks" + css::uno::Any(rParent.bNetRel), // "Text/InternetLinks" + css::uno::Any(rParent.bAutoTextPreview), // "Text/ShowPreview" + css::uno::Any(rParent.bAutoTextTip), // "Text/ShowToolTip" + css::uno::Any(rParent.bSearchInAllCategories), + // "Text/SearchInAllCategories" + css::uno::Any(rSwFlags.bAutoCorrect), + // "Format/Option/UseReplacementTable" + css::uno::Any(rSwFlags.bCapitalStartSentence), + // "Format/Option/TwoCapitalsAtStart" + css::uno::Any(rSwFlags.bCapitalStartWord), + // "Format/Option/CapitalAtStartSentence" + css::uno::Any(rSwFlags.bChgWeightUnderl), + // "Format/Option/ChangeUnderlineWeight" + css::uno::Any(rSwFlags.bSetINetAttr), + // "Format/Option/SetInetAttribute" + css::uno::Any(rSwFlags.bChgOrdinalNumber), + // "Format/Option/ChangeOrdinalNumber" + css::uno::Any(rSwFlags.bAddNonBrkSpace), + // "Format/Option/AddNonBreakingSpace" + css::uno::Any(true), + // "Format/Option/ChangeDash"; it doesn't exist here - the common + // flags are used for that -> LM + css::uno::Any(rSwFlags.bDelEmptyNode), + // "Format/Option/DelEmptyParagraphs" + css::uno::Any(rSwFlags.bChgUserColl), + // "Format/Option/ReplaceUserStyle" + css::uno::Any(rSwFlags.bChgEnumNum), + // "Format/Option/ChangeToBullets/Enable" + css::uno::Any(sal_Int32(rSwFlags.cBullet)), + // "Format/Option/ChangeToBullets/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aBulletFont.GetFamilyName()), + // "Format/Option/ChangeToBullets/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetFamilyType())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetCharSet())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aBulletFont.GetPitch())), + // "Format/Option/ChangeToBullets/SpecialCharacter/FontPitch" + css::uno::Any(rSwFlags.bRightMargin), + // "Format/Option/CombineParagraphs" + css::uno::Any(sal_Int32(rSwFlags.nRightMargin)), + // "Format/Option/CombineValue" + css::uno::Any(rSwFlags.bAFormatDelSpacesAtSttEnd), + // "Format/Option/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatDelSpacesBetweenLines), + // "Format/Option/DelSpacesBetween" + css::uno::Any(rParent.bAutoFmtByInput), // "Format/ByInput/Enable" + css::uno::Any(rSwFlags.bChgToEnEmDash), // "Format/ByInput/ChangeDash" + css::uno::Any(rSwFlags.bSetNumRule), + // "Format/ByInput/ApplyNumbering/Enable" + css::uno::Any(rSwFlags.bSetBorder), // "Format/ByInput/ChangeToBorders" + css::uno::Any(rSwFlags.bCreateTable), // "Format/ByInput/ChangeToTable" + css::uno::Any(rSwFlags.bReplaceStyles), + // "Format/ByInput/ReplaceStyle" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesAtSttEnd), + // "Format/ByInput/DelSpacesAtStartEnd" + css::uno::Any(rSwFlags.bAFormatByInpDelSpacesBetweenLines), + // "Format/ByInput/DelSpacesBetween" + css::uno::Any(rSwFlags.bAutoCompleteWords), // "Completion/Enable" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltWordLen)), + // "Completion/MinWordLen" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltListLen)), + // "Completion/MaxListLen" + css::uno::Any(rSwFlags.bAutoCmpltCollectWords), + // "Completion/CollectWords" + css::uno::Any(rSwFlags.bAutoCmpltEndless), // "Completion/EndlessList" + css::uno::Any(rSwFlags.bAutoCmpltAppendBlank), + // "Completion/AppendBlank" + css::uno::Any(rSwFlags.bAutoCmpltShowAsTip), // "Completion/ShowAsTip" + css::uno::Any(sal_Int32(rSwFlags.nAutoCmpltExpandKey)), + // "Completion/AcceptKey" + css::uno::Any(rSwFlags.bAutoCmpltKeepList), // "Completion/KeepList" + css::uno::Any(sal_Int32(rSwFlags.cByInputBullet)), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Char" + css::uno::Any(rSwFlags.aByInputBulletFont.GetFamilyName()), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/Font" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetFamilyType())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontFamily" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetCharSet())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontCharset" + css::uno::Any(sal_Int32(rSwFlags.aByInputBulletFont.GetPitch())), + // "Format/ByInput/ApplyNumbering/SpecialCharacter/FontPitch" + css::uno::Any(rSwFlags.bSetDOIAttr), + css::uno::Any(rSwFlags.bSetNumRuleAfterSpace), // "Format/ByInput/ApplyNumberingAfterSpace" + }); + // "Format/Option/SetDOIAttribute" +} + +void SvxSwAutoCorrCfg::Notify( const Sequence<OUString>& /* aPropertyNames */ ) +{ + Load(false); +} + +SvxAutoCorrCfg& SvxAutoCorrCfg::Get() +{ + static SvxAutoCorrCfg theSvxAutoCorrCfg; + return theSvxAutoCorrCfg; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/edtdlg.cxx b/editeng/source/misc/edtdlg.cxx new file mode 100644 index 0000000000..a3f4390ab0 --- /dev/null +++ b/editeng/source/misc/edtdlg.cxx @@ -0,0 +1,29 @@ +/* -*- 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 <editeng/edtdlg.hxx> + +EditAbstractDialogFactory* EditAbstractDialogFactory::Create() +{ + return dynamic_cast<EditAbstractDialogFactory*>(VclAbstractDialogFactory::Create()); +} + +EditAbstractDialogFactory::~EditAbstractDialogFactory() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/forbiddencharacterstable.cxx b/editeng/source/misc/forbiddencharacterstable.cxx new file mode 100644 index 0000000000..7276da584b --- /dev/null +++ b/editeng/source/misc/forbiddencharacterstable.cxx @@ -0,0 +1,65 @@ +/* -*- 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 <editeng/forbiddencharacterstable.hxx> + +#include <unotools/localedatawrapper.hxx> +#include <utility> + +SvxForbiddenCharactersTable::SvxForbiddenCharactersTable( + css::uno::Reference<css::uno::XComponentContext> xContext) + : m_xContext(std::move(xContext)) +{ +} + +std::shared_ptr<SvxForbiddenCharactersTable> +SvxForbiddenCharactersTable::makeForbiddenCharactersTable( + const css::uno::Reference<css::uno::XComponentContext>& rxContext) +{ + return std::shared_ptr<SvxForbiddenCharactersTable>(new SvxForbiddenCharactersTable(rxContext)); +} + +const css::i18n::ForbiddenCharacters* +SvxForbiddenCharactersTable::GetForbiddenCharacters(LanguageType nLanguage, bool bGetDefault) +{ + css::i18n::ForbiddenCharacters* pForbiddenCharacters = nullptr; + Map::iterator it = maMap.find(nLanguage); + if (it != maMap.end()) + pForbiddenCharacters = &(it->second); + else if (bGetDefault && m_xContext.is()) + { + LocaleDataWrapper aWrapper(m_xContext, LanguageTag(nLanguage)); + maMap[nLanguage] = aWrapper.getForbiddenCharacters(); + pForbiddenCharacters = &maMap[nLanguage]; + } + return pForbiddenCharacters; +} + +void SvxForbiddenCharactersTable::SetForbiddenCharacters( + LanguageType nLanguage, const css::i18n::ForbiddenCharacters& rForbiddenChars) +{ + maMap[nLanguage] = rForbiddenChars; +} + +void SvxForbiddenCharactersTable::ClearForbiddenCharacters(LanguageType nLanguage) +{ + maMap.erase(nLanguage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/hangulhanja.cxx b/editeng/source/misc/hangulhanja.cxx new file mode 100644 index 0000000000..5a9a8c1034 --- /dev/null +++ b/editeng/source/misc/hangulhanja.cxx @@ -0,0 +1,1002 @@ +/* -*- 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 <editeng/hangulhanja.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> + +#include <set> +#include <map> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/TextConversion.hpp> +#include <com/sun/star/i18n/XExtendedTextConversion.hpp> +#include <com/sun/star/i18n/TextConversionType.hpp> +#include <com/sun/star/i18n/TextConversionOption.hpp> +#include <vcl/weld.hxx> +#include <unotools/charclass.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <editeng/edtdlg.hxx> + +#define HHC HangulHanjaConversion + + +namespace editeng +{ + + + using namespace ::com::sun::star; + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::i18n; + using namespace ::com::sun::star::i18n::TextConversionOption; + using namespace ::com::sun::star::i18n::TextConversionType; + + class HangulHanjaConversion_Impl + { + private: + typedef std::set<OUString> StringBag; + typedef std::map<OUString, OUString> StringMap; + + private: + StringBag m_sIgnoreList; + StringMap m_aChangeList; + static StringMap m_aRecentlyUsedList; + + // general + VclPtr<AbstractHangulHanjaConversionDialog> + m_pConversionDialog; // the dialog to display for user interaction + weld::Widget* m_pUIParent; // the parent window for any UI we raise + Reference< XComponentContext > + m_xContext; // the service factory to use + Reference< XExtendedTextConversion > + m_xConverter; // the text conversion service + lang::Locale m_aSourceLocale; // the locale we're working with + + // additions for Chinese simplified / traditional conversion + HHC::ConversionType m_eConvType; // conversion type (Hangul/Hanja, simplified/traditional Chinese,...) + LanguageType m_nSourceLang; // just a 'copy' of m_aSourceLocale in order to + // save the applications from always converting to this + // type in their implementations + LanguageType m_nTargetLang; // target language of new replacement text + const vcl::Font* m_pTargetFont; // target font of new replacement text + sal_Int32 m_nConvOptions; // text conversion options (as used by 'getConversions') + bool m_bIsInteractive; // specifies if the conversion requires user interaction + // (and likely a specialised dialog) or if it is to run + // automatically without any user interaction. + // True for Hangul / Hanja conversion + // False for Chinese simplified / traditional conversion + + HangulHanjaConversion* m_pAntiImpl; // our "anti-impl" instance + + // options + bool m_bByCharacter; // are we in "by character" mode currently? + HHC::ConversionFormat m_eConversionFormat; // the current format for the conversion + HHC::ConversionDirection m_ePrimaryConversionDirection; // the primary conversion direction + HHC::ConversionDirection m_eCurrentConversionDirection; // the primary conversion direction + + //options from Hangul/Hanja Options dialog (also saved to configuration) + bool m_bIgnorePostPositionalWord; + bool m_bShowRecentlyUsedFirst; + bool m_bAutoReplaceUnique; + + // state + OUString m_sCurrentPortion; // the text which we are currently working on + LanguageType m_nCurrentPortionLang; // language of m_sCurrentPortion found + sal_Int32 m_nCurrentStartIndex; // the start index within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nCurrentEndIndex; // the end index (excluding) within m_sCurrentPortion of the current convertible portion + sal_Int32 m_nReplacementBaseIndex;// index which ReplaceUnit-calls need to be relative to + sal_Int32 m_nCurrentConversionOption; + sal_Int16 m_nCurrentConversionType; + Sequence< OUString > + m_aCurrentSuggestions; // the suggestions for the current unit + // (means for the text [m_nCurrentStartIndex, m_nCurrentEndIndex) in m_sCurrentPortion) + bool m_bTryBothDirections; // specifies if other conversion directions should be tried when looking for convertible characters + + + public: + HangulHanjaConversion_Impl( + weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nConvOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ); + + public: + void DoDocumentConversion( ); + + bool IsValid() const { return m_xConverter.is(); } + + weld::Widget* GetUIParent() const { return m_pUIParent; } + LanguageType GetSourceLang() const { return m_nSourceLang; } + LanguageType GetTargetLang() const { return m_nTargetLang; } + const vcl::Font * GetTargetFont() const { return m_pTargetFont; } + sal_Int32 GetConvOptions() const { return m_nConvOptions; } + bool IsInteractive() const { return m_bIsInteractive; } + + protected: + void createDialog(); + + /** continue with the conversion, return <TRUE/> if and only if the complete conversion is done + @param _bRepeatCurrentUnit + if <TRUE/>, an implNextConvertible will be called initially to advance to the next convertible. + if <FALSE/>, the method will initially work with the current convertible unit + */ + bool ContinueConversion( bool _bRepeatCurrentUnit ); + + private: + DECL_LINK( OnOptionsChanged, LinkParamNone*, void ); + DECL_LINK( OnIgnore, weld::Button&, void ); + DECL_LINK( OnIgnoreAll, weld::Button&, void ); + DECL_LINK( OnChange, weld::Button&, void ); + DECL_LINK( OnChangeAll, weld::Button&, void ); + DECL_LINK( OnByCharClicked, weld::Toggleable&, void ); + DECL_LINK( OnConversionTypeChanged, weld::Toggleable&, void ); + DECL_LINK( OnFind, weld::Button&, void ); + + /** proceed, after the current convertible has been handled + + <p><b>Attention:</b> + When returning from this method, the dialog may have been deleted!</p> + + @param _bRepeatCurrentUnit + will be passed to the <member>ContinueConversion</member> call + */ + void implProceed( bool _bRepeatCurrentUnit ); + + // change the current convertible, and do _not_ proceed + void implChange( const OUString& _rChangeInto ); + + /** find the next convertible piece of text, with possibly advancing to the next portion + + @see HangulHanjaConversion::GetNextPortion + */ + bool implNextConvertible( bool _bRepeatUnit ); + + /** find the next convertible unit within the current portion + @param _bRepeatUnit + if <TRUE/>, the search will start at the beginning of the current unit, + if <FALSE/>, it will start at the end of the current unit + */ + bool implNextConvertibleUnit( const sal_Int32 _nStartAt ); + + /** retrieves the next portion, with setting the index members properly + @return + <TRUE/> if and only if there is a next portion + */ + bool implRetrieveNextPortion( ); + + /** determine the ConversionDirection for m_sCurrentPortion + @return + <FALSE/> if and only if something went wrong + */ + bool implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ); + + /** member m_aCurrentSuggestions and m_nCurrentEndIndex are updated according to the other settings and current dictionaries + + if _bAllowSearchNextConvertibleText is true _nStartAt is used as starting point to search the next + convertible text portion. This may result in changing of the member m_nCurrentStartIndex additionally. + + @return + <TRUE/> if Suggestions were found + */ + bool implUpdateSuggestions( const bool _bAllowSearchNextConvertibleText=false, const sal_Int32 _nStartAt=-1 ); + + /** reads the options from Hangul/Hanja Options dialog that are saved to configuration + */ + void implReadOptionsFromConfiguration(); + + /** get the string currently considered to be replaced or ignored + */ + OUString GetCurrentUnit() const; + + /** read options from configuration, update suggestion list and dialog content + */ + void implUpdateData(); + + /** get the conversion direction dependent from m_eConvType and m_eCurrentConversionDirection + in case of switching the direction is allowed this can be triggered with parameter bSwitchDirection + */ + sal_Int16 implGetConversionType( bool bSwitchDirection=false ) const; + }; + + HangulHanjaConversion_Impl::StringMap HangulHanjaConversion_Impl::m_aRecentlyUsedList = HangulHanjaConversion_Impl::StringMap(); + + HangulHanjaConversion_Impl::HangulHanjaConversion_Impl( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, + const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, + bool _bIsInteractive, + HangulHanjaConversion* _pAntiImpl ) + : m_pUIParent( pUIParent ) + , m_xContext( rxContext ) + , m_aSourceLocale( _rSourceLocale ) + , m_nSourceLang( LanguageTag::convertToLanguageType( _rSourceLocale ) ) + , m_nTargetLang( LanguageTag::convertToLanguageType( _rTargetLocale ) ) + , m_pTargetFont( _pTargetFont ) + , m_nConvOptions(_nOptions) + , m_bIsInteractive( _bIsInteractive ) + , m_pAntiImpl( _pAntiImpl ) + , m_bByCharacter((_nOptions & CHARACTER_BY_CHARACTER) != 0) + , m_eConversionFormat( HHC::eSimpleConversion) + , m_ePrimaryConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_eCurrentConversionDirection( HHC::eHangulToHanja) // used for eConvHangulHanja + , m_nCurrentPortionLang( LANGUAGE_NONE ) + , m_nCurrentStartIndex( 0 ) + , m_nCurrentEndIndex( 0 ) + , m_nReplacementBaseIndex( 0 ) + , m_nCurrentConversionOption( TextConversionOption::NONE ) + , m_nCurrentConversionType( -1 ) // not yet known + , m_bTryBothDirections( true ) + { + implReadOptionsFromConfiguration(); + + DBG_ASSERT( m_xContext.is(), "HangulHanjaConversion_Impl::HangulHanjaConversion_Impl: no ORB!" ); + + // determine conversion type + if (m_nSourceLang == LANGUAGE_KOREAN && m_nTargetLang == LANGUAGE_KOREAN) + m_eConvType = HHC::eConvHangulHanja; + else if ( (m_nSourceLang == LANGUAGE_CHINESE_TRADITIONAL && m_nTargetLang == LANGUAGE_CHINESE_SIMPLIFIED) || + (m_nSourceLang == LANGUAGE_CHINESE_SIMPLIFIED && m_nTargetLang == LANGUAGE_CHINESE_TRADITIONAL) ) + m_eConvType = HHC::eConvSimplifiedTraditional; + else + { + m_eConvType = HHC::eConvHangulHanja; + OSL_FAIL( "failed to determine conversion type from languages" ); + } + + m_xConverter = TextConversion::create( m_xContext ); + } + + void HangulHanjaConversion_Impl::createDialog() + { + DBG_ASSERT( m_bIsInteractive, "createDialog when the conversion should not be interactive?" ); + if ( !m_bIsInteractive || m_pConversionDialog ) + return; + + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + m_pConversionDialog = pFact->CreateHangulHanjaConversionDialog(m_pUIParent); + + m_pConversionDialog->EnableRubySupport( m_pAntiImpl->HasRubySupport() ); + + m_pConversionDialog->SetByCharacter( m_bByCharacter ); + m_pConversionDialog->SetConversionFormat( m_eConversionFormat ); + m_pConversionDialog->SetConversionDirectionState( m_bTryBothDirections, m_ePrimaryConversionDirection ); + + // the handlers + m_pConversionDialog->SetOptionsChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnOptionsChanged ) ); + m_pConversionDialog->SetIgnoreHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnore ) ); + m_pConversionDialog->SetIgnoreAllHdl( LINK( this, HangulHanjaConversion_Impl, OnIgnoreAll ) ); + m_pConversionDialog->SetChangeHdl( LINK( this, HangulHanjaConversion_Impl, OnChange ) ); + m_pConversionDialog->SetChangeAllHdl( LINK( this, HangulHanjaConversion_Impl, OnChangeAll ) ); + m_pConversionDialog->SetClickByCharacterHdl( LINK( this, HangulHanjaConversion_Impl, OnByCharClicked ) ); + m_pConversionDialog->SetConversionFormatChangedHdl( LINK( this, HangulHanjaConversion_Impl, OnConversionTypeChanged ) ); + m_pConversionDialog->SetFindHdl( LINK( this, HangulHanjaConversion_Impl, OnFind ) ); + } + + sal_Int16 HangulHanjaConversion_Impl::implGetConversionType( bool bSwitchDirection ) const + { + sal_Int16 nConversionType = -1; + if (m_eConvType == HHC::eConvHangulHanja) + nConversionType = ( HHC::eHangulToHanja == m_eCurrentConversionDirection && !bSwitchDirection ) ? TO_HANJA : TO_HANGUL; + else if (m_eConvType == HHC::eConvSimplifiedTraditional) + nConversionType = LANGUAGE_CHINESE_SIMPLIFIED == m_nTargetLang ? TO_SCHINESE : TO_TCHINESE; + DBG_ASSERT( nConversionType != -1, "unexpected conversion type" ); + return nConversionType; + } + + bool HangulHanjaConversion_Impl::implUpdateSuggestions( bool _bAllowSearchNextConvertibleText, const sal_Int32 _nStartAt ) + { + // parameters for the converter + sal_Int32 nStartSearch = m_nCurrentStartIndex; + if( _bAllowSearchNextConvertibleText ) + nStartSearch = _nStartAt; + + sal_Int32 nLength = m_sCurrentPortion.getLength() - nStartSearch; + m_nCurrentConversionType = implGetConversionType(); + m_nCurrentConversionOption = m_bByCharacter ? CHARACTER_BY_CHARACTER : css::i18n::TextConversionOption::NONE; + if( m_bIgnorePostPositionalWord ) + m_nCurrentConversionOption = m_nCurrentConversionOption | IGNORE_POST_POSITIONAL_WORD; + + // no need to check both directions for chinese conversion (saves time) + if (m_eConvType == HHC::eConvSimplifiedTraditional) + m_bTryBothDirections = false; + + bool bFoundAny = true; + try + { + TextConversionResult aResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption + ); + const bool bFoundPrimary = aResult.Boundary.startPos < aResult.Boundary.endPos; + bFoundAny = bFoundPrimary; + + if ( m_bTryBothDirections ) + { // see if we find another convertible when assuming the other direction + TextConversionResult aSecondResult = m_xConverter->getConversions( + m_sCurrentPortion, + nStartSearch, + nLength, + m_aSourceLocale, + implGetConversionType( true ), // switched! + m_nCurrentConversionOption + ); + if ( aSecondResult.Boundary.startPos < aSecondResult.Boundary.endPos ) + { // we indeed found such a convertible + + // in case the first attempt (with the original conversion direction) + // didn't find anything + if ( !bFoundPrimary + // or if the second location is _before_ the first one + || ( aSecondResult.Boundary.startPos < aResult.Boundary.startPos ) + ) + { + // then use the second finding + aResult = aSecondResult; + + // our current conversion direction changed now + m_eCurrentConversionDirection = ( HHC::eHangulToHanja == m_eCurrentConversionDirection ) + ? HHC::eHanjaToHangul : HHC::eHangulToHanja; + bFoundAny = true; + } + } + } + + if( _bAllowSearchNextConvertibleText ) + { + //this might change the current position + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentStartIndex = aResult.Boundary.startPos; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + //the change of starting position is not allowed + if( m_nCurrentStartIndex == aResult.Boundary.startPos + && aResult.Boundary.endPos != aResult.Boundary.startPos ) + { + m_aCurrentSuggestions = aResult.Candidates; + m_nCurrentEndIndex = aResult.Boundary.endPos; + } + else + { + m_aCurrentSuggestions.realloc( 0 ); + if( m_sCurrentPortion.getLength() >= m_nCurrentStartIndex+1 ) + m_nCurrentEndIndex = m_nCurrentStartIndex+1; + } + } + + //put recently used string to front: + if( m_bShowRecentlyUsedFirst && m_aCurrentSuggestions.getLength()>1 ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + StringMap::const_iterator aRecentlyUsed = m_aRecentlyUsedList.find( sCurrentUnit ); + bool bUsedBefore = aRecentlyUsed != m_aRecentlyUsedList.end(); + if( bUsedBefore && m_aCurrentSuggestions[0] != aRecentlyUsed->second ) + { + sal_Int32 nCount = m_aCurrentSuggestions.getLength(); + Sequence< OUString > aTmp(nCount); + auto pTmp = aTmp.getArray(); + pTmp[0]=aRecentlyUsed->second; + sal_Int32 nDiff = 1; + for( sal_Int32 n=1; n<nCount; n++)//we had 0 already + { + if( nDiff && m_aCurrentSuggestions[n-nDiff]==aRecentlyUsed->second ) + nDiff=0; + pTmp[n]=m_aCurrentSuggestions[n-nDiff]; + } + m_aCurrentSuggestions = aTmp; + } + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implNextConvertibleUnit" ); + + //!!! at least we want to move on in the text in order + //!!! to avoid an endless loop... + return false; + } + return bFoundAny; + } + + bool HangulHanjaConversion_Impl::implNextConvertibleUnit( const sal_Int32 _nStartAt ) + { + m_aCurrentSuggestions.realloc( 0 ); + + // ask the TextConversion service for the next convertible piece of text + + // get current values from dialog + if( m_eConvType == HHC::eConvHangulHanja && m_pConversionDialog ) + { + m_bTryBothDirections = m_pConversionDialog->GetUseBothDirections(); + HHC::ConversionDirection eDialogDirection = m_pConversionDialog->GetDirection( HHC::eHangulToHanja ); + + if( !m_bTryBothDirections && eDialogDirection != m_eCurrentConversionDirection ) + { + m_eCurrentConversionDirection = eDialogDirection; + } + + // save currently used value for possible later use + HangulHanjaConversion::m_bTryBothDirectionsSave = m_bTryBothDirections; + HangulHanjaConversion::m_ePrimaryConversionDirectionSave = m_eCurrentConversionDirection; + } + + bool bFoundAny = implUpdateSuggestions( true, _nStartAt ); + + return bFoundAny && + (m_nCurrentStartIndex < m_sCurrentPortion.getLength()); + } + + bool HangulHanjaConversion_Impl::implRetrieveNextPortion( ) + { + const bool bAllowImplicitChanges = m_eConvType == HHC::eConvSimplifiedTraditional; + + m_sCurrentPortion.clear(); + m_nCurrentPortionLang = LANGUAGE_NONE; + m_pAntiImpl->GetNextPortion( m_sCurrentPortion, m_nCurrentPortionLang, bAllowImplicitChanges ); + m_nReplacementBaseIndex = 0; + m_nCurrentStartIndex = m_nCurrentEndIndex = 0; + + bool bRet = !m_sCurrentPortion.isEmpty(); + + if (m_eConvType == HHC::eConvHangulHanja && m_bTryBothDirections) + implGetConversionDirectionForCurrentPortion( m_eCurrentConversionDirection ); + + return bRet; + } + + bool HangulHanjaConversion_Impl::implNextConvertible( bool _bRepeatUnit ) + { + if ( _bRepeatUnit || ( m_nCurrentEndIndex < m_sCurrentPortion.getLength() ) ) + { + if ( implNextConvertibleUnit( + _bRepeatUnit + ? m_nCurrentStartIndex + : m_nCurrentEndIndex + ) ) + return true; + } + + // no convertible text in the current portion anymore + // -> advance to the next portion + do + { + // next portion + if ( implRetrieveNextPortion( ) ) + { // there is a next portion + // -> find the next convertible unit in the current portion + if ( implNextConvertibleUnit( 0 ) ) + return true; + } + } + while ( !m_sCurrentPortion.isEmpty() ); + + // no more portions + return false; + } + + OUString HangulHanjaConversion_Impl::GetCurrentUnit() const + { + DBG_ASSERT( m_nCurrentStartIndex < m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentEndIndex <= m_sCurrentPortion.getLength(), + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid index into current portion!" ); + DBG_ASSERT( m_nCurrentStartIndex <= m_nCurrentEndIndex, + "HangulHanjaConversion_Impl::GetCurrentUnit: invalid interval!" ); + + OUString sCurrentUnit = m_sCurrentPortion.copy( m_nCurrentStartIndex, m_nCurrentEndIndex - m_nCurrentStartIndex ); + return sCurrentUnit; + } + + bool HangulHanjaConversion_Impl::ContinueConversion( bool _bRepeatCurrentUnit ) + { + while ( implNextConvertible( _bRepeatCurrentUnit ) ) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + // do we need to ignore it? + const bool bAlwaysIgnoreThis = m_sIgnoreList.end() != m_sIgnoreList.find( sCurrentUnit ); + + // do we need to change it? + StringMap::const_iterator aChangeListPos = m_aChangeList.find( sCurrentUnit ); + const bool bAlwaysChangeThis = m_aChangeList.end() != aChangeListPos; + + // do we automatically change this? + const bool bAutoChange = m_bAutoReplaceUnique && m_aCurrentSuggestions.getLength() == 1; + + if (!m_bIsInteractive) + { + // silent conversion (e.g. for simplified/traditional Chinese)... + if(m_aCurrentSuggestions.hasElements()) + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if (bAutoChange) + { + implChange( m_aCurrentSuggestions.getConstArray()[0] ); + } + else if ( bAlwaysChangeThis ) + { + implChange( aChangeListPos->second ); + } + else if ( !bAlwaysIgnoreThis ) + { + // here we need to ask the user for what to do with the text + // for this, allow derivees to highlight the current text unit in a possible document view + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + + // do not look for the next convertible: We have to wait for the user to interactively + // decide what happens with the current convertible + return false; + } + } + + return true; + } + + bool HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion( HHC::ConversionDirection& rDirection ) + { + // - For eConvHangulHanja the direction is determined by + // the first encountered Korean character. + // - For eConvSimplifiedTraditional the conversion direction + // is already specified by the source language. + + bool bSuccess = true; + + if (m_eConvType == HHC::eConvHangulHanja) + { + bSuccess = false; + try + { + // get the break iterator service + Reference< XBreakIterator > xBreakIter = i18n::BreakIterator::create( m_xContext ); + sal_Int32 nNextAsianScript = xBreakIter->beginOfScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( -1 == nNextAsianScript ) + nNextAsianScript = xBreakIter->nextScript( m_sCurrentPortion, m_nCurrentStartIndex, css::i18n::ScriptType::ASIAN ); + if ( ( nNextAsianScript >= m_nCurrentStartIndex ) && ( nNextAsianScript < m_sCurrentPortion.getLength() ) ) + { // found asian text + + // determine if it's Hangul + CharClass aCharClassificaton( m_xContext, LanguageTag( m_aSourceLocale) ); + css::i18n::UnicodeScript nScript = aCharClassificaton.getScript( m_sCurrentPortion, sal::static_int_cast< sal_uInt16 >(nNextAsianScript) ); + if ( ( UnicodeScript_kHangulJamo == nScript ) + || ( UnicodeScript_kHangulCompatibilityJamo == nScript ) + || ( UnicodeScript_kHangulSyllable == nScript ) + ) + { + rDirection = HHC::eHangulToHanja; + } + else + { + rDirection = HHC::eHanjaToHangul; + } + + bSuccess = true; + } + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implGetConversionDirectionForCurrentPortion" ); + } + } + + return bSuccess; + } + + void HangulHanjaConversion_Impl::DoDocumentConversion( ) + { + // clear the change-all list - it's to be re-initialized for every single document + StringMap().swap(m_aChangeList); + + // first of all, we need to guess the direction of our conversion - it is determined by the first + // hangul or hanja character in the first text + if ( !implRetrieveNextPortion() ) + { + SAL_INFO( "editeng", "HangulHanjaConversion_Impl::DoDocumentConversion: why did you call me if you do have nothing to convert?" ); + // nothing to do + return; + } + if( m_eConvType == HHC::eConvHangulHanja ) + { + //init conversion direction from saved value + HHC::ConversionDirection eDirection = HHC::eHangulToHanja; + if(!implGetConversionDirectionForCurrentPortion( eDirection )) + // something went wrong, has already been asserted + return; + + if (HangulHanjaConversion::IsUseSavedConversionDirectionState()) + { + m_ePrimaryConversionDirection = HangulHanjaConversion::m_ePrimaryConversionDirectionSave; + m_bTryBothDirections = HangulHanjaConversion::m_bTryBothDirectionsSave; + if( m_bTryBothDirections ) + m_eCurrentConversionDirection = eDirection; + else + m_eCurrentConversionDirection = m_ePrimaryConversionDirection; + } + else + { + m_ePrimaryConversionDirection = eDirection; + m_eCurrentConversionDirection = eDirection; + } + } + + if (m_bIsInteractive && m_eConvType == HHC::eConvHangulHanja) + { + //always open dialog if at least having a hangul or hanja text portion + createDialog(); + if(HangulHanjaConversion::IsUseSavedConversionDirectionState()) + ContinueConversion( false ); + else + implUpdateData(); + m_pConversionDialog->Execute(); + m_pConversionDialog.disposeAndClear(); + } + else + { + const bool bCompletelyDone = ContinueConversion( false ); + DBG_ASSERT( bCompletelyDone, "HangulHanjaConversion_Impl::DoDocumentConversion: ContinueConversion should have returned true here!" ); + } + } + + void HangulHanjaConversion_Impl::implProceed( bool _bRepeatCurrentUnit ) + { + if ( ContinueConversion( _bRepeatCurrentUnit ) ) + { // we're done with the whole document + DBG_ASSERT( !m_bIsInteractive || m_pConversionDialog, "HangulHanjaConversion_Impl::implProceed: we should not reach this here without dialog!" ); + if ( m_pConversionDialog ) + m_pConversionDialog->EndDialog( RET_OK ); + } + } + + void HangulHanjaConversion_Impl::implChange( const OUString& _rChangeInto ) + { + if( _rChangeInto.isEmpty() ) + return; + + // translate the conversion format into a replacement action + // this translation depends on whether we have a Hangul original, or a Hanja original + + HHC::ReplacementAction eAction( HHC::eExchange ); + + if (m_eConvType == HHC::eConvHangulHanja) + { + // is the original we're about to change in Hangul? + const bool bOriginalIsHangul = HHC::eHangulToHanja == m_eCurrentConversionDirection; + + switch ( m_eConversionFormat ) + { + case HHC::eSimpleConversion: eAction = HHC::eExchange; break; + case HHC::eHangulBracketed: eAction = bOriginalIsHangul ? HHC::eOriginalBracketed : HHC::eReplacementBracketed; break; + case HHC::eHanjaBracketed: eAction = bOriginalIsHangul ? HHC::eReplacementBracketed : HHC::eOriginalBracketed; break; + case HHC::eRubyHanjaAbove: eAction = bOriginalIsHangul ? HHC::eReplacementAbove : HHC::eOriginalAbove; break; + case HHC::eRubyHanjaBelow: eAction = bOriginalIsHangul ? HHC::eReplacementBelow : HHC::eOriginalBelow; break; + case HHC::eRubyHangulAbove: eAction = bOriginalIsHangul ? HHC::eOriginalAbove : HHC::eReplacementAbove; break; + case HHC::eRubyHangulBelow: eAction = bOriginalIsHangul ? HHC::eOriginalBelow : HHC::eReplacementBelow; break; + default: + OSL_FAIL( "HangulHanjaConversion_Impl::implChange: invalid/unexpected conversion format!" ); + } + } + + // the proper indices (the wrapper implementation needs indices relative to the + // previous replacement) + DBG_ASSERT( ( m_nReplacementBaseIndex <= m_nCurrentStartIndex ) && ( m_nReplacementBaseIndex <= m_nCurrentEndIndex ), + "HangulHanjaConversion_Impl::implChange: invalid replacement base!" ); + + sal_Int32 nStartIndex = m_nCurrentStartIndex - m_nReplacementBaseIndex; + sal_Int32 nEndIndex = m_nCurrentEndIndex - m_nReplacementBaseIndex; + + //remind this decision + m_aRecentlyUsedList[ GetCurrentUnit() ] = _rChangeInto; + + LanguageType *pNewUnitLang = nullptr; + LanguageType nNewUnitLang = LANGUAGE_NONE; + if (m_eConvType == HHC::eConvSimplifiedTraditional) + { + // check if language needs to be changed + if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL && + !HangulHanjaConversion::IsTraditional( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_TRADITIONAL; + else if ( m_pAntiImpl->GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED && + !HangulHanjaConversion::IsSimplified( m_nCurrentPortionLang )) + nNewUnitLang = LANGUAGE_CHINESE_SIMPLIFIED; + if (nNewUnitLang != LANGUAGE_NONE) + pNewUnitLang = &nNewUnitLang; + } + + // according to FT we should not (yet) bother about Hangul/Hanja conversion here + // + // aOffsets is needed in ReplaceUnit below in order to find out + // exactly which characters are really changed in order to keep as much + // from attributation for the text as possible. + Sequence< sal_Int32 > aOffsets; + if (m_eConvType == HHC::eConvSimplifiedTraditional && m_xConverter.is()) + { + try + { + m_xConverter->getConversionWithOffset( + m_sCurrentPortion, + m_nCurrentStartIndex, + m_nCurrentEndIndex - m_nCurrentStartIndex, + m_aSourceLocale, + m_nCurrentConversionType, + m_nCurrentConversionOption, + aOffsets + ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::implChange: caught unexpected exception!" ); + aOffsets.realloc(0); + } + } + + // do the replacement + m_pAntiImpl->ReplaceUnit( nStartIndex, nEndIndex, m_sCurrentPortion, + _rChangeInto, aOffsets, eAction, pNewUnitLang ); + + + // adjust the replacement base + m_nReplacementBaseIndex = m_nCurrentEndIndex; + } + + void HangulHanjaConversion_Impl::implReadOptionsFromConfiguration() + { + SvtLinguConfig aLngCfg; + aLngCfg.GetProperty( UPH_IS_IGNORE_POST_POSITIONAL_WORD ) >>= m_bIgnorePostPositionalWord; + aLngCfg.GetProperty( UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST ) >>= m_bShowRecentlyUsedFirst; + aLngCfg.GetProperty( UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES ) >>= m_bAutoReplaceUnique; + } + + void HangulHanjaConversion_Impl::implUpdateData() + { + implReadOptionsFromConfiguration(); + implUpdateSuggestions(); + + if(m_pConversionDialog) + { + OUString sCurrentUnit( GetCurrentUnit() ); + + m_pConversionDialog->SetCurrentString( sCurrentUnit, m_aCurrentSuggestions ); + m_pConversionDialog->FocusSuggestion(); + } + + m_pAntiImpl->HandleNewUnit( m_nCurrentStartIndex - m_nReplacementBaseIndex, m_nCurrentEndIndex - m_nReplacementBaseIndex ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnOptionsChanged, LinkParamNone*, void) + { + //options and dictionaries might have been changed + //-> update our internal settings and the dialog + implUpdateData(); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnore, weld::Button&, void) + { + // simply ignore, and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnIgnoreAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnIgnoreAll: no dialog! How this?" ); + + if ( m_pConversionDialog ) + { + OUString sCurrentUnit = m_pConversionDialog->GetCurrentString(); + DBG_ASSERT( m_sIgnoreList.end() == m_sIgnoreList.find( sCurrentUnit ), + "HangulHanjaConversion_Impl, OnIgnoreAll: shouldn't this have been ignored before" ); + + // put into the "ignore all" list + m_sIgnoreList.insert( sCurrentUnit ); + + // and proceed + implProceed( false ); + } + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChange, weld::Button&, void) + { + // change + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + implChange( m_pConversionDialog->GetCurrentSuggestion( ) ); + // and proceed + implProceed( false ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnChangeAll, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnChangeAll: no dialog! How this?" ); + if ( !m_pConversionDialog ) + return; + + OUString sCurrentUnit( m_pConversionDialog->GetCurrentString() ); + OUString sChangeInto( m_pConversionDialog->GetCurrentSuggestion( ) ); + + if( !sChangeInto.isEmpty() ) + { + // change the current occurrence + implChange( sChangeInto ); + + // put into the "change all" list + m_aChangeList.emplace( sCurrentUnit, sChangeInto ); + } + + // and proceed + implProceed( false ); + } + + IMPL_LINK(HangulHanjaConversion_Impl, OnByCharClicked, weld::Toggleable&, rBox, void) + { + m_bByCharacter = rBox.get_active(); + + // continue conversion, without advancing to the next unit, but instead continuing with the current unit + implProceed( true ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnConversionTypeChanged, weld::Toggleable&, void) + { + DBG_ASSERT( m_pConversionDialog, "we should always have a dialog here!" ); + if( m_pConversionDialog ) + m_eConversionFormat = m_pConversionDialog->GetConversionFormat( ); + } + + IMPL_LINK_NOARG(HangulHanjaConversion_Impl, OnFind, weld::Button&, void) + { + DBG_ASSERT( m_pConversionDialog, "HangulHanjaConversion_Impl::OnFind: where did this come from?" ); + if ( !m_pConversionDialog ) + return; + + try + { + OUString sNewOriginal( m_pConversionDialog->GetCurrentSuggestion( ) ); + Sequence< OUString > aSuggestions; + + DBG_ASSERT( m_xConverter.is(), "HangulHanjaConversion_Impl::OnFind: no converter!" ); + TextConversionResult aToHanja = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANJA, + TextConversionOption::NONE + ); + TextConversionResult aToHangul = m_xConverter->getConversions( + sNewOriginal, + 0, sNewOriginal.getLength(), + m_aSourceLocale, + TextConversionType::TO_HANGUL, + TextConversionOption::NONE + ); + + bool bHaveToHanja = ( aToHanja.Boundary.startPos < aToHanja.Boundary.endPos ); + bool bHaveToHangul = ( aToHangul.Boundary.startPos < aToHangul.Boundary.endPos ); + + TextConversionResult* pResult = nullptr; + if ( bHaveToHanja && bHaveToHangul ) + { // it found convertibles in both directions -> use the first + if ( aToHangul.Boundary.startPos < aToHanja.Boundary.startPos ) + pResult = &aToHangul; + else + pResult = &aToHanja; + } + else if ( bHaveToHanja ) + { // only found toHanja + pResult = &aToHanja; + } + else + { // only found toHangul + pResult = &aToHangul; + } + if ( pResult ) + aSuggestions = pResult->Candidates; + + m_pConversionDialog->SetCurrentString( sNewOriginal, aSuggestions, false ); + m_pConversionDialog->FocusSuggestion(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "editeng", "HangulHanjaConversion_Impl::OnFind" ); + } + } + + bool HangulHanjaConversion::m_bUseSavedValues = false; + bool HangulHanjaConversion::m_bTryBothDirectionsSave = false; + HHC::ConversionDirection HangulHanjaConversion::m_ePrimaryConversionDirectionSave = HHC::eHangulToHanja; + + HangulHanjaConversion::HangulHanjaConversion( weld::Widget* pUIParent, + const Reference< XComponentContext >& rxContext, + const lang::Locale& _rSourceLocale, const lang::Locale& _rTargetLocale, + const vcl::Font* _pTargetFont, + sal_Int32 _nOptions, bool _bIsInteractive) + :m_pImpl( new HangulHanjaConversion_Impl( pUIParent, rxContext, _rSourceLocale, _rTargetLocale, _pTargetFont, _nOptions, _bIsInteractive, this ) ) + { + } + + HangulHanjaConversion::~HangulHanjaConversion() COVERITY_NOEXCEPT_FALSE + { + } + + void HangulHanjaConversion::SetUseSavedConversionDirectionState( bool bVal ) + { + m_bUseSavedValues = bVal; + } + + bool HangulHanjaConversion::IsUseSavedConversionDirectionState() + { + return m_bUseSavedValues; + } + + weld::Widget* HangulHanjaConversion::GetUIParent() const + { + return m_pImpl->GetUIParent(); + } + + LanguageType HangulHanjaConversion::GetSourceLanguage( ) const + { + return m_pImpl->GetSourceLang(); + } + + LanguageType HangulHanjaConversion::GetTargetLanguage( ) const + { + return m_pImpl->GetTargetLang(); + } + + const vcl::Font * HangulHanjaConversion::GetTargetFont( ) const + { + return m_pImpl->GetTargetFont(); + } + + sal_Int32 HangulHanjaConversion::GetConversionOptions( ) const + { + return m_pImpl->GetConvOptions(); + } + + bool HangulHanjaConversion::IsInteractive( ) const + { + return m_pImpl->IsInteractive(); + } + + void HangulHanjaConversion::ConvertDocument() + { + if ( m_pImpl->IsValid() ) + m_pImpl->DoDocumentConversion( ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/splwrap.cxx b/editeng/source/misc/splwrap.cxx new file mode 100644 index 0000000000..dd59113a69 --- /dev/null +++ b/editeng/source/misc/splwrap.cxx @@ -0,0 +1,472 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svtools/langtab.hxx> + +#include <vcl/errinf.hxx> +#include <editeng/unolingu.hxx> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp> +#include <com/sun/star/linguistic2/XDictionary.hpp> + +#include <editeng/svxenum.hxx> +#include <editeng/splwrap.hxx> +#include <editeng/edtdlg.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <editeng/editerr.hxx> + +#include <map> +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + + +// misc functions --------------------------------------------- + +void SvxPrepareAutoCorrect( OUString &rOldText, std::u16string_view rNewText ) +{ + // This function should be used to strip (or add) trailing '.' from + // the strings before passing them on to the autocorrect function in + // order that the autocorrect function will hopefully + // works properly with normal words and abbreviations (with trailing '.') + // independent of if they are at the end of the sentence or not. + // + // rOldText: text to be replaced + // rNewText: replacement text + + sal_Int32 nOldLen = rOldText.getLength(); + sal_Int32 nNewLen = rNewText.size(); + if (nOldLen && nNewLen) + { + bool bOldHasDot = '.' == rOldText[ nOldLen - 1 ], + bNewHasDot = '.' == rNewText[ nNewLen - 1 ]; + if (bOldHasDot && !bNewHasDot + /*this is: !(bOldHasDot && bNewHasDot) && bOldHasDot*/) + rOldText = rOldText.copy( 0, nOldLen - 1 ); + } +} + +#define SVX_LANG_NEED_CHECK 0 +#define SVX_LANG_OK 1 +#define SVX_LANG_MISSING 2 +#define SVX_LANG_MISSING_DO_WARN 3 + +typedef std::map< LanguageType, sal_uInt16 > LangCheckState_map_t; + +static LangCheckState_map_t & GetLangCheckState() +{ + static LangCheckState_map_t aLangCheckState; + return aLangCheckState; +} + +void SvxSpellWrapper::ShowLanguageErrors() +{ + // display message boxes for languages not available for + // spellchecking or hyphenation + LangCheckState_map_t &rLCS = GetLangCheckState(); + for (auto const& elem : rLCS) + { + LanguageType nLang = elem.first; + sal_uInt16 nVal = elem.second; + sal_uInt16 nTmpSpell = nVal & 0x00FF; + sal_uInt16 nTmpHyph = (nVal >> 8) & 0x00FF; + + if (SVX_LANG_MISSING_DO_WARN == nTmpSpell) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + ErrCodeMsg( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpSpell = SVX_LANG_MISSING; + } + if (SVX_LANG_MISSING_DO_WARN == nTmpHyph) + { + OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) ); + ErrorHandler::HandleError( + ErrCodeMsg( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) ); + nTmpHyph = SVX_LANG_MISSING; + } + + rLCS[ nLang ] = (nTmpHyph << 8) | nTmpSpell; + } + +} + +SvxSpellWrapper::~SvxSpellWrapper() +{ +} + +/*-------------------------------------------------------------------- + * Description: Constructor, the test sequence is determined + * + * !bStart && !bOtherCntnt: BODY_END, BODY_START, OTHER + * !bStart && bOtherCntnt: OTHER, BODY + * bStart && !bOtherCntnt: BODY_END, OTHER + * bStart && bOtherCntnt: OTHER + * + --------------------------------------------------------------------*/ + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + const bool bStart, const bool bIsAllRight ) : + + pWin ( pWn ), + bOtherCntnt ( false ), + bStartChk ( false ), + bRevAllowed ( true ), + bAllRight ( bIsAllRight ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bReverse = bWrapReverse; + bStartDone = !bReverse && bStart; + bEndDone = bReverse && bStart; +} + + +SvxSpellWrapper::SvxSpellWrapper( weld::Widget* pWn, + Reference< XHyphenator > const &xHyphenator, + const bool bStart, const bool bOther ) : + pWin ( pWn ), + xHyph ( xHyphenator ), + bOtherCntnt ( bOther ), + bReverse ( false ), + bStartDone ( bOther || bStart ), + bEndDone ( false ), + bStartChk ( bOther ), + bRevAllowed ( false ), + bAllRight ( true ) +{ +} + + +sal_Int16 SvxSpellWrapper::CheckSpellLang( + Reference< XSpellChecker1 > const & xSpell, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? SVX_LANG_NEED_CHECK : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == (nVal & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xSpell.is() && xSpell->hasLanguage( static_cast<sal_uInt16>(nLang) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0xFF00; + nVal |= nTmpVal; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + +sal_Int16 SvxSpellWrapper::CheckHyphLang( + Reference< XHyphenator > const & xHyph, LanguageType nLang) +{ + LangCheckState_map_t &rLCS = GetLangCheckState(); + + LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) ); + sal_uInt16 nVal = aIt == rLCS.end() ? 0 : aIt->second; + + if (aIt == rLCS.end()) + rLCS[ nLang ] = nVal; + + if (SVX_LANG_NEED_CHECK == ((nVal >> 8) & 0x00FF)) + { + sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN; + if (xHyph.is() && xHyph->hasLocale( LanguageTag::convertToLocale( nLang ) )) + nTmpVal = SVX_LANG_OK; + nVal &= 0x00FF; + nVal |= nTmpVal << 8; + + rLCS[ nLang ] = nVal; + } + + return static_cast<sal_Int16>(nVal); +} + + +void SvxSpellWrapper::SpellStart( SvxSpellArea /*eSpell*/ ) +{ // Here, the necessary preparations be made for SpellContinue in the +} // given area. + + +bool SvxSpellWrapper::SpellMore() +{ + return false; // Should additional documents be examined? +} + + +void SvxSpellWrapper::SpellEnd() +{ // Area is complete, tidy up if necessary + + // display error for last language not found + ShowLanguageErrors(); +} + +void SvxSpellWrapper::SpellContinue() +{ +} + +void SvxSpellWrapper::ReplaceAll( const OUString & ) +{ // Replace Word from the Replace list +} + +void SvxSpellWrapper::InsertHyphen( const sal_Int32 ) +{ // inserting and deleting Hyphen +} + +// Testing of the document areas in the order specified by the flags +void SvxSpellWrapper::SpellDocument( ) +{ +#if ENABLE_WASM_STRIP_HUNSPELL + return; +#else + if ( bOtherCntnt ) + { + bReverse = false; + SpellStart( SvxSpellArea::Other ); + } + else + { + bStartChk = bReverse; + SpellStart( bReverse ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + } + + if ( !FindSpellError() ) + return; + + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xHyphWord.is()) + { + EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractHyphenWordDialog> pDlg(pFact->CreateHyphenWordDialog( + pWin, + xHyphWord->getWord(), + LanguageTag( xHyphWord->getLocale() ).getLanguageType(), + xHyph, this )); + pDlg->Execute(); + } +#endif +} + + +// Select the next area + + +bool SvxSpellWrapper::SpellNext( ) +{ + Reference< linguistic2::XLinguProperties > xProp( LinguMgr::GetLinguPropertySet() ); + bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse(); + bool bActRev = bRevAllowed && bWrapReverse; + + // bActRev is the direction after Spell checking, bReverse is the one + // at the beginning. + if( bActRev == bReverse ) + { // No change of direction, thus is the + if( bStartChk ) // desired area ( bStartChk ) + bStartDone = true; // completely processed. + else + bEndDone = true; + } + else if( bReverse == bStartChk ) //For a change of direction, an area can + { // be processed during certain circumstances + if( bStartChk ) // If the first part is spell checked in backwards + bEndDone = true; // and this is reversed in the process, then + else // then the end part is processed (and vice-versa). + bStartDone = true; + } + + bReverse = bActRev; + if( bOtherCntnt && bStartDone && bEndDone ) // Document has been fully checked? + { + if ( SpellMore() ) // spell check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + return false; + } + + bool bGoOn = false; + + if ( bOtherCntnt ) + { + bStartChk = false; + SpellStart( SvxSpellArea::Body ); + bGoOn = true; + } + else if ( bStartDone && bEndDone ) + { + if ( SpellMore() ) // check another document? + { + bOtherCntnt = false; + bStartDone = !bReverse; + bEndDone = bReverse; + SpellStart( SvxSpellArea::Body ); + return true; + } + } + else + { + // a BODY_area done, ask for the other BODY_area + xWait.reset(); + + TranslateId pResId = bReverse ? RID_SVXSTR_QUERY_BW_CONTINUE : RID_SVXSTR_QUERY_CONTINUE; + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Question, VclButtonsType::YesNo, + EditResId(pResId))); + if (xBox->run() != RET_YES) + { + // sacrifice the other area if necessary ask for special area + xWait.reset(new weld::WaitObject(pWin)); + bStartDone = bEndDone = true; + return SpellNext(); + } + else + { + bStartChk = !bStartDone; + SpellStart( bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd ); + bGoOn = true; + } + xWait.reset(new weld::WaitObject(pWin)); + } + return bGoOn; +} + + +Reference< XDictionary > SvxSpellWrapper::GetAllRightDic() +{ + Reference< XDictionary > xDic; + + Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() ); + if (xDicList.is()) + { + Sequence< Reference< XDictionary > > aDics( xDicList->getDictionaries() ); + const Reference< XDictionary > *pDic = aDics.getConstArray(); + sal_Int32 nCount = aDics.getLength(); + + sal_Int32 i = 0; + while (!xDic.is() && i < nCount) + { + Reference< XDictionary > xTmp = pDic[i]; + if (xTmp.is()) + { + if ( xTmp->isActive() && + xTmp->getDictionaryType() != DictionaryType_NEGATIVE && + LanguageTag( xTmp->getLocale() ).getLanguageType() == LANGUAGE_NONE ) + { + Reference< frame::XStorable > xStor( xTmp, UNO_QUERY ); + if (xStor.is() && xStor->hasLocation() && !xStor->isReadonly()) + { + xDic = xTmp; + } + } + } + ++i; + } + + if (!xDic.is()) + { + xDic = LinguMgr::GetStandardDic(); + if (xDic.is()) + xDic->setActive( true ); + } + } + + return xDic; +} + + +bool SvxSpellWrapper::FindSpellError() +{ + ShowLanguageErrors(); + + xWait.reset(new weld::WaitObject(pWin)); + bool bSpell = true; + + Reference< XDictionary > xAllRightDic; + if (IsAllRight()) + xAllRightDic = GetAllRightDic(); + + while ( bSpell ) + { + SpellContinue(); + + Reference< XSpellAlternatives > xAlt( GetLast(), UNO_QUERY ); + Reference< XHyphenatedWord > xHyphWord( GetLast(), UNO_QUERY ); + + if (xAlt.is()) + { + if (IsAllRight() && xAllRightDic.is()) + { + xAllRightDic->add( xAlt->getWord(), false, OUString() ); + } + else + { + // look up in ChangeAllList for misspelled word + Reference< XDictionary > xChangeAllList = + LinguMgr::GetChangeAllList(); + Reference< XDictionaryEntry > xEntry; + if (xChangeAllList.is()) + xEntry = xChangeAllList->getEntry( xAlt->getWord() ); + + if (xEntry.is()) + { + // replace word without asking + ReplaceAll( xEntry->getReplacementText() ); + } + else + bSpell = false; + } + } + else if (xHyphWord.is()) + bSpell = false; + else + { + SpellEnd(); + bSpell = SpellNext(); + } + } + xWait.reset(); + return GetLast().is(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/svxacorr.cxx b/editeng/source/misc/svxacorr.cxx new file mode 100644 index 0000000000..ff2c4518aa --- /dev/null +++ b/editeng/source/misc/svxacorr.cxx @@ -0,0 +1,3161 @@ +/* -*- 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 <memory> +#include <utility> +#include <algorithm> +#include <string_view> +#include <sal/config.h> + +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <tools/urlobj.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nutil/transliteration.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <svl/fstathelper.hxx> +#include <svl/urihelper.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/OrdinalSuffix.hpp> +#include <unotools/localedatawrapper.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <o3tl/string_view.hxx> +#include <editeng/editids.hrc> +#include <sot/storage.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/unolingu.hxx> +#include <vcl/window.hxx> +#include <com/sun/star/xml/sax/InputSource.hpp> +#include <com/sun/star/xml/sax/FastParser.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/SAXParseException.hpp> +#include <unotools/streamwrap.hxx> +#include "SvXMLAutoCorrectImport.hxx" +#include "SvXMLAutoCorrectExport.hxx" +#include "SvXMLAutoCorrectTokenHandler.hxx" +#include <ucbhelper/content.hxx> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <xmloff/xmltoken.hxx> +#include <unordered_map> +#include <rtl/character.hxx> + +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star; +using namespace ::xmloff::token; +using namespace ::utl; + +namespace { + +enum class Flags { + NONE = 0x00, + FullStop = 0x01, + ExclamationMark = 0x02, + QuestionMark = 0x04, +}; + +} + +namespace o3tl { + template<> struct typed_flags<Flags> : is_typed_flags<Flags, 0x07> {}; +} +const sal_Unicode cNonBreakingSpace = 0xA0; // UNICODE code for no break space + +constexpr OUString pXMLImplWordStart_ExcptLstStr = u"WordExceptList.xml"_ustr; +constexpr OUString pXMLImplCplStt_ExcptLstStr = u"SentenceExceptList.xml"_ustr; +constexpr OUString pXMLImplAutocorr_ListStr = u"DocumentList.xml"_ustr; + +// tdf#54409 check also typographical quotation marks in the case of skipped ASCII quotation marks +// Curious, why these \u0083\u0084\u0089\u0091\u0092\u0093\u0094 are handled as "begin characters"? +constexpr std::u16string_view + /* also at these beginnings - Brackets and all kinds of begin characters */ + sImplSttSkipChars = u"\"'([{\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u0083\u0084\u0089\u0091\u0092\u0093\u0094", + /* also at these ends - Brackets and all kinds of begin characters */ + sImplEndSkipChars = u"\"')]}\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u201f\u0083\u0084\u0089\u0091\u0092\u0093\u0094"; + +static OUString EncryptBlockName_Imp(std::u16string_view rName); + +static bool NonFieldWordDelim( const sal_Unicode c ) +{ + return ' ' == c || '\t' == c || 0x0a == c || + cNonBreakingSpace == c || 0x2011 == c; +} + +static bool IsWordDelim( const sal_Unicode c ) +{ + return c == 0x1 || NonFieldWordDelim(c); +} + + +static bool IsLowerLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::LOWER & nCharType); +} + +static bool IsUpperLetter( sal_Int32 nCharType ) +{ + return CharClass::isLetterType( nCharType ) && + ( css::i18n::KCharacterType::UPPER & nCharType); +} + +static bool lcl_IsUnsupportedUnicodeChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + css::i18n::UnicodeScript nScript = rCC.getScript( rTxt, nStt ); + switch( nScript ) + { + case css::i18n::UnicodeScript_kCJKRadicalsSupplement: + case css::i18n::UnicodeScript_kHangulJamo: + case css::i18n::UnicodeScript_kCJKSymbolPunctuation: + case css::i18n::UnicodeScript_kHiragana: + case css::i18n::UnicodeScript_kKatakana: + case css::i18n::UnicodeScript_kHangulCompatibilityJamo: + case css::i18n::UnicodeScript_kEnclosedCJKLetterMonth: + case css::i18n::UnicodeScript_kCJKCompatibility: + case css::i18n::UnicodeScript_kCJKUnifiedIdeographsExtensionA: + case css::i18n::UnicodeScript_kCJKUnifiedIdeograph: + case css::i18n::UnicodeScript_kHangulSyllable: + case css::i18n::UnicodeScript_kCJKCompatibilityIdeograph: + case css::i18n::UnicodeScript_kHalfwidthFullwidthForm: + return true; + default: ; //do nothing + } + } + return false; +} + +static bool lcl_IsSymbolChar( CharClass const & rCC, const OUString& rTxt, + sal_Int32 nStt, sal_Int32 nEnd ) +{ + for( ; nStt < nEnd; ++nStt ) + { + if( css::i18n::UnicodeType::PRIVATE_USE == rCC.getType( rTxt, nStt )) + return true; + } + return false; +} + +static bool lcl_IsInArr(std::u16string_view arr, const sal_uInt32 c) +{ + return std::any_of(arr.begin(), arr.end(), [c](const auto c1) { return c1 == c; }); +} + +SvxAutoCorrDoc::~SvxAutoCorrDoc() +{ +} + +// Called by the functions: +// - FnCapitalStartWord +// - FnCapitalStartSentence +// after the exchange of characters. Then the words, if necessary, can be inserted +// into the exception list. +void SvxAutoCorrDoc::SaveCpltSttWord( ACFlags, sal_Int32, const OUString&, + sal_Unicode ) +{ +} + +LanguageType SvxAutoCorrDoc::GetLanguage( sal_Int32 ) const +{ + return LANGUAGE_SYSTEM; +} + +static const LanguageTag& GetAppLang() +{ + return Application::GetSettings().GetLanguageTag(); +} + +/// Never use an unresolved LANGUAGE_SYSTEM. +static LanguageType GetDocLanguage( const SvxAutoCorrDoc& rDoc, sal_Int32 nPos ) +{ + LanguageType eLang = rDoc.GetLanguage( nPos ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); // the current work locale + return eLang; +} + +static LocaleDataWrapper& GetLocaleDataWrapper( LanguageType nLang ) +{ + static std::unique_ptr<LocaleDataWrapper> xLclDtWrp; + LanguageTag aLcl( nLang ); + if (!xLclDtWrp || xLclDtWrp->getLoadedLanguageTag() != aLcl) + xLclDtWrp.reset(new LocaleDataWrapper(std::move(aLcl))); + return *xLclDtWrp; +} +static TransliterationWrapper& GetIgnoreTranslWrapper() +{ + static int bIsInit = 0; + static TransliterationWrapper aWrp( ::comphelper::getProcessComponentContext(), + TransliterationFlags::IGNORE_KANA | + TransliterationFlags::IGNORE_WIDTH ); + if( !bIsInit ) + { + aWrp.loadModuleIfNeeded( GetAppLang().getLanguageType() ); + bIsInit = 1; + } + return aWrp; +} +static CollatorWrapper& GetCollatorWrapper() +{ + static CollatorWrapper aCollWrp = []() + { + CollatorWrapper tmp( ::comphelper::getProcessComponentContext() ); + tmp.loadDefaultCollator( GetAppLang().getLocale(), 0 ); + return tmp; + }(); + return aCollWrp; +} + +bool SvxAutoCorrect::IsAutoCorrectChar( sal_Unicode cChar ) +{ + return cChar == '\0' || cChar == '\t' || cChar == 0x0a || + cChar == ' ' || cChar == '\'' || cChar == '\"' || + cChar == '*' || cChar == '_' || cChar == '%' || + cChar == '.' || cChar == ',' || cChar == ';' || + cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '<' || cChar == '>' || + cChar == '/' || cChar == '-'; +} + +namespace +{ + bool IsCompoundWordDelimChar(sal_Unicode cChar) + { + return cChar == '-' || SvxAutoCorrect::IsAutoCorrectChar(cChar); + } +} + +bool SvxAutoCorrect::NeedsHardspaceAutocorr( sal_Unicode cChar ) +{ + return cChar == '%' || cChar == ';' || cChar == ':' || cChar == '?' || cChar == '!' || + cChar == '/' /*case for the urls exception*/; +} + +ACFlags SvxAutoCorrect::GetDefaultFlags() +{ + ACFlags nRet = ACFlags::Autocorrect + | ACFlags::CapitalStartSentence + | ACFlags::CapitalStartWord + | ACFlags::ChgOrdinalNumber + | ACFlags::ChgToEnEmDash + | ACFlags::AddNonBrkSpace + | ACFlags::TransliterateRTL + | ACFlags::ChgAngleQuotes + | ACFlags::ChgWeightUnderl + | ACFlags::SetINetAttr + | ACFlags::SetDOIAttr + | ACFlags::ChgQuotes + | ACFlags::SaveWordCplSttLst + | ACFlags::SaveWordWordStartLst + | ACFlags::CorrectCapsLock; + LanguageType eLang = GetAppLang().getLanguageType(); + if( eLang.anyOf( + LANGUAGE_ENGLISH, + LANGUAGE_ENGLISH_US, + LANGUAGE_ENGLISH_UK, + LANGUAGE_ENGLISH_AUS, + LANGUAGE_ENGLISH_CAN, + LANGUAGE_ENGLISH_NZ, + LANGUAGE_ENGLISH_EIRE, + LANGUAGE_ENGLISH_SAFRICA, + LANGUAGE_ENGLISH_JAMAICA, + LANGUAGE_ENGLISH_CARIBBEAN)) + nRet &= ~ACFlags(ACFlags::ChgQuotes|ACFlags::ChgSglQuotes); + return nRet; +} + +constexpr sal_Unicode cEmDash = 0x2014; +constexpr sal_Unicode cEnDash = 0x2013; +constexpr OUString sEmDash(u"\u2014"_ustr); +constexpr OUString sEnDash(u"\u2013"_ustr); +constexpr sal_Unicode cApostrophe = 0x2019; +constexpr sal_Unicode cLeftDoubleAngleQuote = 0xAB; +constexpr sal_Unicode cRightDoubleAngleQuote = 0xBB; +constexpr sal_Unicode cLeftSingleAngleQuote = 0x2039; +constexpr sal_Unicode cRightSingleAngleQuote = 0x203A; +// stop characters for searching preceding quotes +// (the first character is also the opening quote we are looking for) +const sal_Unicode aStopDoubleAngleQuoteStart[] = { 0x201E, 0x201D, 0x201C, 0 }; // preceding ,, +const sal_Unicode aStopDoubleAngleQuoteEnd[] = { cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0x201D, 0x201E, 0 }; // preceding >> +// preceding << for Romanian, handle also alternative primary closing quotation mark U+201C +const sal_Unicode aStopDoubleAngleQuoteEndRo[] = { cLeftDoubleAngleQuote, cRightDoubleAngleQuote, 0x201D, 0x201E, 0x201C, 0 }; +const sal_Unicode aStopSingleQuoteEnd[] = { 0x201A, 0x2018, 0x201C, 0x201E, 0 }; +const sal_Unicode aStopSingleQuoteEndRuUa[] = { 0x201E, 0x201C, cRightDoubleAngleQuote, cLeftDoubleAngleQuote, 0 }; + +SvxAutoCorrect::SvxAutoCorrect( OUString aShareAutocorrFile, + OUString aUserAutocorrFile ) + : sShareAutoCorrFile(std::move( aShareAutocorrFile )) + , sUserAutoCorrFile(std::move( aUserAutocorrFile )) + , eCharClassLang( LANGUAGE_DONTKNOW ) + , nFlags(SvxAutoCorrect::GetDefaultFlags()) + , cStartDQuote( 0 ) + , cEndDQuote( 0 ) + , cStartSQuote( 0 ) + , cEndSQuote( 0 ) +{ +} + +SvxAutoCorrect::SvxAutoCorrect( const SvxAutoCorrect& rCpy ) + : sShareAutoCorrFile( rCpy.sShareAutoCorrFile ) + , sUserAutoCorrFile( rCpy.sUserAutoCorrFile ) + , aSwFlags( rCpy.aSwFlags ) + , eCharClassLang(rCpy.eCharClassLang) + , nFlags( rCpy.nFlags & ~ACFlags(ACFlags::ChgWordLstLoad|ACFlags::CplSttLstLoad|ACFlags::WordStartLstLoad)) + , cStartDQuote( rCpy.cStartDQuote ) + , cEndDQuote( rCpy.cEndDQuote ) + , cStartSQuote( rCpy.cStartSQuote ) + , cEndSQuote( rCpy.cEndSQuote ) +{ +} + + +SvxAutoCorrect::~SvxAutoCorrect() +{ +} + +void SvxAutoCorrect::GetCharClass_( LanguageType eLang ) +{ + moCharClass.emplace( LanguageTag( eLang) ); + eCharClassLang = eLang; +} + +void SvxAutoCorrect::SetAutoCorrFlag( ACFlags nFlag, bool bOn ) +{ + ACFlags nOld = nFlags; + nFlags = bOn ? nFlags | nFlag + : nFlags & ~nFlag; + + if( !bOn ) + { + if( (nOld & ACFlags::CapitalStartSentence) != (nFlags & ACFlags::CapitalStartSentence) ) + nFlags &= ~ACFlags::CplSttLstLoad; + if( (nOld & ACFlags::CapitalStartWord) != (nFlags & ACFlags::CapitalStartWord) ) + nFlags &= ~ACFlags::WordStartLstLoad; + if( (nOld & ACFlags::Autocorrect) != (nFlags & ACFlags::Autocorrect) ) + nFlags &= ~ACFlags::ChgWordLstLoad; + } +} + + +// Correct TWo INitial CApitals +void SvxAutoCorrect::FnCapitalStartWord( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + CharClass& rCC = GetCharClass( eLang ); + + // Delete all non alphanumeric. Test the characters at the beginning/end of + // the word ( recognizes: "(min.", "/min.", and so on.) + for( ; nSttPos < nEndPos; ++nSttPos ) + if( rCC.isLetterNumeric( rTxt, nSttPos )) + break; + for( ; nSttPos < nEndPos; --nEndPos ) + if( rCC.isLetterNumeric( rTxt, nEndPos - 1 )) + break; + + // Is the word a compounded word separated by delimiters? + // If so, keep track of all delimiters so each constituent + // word can be checked for two initial capital letters. + std::deque<sal_Int32> aDelimiters; + + // Always check for two capitals at the beginning + // of the entire word, so start at nSttPos. + aDelimiters.push_back(nSttPos); + + // Find all compound word delimiters + for (sal_Int32 n = nSttPos; n < nEndPos; ++n) + { + if (IsCompoundWordDelimChar(rTxt[ n ])) + { + aDelimiters.push_back( n + 1 ); // Get position of char after delimiter + } + } + + // Decide where to put the terminating delimiter. + // If the last AutoCorrect char was a newline, then the AutoCorrect + // char will not be included in rTxt. + // If the last AutoCorrect char was not a newline, then the AutoCorrect + // character will be the last character in rTxt. + if (!IsCompoundWordDelimChar(rTxt[nEndPos-1])) + aDelimiters.push_back(nEndPos); + + // Iterate through the word and all words that compose it. + // Two capital letters at the beginning of word? + for (size_t nI = 0; nI < aDelimiters.size() - 1; ++nI) + { + nSttPos = aDelimiters[nI]; + nEndPos = aDelimiters[nI + 1]; + + if( nSttPos+2 < nEndPos && + IsUpperLetter( rCC.getCharacterType( rTxt, nSttPos )) && + IsUpperLetter( rCC.getCharacterType( rTxt, ++nSttPos )) && + // Is the third character a lower case + IsLowerLetter( rCC.getCharacterType( rTxt, nSttPos +1 )) && + // Do not replace special attributes + 0x1 != rTxt[ nSttPos ] && 0x2 != rTxt[ nSttPos ]) + { + // test if the word is in an exception list + OUString sWord( rTxt.copy( nSttPos - 1, nEndPos - nSttPos + 1 )); + if( !FindInWordStartExceptList(eLang, sWord) ) + { + // Check that word isn't correctly spelt before correcting: + css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller = + LinguMgr::GetSpellChecker(); + if( xSpeller->hasLanguage(static_cast<sal_uInt16>(eLang)) ) + { + Sequence< css::beans::PropertyValue > aEmptySeq; + if (xSpeller->isValid(sWord, static_cast<sal_uInt16>(eLang), aEmptySeq)) + { + return; + } + } + sal_Unicode cSave = rTxt[ nSttPos ]; + OUString sChar = rCC.lowercase( OUString(cSave) ); + if( sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar )) + { + if( ACFlags::SaveWordWordStartLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartWord, nSttPos, sWord, cSave ); + } + } + } + } +} + +// Format ordinal numbers suffixes (1st -> 1^st) +bool SvxAutoCorrect::FnChgOrdinalNumber( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang) +{ + // 1st, 2nd, 3rd, 4 - 0th + // 201th or 201st + // 12th or 12nd + bool bChg = false; + + // In some languages ordinal suffixes should never be + // changed to superscript. Let's break for those languages. + if (!eLang.anyOf( + LANGUAGE_SWEDISH, + LANGUAGE_SWEDISH_FINLAND)) + { + CharClass& rCC = GetCharClass(eLang); + + for (; nSttPos < nEndPos; ++nSttPos) + if (!lcl_IsInArr(sImplSttSkipChars, rTxt[nSttPos])) + break; + for (; nSttPos < nEndPos; --nEndPos) + if (!lcl_IsInArr(sImplEndSkipChars, rTxt[nEndPos - 1])) + break; + + + // Get the last number in the string to check + sal_Int32 nNumEnd = nEndPos; + bool bFoundEnd = false; + bool isValidNumber = true; + sal_Int32 i = nEndPos; + while (i > nSttPos) + { + i--; + bool isDigit = rCC.isDigit(rTxt, i); + if (bFoundEnd) + isValidNumber &= (isDigit || !rCC.isLetter(rTxt, i)); + + if (isDigit && !bFoundEnd) + { + bFoundEnd = true; + nNumEnd = i; + } + } + + if (bFoundEnd && isValidNumber) { + sal_Int32 nNum = o3tl::toInt32(rTxt.subView(nSttPos, nNumEnd - nSttPos + 1)); + + // Check if the characters after that number correspond to the ordinal suffix + uno::Reference< i18n::XOrdinalSuffix > xOrdSuffix + = i18n::OrdinalSuffix::create(comphelper::getProcessComponentContext()); + + const uno::Sequence< OUString > aSuffixes = xOrdSuffix->getOrdinalSuffix(nNum, rCC.getLanguageTag().getLocale()); + for (OUString const & sSuffix : aSuffixes) + { + std::u16string_view sEnd = rTxt.subView(nNumEnd + 1, nEndPos - nNumEnd - 1); + + if (sSuffix == sEnd) + { + // Check if the ordinal suffix has to be set as super script + if (rCC.isLetter(sSuffix)) + { + // Do the change + SvxEscapementItem aSvxEscapementItem(DFLT_ESC_AUTO_SUPER, + DFLT_ESC_PROP, SID_ATTR_CHAR_ESCAPEMENT); + rDoc.SetAttr(nNumEnd + 1, nEndPos, + SID_ATTR_CHAR_ESCAPEMENT, + aSvxEscapementItem); + bChg = true; + } + } + } + } + } + return bChg; +} + +// Replace dashes +bool SvxAutoCorrect::FnChgToEnEmDash( + SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + bool bRet = false; + CharClass& rCC = GetCharClass( eLang ); + if (eLang == LANGUAGE_SYSTEM) + eLang = GetAppLang().getLanguageType(); + bool bAlwaysUseEmDash = (eLang == LANGUAGE_RUSSIAN || eLang == LANGUAGE_UKRAINIAN); + + // rTxt may refer to the frame text that will change in the calls to rDoc.Delete / rDoc.Insert; + // keep a local copy for later use + OUString aOrigTxt = rTxt; + sal_Int32 nFirstReplacementTextLengthChange = 0; + + // replace " - " or " --" with "enDash" + if( 1 < nSttPos && 1 <= nEndPos - nSttPos ) + { + sal_Unicode cCh = rTxt[ nSttPos ]; + if( '-' == cCh ) + { + if( 1 < nEndPos - nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos+1 ]) + { + sal_Int32 n; + for( n = nSttPos+2; n < nEndPos && lcl_IsInArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + for( n = nSttPos-1; n && lcl_IsInArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + + // found: "[A-z0-9][<AnyEndChars>] --[<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nSttPos, nSttPos + 2 ); + rDoc.Insert( nSttPos, bAlwaysUseEmDash ? sEmDash : sEnDash ); + nFirstReplacementTextLengthChange = -1; // 2 ch -> 1 ch + bRet = true; + } + } + } + } + else if( 3 < nSttPos && + ' ' == rTxt[ nSttPos-1 ] && + '-' == rTxt[ nSttPos-2 ]) + { + sal_Int32 n, nLen = 1, nTmpPos = nSttPos - 2; + if( '-' == ( cCh = rTxt[ nTmpPos-1 ]) ) + { + --nTmpPos; + ++nLen; + cCh = rTxt[ nTmpPos-1 ]; + } + if( ' ' == cCh ) + { + for( n = nSttPos; n < nEndPos && lcl_IsInArr( + sImplSttSkipChars,(cCh = rTxt[ n ])); + ++n ) + ; + + // found: " - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) ) ) + { + cCh = ' '; + for( n = nTmpPos-1; n && lcl_IsInArr( + sImplEndSkipChars,(cCh = rTxt[ --n ])); ) + ; + // found: "[A-z0-9][<AnyEndChars>] - [<AnySttChars>][A-z0-9] + if( rCC.isLetterNumeric( OUString(cCh) )) + { + rDoc.Delete( nTmpPos, nTmpPos + nLen ); + rDoc.Insert( nTmpPos, bAlwaysUseEmDash ? sEmDash : sEnDash ); + nFirstReplacementTextLengthChange = 1 - nLen; // nLen ch -> 1 ch + bRet = true; + } + } + } + } + } + + // Replace [A-z0-9]--[A-z0-9] double dash with "emDash" or "enDash" + // [0-9]--[0-9] double dash always replaced with "enDash" + // Finnish and Hungarian use enDash instead of emDash. + bool bEnDash = (eLang == LANGUAGE_HUNGARIAN || eLang == LANGUAGE_FINNISH); + if( 4 <= nEndPos - nSttPos ) + { + std::u16string_view sTmpView( aOrigTxt.subView( nSttPos, nEndPos - nSttPos ) ); + size_t nFndPos = sTmpView.find(u"--"); + if (nFndPos > 0 && nFndPos < sTmpView.size() - 2) + { + // Use proper codepoints. Currently, CharClass::isLetterNumeric is broken, it + // uses the index *both* as code unit index (when checking it as ASCII), *and* + // as code point index (when passes to css::i18n::XCharacterClassification). + // Oh well... Anyway, single-codepoint strings will workaround it. + sal_Int32 nStart = nSttPos + nFndPos; + sal_uInt32 chStart = aOrigTxt.iterateCodePoints(&nStart, -1); + OUString sStart(&chStart, 1); + // No idea why sImplEndSkipChars is checked at start + if (rCC.isLetterNumeric(sStart, 0) || lcl_IsInArr(sImplEndSkipChars, chStart)) + { + sal_Int32 nEnd = nSttPos + nFndPos + 2; + sal_uInt32 chEnd = aOrigTxt.iterateCodePoints(&nEnd, 1); + OUString sEnd(&chEnd, 1); + // No idea why sImplSttSkipChars is checked at end + if (rCC.isLetterNumeric(sEnd, 0) || lcl_IsInArr(sImplSttSkipChars, chEnd)) + { + nSttPos = nSttPos + nFndPos + nFirstReplacementTextLengthChange; + rDoc.Delete(nSttPos, nSttPos + 2); + rDoc.Insert(nSttPos, + (bEnDash || (rCC.isDigit(sStart, 0) && rCC.isDigit(sEnd, 0)) + ? sEnDash + : sEmDash)); + bRet = true; + } + } + } + } + return bRet; +} + +// Add non-breaking space before specific punctuation marks in French text +sal_Int32 SvxAutoCorrect::FnAddNonBrkSpace( + SvxAutoCorrDoc& rDoc, std::u16string_view rTxt, + sal_Int32 nEndPos, + LanguageType eLang, bool& io_bNbspRunNext ) +{ + sal_Int32 nRet = -1; + + CharClass& rCC = GetCharClass( eLang ); + + if ( rCC.getLanguageTag().getLanguage() == "fr" ) + { + bool bFrCA = (rCC.getLanguageTag().getCountry() == "CA"); + OUString allChars = ":;?!%"; + OUString chars( allChars ); + if ( bFrCA ) + chars = ":"; + + sal_Unicode cChar = rTxt[ nEndPos ]; + bool bHasSpace = chars.indexOf( cChar ) != -1; + bool bIsSpecial = allChars.indexOf( cChar ) != -1; + if ( bIsSpecial ) + { + // Get the last word delimiter position + sal_Int32 nSttWdPos = nEndPos; + bool bWasWordDelim = false; + while( nSttWdPos ) + { + bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]); + if (bWasWordDelim) + break; + } + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + size_t nIndex = nSttWdPos + (bWasWordDelim ? 1 : 0); + size_t nProtocolLen = nEndPos - nSttWdPos + 1; + if (nIndex + nProtocolLen <= rTxt.size()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.substr(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return -1; + } + + // Check the presence of "://" in the word + size_t nStrPos = rTxt.find( u"://", nSttWdPos + 1 ); + if ( nStrPos == std::u16string_view::npos && nEndPos > 0 ) + { + // Check the previous char + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + if ( ( chars.indexOf( cPrevChar ) == -1 ) && cPrevChar != '\t' ) + { + // Remove any previous normal space + sal_Int32 nPos = nEndPos - 1; + while ( cPrevChar == ' ' || cPrevChar == cNonBreakingSpace ) + { + if ( nPos == 0 ) break; + nPos--; + cPrevChar = rTxt[ nPos ]; + } + + nPos++; + if ( nEndPos - nPos > 0 ) + rDoc.Delete( nPos, nEndPos ); + + // Add the non-breaking space at the end pos + if ( bHasSpace ) + rDoc.Insert( nPos, OUString(cNonBreakingSpace) ); + io_bNbspRunNext = true; + nRet = nPos; + } + else if ( chars.indexOf( cPrevChar ) != -1 ) + io_bNbspRunNext = true; + } + } + else if ( cChar == '/' && nEndPos > 1 && static_cast<sal_Int32>(rTxt.size()) > (nEndPos - 1) ) + { + // Remove the hardspace right before to avoid formatting URLs + sal_Unicode cPrevChar = rTxt[ nEndPos - 1 ]; + sal_Unicode cMaybeSpaceChar = rTxt[ nEndPos - 2 ]; + if ( cPrevChar == ':' && cMaybeSpaceChar == cNonBreakingSpace ) + { + rDoc.Delete( nEndPos - 2, nEndPos - 1 ); + nRet = nEndPos - 1; + } + } + } + + return nRet; +} + +// URL recognition +bool SvxAutoCorrect::FnSetINetAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + OUString sURL( URIHelper::FindFirstURLInText( rTxt, nSttPos, nEndPos, + GetCharClass( eLang ) )); + bool bRet = !sURL.isEmpty(); + if( bRet ) // so, set attribute: + rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); + return bRet; +} + +// DOI citation recognition +bool SvxAutoCorrect::FnSetDOIAttr( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + OUString sURL( URIHelper::FindFirstDOIInText( rTxt, nSttPos, nEndPos, GetCharClass( eLang ) )); + bool bRet = !sURL.isEmpty(); + if( bRet ) // so, set attribute: + rDoc.SetINetAttr( nSttPos, nEndPos, sURL ); + return bRet; +} + +// Automatic *bold*, /italic/, -strikeout- and _underline_ +bool SvxAutoCorrect::FnChgWeightUnderl( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nEndPos ) +{ + // Condition: + // at the beginning: _, *, / or ~ after Space with the following !Space + // at the end: _, *, / or ~ before Space (word delimiter?) + + sal_Unicode cInsChar = rTxt[ nEndPos ]; // underline, bold, italic or strikeout + if( ++nEndPos != rTxt.getLength() && + !IsWordDelim( rTxt[ nEndPos ] ) ) + return false; + + --nEndPos; + + bool bAlphaNum = false; + sal_Int32 nPos = nEndPos; + sal_Int32 nFndPos = -1; + CharClass& rCC = GetCharClass( LANGUAGE_SYSTEM ); + + while( nPos ) + { + switch( sal_Unicode c = rTxt[ --nPos ] ) + { + case '_': + case '-': + case '/': + case '*': + if( c == cInsChar ) + { + if( bAlphaNum && nPos+1 < nEndPos && ( !nPos || + IsWordDelim( rTxt[ nPos-1 ])) && + !IsWordDelim( rTxt[ nPos+1 ])) + nFndPos = nPos; + else + // Condition is not satisfied, so cancel + nFndPos = -1; + nPos = 0; + } + break; + default: + if( !bAlphaNum ) + bAlphaNum = rCC.isLetterNumeric( rTxt, nPos ); + } + } + + if( -1 != nFndPos ) + { + // first delete the Character at the end - this allows insertion + // of an empty hint in SetAttr which would be removed by Delete + // (fdo#62536, AUTOFMT in Writer) + rDoc.Delete( nEndPos, nEndPos + 1 ); + + // Span the Attribute over the area + // the end. + if( '*' == cInsChar ) // Bold + { + SvxWeightItem aSvxWeightItem( WEIGHT_BOLD, SID_ATTR_CHAR_WEIGHT ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_WEIGHT, + aSvxWeightItem); + } + else if( '/' == cInsChar ) // Italic + { + SvxPostureItem aSvxPostureItem( ITALIC_NORMAL, SID_ATTR_CHAR_POSTURE ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_POSTURE, + aSvxPostureItem); + } + else if( '-' == cInsChar ) // Strikeout + { + SvxCrossedOutItem aSvxCrossedOutItem( STRIKEOUT_SINGLE, SID_ATTR_CHAR_STRIKEOUT ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_STRIKEOUT, + aSvxCrossedOutItem); + } + else // Underline + { + SvxUnderlineItem aSvxUnderlineItem( LINESTYLE_SINGLE, SID_ATTR_CHAR_UNDERLINE ); + rDoc.SetAttr( nFndPos + 1, nEndPos, + SID_ATTR_CHAR_UNDERLINE, + aSvxUnderlineItem); + } + rDoc.Delete( nFndPos, nFndPos + 1 ); + } + + return -1 != nFndPos; +} + +// Capitalize first letter of every sentence +void SvxAutoCorrect::FnCapitalStartSentence( SvxAutoCorrDoc& rDoc, + const OUString& rTxt, bool bNormalPos, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + + if( rTxt.isEmpty() || nEndPos <= nSttPos ) + return; + + CharClass& rCC = GetCharClass( eLang ); + OUString aText( rTxt ); + const sal_Unicode *pStart = aText.getStr(), + *pStr = pStart + nEndPos, + *pWordStt = nullptr, + *pDelim = nullptr; + + bool bAtStart = false; + do { + --pStr; + if (rCC.isLetter(aText, pStr - pStart)) + { + if( !pWordStt ) + pDelim = pStr+1; + pWordStt = pStr; + } + else if (pWordStt && !rCC.isDigit(aText, pStr - pStart)) + { + if( (lcl_IsInArr( u"-'", *pStr ) || *pStr == cApostrophe) && // These characters are allowed in words + pWordStt - 1 == pStr && + // Installation at beginning of paragraph. Replaced < by <= (#i38971#) + (pStart + 1) <= pStr && + rCC.isLetter(aText, pStr-1 - pStart)) + pWordStt = --pStr; + else + break; + } + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if (!pWordStt) + return; // no character to be replaced + + + if (rCC.isDigit(aText, pStr - pStart)) + return; // already ok + + if (IsUpperLetter(rCC.getCharacterType(aText, pWordStt - pStart))) + return; // already ok + + //See if the text is the start of a protocol string, e.g. have text of + //"http" see if it is the start of "http:" and if so leave it alone + sal_Int32 nIndex = pWordStt - pStart; + sal_Int32 nProtocolLen = pDelim - pWordStt + 1; + if (nIndex + nProtocolLen <= rTxt.getLength()) + { + if (INetURLObject::CompareProtocolScheme(rTxt.subView(nIndex, nProtocolLen)) != INetProtocol::NotValid) + return; // already ok + } + + if (0x1 == *pWordStt || 0x2 == *pWordStt) + return; // already ok + + // Only capitalize, if string before specified characters is long enough + if( *pDelim && 2 >= pDelim - pWordStt && + lcl_IsInArr( u".-)>", *pDelim ) ) + return; + + // tdf#59666 don't capitalize single Greek letters (except in Greek texts) + if ( 1 == pDelim - pWordStt && 0x03B1 <= *pWordStt && *pWordStt <= 0x03C9 && eLang != LANGUAGE_GREEK ) + return; + + if( !bAtStart ) // Still no beginning of a paragraph? + { + if (NonFieldWordDelim(*pStr)) + { + for (;;) + { + bAtStart = (pStart == pStr--); + if (bAtStart || !NonFieldWordDelim(*pStr)) + break; + } + } + // Asian full stop, full width full stop, full width exclamation mark + // and full width question marks are treated as word delimiters + else if ( 0x3002 != *pStr && 0xFF0E != *pStr && 0xFF01 != *pStr && + 0xFF1F != *pStr ) + return; // no valid separator -> no replacement + } + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, OUString(pWordStt, pDelim - pWordStt))) + return; + + if( bAtStart ) // at the beginning of a paragraph? + { + // Check out the previous paragraph, if it exists. + // If so, then check to paragraph separator at the end. + OUString const*const pPrevPara = rDoc.GetPrevPara(bNormalPos); + if (!pPrevPara) + { + // valid separator -> replace + OUString sChar( *pWordStt ); + sChar = rCC.titlecase(sChar); //see fdo#56740 + if (sChar != OUStringChar(*pWordStt)) + rDoc.ReplaceRange( pWordStt - pStart, 1, sChar ); + return; + } + + aText = *pPrevPara; + bAtStart = false; + pStart = aText.getStr(); + pStr = pStart + aText.getLength(); + + do { // overwrite all blanks + --pStr; + if (!NonFieldWordDelim(*pStr)) + break; + bAtStart = (pStart == pStr); + } while( !bAtStart ); + + if( bAtStart ) + return; // no valid separator -> no replacement + } + + // Found [ \t]+[A-Z0-9]+ until here. Test now on the paragraph separator. + // all three can happen, but not more than once! + const sal_Unicode* pExceptStt = nullptr; + bool bContinue = true; + Flags nFlag = Flags::NONE; + do + { + switch (*pStr) + { + // Western and Asian full stop + case '.': + case 0x3002: + case 0xFF0E: + { + if (pStr >= pStart + 2 && *(pStr - 2) == '.') + { + //e.g. text "f.o.o. word": Now currently considering + //capitalizing word but second last character of + //previous word is a . So probably last word is an + //anagram that ends in . and not truly the end of a + //previous sentence, so don't autocapitalize this word + return; + } + if (nFlag & Flags::FullStop) + return; // no valid separator -> no replacement + nFlag |= Flags::FullStop; + pExceptStt = pStr; + } + break; + case '!': + case 0xFF01: + { + if (nFlag & Flags::ExclamationMark) + return; // no valid separator -> no replacement + nFlag |= Flags::ExclamationMark; + } + break; + case '?': + case 0xFF1F: + { + if (nFlag & Flags::QuestionMark) + return; // no valid separator -> no replacement + nFlag |= Flags::QuestionMark; + } + break; + default: + if (nFlag == Flags::NONE) + return; // no valid separator -> no replacement + else + bContinue = false; + break; + } + + if (bContinue && pStr-- == pStart) + { + return; // no valid separator -> no replacement + } + } while (bContinue); + if (Flags::FullStop != nFlag) + pExceptStt = nullptr; + + // Only capitalize, if string is long enough + if( 2 > ( pStr - pStart ) ) + return; + + if (!rCC.isLetterNumeric(aText, pStr-- - pStart)) + { + bool bValid = false, bAlphaFnd = false; + const sal_Unicode* pTmpStr = pStr; + while( !bValid ) + { + if( rCC.isDigit( aText, pTmpStr - pStart ) ) + { + bValid = true; + pStr = pTmpStr - 1; + } + else if( rCC.isLetter( aText, pTmpStr - pStart ) ) + { + if( bAlphaFnd ) + { + bValid = true; + pStr = pTmpStr; + } + else + bAlphaFnd = true; + } + else if (bAlphaFnd || NonFieldWordDelim(*pTmpStr)) + break; + + if( pTmpStr == pStart ) + break; + + --pTmpStr; + } + + if( !bValid ) + return; // no valid separator -> no replacement + } + + bool bNumericOnly = '0' <= *(pStr+1) && *(pStr+1) <= '9'; + + // Search for the beginning of the word + while (!NonFieldWordDelim(*pStr)) + { + if( bNumericOnly && rCC.isLetter( aText, pStr - pStart ) ) + bNumericOnly = false; + + if( pStart == pStr ) + break; + + --pStr; + } + + if( bNumericOnly ) // consists of only numbers, then not + return; + + if (NonFieldWordDelim(*pStr)) + ++pStr; + + OUString sWord; + + // check on the basis of the exception list + if( pExceptStt ) + { + sWord = OUString(pStr, pExceptStt - pStr + 1); + if( FindInCplSttExceptList(eLang, sWord) ) + return; + + // Delete all non alphanumeric. Test the characters at the + // beginning/end of the word ( recognizes: "(min.", "/min.", and so on.) + OUString sTmp( sWord ); + while( !sTmp.isEmpty() && + !rCC.isLetterNumeric( sTmp, 0 ) ) + sTmp = sTmp.copy(1); + + // Remove all non alphanumeric characters towards the end up until + // the last one. + sal_Int32 nLen = sTmp.getLength(); + while( nLen && !rCC.isLetterNumeric( sTmp, nLen-1 ) ) + --nLen; + if( nLen + 1 < sTmp.getLength() ) + sTmp = sTmp.copy( 0, nLen + 1 ); + + if( !sTmp.isEmpty() && sTmp.getLength() != sWord.getLength() && + FindInCplSttExceptList(eLang, sTmp)) + return; + + if(FindInCplSttExceptList(eLang, sWord, true)) + return; + } + + // Ok, then replace + sal_Unicode cSave = *pWordStt; + nSttPos = pWordStt - rTxt.getStr(); + OUString sChar = rCC.titlecase(OUString(cSave)); //see fdo#56740 + bool bRet = sChar[0] != cSave && rDoc.ReplaceRange( nSttPos, 1, sChar ); + + // Perhaps someone wants to have the word + if( bRet && ACFlags::SaveWordCplSttLst & nFlags ) + rDoc.SaveCpltSttWord( ACFlags::CapitalStartSentence, nSttPos, sWord, cSave ); +} + +// Correct accidental use of cAPS LOCK key +bool SvxAutoCorrect::FnCorrectCapsLock( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nSttPos, sal_Int32 nEndPos, + LanguageType eLang ) +{ + if (nEndPos - nSttPos < 2) + // string must be at least 2-character long. + return false; + + CharClass& rCC = GetCharClass( eLang ); + + // Check the first 2 letters. + if ( !IsLowerLetter(rCC.getCharacterType(rTxt, nSttPos)) ) + return false; + + if ( !IsUpperLetter(rCC.getCharacterType(rTxt, nSttPos+1)) ) + return false; + + OUStringBuffer aConverted; + aConverted.append( rCC.uppercase(OUString(rTxt[nSttPos])) ); + aConverted.append( rCC.lowercase(OUString(rTxt[nSttPos+1])) ); + + // No replacement for words in TWo INitial CApitals or sMALL iNITIAL list + if (FindInWordStartExceptList(eLang, rTxt.copy(nSttPos, nEndPos - nSttPos))) + return false; + + for( sal_Int32 i = nSttPos+2; i < nEndPos; ++i ) + { + if ( IsLowerLetter(rCC.getCharacterType(rTxt, i)) ) + // A lowercase letter disqualifies the whole text. + return false; + + if ( IsUpperLetter(rCC.getCharacterType(rTxt, i)) ) + // Another uppercase letter. Convert it. + aConverted.append( rCC.lowercase(OUString(rTxt[i])) ); + else + // This is not an alphabetic letter. Leave it as-is. + aConverted.append( rTxt[i] ); + } + + // Replace the word. + rDoc.Delete(nSttPos, nEndPos); + rDoc.Insert(nSttPos, aConverted.makeStringAndClear()); + + return true; +} + + +sal_Unicode SvxAutoCorrect::GetQuote( sal_Unicode cInsChar, bool bSttQuote, + LanguageType eLang ) const +{ + sal_Unicode cRet = bSttQuote ? ( '\"' == cInsChar + ? GetStartDoubleQuote() + : GetStartSingleQuote() ) + : ( '\"' == cInsChar + ? GetEndDoubleQuote() + : GetEndSingleQuote() ); + if( !cRet ) + { + // then through the Language find the right character + if( LANGUAGE_NONE == eLang ) + cRet = cInsChar; + else + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + OUString sRet( bSttQuote + ? ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkStart() + : rLcl.getQuotationMarkStart() ) + : ( '\"' == cInsChar + ? rLcl.getDoubleQuotationMarkEnd() + : rLcl.getQuotationMarkEnd() )); + cRet = !sRet.isEmpty() ? sRet[0] : cInsChar; + } + } + return cRet; +} + +void SvxAutoCorrect::InsertQuote( SvxAutoCorrDoc& rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote, + bool bIns, LanguageType eLang, ACQuotes eType ) const +{ + sal_Unicode cRet; + + if ( eType == ACQuotes::DoubleAngleQuote ) + { + bool bSwiss = eLang == LANGUAGE_FRENCH_SWISS; + // pressing " inside a quotation -> use second level angle quotes + bool bLeftQuote = '\"' == cInsChar && + // start position and Romanian OR + // not start position and Hungarian + bSttQuote == (eLang != LANGUAGE_HUNGARIAN); + cRet = ( '<' == cInsChar || bLeftQuote ) + ? ( bSwiss ? cLeftSingleAngleQuote : cLeftDoubleAngleQuote ) + : ( bSwiss ? cRightSingleAngleQuote : cRightDoubleAngleQuote ); + } + else if ( eType == ACQuotes::UseApostrophe ) + cRet = cApostrophe; + else + cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sChg( cInsChar ); + if( bIns ) + rDoc.Insert( nInsPos, sChg ); + else + rDoc.Replace( nInsPos, sChg ); + + sChg = OUString(cRet); + + if( eType == ACQuotes::NonBreakingSpace ) + { + if( rDoc.Insert( bSttQuote ? nInsPos+1 : nInsPos, OUStringChar(cNonBreakingSpace) )) + { + if( !bSttQuote ) + ++nInsPos; + } + } + else if( eType == ACQuotes::DoubleAngleQuote && cInsChar != '\"' ) + { + rDoc.Delete( nInsPos-1, nInsPos); + --nInsPos; + } + + rDoc.Replace( nInsPos, sChg ); + + // i' -> I' in English (last step for the Undo) + if( eType == ACQuotes::CapitalizeIAm ) + rDoc.Replace( nInsPos-1, "I" ); +} + +OUString SvxAutoCorrect::GetQuote( SvxAutoCorrDoc const & rDoc, sal_Int32 nInsPos, + sal_Unicode cInsChar, bool bSttQuote ) +{ + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + sal_Unicode cRet = GetQuote( cInsChar, bSttQuote, eLang ); + + OUString sRet(cRet); + + if( '\"' == cInsChar ) + { + if (primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS) + { + if( bSttQuote ) + sRet += " "; + else + sRet = " " + sRet; + } + } + return sRet; +} + +// search preceding opening quote in the paragraph before the insert position +static bool lcl_HasPrecedingChar( std::u16string_view rTxt, sal_Int32 nPos, + const sal_Unicode sPrecedingChar, const sal_Unicode sStopChar, const sal_Unicode* aStopChars ) +{ + sal_Unicode cTmpChar; + + do { + cTmpChar = rTxt[ --nPos ]; + if ( cTmpChar == sPrecedingChar ) + return true; + + if ( cTmpChar == sStopChar ) + return false; + + for ( const sal_Unicode* pCh = aStopChars; *pCh; ++pCh ) + if ( cTmpChar == *pCh ) + return false; + + } while ( nPos > 0 ); + + return false; +} + +// WARNING: rText may become invalid, see comment below +void SvxAutoCorrect::DoAutoCorrect( SvxAutoCorrDoc& rDoc, const OUString& rTxt, + sal_Int32 nInsPos, sal_Unicode cChar, + bool bInsert, bool& io_bNbspRunNext, vcl::Window const * pFrameWin ) +{ + bool bIsNextRun = io_bNbspRunNext; + io_bNbspRunNext = false; // if it was set, then it has to be turned off + + do{ // only for middle check loop !! + if( cChar ) + { + // Prevent double space + if( nInsPos && ' ' == cChar && + IsAutoCorrFlag( ACFlags::IgnoreDoubleSpace ) && + ' ' == rTxt[ nInsPos - 1 ]) + { + break; + } + + bool bSingle = '\'' == cChar; + bool bIsReplaceQuote = + (IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == cChar )) || + (IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && bSingle ); + if( bIsReplaceQuote ) + { + bool bSttQuote = !nInsPos; + ACQuotes eType = ACQuotes::NONE; + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if (!bSttQuote) + { + sal_Unicode cPrev = rTxt[ nInsPos-1 ]; + bSttQuote = NonFieldWordDelim(cPrev) || + lcl_IsInArr( u"([{", cPrev ) || + ( cEmDash == cPrev ) || + ( cEnDash == cPrev ); + // tdf#38394 use opening quotation mark << in French l'<<word>> + if ( !bSingle && !bSttQuote && cPrev == cApostrophe && + primary(eLang) == primary(LANGUAGE_FRENCH) && + ( ( ( nInsPos == 2 || ( nInsPos > 2 && IsWordDelim( rTxt[ nInsPos-3 ] ) ) ) && + // abbreviated form of ce, de, je, la, le, ne, me, te, se or si + OUString("cdjlnmtsCDJLNMTS").indexOf( rTxt[ nInsPos-2 ] ) > -1 ) || + ( ( nInsPos == 3 || (nInsPos > 3 && IsWordDelim( rTxt[ nInsPos-4 ] ) ) ) && + // abbreviated form of que + ( rTxt[ nInsPos-2 ] == 'u' || rTxt[ nInsPos-2 ] == 'U' ) && + ( rTxt[ nInsPos-3 ] == 'q' || rTxt[ nInsPos-3 ] == 'Q' ) ) ) ) + { + bSttQuote = true; + } + // tdf#108423 for capitalization of English i'm + else if ( bSingle && ( cPrev == 'i' ) && + primary(eLang) == primary(LANGUAGE_ENGLISH) && + ( nInsPos == 1 || IsWordDelim( rTxt[ nInsPos-2 ] ) ) ) + { + eType = ACQuotes::CapitalizeIAm; + } + // tdf#133524 support >>Hungarian<< and <<Romanian>> secondary level quotations + else if ( !bSingle && nInsPos && + ( ( eLang == LANGUAGE_HUNGARIAN && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEnd[0], + bSttQuote ? aStopDoubleAngleQuoteStart[1] : aStopDoubleAngleQuoteEnd[1], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEnd + 2 ) ) || + ( eLang.anyOf( + LANGUAGE_ROMANIAN, + LANGUAGE_ROMANIAN_MOLDOVA ) && + lcl_HasPrecedingChar( rTxt, nInsPos, + bSttQuote ? aStopDoubleAngleQuoteStart[0] : aStopDoubleAngleQuoteEndRo[0], + bSttQuote ? aStopDoubleAngleQuoteStart[1] : aStopDoubleAngleQuoteEndRo[1], + bSttQuote ? aStopDoubleAngleQuoteStart + 1 : aStopDoubleAngleQuoteEndRo + 2 ) ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + // only if the opening double quotation mark is the default one + if ( rLcl.getDoubleQuotationMarkStart() == OUStringChar(aStopDoubleAngleQuoteStart[0]) ) + eType = ACQuotes::DoubleAngleQuote; + } + else if ( bSingle && nInsPos && !bSttQuote && + // tdf#128860 use apostrophe outside of second level quotation in Czech, German, Icelandic, + // Slovak and Slovenian instead of the – in this case, bad – closing quotation mark U+2018. + // tdf#123786 the same for Russian and Ukrainian + ( eLang.anyOf ( + LANGUAGE_CZECH, + LANGUAGE_GERMAN, + LANGUAGE_GERMAN_SWISS, + LANGUAGE_GERMAN_AUSTRIAN, + LANGUAGE_GERMAN_LUXEMBOURG, + LANGUAGE_GERMAN_LIECHTENSTEIN, + LANGUAGE_ICELANDIC, + LANGUAGE_SLOVAK, + LANGUAGE_SLOVENIAN ) ) ) + { + sal_Unicode sStartChar = GetStartSingleQuote(); + sal_Unicode sEndChar = GetEndSingleQuote(); + if ( !sStartChar || !sEndChar ) { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + if ( !sStartChar ) sStartChar = rLcl.getQuotationMarkStart()[0]; + if ( !sEndChar ) sEndChar = rLcl.getQuotationMarkStart()[0]; + } + if ( !lcl_HasPrecedingChar( rTxt, nInsPos, sStartChar, sEndChar, aStopSingleQuoteEnd + 1 ) ) + { + CharClass& rCC = GetCharClass( eLang ); + if ( rCC.isLetter(rTxt, nInsPos-1) ) + { + eType = ACQuotes::UseApostrophe; + } + } + } + else if ( bSingle && nInsPos && !bSttQuote && + ( eLang.anyOf ( + LANGUAGE_RUSSIAN, + LANGUAGE_UKRAINIAN ) && + !lcl_HasPrecedingChar( rTxt, nInsPos, aStopSingleQuoteEndRuUa[0], aStopSingleQuoteEndRuUa[1], aStopSingleQuoteEndRuUa + 2 ) ) ) + { + LocaleDataWrapper& rLcl = GetLocaleDataWrapper( eLang ); + CharClass& rCC = GetCharClass( eLang ); + if ( rLcl.getQuotationMarkStart() == OUStringChar(aStopSingleQuoteEndRuUa[0]) && + // use apostrophe only after letters, not after digits or punctuation + rCC.isLetter(rTxt, nInsPos-1) ) + { + eType = ACQuotes::UseApostrophe; + } + } + } + + if ( eType == ACQuotes::NONE && !bSingle && + ( primary(eLang) == primary(LANGUAGE_FRENCH) && eLang != LANGUAGE_FRENCH_SWISS ) ) + eType = ACQuotes::NonBreakingSpace; + + InsertQuote( rDoc, nInsPos, cChar, bSttQuote, bInsert, eLang, eType ); + break; + } + // tdf#133524 change "<<" and ">>" to double angle quotation marks + else if ( IsAutoCorrFlag( ACFlags::ChgQuotes ) && + IsAutoCorrFlag( ACFlags::ChgAngleQuotes ) && + ('<' == cChar || '>' == cChar) && + nInsPos > 0 && cChar == rTxt[ nInsPos-1 ] ) + { + const LanguageType eLang = GetDocLanguage( rDoc, nInsPos ); + if ( eLang.anyOf( + LANGUAGE_CATALAN, // primary level + LANGUAGE_CATALAN_VALENCIAN, // primary level + LANGUAGE_FINNISH, // alternative primary level + LANGUAGE_FRENCH_SWISS, // second level + LANGUAGE_GALICIAN, // primary level + LANGUAGE_HUNGARIAN, // second level + LANGUAGE_POLISH, // second level + LANGUAGE_PORTUGUESE, // primary level + LANGUAGE_PORTUGUESE_BRAZILIAN, // primary level + LANGUAGE_ROMANIAN, // second level + LANGUAGE_ROMANIAN_MOLDOVA, // second level + LANGUAGE_SWEDISH, // alternative primary level + LANGUAGE_SWEDISH_FINLAND, // alternative primary level + LANGUAGE_UKRAINIAN, // primary level + LANGUAGE_USER_ARAGONESE, // primary level + LANGUAGE_USER_ASTURIAN ) || // primary level + primary(eLang) == primary(LANGUAGE_GERMAN) || // alternative primary level + primary(eLang) == primary(LANGUAGE_SPANISH) ) // primary level + { + InsertQuote( rDoc, nInsPos, cChar, false, bInsert, eLang, ACQuotes::DoubleAngleQuote ); + break; + } + } + + if( bInsert ) + rDoc.Insert( nInsPos, OUString(cChar) ); + else + rDoc.Replace( nInsPos, OUString(cChar) ); + + // Hardspaces autocorrection + if ( IsAutoCorrFlag( ACFlags::AddNonBrkSpace ) ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and its length may change (even become shorter) if FnAddNonBrkSpace succeeds! + sal_Int32 nUpdatedPos = -1; + if (NeedsHardspaceAutocorr(cChar)) + nUpdatedPos = FnAddNonBrkSpace( rDoc, rTxt, nInsPos, GetDocLanguage( rDoc, nInsPos ), io_bNbspRunNext ); + if (nUpdatedPos >= 0) + { + nInsPos = nUpdatedPos; + } + else if ( bIsNextRun && !IsAutoCorrectChar( cChar ) ) + { + // Remove the NBSP if it wasn't an autocorrection + if ( nInsPos != 0 && NeedsHardspaceAutocorr( rTxt[ nInsPos - 1 ] ) && + cChar != ' ' && cChar != '\t' && cChar != cNonBreakingSpace ) + { + // Look for the last HARD_SPACE + sal_Int32 nPos = nInsPos - 1; + bool bContinue = true; + while ( bContinue ) + { + const sal_Unicode cTmpChar = rTxt[ nPos ]; + if ( cTmpChar == cNonBreakingSpace ) + { + rDoc.Delete( nPos, nPos + 1 ); + bContinue = false; + } + else if ( !NeedsHardspaceAutocorr( cTmpChar ) || nPos == 0 ) + bContinue = false; + nPos--; + } + } + } + } + } + + if( !nInsPos ) + break; + + sal_Int32 nPos = nInsPos - 1; + + if( IsWordDelim( rTxt[ nPos ])) + break; + + // Set bold or underline automatically? + if (('*' == cChar || '_' == cChar || '/' == cChar || '-' == cChar) && (nPos+1 < rTxt.getLength())) + { + if( IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) ) + { + FnChgWeightUnderl( rDoc, rTxt, nPos+1 ); + } + break; + } + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // begin of paragraph and no blank + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + CharClass& rCC = GetCharClass( eLang ); + + // no symbol characters + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nInsPos )) + break; + + if( IsAutoCorrFlag( ACFlags::Autocorrect ) && + // tdf#134940 fix regression of arrow "-->" resulted by premature + // replacement of "--" since '>' was added to IsAutoCorrectChar() + '>' != cChar ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if ChgAutoCorrWord returns true! + // => use aPara/pPara to create a valid copy of the string! + OUString aPara; + OUString* pPara = IsAutoCorrFlag(ACFlags::CapitalStartSentence) ? &aPara : nullptr; + + bool bChgWord = rDoc.ChgAutoCorrWord( nCapLttrPos, nInsPos, + *this, pPara ); + if( !bChgWord ) + { + sal_Int32 nCapLttrPos1 = nCapLttrPos, nInsPos1 = nInsPos; + while( nCapLttrPos1 < nInsPos && + lcl_IsInArr( sImplSttSkipChars, rTxt[ nCapLttrPos1 ] ) + ) + ++nCapLttrPos1; + while( nCapLttrPos1 < nInsPos1 && nInsPos1 && + lcl_IsInArr( sImplEndSkipChars, rTxt[ nInsPos1-1 ] ) + ) + --nInsPos1; + + if( (nCapLttrPos1 != nCapLttrPos || nInsPos1 != nInsPos ) && + nCapLttrPos1 < nInsPos1 && + rDoc.ChgAutoCorrWord( nCapLttrPos1, nInsPos1, *this, pPara )) + { + bChgWord = true; + nCapLttrPos = nCapLttrPos1; + } + } + + if( bChgWord ) + { + if( !aPara.isEmpty() ) + { + sal_Int32 nEnd = nCapLttrPos; + while( nEnd < aPara.getLength() && + !IsWordDelim( aPara[ nEnd ])) + ++nEnd; + + // Capital letter at beginning of paragraph? + if( IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, aPara, false, + nCapLttrPos, nEnd, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, aPara, nCapLttrPos, nEnd, eLang ); + } + } + break; + } + } + + if( IsAutoCorrFlag( ACFlags::TransliterateRTL ) && GetDocLanguage( rDoc, nInsPos ) == LANGUAGE_HUNGARIAN ) + { + // WARNING ATTENTION: rTxt is an alias of the text node's OUString + // and becomes INVALID if TransliterateRTLWord returns true! + if ( rDoc.TransliterateRTLWord( nCapLttrPos, nInsPos ) ) + break; + } + + if( ( IsAutoCorrFlag( ACFlags::ChgOrdinalNumber ) && + (nInsPos >= 2 ) && // fdo#69762 avoid autocorrect for 2e-3 + ( '-' != cChar || 'E' != rtl::toAsciiUpperCase(rTxt[nInsPos-1]) || '0' > rTxt[nInsPos-2] || '9' < rTxt[nInsPos-2] ) && + FnChgOrdinalNumber( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || + ( IsAutoCorrFlag( ACFlags::SetINetAttr ) && + ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && + FnSetINetAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) || + ( IsAutoCorrFlag( ACFlags::SetDOIAttr ) && + ( ' ' == cChar || '\t' == cChar || 0x0a == cChar || !cChar ) && + FnSetDOIAttr( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) ) + ; + else + { + bool bLockKeyOn = pFrameWin && (pFrameWin->GetIndicatorState() & KeyIndicatorState::CAPSLOCK); + bool bUnsupported = lcl_IsUnsupportedUnicodeChar( rCC, rTxt, nCapLttrPos, nInsPos ); + + if ( bLockKeyOn && IsAutoCorrFlag( ACFlags::CorrectCapsLock ) && + FnCorrectCapsLock( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ) ) + { + // Correct accidental use of cAPS LOCK key (do this only when + // the caps or shift lock key is pressed). Turn off the caps + // lock afterwards. + pFrameWin->SimulateKeyPress( KEY_CAPSLOCK ); + } + + // Capital letter at beginning of paragraph ? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartSentence ) ) + { + FnCapitalStartSentence( rDoc, rTxt, true, nCapLttrPos, nInsPos, eLang ); + } + + // Two capital letters at beginning of word ?? + if( !bUnsupported && + IsAutoCorrFlag( ACFlags::CapitalStartWord ) ) + { + FnCapitalStartWord( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + + if( IsAutoCorrFlag( ACFlags::ChgToEnEmDash ) ) + { + FnChgToEnEmDash( rDoc, rTxt, nCapLttrPos, nInsPos, eLang ); + } + } + + } while( false ); +} + +SvxAutoCorrectLanguageLists& SvxAutoCorrect::GetLanguageList_( + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + if (m_aLangTable.find(aLanguageTag) == m_aLangTable.end()) + (void)CreateLanguageFile(aLanguageTag); + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + return iter->second; +} + +void SvxAutoCorrect::SaveCplSttExceptList( LanguageType eLang ) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + iter->second.SaveCplSttExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +void SvxAutoCorrect::SaveWordStartExceptList(LanguageType eLang) +{ + auto const iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + iter->second.SaveWordStartExceptList(); + else + { + SAL_WARN("editeng", "Save an empty list? "); + } +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddCplSttException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + // either the right language is present or it will be this in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else if(CreateLanguageFile(aLangTagUndetermined)) + { + iter = m_aLangTable.find(aLangTagUndetermined); + assert(iter != m_aLangTable.end()); + pLists = &iter->second; + } + } + OSL_ENSURE(pLists, "No auto correction data"); + return pLists && pLists->AddToCplSttExceptList(rNew); +} + +// Adds a single word. The list will immediately be written to the file! +bool SvxAutoCorrect::AddWordStartException( const OUString& rNew, + LanguageType eLang ) +{ + SvxAutoCorrectLanguageLists* pLists = nullptr; + //either the right language is present or it is set in the general list + auto iter = m_aLangTable.find(LanguageTag(eLang)); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else + { + LanguageTag aLangTagUndetermined( LANGUAGE_UNDETERMINED); + iter = m_aLangTable.find(aLangTagUndetermined); + if (iter != m_aLangTable.end()) + pLists = &iter->second; + else if(CreateLanguageFile(aLangTagUndetermined)) + { + iter = m_aLangTable.find(aLangTagUndetermined); + assert(iter != m_aLangTable.end()); + pLists = &iter->second; + } + } + OSL_ENSURE(pLists, "No auto correction file!"); + return pLists && pLists->AddToWordStartExceptList(rNew); +} + +OUString SvxAutoCorrect::GetPrevAutoCorrWord(SvxAutoCorrDoc const& rDoc, const OUString& rTxt, + sal_Int32 nPos) +{ + OUString sRet; + if( !nPos ) + return sRet; + + sal_Int32 nEnd = nPos; + + // it must be followed by a blank or tab! + if( ( nPos < rTxt.getLength() && + !IsWordDelim( rTxt[ nPos ])) || + IsWordDelim( rTxt[ --nPos ])) + return sRet; + + while( nPos && !IsWordDelim( rTxt[ --nPos ])) + ; + + // Found a Paragraph-start or a Blank, search for the word shortcut in + // auto. + sal_Int32 nCapLttrPos = nPos+1; // on the 1st Character + if( !nPos && !IsWordDelim( rTxt[ 0 ])) + --nCapLttrPos; // Beginning of paragraph and no Blank! + + while( lcl_IsInArr( sImplSttSkipChars, rTxt[ nCapLttrPos ]) ) + if( ++nCapLttrPos >= nEnd ) + return sRet; + + if( 3 > nEnd - nCapLttrPos ) + return sRet; + + const LanguageType eLang = GetDocLanguage( rDoc, nCapLttrPos ); + + CharClass& rCC = GetCharClass(eLang); + + if( lcl_IsSymbolChar( rCC, rTxt, nCapLttrPos, nEnd )) + return sRet; + + sRet = rTxt.copy( nCapLttrPos, nEnd - nCapLttrPos ); + return sRet; +} + +// static +std::vector<OUString> SvxAutoCorrect::GetChunkForAutoText(std::u16string_view rTxt, + const sal_Int32 nPos) +{ + constexpr sal_Int32 nMinLen = 3; + constexpr sal_Int32 nMaxLen = 9; + std::vector<OUString> aRes; + if (nPos >= nMinLen) + { + sal_Int32 nBegin = std::max<sal_Int32>(nPos - nMaxLen, 0); + // TODO: better detect word boundaries (not only whitespaces, but also e.g. punctuation) + if (nBegin > 0 && !IsWordDelim(rTxt[nBegin-1])) + { + while (nBegin + nMinLen <= nPos && !IsWordDelim(rTxt[nBegin])) + ++nBegin; + } + if (nBegin + nMinLen <= nPos) + { + OUString sRes( rTxt.substr(nBegin, nPos - nBegin) ); + aRes.push_back(sRes); + bool bLastStartedWithDelim = IsWordDelim(sRes[0]); + for (sal_Int32 i = 1; i <= sRes.getLength() - nMinLen; ++i) + { + bool bAdd = bLastStartedWithDelim; + bLastStartedWithDelim = IsWordDelim(sRes[i]); + bAdd = bAdd || bLastStartedWithDelim; + if (bAdd) + aRes.push_back(sRes.copy(i)); + } + } + } + return aRes; +} + +bool SvxAutoCorrect::CreateLanguageFile( const LanguageTag& rLanguageTag, bool bNewFile ) +{ + OSL_ENSURE(m_aLangTable.find(rLanguageTag) == m_aLangTable.end(), "Language already exists "); + + OUString sUserDirFile( GetAutoCorrFileName( rLanguageTag, true )); + OUString sShareDirFile( sUserDirFile ); + + SvxAutoCorrectLanguageLists* pLists = nullptr; + + tools::Time nMinTime( 0, 2 ), nAktTime( tools::Time::SYSTEM ), nLastCheckTime( tools::Time::EMPTY ); + + auto nFndPos = aLastFileTable.find(rLanguageTag); + if(nFndPos != aLastFileTable.end() && + (nLastCheckTime.SetTime(nFndPos->second), nLastCheckTime < nAktTime) && + nAktTime - nLastCheckTime < nMinTime) + { + // no need to test the file, because the last check is not older then + // 2 minutes. + if( bNewFile ) + { + sShareDirFile = sUserDirFile; + auto itBool = m_aLangTable.emplace(std::piecewise_construct, + std::forward_as_tuple(rLanguageTag), + std::forward_as_tuple(*this, sShareDirFile, sUserDirFile)); + pLists = &itBool.first->second; + aLastFileTable.erase(nFndPos); + } + } + else if( + ( FStatHelper::IsDocument( sUserDirFile ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag ) ) || + FStatHelper::IsDocument( sShareDirFile = + GetAutoCorrFileName( rLanguageTag, false, false, true) ) + ) || + ( sShareDirFile = sUserDirFile, bNewFile ) + ) + { + auto itBool = m_aLangTable.emplace(std::piecewise_construct, + std::forward_as_tuple(rLanguageTag), + std::forward_as_tuple(*this, sShareDirFile, sUserDirFile)); + pLists = &itBool.first->second; + if (nFndPos != aLastFileTable.end()) + aLastFileTable.erase(nFndPos); + } + else if( !bNewFile ) + { + aLastFileTable[rLanguageTag] = nAktTime.GetTime(); + } + return pLists != nullptr; +} + +bool SvxAutoCorrect::PutText( const OUString& rShort, const OUString& rLong, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + if (auto const iter = m_aLangTable.find(aLanguageTag); iter != m_aLangTable.end()) + return iter->second.PutText(rShort, rLong); + if (CreateLanguageFile(aLanguageTag)) + { + auto const iter = m_aLangTable.find(aLanguageTag); + assert (iter != m_aLangTable.end()); + return iter->second.PutText(rShort, rLong); + } + return false; +} + +void SvxAutoCorrect::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, + std::vector<SvxAutocorrWord>& aDeleteEntries, + LanguageType eLang ) +{ + LanguageTag aLanguageTag( eLang); + auto iter = m_aLangTable.find(aLanguageTag); + if (iter != m_aLangTable.end()) + { + iter->second.MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } + else if(CreateLanguageFile( aLanguageTag )) + { + iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + iter->second.MakeCombinedChanges( aNewEntries, aDeleteEntries ); + } +} + +// - return the replacement text (only for SWG-Format, all other +// can be taken from the word list!) +bool SvxAutoCorrect::GetLongText( const OUString&, OUString& ) +{ + return false; +} + +void SvxAutoCorrect::refreshBlockList( const uno::Reference< embed::XStorage >& ) +{ +} + +// Text with attribution (only the SWG - SWG format!) +bool SvxAutoCorrect::PutText( const css::uno::Reference < css::embed::XStorage >&, + const OUString&, const OUString&, SfxObjectShell&, OUString& ) +{ + return false; +} + +OUString EncryptBlockName_Imp(std::u16string_view rName) +{ + OUStringBuffer aName; + aName.append('#').append(rName); + for (size_t nLen = rName.size(), nPos = 1; nPos < nLen; ++nPos) + { + if (lcl_IsInArr( u"!/:.\\", aName[nPos])) + aName[nPos] &= 0x0f; + } + return aName.makeStringAndClear(); +} + +/* This code is copied from SwXMLTextBlocks::GeneratePackageName */ +static void GeneratePackageName ( std::u16string_view rShort, OUString& rPackageName ) +{ + OString sByte(OUStringToOString(rShort, RTL_TEXTENCODING_UTF7)); + OUStringBuffer aBuf(OStringToOUString(sByte, RTL_TEXTENCODING_ASCII_US)); + + for (sal_Int32 nPos = 0; nPos < aBuf.getLength(); ++nPos) + { + switch (aBuf[nPos]) + { + case '!': + case '/': + case ':': + case '.': + case '\\': + aBuf[nPos] = '_'; + break; + default: + break; + } + } + + rPackageName = aBuf.makeStringAndClear(); +} + +static const SvxAutocorrWord* lcl_SearchWordsInList( + SvxAutoCorrectLanguageLists* pList, std::u16string_view rTxt, + sal_Int32& rStt, sal_Int32 nEndPos) +{ + const SvxAutocorrWordList* pAutoCorrWordList = pList->GetAutocorrWordList(); + return pAutoCorrWordList->SearchWordsInList( rTxt, rStt, nEndPos ); +} + +// the search for the words in the substitution table +const SvxAutocorrWord* SvxAutoCorrect::SearchWordsInList( + std::u16string_view rTxt, sal_Int32& rStt, sal_Int32 nEndPos, + SvxAutoCorrDoc&, LanguageTag& rLang ) +{ + const SvxAutocorrWord* pRet = nullptr; + LanguageTag aLanguageTag( rLang); + if( aLanguageTag.isSystemLocale() ) + aLanguageTag.reset( MsLangId::getConfiguredSystemLanguage()); + + /* TODO-BCP47: this is so ugly, should all maybe be a proper fallback + * list instead? */ + + // First search for eLang, then US-English -> English + // and last in LANGUAGE_UNDETERMINED + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + SvxAutoCorrectLanguageLists & rList = iter->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + else + return nullptr; + } + + // If it still could not be found here, then keep on searching + LanguageType eLang = aLanguageTag.getLanguageType(); + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + SvxAutoCorrectLanguageLists& rList = m_aLangTable.find(aLanguageTag)->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + SvxAutoCorrectLanguageLists& rList = iter->second; + pRet = lcl_SearchWordsInList( &rList, rTxt, rStt, nEndPos ); + if( pRet ) + { + rLang = aLanguageTag; + return pRet; + } + } + return nullptr; +} + +bool SvxAutoCorrect::FindInWordStartExceptList( LanguageType eLang, + const OUString& sWord ) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: again horrible ugliness */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end()); + auto& rList = iter->second; + if(rList.GetWordStartExceptList()->find(sWord) != rList.GetWordStartExceptList()->end() ) + return true; + } + return false; +} + +static bool lcl_FindAbbreviation(const SvStringsISortDtor* pList, const OUString& sWord) +{ + SvStringsISortDtor::const_iterator it = pList->find( "~" ); + SvStringsISortDtor::size_type nPos = it - pList->begin(); + if( nPos < pList->size() ) + { + OUString sLowerWord(sWord.toAsciiLowerCase()); + OUString sAbr; + for( SvStringsISortDtor::size_type n = nPos; n < pList->size(); ++n ) + { + sAbr = (*pList)[ n ]; + if (sAbr[0] != '~') + break; + // ~ and ~. are not allowed! + if( 2 < sAbr.getLength() && sAbr.getLength() - 1 <= sWord.getLength() ) + { + OUString sLowerAbk(sAbr.toAsciiLowerCase()); + for (sal_Int32 i = sLowerAbk.getLength(), ii = sLowerWord.getLength(); i;) + { + if( !--i ) // agrees + return true; + + if( sLowerAbk[i] != sLowerWord[--ii]) + break; + } + } + } + } + OSL_ENSURE( !(nPos && '~' == (*pList)[ --nPos ][ 0 ] ), + "Wrongly sorted exception list?" ); + return false; +} + +bool SvxAutoCorrect::FindInCplSttExceptList(LanguageType eLang, + const OUString& sWord, bool bAbbreviation) +{ + LanguageTag aLanguageTag( eLang); + + /* TODO-BCP47: did I mention terrible horrible ugliness? */ + + // First search for eLang, then primary language of eLang + // and last in LANGUAGE_UNDETERMINED + + if (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + // If it still could not be found here, then keep on searching + // the primary language for example EN + aLanguageTag.reset(aLanguageTag.getLanguage()); + LanguageType nTmpKey = aLanguageTag.getLanguageType(false); + if (nTmpKey != eLang && nTmpKey != LANGUAGE_UNDETERMINED && + (m_aLangTable.find(aLanguageTag) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false))) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + + if (m_aLangTable.find(aLanguageTag.reset(LANGUAGE_UNDETERMINED)) != m_aLangTable.end() || + CreateLanguageFile(aLanguageTag, false)) + { + //the language is available - so bring it on + const auto iter = m_aLangTable.find(aLanguageTag); + assert(iter != m_aLangTable.end() && "CreateLanguageFile can't fail"); + const SvStringsISortDtor* pList = iter->second.GetCplSttExceptList(); + if(bAbbreviation ? lcl_FindAbbreviation(pList, sWord) : pList->find(sWord) != pList->end() ) + return true; + } + return false; +} + +OUString SvxAutoCorrect::GetAutoCorrFileName( const LanguageTag& rLanguageTag, + bool bNewFile, bool bTst, bool bUnlocalized ) const +{ + OUString sRet, sExt( rLanguageTag.getBcp47() ); + if (bUnlocalized) + { + // we don't want variant, so we'll take "fr" instead of "fr-CA" for example + std::vector< OUString > vecFallBackStrings = rLanguageTag.getFallbackStrings(false); + if (!vecFallBackStrings.empty()) + sExt = vecFallBackStrings[0]; + } + + sExt = "_" + sExt + ".dat"; + if( bNewFile ) + sRet = sUserAutoCorrFile + sExt; + else if( !bTst ) + sRet = sShareAutoCorrFile + sExt; + else + { + // test first in the user directory - if not exist, then + sRet = sUserAutoCorrFile + sExt; + if( !FStatHelper::IsDocument( sRet )) + sRet = sShareAutoCorrFile + sExt; + } + return sRet; +} + +SvxAutoCorrectLanguageLists::SvxAutoCorrectLanguageLists( + SvxAutoCorrect& rParent, + OUString aShareAutoCorrectFile, + OUString aUserAutoCorrectFile) +: sShareAutoCorrFile(std::move( aShareAutoCorrectFile )), + sUserAutoCorrFile(std::move( aUserAutoCorrectFile )), + aModifiedDate( Date::EMPTY ), + aModifiedTime( tools::Time::EMPTY ), + aLastCheckTime( tools::Time::EMPTY ), + rAutoCorrect(rParent), + nFlags(ACFlags::NONE) +{ +} + +SvxAutoCorrectLanguageLists::~SvxAutoCorrectLanguageLists() +{ +} + +bool SvxAutoCorrectLanguageLists::IsFileChanged_Imp() +{ + // Access the file system only every 2 minutes to check the date stamp + bool bRet = false; + + tools::Time nMinTime( 0, 2 ); + tools::Time nAktTime( tools::Time::SYSTEM ); + if( aLastCheckTime <= nAktTime) // overflow? + return false; + nAktTime -= aLastCheckTime; + if( nAktTime > nMinTime ) // min time past + { + Date aTstDate( Date::EMPTY ); tools::Time aTstTime( tools::Time::EMPTY ); + if( FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aTstDate, &aTstTime ) && + ( aModifiedDate != aTstDate || aModifiedTime != aTstTime )) + { + bRet = true; + // then remove all the lists fast! + if( (ACFlags::CplSttLstLoad & nFlags) && pCplStt_ExcptLst ) + { + pCplStt_ExcptLst.reset(); + } + if( (ACFlags::WordStartLstLoad & nFlags) && pWordStart_ExcptLst ) + { + pWordStart_ExcptLst.reset(); + } + if( (ACFlags::ChgWordLstLoad & nFlags) && pAutocorr_List ) + { + pAutocorr_List.reset(); + } + nFlags &= ~ACFlags(ACFlags::CplSttLstLoad | ACFlags::WordStartLstLoad | ACFlags::ChgWordLstLoad ); + } + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::LoadXMLExceptList_Imp( + std::unique_ptr<SvStringsISortDtor>& rpLst, + const OUString& sStrmName, + tools::SvRef<SotStorage>& rStg) +{ + if( rpLst ) + rpLst->clear(); + else + rpLst.reset( new SvStringsISortDtor ); + + { + if( rStg.is() && rStg->IsStream( sStrmName ) ) + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::SHARE_DENYWRITE | StreamMode::NOCREATE ) ); + if( ERRCODE_NONE != xStrm->GetError()) + { + xStrm.clear(); + rStg.clear(); + RemoveStream_Imp( sStrmName ); + } + else + { + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = sStrmName; + + xStrm->Seek( 0 ); + xStrm->SetBufferSize( 8 * 1024 ); + aParserInput.aInputStream = new utl::OInputStreamWrapper( *xStrm ); + + // get filter + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLExceptionListImport ( xContext, *rpLst ); + + // connect parser and filter + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create( xContext ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler( xTokenHandler ); + + // parse + try + { + xParser->parseStream( aParserInput ); + } + catch( const xml::sax::SAXParseException& ) + { + // re throw ? + } + catch( const xml::sax::SAXException& ) + { + // re throw ? + } + catch( const io::IOException& ) + { + // re throw ? + } + } + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + } + +} + +void SvxAutoCorrectLanguageLists::SaveExceptList_Imp( + const SvStringsISortDtor& rLst, + const OUString& sStrmName, + tools::SvRef<SotStorage> const &rStg, + bool bConvert ) +{ + if( !rStg.is() ) + return; + + if( rLst.empty() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + else + { + tools::SvRef<SotStorageStream> xStrm = rStg->OpenSotStream( sStrmName, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( xStrm.is() ) + { + xStrm->SetSize( 0 ); + xStrm->SetBufferSize( 8192 ); + xStrm->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *xStrm ); + xWriter->setOutputStream(xOut); + + uno::Reference < xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); + rtl::Reference< SvXMLExceptionListExport > xExp( new SvXMLExceptionListExport( xContext, rLst, sStrmName, xHandler ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + xStrm->Commit(); + if( xStrm->GetError() == ERRCODE_NONE ) + { + xStrm.clear(); + if (!bConvert) + { + rStg->Commit(); + if( ERRCODE_NONE != rStg->GetError() ) + { + rStg->Remove( sStrmName ); + rStg->Commit(); + } + } + } + } + } +} + +SvxAutocorrWordList* SvxAutoCorrectLanguageLists::LoadAutocorrWordList() +{ + if( pAutocorr_List ) + pAutocorr_List->DeleteAndDestroyAll(); + else + pAutocorr_List.reset( new SvxAutocorrWordList() ); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sShareAutoCorrFile, embed::ElementModes::READ ); + uno::Reference < io::XStream > xStrm = xStg->openStreamElement( pXMLImplAutocorr_ListStr, embed::ElementModes::READ ); + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + + xml::sax::InputSource aParserInput; + aParserInput.sSystemId = pXMLImplAutocorr_ListStr; + aParserInput.aInputStream = xStrm->getInputStream(); + + // get parser + uno::Reference< xml::sax::XFastParser > xParser = xml::sax::FastParser::create(xContext); + SAL_INFO("editeng", "AutoCorrect Import" ); + uno::Reference< xml::sax::XFastDocumentHandler > xFilter = new SvXMLAutoCorrectImport( xContext, pAutocorr_List.get(), rAutoCorrect, xStg ); + uno::Reference<xml::sax::XFastTokenHandler> xTokenHandler = new SvXMLAutoCorrectTokenHandler; + + // connect parser and filter + xParser->setFastDocumentHandler( xFilter ); + xParser->registerNamespace( "http://openoffice.org/2001/block-list", SvXMLAutoCorrectToken::NAMESPACE ); + xParser->setTokenHandler(xTokenHandler); + + // parse + xParser->parseStream( aParserInput ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("editeng", "when loading " << sShareAutoCorrFile); + } + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sShareAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + + return pAutocorr_List.get(); +} + +const SvxAutocorrWordList* SvxAutoCorrectLanguageLists::GetAutocorrWordList() +{ + if( !( ACFlags::ChgWordLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadAutocorrWordList(); + if( !pAutocorr_List ) + { + OSL_ENSURE( false, "No valid list" ); + pAutocorr_List.reset( new SvxAutocorrWordList() ); + } + nFlags |= ACFlags::ChgWordLstLoad; + } + return pAutocorr_List.get(); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetCplSttExceptList() +{ + if( !( ACFlags::CplSttLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadCplSttExceptList(); + if( !pCplStt_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pCplStt_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::CplSttLstLoad; + } + return pCplStt_ExcptLst.get(); +} + +bool SvxAutoCorrectLanguageLists::AddToCplSttExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetCplSttExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::AddToWordStartExceptList(const OUString& rNew) +{ + bool bRet = false; + if( !rNew.isEmpty() && GetWordStartExceptList()->insert( rNew ).second ) + { + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); + bRet = true; + } + return bRet; +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadCplSttExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException&) + { + } + return pCplStt_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveCplSttExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pCplStt_ExcptLst, pXMLImplCplStt_ExcptLstStr, xStg ); + + xStg = nullptr; + + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::LoadWordStartExceptList() +{ + try + { + tools::SvRef<SotStorage> xStg = new SotStorage( sShareAutoCorrFile, StreamMode::READ | StreamMode::SHARE_DENYNONE ); + if( xStg.is() && xStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + } + catch (const css::ucb::ContentCreationException &) + { + TOOLS_WARN_EXCEPTION("editeng", "SvxAutoCorrectLanguageLists::LoadWordStartExceptList"); + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::SaveWordStartExceptList() +{ + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + SaveExceptList_Imp( *pWordStart_ExcptLst, pXMLImplWordStart_ExcptLstStr, xStg ); + + xStg = nullptr; + // Set time stamp + FStatHelper::GetModifiedDateTimeOfFile( sUserAutoCorrFile, + &aModifiedDate, &aModifiedTime ); + aLastCheckTime = tools::Time( tools::Time::SYSTEM ); +} + +SvStringsISortDtor* SvxAutoCorrectLanguageLists::GetWordStartExceptList() +{ + if( !( ACFlags::WordStartLstLoad & nFlags ) || IsFileChanged_Imp() ) + { + LoadWordStartExceptList(); + if( !pWordStart_ExcptLst ) + { + OSL_ENSURE( false, "No valid list" ); + pWordStart_ExcptLst.reset( new SvStringsISortDtor ); + } + nFlags |= ACFlags::WordStartLstLoad; + } + return pWordStart_ExcptLst.get(); +} + +void SvxAutoCorrectLanguageLists::RemoveStream_Imp( const OUString& rName ) +{ + if( sShareAutoCorrFile != sUserAutoCorrFile ) + { + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + if( xStg.is() && ERRCODE_NONE == xStg->GetError() && + xStg->IsStream( rName ) ) + { + xStg->Remove( rName ); + xStg->Commit(); + + xStg = nullptr; + } + } +} + +void SvxAutoCorrectLanguageLists::MakeUserStorage_Impl() +{ + // The conversion needs to happen if the file is already in the user + // directory and is in the old format. Additionally it needs to + // happen when the file is being copied from share to user. + + bool bError = false, bConvert = false, bCopy = false; + INetURLObject aDest; + INetURLObject aSource; + + if (sUserAutoCorrFile != sShareAutoCorrFile ) + { + aSource = INetURLObject ( sShareAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + if ( SotStorage::IsOLEStorage ( sShareAutoCorrFile ) ) + { + aDest.SetExtension ( u"bak" ); + bConvert = true; + } + bCopy = true; + } + else if ( SotStorage::IsOLEStorage ( sUserAutoCorrFile ) ) + { + aSource = INetURLObject ( sUserAutoCorrFile ); + aDest = INetURLObject ( sUserAutoCorrFile ); + aDest.SetExtension ( u"bak" ); + bCopy = bConvert = true; + } + if (bCopy) + { + try + { + OUString sMain(aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri )); + sal_Int32 nSlashPos = sMain.lastIndexOf('/'); + sMain = sMain.copy(0, nSlashPos); + ::ucbhelper::Content aNewContent( sMain, uno::Reference< XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + TransferInfo aInfo; + aInfo.NameClash = NameClash::OVERWRITE; + aInfo.NewTitle = aDest.GetLastName(); + aInfo.SourceURL = aSource.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ); + aInfo.MoveData = false; + aNewContent.executeCommand( "transfer", Any(aInfo)); + } + catch (...) + { + bError = true; + } + } + if (bConvert && !bError) + { + tools::SvRef<SotStorage> xSrcStg = new SotStorage( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), StreamMode::READ ); + tools::SvRef<SotStorage> xDstStg = new SotStorage( sUserAutoCorrFile, StreamMode::WRITE ); + + if( xSrcStg.is() && xDstStg.is() ) + { + std::unique_ptr<SvStringsISortDtor> pTmpWordList; + + if (xSrcStg->IsContained( pXMLImplWordStart_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplWordStart_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplWordStart_ExcptLstStr, xDstStg, true ); + pTmpWordList.reset(); + } + + + if (xSrcStg->IsContained( pXMLImplCplStt_ExcptLstStr ) ) + LoadXMLExceptList_Imp( pTmpWordList, pXMLImplCplStt_ExcptLstStr, xSrcStg ); + + if (pTmpWordList) + { + SaveExceptList_Imp( *pTmpWordList, pXMLImplCplStt_ExcptLstStr, xDstStg, true ); + pTmpWordList->clear(); + } + + GetAutocorrWordList(); + MakeBlocklist_Imp( *xDstStg ); + sShareAutoCorrFile = sUserAutoCorrFile; + xDstStg = nullptr; + try + { + ::ucbhelper::Content aContent ( aDest.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ), uno::Reference < XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + aContent.executeCommand ( "delete", Any ( true ) ); + } + catch (...) + { + } + } + } + else if( bCopy && !bError ) + sShareAutoCorrFile = sUserAutoCorrFile; +} + +bool SvxAutoCorrectLanguageLists::MakeBlocklist_Imp( SotStorage& rStg ) +{ + bool bRet = true, bRemove = !pAutocorr_List || pAutocorr_List->empty(); + if( !bRemove ) + { + tools::SvRef<SotStorageStream> refList = rStg.OpenSotStream( pXMLImplAutocorr_ListStr, + ( StreamMode::READ | StreamMode::WRITE | StreamMode::SHARE_DENYWRITE ) ); + if( refList.is() ) + { + refList->SetSize( 0 ); + refList->SetBufferSize( 8192 ); + refList->SetProperty( "MediaType", Any(OUString( "text/xml" )) ); + + uno::Reference< uno::XComponentContext > xContext = + comphelper::getProcessComponentContext(); + + uno::Reference < xml::sax::XWriter > xWriter = xml::sax::Writer::create(xContext); + uno::Reference < io::XOutputStream> xOut = new utl::OOutputStreamWrapper( *refList ); + xWriter->setOutputStream(xOut); + + rtl::Reference< SvXMLAutoCorrectExport > xExp( new SvXMLAutoCorrectExport( xContext, pAutocorr_List.get(), pXMLImplAutocorr_ListStr, xWriter ) ); + + xExp->exportDoc( XML_BLOCK_LIST ); + + refList->Commit(); + bRet = ERRCODE_NONE == refList->GetError(); + if( bRet ) + { + refList.clear(); + rStg.Commit(); + if( ERRCODE_NONE != rStg.GetError() ) + { + bRemove = true; + bRet = false; + } + } + } + else + bRet = false; + } + + if( bRemove ) + { + rStg.Remove( pXMLImplAutocorr_ListStr ); + rStg.Commit(); + } + + return bRet; +} + +bool SvxAutoCorrectLanguageLists::MakeCombinedChanges( std::vector<SvxAutocorrWord>& aNewEntries, std::vector<SvxAutocorrWord>& aDeleteEntries ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStorage = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStorage.is() && ERRCODE_NONE == xStorage->GetError(); + + if( bRet ) + { + for (SvxAutocorrWord & aWordToDelete : aDeleteEntries) + { + std::optional<SvxAutocorrWord> xFoundEntry = pAutocorr_List->FindAndRemove( &aWordToDelete ); + if( xFoundEntry ) + { + if( !xFoundEntry->IsTextOnly() ) + { + OUString aName( aWordToDelete.GetShort() ); + if (xStorage->IsOLEStorage()) + aName = EncryptBlockName_Imp(aName); + else + GeneratePackageName ( aWordToDelete.GetShort(), aName ); + + if( xStorage->IsContained( aName ) ) + { + xStorage->Remove( aName ); + bRet = xStorage->Commit(); + } + } + } + } + + for (const SvxAutocorrWord & aNewEntrie : aNewEntries) + { + SvxAutocorrWord aWordToAdd(aNewEntrie.GetShort(), aNewEntrie.GetLong(), true ); + std::optional<SvxAutocorrWord> xRemoved = pAutocorr_List->FindAndRemove( &aWordToAdd ); + if( xRemoved ) + { + if( !xRemoved->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStorageName( aWordToAdd.GetShort() ); + if (xStorage->IsOLEStorage()) + sStorageName = EncryptBlockName_Imp(sStorageName); + else + GeneratePackageName ( aWordToAdd.GetShort(), sStorageName); + + if( xStorage->IsContained( sStorageName ) ) + xStorage->Remove( sStorageName ); + } + } + bRet = pAutocorr_List->Insert( std::move(aWordToAdd) ); + + if ( !bRet ) + { + break; + } + } + + if ( bRet ) + { + bRet = MakeBlocklist_Imp( *xStorage ); + } + } + return bRet; +} + +bool SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, const OUString& rLong ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + tools::SvRef<SotStorage> xStg = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + + bool bRet = xStg.is() && ERRCODE_NONE == xStg->GetError(); + + // Update the word list + if( bRet ) + { + SvxAutocorrWord aNew(rShort, rLong, true ); + std::optional<SvxAutocorrWord> xRemove = pAutocorr_List->FindAndRemove( &aNew ); + if( xRemove ) + { + if( !xRemove->IsTextOnly() ) + { + // Still have to remove the Storage + OUString sStgNm( rShort ); + if (xStg->IsOLEStorage()) + sStgNm = EncryptBlockName_Imp(sStgNm); + else + GeneratePackageName ( rShort, sStgNm); + + if( xStg->IsContained( sStgNm ) ) + xStg->Remove( sStgNm ); + } + } + + if( pAutocorr_List->Insert( std::move(aNew) ) ) + { + bRet = MakeBlocklist_Imp( *xStg ); + xStg = nullptr; + } + else + { + bRet = false; + } + } + return bRet; +} + +void SvxAutoCorrectLanguageLists::PutText( const OUString& rShort, + SfxObjectShell& rShell ) +{ + // First get the current list! + GetAutocorrWordList(); + + MakeUserStorage_Impl(); + + try + { + uno::Reference < embed::XStorage > xStg = comphelper::OStorageHelper::GetStorageFromURL( sUserAutoCorrFile, embed::ElementModes::READWRITE ); + OUString sLong; + bool bRet = rAutoCorrect.PutText( xStg, sUserAutoCorrFile, rShort, rShell, sLong ); + xStg = nullptr; + + // Update the word list + if( bRet ) + { + if( pAutocorr_List->Insert( SvxAutocorrWord(rShort, sLong, false) ) ) + { + tools::SvRef<SotStorage> xStor = new SotStorage( sUserAutoCorrFile, StreamMode::READWRITE ); + MakeBlocklist_Imp( *xStor ); + } + } + } + catch ( const uno::Exception& ) + { + } +} + +// Keep the list sorted ... +struct SvxAutocorrWordList::CompareSvxAutocorrWordList +{ + bool operator()( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) const + { + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + return rCmp.compareString( lhs.GetShort(), rhs.GetShort() ) < 0; + } +}; + +namespace { + +typedef std::unordered_map<OUString, SvxAutocorrWord> AutocorrWordHashType; + +} + +struct SvxAutocorrWordList::Impl +{ + + // only one of these contains the data + // maSortedVector is manually sorted so we can optimise data movement + mutable AutocorrWordSetType maSortedVector; + mutable AutocorrWordHashType maHash; // key is 'Short' + + void DeleteAndDestroyAll() + { + maHash.clear(); + maSortedVector.clear(); + } +}; + +SvxAutocorrWordList::SvxAutocorrWordList() : mpImpl(new Impl) {} + +SvxAutocorrWordList::~SvxAutocorrWordList() +{ +} + +void SvxAutocorrWordList::DeleteAndDestroyAll() +{ + mpImpl->DeleteAndDestroyAll(); +} + +// returns true if inserted +const SvxAutocorrWord* SvxAutocorrWordList::Insert(SvxAutocorrWord aWord) const +{ + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + OUString aShort = aWord.GetShort(); + auto [it,inserted] = mpImpl->maHash.emplace( std::move(aShort), std::move(aWord) ); + if (inserted) + return &(it->second); + return nullptr; + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), aWord, CompareSvxAutocorrWordList()); + CollatorWrapper& rCmp = ::GetCollatorWrapper(); + if (it == mpImpl->maSortedVector.end() || rCmp.compareString( aWord.GetShort(), it->GetShort() ) != 0) + { + it = mpImpl->maSortedVector.insert(it, std::move(aWord)); + return &*it; + } + return nullptr; + } +} + +void SvxAutocorrWordList::LoadEntry(const OUString& sWrong, const OUString& sRight, bool bOnlyTxt) +{ + (void)Insert(SvxAutocorrWord( sWrong, sRight, bOnlyTxt )); +} + +bool SvxAutocorrWordList::empty() const +{ + return mpImpl->maHash.empty() && mpImpl->maSortedVector.empty(); +} + +std::optional<SvxAutocorrWord> SvxAutocorrWordList::FindAndRemove(const SvxAutocorrWord *pWord) +{ + + if ( mpImpl->maSortedVector.empty() ) // use the hash + { + AutocorrWordHashType::iterator it = mpImpl->maHash.find( pWord->GetShort() ); + if( it != mpImpl->maHash.end() ) + { + SvxAutocorrWord pMatch = std::move(it->second); + mpImpl->maHash.erase (it); + return pMatch; + } + } + else + { + auto it = std::lower_bound(mpImpl->maSortedVector.begin(), mpImpl->maSortedVector.end(), *pWord, CompareSvxAutocorrWordList()); + if (it != mpImpl->maSortedVector.end() && !CompareSvxAutocorrWordList()(*pWord, *it)) + { + SvxAutocorrWord pMatch = std::move(*it); + mpImpl->maSortedVector.erase (it); + return pMatch; + } + } + return std::optional<SvxAutocorrWord>(); +} + +// return the sorted contents - defer sorting until we have to. +const SvxAutocorrWordList::AutocorrWordSetType& SvxAutocorrWordList::getSortedContent() const +{ + // convert from hash to set permanently + if ( mpImpl->maSortedVector.empty() ) + { + std::vector<SvxAutocorrWord> tmp; + tmp.reserve(mpImpl->maHash.size()); + for (auto & rPair : mpImpl->maHash) + tmp.emplace_back(std::move(rPair.second)); + mpImpl->maHash.clear(); + // sort twice - this gets the list into mostly-sorted order, which + // reduces the number of times we need to invoke the expensive ICU collate fn. + std::sort(tmp.begin(), tmp.end(), + [] ( SvxAutocorrWord const & lhs, SvxAutocorrWord const & rhs ) + { + return lhs.GetShort() < rhs.GetShort(); + }); + // This beast has some O(N log(N)) in a terribly slow ICU collate fn. + // stable_sort is twice as fast as sort in this situation because it does + // fewer comparison operations. + std::stable_sort(tmp.begin(), tmp.end(), CompareSvxAutocorrWordList()); + mpImpl->maSortedVector = std::move(tmp); + } + return mpImpl->maSortedVector; +} + +const SvxAutocorrWord* SvxAutocorrWordList::WordMatches(const SvxAutocorrWord *pFnd, + std::u16string_view rTxt, + sal_Int32 &rStt, + sal_Int32 nEndPos) const +{ + const OUString& rChk = pFnd->GetShort(); + + sal_Int32 left_wildcard = rChk.startsWith( ".*" ) ? 2 : 0; // ".*word" pattern? + sal_Int32 right_wildcard = rChk.endsWith( ".*" ) ? 2 : 0; // "word.*" pattern? + assert(nEndPos >= 0); + size_t nSttWdPos = nEndPos; + + // direct replacement of keywords surrounded by colons (for example, ":name:") + bool bColonNameColon = static_cast<sal_Int32>(rTxt.size()) > nEndPos && + rTxt[nEndPos] == ':' && rChk[0] == ':' && rChk.endsWith(":"); + if ( nEndPos + (bColonNameColon ? 1 : 0) < rChk.getLength() - left_wildcard - right_wildcard ) + return nullptr; + + bool bWasWordDelim = false; + sal_Int32 nCalcStt = nEndPos - rChk.getLength() + left_wildcard; + if (bColonNameColon) + nCalcStt++; + if( !right_wildcard && ( !nCalcStt || nCalcStt == rStt || left_wildcard || bColonNameColon || + ( nCalcStt < rStt && + IsWordDelim( rTxt[ nCalcStt - 1 ] ))) ) + { + TransliterationWrapper& rCmp = GetIgnoreTranslWrapper(); + OUString sWord( rTxt.substr(nCalcStt, rChk.getLength() - left_wildcard) ); + if( (!left_wildcard && rCmp.isEqual( rChk, sWord )) || (left_wildcard && rCmp.isEqual( rChk.copy(left_wildcard), sWord) )) + { + rStt = nCalcStt; + if (!left_wildcard) + { + // fdo#33899 avoid "1/2", "1/3".. to be replaced by fractions in dates, eg. 1/2/14 + if (static_cast<sal_Int32>(rTxt.size()) > nEndPos && rTxt[nEndPos] == '/' && rChk.indexOf('/') != -1) + return nullptr; + return pFnd; + } + // get the first word delimiter position before the matching ".*word" pattern + while( rStt && !(bWasWordDelim = IsWordDelim( rTxt[ --rStt ]))) + ; + if (bWasWordDelim) rStt++; + OUString left_pattern( rTxt.substr(rStt, nEndPos - rStt - rChk.getLength() + left_wildcard) ); + // avoid double spaces before simple "word" replacement + left_pattern += (left_pattern.getLength() == 0 && pFnd->GetLong()[0] == 0x20) ? pFnd->GetLong().subView(1) : pFnd->GetLong(); + if( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(OUString(rTxt.substr(rStt, nEndPos - rStt)), left_pattern) ) ) + return pNew; + } + } else + // match "word.*" or ".*word.*" patterns, eg. "i18n.*", ".*---.*", TODO: add transliteration support + if ( right_wildcard ) + { + + OUString sTmp( rChk.copy( left_wildcard, rChk.getLength() - left_wildcard - right_wildcard ) ); + // Get the last word delimiter position + bool not_suffix; + + while( nSttWdPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nSttWdPos ]))) + ; + // search the first occurrence (with a left word delimitation, if needed) + size_t nFndPos = std::u16string_view::npos; + do { + nFndPos = rTxt.find( sTmp, nFndPos + 1); + if (nFndPos == std::u16string_view::npos) + break; + not_suffix = bWasWordDelim && (nSttWdPos >= (nFndPos + sTmp.getLength())); + } while ( (!left_wildcard && nFndPos && !IsWordDelim( rTxt[ nFndPos - 1 ])) || not_suffix ); + + if ( nFndPos != std::u16string_view::npos ) + { + sal_Int32 extra_repl = static_cast<sal_Int32>(nFndPos) + sTmp.getLength() > nEndPos ? 1: 0; // for patterns with terminating characters, eg. "a:" + + if ( left_wildcard ) + { + // get the first word delimiter position before the matching ".*word.*" pattern + while( nFndPos && !(bWasWordDelim = IsWordDelim( rTxt[ --nFndPos ]))) + ; + if (bWasWordDelim) nFndPos++; + } + if (nEndPos + extra_repl <= static_cast<sal_Int32>(nFndPos)) + { + return nullptr; + } + // store matching pattern and its replacement as a new list item, eg. "i18ns" -> "internationalizations" + OUString aShort( rTxt.substr(nFndPos, nEndPos - nFndPos + extra_repl) ); + + OUString aLong; + rStt = nFndPos; + if ( !left_wildcard ) + { + sal_Int32 siz = nEndPos - nFndPos - sTmp.getLength(); + aLong = pFnd->GetLong() + (siz > 0 ? rTxt.substr(nFndPos + sTmp.getLength(), siz) : u""); + } else { + OUStringBuffer buf; + do { + nSttWdPos = rTxt.find( sTmp, nFndPos); + if (nSttWdPos != std::u16string_view::npos) + { + sal_Int32 nTmp(nFndPos); + while (nTmp < static_cast<sal_Int32>(nSttWdPos) && !IsWordDelim(rTxt[nTmp])) + nTmp++; + if (nTmp < static_cast<sal_Int32>(nSttWdPos)) + break; // word delimiter found + buf.append(rTxt.substr(nFndPos, nSttWdPos - nFndPos)).append(pFnd->GetLong()); + nFndPos = nSttWdPos + sTmp.getLength(); + } + } while (nSttWdPos != std::u16string_view::npos); + if (static_cast<sal_Int32>(nEndPos - nFndPos) > extra_repl) + buf.append(rTxt.substr(nFndPos, nEndPos - nFndPos)); + aLong = buf.makeStringAndClear(); + } + if ( const SvxAutocorrWord* pNew = Insert( SvxAutocorrWord(aShort, aLong) ) ) + { + if ( (static_cast<sal_Int32>(rTxt.size()) > nEndPos && IsWordDelim(rTxt[nEndPos])) || static_cast<sal_Int32>(rTxt.size()) == nEndPos ) + return pNew; + } + } + } + return nullptr; +} + +const SvxAutocorrWord* SvxAutocorrWordList::SearchWordsInList(std::u16string_view rTxt, sal_Int32& rStt, + sal_Int32 nEndPos) const +{ + for (auto const& elem : mpImpl->maHash) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem.second, rTxt, rStt, nEndPos ) ) + return pTmp; + } + + for (auto const& elem : mpImpl->maSortedVector) + { + if( const SvxAutocorrWord *pTmp = WordMatches( &elem, rTxt, rStt, nEndPos ) ) + return pTmp; + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/swafopt.cxx b/editeng/source/misc/swafopt.cxx new file mode 100644 index 0000000000..25f3b1b466 --- /dev/null +++ b/editeng/source/misc/swafopt.cxx @@ -0,0 +1,81 @@ +/* -*- 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 <editeng/swafopt.hxx> +#include <tools/gen.hxx> +#include <vcl/keycodes.hxx> + +SvxSwAutoFormatFlags::SvxSwAutoFormatFlags() + : aBulletFont( "OpenSymbol", Size( 0, 14 ) ) +{ + bAutoCorrect = + bCapitalStartSentence = + bCapitalStartWord = + bChgEnumNum = + bAddNonBrkSpace = + bChgOrdinalNumber = + bTransliterateRTL = + bChgAngleQuotes = + bChgToEnEmDash = + bChgWeightUnderl = + bSetINetAttr = + bSetDOIAttr = + bAFormatDelSpacesAtSttEnd = + bAFormatDelSpacesBetweenLines = + bAFormatByInpDelSpacesAtSttEnd = + bAFormatByInpDelSpacesBetweenLines = true; + + bChgUserColl = + bReplaceStyles = + bDelEmptyNode = + bWithRedlining = + bAutoCmpltEndless = + bSetNumRuleAfterSpace = + bAutoCmpltAppendBlank = false; + + bAutoCmpltShowAsTip = + bSetBorder = + bCreateTable = + bSetNumRule = + bAFormatByInput = + bRightMargin = + bAutoCompleteWords = + bAutoCmpltCollectWords = + bAutoCmpltKeepList = true; + + nRightMargin = 50; // default 50% + nAutoCmpltExpandKey = KEY_RETURN; + + aBulletFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + aBulletFont.SetFamily( FAMILY_DONTKNOW ); + aBulletFont.SetPitch( PITCH_DONTKNOW ); + aBulletFont.SetWeight( WEIGHT_DONTKNOW ); + aBulletFont.SetTransparent( true ); + + cBullet = 0x2022; + cByInputBullet = cBullet; + aByInputBulletFont = aBulletFont; + + nAutoCmpltWordLen = 8; + nAutoCmpltListLen = 1000; + m_pAutoCompleteList = nullptr; + pSmartTagMgr = nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/tokens.txt b/editeng/source/misc/tokens.txt new file mode 100644 index 0000000000..0b5a64607b --- /dev/null +++ b/editeng/source/misc/tokens.txt @@ -0,0 +1,7 @@ +abbreviated-name +block +block-list +list-name +name +package-name +unformatted-text diff --git a/editeng/source/misc/txtrange.cxx b/editeng/source/misc/txtrange.cxx new file mode 100644 index 0000000000..2f02a1150f --- /dev/null +++ b/editeng/source/misc/txtrange.cxx @@ -0,0 +1,667 @@ +/* -*- 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 <editeng/txtrange.hxx> +#include <math.h> +#include <tools/poly.hxx> +#include <tools/debug.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <vector> + +TextRanger::TextRanger( const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DPolyPolygon* pLinePolyPolygon, + sal_uInt16 nCacheSz, sal_uInt16 nLft, sal_uInt16 nRght, + bool bSimpl, bool bInnr, bool bVert ) : + maPolyPolygon( rPolyPolygon.count() ), + nCacheSize( nCacheSz ), + nRight( nRght ), + nLeft( nLft ), + nUpper( 0 ), + nLower( 0 ), + nPointCount( 0 ), + bSimple( bSimpl ), + bInner( bInnr ), + bVertical( bVert ) +{ + sal_uInt32 nCount(rPolyPolygon.count()); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(rPolyPolygon.getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + maPolyPolygon.Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + + if( pLinePolyPolygon ) + { + nCount = pLinePolyPolygon->count(); + mpLinePolyPolygon = tools::PolyPolygon(nCount); + + for(sal_uInt32 i(0); i < nCount; i++) + { + const basegfx::B2DPolygon aCandidate(pLinePolyPolygon->getB2DPolygon(i).getDefaultAdaptiveSubdivision()); + nPointCount += aCandidate.count(); + mpLinePolyPolygon->Insert( tools::Polygon(aCandidate), static_cast<sal_uInt16>(i) ); + } + } + else + mpLinePolyPolygon.reset(); +} + + +TextRanger::~TextRanger() +{ + mRangeCache.clear(); +} + +/* TextRanger::SetVertical(..) + If there's is a change in the writing direction, + the cache has to be cleared. +*/ +void TextRanger::SetVertical( bool bNew ) +{ + if( IsVertical() != bNew ) + { + bVertical = bNew; + mRangeCache.clear(); + } +} + +namespace { + +//! SvxBoundArgs is used to perform temporary calculations on a range array. +//! Temporary instances are created in TextRanger::GetTextRanges() +class SvxBoundArgs +{ + std::vector<bool> aBoolArr; + std::deque<tools::Long>* pLongArr; + TextRanger *pTextRanger; + tools::Long nMin; + tools::Long nMax; + tools::Long nTop; + tools::Long nBottom; + tools::Long nUpDiff; + tools::Long nLowDiff; + tools::Long nUpper; + tools::Long nLower; + tools::Long nStart; + tools::Long nEnd; + sal_uInt16 nCut; + sal_uInt16 nLast; + sal_uInt16 nNext; + sal_uInt8 nAct; + sal_uInt8 nFirst; + bool bClosed : 1; + bool bInner : 1; + bool bMultiple : 1; + bool bConcat : 1; + bool bRotate : 1; + void NoteRange( bool bToggle ); + tools::Long Cut( tools::Long nY, const Point& rPt1, const Point& rPt2 ); + void Add(); + void NoteFarPoint_( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ); + void NoteFarPoint( tools::Long nPx, tools::Long nPyDiff, tools::Long nDiff ) + { if( nDiff ) NoteFarPoint_( nPx, nPyDiff, nDiff ); } + tools::Long CalcMax( const Point& rPt1, const Point& rPt2, tools::Long nRange, tools::Long nFar ); + void CheckCut( const Point& rLst, const Point& rNxt ); + tools::Long A( const Point& rP ) const { return bRotate ? rP.Y() : rP.X(); } + tools::Long B( const Point& rP ) const { return bRotate ? rP.X() : rP.Y(); } +public: + SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, const Range& rRange ); + void NotePoint( const tools::Long nA ) { NoteMargin( nA - nStart, nA + nEnd ); } + void NoteMargin( const tools::Long nL, const tools::Long nR ) + { if( nMin > nL ) nMin = nL; if( nMax < nR ) nMax = nR; } + sal_uInt16 Area( const Point& rPt ); + void NoteUpLow( tools::Long nA, const sal_uInt8 nArea ); + void Calc( const tools::PolyPolygon& rPoly ); + void Concat( const tools::PolyPolygon* pPoly ); + // inlines + void NoteLast() { if( bMultiple ) NoteRange( nAct == nFirst ); } + void SetConcat( const bool bNew ){ bConcat = bNew; } + bool IsConcat() const { return bConcat; } +}; + +} + +SvxBoundArgs::SvxBoundArgs( TextRanger* pRanger, std::deque<tools::Long>* pLong, + const Range& rRange ) + : pLongArr(pLong) + , pTextRanger(pRanger) + , nMin(0) + , nMax(0) + , nTop(rRange.Min()) + , nBottom(rRange.Max()) + , nCut(0) + , nLast(0) + , nNext(0) + , nAct(0) + , nFirst(0) + , bClosed(false) + , bInner(pRanger->IsInner()) + , bMultiple(bInner || !pRanger->IsSimple()) + , bConcat(false) + , bRotate(pRanger->IsVertical()) +{ + if( bRotate ) + { + nStart = pRanger->GetUpper(); + nEnd = pRanger->GetLower(); + nLowDiff = pRanger->GetLeft(); + nUpDiff = pRanger->GetRight(); + } + else + { + nStart = pRanger->GetLeft(); + nEnd = pRanger->GetRight(); + nLowDiff = pRanger->GetUpper(); + nUpDiff = pRanger->GetLower(); + } + nUpper = nTop - nUpDiff; + nLower = nBottom + nLowDiff; + pLongArr->clear(); +} + +tools::Long SvxBoundArgs::CalcMax( const Point& rPt1, const Point& rPt2, + tools::Long nRange, tools::Long nFarRange ) +{ + double nDa = Cut( nRange, rPt1, rPt2 ) - Cut( nFarRange, rPt1, rPt2 ); + double nB; + if( nDa < 0 ) + { + nDa = -nDa; + nB = nEnd; + } + else + nB = nStart; + + nB = std::hypot(nB, nDa); + + if (nB == 0) // avoid div / 0 + return 0; + + nB = nRange + nDa * ( nFarRange - nRange ) / nB; + + bool bNote; + if( nB < B(rPt2) ) + bNote = nB > B(rPt1); + else + bNote = nB < B(rPt1); + if( bNote ) + return( tools::Long( nB ) ); + return 0; +} + +void SvxBoundArgs::CheckCut( const Point& rLst, const Point& rNxt ) +{ + if( nCut & 1 ) + NotePoint( Cut( nBottom, rLst, rNxt ) ); + if( nCut & 2 ) + NotePoint( Cut( nTop, rLst, rNxt ) ); + if( rLst.X() == rNxt.X() || rLst.Y() == rNxt.Y() ) + return; + + tools::Long nYps; + if( nLowDiff && ( ( nCut & 1 ) || nLast == 1 || nNext == 1 ) ) + { + nYps = CalcMax( rLst, rNxt, nBottom, nLower ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nLower-nYps, nLowDiff ); + } + if( nUpDiff && ( ( nCut & 2 ) || nLast == 2 || nNext == 2 ) ) + { + nYps = CalcMax( rLst, rNxt, nTop, nUpper ); + if( nYps ) + NoteFarPoint_( Cut( nYps, rLst, rNxt ), nYps-nUpper, nUpDiff ); + } +} + +void SvxBoundArgs::NoteFarPoint_( tools::Long nPa, tools::Long nPbDiff, tools::Long nDiff ) +{ + tools::Long nTmpA; + double nQuot = 2 * nDiff - nPbDiff; + nQuot *= nPbDiff; + nQuot = sqrt( nQuot ); + nQuot /= nDiff; + nTmpA = nPa - tools::Long( nStart * nQuot ); + nPbDiff = nPa + tools::Long( nEnd * nQuot ); + NoteMargin( nTmpA, nPbDiff ); +} + +void SvxBoundArgs::NoteRange( bool bToggle ) +{ + DBG_ASSERT( nMax >= nMin || bInner, "NoteRange: Min > Max?"); + if( nMax < nMin ) + return; + if( !bClosed ) + bToggle = false; + sal_uInt16 nIdx = 0; + sal_uInt16 nCount = pLongArr->size(); + DBG_ASSERT( nCount == 2 * aBoolArr.size(), "NoteRange: Incompatible Sizes" ); + while( nIdx < nCount && (*pLongArr)[ nIdx ] < nMin ) + ++nIdx; + bool bOdd = (nIdx % 2) != 0; + // No overlap with existing intervals? + if( nIdx == nCount || ( !bOdd && nMax < (*pLongArr)[ nIdx ] ) ) + { // Then a new one is inserted ... + pLongArr->insert( pLongArr->begin() + nIdx, nMin ); + pLongArr->insert( pLongArr->begin() + nIdx + 1, nMax ); + aBoolArr.insert( aBoolArr.begin() + (nIdx/2), bToggle ); + } + else + { // expand an existing interval ... + sal_uInt16 nMaxIdx = nIdx; + // If we end up on a left interval boundary, it must be reduced to nMin. + if( bOdd ) + --nIdx; + else + (*pLongArr)[ nIdx ] = nMin; + while( nMaxIdx < nCount && (*pLongArr)[ nMaxIdx ] < nMax ) + ++nMaxIdx; + DBG_ASSERT( nMaxIdx > nIdx || nMin == nMax, "NoteRange: Funny Situation." ); + if( nMaxIdx ) + --nMaxIdx; + if( nMaxIdx < nIdx ) + nMaxIdx = nIdx; + // If we end up on a right interval boundary, it must be raised to nMax. + if( nMaxIdx % 2 ) + (*pLongArr)[ nMaxIdx-- ] = nMax; + // Possible merge of intervals. + sal_uInt16 nDiff = nMaxIdx - nIdx; + nMaxIdx = nIdx / 2; // From here on is nMaxIdx the Index in BoolArray. + if( nDiff ) + { + pLongArr->erase( pLongArr->begin() + nIdx + 1, pLongArr->begin() + nIdx + 1 + nDiff ); + nDiff /= 2; + sal_uInt16 nStop = nMaxIdx + nDiff; + for( sal_uInt16 i = nMaxIdx; i < nStop; ++i ) + bToggle ^= aBoolArr[ i ]; + aBoolArr.erase( aBoolArr.begin() + nMaxIdx, aBoolArr.begin() + (nMaxIdx + nDiff) ); + } + DBG_ASSERT( nMaxIdx < aBoolArr.size(), "NoteRange: Too much deleted" ); + aBoolArr[ nMaxIdx ] = aBoolArr[ nMaxIdx ] != bToggle; + } +} + +void SvxBoundArgs::Calc( const tools::PolyPolygon& rPoly ) +{ + sal_uInt16 nCount; + nAct = 0; + for( sal_uInt16 i = 0; i < rPoly.Count(); ++i ) + { + const tools::Polygon& rPol = rPoly[ i ]; + nCount = rPol.GetSize(); + if( nCount ) + { + const Point& rNull = rPol[ 0 ]; + bClosed = IsConcat() || ( rNull == rPol[ nCount - 1 ] ); + nLast = Area( rNull ); + if( nLast & 12 ) + { + nFirst = 3; + if( bMultiple ) + nAct = 0; + } + else + { + // The first point of the polygon is within the line. + if( nLast ) + { + if( bMultiple || !nAct ) + { + nMin = USHRT_MAX; + nMax = 0; + } + if( nLast & 1 ) + NoteFarPoint( A(rNull), nLower - B(rNull), nLowDiff ); + else + NoteFarPoint( A(rNull), B(rNull) - nUpper, nUpDiff ); + } + else + { + if( bMultiple || !nAct ) + { + nMin = A(rNull); + nMax = nMin + nEnd; + nMin -= nStart; + } + else + NotePoint( A(rNull) ); + } + nFirst = 0; // leaving the line in which direction? + nAct = 3; // we are within the line at the moment. + } + if( nCount > 1 ) + { + sal_uInt16 nIdx = 1; + while( true ) + { + const Point& rLast = rPol[ nIdx - 1 ]; + if( nIdx == nCount ) + nIdx = 0; + const Point& rNext = rPol[ nIdx ]; + nNext = Area( rNext ); + nCut = nNext ^ nLast; + sal_uInt16 nOldAct = nAct; + if( nAct ) + CheckCut( rLast, rNext ); + if( nCut & 4 ) + { + NoteUpLow( Cut( nLower, rLast, rNext ), 2 ); + if( nAct && nAct != nOldAct ) + { + nOldAct = nAct; + CheckCut( rLast, rNext ); + } + } + if( nCut & 8 ) + { + NoteUpLow( Cut( nUpper, rLast, rNext ), 1 ); + if( nAct && nAct != nOldAct ) + CheckCut( rLast, rNext ); + } + if( !nIdx ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + if( !( nNext & 12 ) ) + { + if( !nNext ) + NotePoint( A(rNext) ); + else if( nNext & 1 ) + NoteFarPoint( A(rNext), nLower-B(rNext), nLowDiff ); + else + NoteFarPoint( A(rNext), B(rNext)-nUpper, nUpDiff ); + } + nLast = nNext; + if( ++nIdx == nCount && !bClosed ) + { + if( !( nNext & 12 ) ) + NoteLast(); + break; + } + } + } + if( bMultiple && IsConcat() ) + { + Add(); + nAct = 0; + } + } + } + if( !bMultiple ) + { + DBG_ASSERT( pLongArr->empty(), "I said: Simple!" ); + if( nAct ) + { + if( bInner ) + { + tools::Long nTmpMin = nMin + 2 * nStart; + tools::Long nTmpMax = nMax - 2 * nEnd; + if( nTmpMin <= nTmpMax ) + { + pLongArr->push_front(nTmpMax); + pLongArr->push_front(nTmpMin); + } + } + else + { + pLongArr->push_front(nMax); + pLongArr->push_front(nMin); + } + } + } + else if( !IsConcat() ) + Add(); +} + +void SvxBoundArgs::Add() +{ + size_t nCount = aBoolArr.size(); + if( nCount && ( !bInner || !pTextRanger->IsSimple() ) ) + { + bool bDelete = aBoolArr.front(); + if( bInner ) + bDelete = !bDelete; + sal_uInt16 nLongIdx = 1; + for( size_t nBoolIdx = 1; nBoolIdx < nCount; ++nBoolIdx ) + { + if( bDelete ) + { + sal_uInt16 next = 2; + while( nBoolIdx < nCount && !aBoolArr[ nBoolIdx++ ] && + (!bInner || nBoolIdx < nCount ) ) + next += 2; + pLongArr->erase( pLongArr->begin() + nLongIdx, pLongArr->begin() + nLongIdx + next ); + next /= 2; + nBoolIdx = nBoolIdx - next; + nCount = nCount - next; + aBoolArr.erase( aBoolArr.begin() + nBoolIdx, aBoolArr.begin() + (nBoolIdx + next) ); + if( nBoolIdx ) + aBoolArr[ nBoolIdx - 1 ] = false; +#if OSL_DEBUG_LEVEL > 1 + else + ++next; +#endif + } + bDelete = nBoolIdx < nCount && aBoolArr[ nBoolIdx ]; + nLongIdx += 2; + DBG_ASSERT( nLongIdx == 2*nBoolIdx+1, "BoundArgs: Array-Idx Confusion" ); + DBG_ASSERT( aBoolArr.size()*2 == pLongArr->size(), + "BoundArgs: Array-Count: Confusion" ); + } + } + if( pLongArr->empty() ) + return; + + if( !bInner ) + return; + + pLongArr->pop_front(); + pLongArr->pop_back(); + + // Here the line is held inside a large rectangle for "simple" + // contour wrap. Currently (April 1999) the EditEngine evaluates + // only the first rectangle. If it one day is able to output a line + // in several parts, it may be advisable to delete the following lines. + if( pTextRanger->IsSimple() && pLongArr->size() > 2 ) + pLongArr->erase( pLongArr->begin() + 1, pLongArr->end() - 1 ); +} + +void SvxBoundArgs::Concat( const tools::PolyPolygon* pPoly ) +{ + SetConcat( true ); + DBG_ASSERT( pPoly, "Nothing to do?" ); + std::deque<tools::Long>* pOld = pLongArr; + pLongArr = new std::deque<tools::Long>; + aBoolArr.clear(); + bInner = false; + Calc( *pPoly ); // Note that this updates pLongArr, which is why we swapped it out earlier. + std::deque<tools::Long>::size_type nCount = pLongArr->size(); + std::deque<tools::Long>::size_type nIdx = 0; + std::deque<tools::Long>::size_type i = 0; + bool bSubtract = pTextRanger->IsInner(); + while( i < nCount ) + { + std::deque<tools::Long>::size_type nOldCount = pOld->size(); + if( nIdx == nOldCount ) + { // Reached the end of the old Array... + if( !bSubtract ) + pOld->insert( pOld->begin() + nIdx, pLongArr->begin() + i, pLongArr->end() ); + break; + } + tools::Long nLeft = (*pLongArr)[ i++ ]; + tools::Long nRight = (*pLongArr)[ i++ ]; + std::deque<tools::Long>::size_type nLeftPos = nIdx + 1; + while( nLeftPos < nOldCount && nLeft > (*pOld)[ nLeftPos ] ) + nLeftPos += 2; + if( nLeftPos >= nOldCount ) + { // The current interval belongs to the end of the old array ... + if( !bSubtract ) + pOld->insert( pOld->begin() + nOldCount, pLongArr->begin() + i - 2, pLongArr->end() ); + break; + } + std::deque<tools::Long>::size_type nRightPos = nLeftPos - 1; + while( nRightPos < nOldCount && nRight >= (*pOld)[ nRightPos ] ) + nRightPos += 2; + if( nRightPos < nLeftPos ) + { // The current interval belongs between two old intervals + if( !bSubtract ) + pOld->insert( pOld->begin() + nRightPos, pLongArr->begin() + i - 2, pLongArr->begin() + i ); + } + else if( bSubtract ) // Subtract, if necessary separate + { + const tools::Long nOld = (*pOld)[nLeftPos - 1]; + if (nLeft > nOld) + { // Now we split the left part... + if( nLeft - 1 > nOld ) + { + pOld->insert( pOld->begin() + nLeftPos - 1, nOld ); + pOld->insert( pOld->begin() + nLeftPos, nLeft - 1 ); + nLeftPos += 2; + nRightPos += 2; + } + } + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + if (++nRight >= (*pOld)[nLeftPos]) + pOld->erase( pOld->begin() + nLeftPos - 1, pOld->begin() + nLeftPos + 1 ); + else + (*pOld)[ nLeftPos - 1 ] = nRight; + } + else // Merge + { + if( nLeft < (*pOld)[ nLeftPos - 1 ] ) + (*pOld)[ nLeftPos - 1 ] = nLeft; + if( nRight > (*pOld)[ nRightPos - 1 ] ) + (*pOld)[ nRightPos - 1 ] = nRight; + if( nRightPos - nLeftPos > 1 ) + pOld->erase( pOld->begin() + nLeftPos, pOld->begin() + nRightPos - 1 ); + + } + nIdx = nLeftPos - 1; + } + delete pLongArr; +} + +/************************************************************************* + * SvxBoundArgs::Area returns the area in which the point is located. + * 0 = within the line + * 1 = below, but within the upper edge + * 2 = above, but within the lower edge + * 5 = below the upper edge + *10 = above the lower edge + *************************************************************************/ + +sal_uInt16 SvxBoundArgs::Area( const Point& rPt ) +{ + tools::Long nB = B( rPt ); + if( nB >= nBottom ) + { + if( nB >= nLower ) + return 5; + return 1; + } + if( nB <= nTop ) + { + if( nB <= nUpper ) + return 10; + return 2; + } + return 0; +} + +/************************************************************************* + * lcl_Cut calculates the X-Coordinate of the distance (Pt1-Pt2) at the + * Y-Coordinate nY. + * It is assumed that the one of the points are located above and the other + * one below the Y-Coordinate. + *************************************************************************/ + +tools::Long SvxBoundArgs::Cut( tools::Long nB, const Point& rPt1, const Point& rPt2 ) +{ + if( pTextRanger->IsVertical() ) + { + double nQuot = nB - rPt1.X(); + nQuot /= ( rPt2.X() - rPt1.X() ); + nQuot *= ( rPt2.Y() - rPt1.Y() ); + return tools::Long( rPt1.Y() + nQuot ); + } + double nQuot = nB - rPt1.Y(); + nQuot /= ( rPt2.Y() - rPt1.Y() ); + nQuot *= ( rPt2.X() - rPt1.X() ); + return tools::Long( rPt1.X() + nQuot ); +} + +void SvxBoundArgs::NoteUpLow( tools::Long nA, const sal_uInt8 nArea ) +{ + if( nAct ) + { + NoteMargin( nA, nA ); + if( bMultiple ) + { + NoteRange( nArea != nAct ); + nAct = 0; + } + if( !nFirst ) + nFirst = nArea; + } + else + { + nAct = nArea; + nMin = nA; + nMax = nA; + } +} + +std::deque<tools::Long>* TextRanger::GetTextRanges( const Range& rRange ) +{ + DBG_ASSERT( rRange.Min() || rRange.Max(), "Zero-Range not allowed, Bye Bye" ); + //Can we find the result we need in the cache? + for (auto & elem : mRangeCache) + { + if (elem.range == rRange) + return &(elem.results); + } + //Calculate a new result + RangeCacheItem rngCache(rRange); + SvxBoundArgs aArg( this, &(rngCache.results), rRange ); + aArg.Calc( maPolyPolygon ); + if( mpLinePolyPolygon ) + aArg.Concat( &*mpLinePolyPolygon ); + //Add new result to the cache + mRangeCache.push_back(std::move(rngCache)); + if (mRangeCache.size() > nCacheSize) + mRangeCache.pop_front(); + return &(mRangeCache.back().results); +} + +const tools::Rectangle& TextRanger::GetBoundRect_() const +{ + DBG_ASSERT( !mxBound, "Don't call twice." ); + mxBound = maPolyPolygon.GetBoundRect(); + return *mxBound; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/unolingu.cxx b/editeng/source/misc/unolingu.cxx new file mode 100644 index 0000000000..1e7b69a25f --- /dev/null +++ b/editeng/source/misc/unolingu.cxx @@ -0,0 +1,754 @@ +/* -*- 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 <memory> +#include <editeng/unolingu.hxx> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <com/sun/star/linguistic2/DictionaryList.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> +#include <com/sun/star/linguistic2/LinguProperties.hpp> +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> + +#include <comphelper/processfactory.hxx> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <unotools/lingucfg.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <linguistic/misc.hxx> +#include <editeng/eerdll.hxx> +#include <editeng/editrids.hrc> +#include <svtools/strings.hrc> +#include <unotools/resmgr.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace ::comphelper; +using namespace ::linguistic; +using namespace ::com::sun::star; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::linguistic2; + +static uno::Reference< XLinguServiceManager2 > GetLngSvcMgr_Impl() +{ + uno::Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< XLinguServiceManager2 > xRes = LinguServiceManager::create(xContext); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL +//! when only the XSupportedLocales interface is used. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when "real" work needs to be done only. +class ThesDummy_Impl : + public cppu::WeakImplHelper< XThesaurus > +{ + uno::Reference< XThesaurus > xThes; // the real one... + std::unique_ptr<Sequence< lang::Locale >> pLocaleSeq; + + void GetCfgLocales(); + + void GetThes_Impl(); + +public: + ThesDummy_Impl() {} + + // XSupportedLocales + virtual css::uno::Sequence< css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XThesaurus + virtual css::uno::Sequence< + css::uno::Reference< css::linguistic2::XMeaning > > SAL_CALL + queryMeanings( const OUString& rTerm, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void ThesDummy_Impl::GetCfgLocales() +{ + if (pLocaleSeq) + return; + + SvtLinguConfig aCfg; + Sequence < OUString > aNodeNames( aCfg.GetNodeNames( "ServiceManager/ThesaurusList" ) ); + const OUString *pNodeNames = aNodeNames.getConstArray(); + sal_Int32 nLen = aNodeNames.getLength(); + pLocaleSeq.reset( new Sequence< lang::Locale >( nLen ) ); + lang::Locale *pLocale = pLocaleSeq->getArray(); + for (sal_Int32 i = 0; i < nLen; ++i) + { + pLocale[i] = LanguageTag::convertToLocaleWithFallback( pNodeNames[i] ); + } +} + + +void ThesDummy_Impl::GetThes_Impl() +{ + if (!xThes.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xThes = xLngSvcMgr->getThesaurus(); + + if (xThes.is()) + { + // no longer needed... + pLocaleSeq.reset(); + } + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + ThesDummy_Impl::getLocales() +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->getLocales(); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + return *pLocaleSeq; +} + + +sal_Bool SAL_CALL + ThesDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetThes_Impl(); + if (xThes.is()) + return xThes->hasLocale( rLocale ); + else if (!pLocaleSeq) // if not already loaded save startup time by avoiding loading them now + GetCfgLocales(); + bool bFound = false; + sal_Int32 nLen = pLocaleSeq->getLength(); + const lang::Locale *pLocale = pLocaleSeq->getConstArray(); + const lang::Locale *pEnd = pLocale + nLen; + for ( ; pLocale < pEnd && !bFound; ++pLocale) + { + bFound = pLocale->Language == rLocale.Language && + pLocale->Country == rLocale.Country && + pLocale->Variant == rLocale.Variant; + } + return bFound; +} + + +uno::Sequence< uno::Reference< linguistic2::XMeaning > > SAL_CALL + ThesDummy_Impl::queryMeanings( + const OUString& rTerm, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetThes_Impl(); + uno::Sequence< uno::Reference< linguistic2::XMeaning > > aRes; + OSL_ENSURE( xThes.is(), "Thesaurus missing" ); + if (xThes.is()) + aRes = xThes->queryMeanings( rTerm, rLocale, rProperties ); + return aRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class SpellDummy_Impl : + public cppu::WeakImplHelper< XSpellChecker1 > +{ + uno::Reference< XSpellChecker1 > xSpell; // the real one... + + void GetSpell_Impl(); + +public: + + // XSupportedLanguages (for XSpellChecker1) + virtual css::uno::Sequence< sal_Int16 > SAL_CALL + getLanguages() override; + virtual sal_Bool SAL_CALL + hasLanguage( sal_Int16 nLanguage ) override; + + // XSpellChecker1 (same as XSpellChecker but sal_Int16 for language) + virtual sal_Bool SAL_CALL + isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< css::linguistic2::XSpellAlternatives > SAL_CALL + spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void SpellDummy_Impl::GetSpell_Impl() +{ + if (!xSpell.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xSpell.set( xLngSvcMgr->getSpellChecker(), UNO_QUERY ); + } +} + + +uno::Sequence< sal_Int16 > SAL_CALL + SpellDummy_Impl::getLanguages() +{ + GetSpell_Impl(); + if (xSpell.is()) + return xSpell->getLanguages(); + else + return uno::Sequence< sal_Int16 >(); +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::hasLanguage( sal_Int16 nLanguage ) +{ + GetSpell_Impl(); + bool bRes = false; + if (xSpell.is()) + bRes = xSpell->hasLanguage( nLanguage ); + return bRes; +} + + +sal_Bool SAL_CALL + SpellDummy_Impl::isValid( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + bool bRes = true; + if (xSpell.is()) + bRes = xSpell->isValid( rWord, nLanguage, rProperties ); + return bRes; +} + + +uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL + SpellDummy_Impl::spell( const OUString& rWord, sal_Int16 nLanguage, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetSpell_Impl(); + uno::Reference< linguistic2::XSpellAlternatives > xRes; + if (xSpell.is()) + xRes = xSpell->spell( rWord, nLanguage, rProperties ); + return xRes; +} + +namespace { + +//! Dummy implementation in order to avoid loading of lingu DLL. +//! The dummy accesses the real implementation (and thus loading the DLL) +//! when it needs to be done only. +class HyphDummy_Impl : + public cppu::WeakImplHelper< XHyphenator > +{ + uno::Reference< XHyphenator > xHyph; // the real one... + + void GetHyph_Impl(); + +public: + + // XSupportedLocales + virtual css::uno::Sequence< + css::lang::Locale > SAL_CALL + getLocales() override; + virtual sal_Bool SAL_CALL + hasLocale( const css::lang::Locale& rLocale ) override; + + // XHyphenator + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + hyphenate( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XHyphenatedWord > SAL_CALL + queryAlternativeSpelling( const OUString& rWord, + const css::lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; + virtual css::uno::Reference< + css::linguistic2::XPossibleHyphens > SAL_CALL + createPossibleHyphens( + const OUString& rWord, + const css::lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) override; +}; + +} + +void HyphDummy_Impl::GetHyph_Impl() +{ + if (!xHyph.is()) + { + uno::Reference< XLinguServiceManager2 > xLngSvcMgr( GetLngSvcMgr_Impl() ); + xHyph = xLngSvcMgr->getHyphenator(); + } +} + + +uno::Sequence< lang::Locale > SAL_CALL + HyphDummy_Impl::getLocales() +{ + GetHyph_Impl(); + if (xHyph.is()) + return xHyph->getLocales(); + else + return uno::Sequence< lang::Locale >(); +} + + +sal_Bool SAL_CALL + HyphDummy_Impl::hasLocale( const lang::Locale& rLocale ) +{ + GetHyph_Impl(); + bool bRes = false; + if (xHyph.is()) + bRes = xHyph->hasLocale( rLocale ); + return bRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::hyphenate( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nMaxLeading, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->hyphenate( rWord, rLocale, nMaxLeading, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XHyphenatedWord > SAL_CALL + HyphDummy_Impl::queryAlternativeSpelling( + const OUString& rWord, + const lang::Locale& rLocale, + sal_Int16 nIndex, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XHyphenatedWord > xRes; + if (xHyph.is()) + xRes = xHyph->queryAlternativeSpelling( rWord, rLocale, nIndex, rProperties ); + return xRes; +} + + +uno::Reference< linguistic2::XPossibleHyphens > SAL_CALL + HyphDummy_Impl::createPossibleHyphens( + const OUString& rWord, + const lang::Locale& rLocale, + const css::uno::Sequence< css::beans::PropertyValue >& rProperties ) +{ + GetHyph_Impl(); + uno::Reference< linguistic2::XPossibleHyphens > xRes; + if (xHyph.is()) + xRes = xHyph->createPossibleHyphens( rWord, rLocale, rProperties ); + return xRes; +} + +class LinguMgrExitLstnr : public cppu::WeakImplHelper<XEventListener> +{ + uno::Reference< XDesktop2 > xDesktop; + + static void AtExit(); + +public: + LinguMgrExitLstnr(); + virtual ~LinguMgrExitLstnr() override; + + // lang::XEventListener + virtual void SAL_CALL disposing(const EventObject& rSource) override; +}; + +LinguMgrExitLstnr::LinguMgrExitLstnr() +{ + // add object to frame::Desktop EventListeners in order to properly call + // the AtExit function at application exit. + + uno::Reference< XComponentContext > xContext = getProcessComponentContext(); + xDesktop = Desktop::create( xContext ); + xDesktop->addEventListener( this ); +} + +LinguMgrExitLstnr::~LinguMgrExitLstnr() +{ + if (xDesktop.is()) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + } + OSL_ENSURE(!xDesktop.is(), "reference to desktop should be released"); +} + +void LinguMgrExitLstnr::disposing(const EventObject& rSource) +{ + if (xDesktop.is() && rSource.Source == xDesktop) + { + xDesktop->removeEventListener( this ); + xDesktop = nullptr; //! release reference to desktop + + AtExit(); + } +} + +void LinguMgrExitLstnr::AtExit() +{ + SolarMutexGuard g; + + // release references + LinguMgr::xLngSvcMgr = nullptr; + LinguMgr::xSpell = nullptr; + LinguMgr::xHyph = nullptr; + LinguMgr::xThes = nullptr; + LinguMgr::xDicList = nullptr; + LinguMgr::xProp = nullptr; + LinguMgr::xIgnoreAll = nullptr; + LinguMgr::xChangeAll = nullptr; + + LinguMgr::bExiting = true; + + LinguMgr::pExitLstnr = nullptr; +} + + +rtl::Reference<LinguMgrExitLstnr> LinguMgr::pExitLstnr; +bool LinguMgr::bExiting = false; +uno::Reference< XLinguServiceManager2 > LinguMgr::xLngSvcMgr; +uno::Reference< XSpellChecker1 > LinguMgr::xSpell; +uno::Reference< XHyphenator > LinguMgr::xHyph; +uno::Reference< XThesaurus > LinguMgr::xThes; +uno::Reference< XSearchableDictionaryList > LinguMgr::xDicList; +uno::Reference< XLinguProperties > LinguMgr::xProp; +uno::Reference< XDictionary > LinguMgr::xIgnoreAll; +uno::Reference< XDictionary > LinguMgr::xChangeAll; + + +uno::Reference< XLinguServiceManager2 > LinguMgr::GetLngSvcMgr() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + if (!xLngSvcMgr.is()) + xLngSvcMgr = GetLngSvcMgr_Impl(); + + return xLngSvcMgr; +} + + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpellChecker() +{ + return xSpell.is() ? xSpell : GetSpell(); +} + +uno::Reference< XHyphenator > LinguMgr::GetHyphenator() +{ + return xHyph.is() ? xHyph : GetHyph(); +} + +uno::Reference< XThesaurus > LinguMgr::GetThesaurus() +{ + return xThes.is() ? xThes : GetThes(); +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDictionaryList() +{ + return xDicList.is() ? xDicList : GetDicList(); +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetLinguPropertySet() +{ + return xProp.is() ? xProp : GetProp(); +} + +uno::Reference< XDictionary > LinguMgr::GetStandardDic() +{ + //! don't hold reference to this + //! (it may be removed from dictionary list and needs to be + //! created empty if accessed again) + return GetStandard(); +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAllList() +{ + return xIgnoreAll.is() ? xIgnoreAll : GetIgnoreAll(); +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAllList() +{ + return xChangeAll.is() ? xChangeAll : GetChangeAll(); +} + +uno::Reference< XSpellChecker1 > LinguMgr::GetSpell() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xSpell = new SpellDummy_Impl; + return xSpell; +} + +uno::Reference< XHyphenator > LinguMgr::GetHyph() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + xHyph = new HyphDummy_Impl; + return xHyph; +} + +uno::Reference< XThesaurus > LinguMgr::GetThes() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + //! use dummy implementation in order to avoid loading of lingu DLL + //! when only the XSupportedLocales interface is used. + //! The dummy accesses the real implementation (and thus loading the DLL) + //! when "real" work needs to be done only. + xThes = new ThesDummy_Impl; + return xThes; +} + +uno::Reference< XSearchableDictionaryList > LinguMgr::GetDicList() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xDicList = linguistic2::DictionaryList::create( getProcessComponentContext() ); + return xDicList; +} + +uno::Reference< linguistic2::XLinguProperties > LinguMgr::GetProp() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + xProp = linguistic2::LinguProperties::create( getProcessComponentContext() ); + return xProp; +} + +uno::Reference< XDictionary > LinguMgr::GetIgnoreAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (xTmpDicList.is()) + { + std::locale loc(Translate::Create("svt")); + xIgnoreAll = xTmpDicList->getDictionaryByName( + Translate::get(STR_DESCRIPTION_IGNOREALLLIST, loc) ); + } + return xIgnoreAll; +} + +uno::Reference< XDictionary > LinguMgr::GetChangeAll() +{ + if (bExiting) + return nullptr; + + if (!pExitLstnr) + pExitLstnr = new LinguMgrExitLstnr; + + uno::Reference< XSearchableDictionaryList > _xDicList = GetDictionaryList(); + if (_xDicList.is()) + { + xChangeAll = _xDicList->createDictionary( + "ChangeAllList", + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_NEGATIVE, OUString() ); + } + return xChangeAll; +} + +uno::Reference< XDictionary > LinguMgr::GetStandard() +{ + // Tries to return a dictionary which may hold positive entries is + // persistent and not read-only. + + if (bExiting) + return nullptr; + + uno::Reference< XSearchableDictionaryList > xTmpDicList( GetDictionaryList() ); + if (!xTmpDicList.is()) + return nullptr; + + static constexpr OUString aDicName( u"standard.dic"_ustr ); + uno::Reference< XDictionary > xDic = xTmpDicList->getDictionaryByName( aDicName ); + if (!xDic.is()) + { + // try to create standard dictionary + uno::Reference< XDictionary > xTmp; + try + { + xTmp = xTmpDicList->createDictionary( aDicName, + LanguageTag::convertToLocale( LANGUAGE_NONE ), + DictionaryType_POSITIVE, + linguistic::GetWritableDictionaryURL( aDicName ) ); + } + catch(const css::uno::Exception &) + { + } + + // add new dictionary to list + if (xTmp.is()) + { + xTmpDicList->addDictionary( xTmp ); + xTmp->setActive( true ); + } + xDic = xTmp; + } +#if OSL_DEBUG_LEVEL > 1 + uno::Reference< XStorable > xStor( xDic, UNO_QUERY ); + OSL_ENSURE( xDic.is() && xDic->getDictionaryType() == DictionaryType_POSITIVE, + "wrong dictionary type"); + OSL_ENSURE( xDic.is() && LanguageTag( xDic->getLocale() ).getLanguageType() == LANGUAGE_NONE, + "wrong dictionary language"); + OSL_ENSURE( !xStor.is() || (xStor->hasLocation() && !xStor->isReadonly()), + "dictionary not editable" ); +#endif + + return xDic; +} + +SvxAlternativeSpelling SvxGetAltSpelling( + const css::uno::Reference< css::linguistic2::XHyphenatedWord > & rHyphWord ) +{ + SvxAlternativeSpelling aRes; + if (rHyphWord.is() && rHyphWord->isAlternativeSpelling()) + { + OUString aWord( rHyphWord->getWord() ), + aAltWord( rHyphWord->getHyphenatedWord() ); + sal_Int16 nHyphenationPos = rHyphWord->getHyphenationPos(), + nHyphenPos = rHyphWord->getHyphenPos(); + sal_Int16 nLen = static_cast<sal_Int16>(aWord.getLength()); + sal_Int16 nAltLen = static_cast<sal_Int16>(aAltWord.getLength()); + const sal_Unicode *pWord = aWord.getStr(), + *pAltWord = aAltWord.getStr(); + + // count number of chars from the left to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nL = 0; + while (nL <= nHyphenationPos && nL <= nHyphenPos + && pWord[ nL ] == pAltWord[ nL ]) + ++nL; + // count number of chars from the right to the + // hyphenation pos / hyphen pos that are equal + sal_Int16 nR = 0; + sal_Int32 nIdx = nLen - 1; + sal_Int32 nAltIdx = nAltLen - 1; + while (nIdx > nHyphenationPos && nAltIdx > nHyphenPos + && pWord[ nIdx-- ] == pAltWord[ nAltIdx-- ]) + ++nR; + + aRes.aReplacement = aAltWord.copy( nL, nAltLen - nL - nR ); + aRes.nChangedPos = nL; + aRes.nChangedLength = nLen - nL - nR; + aRes.bIsAltSpelling = true; + } + return aRes; +} + + +SvxDicListChgClamp::SvxDicListChgClamp( uno::Reference< XSearchableDictionaryList > _xDicList ) : + xDicList (std::move( _xDicList )) +{ + if (xDicList.is()) + { + xDicList->beginCollectEvents(); + } +} + +SvxDicListChgClamp::~SvxDicListChgClamp() +{ + if (xDicList.is()) + { + xDicList->endCollectEvents(); + } +} + +short SvxDicError(weld::Window *pParent, linguistic::DictionaryError nError) +{ + short nRes = 0; + if (linguistic::DictionaryError::NONE != nError) + { + TranslateId pRid; + switch (nError) + { + case linguistic::DictionaryError::FULL : pRid = RID_SVXSTR_DIC_ERR_FULL; break; + case linguistic::DictionaryError::READONLY : pRid = RID_SVXSTR_DIC_ERR_READONLY; break; + default: + pRid = RID_SVXSTR_DIC_ERR_UNKNOWN; + SAL_WARN("editeng", "unexpected case"); + } + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + EditResId(pRid))); + nRes = xInfoBox->run(); + + } + return nRes; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/misc/urlfieldhelper.cxx b/editeng/source/misc/urlfieldhelper.cxx new file mode 100644 index 0000000000..57f2a042c6 --- /dev/null +++ b/editeng/source/misc/urlfieldhelper.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <editeng/urlfieldhelper.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> + +void URLFieldHelper::RemoveURLField(EditView& pEditView) +{ + pEditView.SelectFieldAtCursor(); + const SvxFieldItem* pFieldItem = pEditView.GetFieldAtSelection(); + const SvxFieldData* pField = pFieldItem ? pFieldItem->GetField() : nullptr; + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pField)) + { + ESelection aSel = pEditView.GetSelection(); + pEditView.GetEditEngine()->QuickInsertText(pUrlField->GetRepresentation(), aSel); + pEditView.Invalidate(); + } +} + +bool URLFieldHelper::IsCursorAtURLField(const EditView& pEditView, bool bAlsoCheckBeforeCursor) +{ + // tdf#128666 Make sure only URL field (or nothing) is selected + ESelection aSel = pEditView.GetSelection(); + auto nSelectedParas = aSel.nEndPara - aSel.nStartPara; + auto nSelectedChars = aSel.nEndPos - aSel.nStartPos; + bool bIsValidSelection + = nSelectedParas == 0 + && (nSelectedChars == 0 || nSelectedChars == 1 || nSelectedChars == -1); + if (!bIsValidSelection) + return false; + + const SvxFieldData* pField + = pEditView.GetFieldUnderMouseOrInSelectionOrAtCursor(bAlsoCheckBeforeCursor); + if (dynamic_cast<const SvxURLField*>(pField)) + return true; + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/editeng/source/outliner/outleeng.cxx b/editeng/source/outliner/outleeng.cxx new file mode 100644 index 0000000000..1136ef37b9 --- /dev/null +++ b/editeng/source/outliner/outleeng.cxx @@ -0,0 +1,203 @@ +/* -*- 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 <editeng/editeng.hxx> +#include <editeng/eerdll.hxx> + +#include <editeng/outliner.hxx> +#include <outleeng.hxx> +#include "paralist.hxx" +#include <editeng/editrids.hrc> +#include <optional> +#include <svl/itemset.hxx> +#include <editeng/editstat.hxx> +#include "outlundo.hxx" + +OutlinerEditEng::OutlinerEditEng( Outliner* pEngOwner, SfxItemPool* pPool ) + : EditEngine( pPool ) +{ + pOwner = pEngOwner; +} + +OutlinerEditEng::~OutlinerEditEng() +{ +} + +void OutlinerEditEng::PaintingFirstLine(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev) +{ + if( GetControlWord() & EEControlBits::OUTLINER ) + { + PaintFirstLineInfo aInfo(nPara, rStartPos, &rOutDev); + pOwner->maPaintFirstLineHdl.Call( &aInfo ); + } + + pOwner->PaintBullet(nPara, rStartPos, rOrigin, nOrientation, rOutDev); +} + +const SvxNumberFormat* OutlinerEditEng::GetNumberFormat( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = nullptr; + if (pOwner) + pFmt = pOwner->GetNumberFormat( nPara ); + return pFmt; +} + + +tools::Rectangle OutlinerEditEng::GetBulletArea( sal_Int32 nPara ) +{ + tools::Rectangle aBulletArea { Point(), Point() }; + if ( nPara < pOwner->pParaList->GetParagraphCount() ) + { + if ( pOwner->ImplHasNumberFormat( nPara ) ) + aBulletArea = pOwner->ImpCalcBulletArea( nPara, false, false ); + } + return aBulletArea; +} + +std::optional<bool> OutlinerEditEng::GetCompatFlag(SdrCompatibilityFlag eFlag) const +{ + if(pOwner) + { + return pOwner->GetCompatFlag(eFlag); + } + return {}; +} + +void OutlinerEditEng::ParagraphInserted( sal_Int32 nNewParagraph ) +{ + pOwner->ParagraphInserted( nNewParagraph ); + + EditEngine::ParagraphInserted( nNewParagraph ); +} + +void OutlinerEditEng::ParagraphDeleted( sal_Int32 nDeletedParagraph ) +{ + pOwner->ParagraphDeleted( nDeletedParagraph ); + + EditEngine::ParagraphDeleted( nDeletedParagraph ); +} + +void OutlinerEditEng::ParagraphConnected( sal_Int32 /*nLeftParagraph*/, sal_Int32 nRightParagraph ) +{ + if( pOwner && pOwner->IsUndoEnabled() && !pOwner->GetEditEngine().IsInUndo() ) + { + Paragraph* pPara = pOwner->GetParagraph( nRightParagraph ); + if( pPara && Outliner::HasParaFlag( pPara, ParaFlag::ISPAGE ) ) + { + pOwner->InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( pOwner, nRightParagraph, ParaFlag::ISPAGE, ParaFlag::NONE ) ); + } + } +} + + +void OutlinerEditEng::StyleSheetChanged( SfxStyleSheet* pStyle ) +{ + pOwner->StyleSheetChanged( pStyle ); +} + +void OutlinerEditEng::ParaAttribsChanged( sal_Int32 nPara ) +{ + pOwner->ParaAttribsChanged( nPara ); +} + +bool OutlinerEditEng::SpellNextDocument() +{ + return pOwner->SpellNextDocument(); +} + +bool OutlinerEditEng::ConvertNextDocument() +{ + return pOwner->ConvertNextDocument(); +} + +OUString OutlinerEditEng::GetUndoComment( sal_uInt16 nUndoId ) const +{ + switch( nUndoId ) + { + case OLUNDO_DEPTH: + return EditResId(RID_OUTLUNDO_DEPTH); + + case OLUNDO_EXPAND: + return EditResId(RID_OUTLUNDO_EXPAND); + + case OLUNDO_COLLAPSE: + return EditResId(RID_OUTLUNDO_COLLAPSE); + + case OLUNDO_ATTR: + return EditResId(RID_OUTLUNDO_ATTR); + + case OLUNDO_INSERT: + return EditResId(RID_OUTLUNDO_INSERT); + + default: + return EditEngine::GetUndoComment( nUndoId ); + } +} + +void OutlinerEditEng::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, sal_Int32 nTextLen, + std::span<const sal_Int32> pDXArray, std::span<const sal_Bool> pKashidaArray, + const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, + const EEngineData::WrongSpellVector* pWrongSpellVector, + const SvxFieldData* pFieldData, + bool bEndOfLine, + bool bEndOfParagraph, + const css::lang::Locale* pLocale, + const Color& rOverlineColor, + const Color& rTextLineColor) +{ + pOwner->DrawingText(rStartPos,rText,nTextStart,nTextLen,pDXArray,pKashidaArray,rFont,nPara,nRightToLeft, + pWrongSpellVector, pFieldData, bEndOfLine, bEndOfParagraph, false/*bEndOfBullet*/, pLocale, rOverlineColor, rTextLineColor); +} + +void OutlinerEditEng::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, + const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft, + bool bEndOfLine, bool bEndOfParagraph, + const Color& rOverlineColor, const Color& rTextLineColor) +{ + pOwner->DrawingTab(rStartPos, nWidth, rChar, rFont, nPara, nRightToLeft, + bEndOfLine, bEndOfParagraph, rOverlineColor, rTextLineColor ); +} + +OUString OutlinerEditEng::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle ) +{ + return pOwner->CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor, rpFldLineStyle ); +} + +void OutlinerEditEng::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + if( !pPara ) + return; + + if ( !IsInUndo() && IsUndoEnabled() ) + pOwner->UndoActionStart( OLUNDO_ATTR ); + + EditEngine::SetParaAttribs( nPara, rSet ); + + pOwner->ImplCheckNumBulletItem( nPara ); + // #i100014# + // It is not a good idea to subtract 1 from a count and cast the result + // to sal_uInt16 without check, if the count is 0. + pOwner->ImplCheckParagraphs( nPara, pOwner->pParaList->GetParagraphCount() ); + + if ( !IsInUndo() && IsUndoEnabled() ) + pOwner->UndoActionEnd(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlin2.cxx b/editeng/source/outliner/outlin2.cxx new file mode 100644 index 0000000000..e4d0386cf2 --- /dev/null +++ b/editeng/source/outliner/outlin2.cxx @@ -0,0 +1,600 @@ +/* -*- 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 <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editund2.hxx> + +#include <svl/style.hxx> +#include <vcl/mapmod.hxx> + +#include <editeng/forbiddencharacterstable.hxx> + +#include <editeng/outliner.hxx> +#include "paralist.hxx" +#include <outleeng.hxx> +#include <editeng/editstat.hxx> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::linguistic2; + + +// ====================== Simple pass-through ======================= + + +bool Outliner::SetUpdateLayout( bool bUpdate ) +{ + return pEditEngine->SetUpdateLayout( bUpdate ); +} + + +bool Outliner::IsUpdateLayout() const +{ + return pEditEngine->IsUpdateLayout(); +} + +const SfxItemSet& Outliner::GetEmptyItemSet() const +{ + return pEditEngine->GetEmptyItemSet(); +} + +void Outliner::EnableUndo( bool bEnable ) +{ + pEditEngine->EnableUndo( bEnable ); +} + +bool Outliner::IsUndoEnabled() const +{ + return pEditEngine->IsUndoEnabled(); +} + +MapMode const & Outliner::GetRefMapMode() const +{ + return pEditEngine->GetRefMapMode(); +} + +void Outliner::SetRefMapMode( const MapMode& rMMode ) +{ + pEditEngine->SetRefMapMode( rMMode ); +} + +void Outliner::SetBackgroundColor( const Color& rColor ) +{ + pEditEngine->SetBackgroundColor( rColor ); +} + +Color const & Outliner::GetBackgroundColor() const +{ + return pEditEngine->GetBackgroundColor(); +} + + +void Outliner::ClearModifyFlag() +{ + pEditEngine->ClearModifyFlag(); +} + +bool Outliner::IsModified() const +{ + return pEditEngine->IsModified(); +} + +sal_uInt32 Outliner::GetTextHeight() const +{ + return pEditEngine->GetTextHeight(); +} + +void Outliner::SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) +{ + pEditEngine->SetModifyHdl( rLink ); +} + +void Outliner::SetNotifyHdl( const Link<EENotify&,void>& rLink ) +{ + pEditEngine->aOutlinerNotifyHdl = rLink; + + if ( rLink.IsSet() ) + pEditEngine->SetNotifyHdl( LINK( this, Outliner, EditEngineNotifyHdl ) ); + else + pEditEngine->SetNotifyHdl( Link<EENotify&,void>() ); +} + +void Outliner::SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) +{ + pEditEngine->SetStatusEventHdl( rLink ); +} + +Link<EditStatus&, void> const & Outliner::GetStatusEventHdl() const +{ + return pEditEngine->GetStatusEventHdl(); +} + +void Outliner::SetDefTab( sal_uInt16 nTab ) +{ + pEditEngine->SetDefTab( nTab ); +} + +bool Outliner::IsFlatMode() const +{ + return pEditEngine->IsFlatMode(); +} + +bool Outliner::UpdateFields() +{ + return pEditEngine->UpdateFields(); +} + +void Outliner::RemoveFields( const std::function<bool ( const SvxFieldData* )>& isFieldData ) +{ + pEditEngine->RemoveFields( isFieldData ); +} + +void Outliner::SetWordDelimiters( const OUString& rDelimiters ) +{ + pEditEngine->SetWordDelimiters( rDelimiters ); +} + +OUString const & Outliner::GetWordDelimiters() const +{ + return pEditEngine->GetWordDelimiters(); +} + +OUString Outliner::GetWord( sal_Int32 nPara, sal_Int32 nIndex ) +{ + return pEditEngine->GetWord( nPara, nIndex ); +} + +void Outliner::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect ) +{ + pEditEngine->Draw( rOutDev, rOutRect ); +} + +void Outliner::Draw( OutputDevice& rOutDev, const Point& rStartPos ) +{ + pEditEngine->Draw( rOutDev, rStartPos ); +} + +void Outliner::SetPaperSize( const Size& rSize ) +{ + pEditEngine->SetPaperSize( rSize ); +} + +const Size& Outliner::GetPaperSize() const +{ + return pEditEngine->GetPaperSize(); +} + +void Outliner::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon ) +{ + pEditEngine->SetPolygon( rPolyPolygon ); +} + +void Outliner::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DPolyPolygon* pLinePolyPolygon) +{ + pEditEngine->SetPolygon( rPolyPolygon, pLinePolyPolygon); +} + +void Outliner::ClearPolygon() +{ + pEditEngine->ClearPolygon(); +} + +const Size& Outliner::GetMinAutoPaperSize() const +{ + return pEditEngine->GetMinAutoPaperSize(); +} + +void Outliner::SetMinAutoPaperSize( const Size& rSz ) +{ + pEditEngine->SetMinAutoPaperSize( rSz ); +} + +const Size& Outliner::GetMaxAutoPaperSize() const +{ + return pEditEngine->GetMaxAutoPaperSize(); +} + +void Outliner::SetMaxAutoPaperSize( const Size& rSz ) +{ + pEditEngine->SetMaxAutoPaperSize( rSz ); +} + +void Outliner::SetMinColumnWrapHeight(tools::Long nVal) +{ + pEditEngine->SetMinColumnWrapHeight(nVal); +} + +bool Outliner::IsExpanded( Paragraph const * pPara ) const +{ + return pParaList->HasVisibleChildren( pPara ); +} + +Paragraph* Outliner::GetParent( Paragraph const * pParagraph ) const +{ + return pParaList->GetParent( pParagraph ); +} + +sal_Int32 Outliner::GetChildCount( Paragraph const * pParent ) const +{ + return pParaList->GetChildCount( pParent ); +} + +Size Outliner::CalcTextSize() +{ + return Size(pEditEngine->CalcTextWidth(),pEditEngine->GetTextHeight()); +} + +Size Outliner::CalcTextSizeNTP() +{ + return Size(pEditEngine->CalcTextWidth(),pEditEngine->GetTextHeightNTP()); +} + +void Outliner::SetStyleSheetPool( SfxStyleSheetPool* pSPool ) +{ + pEditEngine->SetStyleSheetPool( pSPool ); +} + +SfxStyleSheetPool* Outliner::GetStyleSheetPool() +{ + return pEditEngine->GetStyleSheetPool(); +} + +SfxStyleSheet* Outliner::GetStyleSheet( sal_Int32 nPara ) +{ + return pEditEngine->GetStyleSheet( nPara ); +} + +bool Outliner::IsInSelectionMode() const +{ + return pEditEngine->IsInSelectionMode(); +} + +void Outliner::SetControlWord( EEControlBits nWord ) +{ + pEditEngine->SetControlWord( nWord ); +} + +EEControlBits Outliner::GetControlWord() const +{ + return pEditEngine->GetControlWord(); +} + +void Outliner::SetAsianCompressionMode( CharCompressType n ) +{ + pEditEngine->SetAsianCompressionMode( n ); +} + +void Outliner::SetKernAsianPunctuation( bool b ) +{ + pEditEngine->SetKernAsianPunctuation( b ); +} + +void Outliner::SetAddExtLeading( bool bExtLeading ) +{ + pEditEngine->SetAddExtLeading( bExtLeading ); +} + +void Outliner::UndoActionStart( sal_uInt16 nId ) +{ + pEditEngine->UndoActionStart( nId ); +} + +void Outliner::UndoActionEnd() +{ + pEditEngine->UndoActionEnd(); +} + +void Outliner::InsertUndo( std::unique_ptr<EditUndo> pUndo ) +{ + pEditEngine->GetUndoManager().AddUndoAction( std::move(pUndo) ); +} + +bool Outliner::IsInUndo() const +{ + return pEditEngine->IsInUndo(); +} + +sal_uInt32 Outliner::GetLineCount( sal_Int32 nParagraph ) const +{ + return pEditEngine->GetLineCount( nParagraph ); +} + +sal_Int32 Outliner::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + return pEditEngine->GetLineLen( nParagraph, nLine ); +} + +sal_uInt32 Outliner::GetLineHeight( sal_Int32 nParagraph ) +{ + return pEditEngine->GetLineHeight( nParagraph ); +} + +void Outliner::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich ) +{ + pEditEngine->RemoveCharAttribs( nPara, nWhich ); +} + +EESpellState Outliner::HasSpellErrors() +{ + return pEditEngine->HasSpellErrors(); +} + +bool Outliner::HasConvertibleTextPortion( LanguageType nLang ) +{ + return pEditEngine->HasConvertibleTextPortion( nLang ); +} + +bool Outliner::ConvertNextDocument() +{ + return false; +} + +void Outliner::SetDefaultLanguage( LanguageType eLang ) +{ + pEditEngine->SetDefaultLanguage( eLang ); +} + +void Outliner::CompleteOnlineSpelling() +{ + pEditEngine->CompleteOnlineSpelling(); +} + +bool Outliner::HasText( const SvxSearchItem& rSearchItem ) +{ + return pEditEngine->HasText( rSearchItem ); +} + +void Outliner::SetEditTextObjectPool( SfxItemPool* pPool ) +{ + pEditEngine->SetEditTextObjectPool( pPool ); +} + +SfxItemPool* Outliner::GetEditTextObjectPool() const +{ + return pEditEngine->GetEditTextObjectPool(); +} + +bool Outliner::SpellNextDocument() +{ + return false; +} + + +void Outliner::SetSpeller( Reference< XSpellChecker1 > const &xSpeller ) +{ + pEditEngine->SetSpeller( xSpeller ); +} + +Reference< XSpellChecker1 > const & Outliner::GetSpeller() +{ + return pEditEngine->GetSpeller(); +} + +void Outliner::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars) +{ + EditEngine::SetForbiddenCharsTable(xForbiddenChars); +} + +void Outliner::SetHyphenator( Reference< XHyphenator > const & xHyph ) +{ + pEditEngine->SetHyphenator( xHyph ); +} + +OutputDevice* Outliner::GetRefDevice() const +{ + return pEditEngine->GetRefDevice(); +} + +tools::Rectangle Outliner::GetParaBounds( sal_Int32 nParagraph ) const +{ + return pEditEngine->GetParaBounds(nParagraph ); +} + +Point Outliner::GetDocPos( const Point& rPaperPos ) const +{ + return pEditEngine->GetDocPos( rPaperPos ); +} + +bool Outliner::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder ) +{ + return IsTextPos( rPaperPos, nBorder, nullptr ); +} + +bool Outliner::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder, bool* pbBullet ) +{ + if ( pbBullet) + *pbBullet = false; + bool bTextPos = pEditEngine->IsTextPos( rPaperPos, nBorder ); + if ( !bTextPos ) + { + Point aDocPos = GetDocPos( rPaperPos ); + sal_Int32 nPara = pEditEngine->FindParagraph( aDocPos.Y() ); + if ( ( nPara != EE_PARA_NOT_FOUND ) && ImplHasNumberFormat( nPara ) ) + { + tools::Rectangle aBulArea = ImpCalcBulletArea( nPara, true, true ); + if ( aBulArea.Contains( rPaperPos ) ) + { + bTextPos = true; + if ( pbBullet) + *pbBullet = true; + } + } + } + + return bTextPos; +} + +void Outliner::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + pEditEngine->QuickSetAttribs( rSet, rSel ); +} + +void Outliner::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertText( rText, rSel ); +} + +void Outliner::QuickDelete( const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickDelete( rSel ); +} + +void Outliner::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertField( rFld, rSel ); +} + +void Outliner::QuickInsertLineBreak( const ESelection& rSel ) +{ + bFirstParaIsEmpty = false; + pEditEngine->QuickInsertLineBreak( rSel ); +} + +void Outliner::QuickFormatDoc() +{ + pEditEngine->QuickFormatDoc(); +} + +void Outliner::setGlobalScale(double rFontX, double rFontY, double rSpacingX, double rSpacingY) +{ + // reset bullet size + sal_Int32 nParagraphs = pParaList->GetParagraphCount(); + for ( sal_Int32 nPara = 0; nPara < nParagraphs; nPara++ ) + { + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if ( pPara ) + pPara->aBulSize.setWidth( -1 ); + } + + pEditEngine->setGlobalScale(rFontX, rFontY, rSpacingX, rSpacingY); +} + +void Outliner::getGlobalScale(double& rFontX, double& rFontY, double& rSpacingX, double& rSpacingY) const +{ + pEditEngine->getGlobalFontScale(rFontX, rFontY); + pEditEngine->getGlobalSpacingScale(rSpacingX, rSpacingY); +} + +void Outliner::setRoundFontSizeToPt(bool bRound) const +{ + pEditEngine->setRoundFontSizeToPt(bRound); +} + +void Outliner::EraseVirtualDevice() +{ + pEditEngine->EraseVirtualDevice(); +} + +bool Outliner::ShouldCreateBigTextObject() const +{ + return pEditEngine->ShouldCreateBigTextObject(); +} + +const EditEngine& Outliner::GetEditEngine() const +{ + return *pEditEngine; +} + +void Outliner::SetVertical(bool bVertical) +{ + pEditEngine->SetVertical(bVertical); +} + +void Outliner::SetRotation(TextRotation nRotation) +{ + pEditEngine->SetRotation(nRotation); +} + +bool Outliner::IsVertical() const +{ + return pEditEngine->IsEffectivelyVertical(); +} + +bool Outliner::IsTopToBottom() const +{ + return pEditEngine->IsTopToBottom(); +} + +void Outliner::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) +{ + pEditEngine->SetTextColumns(nColumns, nSpacing); +} + +void Outliner::SetFixedCellHeight( bool bUseFixedCellHeight ) +{ + pEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +} + +void Outliner::SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) +{ + pEditEngine->SetDefaultHorizontalTextDirection( eHTextDir ); +} + +EEHorizontalTextDirection Outliner::GetDefaultHorizontalTextDirection() const +{ + return pEditEngine->GetDefaultHorizontalTextDirection(); +} + +LanguageType Outliner::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + return pEditEngine->GetLanguage( nPara, nPos ).nLang; +} + +void Outliner::RemoveAttribs( const ESelection& rSelection, bool bRemoveParaAttribs, sal_uInt16 nWhich ) +{ + pEditEngine->RemoveAttribs( rSelection, bRemoveParaAttribs, nWhich ); +} + +void Outliner::EnableAutoColor( bool b ) +{ + pEditEngine->EnableAutoColor( b ); +} + +void Outliner::ForceAutoColor( bool b ) +{ + pEditEngine->ForceAutoColor( b ); +} + +bool Outliner::IsForceAutoColor() const +{ + return pEditEngine->IsForceAutoColor(); +} + +bool Outliner::SpellSentence(EditView const & rEditView, svx::SpellPortions& rToFill ) +{ + return pEditEngine->SpellSentence(rEditView, rToFill ); +} + +void Outliner::PutSpellingToSentenceStart( EditView const & rEditView ) +{ + pEditEngine->PutSpellingToSentenceStart( rEditView ); +} + +void Outliner::ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck ) +{ + pEditEngine->ApplyChangedSentence( rEditView, rNewPortions, bRecheck ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outliner.cxx b/editeng/source/outliner/outliner.cxx new file mode 100644 index 0000000000..bb8a8ac419 --- /dev/null +++ b/editeng/source/outliner/outliner.cxx @@ -0,0 +1,2163 @@ +/* -*- 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 <comphelper/string.hxx> +#include <svl/eitem.hxx> +#include <svl/intitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> +#include <editeng/lrspitem.hxx> + +#include <math.h> +#include <svl/style.hxx> +#include <editeng/outliner.hxx> +#include "paralist.hxx" +#include <editeng/outlobj.hxx> +#include <outleeng.hxx> +#include "outlundo.hxx" +#include <editeng/eeitem.hxx> +#include <editeng/editstat.hxx> +#include <editeng/overflowingtxt.hxx> +#include <editeng/editobj.hxx> +#include <svl/itemset.hxx> +#include <vcl/metric.hxx> +#include <editeng/numitem.hxx> +#include <editeng/adjustitem.hxx> +#include <vcl/GraphicObject.hxx> +#include <editeng/svxfont.hxx> +#include <editeng/brushitem.hxx> +#include <svl/itempool.hxx> +#include <libxml/xmlwriter.h> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <o3tl/temporary.hxx> +#include <osl/diagnose.h> + +#include <memory> +using std::advance; + + +// Outliner + + +void Outliner::ImplCheckDepth( sal_Int16& rnDepth ) const +{ + if( rnDepth < gnMinDepth ) + rnDepth = gnMinDepth; + else if( rnDepth > nMaxDepth ) + rnDepth = nMaxDepth; +} + +Paragraph* Outliner::Insert(const OUString& rText, sal_Int32 nAbsPos, sal_Int16 nDepth) +{ + DBG_ASSERT(pParaList->GetParagraphCount(),"Insert:No Paras"); + + Paragraph* pPara; + + ImplCheckDepth( nDepth ); + + sal_Int32 nParagraphCount = pParaList->GetParagraphCount(); + if( nAbsPos > nParagraphCount ) + nAbsPos = nParagraphCount; + + if( bFirstParaIsEmpty ) + { + pPara = pParaList->GetParagraph( 0 ); + if( pPara->GetDepth() != nDepth ) + { + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + pPara->SetDepth( nDepth ); + DepthChangedHdl(pPara, nPrevFlags); + } + pPara->nFlags |= ParaFlag::HOLDDEPTH; + SetText( rText, pPara ); + } + else + { + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + ImplBlockInsertionCallbacks( true ); + pPara = new Paragraph( nDepth ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nAbsPos ); + pEditEngine->InsertParagraph( nAbsPos, OUString() ); + DBG_ASSERT(pPara==pParaList->GetParagraph(nAbsPos),"Insert:Failed"); + ImplInitDepth( nAbsPos, nDepth, false ); + ParagraphInsertedHdl(pPara); + pPara->nFlags |= ParaFlag::HOLDDEPTH; + SetText( rText, pPara ); + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + } + bFirstParaIsEmpty = false; + DBG_ASSERT(pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(),"SetText failed"); + return pPara; +} + + +void Outliner::ParagraphInserted( sal_Int32 nPara ) +{ + + if ( nBlockInsCallback ) + return; + + if( bPasting || pEditEngine->IsInUndo() ) + { + Paragraph* pPara = new Paragraph( -1 ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara ); + if( pEditEngine->IsInUndo() ) + { + pPara->bVisible = true; + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + pPara->SetDepth( rLevel.GetValue() ); + } + } + else + { + sal_Int16 nDepth = -1; + Paragraph* pParaBefore = pParaList->GetParagraph( nPara-1 ); + if ( pParaBefore ) + nDepth = pParaBefore->GetDepth(); + + Paragraph* pPara = new Paragraph( nDepth ); + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nPara ); + + if( !pEditEngine->IsInUndo() ) + { + ImplCalcBulletText( nPara, true, false ); + ParagraphInsertedHdl(pPara); + } + } +} + +void Outliner::ParagraphDeleted( sal_Int32 nPara ) +{ + + if ( nBlockInsCallback || ( nPara == EE_PARA_ALL ) ) + return; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return; + + sal_Int16 nDepth = pPara->GetDepth(); + + if( !pEditEngine->IsInUndo() ) + { + aParaRemovingHdl.Call( { this, pPara } ); + } + + pParaList->Remove( nPara ); + + if( pEditEngine->IsInUndo() || bPasting ) + return; + + pPara = pParaList->GetParagraph( nPara ); + if ( pPara && ( pPara->GetDepth() > nDepth ) ) + { + ImplCalcBulletText( nPara, true, false ); + // Search for next on the this level ... + while ( pPara && pPara->GetDepth() > nDepth ) + pPara = pParaList->GetParagraph( ++nPara ); + } + + if ( pPara && ( pPara->GetDepth() == nDepth ) ) + ImplCalcBulletText( nPara, true, false ); +} + +void Outliner::Init( OutlinerMode nMode ) +{ + nOutlinerMode = nMode; + + Clear(); + + EEControlBits nCtrl = pEditEngine->GetControlWord(); + nCtrl &= ~EEControlBits(EEControlBits::OUTLINER|EEControlBits::OUTLINER2); + + SetMaxDepth( 9 ); + + switch ( GetOutlinerMode() ) + { + case OutlinerMode::TextObject: + case OutlinerMode::TitleObject: + break; + + case OutlinerMode::OutlineObject: + nCtrl |= EEControlBits::OUTLINER2; + break; + case OutlinerMode::OutlineView: + nCtrl |= EEControlBits::OUTLINER; + break; + + default: OSL_FAIL( "Outliner::Init - Invalid Mode!" ); + } + + pEditEngine->SetControlWord( nCtrl ); + + const bool bWasUndoEnabled(IsUndoEnabled()); + EnableUndo(false); + ImplInitDepth( 0, -1, false ); + GetUndoManager().Clear(); + EnableUndo(bWasUndoEnabled); +} + +void Outliner::SetMaxDepth( sal_Int16 nDepth ) +{ + if( nMaxDepth != nDepth ) + { + nMaxDepth = std::min( nDepth, sal_Int16(SVX_MAX_NUM-1) ); + } +} + +sal_Int16 Outliner::GetDepth( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetDepth - Paragraph not found!" ); + return pPara ? pPara->GetDepth() : -1; +} + +void Outliner::SetDepth( Paragraph* pPara, sal_Int16 nNewDepth ) +{ + + ImplCheckDepth( nNewDepth ); + + if ( nNewDepth == pPara->GetDepth() ) + return; + + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + sal_Int32 nPara = GetAbsPos( pPara ); + ImplInitDepth( nPara, nNewDepth, true ); + ImplCalcBulletText( nPara, false, false ); + + if ( GetOutlinerMode() == OutlinerMode::OutlineObject ) + ImplSetLevelDependentStyleSheet( nPara ); + + DepthChangedHdl(pPara, nPrevFlags); +} + +sal_Int16 Outliner::GetNumberingStartValue( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); + return pPara ? pPara->GetNumberingStartValue() : -1; +} + +void Outliner::SetNumberingStartValue( sal_Int32 nPara, sal_Int16 nNumberingStartValue ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::GetNumberingStartValue - Paragraph not found!" ); + if( pPara && pPara->GetNumberingStartValue() != nNumberingStartValue ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara, + pPara->GetNumberingStartValue(), nNumberingStartValue, + pPara->IsParaIsNumberingRestart(), pPara->IsParaIsNumberingRestart() ) ); + + pPara->SetNumberingStartValue( nNumberingStartValue ); + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + pEditEngine->SetModified(); + } +} + +bool Outliner::IsParaIsNumberingRestart( sal_Int32 nPara ) const +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::IsParaIsNumberingRestart - Paragraph not found!" ); + return pPara && pPara->IsParaIsNumberingRestart(); +} + +void Outliner::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + DBG_ASSERT( pPara, "Outliner::SetParaIsNumberingRestart - Paragraph not found!" ); + if( pPara && (pPara->IsParaIsNumberingRestart() != bParaIsNumberingRestart) ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaNumberingRestart>( this, nPara, + pPara->GetNumberingStartValue(), pPara->GetNumberingStartValue(), + pPara->IsParaIsNumberingRestart(), bParaIsNumberingRestart ) ); + + pPara->SetParaIsNumberingRestart( bParaIsNumberingRestart ); + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + pEditEngine->SetModified(); + } +} + +sal_Int32 Outliner::GetBulletsNumberingStatus( + const sal_Int32 nParaStart, + const sal_Int32 nParaEnd ) const +{ + if ( nParaStart > nParaEnd + || nParaEnd >= pParaList->GetParagraphCount() ) + { + SAL_WARN("editeng", "<Outliner::GetBulletsNumberingStatus> - unexpected parameter values" ); + return 2; + } + + sal_Int32 nBulletsCount = 0; + sal_Int32 nNumberingCount = 0; + for (sal_Int32 nPara = nParaStart; nPara <= nParaEnd; ++nPara) + { + if ( !pParaList->GetParagraph(nPara) ) + { + break; + } + const SvxNumberFormat* pFmt = GetNumberFormat(nPara); + if (!pFmt) + { + // At least, exists one paragraph that has no Bullets/Numbering. + break; + } + else if ((pFmt->GetNumberingType() == SVX_NUM_BITMAP) || (pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL)) + { + // Having Bullets in this paragraph. + nBulletsCount++; + } + else + { + // Having Numbering in this paragraph. + nNumberingCount++; + } + } + + const sal_Int32 nParaCount = nParaEnd - nParaStart + 1; + if ( nBulletsCount == nParaCount ) + { + return 0; + } + else if ( nNumberingCount == nParaCount ) + { + return 1; + } + return 2; +} + +sal_Int32 Outliner::GetBulletsNumberingStatus() const +{ + return pParaList->GetParagraphCount() > 0 + ? GetBulletsNumberingStatus( 0, pParaList->GetParagraphCount()-1 ) + : 2; +} + +std::optional<OutlinerParaObject> Outliner::CreateParaObject( sal_Int32 nStartPara, sal_Int32 nCount ) const +{ + if ( static_cast<sal_uInt64>(nStartPara) + nCount > + o3tl::make_unsigned(pParaList->GetParagraphCount()) ) + nCount = pParaList->GetParagraphCount() - nStartPara; + + // When a new OutlinerParaObject is created because a paragraph is just being deleted, + // it can happen that the ParaList is not updated yet... + if ( ( nStartPara + nCount ) > pEditEngine->GetParagraphCount() ) + nCount = pEditEngine->GetParagraphCount() - nStartPara; + + if (nCount <= 0) + return std::nullopt; + + std::unique_ptr<EditTextObject> xText = pEditEngine->CreateTextObject( nStartPara, nCount ); + const bool bIsEditDoc(OutlinerMode::TextObject == GetOutlinerMode()); + ParagraphDataVector aParagraphDataVector(nCount); + const sal_Int32 nLastPara(nStartPara + nCount - 1); + + for(sal_Int32 nPara(nStartPara); nPara <= nLastPara; nPara++) + { + aParagraphDataVector[nPara-nStartPara] = *GetParagraph(nPara); + } + + xText->ClearPortionInfo(); // tdf#147166 the PortionInfo is unwanted here + OutlinerParaObject aPObj(std::move(xText), std::move(aParagraphDataVector), bIsEditDoc); + aPObj.SetOutlinerMode(GetOutlinerMode()); + + return aPObj; +} + +void Outliner::SetToEmptyText() +{ + SetText(GetEmptyParaObject()); +} + +void Outliner::SetText( const OUString& rText, Paragraph* pPara ) +{ + DBG_ASSERT(pPara,"SetText:No Para"); + + const sal_Int32 nPara = pParaList->GetAbsPos( pPara ); + + if (pEditEngine->GetText( nPara ) == rText) + { + // short-circuit logic to improve performance + bFirstParaIsEmpty = false; + return; + } + + const bool bUpdate = pEditEngine->SetUpdateLayout( false ); + ImplBlockInsertionCallbacks( true ); + + if (rText.isEmpty()) + { + pEditEngine->SetText( nPara, rText ); + ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + else + { + const OUString aText(convertLineEnd(rText, LINEEND_LF)); + + sal_Int32 nPos = 0; + sal_Int32 nInsPos = nPara+1; + sal_Int32 nIdx {0}; + // Loop over all tokens, but ignore the last one if empty + // (i.e. if strings ends with the delimiter, detected by + // checking nIdx against string length). This check also + // handle empty strings. + while( nIdx>=0 && nIdx<aText.getLength() ) + { + std::u16string_view aStr = o3tl::getToken(aText, 0, '\x0A', nIdx ); + + sal_Int16 nCurDepth; + if( nPos ) + { + pPara = new Paragraph( -1 ); + nCurDepth = -1; + } + else + nCurDepth = pPara->GetDepth(); + + // In the outliner mode, filter the tabs and set the indentation + // about a LRSpaceItem. In EditEngine mode intend over old tabs + if( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || + ( GetOutlinerMode() == OutlinerMode::OutlineView ) ) + { + // Extract Tabs + size_t nTabs = 0; + while ( ( nTabs < aStr.size() ) && ( aStr[nTabs] == '\t' ) ) + nTabs++; + if ( nTabs ) + aStr = aStr.substr(nTabs); + + // Keep depth? (see Outliner::Insert) + if( !(pPara->nFlags & ParaFlag::HOLDDEPTH) ) + { + nCurDepth = nTabs-1; //TODO: sal_Int32 -> sal_Int16! + ImplCheckDepth( nCurDepth ); + pPara->SetDepth( nCurDepth ); + } + } + if( nPos ) // not with the first paragraph + { + pParaList->Insert( std::unique_ptr<Paragraph>(pPara), nInsPos ); + pEditEngine->InsertParagraph( nInsPos, OUString(aStr) ); + ParagraphInsertedHdl(pPara); + } + else + { + nInsPos--; + pEditEngine->SetText( nInsPos, OUString(aStr) ); + } + ImplInitDepth( nInsPos, nCurDepth, false ); + nInsPos++; + nPos++; + } + } + + DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"SetText failed!"); + bFirstParaIsEmpty = false; + ImplBlockInsertionCallbacks( false ); + // Restore the update mode. + pEditEngine->SetUpdateLayout(bUpdate, /*bRestoring=*/true); +} + +// pView == 0 -> Ignore tabs + +bool Outliner::ImpConvertEdtToOut( sal_Int32 nPara ) +{ + + bool bConverted = false; + sal_Int32 nTabs = 0; + ESelection aDelSel; + + OUString aName; + + OUString aStr( pEditEngine->GetText( nPara ) ); + const sal_Unicode* pPtr = aStr.getStr(); + + sal_Int32 nHeadingNumberStart = 0; + sal_Int32 nNumberingNumberStart = 0; + SfxStyleSheet* pStyle= pEditEngine->GetStyleSheet( nPara ); + if( pStyle ) + { + OUString aHeading_US( "heading" ); + OUString aNumber_US( "Numbering" ); + aName = pStyle->GetName(); + sal_Int32 nSearch; + if ( ( nSearch = aName.indexOf( aHeading_US ) ) != -1 ) + nHeadingNumberStart = nSearch + aHeading_US.getLength(); + else if ( ( nSearch = aName.indexOf( aNumber_US ) ) != -1 ) + nNumberingNumberStart = nSearch + aNumber_US.getLength(); + } + + if ( nHeadingNumberStart || nNumberingNumberStart ) + { + // PowerPoint import ? + if( nHeadingNumberStart && ( aStr.getLength() >= 2 ) && + ( pPtr[0] != '\t' ) && ( pPtr[1] == '\t' ) ) + { + // Extract Bullet and Tab + aDelSel = ESelection( nPara, 0, nPara, 2 ); + } + + sal_Int32 nPos = nHeadingNumberStart ? nHeadingNumberStart : nNumberingNumberStart; + std::u16string_view aLevel = comphelper::string::stripStart(aName.subView(nPos), ' '); + nTabs = o3tl::toInt32(aLevel); + if( nTabs ) + nTabs--; // Level 0 = "heading 1" + bConverted = true; + } + else + { + // filter leading tabs + while( *pPtr == '\t' ) + { + pPtr++; + nTabs++; + } + // Remove tabs from the text + if( nTabs ) + aDelSel = ESelection( nPara, 0, nPara, nTabs ); + } + + if ( aDelSel.HasRange() ) + { + pEditEngine->QuickDelete( aDelSel ); + } + + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + sal_Int16 nOutlLevel = rLevel.GetValue(); + + ImplCheckDepth( nOutlLevel ); + ImplInitDepth( nPara, nOutlLevel, false ); + + return bConverted; +} + +void Outliner::SetText( const OutlinerParaObject& rPObj ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + bool bUndo = pEditEngine->IsUndoEnabled(); + EnableUndo( false ); + + Init( rPObj.GetOutlinerMode() ); + + ImplBlockInsertionCallbacks( true ); + pEditEngine->SetText(rPObj.GetTextObject()); + + bFirstParaIsEmpty = false; + + pParaList->Clear(); + for( sal_Int32 nCurPara = 0; nCurPara < rPObj.Count(); nCurPara++ ) + { + std::unique_ptr<Paragraph> pPara(new Paragraph( rPObj.GetParagraphData(nCurPara))); + ImplCheckDepth( pPara->nDepth ); + + pParaList->Append(std::move(pPara)); + ImplCheckNumBulletItem( nCurPara ); + } + + ImplCheckParagraphs( 0, pParaList->GetParagraphCount() ); + + EnableUndo( bUndo ); + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + + DBG_ASSERT( pParaList->GetParagraphCount()==rPObj.Count(),"SetText failed"); + DBG_ASSERT( pEditEngine->GetParagraphCount()==rPObj.Count(),"SetText failed"); +} + +void Outliner::AddText( const OutlinerParaObject& rPObj, bool bAppend ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + ImplBlockInsertionCallbacks( true ); + sal_Int32 nPara; + if( bFirstParaIsEmpty ) + { + pParaList->Clear(); + pEditEngine->SetText(rPObj.GetTextObject()); + nPara = 0; + bAppend = false; + } + else + { + nPara = pParaList->GetParagraphCount(); + pEditEngine->InsertParagraph( EE_PARA_APPEND, rPObj.GetTextObject(), bAppend ); + } + bFirstParaIsEmpty = false; + + for( sal_Int32 n = 0; n < rPObj.Count(); n++ ) + { + if ( n == 0 && bAppend ) + { + // This first "paragraph" was just appended to an existing (incomplete) paragraph. + // Since no new paragraph will be added, the assumed increase-by-1 also won't happen. + --nPara; + continue; + } + + Paragraph* pPara = new Paragraph( rPObj.GetParagraphData(n) ); + pParaList->Append(std::unique_ptr<Paragraph>(pPara)); + sal_Int32 nP = nPara+n; + DBG_ASSERT(pParaList->GetAbsPos(pPara)==nP,"AddText:Out of sync"); + ImplInitDepth( nP, pPara->GetDepth(), false ); + } + DBG_ASSERT( pEditEngine->GetParagraphCount()==pParaList->GetParagraphCount(), "SetText: OutOfSync" ); + + ImplCheckParagraphs( nPara, pParaList->GetParagraphCount() ); + + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); +} + +OUString Outliner::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle ) +{ + if ( !aCalcFieldValueHdl.IsSet() ) + return OUString( ' ' ); + + EditFieldInfo aFldInfo( this, rField, nPara, nPos ); + // The FldColor is preset with COL_LIGHTGRAY. + if ( rpFldColor ) + aFldInfo.SetFieldColor( *rpFldColor ); + + aCalcFieldValueHdl.Call( &aFldInfo ); + if ( aFldInfo.GetTextColor() ) + { + rpTxtColor = *aFldInfo.GetTextColor(); + } + + if ( aFldInfo.GetFontLineStyle() ) + { + rpFldLineStyle = *aFldInfo.GetFontLineStyle(); + } + + if (aFldInfo.GetFieldColor()) + rpFldColor = *aFldInfo.GetFieldColor(); + else + rpFldColor.reset(); + + return aFldInfo.GetRepresentation(); +} + +void Outliner::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + pEditEngine->SetStyleSheet( nPara, pStyle ); + ImplCheckNumBulletItem( nPara ); + } +} + +void Outliner::ImplCheckNumBulletItem( sal_Int32 nPara ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + pPara->aBulSize.setWidth( -1 ); +} + +void Outliner::ImplSetLevelDependentStyleSheet( sal_Int32 nPara ) +{ + + DBG_ASSERT( ( GetOutlinerMode() == OutlinerMode::OutlineObject ) || ( GetOutlinerMode() == OutlinerMode::OutlineView ), "SetLevelDependentStyleSheet: Wrong Mode!" ); + + SfxStyleSheet* pStyle = GetStyleSheet( nPara ); + + if ( !pStyle ) + return; + + sal_Int16 nDepth = GetDepth( nPara ); + if( nDepth < 0 ) + nDepth = 0; + + OUString aNewStyleSheetName( pStyle->GetName() ); + aNewStyleSheetName = aNewStyleSheetName.subView( 0, aNewStyleSheetName.getLength()-1 ) + + OUString::number( nDepth+1 ); + SfxStyleSheet* pNewStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( aNewStyleSheetName, pStyle->GetFamily() )); + DBG_ASSERT( pNewStyle, "AutoStyleSheetName - Style not found!" ); + if ( pNewStyle && ( pNewStyle != GetStyleSheet( nPara ) ) ) + { + SfxItemSet aOldAttrs( GetParaAttribs( nPara ) ); + SetStyleSheet( nPara, pNewStyle ); + if ( aOldAttrs.GetItemState( EE_PARA_NUMBULLET ) == SfxItemState::SET ) + { + SfxItemSet aAttrs( GetParaAttribs( nPara ) ); + aAttrs.Put( aOldAttrs.Get( EE_PARA_NUMBULLET ) ); + SetParaAttribs( nPara, aAttrs ); + } + } +} + +void Outliner::ImplInitDepth( sal_Int32 nPara, sal_Int16 nDepth, bool bCreateUndo ) +{ + + DBG_ASSERT( ( nDepth >= gnMinDepth ) && ( nDepth <= nMaxDepth ), "ImplInitDepth - Depth is invalid!" ); + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return; + sal_Int16 nOldDepth = pPara->GetDepth(); + pPara->SetDepth( nDepth ); + + // For IsInUndo attributes and style do not have to be set, there + // the old values are restored by the EditEngine. + if( IsInUndo() ) + return; + + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + bool bUndo = bCreateUndo && IsUndoEnabled(); + + SfxItemSet aAttrs( pEditEngine->GetParaAttribs( nPara ) ); + aAttrs.Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nDepth ) ); + pEditEngine->SetParaAttribs( nPara, aAttrs ); + ImplCheckNumBulletItem( nPara ); + ImplCalcBulletText( nPara, false, false ); + + if ( bUndo ) + { + InsertUndo( std::make_unique<OutlinerUndoChangeDepth>( this, nPara, nOldDepth, nDepth ) ); + } + + pEditEngine->SetUpdateLayout( bUpdate ); +} + +void Outliner::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + + pEditEngine->SetParaAttribs( nPara, rSet ); +} + +void Outliner::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet) +{ + pEditEngine->SetCharAttribs(nPara, rSet); +} + +bool Outliner::Expand( Paragraph const * pPara ) +{ + if ( !pParaList->HasHiddenChildren( pPara ) ) + return false; + + std::unique_ptr<OLUndoExpand> pUndo; + bool bUndo = IsUndoEnabled() && !IsInUndo(); + if( bUndo ) + { + UndoActionStart( OLUNDO_EXPAND ); + pUndo.reset( new OLUndoExpand( this, OLUNDO_EXPAND ) ); + pUndo->nCount = pParaList->GetAbsPos( pPara ); + } + pParaList->Expand( pPara ); + InvalidateBullet(pParaList->GetAbsPos(pPara)); + if( bUndo ) + { + InsertUndo( std::move(pUndo) ); + UndoActionEnd(); + } + return true; +} + +bool Outliner::Collapse( Paragraph const * pPara ) +{ + if ( !pParaList->HasVisibleChildren( pPara ) ) // collapsed + return false; + + std::unique_ptr<OLUndoExpand> pUndo; + bool bUndo = false; + + if( !IsInUndo() && IsUndoEnabled() ) + bUndo = true; + if( bUndo ) + { + UndoActionStart( OLUNDO_COLLAPSE ); + pUndo.reset( new OLUndoExpand( this, OLUNDO_COLLAPSE ) ); + pUndo->nCount = pParaList->GetAbsPos( pPara ); + } + + pParaList->Collapse( pPara ); + InvalidateBullet(pParaList->GetAbsPos(pPara)); + if( bUndo ) + { + InsertUndo( std::move(pUndo) ); + UndoActionEnd(); + } + return true; +} + +vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + DBG_ASSERT( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ), "ImpCalcBulletFont: Missing or BitmapBullet!" ); + + vcl::Font aStdFont; + if ( !pEditEngine->IsFlatMode() ) + { + ESelection aSel( nPara, 0, nPara, 0 ); + aStdFont = EditEngine::CreateFontFromItemSet( pEditEngine->GetAttribs( aSel ), pEditEngine->GetScriptType( aSel ) ); + } + else + { + aStdFont = pEditEngine->GetStandardFont( nPara ); + } + + vcl::Font aBulletFont; + std::optional<vcl::Font> pSourceFont; + if ( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) + { + pSourceFont = pFmt->GetBulletFont(); + } + + if (pSourceFont) + { + aBulletFont = *pSourceFont; + } + else + { + aBulletFont = aStdFont; + aBulletFont.SetUnderline( LINESTYLE_NONE ); + aBulletFont.SetOverline( LINESTYLE_NONE ); + aBulletFont.SetStrikeout( STRIKEOUT_NONE ); + aBulletFont.SetEmphasisMark( FontEmphasisMark::NONE ); + aBulletFont.SetRelief( FontRelief::NONE ); + } + + // Use original scale... + double nStretchY = 100.0; + getGlobalScale(o3tl::temporary(double()), nStretchY, o3tl::temporary(double()), o3tl::temporary(double())); + + double fScale = pFmt->GetBulletRelSize() * nStretchY / 100.0; + double fScaledLineHeight = aStdFont.GetFontSize().Height(); + fScaledLineHeight *= fScale * 10; + fScaledLineHeight /= 1000.0; + + aBulletFont.SetAlignment( ALIGN_BOTTOM ); + aBulletFont.SetFontSize(Size(0, basegfx::fround(fScaledLineHeight))); + bool bVertical = IsVertical(); + aBulletFont.SetVertical( bVertical ); + aBulletFont.SetOrientation( Degree10(bVertical ? (IsTopToBottom() ? 2700 : 900) : 0) ); + + Color aColor( COL_AUTO ); + if( !pEditEngine->IsFlatMode() && !( pEditEngine->GetControlWord() & EEControlBits::NOCOLORS ) ) + { + aColor = pFmt->GetBulletColor(); + } + + if ( ( aColor == COL_AUTO ) || ( IsForceAutoColor() ) ) + aColor = pEditEngine->GetAutoColor(); + + aBulletFont.SetColor( aColor ); + return aBulletFont; +} + +void Outliner::PaintBullet(sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin, + Degree10 nOrientation, OutputDevice& rOutDev) +{ + + bool bDrawBullet = false; + if (pEditEngine) + { + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); + bDrawBullet = rBulletState.GetValue(); + } + + if (!(bDrawBullet && ImplHasNumberFormat(nPara))) + return; + + bool bVertical = IsVertical(); + bool bTopToBottom = IsTopToBottom(); + + bool bRightToLeftPara = pEditEngine->IsRightToLeft( nPara ); + + tools::Rectangle aBulletArea( ImpCalcBulletArea( nPara, true, false ) ); + + double nStretchX = 100.0; + getGlobalScale(o3tl::temporary(double()), o3tl::temporary(double()), + nStretchX, o3tl::temporary(double())); + + tools::Long nStretchBulletX = basegfx::fround(double(aBulletArea.Left()) * nStretchX / 100.0); + tools::Long nStretchBulletWidth = basegfx::fround(double(aBulletArea.GetWidth()) * nStretchX / 100.0); + aBulletArea = tools::Rectangle(Point(nStretchBulletX, aBulletArea.Top()), + Size(nStretchBulletWidth, aBulletArea.GetHeight()) ); + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if ( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) ) + { + if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + // Use baseline + bool bSymbol = pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL; + aBulletFont.SetAlignment( bSymbol ? ALIGN_BOTTOM : ALIGN_BASELINE ); + vcl::Font aOldFont = rOutDev.GetFont(); + rOutDev.SetFont( aBulletFont ); + + ParagraphInfos aParaInfos = pEditEngine->GetParagraphInfos( nPara ); + Point aTextPos; + if ( !bVertical ) + { +// aTextPos.Y() = rStartPos.Y() + aBulletArea.Bottom(); + aTextPos.setY( rStartPos.Y() + ( bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent ) ); + if ( !bRightToLeftPara ) + aTextPos.setX( rStartPos.X() + aBulletArea.Left() ); + else + aTextPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); + } + else + { + if (bTopToBottom) + { +// aTextPos.X() = rStartPos.X() - aBulletArea.Bottom(); + aTextPos.setX( rStartPos.X() - (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); + aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + else + { + aTextPos.setX( rStartPos.X() + (bSymbol ? aBulletArea.Bottom() : aParaInfos.nFirstLineMaxAscent) ); + aTextPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + } + + if ( nOrientation ) + { + // Both TopLeft and bottom left is not quite correct, + // since in EditEngine baseline ... + rOrigin.RotateAround(aTextPos, nOrientation); + + vcl::Font aRotatedFont( aBulletFont ); + aRotatedFont.SetOrientation( nOrientation ); + rOutDev.SetFont( aRotatedFont ); + } + + // VCL will take care of brackets and so on... + vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode(); + nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags(vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong); + if ( bRightToLeftPara ) + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + rOutDev.SetLayoutMode( nLayoutMode ); + + if(bStrippingPortions) + { + const vcl::Font& aSvxFont(rOutDev.GetFont()); + KernArray aBuf; + rOutDev.GetTextArray( pPara->GetText(), &aBuf ); + + if(bSymbol) + { + // aTextPos is Bottom, go to Baseline + FontMetric aMetric(rOutDev.GetFontMetric()); + aTextPos.AdjustY( -(aMetric.GetDescent()) ); + } + + assert(aBuf.get_factor() == 1); + DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf.get_subunit_array(), {}, + aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, nullptr, false, false, true, nullptr, Color(), Color()); + } + else + { + rOutDev.DrawText( aTextPos, pPara->GetText() ); + } + + rOutDev.SetFont( aOldFont ); + } + else + { + if ( pFmt->GetBrush()->GetGraphicObject() ) + { + Point aBulletPos; + if ( !bVertical ) + { + aBulletPos.setY( rStartPos.Y() + aBulletArea.Top() ); + if ( !bRightToLeftPara ) + aBulletPos.setX( rStartPos.X() + aBulletArea.Left() ); + else + aBulletPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Right() ); + } + else + { + if (bTopToBottom) + { + aBulletPos.setX( rStartPos.X() - aBulletArea.Bottom() ); + aBulletPos.setY( rStartPos.Y() + aBulletArea.Left() ); + } + else + { + aBulletPos.setX( rStartPos.X() + aBulletArea.Top() ); + aBulletPos.setY( rStartPos.Y() - aBulletArea.Right() ); + } + } + + if(bStrippingPortions) + { + if(aDrawBulletHdl.IsSet()) + { + // call something analog to aDrawPortionHdl (if set) and feed it something + // analog to DrawPortionInfo... + // created aDrawBulletHdl, Set/GetDrawBulletHdl. + // created DrawBulletInfo and added handling to sdrtextdecomposition.cxx + DrawBulletInfo aDrawBulletInfo( + *pFmt->GetBrush()->GetGraphicObject(), + aBulletPos, + pPara->aBulSize); + + aDrawBulletHdl.Call(&aDrawBulletInfo); + } + } + else + { + pFmt->GetBrush()->GetGraphicObject()->Draw(rOutDev, aBulletPos, pPara->aBulSize); + } + } + } + } + + // In case of collapsed subparagraphs paint a line before the text. + if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) || + bStrippingPortions || nOrientation ) + return; + + tools::Long nWidth = rOutDev.PixelToLogic( Size( 10, 0 ) ).Width(); + + Point aStartPos, aEndPos; + if ( !bVertical ) + { + aStartPos.setY( rStartPos.Y() + aBulletArea.Bottom() ); + if ( !bRightToLeftPara ) + aStartPos.setX( rStartPos.X() + aBulletArea.Right() ); + else + aStartPos.setX( rStartPos.X() + GetPaperSize().Width() - aBulletArea.Left() ); + aEndPos = aStartPos; + aEndPos.AdjustX(nWidth ); + } + else + { + aStartPos.setX( rStartPos.X() - aBulletArea.Bottom() ); + aStartPos.setY( rStartPos.Y() + aBulletArea.Right() ); + aEndPos = aStartPos; + aEndPos.AdjustY(nWidth ); + } + + const Color& rOldLineColor = rOutDev.GetLineColor(); + rOutDev.SetLineColor( COL_BLACK ); + rOutDev.DrawLine( aStartPos, aEndPos ); + rOutDev.SetLineColor( rOldLineColor ); +} + +void Outliner::InvalidateBullet(sal_Int32 nPara) +{ + tools::Long nLineHeight = static_cast<tools::Long>(pEditEngine->GetLineHeight(nPara )); + for (OutlinerView* pView : aViewList) + { + Point aPos( pView->pEditView->GetWindowPosTopLeft(nPara ) ); + tools::Rectangle aRect( pView->GetOutputArea() ); + aRect.SetRight( aPos.X() ); + aRect.SetTop( aPos.Y() ); + aRect.SetBottom( aPos.Y() ); + aRect.AdjustBottom(nLineHeight ); + + pView->pEditView->InvalidateWindow(aRect); + } +} + +ErrCode Outliner::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + + bool bOldUndo = pEditEngine->IsUndoEnabled(); + EnableUndo( false ); + + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + Clear(); + + ImplBlockInsertionCallbacks( true ); + ErrCode nRet = pEditEngine->Read( rInput, rBaseURL, eFormat, pHTTPHeaderAttrs ); + + bFirstParaIsEmpty = false; + + sal_Int32 nParas = pEditEngine->GetParagraphCount(); + pParaList->Clear(); + for ( sal_Int32 n = 0; n < nParas; n++ ) + { + std::unique_ptr<Paragraph> pPara(new Paragraph( 0 )); + pParaList->Append(std::move(pPara)); + } + + ImpFilterIndents( 0, nParas-1 ); + + ImplBlockInsertionCallbacks( false ); + pEditEngine->SetUpdateLayout( bUpdate ); + EnableUndo( bOldUndo ); + + return nRet; +} + + +void Outliner::ImpFilterIndents( sal_Int32 nFirstPara, sal_Int32 nLastPara ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + Paragraph* pLastConverted = nullptr; + for( sal_Int32 nPara = nFirstPara; nPara <= nLastPara; nPara++ ) + { + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + if( ImpConvertEdtToOut( nPara ) ) + { + pLastConverted = pPara; + } + else if ( pLastConverted ) + { + // Arrange normal paragraphs below the heading ... + pPara->SetDepth( pLastConverted->GetDepth() ); + } + + ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + } + + pEditEngine->SetUpdateLayout( bUpdate ); +} + +EditUndoManager& Outliner::GetUndoManager() +{ + return pEditEngine->GetUndoManager(); +} + +EditUndoManager* Outliner::SetUndoManager(EditUndoManager* pNew) +{ + return pEditEngine->SetUndoManager(pNew); +} + +void Outliner::ImpTextPasted( sal_Int32 nStartPara, sal_Int32 nCount ) +{ + bool bUpdate = pEditEngine->SetUpdateLayout( false ); + + const sal_Int32 nStart = nStartPara; + + Paragraph* pPara = pParaList->GetParagraph( nStartPara ); + + while( nCount && pPara ) + { + if( GetOutlinerMode() != OutlinerMode::TextObject ) + { + nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + ImpConvertEdtToOut( nStartPara ); + + if( nStartPara == nStart ) + { + // the existing paragraph has changed depth or flags + if( (pPara->GetDepth() != nDepthChangedHdlPrevDepth) || (pPara->nFlags != nPrevFlags) ) + DepthChangedHdl(pPara, nPrevFlags); + } + } + else // EditEngine mode + { + sal_Int16 nDepth = -1; + const SfxItemSet& rAttrs = pEditEngine->GetParaAttribs( nStartPara ); + if ( rAttrs.GetItemState( EE_PARA_OUTLLEVEL ) == SfxItemState::SET ) + { + const SfxInt16Item& rLevel = rAttrs.Get( EE_PARA_OUTLLEVEL ); + nDepth = rLevel.GetValue(); + } + if ( nDepth != GetDepth( nStartPara ) ) + ImplInitDepth( nStartPara, nDepth, false ); + } + + nCount--; + nStartPara++; + pPara = pParaList->GetParagraph( nStartPara ); + } + + pEditEngine->SetUpdateLayout( bUpdate ); + + DBG_ASSERT(pParaList->GetParagraphCount()==pEditEngine->GetParagraphCount(),"ImpTextPasted failed"); +} + +bool Outliner::IndentingPagesHdl( OutlinerView* pView ) +{ + if( !aIndentingPagesHdl.IsSet() ) + return true; + return aIndentingPagesHdl.Call( pView ); +} + +bool Outliner::ImpCanIndentSelectedPages( OutlinerView* pCurView ) +{ + // The selected pages must already be set in advance through + // ImpCalcSelectedPages + + // If the first paragraph is on level 0 it can not indented in any case, + // possible there might be indentations in the following on the 0 level. + if ( ( mnFirstSelPage == 0 ) && ( GetOutlinerMode() != OutlinerMode::TextObject ) ) + { + if ( nDepthChangedHdlPrevDepth == 1 ) // is the only page + return false; + else + (void)pCurView->ImpCalcSelectedPages( false ); // without the first + } + return IndentingPagesHdl( pCurView ); +} + + +bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView ) +{ + // The selected pages must already be set in advance through + // ImpCalcSelectedPages + return RemovingPagesHdl( pCurView ); +} + +Outliner::Outliner(SfxItemPool* pPool, OutlinerMode nMode) + : mnFirstSelPage(0) + , nDepthChangedHdlPrevDepth(0) + , nMaxDepth(9) + , bFirstParaIsEmpty(true) + , nBlockInsCallback(0) + , bStrippingPortions(false) + , bPasting(false) +{ + + pParaList.reset( new ParagraphList ); + pParaList->SetVisibleStateChangedHdl( LINK( this, Outliner, ParaVisibleStateChangedHdl ) ); + std::unique_ptr<Paragraph> pPara(new Paragraph( 0 )); + pParaList->Append(std::move(pPara)); + + pEditEngine.reset( new OutlinerEditEng( this, pPool ) ); + pEditEngine->SetBeginMovingParagraphsHdl( LINK( this, Outliner, BeginMovingParagraphsHdl ) ); + pEditEngine->SetEndMovingParagraphsHdl( LINK( this, Outliner, EndMovingParagraphsHdl ) ); + pEditEngine->SetBeginPasteOrDropHdl( LINK( this, Outliner, BeginPasteOrDropHdl ) ); + pEditEngine->SetEndPasteOrDropHdl( LINK( this, Outliner, EndPasteOrDropHdl ) ); + + Init( nMode ); +} + +Outliner::~Outliner() +{ + pParaList->Clear(); + pParaList.reset(); + pEditEngine.reset(); +} + +size_t Outliner::InsertView( OutlinerView* pView, size_t nIndex ) +{ + size_t ActualIndex; + + if ( nIndex >= aViewList.size() ) + { + aViewList.push_back( pView ); + ActualIndex = aViewList.size() - 1; + } + else + { + ViewList::iterator it = aViewList.begin(); + advance( it, nIndex ); + ActualIndex = nIndex; + } + pEditEngine->InsertView( pView->pEditView.get(), nIndex ); + return ActualIndex; +} + +void Outliner::RemoveView( OutlinerView const * pView ) +{ + ViewList::iterator it = std::find(aViewList.begin(), aViewList.end(), pView); + if (it != aViewList.end()) + { + pView->pEditView->HideCursor(); // HACK + pEditEngine->RemoveView( pView->pEditView.get() ); + aViewList.erase( it ); + } +} + +void Outliner::RemoveView( size_t nIndex ) +{ + EditView* pEditView = pEditEngine->GetView( nIndex ); + pEditView->HideCursor(); // HACK + + pEditEngine->RemoveView( nIndex ); + + { + ViewList::iterator it = aViewList.begin(); + advance( it, nIndex ); + aViewList.erase( it ); + } +} + + +OutlinerView* Outliner::GetView( size_t nIndex ) const +{ + return ( nIndex >= aViewList.size() ) ? nullptr : aViewList[ nIndex ]; +} + +size_t Outliner::GetViewCount() const +{ + return aViewList.size(); +} + +void Outliner::ParagraphInsertedHdl(Paragraph* pPara) +{ + if( !IsInUndo() ) + aParaInsertedHdl.Call( { this, pPara } ); +} + + +void Outliner::DepthChangedHdl(Paragraph* pPara, ParaFlag nPrevFlags) +{ + if( !IsInUndo() ) + aDepthChangedHdl.Call( { this, pPara, nPrevFlags } ); +} + + +sal_Int32 Outliner::GetAbsPos( Paragraph const * pPara ) const +{ + DBG_ASSERT(pPara,"GetAbsPos:No Para"); + return pParaList->GetAbsPos( pPara ); +} + +sal_Int32 Outliner::GetParagraphCount() const +{ + return pParaList->GetParagraphCount(); +} + +Paragraph* Outliner::GetParagraph( sal_Int32 nAbsPos ) const +{ + return pParaList->GetParagraph( nAbsPos ); +} + +bool Outliner::HasChildren( Paragraph const * pParagraph ) const +{ + return pParaList->HasChildren( pParagraph ); +} + +bool Outliner::ImplHasNumberFormat( sal_Int32 nPara ) const +{ + return GetNumberFormat(nPara) != nullptr; +} + +const SvxNumberFormat* Outliner::GetNumberFormat( sal_Int32 nPara ) const +{ + const SvxNumberFormat* pFmt = nullptr; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return nullptr; + + sal_Int16 nDepth = pPara->GetDepth(); + + if( nDepth >= 0 ) + { + const SvxNumBulletItem& rNumBullet = pEditEngine->GetParaAttrib( nPara, EE_PARA_NUMBULLET ); + if ( rNumBullet.GetNumRule().GetLevelCount() > nDepth ) + pFmt = rNumBullet.GetNumRule().Get( nDepth ); + } + + return pFmt; +} + +Size Outliner::ImplGetBulletSize( sal_Int32 nPara ) +{ + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (!pPara) + return Size(); + + if( pPara->aBulSize.Width() == -1 ) + { + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + DBG_ASSERT( pFmt, "ImplGetBulletSize - no Bullet!" ); + + if ( pFmt->GetNumberingType() == SVX_NUM_NUMBER_NONE ) + { + pPara->aBulSize = Size( 0, 0 ); + } + else if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + OUString aBulletText = ImplGetBulletText( nPara ); + OutputDevice* pRefDev = pEditEngine->GetRefDevice(); + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + vcl::Font aRefFont( pRefDev->GetFont()); + pRefDev->SetFont( aBulletFont ); + pPara->aBulSize.setWidth( pRefDev->GetTextWidth( aBulletText ) ); + pPara->aBulSize.setHeight( pRefDev->GetTextHeight() ); + pRefDev->SetFont( aRefFont ); + } + else + { + pPara->aBulSize = OutputDevice::LogicToLogic(pFmt->GetGraphicSize(), + MapMode(MapUnit::Map100thMM), + pEditEngine->GetRefDevice()->GetMapMode()); + } + } + + return pPara->aBulSize; +} + +void Outliner::ImplCheckParagraphs( sal_Int32 nStart, sal_Int32 nEnd ) +{ + + for ( sal_Int32 n = nStart; n < nEnd; n++ ) + { + Paragraph* pPara = pParaList->GetParagraph( n ); + if (pPara) + { + pPara->Invalidate(); + ImplCalcBulletText( n, false, false ); + } + } +} + +void Outliner::SetRefDevice( OutputDevice* pRefDev ) +{ + pEditEngine->SetRefDevice( pRefDev ); + for ( sal_Int32 n = pParaList->GetParagraphCount(); n; ) + { + Paragraph* pPara = pParaList->GetParagraph( --n ); + pPara->Invalidate(); + } +} + +void Outliner::ParaAttribsChanged( sal_Int32 nPara ) +{ + // The Outliner does not have an undo of its own, when paragraphs are + // separated/merged. When ParagraphInserted the attribute EE_PARA_OUTLLEVEL + // may not be set, this is however needed when the depth of the paragraph + // is to be determined. + if (!pEditEngine->IsInUndo()) + return; + if (pParaList->GetParagraphCount() != pEditEngine->GetParagraphCount()) + return; + Paragraph* pPara = pParaList->GetParagraph(nPara); + if (!pPara) + return; + // tdf#100734: force update of bullet + pPara->Invalidate(); + const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); + if (pPara->GetDepth() == rLevel.GetValue()) + return; + pPara->SetDepth(rLevel.GetValue()); + ImplCalcBulletText(nPara, true, true); +} + +void Outliner::StyleSheetChanged( SfxStyleSheet const * pStyle ) +{ + + // The EditEngine calls StyleSheetChanged also for derived styles. + // Here all the paragraphs, which had the said template, used to be + // hunted by an ImpRecalcParaAttribs, why? + // => only the Bullet-representation can really change... + sal_Int32 nParas = pParaList->GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParas; nPara++ ) + { + if ( pEditEngine->GetStyleSheet( nPara ) == pStyle ) + { + ImplCheckNumBulletItem( nPara ); + ImplCalcBulletText( nPara, false, false ); + // EditEngine formats changed paragraphs before calling this method, + // so they are not reformatted now and use wrong bullet indent + pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + } + } +} + +tools::Rectangle Outliner::ImpCalcBulletArea( sal_Int32 nPara, bool bAdjust, bool bReturnPaperPos ) +{ + // Bullet area within the paragraph ... + tools::Rectangle aBulletArea; + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if ( pFmt ) + { + Point aTopLeft; + Size aBulletSize( ImplGetBulletSize( nPara ) ); + + bool bOutlineMode = bool( pEditEngine->GetControlWord() & EEControlBits::OUTLINER ); + + // the ODF attribute text:space-before which holds the spacing to add to the left of the label + const auto nSpaceBefore = pFmt->GetAbsLSpace() + pFmt->GetFirstLineOffset(); + + const SvxLRSpaceItem& rLR = pEditEngine->GetParaAttrib( nPara, bOutlineMode ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); + aTopLeft.setX( rLR.GetTextLeft() + rLR.GetTextFirstLineOffset() + nSpaceBefore ); + + tools::Long nBulletWidth = std::max( static_cast<tools::Long>(-rLR.GetTextFirstLineOffset()), static_cast<tools::Long>((-pFmt->GetFirstLineOffset()) + pFmt->GetCharTextDistance()) ); + if ( nBulletWidth < aBulletSize.Width() ) // The Bullet creates its space + nBulletWidth = aBulletSize.Width(); + + if ( bAdjust && !bOutlineMode ) + { + // Adjust when centered or align right + const SvxAdjustItem& rItem = pEditEngine->GetParaAttrib( nPara, EE_PARA_JUST ); + if ( ( !pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Left ) ) || + ( pEditEngine->IsRightToLeft( nPara ) && ( rItem.GetAdjust() != SvxAdjust::Right ) ) ) + { + aTopLeft.setX( pEditEngine->GetFirstLineStartX( nPara ) - nBulletWidth ); + } + } + + // Vertical: + ParagraphInfos aInfos = pEditEngine->GetParagraphInfos( nPara ); + if ( aInfos.bValid ) + { + aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset is already added to the StartPos (PaintBullet) from the EditEngine + aInfos.nFirstLineHeight - aInfos.nFirstLineTextHeight + + aInfos.nFirstLineTextHeight / 2 + - aBulletSize.Height() / 2 ); + // may prefer to print out on the baseline ... + if( ( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) && ( pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) + { + vcl::Font aBulletFont( ImpCalcBulletFont( nPara ) ); + if ( aBulletFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ) + { + OutputDevice* pRefDev = pEditEngine->GetRefDevice(); + vcl::Font aOldFont = pRefDev->GetFont(); + pRefDev->SetFont( aBulletFont ); + FontMetric aMetric( pRefDev->GetFontMetric() ); + // Leading on the first line ... + aTopLeft.setY( /* aInfos.nFirstLineOffset + */ aInfos.nFirstLineMaxAscent ); + aTopLeft.AdjustY( -(aMetric.GetAscent()) ); + pRefDev->SetFont( aOldFont ); + } + } + } + + // Horizontal: + if( pFmt->GetNumAdjust() == SvxAdjust::Right ) + { + aTopLeft.AdjustX(nBulletWidth - aBulletSize.Width() ); + } + else if( pFmt->GetNumAdjust() == SvxAdjust::Center ) + { + aTopLeft.AdjustX(( nBulletWidth - aBulletSize.Width() ) / 2 ); + } + + if ( aTopLeft.X() < 0 ) // then push + aTopLeft.setX( 0 ); + + aBulletArea = tools::Rectangle( aTopLeft, aBulletSize ); + } + if ( bReturnPaperPos ) + { + Size aBulletSize( aBulletArea.GetSize() ); + Point aBulletDocPos( aBulletArea.TopLeft() ); + aBulletDocPos.AdjustY(pEditEngine->GetDocPosTopLeft( nPara ).Y() ); + Point aBulletPos( aBulletDocPos ); + + if ( IsVertical() ) + { + aBulletPos.setY( aBulletDocPos.X() ); + aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.Y() ); + // Rotate: + aBulletPos.AdjustX( -(aBulletSize.Height()) ); + Size aSz( aBulletSize ); + aBulletSize.setWidth( aSz.Height() ); + aBulletSize.setHeight( aSz.Width() ); + } + else if ( pEditEngine->IsRightToLeft( nPara ) ) + { + aBulletPos.setX( GetPaperSize().Width() - aBulletDocPos.X() - aBulletSize.Width() ); + } + + aBulletArea = tools::Rectangle( aBulletPos, aBulletSize ); + } + return aBulletArea; +} + +EBulletInfo Outliner::GetBulletInfo( sal_Int32 nPara ) +{ + EBulletInfo aInfo; + + aInfo.nParagraph = nPara; + aInfo.bVisible = ImplHasNumberFormat( nPara ); + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + aInfo.nType = pFmt ? pFmt->GetNumberingType() : 0; + + if( pFmt ) + { + if( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) + { + aInfo.aText = ImplGetBulletText( nPara ); + + if( pFmt->GetBulletFont() ) + aInfo.aFont = *pFmt->GetBulletFont(); + } + } + + if ( aInfo.bVisible ) + { + aInfo.aBounds = ImpCalcBulletArea( nPara, true, true ); + } + + return aInfo; +} + +OUString Outliner::GetText( Paragraph const * pParagraph, sal_Int32 nCount ) const +{ + + OUStringBuffer aText(128); + sal_Int32 nStartPara = pParaList->GetAbsPos( pParagraph ); + for ( sal_Int32 n = 0; n < nCount; n++ ) + { + aText.append(pEditEngine->GetText( nStartPara + n )); + if ( (n+1) < nCount ) + aText.append("\n"); + } + return aText.makeStringAndClear(); +} + +void Outliner::Remove( Paragraph const * pPara, sal_Int32 nParaCount ) +{ + + sal_Int32 nPos = pParaList->GetAbsPos( pPara ); + if( !nPos && ( nParaCount >= pParaList->GetParagraphCount() ) ) + { + Clear(); + } + else + { + for( sal_Int32 n = 0; n < nParaCount; n++ ) + pEditEngine->RemoveParagraph( nPos ); + } +} + +void Outliner::StripPortions() +{ + bStrippingPortions = true; + pEditEngine->StripPortions(); + bStrippingPortions = false; +} + +void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, + sal_Int32 nTextLen, std::span<const sal_Int32> pDXArray, + std::span<const sal_Bool> pKashidaArray, const SvxFont& rFont, + sal_Int32 nPara, sal_uInt8 nRightToLeft, + const EEngineData::WrongSpellVector* pWrongSpellVector, + const SvxFieldData* pFieldData, + bool bEndOfLine, + bool bEndOfParagraph, + bool bEndOfBullet, + const css::lang::Locale* pLocale, + const Color& rOverlineColor, + const Color& rTextLineColor) +{ + if(aDrawPortionHdl.IsSet()) + { + DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pKashidaArray, pWrongSpellVector, + pFieldData, pLocale, rOverlineColor, rTextLineColor, nRightToLeft, false, 0, bEndOfLine, bEndOfParagraph, bEndOfBullet); + + aDrawPortionHdl.Call( &aInfo ); + } +} + +void Outliner::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUString& rChar, const SvxFont& rFont, + sal_Int32 nPara, sal_uInt8 nRightToLeft, bool bEndOfLine, bool bEndOfParagraph, + const Color& rOverlineColor, const Color& rTextLineColor) +{ + if(aDrawPortionHdl.IsSet()) + { + DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, {}, nullptr, + nullptr, nullptr, rOverlineColor, rTextLineColor, nRightToLeft, true, nWidth, bEndOfLine, bEndOfParagraph, false); + + aDrawPortionHdl.Call( &aInfo ); + } +} + +bool Outliner::RemovingPagesHdl( OutlinerView* pView ) +{ + return !aRemovingPagesHdl.IsSet() || aRemovingPagesHdl.Call( pView ); +} + +bool Outliner::ImpCanDeleteSelectedPages( OutlinerView* pCurView, sal_Int32 _nFirstPage, sal_Int32 nPages ) +{ + + nDepthChangedHdlPrevDepth = nPages; + mnFirstSelPage = _nFirstPage; + return RemovingPagesHdl( pCurView ); +} + +SfxItemSet const & Outliner::GetParaAttribs( sal_Int32 nPara ) const +{ + return pEditEngine->GetParaAttribs( nPara ); +} + +IMPL_LINK( Outliner, ParaVisibleStateChangedHdl, Paragraph&, rPara, void ) +{ + sal_Int32 nPara = pParaList->GetAbsPos( &rPara ); + pEditEngine->ShowParagraph( nPara, rPara.IsVisible() ); +} + +IMPL_LINK_NOARG(Outliner, BeginMovingParagraphsHdl, MoveParagraphsInfo&, void) +{ + if( !IsInUndo() ) + aBeginMovingHdl.Call( this ); +} + +IMPL_LINK( Outliner, BeginPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) +{ + UndoActionStart( EDITUNDO_DRAGANDDROP ); + maBeginPasteOrDropHdl.Call(&rInfos); +} + +IMPL_LINK( Outliner, EndPasteOrDropHdl, PasteOrDropInfos&, rInfos, void ) +{ + bPasting = false; + ImpTextPasted( rInfos.nStartPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); + maEndPasteOrDropHdl.Call( &rInfos ); + UndoActionEnd(); +} + +IMPL_LINK( Outliner, EndMovingParagraphsHdl, MoveParagraphsInfo&, rInfos, void ) +{ + pParaList->MoveParagraphs( rInfos.nStartPara, rInfos.nDestPara, rInfos.nEndPara - rInfos.nStartPara + 1 ); + sal_Int32 nChangesStart = std::min( rInfos.nStartPara, rInfos.nDestPara ); + sal_Int32 nParas = pParaList->GetParagraphCount(); + for ( sal_Int32 n = nChangesStart; n < nParas; n++ ) + ImplCalcBulletText( n, false, false ); + + if( !IsInUndo() ) + aEndMovingHdl.Call( this ); +} + +static bool isSameNumbering( const SvxNumberFormat& rN1, const SvxNumberFormat& rN2 ) +{ + if( rN1.GetNumberingType() != rN2.GetNumberingType() ) + return false; + + if( rN1.GetNumStr(1) != rN2.GetNumStr(1) ) + return false; + + if( (rN1.GetPrefix() != rN2.GetPrefix()) || (rN1.GetSuffix() != rN2.GetSuffix()) ) + return false; + + return true; +} + +sal_uInt16 Outliner::ImplGetNumbering( sal_Int32 nPara, const SvxNumberFormat* pParaFmt ) +{ + sal_uInt16 nNumber = pParaFmt->GetStart() - 1; + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + const sal_Int16 nParaDepth = pPara->GetDepth(); + + do + { + pPara = pParaList->GetParagraph( nPara ); + const sal_Int16 nDepth = pPara->GetDepth(); + + // ignore paragraphs that are below our paragraph or have no numbering + if( (nDepth > nParaDepth) || (nDepth == -1) ) + continue; + + // stop on paragraphs that are above our paragraph + if( nDepth < nParaDepth ) + break; + + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + + if( pFmt == nullptr ) + continue; // ignore paragraphs without bullets + + // check if numbering less than or equal to pParaFmt + if( !isSameNumbering( *pFmt, *pParaFmt ) || ( pFmt->GetStart() < pParaFmt->GetStart() ) ) + break; + + if ( pFmt->GetStart() > pParaFmt->GetStart() ) + { + nNumber += pFmt->GetStart() - pParaFmt->GetStart(); + pParaFmt = pFmt; + } + + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ); + + if( rBulletState.GetValue() ) + nNumber += 1; + + // same depth, same number format, check for restart + const sal_Int16 nNumberingStartValue = pPara->GetNumberingStartValue(); + if( (nNumberingStartValue != -1) || pPara->IsParaIsNumberingRestart() ) + { + if( nNumberingStartValue != -1 ) + nNumber += nNumberingStartValue - 1; + break; + } + } + while( nPara-- ); + + return nNumber; +} + +void Outliner::ImplCalcBulletText( sal_Int32 nPara, bool bRecalcLevel, bool bRecalcChildren ) +{ + + Paragraph* pPara = pParaList->GetParagraph( nPara ); + + while ( pPara ) + { + OUString aBulletText; + const SvxNumberFormat* pFmt = GetNumberFormat( nPara ); + if( pFmt && ( pFmt->GetNumberingType() != SVX_NUM_BITMAP ) ) + { + aBulletText += pFmt->GetPrefix(); + if( pFmt->GetNumberingType() == SVX_NUM_CHAR_SPECIAL ) + { + sal_UCS4 cChar = pFmt->GetBulletChar(); + aBulletText += OUString(&cChar, 1); + } + else if( pFmt->GetNumberingType() != SVX_NUM_NUMBER_NONE ) + { + aBulletText += pFmt->GetNumStr( ImplGetNumbering( nPara, pFmt ) ); + } + aBulletText += pFmt->GetSuffix(); + } + + if (pPara->GetText() != aBulletText) + pPara->SetText( aBulletText ); + + if ( bRecalcLevel ) + { + sal_Int16 nDepth = pPara->GetDepth(); + pPara = pParaList->GetParagraph( ++nPara ); + if ( !bRecalcChildren ) + { + while ( pPara && ( pPara->GetDepth() > nDepth ) ) + pPara = pParaList->GetParagraph( ++nPara ); + } + + if ( pPara && ( pPara->GetDepth() < nDepth ) ) + pPara = nullptr; + } + else + { + pPara = nullptr; + } + } +} + +void Outliner::Clear() +{ + + if( !bFirstParaIsEmpty ) + { + ImplBlockInsertionCallbacks( true ); + pEditEngine->Clear(); + pParaList->Clear(); + pParaList->Append( std::unique_ptr<Paragraph>(new Paragraph( gnMinDepth ))); + bFirstParaIsEmpty = true; + ImplBlockInsertionCallbacks( false ); + } + else + { + Paragraph* pPara = pParaList->GetParagraph( 0 ); + if(pPara) + pPara->SetDepth( gnMinDepth ); + } +} + +void Outliner::SetFlatMode( bool bFlat ) +{ + + if( bFlat != pEditEngine->IsFlatMode() ) + { + for ( sal_Int32 nPara = pParaList->GetParagraphCount(); nPara; ) + pParaList->GetParagraph( --nPara )->aBulSize.setWidth( -1 ); + + pEditEngine->SetFlatMode( bFlat ); + } +} + +OUString Outliner::ImplGetBulletText( sal_Int32 nPara ) +{ + OUString aRes; + Paragraph* pPara = pParaList->GetParagraph( nPara ); + if (pPara) + { + ImplCalcBulletText( nPara, false, false ); + aRes = pPara->GetText(); + } + return aRes; +} + +// this is needed for StarOffice Api +void Outliner::SetLevelDependentStyleSheet( sal_Int32 nPara ) +{ + SfxItemSet aOldAttrs( pEditEngine->GetParaAttribs( nPara ) ); + ImplSetLevelDependentStyleSheet( nPara ); + pEditEngine->SetParaAttribs( nPara, aOldAttrs ); +} + +void Outliner::ImplBlockInsertionCallbacks( bool b ) +{ + if ( b ) + { + nBlockInsCallback++; + } + else + { + DBG_ASSERT( nBlockInsCallback, "ImplBlockInsertionCallbacks ?!" ); + nBlockInsCallback--; + if ( !nBlockInsCallback ) + { + // Call blocked notify events... + while(!pEditEngine->aNotifyCache.empty()) + { + EENotify aNotify(pEditEngine->aNotifyCache.front()); + // Remove from list before calling, maybe we enter LeaveBlockNotifications while calling the handler... + pEditEngine->aNotifyCache.erase(pEditEngine->aNotifyCache.begin()); + pEditEngine->aOutlinerNotifyHdl.Call( aNotify ); + } + } + } +} + +IMPL_LINK( Outliner, EditEngineNotifyHdl, EENotify&, rNotify, void ) +{ + if ( !nBlockInsCallback ) + pEditEngine->aOutlinerNotifyHdl.Call( rNotify ); + else + pEditEngine->aNotifyCache.push_back(rNotify); +} + +/** sets a link that is called at the beginning of a drag operation at an edit view */ +void Outliner::SetBeginDropHdl( const Link<EditView*,void>& rLink ) +{ + pEditEngine->SetBeginDropHdl( rLink ); +} + +/** sets a link that is called at the end of a drag operation at an edit view */ +void Outliner::SetEndDropHdl( const Link<EditView*,void>& rLink ) +{ + pEditEngine->SetEndDropHdl( rLink ); +} + +/** sets a link that is called before a drop or paste operation. */ +void Outliner::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink ) +{ + maBeginPasteOrDropHdl = rLink; +} + +/** sets a link that is called after a drop or paste operation. */ +void Outliner::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos*,void>& rLink ) +{ + maEndPasteOrDropHdl = rLink; +} + +void Outliner::SetParaFlag( Paragraph* pPara, ParaFlag nFlag ) +{ + if( pPara && !pPara->HasFlag( nFlag ) ) + { + if( IsUndoEnabled() && !IsInUndo() ) + InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( this, GetAbsPos( pPara ), pPara->nFlags, pPara->nFlags|nFlag ) ); + + pPara->SetFlag( nFlag ); + } +} + +bool Outliner::HasParaFlag( const Paragraph* pPara, ParaFlag nFlag ) +{ + return pPara && pPara->HasFlag( nFlag ); +} + + +bool Outliner::IsPageOverflow() +{ + return pEditEngine->IsPageOverflow(); +} + +std::optional<NonOverflowingText> Outliner::GetNonOverflowingText() const +{ + /* XXX: + * nCount should be the number of paragraphs of the non overflowing text + * nStart should be the starting paragraph of the non overflowing text (XXX: Always 0?) + */ + + if ( GetParagraphCount() < 1 ) + return {}; + + // last non-overflowing paragraph is before the first overflowing one + sal_Int32 nCount = pEditEngine->GetOverflowingParaNum(); + sal_Int32 nOverflowLine = pEditEngine->GetOverflowingLineNum(); // XXX: Unused for now + + // Defensive check: overflowing para index beyond actual # of paragraphs? + if ( nCount > GetParagraphCount()-1) { + SAL_INFO("editeng.chaining", + "[Overflowing] Ops, trying to retrieve para " + << nCount << " when max index is " << GetParagraphCount()-1 ); + return {}; + } + + if (nCount < 0) + { + SAL_INFO("editeng.chaining", + "[Overflowing] No Overflowing text but GetNonOverflowinText called?!"); + return {}; + } + + // NOTE: We want the selection of the overflowing text from here + // At the same time we may want to consider the beginning of such text + // in a more fine grained way (i.e. as GetNonOverflowingText did) + +/* + sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); + sal_uInt32 nParaCount = GetParagraphCount(); + + sal_uInt32 nLen = 0; + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) { + nLen += GetLineLen(nHeadPara, nLine); + } + + sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); + ESelection aOverflowingTextSel; + sal_Int32 nLastPara = nParaCount-1; + sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); + aOverflowingTextSel = ESelection(nOverflowingPara, nLen, + nLastPara, nLastParaLen); + bool bLastParaInterrupted = + pEditEngine->GetOverflowingLineNum() > 0; + + return new NonOverflowingText(aOverflowingTextSel, bLastParaInterrupted); + **/ + + + // Only overflowing text, i.e. 1st line of 1st paragraph overflowing + bool bItAllOverflew = nCount == 0 && nOverflowLine == 0; + if ( bItAllOverflew ) + { + ESelection aEmptySel(0,0,0,0); + //EditTextObject *pTObj = pEditEngine->CreateTextObject(aEmptySel); + bool const bLastParaInterrupted = true; // Last Para was interrupted since everything overflew + return NonOverflowingText(aEmptySel, bLastParaInterrupted); + } else { // Get the lines that of the overflowing para fit in the box + + sal_Int32 nOverflowingPara = nCount; + sal_uInt32 nLen = 0; + + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) + { + nLen += GetLineLen(nOverflowingPara, nLine); + } + + //sal_Int32 nStartPara = 0; + //sal_Int32 nStartPos = 0; + ESelection aOverflowingTextSelection; + + const sal_Int32 nEndPara = GetParagraphCount()-1; + const sal_Int32 nEndPos = pEditEngine->GetTextLen(nEndPara); + + if (nLen == 0) { + // XXX: What happens inside this case might be dependent on the joining paragraph or not-thingy + // Overflowing paragraph is empty or first line overflowing: it's not "Non-Overflowing" text then + sal_Int32 nParaLen = GetText(GetParagraph(nOverflowingPara-1)).getLength(); + aOverflowingTextSelection = + ESelection(nOverflowingPara-1, nParaLen, nEndPara, nEndPos); + } else { + // We take until we have to from the overflowing paragraph + aOverflowingTextSelection = + ESelection(nOverflowingPara, nLen, nEndPara, nEndPos); + } + //EditTextObject *pTObj = pEditEngine->CreateTextObject(aNonOverflowingTextSelection); + + //sal_Int32 nLastLine = GetLineCount(nOverflowingPara)-1; + bool bLastParaInterrupted = + pEditEngine->GetOverflowingLineNum() > 0; + + return NonOverflowingText(aOverflowingTextSelection, bLastParaInterrupted); + } +} + +OutlinerParaObject Outliner::GetEmptyParaObject() const +{ + std::unique_ptr<EditTextObject> pEmptyText = pEditEngine->GetEmptyTextObject(); + OutlinerParaObject aPObj( std::move(pEmptyText) ); + aPObj.SetOutlinerMode(GetOutlinerMode()); + return aPObj; +} + +std::optional<OverflowingText> Outliner::GetOverflowingText() const +{ + if ( pEditEngine->GetOverflowingParaNum() < 0) + return {}; + + + // Defensive check: overflowing para index beyond actual # of paragraphs? + if ( pEditEngine->GetOverflowingParaNum() > GetParagraphCount()-1) { + SAL_INFO("editeng.chaining", + "[Overflowing] Ops, trying to retrieve para " + << pEditEngine->GetOverflowingParaNum() << " when max index is " + << GetParagraphCount()-1 ); + return {}; + } + + sal_Int32 nHeadPara = pEditEngine->GetOverflowingParaNum(); + sal_uInt32 nParaCount = GetParagraphCount(); + + sal_uInt32 nLen = 0; + for ( sal_Int32 nLine = 0; + nLine < pEditEngine->GetOverflowingLineNum(); + nLine++) { + nLen += GetLineLen(nHeadPara, nLine); + } + + sal_uInt32 nOverflowingPara = pEditEngine->GetOverflowingParaNum(); + ESelection aOverflowingTextSel; + sal_Int32 nLastPara = nParaCount-1; + sal_Int32 nLastParaLen = GetText(GetParagraph(nLastPara)).getLength(); + aOverflowingTextSel = ESelection(nOverflowingPara, nLen, + nLastPara, nLastParaLen); + return OverflowingText(pEditEngine->CreateTransferable(aOverflowingTextSel)); + +} + +void Outliner::ClearOverflowingParaNum() +{ + pEditEngine->ClearOverflowingParaNum(); +} + +void Outliner::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("outliner.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Outliner")); + pParaList->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + (void)xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlobj.cxx b/editeng/source/outliner/outlobj.cxx new file mode 100644 index 0000000000..e6dc6e691c --- /dev/null +++ b/editeng/source/outliner/outlobj.cxx @@ -0,0 +1,254 @@ +/* -*- 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 <editeng/editdata.hxx> + +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editobj.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <o3tl/cow_wrapper.hxx> +#include <o3tl/safeint.hxx> +#include <libxml/xmlwriter.h> + +OutlinerParaObjData::OutlinerParaObjData( std::unique_ptr<EditTextObject> pEditTextObject, ParagraphDataVector&& rParagraphDataVector, bool bIsEditDoc ) : + mpEditTextObject(std::move(pEditTextObject)), + maParagraphDataVector(std::move(rParagraphDataVector)), + mbIsEditDoc(bIsEditDoc) +{ + if( maParagraphDataVector.empty() && (mpEditTextObject->GetParagraphCount() != 0) ) + maParagraphDataVector.resize(mpEditTextObject->GetParagraphCount()); +} + +OutlinerParaObjData::OutlinerParaObjData( const OutlinerParaObjData& r ): + mpEditTextObject(r.mpEditTextObject->Clone()), + maParagraphDataVector(r.maParagraphDataVector), + mbIsEditDoc(r.mbIsEditDoc) +{ +} + +OutlinerParaObjData::~OutlinerParaObjData() +{ +} + +bool OutlinerParaObjData::operator==(const OutlinerParaObjData& rCandidate) const +{ + return (*mpEditTextObject == *rCandidate.mpEditTextObject + && maParagraphDataVector == rCandidate.maParagraphDataVector + && mbIsEditDoc == rCandidate.mbIsEditDoc); +} + +bool OutlinerParaObjData::isWrongListEqual(const OutlinerParaObjData& rCompare) const +{ + return mpEditTextObject->isWrongListEqual(*rCompare.mpEditTextObject); +} + +OutlinerParaObject::OutlinerParaObject( + std::unique_ptr<EditTextObject> xTextObj, ParagraphDataVector&& rParagraphDataVector, bool bIsEditDoc ) : + mpImpl(OutlinerParaObjData(std::move(xTextObj), std::move(rParagraphDataVector), bIsEditDoc)) +{ +} + +OutlinerParaObject::OutlinerParaObject( std::unique_ptr<EditTextObject> pTextObj ) : + mpImpl(OutlinerParaObjData(std::move(pTextObj), ParagraphDataVector(), true)) +{ +} + +OutlinerParaObject::OutlinerParaObject( const OutlinerParaObject& r ) : + mpImpl(r.mpImpl) +{ +} + +OutlinerParaObject::OutlinerParaObject( OutlinerParaObject&& r ) noexcept : + mpImpl(std::move(r.mpImpl)) +{ +} + +OutlinerParaObject::~OutlinerParaObject() +{ +} + +OutlinerParaObject& OutlinerParaObject::operator=( const OutlinerParaObject& r ) +{ + mpImpl = r.mpImpl; + return *this; +} + +OutlinerParaObject& OutlinerParaObject::operator=( OutlinerParaObject&& r ) noexcept +{ + mpImpl = std::move(r.mpImpl); + return *this; +} + +bool OutlinerParaObject::operator==( const OutlinerParaObject& r ) const +{ + return r.mpImpl == mpImpl; +} + +// #i102062# +bool OutlinerParaObject::isWrongListEqual( const OutlinerParaObject& r ) const +{ + if (r.mpImpl.same_object(mpImpl)) + { + return true; + } + + return mpImpl->isWrongListEqual(*r.mpImpl); +} + +OutlinerMode OutlinerParaObject::GetOutlinerMode() const +{ + return mpImpl->mpEditTextObject->GetUserType(); +} + +void OutlinerParaObject::SetOutlinerMode(OutlinerMode nNew) +{ + // create a const pointer to avoid an early call to + // make_unique() in the dereference of mpImpl + const ::o3tl::cow_wrapper< OutlinerParaObjData >* pImpl = &mpImpl; + if ( ( *pImpl )->mpEditTextObject->GetUserType() != nNew ) + { + mpImpl->mpEditTextObject->SetUserType(nNew); + } +} + +bool OutlinerParaObject::IsEffectivelyVertical() const +{ + return mpImpl->mpEditTextObject->IsEffectivelyVertical(); +} + +bool OutlinerParaObject::GetVertical() const +{ + return mpImpl->mpEditTextObject->GetVertical(); +} + +bool OutlinerParaObject::IsTopToBottom() const +{ + return mpImpl->mpEditTextObject->IsTopToBottom(); +} + +void OutlinerParaObject::SetVertical(bool bNew) +{ + const ::o3tl::cow_wrapper< OutlinerParaObjData >* pImpl = &mpImpl; + if ( ( *pImpl )->mpEditTextObject->IsEffectivelyVertical() != bNew) + { + mpImpl->mpEditTextObject->SetVertical(bNew); + } +} +void OutlinerParaObject::SetRotation(TextRotation nRotation) +{ + mpImpl->mpEditTextObject->SetRotation(nRotation); +} + +TextRotation OutlinerParaObject::GetRotation() const +{ + return mpImpl->mpEditTextObject->GetRotation(); +} + +sal_Int32 OutlinerParaObject::Count() const +{ + size_t nSize = mpImpl->maParagraphDataVector.size(); + if (nSize > EE_PARA_MAX_COUNT) + { + SAL_WARN( "editeng", "OutlinerParaObject::Count - overflow " << nSize); + return EE_PARA_MAX_COUNT; + } + return static_cast<sal_Int32>(nSize); +} + +sal_Int16 OutlinerParaObject::GetDepth(sal_Int32 nPara) const +{ + if(0 <= nPara && o3tl::make_unsigned(nPara) < mpImpl->maParagraphDataVector.size()) + { + return mpImpl->maParagraphDataVector[nPara].getDepth(); + } + else + { + return -1; + } +} + +const EditTextObject& OutlinerParaObject::GetTextObject() const +{ + return *mpImpl->mpEditTextObject; +} + +const ParagraphData& OutlinerParaObject::GetParagraphData(sal_Int32 nIndex) const +{ + if(0 <= nIndex && o3tl::make_unsigned(nIndex) < mpImpl->maParagraphDataVector.size()) + { + return mpImpl->maParagraphDataVector[nIndex]; + } + else + { + OSL_FAIL("OutlinerParaObject::GetParagraphData: Access out of range (!)"); + static ParagraphData aEmptyParagraphData; + return aEmptyParagraphData; + } +} + +void OutlinerParaObject::ClearPortionInfo() +{ + mpImpl->mpEditTextObject->ClearPortionInfo(); +} + +bool OutlinerParaObject::ChangeStyleSheets(std::u16string_view rOldName, + SfxStyleFamily eOldFamily, const OUString& rNewName, SfxStyleFamily eNewFamily) +{ + return mpImpl->mpEditTextObject->ChangeStyleSheets(rOldName, eOldFamily, rNewName, eNewFamily); +} + +void OutlinerParaObject::ChangeStyleSheetName(SfxStyleFamily eFamily, + std::u16string_view rOldName, const OUString& rNewName) +{ + mpImpl->mpEditTextObject->ChangeStyleSheetName(eFamily, rOldName, rNewName); +} + +void OutlinerParaObject::SetStyleSheets(sal_uInt16 nLevel, const OUString& rNewName, + const SfxStyleFamily& rNewFamily) +{ + const sal_Int32 nCount(Count()); + + if(nCount) + { + sal_Int32 nDecrementer(nCount); + + while(nDecrementer > 0) + { + if(GetDepth(--nDecrementer) == nLevel) + { + mpImpl->mpEditTextObject->SetStyleSheet(nDecrementer, rNewName, rNewFamily); + } + } + } +} + +void OutlinerParaObject::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("OutlinerParaObject")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + mpImpl->mpEditTextObject->dumpAsXml(pWriter); + for (ParagraphData const & p : mpImpl->maParagraphDataVector) + Paragraph(p).dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlundo.cxx b/editeng/source/outliner/outlundo.cxx new file mode 100644 index 0000000000..c2db1a77f3 --- /dev/null +++ b/editeng/source/outliner/outlundo.cxx @@ -0,0 +1,164 @@ +/* -*- 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 <editeng/outliner.hxx> +#include <tools/debug.hxx> +#include "outlundo.hxx" + + +OutlinerUndoBase::OutlinerUndoBase( sal_uInt16 _nId, Outliner* pOutliner ) + : EditUndo( _nId, nullptr ) +{ + DBG_ASSERT( pOutliner, "Undo: Outliner?!" ); + mpOutliner = pOutliner; +} + +OutlinerUndoChangeParaFlags::OutlinerUndoChangeParaFlags( Outliner* pOutliner, sal_Int32 nPara, ParaFlag nOldFlags, ParaFlag nNewFlags ) +: OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara), mnOldFlags(nOldFlags), mnNewFlags(nNewFlags) +{ +} + +void OutlinerUndoChangeParaFlags::Undo() +{ + ImplChangeFlags( mnOldFlags ); +} + +void OutlinerUndoChangeParaFlags::Redo() +{ + ImplChangeFlags( mnNewFlags ); +} + +void OutlinerUndoChangeParaFlags::ImplChangeFlags( ParaFlag nFlags ) +{ + Outliner* pOutliner = GetOutliner(); + Paragraph* pPara = pOutliner->GetParagraph( mnPara ); + if( pPara ) + { + pOutliner->nDepthChangedHdlPrevDepth = pPara->GetDepth(); + ParaFlag nPrevFlags = pPara->nFlags; + + pPara->nFlags = nFlags; + pOutliner->DepthChangedHdl(pPara, nPrevFlags); + } +} + +OutlinerUndoChangeParaNumberingRestart::OutlinerUndoChangeParaNumberingRestart( Outliner* pOutliner, sal_Int32 nPara, + sal_Int16 nOldNumberingStartValue, sal_Int16 nNewNumberingStartValue, + bool bOldParaIsNumberingRestart, bool bNewParaIsNumberingRestart ) +: OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara) +{ + maUndoData.mnNumberingStartValue = nOldNumberingStartValue; + maUndoData.mbParaIsNumberingRestart = bOldParaIsNumberingRestart; + maRedoData.mnNumberingStartValue = nNewNumberingStartValue; + maRedoData.mbParaIsNumberingRestart = bNewParaIsNumberingRestart; +} + +void OutlinerUndoChangeParaNumberingRestart::Undo() +{ + ImplApplyData( maUndoData ); +} + +void OutlinerUndoChangeParaNumberingRestart::Redo() +{ + ImplApplyData( maRedoData ); +} + +void OutlinerUndoChangeParaNumberingRestart::ImplApplyData( const ParaRestartData& rData ) +{ + Outliner* pOutliner = GetOutliner(); + pOutliner->SetNumberingStartValue( mnPara, rData.mnNumberingStartValue ); + pOutliner->SetParaIsNumberingRestart( mnPara, rData.mbParaIsNumberingRestart ); +} + +OutlinerUndoChangeDepth::OutlinerUndoChangeDepth( Outliner* pOutliner, sal_Int32 nPara, sal_Int16 nOldDepth, sal_Int16 nNewDepth ) + : OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara), mnOldDepth(nOldDepth), mnNewDepth(nNewDepth) +{ +} + +void OutlinerUndoChangeDepth::Undo() +{ + GetOutliner()->ImplInitDepth( mnPara, mnOldDepth, false ); +} + +void OutlinerUndoChangeDepth::Redo() +{ + GetOutliner()->ImplInitDepth( mnPara, mnNewDepth, false ); +} + +OutlinerUndoCheckPara::OutlinerUndoCheckPara( Outliner* pOutliner, sal_Int32 nPara ) + : OutlinerUndoBase( OLUNDO_DEPTH, pOutliner ), mnPara(nPara) +{ +} + +void OutlinerUndoCheckPara::Undo() +{ + Paragraph* pPara = GetOutliner()->GetParagraph( mnPara ); + pPara->Invalidate(); + GetOutliner()->ImplCalcBulletText( mnPara, false, false ); +} + +void OutlinerUndoCheckPara::Redo() +{ + Paragraph* pPara = GetOutliner()->GetParagraph( mnPara ); + pPara->Invalidate(); + GetOutliner()->ImplCalcBulletText( mnPara, false, false ); +} + +OLUndoExpand::OLUndoExpand(Outliner* pOut, sal_uInt16 _nId ) + : EditUndo( _nId, nullptr ), pOutliner(pOut), nCount(0) +{ + DBG_ASSERT(pOut,"Undo:No Outliner"); +} + + +OLUndoExpand::~OLUndoExpand() +{ +} + + +void OLUndoExpand::Restore( bool bUndo ) +{ + DBG_ASSERT(pOutliner,"Undo:No Outliner"); + DBG_ASSERT(pOutliner->pEditEngine,"Outliner already deleted"); + Paragraph* pPara; + + bool bExpand = false; + sal_uInt16 _nId = GetId(); + if((_nId == OLUNDO_EXPAND && !bUndo) || (_nId == OLUNDO_COLLAPSE && bUndo)) + bExpand = true; + + pPara = pOutliner->GetParagraph( nCount ); + if( bExpand ) + pOutliner->Expand( pPara ); + else + pOutliner->Collapse( pPara ); +} + +void OLUndoExpand::Undo() +{ + Restore( true ); +} + +void OLUndoExpand::Redo() +{ + Restore( false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlundo.hxx b/editeng/source/outliner/outlundo.hxx new file mode 100644 index 0000000000..066b415e13 --- /dev/null +++ b/editeng/source/outliner/outlundo.hxx @@ -0,0 +1,118 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/outliner.hxx> +#include <editeng/editund2.hxx> + +class OutlinerUndoBase : public EditUndo +{ +private: + Outliner* mpOutliner; + +public: + OutlinerUndoBase( sal_uInt16 nId, Outliner* pOutliner ); + + Outliner* GetOutliner() const { return mpOutliner; } +}; + +class OutlinerUndoChangeParaFlags : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + ParaFlag mnOldFlags; + ParaFlag mnNewFlags; + + void ImplChangeFlags( ParaFlag nFlags ); + +public: + OutlinerUndoChangeParaFlags( Outliner* pOutliner, sal_Int32 nPara, ParaFlag nOldFlags, ParaFlag nNewFlags ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OutlinerUndoChangeParaNumberingRestart : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + + struct ParaRestartData + { + sal_Int16 mnNumberingStartValue; + bool mbParaIsNumberingRestart; + }; + + ParaRestartData maUndoData; + ParaRestartData maRedoData; + + void ImplApplyData( const ParaRestartData& rData ); +public: + OutlinerUndoChangeParaNumberingRestart( Outliner* pOutliner, sal_Int32 nPara, + sal_Int16 nOldNumberingStartValue, sal_Int16 mnNewNumberingStartValue, + bool bOldbParaIsNumberingRestart, bool bNewParaIsNumberingRestart ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OutlinerUndoChangeDepth : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + sal_Int16 mnOldDepth; + sal_Int16 mnNewDepth; + +public: + OutlinerUndoChangeDepth( Outliner* pOutliner, sal_Int32 nPara, sal_Int16 nOldDepth, sal_Int16 nNewDepth ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +// Help-Undo: If it does not exist an OutlinerUndoAction for a certain action +// because this is handled by the EditEngine, but for example the bullet has +// to be recalculated. +class OutlinerUndoCheckPara : public OutlinerUndoBase +{ +private: + sal_Int32 mnPara; + +public: + OutlinerUndoCheckPara( Outliner* pOutliner, sal_Int32 nPara ); + + virtual void Undo() override; + virtual void Redo() override; +}; + +class OLUndoExpand : public EditUndo +{ + void Restore( bool bUndo ); +public: + OLUndoExpand( Outliner* pOut, sal_uInt16 nId ); + virtual ~OLUndoExpand() override; + virtual void Undo() override; + virtual void Redo() override; + + Outliner* pOutliner; + sal_Int32 nCount; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/outlvw.cxx b/editeng/source/outliner/outlvw.cxx new file mode 100644 index 0000000000..136ecd776c --- /dev/null +++ b/editeng/source/outliner/outlvw.cxx @@ -0,0 +1,1510 @@ +/* -*- 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 <memory> +#include <com/sun/star/i18n/WordType.hpp> + +#include <svl/itempool.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/editdata.hxx> + +#include <svl/style.hxx> +#include <svl/languageoptions.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <editeng/outliner.hxx> +#include <outleeng.hxx> +#include "paralist.hxx" +#include "outlundo.hxx" +#include <editeng/outlobj.hxx> +#include <editeng/flditem.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/numitem.hxx> +#include <vcl/window.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <svl/itemset.hxx> +#include <svl/eitem.hxx> +#include <editeng/editstat.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> + +using namespace ::com::sun::star; + + +OutlinerView::OutlinerView( Outliner* pOut, vcl::Window* pWin ) +{ + pOwner = pOut; + pEditView.reset( new EditView( pOut->pEditEngine.get(), pWin ) ); +} + +OutlinerView::~OutlinerView() +{ +} + +void OutlinerView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) +{ + // For the first Paint/KeyInput/Drop an empty Outliner is turned into + // an Outliner with exactly one paragraph. + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + + pEditView->Paint( rRect, pTargetDevice ); +} + +bool OutlinerView::PostKeyEvent( const KeyEvent& rKEvt, vcl::Window const * pFrameWin ) +{ + // For the first Paint/KeyInput/Drop an empty Outliner is turned into + // an Outliner with exactly one paragraph. + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + + bool bKeyProcessed = false; + ESelection aSel( pEditView->GetSelection() ); + bool bSelection = aSel.HasRange(); + vcl::KeyCode aKeyCode = rKEvt.GetKeyCode(); + KeyFuncType eFunc = aKeyCode.GetFunction(); + sal_uInt16 nCode = aKeyCode.GetCode(); + bool bReadOnly = IsReadOnly(); + + if( bSelection && ( nCode != KEY_TAB ) && EditEngine::DoesKeyChangeText( rKEvt ) ) + { + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return true; + } + + if ( eFunc != KeyFuncType::DONTKNOW ) + { + switch ( eFunc ) + { + case KeyFuncType::CUT: + { + if ( !bReadOnly ) + { + Cut(); + bKeyProcessed = true; + } + } + break; + case KeyFuncType::COPY: + { + Copy(); + bKeyProcessed = true; + } + break; + case KeyFuncType::PASTE: + { + if ( !bReadOnly ) + { + PasteSpecial(); + bKeyProcessed = true; + } + } + break; + case KeyFuncType::DELETE: + { + if( !bReadOnly && !bSelection && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) ) + { + if( aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) + { + Paragraph* pNext = pOwner->pParaList->GetParagraph( aSel.nEndPara+1 ); + if( pNext && pNext->HasFlag(ParaFlag::ISPAGE) ) + { + if( !pOwner->ImpCanDeleteSelectedPages( this, aSel.nEndPara, 1 ) ) + return false; + } + } + } + } + break; + default: // is then possibly edited below. + eFunc = KeyFuncType::DONTKNOW; + } + } + if ( eFunc == KeyFuncType::DONTKNOW ) + { + switch ( nCode ) + { + case KEY_TAB: + { + if ( !bReadOnly && !aKeyCode.IsMod1() && !aKeyCode.IsMod2() ) + { + if ( ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject ) && + ( pOwner->GetOutlinerMode() != OutlinerMode::TitleObject ) && + ( bSelection || !aSel.nStartPos ) ) + { + Indent( aKeyCode.IsShift() ? -1 : +1 ); + bKeyProcessed = true; + } + else if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) && + !bSelection && !aSel.nEndPos && pOwner->ImplHasNumberFormat( aSel.nEndPara ) ) + { + Indent( aKeyCode.IsShift() ? -1 : +1 ); + bKeyProcessed = true; + } + } + } + break; + case KEY_BACKSPACE: + { + if( !bReadOnly && !bSelection && aSel.nEndPara && !aSel.nEndPos ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); + Paragraph* pPrev = pOwner->pParaList->GetParagraph( aSel.nEndPara-1 ); + if( !pPrev->IsVisible() ) + return true; + if( !pPara->GetDepth() ) + { + if(!pOwner->ImpCanDeleteSelectedPages(this, aSel.nEndPara , 1 ) ) + return true; + } + } + } + break; + case KEY_RETURN: + { + if ( !bReadOnly ) + { + // Special treatment: hard return at the end of a paragraph, + // which has collapsed subparagraphs. + Paragraph* pPara = pOwner->pParaList->GetParagraph( aSel.nEndPara ); + + if( !aKeyCode.IsShift() ) + { + // ImpGetCursor again??? + if( !bSelection && + aSel.nEndPos == pOwner->pEditEngine->GetTextLen( aSel.nEndPara ) ) + { + sal_Int32 nChildren = pOwner->pParaList->GetChildCount(pPara); + if( nChildren && !pOwner->pParaList->HasVisibleChildren(pPara)) + { + pOwner->UndoActionStart( OLUNDO_INSERT ); + sal_Int32 nTemp = aSel.nEndPara; + nTemp += nChildren; + nTemp++; // insert above next Non-Child + SAL_WARN_IF( nTemp < 0, "editeng", "OutlinerView::PostKeyEvent - overflow"); + if (nTemp >= 0) + { + pOwner->Insert( OUString(),nTemp,pPara->GetDepth()); + // Position the cursor + ESelection aTmpSel(nTemp,0,nTemp,0); + pEditView->SetSelection( aTmpSel ); + } + pEditView->ShowCursor(); + pOwner->UndoActionEnd(); + bKeyProcessed = true; + } + } + } + if( !bKeyProcessed && !bSelection && + !aKeyCode.IsShift() && aKeyCode.IsMod1() && + ( aSel.nEndPos == pOwner->pEditEngine->GetTextLen(aSel.nEndPara) ) ) + { + pOwner->UndoActionStart( OLUNDO_INSERT ); + sal_Int32 nTemp = aSel.nEndPara; + nTemp++; + pOwner->Insert( OUString(), nTemp, pPara->GetDepth()+1 ); + + // Position the cursor + ESelection aTmpSel(nTemp,0,nTemp,0); + pEditView->SetSelection( aTmpSel ); + pEditView->ShowCursor(); + pOwner->UndoActionEnd(); + bKeyProcessed = true; + } + } + } + break; + } + } + + return bKeyProcessed || pEditView->PostKeyEvent( rKEvt, pFrameWin ); +} + +sal_Int32 OutlinerView::ImpCheckMousePos(const Point& rPosPix, MouseTarget& reTarget) +{ + sal_Int32 nPara = EE_PARA_NOT_FOUND; + + Point aMousePosWin = pEditView->GetOutputDevice().PixelToLogic( rPosPix ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + { + reTarget = MouseTarget::Outside; + } + else + { + reTarget = MouseTarget::Text; + + Point aPaperPos( aMousePosWin ); + tools::Rectangle aOutArea = pEditView->GetOutputArea(); + tools::Rectangle aVisArea = pEditView->GetVisArea(); + aPaperPos.AdjustX( -(aOutArea.Left()) ); + aPaperPos.AdjustX(aVisArea.Left() ); + aPaperPos.AdjustY( -(aOutArea.Top()) ); + aPaperPos.AdjustY(aVisArea.Top() ); + + bool bBullet; + if ( pOwner->IsTextPos( aPaperPos, 0, &bBullet ) ) + { + Point aDocPos = pOwner->GetDocPos( aPaperPos ); + nPara = pOwner->pEditEngine->FindParagraph( aDocPos.Y() ); + + if ( bBullet ) + { + reTarget = MouseTarget::Bullet; + } + else + { + // Check for hyperlink + const SvxFieldItem* pFieldItem = pEditView->GetField( aMousePosWin ); + if ( pFieldItem && pFieldItem->GetField() && dynamic_cast< const SvxURLField* >(pFieldItem->GetField()) != nullptr ) + reTarget = MouseTarget::Hypertext; + } + } + } + return nPara; +} + +bool OutlinerView::MouseMove( const MouseEvent& rMEvt ) +{ + if( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode()) + return pEditView->MouseMove( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + return pEditView->MouseMove( rMEvt ); +} + + +bool OutlinerView::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) + return pEditView->MouseButtonDown( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + + MouseTarget eTarget; + sal_Int32 nPara = ImpCheckMousePos( rMEvt.GetPosPixel(), eTarget ); + if ( eTarget == MouseTarget::Bullet ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + bool bHasChildren = (pPara && pOwner->pParaList->HasChildren(pPara)); + if( rMEvt.GetClicks() == 1 ) + { + sal_Int32 nEndPara = nPara; + if ( bHasChildren && pOwner->pParaList->HasVisibleChildren(pPara) ) + nEndPara += pOwner->pParaList->GetChildCount( pPara ); + // The selection is inverted, so that EditEngine does not scroll + ESelection aSel(nEndPara, EE_TEXTPOS_ALL, nPara, 0 ); + pEditView->SetSelection( aSel ); + } + else if( rMEvt.GetClicks() == 2 && bHasChildren ) + ImpToggleExpand( pPara ); + + return true; + } + + // special case for outliner view in impress, check if double click hits the page icon for toggle + if( (nPara == EE_PARA_NOT_FOUND) && (pOwner->GetOutlinerMode() == OutlinerMode::OutlineView) && (eTarget == MouseTarget::Text) && (rMEvt.GetClicks() == 2) ) + { + ESelection aSel( pEditView->GetSelection() ); + nPara = aSel.nStartPara; + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + if( (pPara && pOwner->pParaList->HasChildren(pPara)) && pPara->HasFlag(ParaFlag::ISPAGE) ) + { + ImpToggleExpand( pPara ); + } + } + return pEditView->MouseButtonDown( rMEvt ); +} + + +bool OutlinerView::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( ( pOwner->GetOutlinerMode() == OutlinerMode::TextObject ) || pEditView->GetEditEngine()->IsInSelectionMode() ) + return pEditView->MouseButtonUp( rMEvt ); + + Point aMousePosWin( pEditView->GetOutputDevice().PixelToLogic( rMEvt.GetPosPixel() ) ); + if( !pEditView->GetOutputArea().Contains( aMousePosWin ) ) + return false; + + PointerStyle aPointer = GetPointer( rMEvt.GetPosPixel() ); + pEditView->GetWindow()->SetPointer( aPointer ); + + return pEditView->MouseButtonUp( rMEvt ); +} + +void OutlinerView::ReleaseMouse() +{ + pEditView->ReleaseMouse(); +} + +void OutlinerView::ImpToggleExpand( Paragraph const * pPara ) +{ + sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pPara ); + pEditView->SetSelection( ESelection( nPara, 0, nPara, 0 ) ); + ImplExpandOrCollaps( nPara, nPara, !pOwner->pParaList->HasVisibleChildren( pPara ) ); + pEditView->ShowCursor(); +} + +void OutlinerView::Select( Paragraph const * pParagraph, bool bSelect ) +{ + sal_Int32 nPara = pOwner->pParaList->GetAbsPos( pParagraph ); + sal_Int32 nEnd = 0; + if ( bSelect ) + nEnd = SAL_MAX_INT32; + + ESelection aSel( nPara, 0, nPara, nEnd ); + pEditView->SetSelection( aSel ); +} + + +void OutlinerView::SetAttribs( const SfxItemSet& rAttrs ) +{ + bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->UndoActionStart( OLUNDO_ATTR ); + + ParaRange aSel = ImpGetSelectedParagraphs( false ); + + pEditView->SetAttribs( rAttrs ); + + // Update Bullet text + for( sal_Int32 nPara= aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + pOwner->ImplCheckNumBulletItem( nPara ); + pOwner->ImplCalcBulletText( nPara, false, false ); + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->InsertUndo( std::make_unique<OutlinerUndoCheckPara>( pOwner, nPara ) ); + } + + if( !pOwner->IsInUndo() && pOwner->IsUndoEnabled() ) + pOwner->UndoActionEnd(); + + pEditView->SetEditEngineUpdateLayout( bUpdate ); +} + +ParaRange OutlinerView::ImpGetSelectedParagraphs( bool bIncludeHiddenChildren ) +{ + ESelection aSel = pEditView->GetSelection(); + ParaRange aParas( aSel.nStartPara, aSel.nEndPara ); + aParas.Adjust(); + + // Record the invisible Children of the last Parents in the selection + if ( bIncludeHiddenChildren ) + { + Paragraph* pLast = pOwner->pParaList->GetParagraph( aParas.nEndPara ); + if ( pOwner->pParaList->HasHiddenChildren( pLast ) ) + aParas.nEndPara = aParas.nEndPara + pOwner->pParaList->GetChildCount( pLast ); + } + return aParas; +} + +// TODO: Name should be changed! +void OutlinerView::AdjustDepth( short nDX ) +{ + Indent( nDX ); +} + +void OutlinerView::Indent( short nDiff ) +{ + if( !nDiff || ( ( nDiff > 0 ) && ImpCalcSelectedPages( true ) && !pOwner->ImpCanIndentSelectedPages( this ) ) ) + return; + + const bool bOutlinerView = bool(pOwner->pEditEngine->GetControlWord() & EEControlBits::OUTLINER); + bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); + + if( bUndo ) + pOwner->UndoActionStart( OLUNDO_DEPTH ); + + sal_Int16 nMinDepth = -1; // Optimization: avoid recalculate too many paragraphs if not really needed. + + ParaRange aSel = ImpGetSelectedParagraphs( true ); + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + + sal_Int16 nOldDepth = pPara->GetDepth(); + sal_Int16 nNewDepth = nOldDepth + nDiff; + + if( bOutlinerView && nPara ) + { + const bool bPage = pPara->HasFlag(ParaFlag::ISPAGE); + if( (bPage && (nDiff == +1)) || (!bPage && (nDiff == -1) && (nOldDepth <= 0)) ) + { + // Notify App + pOwner->nDepthChangedHdlPrevDepth = nOldDepth; + ParaFlag nPrevFlags = pPara->nFlags; + + if( bPage ) + pPara->RemoveFlag( ParaFlag::ISPAGE ); + else + pPara->SetFlag( ParaFlag::ISPAGE ); + + pOwner->DepthChangedHdl(pPara, nPrevFlags); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + + if( bUndo ) + pOwner->InsertUndo( std::make_unique<OutlinerUndoChangeParaFlags>( pOwner, nPara, nPrevFlags, pPara->nFlags ) ); + + continue; + } + } + + // do not switch off numeration with tab + if( (nOldDepth == 0) && (nNewDepth == -1) ) + continue; + + // do not indent if there is no numeration enabled + if( nOldDepth == -1 ) + continue; + + if ( nNewDepth < Outliner::gnMinDepth ) + nNewDepth = Outliner::gnMinDepth; + if ( nNewDepth > pOwner->nMaxDepth ) + nNewDepth = pOwner->nMaxDepth; + + if( nOldDepth < nMinDepth ) + nMinDepth = nOldDepth; + if( nNewDepth < nMinDepth ) + nMinDepth = nNewDepth; + + if( nOldDepth != nNewDepth ) + { + if ( ( nPara == aSel.nStartPara ) && aSel.nStartPara && ( pOwner->GetOutlinerMode() != OutlinerMode::TextObject )) + { + // Special case: the predecessor of an indented paragraph is + // invisible and is now on the same level as the visible + // paragraph. In this case, the next visible paragraph is + // searched for and fluffed. +#ifdef DBG_UTIL + Paragraph* _pPara = pOwner->pParaList->GetParagraph( aSel.nStartPara ); + DBG_ASSERT(_pPara->IsVisible(),"Selected Paragraph invisible ?!"); +#endif + Paragraph* pPrev= pOwner->pParaList->GetParagraph( aSel.nStartPara-1 ); + + if( !pPrev->IsVisible() && ( pPrev->GetDepth() == nNewDepth ) ) + { + // Predecessor is collapsed and is on the same level + // => find next visible paragraph and expand it + pPrev = pOwner->pParaList->GetParent( pPrev ); + while( !pPrev->IsVisible() ) + pPrev = pOwner->pParaList->GetParent( pPrev ); + + pOwner->Expand( pPrev ); + pOwner->InvalidateBullet(pOwner->pParaList->GetAbsPos(pPrev)); + } + } + + pOwner->nDepthChangedHdlPrevDepth = nOldDepth; + ParaFlag nPrevFlags = pPara->nFlags; + + pOwner->ImplInitDepth( nPara, nNewDepth, true ); + pOwner->ImplCalcBulletText( nPara, false, false ); + + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + pOwner->ImplSetLevelDependentStyleSheet( nPara ); + + // Notify App + pOwner->DepthChangedHdl(pPara, nPrevFlags); + } + else + { + // Needs at least a repaint... + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nPara, 0, nPara, 0 ) ); + } + } + + sal_Int32 nParas = pOwner->pParaList->GetParagraphCount(); + for ( sal_Int32 n = aSel.nEndPara+1; n < nParas; n++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( n ); + if ( pPara->GetDepth() < nMinDepth ) + break; + pOwner->ImplCalcBulletText( n, false, false ); + } + + if ( bUpdate ) + { + pEditView->SetEditEngineUpdateLayout( true ); + pEditView->ShowCursor(); + } + + if( bUndo ) + pOwner->UndoActionEnd(); +} + +void OutlinerView::AdjustHeight( tools::Long nDY ) +{ + pEditView->MoveParagraphs( nDY ); +} + +tools::Rectangle OutlinerView::GetVisArea() const +{ + return pEditView->GetVisArea(); +} + +void OutlinerView::Expand() +{ + ParaRange aParas = ImpGetSelectedParagraphs( false ); + ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, true ); +} + + +void OutlinerView::Collapse() +{ + ParaRange aParas = ImpGetSelectedParagraphs( false ); + ImplExpandOrCollaps( aParas.nStartPara, aParas.nEndPara, false ); +} + + +void OutlinerView::ExpandAll() +{ + ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, true ); +} + + +void OutlinerView::CollapseAll() +{ + ImplExpandOrCollaps( 0, pOwner->pParaList->GetParagraphCount()-1, false ); +} + +void OutlinerView::ImplExpandOrCollaps( sal_Int32 nStartPara, sal_Int32 nEndPara, bool bExpand ) +{ + bool bUpdate = pOwner->SetUpdateLayout( false ); + + bool bUndo = !pOwner->IsInUndo() && pOwner->IsUndoEnabled(); + if( bUndo ) + pOwner->UndoActionStart( bExpand ? OLUNDO_EXPAND : OLUNDO_COLLAPSE ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + bool bDone = bExpand ? pOwner->Expand( pPara ) : pOwner->Collapse( pPara ); + if( bDone ) + { + // The line under the paragraph should disappear ... + pOwner->pEditEngine->QuickMarkToBeRepainted( nPara ); + } + } + + if( bUndo ) + pOwner->UndoActionEnd(); + + if ( bUpdate ) + { + pOwner->SetUpdateLayout( true ); + pEditView->ShowCursor(); + } +} + +void OutlinerView::InsertText( const OutlinerParaObject& rParaObj ) +{ + // Like Paste, only EditView::Insert, instead of EditView::Paste. + // Actually not quite true that possible indentations must be corrected, + // but that comes later by a universal import. The indentation level is + // then determined right in the Inserted method. + // Possible structure: + // pImportInfo with DestPara, DestPos, nFormat, pParaObj... + // Possibly problematic: + // EditEngine, RTF => Splitting the area, later join together. + + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return; + + pOwner->UndoActionStart( OLUNDO_INSERT ); + + const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); + sal_Int32 nStart, nParaCount; + nParaCount = pOwner->pEditEngine->GetParagraphCount(); + sal_uInt16 nSize = ImpInitPaste( nStart ); + pEditView->InsertText( rParaObj.GetTextObject() ); + ImpPasted( nStart, nParaCount, nSize); + pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); + + pOwner->UndoActionEnd(); + + pEditView->ShowCursor(); +} + + +void OutlinerView::Cut() +{ + if ( !ImpCalcSelectedPages( false ) || pOwner->ImpCanDeleteSelectedPages( this ) ) { + pEditView->Cut(); + // Chaining handling + aEndCutPasteLink.Call(nullptr); + } +} + +void OutlinerView::PasteSpecial(SotClipboardFormatId format) +{ + Paste( true, format ); +} + +void OutlinerView::Paste( bool bUseSpecial, SotClipboardFormatId format) +{ + if ( ImpCalcSelectedPages( false ) && !pOwner->ImpCanDeleteSelectedPages( this ) ) + return; + + pOwner->UndoActionStart( OLUNDO_INSERT ); + + const bool bPrevUpdateLayout = pOwner->pEditEngine->SetUpdateLayout( false ); + pOwner->bPasting = true; + + if ( bUseSpecial ) + pEditView->PasteSpecial(format); + else + pEditView->Paste(); + + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + { + const sal_Int32 nParaCount = pOwner->pEditEngine->GetParagraphCount(); + + for( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ ) + pOwner->ImplSetLevelDependentStyleSheet( nPara ); + } + + pEditView->SetEditEngineUpdateLayout( bPrevUpdateLayout ); + pOwner->UndoActionEnd(); + pEditView->ShowCursor(); + + // Chaining handling + // NOTE: We need to do this last because it pEditView may be deleted if a switch of box occurs + aEndCutPasteLink.Call(nullptr); +} + +void OutlinerView::CreateSelectionList (std::vector<Paragraph*> &aSelList) +{ + ParaRange aParas = ImpGetSelectedParagraphs( true ); + + for ( sal_Int32 nPara = aParas.nStartPara; nPara <= aParas.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + aSelList.push_back(pPara); + } +} + +void OutlinerView::SetStyleSheet(const OUString& rStyleName) +{ + ParaRange aParas = ImpGetSelectedParagraphs(false); + + auto pStyle = pOwner->GetStyleSheetPool()->Find(rStyleName, SfxStyleFamily::Para); + if (!pStyle) + return; + + for (sal_Int32 nPara = aParas.nStartPara; nPara <= aParas.nEndPara; nPara++) + pOwner->SetStyleSheet(nPara, static_cast<SfxStyleSheet*>(pStyle)); +} + +const SfxStyleSheet* OutlinerView::GetStyleSheet() const +{ + return pEditView->GetStyleSheet(); +} + +SfxStyleSheet* OutlinerView::GetStyleSheet() +{ + return pEditView->GetStyleSheet(); +} + +PointerStyle OutlinerView::GetPointer( const Point& rPosPixel ) +{ + MouseTarget eTarget; + ImpCheckMousePos( rPosPixel, eTarget ); + + PointerStyle ePointerStyle = PointerStyle::Arrow; + if ( eTarget == MouseTarget::Text ) + { + ePointerStyle = GetOutliner()->IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text; + } + else if ( eTarget == MouseTarget::Hypertext ) + { + ePointerStyle = PointerStyle::RefHand; + } + else if ( eTarget == MouseTarget::Bullet ) + { + ePointerStyle = PointerStyle::Move; + } + + return ePointerStyle; +} + + +sal_Int32 OutlinerView::ImpInitPaste( sal_Int32& rStart ) +{ + pOwner->bPasting = true; + ESelection aSelection( pEditView->GetSelection() ); + aSelection.Adjust(); + rStart = aSelection.nStartPara; + sal_Int32 nSize = aSelection.nEndPara - aSelection.nStartPara + 1; + return nSize; +} + + +void OutlinerView::ImpPasted( sal_Int32 nStart, sal_Int32 nPrevParaCount, sal_Int32 nSize) +{ + pOwner->bPasting = false; + sal_Int32 nCurParaCount = pOwner->pEditEngine->GetParagraphCount(); + if( nCurParaCount < nPrevParaCount ) + nSize = nSize - ( nPrevParaCount - nCurParaCount ); + else + nSize = nSize + ( nCurParaCount - nPrevParaCount ); + pOwner->ImpTextPasted( nStart, nSize ); +} + +bool OutlinerView::Command(const CommandEvent& rCEvt) +{ + return pEditView->Command(rCEvt); +} + +void OutlinerView::SelectRange( sal_Int32 nFirst, sal_Int32 nCount ) +{ + sal_Int32 nLast = nFirst+nCount; + nCount = pOwner->pParaList->GetParagraphCount(); + if( nLast <= nCount ) + nLast = nCount - 1; + ESelection aSel( nFirst, 0, nLast, EE_TEXTPOS_ALL ); + pEditView->SetSelection( aSel ); +} + + +sal_Int32 OutlinerView::ImpCalcSelectedPages( bool bIncludeFirstSelected ) +{ + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + sal_Int32 nPages = 0; + sal_Int32 nFirstPage = EE_PARA_MAX_COUNT; + sal_Int32 nStartPara = aSel.nStartPara; + if ( !bIncludeFirstSelected ) + nStartPara++; // All paragraphs after StartPara will be deleted + for ( sal_Int32 nPara = nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "ImpCalcSelectedPages: invalid Selection? "); + if( pPara->HasFlag(ParaFlag::ISPAGE) ) + { + nPages++; + if( nFirstPage == EE_PARA_MAX_COUNT ) + nFirstPage = nPara; + } + } + + if( nPages ) + { + pOwner->nDepthChangedHdlPrevDepth = nPages; + pOwner->mnFirstSelPage = nFirstPage; + } + + return nPages; +} + + +void OutlinerView::ToggleBullets() +{ + pOwner->UndoActionStart( OLUNDO_DEPTH ); + + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + sal_Int16 nNewDepth = -2; + const SvxNumRule* pDefaultBulletNumRule = nullptr; + + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "OutlinerView::ToggleBullets(), illegal selection?"); + + if( pPara ) + { + if( nNewDepth == -2 ) + { + nNewDepth = (pOwner->GetDepth(nPara) == -1) ? 0 : -1; + if ( nNewDepth == 0 ) + { + // determine default numbering rule for bullets + const ESelection aSelection(nPara, 0); + const SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); + const SfxPoolItem& rPoolItem = aTmpSet.GetPool()->GetDefaultItem( EE_PARA_NUMBULLET ); + const SvxNumBulletItem* pNumBulletItem = dynamic_cast< const SvxNumBulletItem* >(&rPoolItem); + pDefaultBulletNumRule = pNumBulletItem ? &pNumBulletItem->GetNumRule() : nullptr; + } + } + + pOwner->SetDepth( pPara, nNewDepth ); + + if( nNewDepth == -1 ) + { + const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); + if ( rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET ) + { + SfxItemSet aAttrs(rAttrs); + aAttrs.ClearItem( EE_PARA_BULLETSTATE ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + else + { + if ( pDefaultBulletNumRule ) + { + const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat( nPara ); + if ( !pFmt + || ( pFmt->GetNumberingType() != SVX_NUM_BITMAP + && pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL ) ) + { + SfxItemSet aAttrs( pOwner->GetParaAttribs( nPara ) ); + SvxNumRule aNewNumRule( *pDefaultBulletNumRule ); + aAttrs.Put( SvxNumBulletItem( std::move(aNewNumRule), EE_PARA_NUMBULLET ) ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + } + } + } + + const sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); + pOwner->ImplCheckParagraphs( aSel.nStartPara, nParaCount ); + + sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; + pOwner->pEditEngine->QuickMarkInvalid( ESelection( aSel.nStartPara, 0, nEndPara, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + + pOwner->UndoActionEnd(); +} + + +void OutlinerView::ToggleBulletsNumbering( + const bool bToggle, + const bool bHandleBullets, + const SvxNumRule* pNumRule ) +{ + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + + bool bToggleOn = true; + if ( bToggle ) + { + bToggleOn = false; + const sal_Int16 nBulletNumberingStatus( pOwner->GetBulletsNumberingStatus( aSel.nStartPara, aSel.nEndPara ) ); + if ( nBulletNumberingStatus != 0 && bHandleBullets ) + { + // not all paragraphs have bullets and method called to toggle bullets --> bullets on + bToggleOn = true; + } + else if ( nBulletNumberingStatus != 1 && !bHandleBullets ) + { + // not all paragraphs have numbering and method called to toggle numberings --> numberings on + bToggleOn = true; + } + } + if ( bToggleOn ) + { + // apply bullets/numbering for selected paragraphs + ApplyBulletsNumbering( bHandleBullets, pNumRule, bToggle, true ); + } + else + { + // switch off bullets/numbering for selected paragraphs + SwitchOffBulletsNumbering( true ); + } +} + +void OutlinerView::EnsureNumberingIsOn() +{ + pOwner->UndoActionStart(OLUNDO_DEPTH); + + ESelection aSel(pEditView->GetSelection()); + aSel.Adjust(); + + const bool bUpdate = pOwner->pEditEngine->IsUpdateLayout(); + pOwner->pEditEngine->SetUpdateLayout(false); + + for (sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); + DBG_ASSERT(pPara, "OutlinerView::EnableBullets(), illegal selection?"); + + if (pPara && pOwner->GetDepth(nPara) == -1) + pOwner->SetDepth(pPara, 0); + } + + sal_Int32 nParaCount = pOwner->pParaList->GetParagraphCount(); + pOwner->ImplCheckParagraphs(aSel.nStartPara, nParaCount); + + const sal_Int32 nEndPara = (nParaCount > 0) ? nParaCount-1 : nParaCount; + pOwner->pEditEngine->QuickMarkInvalid(ESelection(aSel.nStartPara, 0, nEndPara, 0)); + + pOwner->pEditEngine->SetUpdateLayout(bUpdate); + + pOwner->UndoActionEnd(); +} + +void OutlinerView::ApplyBulletsNumbering( + const bool bHandleBullets, + const SvxNumRule* pNewNumRule, + const bool bCheckCurrentNumRuleBeforeApplyingNewNumRule, + const bool bAtSelection ) +{ + if (!pOwner || !pOwner->pEditEngine || !pOwner->pParaList) + { + return; + } + + pOwner->UndoActionStart(OLUNDO_DEPTH); + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout(false); + + sal_Int32 nStartPara = 0; + sal_Int32 nEndPara = 0; + if ( bAtSelection ) + { + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + nStartPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + nStartPara = 0; + nEndPara = pOwner->pParaList->GetParagraphCount() - 1; + } + + for (sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph(nPara); + DBG_ASSERT(pPara, "OutlinerView::ApplyBulletsNumbering(..), illegal selection?"); + + if (pPara) + { + const sal_Int16 nDepth = pOwner->GetDepth(nPara); + if ( nDepth == -1 ) + { + pOwner->SetDepth( pPara, 0 ); + } + + const SfxItemSet& rAttrs = pOwner->GetParaAttribs(nPara); + SfxItemSet aAttrs(rAttrs); + aAttrs.Put(SfxBoolItem(EE_PARA_BULLETSTATE, true)); + + // apply new numbering rule + if ( pNewNumRule ) + { + bool bApplyNumRule = false; + if ( !bCheckCurrentNumRuleBeforeApplyingNewNumRule ) + { + bApplyNumRule = true; + } + else + { + const SvxNumberFormat* pFmt = pOwner ->GetNumberFormat(nPara); + if (!pFmt) + { + bApplyNumRule = true; + } + else + { + sal_Int16 nNumType = pFmt->GetNumberingType(); + if ( bHandleBullets + && nNumType != SVX_NUM_BITMAP && nNumType != SVX_NUM_CHAR_SPECIAL) + { + // Set to Normal bullet, old bullet type is Numbering bullet. + bApplyNumRule = true; + } + else if ( !bHandleBullets + && (nNumType == SVX_NUM_BITMAP || nNumType == SVX_NUM_CHAR_SPECIAL)) + { + // Set to Numbering bullet, old bullet type is Normal bullet. + bApplyNumRule = true; + } + } + } + + if ( bApplyNumRule ) + { + SvxNumRule aNewRule(*pNewNumRule); + + // Get old bullet space. + { + const SvxNumBulletItem* pNumBulletItem = rAttrs.GetItemIfSet(EE_PARA_NUMBULLET, false); + if (pNumBulletItem) + { + // Use default value when has not contain bullet item. + ESelection aSelection(nPara, 0); + SfxItemSet aTmpSet(pOwner->pEditEngine->GetAttribs(aSelection)); + pNumBulletItem = aTmpSet.GetItem(EE_PARA_NUMBULLET); + } + + if (pNumBulletItem) + { + const sal_uInt16 nLevelCnt = std::min(pNumBulletItem->GetNumRule().GetLevelCount(), aNewRule.GetLevelCount()); + for ( sal_uInt16 nLevel = 0; nLevel < nLevelCnt; ++nLevel ) + { + const SvxNumberFormat* pOldFmt = pNumBulletItem->GetNumRule().Get(nLevel); + const SvxNumberFormat* pNewFmt = aNewRule.Get(nLevel); + if (pOldFmt && pNewFmt && (pOldFmt->GetFirstLineOffset() != pNewFmt->GetFirstLineOffset() || pOldFmt->GetAbsLSpace() != pNewFmt->GetAbsLSpace())) + { + SvxNumberFormat aNewFmtClone(*pNewFmt); + aNewFmtClone.SetFirstLineOffset(pOldFmt->GetFirstLineOffset()); + aNewFmtClone.SetAbsLSpace(pOldFmt->GetAbsLSpace()); + aNewRule.SetLevel(nLevel, &aNewFmtClone); + } + } + } + } + + aAttrs.Put(SvxNumBulletItem(std::move(aNewRule), EE_PARA_NUMBULLET)); + } + } + pOwner->SetParaAttribs(nPara, aAttrs); + } + } + + const sal_uInt16 nParaCount = static_cast<sal_uInt16>(pOwner->pParaList->GetParagraphCount()); + pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + + pOwner->UndoActionEnd(); +} + + +void OutlinerView::SwitchOffBulletsNumbering( + const bool bAtSelection ) +{ + sal_Int32 nStartPara = 0; + sal_Int32 nEndPara = 0; + if ( bAtSelection ) + { + ESelection aSel( pEditView->GetSelection() ); + aSel.Adjust(); + nStartPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + nStartPara = 0; + nEndPara = pOwner->pParaList->GetParagraphCount() - 1; + } + + pOwner->UndoActionStart( OLUNDO_DEPTH ); + const bool bUpdate = pOwner->pEditEngine->SetUpdateLayout( false ); + + for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; ++nPara ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + DBG_ASSERT(pPara, "OutlinerView::SwitchOffBulletsNumbering(...), illegal paragraph index?"); + + if( pPara ) + { + pOwner->SetDepth( pPara, -1 ); + + const SfxItemSet& rAttrs = pOwner->GetParaAttribs( nPara ); + if (rAttrs.GetItemState( EE_PARA_BULLETSTATE ) == SfxItemState::SET) + { + SfxItemSet aAttrs(rAttrs); + aAttrs.ClearItem( EE_PARA_BULLETSTATE ); + pOwner->SetParaAttribs( nPara, aAttrs ); + } + } + } + + const sal_uInt16 nParaCount = static_cast<sal_uInt16>(pOwner->pParaList->GetParagraphCount()); + pOwner->ImplCheckParagraphs( nStartPara, nParaCount ); + pOwner->pEditEngine->QuickMarkInvalid( ESelection( nStartPara, 0, nParaCount, 0 ) ); + + pOwner->pEditEngine->SetUpdateLayout( bUpdate ); + pOwner->UndoActionEnd(); +} + + +void OutlinerView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs ) +{ + RemoveAttribs( bRemoveParaAttribs, true /*keep language attribs*/ ); +} + +void OutlinerView::RemoveAttribs( bool bRemoveParaAttribs, bool bKeepLanguages ) +{ + bool bUpdate = pOwner->SetUpdateLayout( false ); + pOwner->UndoActionStart( OLUNDO_ATTR ); + if (bKeepLanguages) + pEditView->RemoveAttribsKeepLanguages( bRemoveParaAttribs ); + else + pEditView->RemoveAttribs( bRemoveParaAttribs ); + if ( bRemoveParaAttribs ) + { + // Loop through all paragraphs and set indentation and level + ESelection aSel = pEditView->GetSelection(); + aSel.Adjust(); + for ( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ ) + { + Paragraph* pPara = pOwner->pParaList->GetParagraph( nPara ); + pOwner->ImplInitDepth( nPara, pPara->GetDepth(), false ); + } + } + pOwner->UndoActionEnd(); + pOwner->SetUpdateLayout( bUpdate ); +} + + +// ====================== Simple pass-through ======================= + + +void OutlinerView::InsertText( const OUString& rNew, bool bSelect ) +{ + if( pOwner->bFirstParaIsEmpty ) + pOwner->Insert( OUString() ); + pEditView->InsertText( rNew, bSelect ); +} + +void OutlinerView::SetVisArea( const tools::Rectangle& rRect ) +{ + pEditView->SetVisArea( rRect ); +} + + +void OutlinerView::SetSelection( const ESelection& rSel ) +{ + pEditView->SetSelection( rSel ); +} + +void OutlinerView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects) const +{ + pEditView->GetSelectionRectangles(rLogicRects); +} + +void OutlinerView::SetReadOnly( bool bReadOnly ) +{ + pEditView->SetReadOnly( bReadOnly ); +} + +bool OutlinerView::IsReadOnly() const +{ + return pEditView->IsReadOnly(); +} + +bool OutlinerView::HasSelection() const +{ + return pEditView->HasSelection(); +} + +void OutlinerView::ShowCursor( bool bGotoCursor, bool bActivate ) +{ + pEditView->ShowCursor( bGotoCursor, /*bForceVisCursor=*/true, bActivate ); +} + +void OutlinerView::HideCursor(bool bDeactivate) +{ + pEditView->HideCursor(bDeactivate); +} + +void OutlinerView::SetWindow( vcl::Window* pWin ) +{ + pEditView->SetWindow( pWin ); +} + +vcl::Window* OutlinerView::GetWindow() const +{ + return pEditView->GetWindow(); +} + +void OutlinerView::SetOutputArea( const tools::Rectangle& rRect ) +{ + pEditView->SetOutputArea( rRect ); +} + +tools::Rectangle const & OutlinerView::GetOutputArea() const +{ + return pEditView->GetOutputArea(); +} + +OUString OutlinerView::GetSelected() const +{ + return pEditView->GetSelected(); +} + +void OutlinerView::StartSpeller(weld::Widget* pDialogParent) +{ + pEditView->StartSpeller(pDialogParent); +} + +EESpellState OutlinerView::StartThesaurus(weld::Widget* pDialogParent) +{ + return pEditView->StartThesaurus(pDialogParent); +} + +void OutlinerView::StartTextConversion(weld::Widget* pDialogParent, + LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, + sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc ) +{ + if ( + (LANGUAGE_KOREAN == nSrcLang && LANGUAGE_KOREAN == nDestLang) || + (LANGUAGE_CHINESE_SIMPLIFIED == nSrcLang && LANGUAGE_CHINESE_TRADITIONAL == nDestLang) || + (LANGUAGE_CHINESE_TRADITIONAL == nSrcLang && LANGUAGE_CHINESE_SIMPLIFIED == nDestLang) + ) + { + pEditView->StartTextConversion(pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc); + } + else + { + OSL_FAIL( "unexpected language" ); + } +} + + +sal_Int32 OutlinerView::StartSearchAndReplace( const SvxSearchItem& rSearchItem ) +{ + return pEditView->StartSearchAndReplace( rSearchItem ); +} + +void OutlinerView::TransliterateText( TransliterationFlags nTransliterationMode ) +{ + pEditView->TransliterateText( nTransliterationMode ); +} + +ESelection OutlinerView::GetSelection() const +{ + return pEditView->GetSelection(); +} + + +void OutlinerView::Scroll( tools::Long nHorzScroll, tools::Long nVertScroll ) +{ + pEditView->Scroll( nHorzScroll, nVertScroll ); +} + +void OutlinerView::SetControlWord( EVControlBits nWord ) +{ + pEditView->SetControlWord( nWord ); +} + +EVControlBits OutlinerView::GetControlWord() const +{ + return pEditView->GetControlWord(); +} + +void OutlinerView::SetAnchorMode( EEAnchorMode eMode ) +{ + pEditView->SetAnchorMode( eMode ); +} + +EEAnchorMode OutlinerView::GetAnchorMode() const +{ + return pEditView->GetAnchorMode(); +} + +void OutlinerView::Copy() +{ + pEditView->Copy(); +} + +void OutlinerView::InsertField( const SvxFieldItem& rFld ) +{ + pEditView->InsertField( rFld ); +} + +const SvxFieldItem* OutlinerView::GetFieldUnderMousePointer() const +{ + return pEditView->GetFieldUnderMousePointer(); +} + +const SvxFieldItem* OutlinerView::GetFieldAtSelection(bool bAlsoCheckBeforeCursor) const +{ + return pEditView->GetFieldAtSelection(bAlsoCheckBeforeCursor); +} + +void OutlinerView::SelectFieldAtCursor() +{ + pEditView->SelectFieldAtCursor(); +} + +void OutlinerView::SetInvalidateMore( sal_uInt16 nPixel ) +{ + pEditView->SetInvalidateMore( nPixel ); +} + + +sal_uInt16 OutlinerView::GetInvalidateMore() const +{ + return pEditView->GetInvalidateMore(); +} + + +bool OutlinerView::IsCursorAtWrongSpelledWord() +{ + return pEditView->IsCursorAtWrongSpelledWord(); +} + + +bool OutlinerView::IsWrongSpelledWordAtPos( const Point& rPosPixel ) +{ + return pEditView->IsWrongSpelledWordAtPos( rPosPixel, /*bMarkIfWrong*/false ); +} + +void OutlinerView::ExecuteSpellPopup(const Point& rPosPixel, const Link<SpellCallbackInfo&,void>& rStartDlg) +{ + pEditView->ExecuteSpellPopup(rPosPixel, rStartDlg); +} + +void OutlinerView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs ) +{ + sal_Int32 nOldParaCount = pEditView->GetEditEngine()->GetParagraphCount(); + ESelection aOldSel = pEditView->GetSelection(); + aOldSel.Adjust(); + + pEditView->Read( rInput, eFormat, pHTTPHeaderAttrs ); + + tools::Long nParaDiff = pEditView->GetEditEngine()->GetParagraphCount() - nOldParaCount; + sal_Int32 nChangesStart = aOldSel.nStartPara; + sal_Int32 nChangesEnd = nChangesStart + nParaDiff + (aOldSel.nEndPara-aOldSel.nStartPara); + + for ( sal_Int32 n = nChangesStart; n <= nChangesEnd; n++ ) + { + if ( pOwner->GetOutlinerMode() == OutlinerMode::OutlineObject ) + pOwner->ImplSetLevelDependentStyleSheet( n ); + } + + pOwner->ImpFilterIndents( nChangesStart, nChangesEnd ); +} + +void OutlinerView::SetBackgroundColor( const Color& rColor ) +{ + pEditView->SetBackgroundColor( rColor ); +} + +void OutlinerView::RegisterViewShell(OutlinerViewShell* pViewShell) +{ + pEditView->RegisterViewShell(pViewShell); +} + +Color const & OutlinerView::GetBackgroundColor() const +{ + return pEditView->GetBackgroundColor(); +} + +SfxItemSet OutlinerView::GetAttribs() +{ + return pEditView->GetAttribs(); +} + +SvtScriptType OutlinerView::GetSelectedScriptType() const +{ + return pEditView->GetSelectedScriptType(); +} + +OUString OutlinerView::GetSurroundingText() const +{ + return pEditView->GetSurroundingText(); +} + +Selection OutlinerView::GetSurroundingTextSelection() const +{ + return pEditView->GetSurroundingTextSelection(); +} + +bool OutlinerView::DeleteSurroundingText(const Selection& rSelection) +{ + return pEditView->DeleteSurroundingText(rSelection); +} + +// ===== some code for thesaurus sub menu within context menu + +namespace { + +bool isSingleScriptType( SvtScriptType nScriptType ) +{ + sal_uInt8 nScriptCount = 0; + + if (nScriptType & SvtScriptType::LATIN) + ++nScriptCount; + if (nScriptType & SvtScriptType::ASIAN) + ++nScriptCount; + if (nScriptType & SvtScriptType::COMPLEX) + ++nScriptCount; + + return nScriptCount == 1; +} + +} + +// returns: true if a word for thesaurus look-up was found at the current cursor position. +// The status string will be word + iso language string (e.g. "light#en-US") +bool GetStatusValueForThesaurusFromContext( + OUString &rStatusVal, + LanguageType &rLang, + const EditView &rEditView ) +{ + // get text and locale for thesaurus look up + OUString aText; + EditEngine *pEditEngine = rEditView.GetEditEngine(); + ESelection aTextSel( rEditView.GetSelection() ); + if (!aTextSel.HasRange()) + aTextSel = pEditEngine->GetWord( aTextSel, i18n::WordType::DICTIONARY_WORD ); + aText = pEditEngine->GetText( aTextSel ); + aTextSel.Adjust(); + + if (!isSingleScriptType(pEditEngine->GetScriptType(aTextSel))) + return false; + + LanguageType nLang = pEditEngine->GetLanguage( aTextSel.nStartPara, aTextSel.nStartPos ).nLang; + OUString aLangText( LanguageTag::convertToBcp47( nLang ) ); + + // set word and locale to look up as status value + rStatusVal = aText + "#" + aLangText; + rLang = nLang; + + return aText.getLength() > 0; +} + + +void ReplaceTextWithSynonym( EditView &rEditView, const OUString &rSynonmText ) +{ + // get selection to use + ESelection aCurSel( rEditView.GetSelection() ); + if (!rEditView.HasSelection()) + { + // select the same word that was used in GetStatusValueForThesaurusFromContext by calling GetWord. + // (In the end both functions will call ImpEditEngine::SelectWord) + rEditView.SelectCurrentWord( i18n::WordType::DICTIONARY_WORD ); + aCurSel = rEditView.GetSelection(); + } + + // replace word ... + rEditView.InsertText( rSynonmText ); + rEditView.ShowCursor( true, false ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/overflowingtxt.cxx b/editeng/source/outliner/overflowingtxt.cxx new file mode 100644 index 0000000000..42316fa1fa --- /dev/null +++ b/editeng/source/outliner/overflowingtxt.cxx @@ -0,0 +1,227 @@ +/* -*- 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 <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <editeng/overflowingtxt.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editdata.hxx> + +#include <editdoc.hxx> +#include <utility> + + +std::optional<OutlinerParaObject> TextChainingUtils::JuxtaposeParaObject( + css::uno::Reference< css::datatransfer::XTransferable > const & xOverflowingContent, + Outliner *pOutl, + OutlinerParaObject const *pNextPObj) +{ + if (!pNextPObj) { + pOutl->SetToEmptyText(); + } else { + pOutl->SetText(*pNextPObj); + } + + // Special case: if only empty text remove it at the end + bool bOnlyOneEmptyPara = !pNextPObj || + (pOutl->GetParagraphCount() == 1 && + pNextPObj->GetTextObject().GetText(0).isEmpty()); + + EditEngine &rEditEngine = const_cast<EditEngine &>(pOutl->GetEditEngine()); + + // XXX: this code should be moved in Outliner directly + // creating Outliner::InsertText(...transferable...) + EditSelection aStartSel(rEditEngine.CreateSelection(ESelection(0,0))); + EditSelection aNewSel = rEditEngine.InsertText(xOverflowingContent, + OUString(), + aStartSel.Min(), + true); + + if (!bOnlyOneEmptyPara) { + // Separate Paragraphs + rEditEngine.InsertParaBreak(aNewSel); + } + + + return pOutl->CreateParaObject(); +} + +std::optional<OutlinerParaObject> TextChainingUtils::DeeplyMergeParaObject( + css::uno::Reference< css::datatransfer::XTransferable > const & xOverflowingContent, + Outliner *pOutl, + OutlinerParaObject const *pNextPObj) +{ + if (!pNextPObj) { + pOutl->SetToEmptyText(); + } else { + pOutl->SetText(*pNextPObj); + } + + EditEngine &rEditEngine = const_cast<EditEngine &>(pOutl->GetEditEngine()); + + // XXX: this code should be moved in Outliner directly + // creating Outliner::InsertText(...transferable...) + EditSelection aStartSel(rEditEngine.CreateSelection(ESelection(0,0))); + // We don't need to mark the selection + // EditSelection aNewSel = + rEditEngine.InsertText(xOverflowingContent, + OUString(), + aStartSel.Min(), + true); + + return pOutl->CreateParaObject(); +} + +css::uno::Reference< css::datatransfer::XTransferable > TextChainingUtils::CreateTransferableFromText(Outliner const *pOutl) +{ + const EditEngine &rEditEngine = pOutl->GetEditEngine(); + sal_Int32 nLastPara = pOutl->GetParagraphCount()-1; + ESelection aWholeTextSel(0, 0, nLastPara, rEditEngine.GetTextLen(nLastPara)); + + return rEditEngine.CreateTransferable(aWholeTextSel); +} + + + +OverflowingText::OverflowingText(css::uno::Reference< css::datatransfer::XTransferable > xOverflowingContent) : + mxOverflowingContent(std::move(xOverflowingContent)) +{ + +} + + + +NonOverflowingText::NonOverflowingText(const ESelection &aSel, bool bLastParaInterrupted) + : maContentSel(aSel) + , mbLastParaInterrupted(bLastParaInterrupted) +{ +} + +bool NonOverflowingText::IsLastParaInterrupted() const +{ + return mbLastParaInterrupted; +} + + +std::optional<OutlinerParaObject> NonOverflowingText::RemoveOverflowingText(Outliner *pOutliner) const +{ + pOutliner->QuickDelete(maContentSel); + SAL_INFO("editeng.chaining", "Deleting selection from (Para: " << maContentSel.nStartPara + << ", Pos: " << maContentSel.nStartPos << ") to (Para: " << maContentSel.nEndPara + << ", Pos: " << maContentSel.nEndPos << ")"); + return pOutliner->CreateParaObject(); +} + +ESelection NonOverflowingText::GetOverflowPointSel() const +{ + //return getLastPositionSel(mpContentTextObj); + + // return the starting point of the selection we are removing + return ESelection(maContentSel.nStartPara, maContentSel.nStartPos); //XXX +} + +// The equivalent of ToParaObject for OverflowingText. Here we are prepending the overflowing text to the old dest box's text +// XXX: In a sense a better name for OverflowingText and NonOverflowingText are respectively DestLinkText and SourceLinkText +std::optional<OutlinerParaObject> OverflowingText::JuxtaposeParaObject(Outliner *pOutl, OutlinerParaObject const *pNextPObj) +{ + return TextChainingUtils::JuxtaposeParaObject(mxOverflowingContent, pOutl, pNextPObj); +} + +std::optional<OutlinerParaObject> OverflowingText::DeeplyMergeParaObject(Outliner *pOutl, OutlinerParaObject const *pNextPObj) +{ + return TextChainingUtils::DeeplyMergeParaObject(mxOverflowingContent, pOutl, pNextPObj); +} + + +OFlowChainedText::OFlowChainedText(Outliner const *pOutl, bool bIsDeepMerge) +{ + mpOverflowingTxt = pOutl->GetOverflowingText(); + mpNonOverflowingTxt = pOutl->GetNonOverflowingText(); + + mbIsDeepMerge = bIsDeepMerge; +} + +OFlowChainedText::~OFlowChainedText() +{ +} + + +ESelection OFlowChainedText::GetOverflowPointSel() const +{ + return mpNonOverflowingTxt->GetOverflowPointSel(); +} + +std::optional<OutlinerParaObject> OFlowChainedText::InsertOverflowingText(Outliner *pOutliner, OutlinerParaObject const *pTextToBeMerged) +{ + // Just return the roughly merged paras for now + if (!mpOverflowingTxt) + return std::nullopt; + + if (mbIsDeepMerge) { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - OF] Deep merging paras" ); + return mpOverflowingTxt->DeeplyMergeParaObject(pOutliner, pTextToBeMerged ); + } else { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - OF] Juxtaposing paras" ); + return mpOverflowingTxt->JuxtaposeParaObject(pOutliner, pTextToBeMerged ); + } +} + + +std::optional<OutlinerParaObject> OFlowChainedText::RemoveOverflowingText(Outliner *pOutliner) +{ + if (!mpNonOverflowingTxt) + return std::nullopt; + + return mpNonOverflowingTxt->RemoveOverflowingText(pOutliner); +} + +bool OFlowChainedText::IsLastParaInterrupted() const +{ + return mpNonOverflowingTxt->IsLastParaInterrupted(); +} + + + +UFlowChainedText::UFlowChainedText(Outliner const *pOutl, bool bIsDeepMerge) +{ + mxUnderflowingTxt = TextChainingUtils::CreateTransferableFromText(pOutl); + mbIsDeepMerge = bIsDeepMerge; +} + +std::optional<OutlinerParaObject> UFlowChainedText::CreateMergedUnderflowParaObject(Outliner *pOutl, OutlinerParaObject const *pNextLinkWholeText) +{ + std::optional<OutlinerParaObject> pNewText; + + if (mbIsDeepMerge) { + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - UF] Deep merging paras" ); + pNewText = TextChainingUtils::DeeplyMergeParaObject(mxUnderflowingTxt, pOutl, pNextLinkWholeText); + } else { + // NewTextForCurBox = Txt(CurBox) ++ Txt(NextBox) + SAL_INFO("editeng.chaining", "[TEXTCHAINFLOW - UF] Juxtaposing paras" ); + pNewText = TextChainingUtils::JuxtaposeParaObject(mxUnderflowingTxt, pOutl, pNextLinkWholeText); + } + + return pNewText; + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/paralist.cxx b/editeng/source/outliner/paralist.cxx new file mode 100644 index 0000000000..5b9f214498 --- /dev/null +++ b/editeng/source/outliner/paralist.cxx @@ -0,0 +1,256 @@ +/* -*- 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 "paralist.hxx" + +#include <editeng/outliner.hxx> +#include <editeng/numdef.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <libxml/xmlwriter.h> + +ParagraphData::ParagraphData() +: nDepth( -1 ) +, mnNumberingStartValue( -1 ) +, mbParaIsNumberingRestart( false ) +{ +} + +bool ParagraphData::operator==(const ParagraphData& rCandidate) const +{ + return (nDepth == rCandidate.nDepth + && mnNumberingStartValue == rCandidate.mnNumberingStartValue + && mbParaIsNumberingRestart == rCandidate.mbParaIsNumberingRestart); +} + +Paragraph::Paragraph( sal_Int16 nDDepth ) +: aBulSize( -1, -1) +{ + + DBG_ASSERT( ( nDDepth >= -1 ) && ( nDDepth < SVX_MAX_NUM ), "Paragraph-CTOR: nDepth invalid!" ); + + nDepth = nDDepth; + nFlags = ParaFlag::NONE; + bVisible = true; +} + +Paragraph::Paragraph( const ParagraphData& rData ) +: aBulSize( -1, -1) +, nFlags( ParaFlag::NONE ) +, bVisible( true ) +{ + nDepth = rData.nDepth; + mnNumberingStartValue = rData.mnNumberingStartValue; + mbParaIsNumberingRestart = rData.mbParaIsNumberingRestart; +} + +Paragraph::~Paragraph() +{ +} + +void Paragraph::SetNumberingStartValue( sal_Int16 nNumberingStartValue ) +{ + mnNumberingStartValue = nNumberingStartValue; + if( mnNumberingStartValue != -1 ) + mbParaIsNumberingRestart = true; +} + +void Paragraph::SetParaIsNumberingRestart( bool bParaIsNumberingRestart ) +{ + mbParaIsNumberingRestart = bParaIsNumberingRestart; + if( !mbParaIsNumberingRestart ) + mnNumberingStartValue = -1; +} + +void Paragraph::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Paragraph")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("nDepth"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(nDepth)); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mnNumberingStartValue"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(mnNumberingStartValue)); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("mbParaIsNumberingRestart"), "%" SAL_PRIdINT32, static_cast<sal_Int32>(mbParaIsNumberingRestart)); + (void)xmlTextWriterEndElement(pWriter); +} + +void ParagraphList::Clear() +{ + maEntries.clear(); +} + +void ParagraphList::Append( std::unique_ptr<Paragraph> pPara) +{ + SAL_WARN_IF( maEntries.size() >= EE_PARA_MAX_COUNT, "editeng", "ParagraphList::Append - overflow"); + maEntries.push_back(std::move(pPara)); +} + +void ParagraphList::Insert( std::unique_ptr<Paragraph> pPara, sal_Int32 nAbsPos) +{ + SAL_WARN_IF( nAbsPos < 0 || (maEntries.size() < o3tl::make_unsigned(nAbsPos) && nAbsPos != EE_PARA_APPEND), + "editeng", "ParagraphList::Insert - bad insert position " << nAbsPos); + SAL_WARN_IF( maEntries.size() >= EE_PARA_MAX_COUNT, "editeng", "ParagraphList::Insert - overflow"); + + if (nAbsPos < 0 || maEntries.size() <= o3tl::make_unsigned(nAbsPos)) + Append( std::move(pPara) ); + else + maEntries.insert(maEntries.begin()+nAbsPos, std::move(pPara)); +} + +void ParagraphList::Remove( sal_Int32 nPara ) +{ + if (nPara < 0 || maEntries.size() <= o3tl::make_unsigned(nPara)) + { + SAL_WARN( "editeng", "ParagraphList::Remove - out of bounds " << nPara); + return; + } + + maEntries.erase(maEntries.begin() + nPara ); +} + +void ParagraphList::MoveParagraphs( sal_Int32 nStart, sal_Int32 nDest, sal_Int32 _nCount ) +{ + OSL_ASSERT(o3tl::make_unsigned(nStart) < maEntries.size() && o3tl::make_unsigned(nDest) < maEntries.size()); + + if ( (( nDest < nStart ) || ( nDest >= ( nStart + _nCount ) )) && nStart >= 0 && nDest >= 0 && _nCount >= 0 ) + { + std::vector<std::unique_ptr<Paragraph>> aParas; + auto iterBeg = maEntries.begin() + nStart; + auto iterEnd = iterBeg + _nCount; + + for (auto it = iterBeg; it != iterEnd; ++it) + aParas.push_back(std::move(*it)); + + maEntries.erase(iterBeg,iterEnd); + + if ( nDest > nStart ) + nDest -= _nCount; + + for (auto & i : aParas) + { + maEntries.insert(maEntries.begin() + nDest, std::move(i)); + ++nDest; + } + } + else + { + OSL_FAIL( "MoveParagraphs: Invalid Parameters" ); + } +} + +bool ParagraphList::HasChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ); +} + +bool ParagraphList::HasHiddenChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ) && !pNext->IsVisible(); +} + +bool ParagraphList::HasVisibleChildren( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pNext = GetParagraph( ++n ); + return pNext && ( pNext->GetDepth() > pParagraph->GetDepth() ) && pNext->IsVisible(); +} + +sal_Int32 ParagraphList::GetChildCount( Paragraph const * pParent ) const +{ + sal_Int32 nChildCount = 0; + sal_Int32 n = GetAbsPos( pParent ); + Paragraph* pPara = GetParagraph( ++n ); + while ( pPara && ( pPara->GetDepth() > pParent->GetDepth() ) ) + { + nChildCount++; + pPara = GetParagraph( ++n ); + } + return nChildCount; +} + +Paragraph* ParagraphList::GetParent( Paragraph const * pParagraph ) const +{ + sal_Int32 n = GetAbsPos( pParagraph ); + Paragraph* pPrev = GetParagraph( --n ); + while ( pPrev && ( pPrev->GetDepth() >= pParagraph->GetDepth() ) ) + { + pPrev = GetParagraph( --n ); + } + + return pPrev; +} + +void ParagraphList::Expand( Paragraph const * pParent ) +{ + sal_Int32 nChildCount = GetChildCount( pParent ); + sal_Int32 nPos = GetAbsPos( pParent ); + + for ( sal_Int32 n = 1; n <= nChildCount; n++ ) + { + Paragraph* pPara = GetParagraph( nPos+n ); + if ( !( pPara->IsVisible() ) ) + { + pPara->bVisible = true; + aVisibleStateChangedHdl.Call( *pPara ); + } + } +} + +void ParagraphList::Collapse( Paragraph const * pParent ) +{ + sal_Int32 nChildCount = GetChildCount( pParent ); + sal_Int32 nPos = GetAbsPos( pParent ); + + for ( sal_Int32 n = 1; n <= nChildCount; n++ ) + { + Paragraph* pPara = GetParagraph( nPos+n ); + if ( pPara->IsVisible() ) + { + pPara->bVisible = false; + aVisibleStateChangedHdl.Call( *pPara ); + } + } +} + +sal_Int32 ParagraphList::GetAbsPos( Paragraph const * pParent ) const +{ + sal_Int32 pos = 0; + for (auto const& entry : maEntries) + { + if (entry.get() == pParent) + return pos; + ++pos; + } + + return EE_PARA_NOT_FOUND; +} + +void ParagraphList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ParagraphList")); + for (auto const & pParagraph : maEntries) + pParagraph->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/outliner/paralist.hxx b/editeng/source/outliner/paralist.hxx new file mode 100644 index 0000000000..47413ff5ff --- /dev/null +++ b/editeng/source/outliner/paralist.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <memory> +#include <vector> + +#include <editeng/outliner.hxx> +#include <o3tl/safeint.hxx> +#include <tools/link.hxx> + +class Paragraph; +typedef struct _xmlTextWriter* xmlTextWriterPtr; + +class ParagraphList +{ +public: + void Clear(); + + sal_Int32 GetParagraphCount() const + { + size_t nSize = maEntries.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "ParagraphList::GetParagraphCount - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; + } + + Paragraph* GetParagraph( sal_Int32 nPos ) const + { + return 0 <= nPos && o3tl::make_unsigned(nPos) < maEntries.size() ? maEntries[nPos].get() : nullptr; + } + + sal_Int32 GetAbsPos( Paragraph const * pParent ) const; + + void Append( std::unique_ptr<Paragraph> pPara); + void Insert( std::unique_ptr<Paragraph> pPara, sal_Int32 nAbsPos); + void Remove( sal_Int32 nPara ); + void MoveParagraphs( sal_Int32 nStart, sal_Int32 nDest, sal_Int32 nCount ); + + Paragraph* GetParent( Paragraph const * pParagraph ) const; + bool HasChildren( Paragraph const * pParagraph ) const; + bool HasHiddenChildren( Paragraph const * pParagraph ) const; + bool HasVisibleChildren( Paragraph const * pParagraph ) const; + sal_Int32 GetChildCount( Paragraph const * pParagraph ) const; + + void Expand( Paragraph const * pParent ); + void Collapse( Paragraph const * pParent ); + + void SetVisibleStateChangedHdl( const Link<Paragraph&,void>& rLink ) { aVisibleStateChangedHdl = rLink; } + + void dumpAsXml(xmlTextWriterPtr pWriter) const; + +private: + + Link<Paragraph&,void> aVisibleStateChangedHdl; + std::vector<std::unique_ptr<Paragraph>> maEntries; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/rtf/rtfitem.cxx b/editeng/source/rtf/rtfitem.cxx new file mode 100644 index 0000000000..bf6b002f97 --- /dev/null +++ b/editeng/source/rtf/rtfitem.cxx @@ -0,0 +1,1873 @@ +/* -*- 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 <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/lspcitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/paravertalignitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/hngpnctitem.hxx> +#include <editeng/scriptspaceitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/charhiddenitem.hxx> + +#include <svtools/rtftoken.h> +#include <svl/itempool.hxx> +#include <svl/itemiter.hxx> +#include <sal/log.hxx> +#include <vcl/font.hxx> + +#include <editeng/svxrtf.hxx> +#include <editeng/editids.hrc> + +#include <limits.h> + +#define BRACELEFT '{' +#define BRACERIGHT '}' + +using namespace ::com::sun::star; +using namespace editeng; + +void SvxRTFParser::SetScriptAttr( RTF_CharTypeDef eType, SfxItemSet& rSet, + SfxPoolItem& rItem ) +{ + std::optional<sal_uInt16> pNormal; + std::optional<sal_uInt16> pCJK; + std::optional<sal_uInt16> pCTL; + switch( rItem.Which() ) + { + case SID_ATTR_CHAR_FONT: + pNormal = aPlainMap[SID_ATTR_CHAR_FONT]; + pCJK = aPlainMap[SID_ATTR_CHAR_CJK_FONT]; + pCTL = aPlainMap[SID_ATTR_CHAR_CTL_FONT]; + break; + + case SID_ATTR_CHAR_FONTHEIGHT: + pNormal = aPlainMap[SID_ATTR_CHAR_FONTHEIGHT]; + pCJK = aPlainMap[SID_ATTR_CHAR_CJK_FONTHEIGHT]; + pCTL = aPlainMap[SID_ATTR_CHAR_CTL_FONTHEIGHT]; + break; + + case SID_ATTR_CHAR_POSTURE: + pNormal = aPlainMap[SID_ATTR_CHAR_POSTURE]; + pCJK = aPlainMap[SID_ATTR_CHAR_CJK_POSTURE]; + pCTL = aPlainMap[SID_ATTR_CHAR_CTL_POSTURE]; + break; + + case SID_ATTR_CHAR_WEIGHT: + pNormal = aPlainMap[SID_ATTR_CHAR_WEIGHT]; + pCJK = aPlainMap[SID_ATTR_CHAR_CJK_WEIGHT]; + pCTL = aPlainMap[SID_ATTR_CHAR_CTL_WEIGHT]; + break; + + case SID_ATTR_CHAR_LANGUAGE: + pNormal = aPlainMap[SID_ATTR_CHAR_LANGUAGE]; + pCJK = aPlainMap[SID_ATTR_CHAR_CJK_LANGUAGE]; + pCTL = aPlainMap[SID_ATTR_CHAR_CTL_LANGUAGE]; + break; + + case 0: + // it exist no WhichId - don't set this item + break; + + default: + rSet.Put( rItem ); + break; + } + + if( DOUBLEBYTE_CHARTYPE == eType ) + { + if( bIsLeftToRightDef && pCJK ) + { + rItem.SetWhich( *pCJK ); + rSet.Put( rItem ); + } + } + else if( !bIsLeftToRightDef ) + { + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + } + else + { + if( LOW_CHARTYPE == eType ) + { + if( pNormal ) + { + rItem.SetWhich( *pNormal ); + rSet.Put( rItem ); + } + } + else if( HIGH_CHARTYPE == eType ) + { + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + } + else + { + if( pCJK ) + { + rItem.SetWhich( *pCJK ); + rSet.Put( rItem ); + } + if( pCTL ) + { + rItem.SetWhich( *pCTL ); + rSet.Put( rItem ); + } + if( pNormal ) + { + rItem.SetWhich( *pNormal ); + rSet.Put( rItem ); + } + } + } +} + + +void SvxRTFParser::ReadAttr( int nToken, SfxItemSet* pSet ) +{ + DBG_ASSERT( pSet, "A SfxItemSet has to be provided as argument!" ); + bool bFirstToken = true; + bool bContinue = true; + FontLineStyle eUnderline; + FontLineStyle eOverline; + FontEmphasisMark eEmphasis; + RTF_CharTypeDef eCharType = NOTDEF_CHARTYPE; + SvxParaVertAlignItem::Align nFontAlign; + + bool bChkStkPos = !bNewGroup && !aAttrStack.empty(); + + while( bContinue && IsParserWorking() ) // as long as known Attribute are recognized + { + switch( nToken ) + { + case RTF_PARD: + RTFPardPlain( true, &pSet ); + break; + + case RTF_PLAIN: + RTFPardPlain( false, &pSet ); + break; + + default: + do { // middle checked loop + if( !bChkStkPos ) + break; + + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + if( !pCurrent || (pCurrent->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pCurrent->nSttCnt == mxInsertPosition->GetCntIdx() )) + break; + + int nLastToken = GetStackPtr(-1)->nTokenId; + if( RTF_PARD == nLastToken || RTF_PLAIN == nLastToken ) + break; + + if (pCurrent->aAttrSet.Count() || !pCurrent->maChildList.empty() || + pCurrent->nStyleNo ) + { + // Open a new Group + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // "Set" all valid attributes up until this point + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + + aAttrStack.push_back( std::move(xNew) ); + pCurrent = aAttrStack.back().get(); + } + else + // continue to use this entry as a new one + pCurrent->SetStartPos( *mxInsertPosition ); + + pSet = &pCurrent->aAttrSet; + } while( false ); + + switch( nToken ) + { + case RTF_INTBL: + case RTF_PAGEBB: + case RTF_SBYS: + case RTF_CS: + case RTF_LS: + case RTF_ILVL: + UnknownAttrToken( nToken ); + break; + + case RTF_S: + if( bIsInReadStyleTab ) + { + if( !bFirstToken ) + SkipToken(); + bContinue = false; + } + else + { + sal_uInt16 nStyleNo = -1 == nTokenValue ? 0 : sal_uInt16(nTokenValue); + // set StyleNo to the current style on the AttrStack + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + if( !pCurrent ) + break; + + pCurrent->nStyleNo = nStyleNo; + } + break; + + case RTF_KEEP: + if (const TypedWhichId<SvxFormatSplitItem> wid = aPardMap[SID_ATTR_PARA_SPLIT]) + { + pSet->Put(SvxFormatSplitItem(false, wid)); + } + break; + + case RTF_KEEPN: + if (const TypedWhichId<SvxFormatKeepItem> wid = aPardMap[SID_ATTR_PARA_KEEP]) + { + pSet->Put(SvxFormatKeepItem(true, wid)); + } + break; + + case RTF_LEVEL: + if (const TypedWhichId<SfxInt16Item> wid = aPardMap[SID_ATTR_PARA_OUTLLEVEL]) + { + pSet->Put(SfxInt16Item(wid, static_cast<sal_uInt16>(nTokenValue))); + } + break; + + case RTF_QL: + if (const TypedWhichId<SvxAdjustItem> wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Left, wid)); + } + break; + case RTF_QR: + if (const TypedWhichId<SvxAdjustItem> wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Right, wid)); + } + break; + case RTF_QJ: + if (const TypedWhichId<SvxAdjustItem> wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Block, wid)); + } + break; + case RTF_QC: + if (const TypedWhichId<SvxAdjustItem> wid = aPardMap[SID_ATTR_PARA_ADJUST]) + { + pSet->Put(SvxAdjustItem(SvxAdjust::Center, wid)); + } + break; + + case RTF_FI: + if (const TypedWhichId<SvxLRSpaceItem> wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(pSet->Get(wid)); + sal_uInt16 nSz = 0; + if( -1 != nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetTextFirstLineOffset( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_LI: + case RTF_LIN: + if (const TypedWhichId<SvxLRSpaceItem> wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(pSet->Get(wid)); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetTextLeft( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_RI: + case RTF_RIN: + if (const TypedWhichId<SvxLRSpaceItem> wid = aPardMap[SID_ATTR_LRSPACE]) + { + SvxLRSpaceItem aLR(pSet->Get(wid)); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aLR.SetRight( nSz ); + pSet->Put( aLR ); + } + break; + + case RTF_SB: + if (const TypedWhichId<SvxULSpaceItem> wid = aPardMap[SID_ATTR_ULSPACE]) + { + SvxULSpaceItem aUL(pSet->Get(wid)); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aUL.SetUpper( nSz ); + pSet->Put( aUL ); + } + break; + + case RTF_SA: + if (const TypedWhichId<SvxULSpaceItem> wid = aPardMap[SID_ATTR_ULSPACE]) + { + SvxULSpaceItem aUL(pSet->Get(wid)); + sal_uInt16 nSz = 0; + if( 0 < nTokenValue ) + { + if( IsCalcValue() ) + CalcValue(); + nSz = sal_uInt16(nTokenValue); + } + aUL.SetLower( nSz ); + pSet->Put( aUL ); + } + break; + + case RTF_SLMULT: + if (const TypedWhichId<SvxLineSpacingItem> wid = aPardMap[SID_ATTR_PARA_LINESPACE]; + wid && 1 == nTokenValue) + { + // then switches to multi-line! + SvxLineSpacingItem aLSpace(pSet->Get(wid, false)); + + // how much do you get from the line height value? + + // Proportional-Size: + // Ie, the ratio is (n / 240) twips + + nTokenValue = 240; + if( IsCalcValue() ) + CalcValue(); + + nTokenValue = short( 100 * aLSpace.GetLineHeight() / nTokenValue ); + + aLSpace.SetPropLineSpace( static_cast<sal_uInt16>(nTokenValue) ); + aLSpace.SetLineSpaceRule( SvxLineSpaceRule::Auto ); + + pSet->Put( aLSpace ); + } + break; + + case RTF_SL: + if (const TypedWhichId<SvxLineSpacingItem> wid = aPardMap[SID_ATTR_PARA_LINESPACE]) + { + // Calculate the ratio between the default font and the + // specified size. The distance consists of the line height + // (100%) and the space above the line (20%). + SvxLineSpacingItem aLSpace(0, wid); + + nTokenValue = !bTokenHasValue ? 0 : nTokenValue; + if (1000 == nTokenValue ) + nTokenValue = 240; + + SvxLineSpaceRule eLnSpc; + if (nTokenValue < 0) + { + eLnSpc = SvxLineSpaceRule::Fix; + nTokenValue = -nTokenValue; + } + else if (nTokenValue == 0) + { + //if \sl0 is used, the line spacing is automatically + //determined + eLnSpc = SvxLineSpaceRule::Auto; + } + else + eLnSpc = SvxLineSpaceRule::Min; + + if (IsCalcValue()) + CalcValue(); + + if (eLnSpc != SvxLineSpaceRule::Auto) + aLSpace.SetLineHeight( static_cast<sal_uInt16>(nTokenValue) ); + + aLSpace.SetLineSpaceRule(eLnSpc); + pSet->Put(aLSpace); + } + break; + + case RTF_NOCWRAP: + if (const TypedWhichId<SvxForbiddenRuleItem> wid = aPardMap[SID_ATTR_PARA_FORBIDDEN_RULES]) + { + pSet->Put(SvxForbiddenRuleItem(false, wid)); + } + break; + case RTF_NOOVERFLOW: + if (const TypedWhichId<SvxHangingPunctuationItem> wid = aPardMap[SID_ATTR_PARA_HANGPUNCTUATION]) + { + pSet->Put(SvxHangingPunctuationItem(false, wid)); + } + break; + + case RTF_ASPALPHA: + if (const TypedWhichId<SvxScriptSpaceItem> wid = aPardMap[SID_ATTR_PARA_SCRIPTSPACE]) + { + pSet->Put(SvxScriptSpaceItem(true, wid)); + } + break; + + case RTF_FAFIXED: + case RTF_FAAUTO: nFontAlign = SvxParaVertAlignItem::Align::Automatic; + goto SET_FONTALIGNMENT; + case RTF_FAHANG: nFontAlign = SvxParaVertAlignItem::Align::Top; + goto SET_FONTALIGNMENT; + case RTF_FAVAR: nFontAlign = SvxParaVertAlignItem::Align::Bottom; + goto SET_FONTALIGNMENT; + case RTF_FACENTER: nFontAlign = SvxParaVertAlignItem::Align::Center; + goto SET_FONTALIGNMENT; + case RTF_FAROMAN: nFontAlign = SvxParaVertAlignItem::Align::Baseline; + goto SET_FONTALIGNMENT; +SET_FONTALIGNMENT: + if (const TypedWhichId<SvxParaVertAlignItem> wid = aPardMap[SID_PARA_VERTALIGN]) + { + pSet->Put(SvxParaVertAlignItem(nFontAlign, wid)); + } + break; + + case RTF_B: + case RTF_AB: + if( IsAttrSttPos() ) // not in the text flow? + { + + SvxWeightItem aTmpItem( + nTokenValue ? WEIGHT_BOLD : WEIGHT_NORMAL, + SID_ATTR_CHAR_WEIGHT ); + SetScriptAttr( eCharType, *pSet, aTmpItem); + } + break; + + case RTF_CAPS: + case RTF_SCAPS: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_CASEMAP]; + wid && IsAttrSttPos()) // not in the text flow? + { + SvxCaseMap eCaseMap; + if( !nTokenValue ) + eCaseMap = SvxCaseMap::NotMapped; + else if( RTF_CAPS == nToken ) + eCaseMap = SvxCaseMap::Uppercase; + else + eCaseMap = SvxCaseMap::SmallCaps; + + pSet->Put(SvxCaseMapItem(eCaseMap, wid)); + } + break; + + case RTF_DN: + case RTF_SUB: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + if( -1 == nTokenValue ) + nTokenValue = 6; //RTF default \dn value in half-points + if( IsCalcValue() ) + CalcValue(); + const SvxEscapementItem& rOld = + static_cast<const SvxEscapementItem&>(pSet->Get( nEsc,false)); + sal_Int16 nEs; + sal_uInt8 nProp; + if( DFLT_ESC_AUTO_SUPER == rOld.GetEsc() ) + { + nEs = DFLT_ESC_AUTO_SUB; + nProp = rOld.GetProportionalHeight(); + } + else + { + nEs = (nToken == RTF_SUB) ? DFLT_ESC_AUTO_SUB : -nTokenValue; + nProp = (nToken == RTF_SUB) ? DFLT_ESC_PROP : 100; + } + pSet->Put( SvxEscapementItem( nEs, nProp, nEsc )); + } + break; + + case RTF_NOSUPERSUB: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + pSet->Put( SvxEscapementItem( nEsc )); + } + break; + + case RTF_EXPND: + if (TypedWhichId<SvxKerningItem> wid = aPlainMap[SID_ATTR_CHAR_KERNING]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + else + nTokenValue *= 5; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxKerningItem(static_cast<short>(nTokenValue), wid)); + } + break; + + case RTF_KERNING: + if (const TypedWhichId<SvxAutoKernItem> wid = aPlainMap[SID_ATTR_CHAR_AUTOKERN]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + else + nTokenValue *= 10; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxAutoKernItem(0 != nTokenValue, wid)); + } + break; + + case RTF_EXPNDTW: + if (TypedWhichId<SvxKerningItem> wid = aPlainMap[SID_ATTR_CHAR_KERNING]) + { + if( -1 == nTokenValue ) + nTokenValue = 0; + if( IsCalcValue() ) + CalcValue(); + pSet->Put(SvxKerningItem(static_cast<short>(nTokenValue), wid)); + } + break; + + case RTF_F: + case RTF_AF: + { + const vcl::Font& rSVFont = GetFont( sal_uInt16(nTokenValue) ); + SvxFontItem aTmpItem( rSVFont.GetFamilyType(), + rSVFont.GetFamilyName(), rSVFont.GetStyleName(), + rSVFont.GetPitch(), rSVFont.GetCharSet(), + SID_ATTR_CHAR_FONT ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + if( RTF_F == nToken ) + { + SetEncoding( rSVFont.GetCharSet() ); + RereadLookahead(); + } + } + break; + + case RTF_FS: + case RTF_AFS: + { + if( -1 == nTokenValue ) + nTokenValue = 240; + else + nTokenValue *= 10; +// #i66167# +// for the SwRTFParser 'IsCalcValue' will be false and for the EditRTFParser +// the conversion takes now place in EditRTFParser since for other reasons +// the wrong MapUnit might still be use there +// if( IsCalcValue() ) +// CalcValue(); + SvxFontHeightItem aTmpItem( + static_cast<sal_uInt16>(nTokenValue), 100, + SID_ATTR_CHAR_FONTHEIGHT ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_I: + case RTF_AI: + if( IsAttrSttPos() ) // not in the text flow? + { + SvxPostureItem aTmpItem( + nTokenValue ? ITALIC_NORMAL : ITALIC_NONE, + SID_ATTR_CHAR_POSTURE ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_OUTL: + if (const TypedWhichId<SvxContourItem> wid = aPlainMap[SID_ATTR_CHAR_CONTOUR]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put(SvxContourItem(nTokenValue != 0, wid)); + } + break; + + case RTF_SHAD: + if (const TypedWhichId<SvxShadowedItem> wid = aPlainMap[SID_ATTR_CHAR_SHADOWED]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put(SvxShadowedItem(nTokenValue != 0, wid)); + } + break; + + case RTF_STRIKE: + if (const TypedWhichId<SvxCrossedOutItem> wid = aPlainMap[SID_ATTR_CHAR_STRIKEOUT]; + wid && IsAttrSttPos()) // not in the text flow? + { + pSet->Put( SvxCrossedOutItem( + nTokenValue ? STRIKEOUT_SINGLE : STRIKEOUT_NONE, + wid )); + } + break; + + case RTF_STRIKED: + if (const TypedWhichId<SvxCrossedOutItem> wid = aPlainMap[SID_ATTR_CHAR_STRIKEOUT]) // not in the text flow? + { + pSet->Put( SvxCrossedOutItem( + nTokenValue ? STRIKEOUT_DOUBLE : STRIKEOUT_NONE, + wid )); + } + break; + + case RTF_UL: + if( !IsAttrSttPos() ) + break; + eUnderline = nTokenValue ? LINESTYLE_SINGLE : LINESTYLE_NONE; + goto ATTR_SETUNDERLINE; + + case RTF_ULD: + eUnderline = LINESTYLE_DOTTED; + goto ATTR_SETUNDERLINE; + case RTF_ULDASH: + eUnderline = LINESTYLE_DASH; + goto ATTR_SETUNDERLINE; + case RTF_ULDASHD: + eUnderline = LINESTYLE_DASHDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULDASHDD: + eUnderline = LINESTYLE_DASHDOTDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULDB: + eUnderline = LINESTYLE_DOUBLE; + goto ATTR_SETUNDERLINE; + case RTF_ULNONE: + eUnderline = LINESTYLE_NONE; + goto ATTR_SETUNDERLINE; + case RTF_ULTH: + eUnderline = LINESTYLE_BOLD; + goto ATTR_SETUNDERLINE; + case RTF_ULWAVE: + eUnderline = LINESTYLE_WAVE; + goto ATTR_SETUNDERLINE; + case RTF_ULTHD: + eUnderline = LINESTYLE_BOLDDOTTED; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASH: + eUnderline = LINESTYLE_BOLDDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULLDASH: + eUnderline = LINESTYLE_LONGDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULTHLDASH: + eUnderline = LINESTYLE_BOLDLONGDASH; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASHD: + eUnderline = LINESTYLE_BOLDDASHDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULTHDASHDD: + eUnderline = LINESTYLE_BOLDDASHDOTDOT; + goto ATTR_SETUNDERLINE; + case RTF_ULHWAVE: + eUnderline = LINESTYLE_BOLDWAVE; + goto ATTR_SETUNDERLINE; + case RTF_ULULDBWAVE: + eUnderline = LINESTYLE_DOUBLEWAVE; + goto ATTR_SETUNDERLINE; + + case RTF_ULW: + eUnderline = LINESTYLE_SINGLE; + + if (const TypedWhichId<SvxWordLineModeItem> wid = aPlainMap[SID_ATTR_CHAR_WORDLINEMODE]) + { + pSet->Put(SvxWordLineModeItem(true, wid)); + } + goto ATTR_SETUNDERLINE; + +ATTR_SETUNDERLINE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_UNDERLINE]) + { + pSet->Put(SvxUnderlineItem(eUnderline, wid)); + } + break; + + case RTF_ULC: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_UNDERLINE]) + { + std::unique_ptr<SvxUnderlineItem> aUL(std::make_unique<SvxUnderlineItem>(LINESTYLE_SINGLE, wid)); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == pSet->GetItemState(wid, false, &pItem)) + { + // is switched off ? + if( LINESTYLE_NONE == static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle() ) + break; + + aUL.reset(static_cast<SvxUnderlineItem*>(pItem->Clone())); + } + else + { + aUL.reset(static_cast<SvxUnderlineItem*>(pSet->Get(wid, false).Clone())); + } + + if(LINESTYLE_NONE == aUL->GetLineStyle()) + { + aUL->SetLineStyle(LINESTYLE_SINGLE); + } + + aUL->SetColor(GetColor(sal_uInt16(nTokenValue))); + + pSet->Put(std::move(aUL)); + } + break; + + case RTF_OL: + if( !IsAttrSttPos() ) + break; + eOverline = nTokenValue ? LINESTYLE_SINGLE : LINESTYLE_NONE; + goto ATTR_SETOVERLINE; + + case RTF_OLD: + eOverline = LINESTYLE_DOTTED; + goto ATTR_SETOVERLINE; + case RTF_OLDASH: + eOverline = LINESTYLE_DASH; + goto ATTR_SETOVERLINE; + case RTF_OLDASHD: + eOverline = LINESTYLE_DASHDOT; + goto ATTR_SETOVERLINE; + case RTF_OLDASHDD: + eOverline = LINESTYLE_DASHDOTDOT; + goto ATTR_SETOVERLINE; + case RTF_OLDB: + eOverline = LINESTYLE_DOUBLE; + goto ATTR_SETOVERLINE; + case RTF_OLNONE: + eOverline = LINESTYLE_NONE; + goto ATTR_SETOVERLINE; + case RTF_OLTH: + eOverline = LINESTYLE_BOLD; + goto ATTR_SETOVERLINE; + case RTF_OLWAVE: + eOverline = LINESTYLE_WAVE; + goto ATTR_SETOVERLINE; + case RTF_OLTHD: + eOverline = LINESTYLE_BOLDDOTTED; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASH: + eOverline = LINESTYLE_BOLDDASH; + goto ATTR_SETOVERLINE; + case RTF_OLLDASH: + eOverline = LINESTYLE_LONGDASH; + goto ATTR_SETOVERLINE; + case RTF_OLTHLDASH: + eOverline = LINESTYLE_BOLDLONGDASH; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASHD: + eOverline = LINESTYLE_BOLDDASHDOT; + goto ATTR_SETOVERLINE; + case RTF_OLTHDASHDD: + eOverline = LINESTYLE_BOLDDASHDOTDOT; + goto ATTR_SETOVERLINE; + case RTF_OLHWAVE: + eOverline = LINESTYLE_BOLDWAVE; + goto ATTR_SETOVERLINE; + case RTF_OLOLDBWAVE: + eOverline = LINESTYLE_DOUBLEWAVE; + goto ATTR_SETOVERLINE; + + case RTF_OLW: + eOverline = LINESTYLE_SINGLE; + + if (const TypedWhichId<SvxWordLineModeItem> wid = aPlainMap[SID_ATTR_CHAR_WORDLINEMODE]) + { + pSet->Put(SvxWordLineModeItem(true, wid)); + } + goto ATTR_SETOVERLINE; + +ATTR_SETOVERLINE: + if (const TypedWhichId<SvxOverlineItem> wid = aPlainMap[SID_ATTR_CHAR_OVERLINE]) + { + pSet->Put(SvxOverlineItem(eOverline, wid)); + } + break; + + case RTF_OLC: + if (const TypedWhichId<SvxOverlineItem> wid = aPlainMap[SID_ATTR_CHAR_OVERLINE]) + { + std::unique_ptr<SvxOverlineItem> aOL(std::make_unique<SvxOverlineItem>(LINESTYLE_SINGLE, wid)); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == pSet->GetItemState(wid, false, &pItem)) + { + // is switched off ? + if( LINESTYLE_NONE == static_cast<const SvxOverlineItem*>(pItem)->GetLineStyle() ) + break; + + aOL.reset(static_cast<SvxOverlineItem*>(pItem->Clone())); + } + else + { + aOL.reset(pSet->Get(wid, false).Clone()); + } + + if(LINESTYLE_NONE == aOL->GetLineStyle()) + { + aOL->SetLineStyle(LINESTYLE_SINGLE); + } + + aOL->SetColor(GetColor(sal_uInt16(nTokenValue))); + + pSet->Put(std::move(aOL)); + } + break; + + case RTF_UP: + case RTF_SUPER: + if (const sal_uInt16 nEsc = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + { + if( -1 == nTokenValue ) + nTokenValue = 6; //RTF default \up value in half-points + if( IsCalcValue() ) + CalcValue(); + const SvxEscapementItem& rOld = + static_cast<const SvxEscapementItem&>(pSet->Get( nEsc,false)); + sal_Int16 nEs; + sal_uInt8 nProp; + if( DFLT_ESC_AUTO_SUB == rOld.GetEsc() ) + { + nEs = DFLT_ESC_AUTO_SUPER; + nProp = rOld.GetProportionalHeight(); + } + else + { + nEs = (nToken == RTF_SUPER) ? DFLT_ESC_AUTO_SUPER : nTokenValue; + nProp = (nToken == RTF_SUPER) ? DFLT_ESC_PROP : 100; + } + pSet->Put( SvxEscapementItem( nEs, nProp, nEsc )); + } + break; + + case RTF_CF: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_COLOR]) + { + pSet->Put(SvxColorItem(GetColor(sal_uInt16(nTokenValue)), wid)); + } + break; + //#i12501# While cb is clearly documented in the rtf spec, word + //doesn't accept it at all +#if 0 + case RTF_CB: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_BRUSH_CHAR]) + { + pSet->Put(SvxBrushItem(GetColor(sal_uInt16(nTokenValue)), wid)); + } + break; +#endif + + case RTF_LANG: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_LANGUAGE]) + { + pSet->Put(SvxLanguageItem(LanguageType(nTokenValue), wid)); + } + break; + + case RTF_LANGFE: + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_CJK_LANGUAGE]) + { + pSet->Put(SvxLanguageItem(LanguageType(nTokenValue), wid)); + } + break; + case RTF_ALANG: + { + SvxLanguageItem aTmpItem( LanguageType(nTokenValue), + SID_ATTR_CHAR_LANGUAGE ); + SetScriptAttr( eCharType, *pSet, aTmpItem ); + } + break; + + case RTF_RTLCH: + bIsLeftToRightDef = false; + break; + case RTF_LTRCH: + bIsLeftToRightDef = true; + break; + case RTF_RTLPAR: + if (const TypedWhichId<SvxFrameDirectionItem> wid = aPardMap[SID_ATTR_FRAMEDIRECTION]) + { + pSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, wid)); + } + break; + case RTF_LTRPAR: + if (const TypedWhichId<SvxFrameDirectionItem> wid = aPardMap[SID_ATTR_FRAMEDIRECTION]) + { + pSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, wid)); + } + break; + case RTF_LOCH: eCharType = LOW_CHARTYPE; break; + case RTF_HICH: eCharType = HIGH_CHARTYPE; break; + case RTF_DBCH: eCharType = DOUBLEBYTE_CHARTYPE; break; + + + case RTF_ACCNONE: + eEmphasis = FontEmphasisMark::NONE; + goto ATTR_SETEMPHASIS; + case RTF_ACCDOT: + eEmphasis = (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove); + goto ATTR_SETEMPHASIS; + + case RTF_ACCCOMMA: + eEmphasis = (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove); +ATTR_SETEMPHASIS: + if (const TypedWhichId<SvxEmphasisMarkItem> wid = aPlainMap[SID_ATTR_CHAR_EMPHASISMARK]) + { + pSet->Put(SvxEmphasisMarkItem(eEmphasis, wid)); + } + break; + + case RTF_TWOINONE: + if (const TypedWhichId<SvxTwoLinesItem> wid = aPlainMap[SID_ATTR_CHAR_TWO_LINES]) + { + sal_Unicode cStt, cEnd; + switch ( nTokenValue ) + { + case 1: cStt = '('; cEnd = ')'; break; + case 2: cStt = '['; cEnd = ']'; break; + case 3: cStt = '<'; cEnd = '>'; break; + case 4: cStt = '{'; cEnd = '}'; break; + default: cStt = 0; cEnd = 0; break; + } + + pSet->Put(SvxTwoLinesItem(true, cStt, cEnd, wid)); + } + break; + + case RTF_CHARSCALEX : + if (const TypedWhichId<SvxCharScaleWidthItem> wid = aPlainMap[SID_ATTR_CHAR_SCALEWIDTH]) + { + //i21372 + if (nTokenValue < 1 || nTokenValue > 600) + nTokenValue = 100; + pSet->Put(SvxCharScaleWidthItem(sal_uInt16(nTokenValue), wid)); + } + break; + + case RTF_HORZVERT: + if (const TypedWhichId<SvxCharRotateItem> wid = aPlainMap[SID_ATTR_CHAR_ROTATED]) + { + // RTF knows only 90deg + pSet->Put(SvxCharRotateItem(900_deg10, 1 == nTokenValue, wid)); + } + break; + + case RTF_EMBO: + if (const TypedWhichId<SvxCharReliefItem> wid = aPlainMap[SID_ATTR_CHAR_RELIEF]) + { + pSet->Put(SvxCharReliefItem(FontRelief::Embossed, wid)); + } + break; + case RTF_IMPR: + if (const TypedWhichId<SvxCharReliefItem> wid = aPlainMap[SID_ATTR_CHAR_RELIEF]) + { + pSet->Put(SvxCharReliefItem(FontRelief::Engraved, wid)); + } + break; + case RTF_V: + if (const TypedWhichId<SvxCharHiddenItem> wid = aPlainMap[SID_ATTR_CHAR_HIDDEN]) + { + pSet->Put(SvxCharHiddenItem(nTokenValue != 0, wid)); + } + break; + case RTF_CHBGFDIAG: + case RTF_CHBGDKVERT: + case RTF_CHBGDKHORIZ: + case RTF_CHBGVERT: + case RTF_CHBGHORIZ: + case RTF_CHBGDKFDIAG: + case RTF_CHBGDCROSS: + case RTF_CHBGCROSS: + case RTF_CHBGBDIAG: + case RTF_CHBGDKDCROSS: + case RTF_CHBGDKCROSS: + case RTF_CHBGDKBDIAG: + case RTF_CHCBPAT: + case RTF_CHCFPAT: + case RTF_CHSHDNG: + if (aPlainMap[SID_ATTR_BRUSH_CHAR]) + ReadBackgroundAttr( nToken, *pSet ); + break; + + case BRACELEFT: + { + // tests on Swg internal tokens + bool bHandled = false; + short nSkip = 0; + if( RTF_IGNOREFLAG != GetNextToken()) + nSkip = -1; + else if( (nToken = GetNextToken() ) & RTF_SWGDEFS ) + { + bHandled = true; + switch( nToken ) + { + case RTF_PGDSCNO: + case RTF_PGBRK: + case RTF_SOUTLVL: + UnknownAttrToken( nToken ); + // overwrite the closing parenthesis + break; + + case RTF_SWG_ESCPROP: + { + // Store percentage change! + sal_uInt8 nProp = sal_uInt8( nTokenValue / 100 ); + short nEsc = 0; + if( 1 == ( nTokenValue % 100 )) + // Recognize own auto-flags! + nEsc = DFLT_ESC_AUTO_SUPER; + + if (const sal_uInt16 wid = aPlainMap[SID_ATTR_CHAR_ESCAPEMENT]) + pSet->Put(SvxEscapementItem(nEsc, nProp, wid)); + } + break; + + case RTF_HYPHEN: + { + SvxHyphenZoneItem aHypenZone( + (nTokenValue & 1) != 0, + aPardMap[SID_ATTR_PARA_HYPHENZONE]); + aHypenZone.SetPageEnd((nTokenValue & 2) != 0); + + if( aPardMap[SID_ATTR_PARA_HYPHENZONE] && + RTF_HYPHLEAD == GetNextToken() && + RTF_HYPHTRAIL == GetNextToken() && + RTF_HYPHMAX == GetNextToken() ) + { + aHypenZone.GetMinLead() = + sal_uInt8(GetStackPtr( -2 )->nTokenValue); + aHypenZone.GetMinTrail() = + sal_uInt8(GetStackPtr( -1 )->nTokenValue); + aHypenZone.GetMaxHyphens() = + sal_uInt8(nTokenValue); + + pSet->Put( aHypenZone ); + } + else + SkipGroup(); // at the end of the group + } + break; + + // We expect these to be preceded by a RTF_HYPHEN and + // so normally are handled by the RTF_HYPHEN case, but + // if they appear 'bare' in a document then safely skip + // them here + case RTF_HYPHLEAD: + case RTF_HYPHTRAIL: + case RTF_HYPHMAX: + SkipGroup(); + break; + + case RTF_SHADOW: + { + bool bSkip = true; + do { // middle check loop + SvxShadowLocation eSL = SvxShadowLocation( nTokenValue ); + if( RTF_SHDW_DIST != GetNextToken() ) + break; + sal_uInt16 nDist = sal_uInt16( nTokenValue ); + + if( RTF_SHDW_STYLE != GetNextToken() ) + break; + + if( RTF_SHDW_COL != GetNextToken() ) + break; + sal_uInt16 nCol = sal_uInt16( nTokenValue ); + + if( RTF_SHDW_FCOL != GetNextToken() ) + break; + + Color aColor = GetColor( nCol ); + + if (const TypedWhichId<SvxShadowItem> wid = aPardMap[SID_ATTR_BORDER_SHADOW]) + pSet->Put(SvxShadowItem(wid, &aColor, nDist, eSL)); + + bSkip = false; + } while( false ); + + if( bSkip ) + SkipGroup(); // at the end of the group + } + break; + + default: + bHandled = false; + if( (nToken & ~(0xff | RTF_SWGDEFS)) == RTF_TABSTOPDEF ) + { + nToken = SkipToken( -2 ); + ReadTabAttr( nToken, *pSet ); + + /* + cmc: #i76140, he who consumed the { must consume the } + We rewound to a state of { being the current + token so it is our responsibility to consume the } + token if we consumed the {. We will not have consumed + the { if it belonged to our caller, i.e. if the { we + are handling is the "firsttoken" passed to us then + the *caller* must consume it, not us. Otherwise *we* + should consume it. + */ + if (nToken == BRACELEFT && !bFirstToken) + { + nToken = GetNextToken(); + SAL_WARN_IF( nToken != BRACERIGHT, + "editeng", + "} did not follow { as expected"); + } + } + else if( (nToken & ~(0xff| RTF_SWGDEFS)) == RTF_BRDRDEF) + { + nToken = SkipToken( -2 ); + ReadBorderAttr( nToken, *pSet ); + } + else // so no more attribute + nSkip = -2; + break; + } + +#if 1 + /* + cmc: #i4727# / #i12713# Who owns this closing bracket? + If we read the opening one, we must read this one, if + other is counting the brackets so as to push/pop off + the correct environment then we will have pushed a new + environment for the start { of this, but will not see + the } and so is out of sync for the rest of the + document. + */ + if (bHandled && !bFirstToken) + GetNextToken(); +#endif + } + else + nSkip = -2; + + if( nSkip ) // all completely unknown + { + if (!bFirstToken) + --nSkip; // BRACELEFT: is the next token + SkipToken( nSkip ); + bContinue = false; + } + } + break; + default: + if( (nToken & ~0xff ) == RTF_TABSTOPDEF ) + ReadTabAttr( nToken, *pSet ); + else if( (nToken & ~0xff ) == RTF_BRDRDEF ) + ReadBorderAttr( nToken, *pSet ); + else if( (nToken & ~0xff ) == RTF_SHADINGDEF ) + ReadBackgroundAttr( nToken, *pSet ); + else + { + // unknown token, so token "returned in Parser" + if( !bFirstToken ) + SkipToken(); + bContinue = false; + } + } + } + if( bContinue ) + { + nToken = GetNextToken(); + } + bFirstToken = false; + } +} + +void SvxRTFParser::ReadTabAttr( int nToken, SfxItemSet& rSet ) +{ + bool bMethodOwnsToken = false; // #i52542# patch from cmc. +// then read all the TabStops + SvxTabStop aTabStop; + SvxTabStopItem aAttr(0, 0, SvxTabAdjust::Default, aPardMap[SID_ATTR_TABSTOP]); + bool bContinue = true; + do { + switch( nToken ) + { + case RTF_TB: // BarTab ??? + case RTF_TX: + { + if( IsCalcValue() ) + CalcValue(); + aTabStop.GetTabPos() = nTokenValue; + aAttr.Insert( aTabStop ); + aTabStop = SvxTabStop(); // all values default + } + break; + + case RTF_TQL: + aTabStop.GetAdjustment() = SvxTabAdjust::Left; + break; + case RTF_TQR: + aTabStop.GetAdjustment() = SvxTabAdjust::Right; + break; + case RTF_TQC: + aTabStop.GetAdjustment() = SvxTabAdjust::Center; + break; + case RTF_TQDEC: + aTabStop.GetAdjustment() = SvxTabAdjust::Decimal; + break; + + case RTF_TLDOT: aTabStop.GetFill() = '.'; break; + case RTF_TLHYPH: aTabStop.GetFill() = ' '; break; + case RTF_TLUL: aTabStop.GetFill() = '_'; break; + case RTF_TLTH: aTabStop.GetFill() = '-'; break; + case RTF_TLEQ: aTabStop.GetFill() = '='; break; + + case BRACELEFT: + { + // Swg - control BRACELEFT RTF_IGNOREFLAG RTF_TLSWG BRACERIGHT + short nSkip = 0; + if( RTF_IGNOREFLAG != GetNextToken() ) + nSkip = -1; + else if( RTF_TLSWG != ( nToken = GetNextToken() )) + nSkip = -2; + else + { + aTabStop.GetDecimal() = sal_uInt8(nTokenValue & 0xff); + aTabStop.GetFill() = sal_uInt8((nTokenValue >> 8) & 0xff); + // overwrite the closing parenthesis + if (bMethodOwnsToken) + GetNextToken(); + } + if( nSkip ) + { + SkipToken( nSkip ); // Ignore back again + bContinue = false; + } + } + break; + + default: + bContinue = false; + } + if( bContinue ) + { + nToken = GetNextToken(); + bMethodOwnsToken = true; + } + } while( bContinue ); + + // Fill with defaults is still missing! + rSet.Put( aAttr ); + SkipToken(); +} + +static void SetBorderLine( int nBorderTyp, SvxBoxItem& rItem, + const SvxBorderLine& rBorder ) +{ + switch( nBorderTyp ) + { + case RTF_BOX: // run through all levels + case RTF_BRDRT: + rItem.SetLine( &rBorder, SvxBoxItemLine::TOP ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRB: + rItem.SetLine( &rBorder, SvxBoxItemLine::BOTTOM ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRL: + rItem.SetLine( &rBorder, SvxBoxItemLine::LEFT ); + if( RTF_BOX != nBorderTyp ) + return; + [[fallthrough]]; + case RTF_BRDRR: + rItem.SetLine( &rBorder, SvxBoxItemLine::RIGHT ); + if( RTF_BOX != nBorderTyp ) + return; + } +} + +void SvxRTFParser::ReadBorderAttr( int nToken, SfxItemSet& rSet, + bool bTableDef ) +{ + // then read the border attribute + std::unique_ptr<SvxBoxItem> aAttr(std::make_unique<SvxBoxItem>(aPardMap[SID_ATTR_BORDER_OUTER])); + const SfxPoolItem* pItem(nullptr); + + if (SfxItemState::SET == rSet.GetItemState(aPardMap[SID_ATTR_BORDER_OUTER], false, &pItem)) + { + aAttr.reset(static_cast<SvxBoxItem*>(pItem->Clone())); + } + + SvxBorderLine aBrd( nullptr, SvxBorderLineWidth::Hairline ); + bool bContinue = true; + int nBorderTyp = 0; + + tools::Long nWidth = 1; + bool bDoubleWidth = false; + + do { + switch( nToken ) + { + case RTF_BOX: + case RTF_BRDRT: + case RTF_BRDRB: + case RTF_BRDRL: + case RTF_BRDRR: + nBorderTyp = nToken; + break; + + case RTF_CLBRDRT: // Cell top border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRT; + } + break; + } + case RTF_CLBRDRB: // Cell bottom border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRB; + } + break; + } + case RTF_CLBRDRL: // Cell left border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRL; + } + break; + } + case RTF_CLBRDRR: // Cell right border + { + if( bTableDef ) + { + if (nBorderTyp != 0) + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + nBorderTyp = RTF_BRDRR; + } + break; + } + + case RTF_BRDRDOT: // dotted border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DOTTED); + break; + case RTF_BRDRDASH: // dashed border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DASHED); + break; + case RTF_BRDRHAIR: // hairline border + { + aBrd.SetBorderLineStyle( SvxBorderLineStyle::SOLID); + aBrd.SetWidth( SvxBorderLineWidth::Hairline ); + } + break; + case RTF_BRDRDB: // Double border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + break; + case RTF_BRDRINSET: // inset border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::INSET); + break; + case RTF_BRDROUTSET: // outset border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::OUTSET); + break; + case RTF_BRDRTNTHSG: // ThinThick Small gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP); + break; + case RTF_BRDRTNTHMG: // ThinThick Medium gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_MEDIUMGAP); + break; + case RTF_BRDRTNTHLG: // ThinThick Large gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_LARGEGAP); + break; + case RTF_BRDRTHTNSG: // ThickThin Small gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP); + break; + case RTF_BRDRTHTNMG: // ThickThin Medium gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_MEDIUMGAP); + break; + case RTF_BRDRTHTNLG: // ThickThin Large gap + aBrd.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_LARGEGAP); + break; + case RTF_BRDREMBOSS: // Embossed border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::EMBOSSED); + break; + case RTF_BRDRENGRAVE: // Engraved border + aBrd.SetBorderLineStyle(SvxBorderLineStyle::ENGRAVED); + break; + + case RTF_BRDRS: // single thickness border + bDoubleWidth = false; + break; + case RTF_BRDRTH: // double thickness border width*2 + bDoubleWidth = true; + break; + case RTF_BRDRW: // border width <255 + nWidth = nTokenValue; + break; + + case RTF_BRDRCF: // Border color + aBrd.SetColor( GetColor( sal_uInt16(nTokenValue) ) ); + break; + + case RTF_BRDRSH: // Shadowed border + rSet.Put( SvxShadowItem( aPardMap[SID_ATTR_BORDER_SHADOW], nullptr, 60 /*3pt*/, + SvxShadowLocation::BottomRight ) ); + break; + + case RTF_BRSP: // Spacing to content in twip + { + switch( nBorderTyp ) + { + case RTF_BRDRB: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::BOTTOM ); + break; + + case RTF_BRDRT: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::TOP ); + break; + + case RTF_BRDRL: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::LEFT ); + break; + + case RTF_BRDRR: + aAttr->SetDistance( static_cast<sal_uInt16>(nTokenValue), SvxBoxItemLine::RIGHT ); + break; + + case RTF_BOX: + aAttr->SetAllDistances( static_cast<sal_uInt16>(nTokenValue) ); + break; + } + } + break; + + case RTF_BRDRBTW: // Border formatting group + case RTF_BRDRBAR: // Border outside + // TODO unhandled ATM + break; + + default: + bContinue = (nToken & ~(0xff| RTF_SWGDEFS)) == RTF_BRDRDEF; + } + if( bContinue ) + nToken = GetNextToken(); + } while( bContinue ); + + // Finally compute the border width + if ( bDoubleWidth ) nWidth *= 2; + aBrd.SetWidth( nWidth ); + + SetBorderLine( nBorderTyp, *aAttr, aBrd ); + + rSet.Put( std::move(aAttr) ); + SkipToken(); +} + +static sal_uInt32 CalcShading( sal_uInt32 nColor, sal_uInt32 nFillColor, sal_uInt8 nShading ) +{ + nColor = (nColor * nShading) / 100; + nFillColor = (nFillColor * ( 100 - nShading )) / 100; + return nColor + nFillColor; +} + +void SvxRTFParser::ReadBackgroundAttr( int nToken, SfxItemSet& rSet, + bool bTableDef ) +{ + // then read the border attribute + bool bContinue = true; + sal_uInt16 nColor = USHRT_MAX, nFillColor = USHRT_MAX; + sal_uInt8 nFillValue = 0; + + sal_uInt16 nWh = ( nToken & ~0xff ) == RTF_CHRFMT + ? aPlainMap[SID_ATTR_BRUSH_CHAR] + : aPardMap[SID_ATTR_BRUSH]; + + do { + switch( nToken ) + { + case RTF_CLCBPAT: + case RTF_CHCBPAT: + case RTF_CBPAT: + nFillColor = sal_uInt16( nTokenValue ); + break; + + case RTF_CLCFPAT: + case RTF_CHCFPAT: + case RTF_CFPAT: + nColor = sal_uInt16( nTokenValue ); + break; + + case RTF_CLSHDNG: + case RTF_CHSHDNG: + case RTF_SHADING: + nFillValue = static_cast<sal_uInt8>( nTokenValue / 100 ); + break; + + case RTF_CLBGDKHOR: + case RTF_CHBGDKHORIZ: + case RTF_BGDKHORIZ: + case RTF_CLBGDKVERT: + case RTF_CHBGDKVERT: + case RTF_BGDKVERT: + case RTF_CLBGDKBDIAG: + case RTF_CHBGDKBDIAG: + case RTF_BGDKBDIAG: + case RTF_CLBGDKFDIAG: + case RTF_CHBGDKFDIAG: + case RTF_BGDKFDIAG: + case RTF_CLBGDKCROSS: + case RTF_CHBGDKCROSS: + case RTF_BGDKCROSS: + case RTF_CLBGDKDCROSS: + case RTF_CHBGDKDCROSS: + case RTF_BGDKDCROSS: + // dark -> 60% + nFillValue = 60; + break; + + case RTF_CLBGHORIZ: + case RTF_CHBGHORIZ: + case RTF_BGHORIZ: + case RTF_CLBGVERT: + case RTF_CHBGVERT: + case RTF_BGVERT: + case RTF_CLBGBDIAG: + case RTF_CHBGBDIAG: + case RTF_BGBDIAG: + case RTF_CLBGFDIAG: + case RTF_CHBGFDIAG: + case RTF_BGFDIAG: + case RTF_CLBGCROSS: + case RTF_CHBGCROSS: + case RTF_BGCROSS: + case RTF_CLBGDCROSS: + case RTF_CHBGDCROSS: + case RTF_BGDCROSS: + // light -> 20% + nFillValue = 20; + break; + + default: + if( bTableDef ) + bContinue = (nToken & ~(0xff | RTF_TABLEDEF) ) == RTF_SHADINGDEF; + else + bContinue = (nToken & ~0xff) == RTF_SHADINGDEF; + } + if( bContinue ) + nToken = GetNextToken(); + } while( bContinue ); + + Color aCol( COL_WHITE ), aFCol; + if( !nFillValue ) + { + // there was only one of two colors specified or no BrushType + if( USHRT_MAX != nFillColor ) + { + nFillValue = 100; + aCol = GetColor( nFillColor ); + } + else if( USHRT_MAX != nColor ) + aFCol = GetColor( nColor ); + } + else + { + if( USHRT_MAX != nColor ) + aCol = GetColor( nColor ); + else + aCol = COL_BLACK; + + if( USHRT_MAX != nFillColor ) + aFCol = GetColor( nFillColor ); + else + aFCol = COL_WHITE; + } + + Color aColor; + if( 0 == nFillValue || 100 == nFillValue ) + aColor = aCol; + else + aColor = Color( + static_cast<sal_uInt8>(CalcShading( aCol.GetRed(), aFCol.GetRed(), nFillValue )), + static_cast<sal_uInt8>(CalcShading( aCol.GetGreen(), aFCol.GetGreen(), nFillValue )), + static_cast<sal_uInt8>(CalcShading( aCol.GetBlue(), aFCol.GetBlue(), nFillValue )) ); + + rSet.Put( SvxBrushItem( aColor, nWh ) ); + SkipToken(); +} + + +// pard / plain handling +void SvxRTFParser::RTFPardPlain( bool const bPard, SfxItemSet** ppSet ) +{ + if( bNewGroup || aAttrStack.empty() ) // not at the beginning of a new group + return; + + SvxRTFItemStackType* pCurrent = aAttrStack.back().get(); + + int nLastToken = GetStackPtr(-1)->nTokenId; + bool bNewStkEntry = true; + if( RTF_PARD != nLastToken && + RTF_PLAIN != nLastToken && + BRACELEFT != nLastToken ) + { + if (pCurrent->aAttrSet.Count() || !pCurrent->maChildList.empty() || pCurrent->nStyleNo) + { + // open a new group + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Set all until here valid attributes + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + aAttrStack.push_back( std::move(xNew) ); + pCurrent = aAttrStack.back().get(); + } + else + { + // continue to use this entry as new + pCurrent->SetStartPos( *mxInsertPosition ); + bNewStkEntry = false; + } + } + + // now reset all to default + if( bNewStkEntry && + ( pCurrent->aAttrSet.GetParent() || pCurrent->aAttrSet.Count() )) + { + const SfxPoolItem *pItem, *pDef; + std::map<sal_uInt16, sal_uInt16>::const_iterator aIt; + std::map<sal_uInt16, sal_uInt16>::const_iterator aEnd; + const SfxItemSet* pDfltSet = &GetRTFDefaults(); + if( bPard ) + { + pCurrent->nStyleNo = 0; + aIt = aPardMap.data.begin(); + aEnd = aPardMap.data.end(); + } + else + { + aIt = aPlainMap.data.begin(); + aEnd = aPlainMap.data.end(); + } + + for (; aIt != aEnd; ++aIt) + { + const sal_uInt16 wid = aIt->second; + // Item set and different -> Set the Default Pool + if (!wid) + ; + else if (SfxItemPool::IsSlot(wid)) + pCurrent->aAttrSet.ClearItem(wid); + else if( IsChkStyleAttr() ) + pCurrent->aAttrSet.Put(pDfltSet->Get(wid)); + else if( !pCurrent->aAttrSet.GetParent() ) + { + if (SfxItemState::SET == pDfltSet->GetItemState(wid, false, &pDef)) + pCurrent->aAttrSet.Put( *pDef ); + else + pCurrent->aAttrSet.ClearItem(wid); + } + else if( SfxItemState::SET == pCurrent->aAttrSet.GetParent()-> + GetItemState(wid, true, &pItem) && + *( pDef = &pDfltSet->Get(wid)) != *pItem ) + pCurrent->aAttrSet.Put( *pDef ); + else + { + if (SfxItemState::SET == pDfltSet->GetItemState(wid, false, &pDef)) + pCurrent->aAttrSet.Put( *pDef ); + else + pCurrent->aAttrSet.ClearItem(wid); + } + } + } + else if( bPard ) + pCurrent->nStyleNo = 0; // reset Style number + + *ppSet = &pCurrent->aAttrSet; + + if (bPard) + return; + + //Once we have a default font, then any text without a font specifier is + //in the default font, and thus has the default font charset, otherwise + //we can fall back to the ansicpg set codeset + if (nDfltFont != -1) + { + const vcl::Font& rSVFont = GetFont(sal_uInt16(nDfltFont)); + SetEncoding(rSVFont.GetCharSet()); + } + else + SetEncoding(GetCodeSet()); +} + +void SvxRTFParser::SetDefault( int nToken, int nValue ) +{ + if( !bNewDoc ) + return; + + SfxItemSet aTmp(*pAttrPool, aWhichMap); + bool bOldFlag = bIsLeftToRightDef; + bIsLeftToRightDef = true; + switch( nToken ) + { + case RTF_ADEFF: + bIsLeftToRightDef = false; + [[fallthrough]]; + case RTF_DEFF: + { + if( -1 == nValue ) + nValue = 0; + const vcl::Font& rSVFont = GetFont( sal_uInt16(nValue) ); + SvxFontItem aTmpItem( + rSVFont.GetFamilyType(), rSVFont.GetFamilyName(), + rSVFont.GetStyleName(), rSVFont.GetPitch(), + rSVFont.GetCharSet(), SID_ATTR_CHAR_FONT ); + SetScriptAttr( NOTDEF_CHARTYPE, aTmp, aTmpItem ); + } + break; + + case RTF_ADEFLANG: + bIsLeftToRightDef = false; + [[fallthrough]]; + case RTF_DEFLANG: + // store default Language + if( -1 != nValue ) + { + SvxLanguageItem aTmpItem( LanguageType(nValue), SID_ATTR_CHAR_LANGUAGE ); + SetScriptAttr( NOTDEF_CHARTYPE, aTmp, aTmpItem ); + } + break; + + case RTF_DEFTAB: + if (const sal_uInt16 wid = aPardMap[SID_ATTR_TABSTOP]) + { + // RTF defines 720 twips as default + bIsSetDfltTab = true; + if( -1 == nValue || !nValue ) + nValue = 720; + + // who would like to have no twips ... + if( IsCalcValue() ) + { + nTokenValue = nValue; + CalcValue(); + nValue = nTokenValue; + } + + // Calculate the ratio of default TabWidth / Tabs and + // calculate the corresponding new number. + // ?? how did one come up with 13 ?? + sal_uInt16 nTabCount = (SVX_TAB_DEFDIST * 13 ) / sal_uInt16(nValue); + /* + cmc, make sure we have at least one, or all hell breaks loose in + everybody exporters, #i8247# + */ + if (nTabCount < 1) + nTabCount = 1; + + // we want Defaulttabs + SvxTabStopItem aNewTab(nTabCount, sal_uInt16(nValue), SvxTabAdjust::Default, wid); + while( nTabCount ) + const_cast<SvxTabStop&>(aNewTab[ --nTabCount ]).GetAdjustment() = SvxTabAdjust::Default; + + pAttrPool->SetPoolDefaultItem( aNewTab ); + } + break; + } + bIsLeftToRightDef = bOldFlag; + + if( aTmp.Count() ) + { + SfxItemIter aIter( aTmp ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + pAttrPool->SetPoolDefaultItem( *pItem ); + pItem = aIter.NextItem(); + } while (pItem); + } +} + +// default: no conversion, leaving everything in twips. +void SvxRTFParser::CalcValue() +{ +} + +// for tokens that are not evaluated in ReadAttr +void SvxRTFParser::UnknownAttrToken( int ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/rtf/svxrtf.cxx b/editeng/source/rtf/svxrtf.cxx new file mode 100644 index 0000000000..1ef6f30b40 --- /dev/null +++ b/editeng/source/rtf/svxrtf.cxx @@ -0,0 +1,1162 @@ +/* -*- 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 <memory> +#include <queue> +#include <comphelper/diagnose_ex.hxx> +#include <rtl/tencinfo.h> +#include <svl/itemiter.hxx> +#include <svl/whiter.hxx> +#include <svtools/rtftoken.h> +#include <svl/itempool.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <unotools/configmgr.hxx> + +#include <comphelper/string.hxx> + +#include <editeng/scriptspaceitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/svxrtf.hxx> +#include <editeng/editids.hrc> +#include <vcl/font.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + + +using namespace ::com::sun::star; + + +static rtl_TextEncoding lcl_GetDefaultTextEncodingForRTF() +{ + + OUString aLangString( Application::GetSettings().GetLanguageTag().getLanguage()); + + if ( aLangString == "ru" || aLangString == "uk" ) + return RTL_TEXTENCODING_MS_1251; + if ( aLangString == "tr" ) + return RTL_TEXTENCODING_MS_1254; + else + return RTL_TEXTENCODING_MS_1252; +} + +// -------------- Methods -------------------- + +SvxRTFParser::SvxRTFParser( SfxItemPool& rPool, SvStream& rIn ) + : SvRTFParser( rIn, 5 ) + , pAttrPool( &rPool ) + , nDfltFont( 0) + , bNewDoc( true ) + , bNewGroup( false) + , bIsSetDfltTab( false) + , bChkStyleAttr( false ) + , bCalcValue( false ) + , bIsLeftToRightDef( true) + , bIsInReadStyleTab( false) +{ + pDfltFont.emplace(); + mxDefaultColor = Color(); + + // generate the correct WhichId table from the set WhichIds. + BuildWhichTable(); +} + +SvxRTFParser::~SvxRTFParser() +{ + if( !aAttrStack.empty() ) + ClearAttrStack(); +} + +void SvxRTFParser::SetInsPos( const EditPosition& rNew ) +{ + mxInsertPosition = rNew; +} + +SvParserState SvxRTFParser::CallParser() +{ + DBG_ASSERT( mxInsertPosition, "no insertion position"); + + if( !mxInsertPosition ) + return SvParserState::Error; + + if( !maColorTable.empty() ) + ClearColorTbl(); + m_FontTable.clear(); + m_StyleTable.clear(); + if( !aAttrStack.empty() ) + ClearAttrStack(); + + bIsSetDfltTab = false; + bNewGroup = false; + nDfltFont = 0; + + return SvRTFParser::CallParser(); +} + +void SvxRTFParser::Continue( int nToken ) +{ + SvRTFParser::Continue( nToken ); + + SvParserState eStatus = GetStatus(); + if (eStatus != SvParserState::Pending && eStatus != SvParserState::Error) + { + SetAllAttrOfStk(); + //Regardless of what "color 0" is, word defaults to auto as the default colour. + //e.g. see #i7713# + } +} + + +// is called for each token that is recognized in CallParser +void SvxRTFParser::NextToken( int nToken ) +{ + sal_Unicode cCh; + switch( nToken ) + { + case RTF_COLORTBL: ReadColorTable(); break; + case RTF_FONTTBL: ReadFontTable(); break; + case RTF_STYLESHEET: ReadStyleTable(); break; + + case RTF_DEFF: + if( bNewDoc ) + { + if (!m_FontTable.empty()) + // Can immediately be set + SetDefault( nToken, nTokenValue ); + else + // is set after reading the font table + nDfltFont = int(nTokenValue); + } + break; + + case RTF_DEFTAB: + case RTF_DEFLANG: + if( bNewDoc ) + SetDefault( nToken, nTokenValue ); + break; + + + case RTF_PICT: ReadBitmapData(); break; + + case RTF_LINE: cCh = '\n'; goto INSINGLECHAR; + case RTF_TAB: cCh = '\t'; goto INSINGLECHAR; + case RTF_SUBENTRYINDEX: cCh = ':'; goto INSINGLECHAR; + + case RTF_EMDASH: cCh = 0x2014; goto INSINGLECHAR; + case RTF_ENDASH: cCh = 0x2013; goto INSINGLECHAR; + case RTF_BULLET: cCh = 0x2022; goto INSINGLECHAR; + case RTF_LQUOTE: cCh = 0x2018; goto INSINGLECHAR; + case RTF_RQUOTE: cCh = 0x2019; goto INSINGLECHAR; + case RTF_LDBLQUOTE: cCh = 0x201C; goto INSINGLECHAR; + case RTF_RDBLQUOTE: cCh = 0x201D; goto INSINGLECHAR; +INSINGLECHAR: + aToken = OUStringChar(cCh); + [[fallthrough]]; // aToken is set as Text + case RTF_TEXTTOKEN: + { + InsertText(); + // all collected Attributes are set + for (size_t n = m_AttrSetList.size(); n; ) + { + auto const& pStkSet = m_AttrSetList[--n]; + SetAttrSet( *pStkSet ); + m_AttrSetList.pop_back(); + } + } + break; + + + case RTF_PAR: + InsertPara(); + break; + case '{': + if (bNewGroup) // Nesting! + GetAttrSet_(); + bNewGroup = true; + break; + case '}': + if( !bNewGroup ) // Empty Group ?? + AttrGroupEnd(); + bNewGroup = false; + break; + case RTF_INFO: + SkipGroup(); + break; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // First overwrite all (all have to be in one group!!) + // Could also appear in the RTF-file without the IGNORE-Flag; all Groups + // with the IGNORE-Flag are overwritten in the default branch. + + case RTF_SWG_PRTDATA: + case RTF_FIELD: + case RTF_ATNID: + case RTF_ANNOTATION: + + case RTF_BKMKSTART: + case RTF_BKMKEND: + case RTF_BKMK_KEY: + case RTF_XE: + case RTF_TC: + case RTF_NEXTFILE: + case RTF_TEMPLATE: + // RTF_SHPRSLT disabled for #i19718# + SkipGroup(); + break; + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + case RTF_PGDSCNO: + case RTF_PGBRK: + case RTF_SHADOW: + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + if( '{' == GetStackPtr( -1 )->nTokenId ) + nToken = SkipToken(); + + ReadAttr( nToken, &GetAttrSet() ); + break; + + default: + switch( nToken & ~(0xff | RTF_SWGDEFS) ) + { + case RTF_PARFMT: // here are no SWGDEFS + ReadAttr( nToken, &GetAttrSet() ); + break; + + case RTF_CHRFMT: + case RTF_BRDRDEF: + case RTF_TABSTOPDEF: + + if( RTF_SWGDEFS & nToken) + { + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + if( '{' == GetStackPtr( -1 )->nTokenId ) + { + nToken = SkipToken(); + } + } + ReadAttr( nToken, &GetAttrSet() ); + break; + default: + { + if( RTF_IGNOREFLAG == GetStackPtr( -1 )->nTokenId && + '{' == GetStackPtr( -2 )->nTokenId ) + SkipGroup(); + } + break; + } + break; + } +} + +void SvxRTFParser::ReadStyleTable() +{ + int bSaveChkStyleAttr = bChkStyleAttr ? 1 : 0; + sal_uInt16 nStyleNo = 0; + bool bHasStyleNo = false; + int _nOpenBrackets = 1; // the first was already detected earlier!! + std::optional<SvxRTFStyleType> xStyle(SvxRTFStyleType(*pAttrPool, aWhichMap)); + xStyle->aAttrSet.Put( GetRTFDefaults() ); + + bIsInReadStyleTab = true; + bChkStyleAttr = false; // Do not check Attribute against the Styles + + while( _nOpenBrackets && IsParserWorking() ) + { + int nToken = GetNextToken(); + switch( nToken ) + { + case '}': if( --_nOpenBrackets && IsParserWorking() ) + // Style has been completely read, + // so this is still a stable status + SaveState( RTF_STYLESHEET ); + break; + case '{': + { + if( RTF_IGNOREFLAG != GetNextToken() ) + SkipToken(); + else if( RTF_UNKNOWNCONTROL != ( nToken = GetNextToken() ) && + RTF_PN != nToken ) + SkipToken( -2 ); + else + { + // filter out at once + ReadUnknownData(); + nToken = GetNextToken(); + if( '}' != nToken ) + eState = SvParserState::Error; + break; + } + ++_nOpenBrackets; + } + break; + + case RTF_SBASEDON: xStyle->nBasedOn = sal_uInt16(nTokenValue); break; + case RTF_SNEXT: break; + case RTF_OUTLINELEVEL: + case RTF_SOUTLVL: xStyle->nOutlineNo = sal_uInt8(nTokenValue); break; + case RTF_S: nStyleNo = static_cast<short>(nTokenValue); + bHasStyleNo = true; + break; + case RTF_CS: nStyleNo = static_cast<short>(nTokenValue); + bHasStyleNo = true; + break; + + case RTF_TEXTTOKEN: + if (bHasStyleNo) + { + DelCharAtEnd( aToken, ';' ); + xStyle->sName = aToken.toString(); + + if (!m_StyleTable.empty()) + { + m_StyleTable.erase(nStyleNo); + } + // All data from the font is available, so off to the table + m_StyleTable.emplace(nStyleNo, std::move(*xStyle)); + xStyle.emplace(*pAttrPool, aWhichMap); + xStyle->aAttrSet.Put( GetRTFDefaults() ); + nStyleNo = 0; + bHasStyleNo = false; + } + break; + default: + switch( nToken & ~(0xff | RTF_SWGDEFS) ) + { + case RTF_PARFMT: // here are no SWGDEFS + ReadAttr( nToken, &xStyle->aAttrSet ); + break; + + case RTF_CHRFMT: + case RTF_BRDRDEF: + case RTF_TABSTOPDEF: +#ifndef NDEBUG + auto nEnteringToken = nToken; +#endif + auto nEnteringIndex = m_nTokenIndex; + int nSkippedTokens = 0; + if( RTF_SWGDEFS & nToken) + { + if( RTF_IGNOREFLAG != GetStackPtr( -1 )->nTokenId ) + break; + nToken = SkipToken(); + ++nSkippedTokens; + if( '{' == GetStackPtr( -1 )->nTokenId ) + { + nToken = SkipToken(); + ++nSkippedTokens; + } + } + ReadAttr( nToken, &xStyle->aAttrSet ); + if (nSkippedTokens && m_nTokenIndex == nEnteringIndex - nSkippedTokens) + { + // we called SkipToken to go back one or two, but ReadAttrs + // read nothing, so on next loop of the outer while we + // would end up in the same state again (assert that) + assert(nEnteringToken == GetNextToken()); + // and loop endlessly, skip format a token + // instead to avoid that + SkipToken(nSkippedTokens); + } + break; + } + break; + } + } + xStyle.reset(); // Delete the Last Style + SkipToken(); // the closing brace is evaluated "above" + + // Flag back to old state + bChkStyleAttr = bSaveChkStyleAttr; + bIsInReadStyleTab = false; +} + +void SvxRTFParser::ReadColorTable() +{ + int nToken; + sal_uInt8 nRed = 0xff, nGreen = 0xff, nBlue = 0xff; + + for (;;) + { + nToken = GetNextToken(); + if ( '}' == nToken || !IsParserWorking() ) + break; + switch( nToken ) + { + case RTF_RED: nRed = sal_uInt8(nTokenValue); break; + case RTF_GREEN: nGreen = sal_uInt8(nTokenValue); break; + case RTF_BLUE: nBlue = sal_uInt8(nTokenValue); break; + + case RTF_TEXTTOKEN: + if( 1 == aToken.getLength() + ? aToken[ 0 ] != ';' + : -1 == aToken.indexOf( ";" ) ) + break; // At least the ';' must be found + + [[fallthrough]]; + + case ';': + if( IsParserWorking() ) + { + // one color is finished, fill in the table + // try to map the values to SV internal names + Color aColor( nRed, nGreen, nBlue ); + if( maColorTable.empty() && + sal_uInt8(-1) == nRed && sal_uInt8(-1) == nGreen && sal_uInt8(-1) == nBlue ) + aColor = COL_AUTO; + maColorTable.push_back( aColor ); + nRed = 0; + nGreen = 0; + nBlue = 0; + + // Color has been completely read, + // so this is still a stable status + SaveState( RTF_COLORTBL ); + } + break; + } + } + SkipToken(); // the closing brace is evaluated "above" +} + +void SvxRTFParser::ReadFontTable() +{ + int _nOpenBrackets = 1; // the first was already detected earlier!! + vcl::Font aFont; + short nFontNo(0), nInsFontNo (0); + OUString sAltNm, sFntNm; + bool bIsAltFntNm = false; + + rtl_TextEncoding nSystemChar = lcl_GetDefaultTextEncodingForRTF(); + aFont.SetCharSet( nSystemChar ); + SetEncoding( nSystemChar ); + + while( _nOpenBrackets && IsParserWorking() ) + { + bool bCheckNewFont = false; + int nToken = GetNextToken(); + switch( nToken ) + { + case '}': + bIsAltFntNm = false; + // Style has been completely read, + // so this is still a stable status + if( --_nOpenBrackets <= 1 && IsParserWorking() ) + SaveState( RTF_FONTTBL ); + bCheckNewFont = true; + nInsFontNo = nFontNo; + break; + case '{': + if( RTF_IGNOREFLAG != GetNextToken() ) + SkipToken(); + // immediately skip unknown and all known but non-evaluated + // groups + else if( RTF_UNKNOWNCONTROL != ( nToken = GetNextToken() ) && + RTF_PANOSE != nToken && RTF_FNAME != nToken && + RTF_FONTEMB != nToken && RTF_FONTFILE != nToken ) + SkipToken( -2 ); + else + { + // filter out at once + ReadUnknownData(); + nToken = GetNextToken(); + if( '}' != nToken ) + eState = SvParserState::Error; + break; + } + ++_nOpenBrackets; + break; + case RTF_FROMAN: + aFont.SetFamily( FAMILY_ROMAN ); + break; + case RTF_FSWISS: + aFont.SetFamily( FAMILY_SWISS ); + break; + case RTF_FMODERN: + aFont.SetFamily( FAMILY_MODERN ); + break; + case RTF_FSCRIPT: + aFont.SetFamily( FAMILY_SCRIPT ); + break; + case RTF_FDECOR: + aFont.SetFamily( FAMILY_DECORATIVE ); + break; + // for technical/symbolic font of the rtl_TextEncoding is changed! + case RTF_FTECH: + aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + [[fallthrough]]; + case RTF_FNIL: + aFont.SetFamily( FAMILY_DONTKNOW ); + break; + case RTF_FCHARSET: + if (-1 != nTokenValue) + { + rtl_TextEncoding nrtl_TextEncoding = rtl_getTextEncodingFromWindowsCharset( + static_cast<sal_uInt8>(nTokenValue)); + aFont.SetCharSet(nrtl_TextEncoding); + //When we're in a font, the fontname is in the font + //charset, except for symbol fonts I believe + if (nrtl_TextEncoding == RTL_TEXTENCODING_SYMBOL) + nrtl_TextEncoding = RTL_TEXTENCODING_DONTKNOW; + SetEncoding(nrtl_TextEncoding); + } + break; + case RTF_FPRQ: + switch( nTokenValue ) + { + case 1: + aFont.SetPitch( PITCH_FIXED ); + break; + case 2: + aFont.SetPitch( PITCH_VARIABLE ); + break; + } + break; + case RTF_F: + bCheckNewFont = true; + nInsFontNo = nFontNo; + nFontNo = static_cast<short>(nTokenValue); + break; + case RTF_FALT: + bIsAltFntNm = true; + break; + case RTF_TEXTTOKEN: + DelCharAtEnd( aToken, ';' ); + if ( !aToken.isEmpty() ) + { + if( bIsAltFntNm ) + sAltNm = aToken; + else + sFntNm = aToken; + } + break; + } + + if( bCheckNewFont && 1 >= _nOpenBrackets && !sFntNm.isEmpty() ) // one font is ready + { + // All data from the font is available, so off to the table + if (!sAltNm.isEmpty()) + sFntNm += ";" + sAltNm; + + aFont.SetFamilyName( sFntNm ); + m_FontTable.insert(std::make_pair(nInsFontNo, aFont)); + aFont = vcl::Font(); + aFont.SetCharSet( nSystemChar ); + sAltNm.clear(); + sFntNm.clear(); + } + } + SkipToken(); // the closing brace is evaluated "above" + + // set the default font in the Document + if( bNewDoc && IsParserWorking() ) + SetDefault( RTF_DEFF, nDfltFont ); +} + +void SvxRTFParser::ClearColorTbl() +{ + maColorTable.clear(); +} + +void SvxRTFParser::ClearAttrStack() +{ + aAttrStack.clear(); +} + +void SvxRTFParser::DelCharAtEnd( OUStringBuffer& rStr, const sal_Unicode cDel ) +{ + rStr.strip(' '); + if( !rStr.isEmpty() && cDel == rStr[ rStr.getLength()-1 ]) + rStr.setLength( rStr.getLength()-1 ); +} + + +const vcl::Font& SvxRTFParser::GetFont( sal_uInt16 nId ) +{ + SvxRTFFontTbl::const_iterator it = m_FontTable.find( nId ); + if (it != m_FontTable.end()) + { + return it->second; + } + const SvxFontItem& rDfltFont = + pAttrPool->GetDefaultItem(aPlainMap[SID_ATTR_CHAR_FONT]); + pDfltFont->SetFamilyName( rDfltFont.GetStyleName() ); + pDfltFont->SetFamily( rDfltFont.GetFamily() ); + return *pDfltFont; +} + +std::unique_ptr<SvxRTFItemStackType> SvxRTFItemStackType::createSvxRTFItemStackType( + SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, const EditPosition& rEditPosition) +{ + struct MakeUniqueEnabler : public SvxRTFItemStackType + { + MakeUniqueEnabler(SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, const EditPosition& rEditPosition) + : SvxRTFItemStackType(rPool, pWhichRange, rEditPosition) + { + } + }; + return std::make_unique<MakeUniqueEnabler>(rPool, pWhichRange, rEditPosition); +} + +SvxRTFItemStackType* SvxRTFParser::GetAttrSet_() +{ + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + std::unique_ptr<SvxRTFItemStackType> xNew; + if( pCurrent ) + xNew = std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, false/*bCopyAttr*/); + else + xNew = SvxRTFItemStackType::createSvxRTFItemStackType(*pAttrPool, aWhichMap, *mxInsertPosition); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + aAttrStack.push_back( std::move(xNew) ); + + if (aAttrStack.size() > 96 && utl::ConfigManager::IsFuzzing()) + throw std::range_error("ecStackOverflow"); + + bNewGroup = false; + return aAttrStack.back().get(); +} + +void SvxRTFParser::ClearStyleAttr_( SvxRTFItemStackType& rStkType ) +{ + // check attributes to the attributes of the stylesheet or to + // the default attrs of the document + SfxItemSet &rSet = rStkType.GetAttrSet(); + const SfxItemPool& rPool = *rSet.GetPool(); + const SfxPoolItem* pItem; + SfxWhichIter aIter( rSet ); + + if( !IsChkStyleAttr() || + !rStkType.GetAttrSet().Count() || + m_StyleTable.count( rStkType.nStyleNo ) == 0 ) + { + for( sal_uInt16 nWhich = aIter.GetCurWhich(); nWhich; nWhich = aIter.NextWhich() ) + { + if (SfxItemPool::IsWhich(nWhich) && + SfxItemState::SET == aIter.GetItemState( false, &pItem ) && + rPool.GetDefaultItem( nWhich ) == *pItem ) + aIter.ClearItem(); // delete + } + } + else + { + // Delete all Attributes, which are already defined in the Style, + // from the current AttrSet. + auto & rStyle = m_StyleTable.find(rStkType.nStyleNo)->second; + SfxItemSet &rStyleSet = rStyle.aAttrSet; + const SfxPoolItem* pSItem; + for( sal_uInt16 nWhich = aIter.GetCurWhich(); nWhich; nWhich = aIter.NextWhich() ) + { + if( SfxItemState::SET == rStyleSet.GetItemState( nWhich, true, &pSItem )) + { + if( SfxItemState::SET == aIter.GetItemState( false, &pItem ) + && *pItem == *pSItem ) + rSet.ClearItem( nWhich ); // delete + } + else if (SfxItemPool::IsWhich(nWhich) && + SfxItemState::SET == aIter.GetItemState( false, &pItem ) && + rPool.GetDefaultItem( nWhich ) == *pItem ) + rSet.ClearItem( nWhich ); // delete + } + } +} + +void SvxRTFParser::AttrGroupEnd() // process the current, delete from Stack +{ + if( aAttrStack.empty() ) + return; + + std::unique_ptr<SvxRTFItemStackType> pOld = std::move(aAttrStack.back()); + aAttrStack.pop_back(); + SvxRTFItemStackType *pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + + do { // middle check loop + sal_Int32 nOldSttNdIdx = pOld->mxStartNodeIdx->GetIdx(); + if (pOld->maChildList.empty() && + ((!pOld->aAttrSet.Count() && !pOld->nStyleNo ) || + (nOldSttNdIdx == mxInsertPosition->GetNodeIdx() && + pOld->nSttCnt == mxInsertPosition->GetCntIdx() ))) + break; // no attributes or Area + + // set only the attributes that are different from the parent + if( pCurrent && pOld->aAttrSet.Count() ) + { + SfxItemIter aIter( pOld->aAttrSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(), *pGet; + do + { + if( SfxItemState::SET == pCurrent->aAttrSet.GetItemState( + pItem->Which(), false, &pGet ) && + *pItem == *pGet ) + aIter.ClearItem(); + + pItem = aIter.NextItem(); + } while (pItem); + + if (!pOld->aAttrSet.Count() && pOld->maChildList.empty() && + !pOld->nStyleNo ) + break; + } + + // Set all attributes which have been defined from start until here + bool bCrsrBack = !mxInsertPosition->GetCntIdx(); + if( bCrsrBack ) + { + // at the beginning of a paragraph? Move back one position + sal_Int32 nNd = mxInsertPosition->GetNodeIdx(); + MovePos(false); + // if can not move backward then later don't move forward ! + bCrsrBack = nNd != mxInsertPosition->GetNodeIdx(); + } + + if( pOld->mxStartNodeIdx->GetIdx() < mxInsertPosition->GetNodeIdx() || + ( pOld->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pOld->nSttCnt <= mxInsertPosition->GetCntIdx() ) ) + { + if( !bCrsrBack ) + { + // all pard attributes are only valid until the previous + // paragraph !! + if( nOldSttNdIdx == mxInsertPosition->GetNodeIdx() ) + { + } + else + { + // Now it gets complicated: + // - all character attributes sre keep the area + // - all paragraph attributes to get the area + // up to the previous paragraph + auto xNew = std::make_unique<SvxRTFItemStackType>(*pOld, *mxInsertPosition, true); + xNew->aAttrSet.SetParent( pOld->aAttrSet.GetParent() ); + + // Delete all paragraph attributes from xNew + for (const auto& pair : aPardMap.data) + if (sal_uInt16 wid = pair.second) + xNew->aAttrSet.ClearItem(wid); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Were there any? + if( xNew->aAttrSet.Count() == pOld->aAttrSet.Count() ) + { + xNew.reset(); + } + else + { + xNew->nStyleNo = 0; + + // Now span the real area of xNew from old + SetEndPrevPara( pOld->mxEndNodeIdx, pOld->nEndCnt ); + xNew->nSttCnt = 0; + + if( IsChkStyleAttr() ) + { + ClearStyleAttr_( *pOld ); + ClearStyleAttr_( *xNew ); //#i10381#, methinks. + } + + if( pCurrent ) + { + pCurrent->Add(std::move(pOld)); + pCurrent->Add(std::move(xNew)); + } + else + { + // Last off the stack, thus cache it until the next text was + // read. (Span no attributes!) + + m_AttrSetList.push_back(std::move(pOld)); + m_AttrSetList.push_back(std::move(xNew)); + } + break; + } + } + } + + pOld->mxEndNodeIdx = mxInsertPosition->MakeNodeIdx(); + pOld->nEndCnt = mxInsertPosition->GetCntIdx(); + + /* + #i21422# + If the parent (pCurrent) sets something e.g. , and the child (pOld) + unsets it and the style both are based on has it unset then + clearing the pOld by looking at the style is clearly a disaster + as the text ends up with pCurrents bold and not pOlds no bold, this + should be rethought out. For the moment its safest to just do + the clean if we have no parent, all we suffer is too many + redundant properties. + */ + if (IsChkStyleAttr() && !pCurrent) + ClearStyleAttr_( *pOld ); + + if( pCurrent ) + { + pCurrent->Add(std::move(pOld)); + // split up and create new entry, because it makes no sense + // to create a "so long" depend list. Bug 95010 + if (bCrsrBack && 50 < pCurrent->maChildList.size()) + { + // at the beginning of a paragraph? Move back one position + MovePos(); + bCrsrBack = false; + + // Open a new Group. + auto xNew(std::make_unique<SvxRTFItemStackType>(*pCurrent, *mxInsertPosition, true)); + xNew->SetRTFDefaults( GetRTFDefaults() ); + + // Set all until here valid Attributes + AttrGroupEnd(); + pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); // can be changed after AttrGroupEnd! + xNew->aAttrSet.SetParent( pCurrent ? &pCurrent->aAttrSet : nullptr ); + aAttrStack.push_back( std::move(xNew) ); + } + } + else + // Last off the stack, thus cache it until the next text was + // read. (Span no attributes!) + m_AttrSetList.push_back(std::move(pOld)); + } + + if( bCrsrBack ) + // at the beginning of a paragraph? Move back one position + MovePos(); + + } while( false ); + + bNewGroup = false; +} + +void SvxRTFParser::SetAllAttrOfStk() // end all Attr. and set it into doc +{ + // repeat until all attributes will be taken from stack + while( !aAttrStack.empty() ) + AttrGroupEnd(); + + for (size_t n = m_AttrSetList.size(); n; ) + { + auto const& pStkSet = m_AttrSetList[--n]; + SetAttrSet( *pStkSet ); + pStkSet->DropChildList(); + m_AttrSetList.pop_back(); + } +} + +// sets all the attributes that are different from the current +void SvxRTFParser::SetAttrSet( SvxRTFItemStackType &rSet ) +{ + // Was DefTab never read? then set to default + if( !bIsSetDfltTab ) + SetDefault( RTF_DEFTAB, 720 ); + + if (!rSet.maChildList.empty()) + rSet.Compress( *this ); + if( rSet.aAttrSet.Count() || rSet.nStyleNo ) + SetAttrInDoc( rSet ); + + // then process all the children + for (size_t n = 0; n < rSet.maChildList.size(); ++n) + SetAttrSet( *(rSet.maChildList[ n ]) ); +} + +// Has no text been inserted yet? (SttPos from the top Stack entry!) +bool SvxRTFParser::IsAttrSttPos() +{ + SvxRTFItemStackType* pCurrent = aAttrStack.empty() ? nullptr : aAttrStack.back().get(); + return !pCurrent || (pCurrent->mxStartNodeIdx->GetIdx() == mxInsertPosition->GetNodeIdx() && + pCurrent->nSttCnt == mxInsertPosition->GetCntIdx()); +} + + +void SvxRTFParser::SetAttrInDoc( SvxRTFItemStackType & ) +{ +} + +void SvxRTFParser::BuildWhichTable() +{ + aWhichMap.reset(); + + // Here are the IDs for all paragraph attributes, which can be detected by + // SvxParser and can be set in a SfxItemSet. The IDs are set correctly through + // the SlotIds from POOL. + static constexpr sal_uInt16 WIDS1[] { + SID_ATTR_PARA_LINESPACE, + SID_ATTR_PARA_ADJUST, + SID_ATTR_TABSTOP, + SID_ATTR_PARA_HYPHENZONE, + SID_ATTR_LRSPACE, + SID_ATTR_ULSPACE, + SID_ATTR_BRUSH, + SID_ATTR_BORDER_OUTER, + SID_ATTR_BORDER_SHADOW, + SID_ATTR_PARA_OUTLLEVEL, + SID_ATTR_PARA_SPLIT, + SID_ATTR_PARA_KEEP, + SID_PARA_VERTALIGN, + SID_ATTR_PARA_SCRIPTSPACE, + SID_ATTR_PARA_HANGPUNCTUATION, + SID_ATTR_PARA_FORBIDDEN_RULES, + SID_ATTR_FRAMEDIRECTION, + }; + for (sal_uInt16 nWid : WIDS1) + { + sal_uInt16 nTrueWid = pAttrPool->GetTrueWhich(nWid, false); + aPardMap.data[nWid] = nTrueWid; + if (nTrueWid == 0) + continue; + aWhichMap = aWhichMap.MergeRange(nTrueWid, nTrueWid); + } + + // Here are the IDs for all character attributes, which can be detected by + // SvxParser and can be set in a SfxItemSet. The IDs are set correctly through + // the SlotIds from POOL. + static constexpr sal_uInt16 WIDS[] { + SID_ATTR_CHAR_CASEMAP, SID_ATTR_BRUSH_CHAR, SID_ATTR_CHAR_COLOR, + SID_ATTR_CHAR_CONTOUR, SID_ATTR_CHAR_STRIKEOUT, SID_ATTR_CHAR_ESCAPEMENT, + SID_ATTR_CHAR_FONT, SID_ATTR_CHAR_FONTHEIGHT, SID_ATTR_CHAR_KERNING, + SID_ATTR_CHAR_LANGUAGE, SID_ATTR_CHAR_POSTURE, SID_ATTR_CHAR_SHADOWED, + SID_ATTR_CHAR_UNDERLINE, SID_ATTR_CHAR_OVERLINE, SID_ATTR_CHAR_WEIGHT, + SID_ATTR_CHAR_WORDLINEMODE, SID_ATTR_CHAR_AUTOKERN, SID_ATTR_CHAR_CJK_FONT, + SID_ATTR_CHAR_CJK_FONTHEIGHT, sal_uInt16(SID_ATTR_CHAR_CJK_LANGUAGE), SID_ATTR_CHAR_CJK_POSTURE, + SID_ATTR_CHAR_CJK_WEIGHT, SID_ATTR_CHAR_CTL_FONT, SID_ATTR_CHAR_CTL_FONTHEIGHT, + SID_ATTR_CHAR_CTL_LANGUAGE, SID_ATTR_CHAR_CTL_POSTURE, SID_ATTR_CHAR_CTL_WEIGHT, + SID_ATTR_CHAR_EMPHASISMARK, SID_ATTR_CHAR_TWO_LINES, SID_ATTR_CHAR_SCALEWIDTH, + SID_ATTR_CHAR_ROTATED, SID_ATTR_CHAR_RELIEF, SID_ATTR_CHAR_HIDDEN, + }; + for (sal_uInt16 nWid : WIDS) + { + sal_uInt16 nTrueWid = pAttrPool->GetTrueWhich(nWid, false); + aPlainMap.data[nWid] = nTrueWid; + if (nTrueWid == 0) + continue; + aWhichMap = aWhichMap.MergeRange(nTrueWid, nTrueWid); + } +} + +const SfxItemSet& SvxRTFParser::GetRTFDefaults() +{ + if( !pRTFDefaults ) + { + pRTFDefaults.reset(new SfxItemSet(*pAttrPool, aWhichMap)); + if (const sal_uInt16 nId = aPardMap[SID_ATTR_PARA_SCRIPTSPACE]) + { + SvxScriptSpaceItem aItem( false, nId ); + if( bNewDoc ) + pAttrPool->SetPoolDefaultItem( aItem ); + else + pRTFDefaults->Put( aItem ); + } + } + return *pRTFDefaults; +} + + +SvxRTFStyleType::SvxRTFStyleType(SfxItemPool& rPool, const WhichRangesContainer& pWhichRange) + : aAttrSet(rPool, pWhichRange) + , nBasedOn(0) + , nOutlineNo(sal_uInt8(-1)) // not set +{ +} + +SvxRTFItemStackType::SvxRTFItemStackType( + SfxItemPool& rPool, const WhichRangesContainer& pWhichRange, + const EditPosition& rPos ) + : aAttrSet( rPool, pWhichRange ) + , mxStartNodeIdx(rPos.MakeNodeIdx()) +#if !defined(__COVERITY__) + // coverity 2020 has difficulty wrt std::optional leading to bogus 'Uninitialized scalar variable' + , mxEndNodeIdx(mxStartNodeIdx) +#endif + , nSttCnt(rPos.GetCntIdx()) + , nEndCnt(nSttCnt) + , nStyleNo(0) +{ +} + +SvxRTFItemStackType::SvxRTFItemStackType( + const SvxRTFItemStackType& rCpy, + const EditPosition& rPos, + bool const bCopyAttr ) + : aAttrSet( *rCpy.aAttrSet.GetPool(), rCpy.aAttrSet.GetRanges() ) + , mxStartNodeIdx(rPos.MakeNodeIdx()) +#if !defined(__COVERITY__) + // coverity 2020 has difficulty wrt std::optional leading to bogus 'Uninitialized scalar variable' + , mxEndNodeIdx(mxStartNodeIdx) +#endif + , nSttCnt(rPos.GetCntIdx()) + , nEndCnt(nSttCnt) + , nStyleNo(rCpy.nStyleNo) +{ + aAttrSet.SetParent( &rCpy.aAttrSet ); + if( bCopyAttr ) + aAttrSet.Put( rCpy.aAttrSet ); +} + +/* ofz#13491 SvxRTFItemStackType dtor recursively + calls the dtor of its m_pChildList. The recurse + depth can grow sufficiently to trigger asan. + + So breadth-first iterate through the nodes + and make a flat vector of them which can + be iterated through in order of most + distant from root first and release + their children linearly +*/ +void SvxRTFItemStackType::DropChildList() +{ + if (maChildList.empty()) + return; + + std::vector<SvxRTFItemStackType*> bfs; + std::queue<SvxRTFItemStackType*> aQueue; + aQueue.push(this); + + while (!aQueue.empty()) + { + auto* front = aQueue.front(); + aQueue.pop(); + if (!front->maChildList.empty()) + { + for (const auto& a : front->maChildList) + aQueue.push(a.get()); + bfs.push_back(front); + } + } + + for (auto it = bfs.rbegin(); it != bfs.rend(); ++it) + { + SvxRTFItemStackType* pNode = *it; + pNode->maChildList.clear(); + } +} + +SvxRTFItemStackType::~SvxRTFItemStackType() +{ +} + +void SvxRTFItemStackType::Add(std::unique_ptr<SvxRTFItemStackType> pIns) +{ + maChildList.push_back(std::move(pIns)); +} + +void SvxRTFItemStackType::SetStartPos( const EditPosition& rPos ) +{ + mxStartNodeIdx = rPos.MakeNodeIdx(); + mxEndNodeIdx = mxStartNodeIdx; + nSttCnt = rPos.GetCntIdx(); +} + +void SvxRTFItemStackType::Compress( const SvxRTFParser& rParser ) +{ + ENSURE_OR_RETURN_VOID(!maChildList.empty(), "Compress: ChildList empty"); + + SvxRTFItemStackType* pTmp = maChildList[0].get(); + + if( !pTmp->aAttrSet.Count() || + mxStartNodeIdx->GetIdx() != pTmp->mxStartNodeIdx->GetIdx() || + nSttCnt != pTmp->nSttCnt ) + return; + + EditNodeIdx aLastNd = *pTmp->mxEndNodeIdx; + sal_Int32 nLastCnt = pTmp->nEndCnt; + + SfxItemSet aMrgSet( pTmp->aAttrSet ); + for (size_t n = 1; n < maChildList.size(); ++n) + { + pTmp = maChildList[n].get(); + if (!pTmp->maChildList.empty()) + pTmp->Compress( rParser ); + + if( !pTmp->nSttCnt + ? (aLastNd.GetIdx()+1 != pTmp->mxStartNodeIdx->GetIdx() || + !rParser.IsEndPara( &aLastNd, nLastCnt ) ) + : ( pTmp->nSttCnt != nLastCnt || + aLastNd.GetIdx() != pTmp->mxStartNodeIdx->GetIdx() )) + { + while (++n < maChildList.size()) + { + pTmp = maChildList[n].get(); + if (!pTmp->maChildList.empty()) + pTmp->Compress( rParser ); + } + return; + } + + if( n ) + { + // Search for all which are set over the whole area + SfxItemIter aIter( aMrgSet ); + const SfxPoolItem* pItem; + const SfxPoolItem* pIterItem = aIter.GetCurItem(); + do { + sal_uInt16 nWhich = pIterItem->Which(); + if( SfxItemState::SET != pTmp->aAttrSet.GetItemState( nWhich, + false, &pItem ) || *pItem != *pIterItem) + aIter.ClearItem(); + + pIterItem = aIter.NextItem(); + } while(pIterItem); + + if( !aMrgSet.Count() ) + return; + } + + aLastNd = *pTmp->mxEndNodeIdx; + nLastCnt = pTmp->nEndCnt; + } + + if( mxEndNodeIdx->GetIdx() != aLastNd.GetIdx() || nEndCnt != nLastCnt ) + return; + + // It can be merged + aAttrSet.Put( aMrgSet ); + + size_t n = 0, nChildLen = maChildList.size(); + while (n < nChildLen) + { + pTmp = maChildList[n].get(); + pTmp->aAttrSet.Differentiate( aMrgSet ); + + if (pTmp->maChildList.empty() && !pTmp->aAttrSet.Count() && !pTmp->nStyleNo) + { + maChildList.erase( maChildList.begin() + n ); + --nChildLen; + continue; + } + ++n; + } +} +void SvxRTFItemStackType::SetRTFDefaults( const SfxItemSet& rDefaults ) +{ + if( rDefaults.Count() ) + { + SfxItemIter aIter( rDefaults ); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do { + sal_uInt16 nWhich = pItem->Which(); + if( SfxItemState::SET != aAttrSet.GetItemState( nWhich, false )) + aAttrSet.Put(*pItem); + + pItem = aIter.NextItem(); + } while(pItem); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/UnoForbiddenCharsTable.cxx b/editeng/source/uno/UnoForbiddenCharsTable.cxx new file mode 100644 index 0000000000..5e77a9252f --- /dev/null +++ b/editeng/source/uno/UnoForbiddenCharsTable.cxx @@ -0,0 +1,132 @@ +/* -*- 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 <com/sun/star/container/NoSuchElementException.hpp> +#include <editeng/UnoForbiddenCharsTable.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <utility> +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::i18n; +using namespace ::cppu; + +SvxUnoForbiddenCharsTable::SvxUnoForbiddenCharsTable(std::shared_ptr<SvxForbiddenCharactersTable> xForbiddenChars) + : mxForbiddenChars(std::move(xForbiddenChars)) +{ +} + +SvxUnoForbiddenCharsTable::~SvxUnoForbiddenCharsTable() +{ +} + +void SvxUnoForbiddenCharsTable::onChange() +{ +} + +ForbiddenCharacters SvxUnoForbiddenCharsTable::getForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + const ForbiddenCharacters* pForbidden = mxForbiddenChars->GetForbiddenCharacters( eLang, false ); + if(!pForbidden) + throw NoSuchElementException(); + + return *pForbidden; +} + +sal_Bool SvxUnoForbiddenCharsTable::hasForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + return false; + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + const ForbiddenCharacters* pForbidden = mxForbiddenChars->GetForbiddenCharacters( eLang, false ); + + return nullptr != pForbidden; +} + +void SvxUnoForbiddenCharsTable::setForbiddenCharacters(const lang::Locale& rLocale, const ForbiddenCharacters& rForbiddenCharacters ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + mxForbiddenChars->SetForbiddenCharacters( eLang, rForbiddenCharacters ); + + onChange(); +} + +void SvxUnoForbiddenCharsTable::removeForbiddenCharacters( const lang::Locale& rLocale ) +{ + SolarMutexGuard aGuard; + + if (!mxForbiddenChars) + throw RuntimeException("No Forbidden Characters present"); + + const LanguageType eLang = LanguageTag::convertToLanguageType( rLocale ); + mxForbiddenChars->ClearForbiddenCharacters( eLang ); + + onChange(); +} + +// XSupportedLocales +Sequence< lang::Locale > SAL_CALL SvxUnoForbiddenCharsTable::getLocales() +{ + SolarMutexGuard aGuard; + + const sal_Int32 nCount = mxForbiddenChars ? mxForbiddenChars->GetMap().size() : 0; + + Sequence< lang::Locale > aLocales( nCount ); + if( nCount ) + { + lang::Locale* pLocales = aLocales.getArray(); + + for (auto const& elem : mxForbiddenChars->GetMap()) + { + const LanguageType nLanguage = elem.first; + *pLocales++ = LanguageTag( nLanguage ).getLocale(); + } + } + + return aLocales; +} + +sal_Bool SAL_CALL SvxUnoForbiddenCharsTable::hasLocale( const lang::Locale& aLocale ) +{ + SolarMutexGuard aGuard; + + return hasForbiddenCharacters( aLocale ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedhlp.cxx b/editeng/source/uno/unoedhlp.cxx new file mode 100644 index 0000000000..2a1b1e2bd5 --- /dev/null +++ b/editeng/source/uno/unoedhlp.cxx @@ -0,0 +1,250 @@ +/* -*- 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 <memory> +#include <editeng/unoedhlp.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <svl/itemset.hxx> + +#include <osl/diagnose.h> + + +SvxEditSourceHint::SvxEditSourceHint( SfxHintId _nId ) : + TextHint( _nId ), + mnStart( 0 ), + mnEnd( 0 ) +{ +} + +SvxEditSourceHint::SvxEditSourceHint( SfxHintId _nId, sal_Int32 nValue, sal_Int32 nStart, sal_Int32 nEnd ) : + TextHint( _nId, nValue ), + mnStart( nStart), + mnEnd( nEnd ) +{ +} + + +std::unique_ptr<SfxHint> SvxEditSourceHelper::EENotification2Hint( EENotify const * aNotify ) +{ + if( aNotify ) + { + switch( aNotify->eNotificationType ) + { + case EE_NOTIFY_TEXTMODIFIED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextModified, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHINSERTED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextParaInserted, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHREMOVED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextParaRemoved, aNotify->nParagraph ) ); + + case EE_NOTIFY_PARAGRAPHSMOVED: + return std::unique_ptr<SfxHint>( new SvxEditSourceHint( SfxHintId::EditSourceParasMoved, aNotify->nParagraph, aNotify->nParam1, aNotify->nParam2 ) ); + + case EE_NOTIFY_TextHeightChanged: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextHeightChanged, aNotify->nParagraph ) ); + + case EE_NOTIFY_TEXTVIEWSCROLLED: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextViewScrolled ) ); + + case EE_NOTIFY_TEXTVIEWSELECTIONCHANGED: + return std::unique_ptr<SfxHint>( new SvxEditSourceHint( SfxHintId::EditSourceSelectionChanged ) ); + + case EE_NOTIFY_PROCESSNOTIFICATIONS: + return std::unique_ptr<SfxHint>( new TextHint( SfxHintId::TextProcessNotifications )); + + case EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA: + return std::unique_ptr<SfxHint>( new SvxEditSourceHintEndPara ); + default: + OSL_FAIL( "SvxEditSourceHelper::EENotification2Hint unknown notification" ); + break; + } + } + + return std::make_unique<SfxHint>( ); +} + +void SvxEditSourceHelper::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, const EditEngine& rEE, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) +{ + // IA2 CWS introduced bInCell, but also did many other changes here. + // Need to verify implementation with AT (IA2 and ATK) + // Old implementation at the end of the method for reference... + + //added dummy attributes for the default text + std::vector<EECharAttrib> aCharAttribs, aTempCharAttribs; + rEE.GetCharAttribs( nPara, aTempCharAttribs ); + + if (!aTempCharAttribs.empty()) + { + sal_Int32 nIndex2 = 0; + sal_Int32 nParaLen = rEE.GetTextLen(nPara); + for (size_t nAttr = 0; nAttr < aTempCharAttribs.size(); ++nAttr) + { + if (nIndex2 < aTempCharAttribs[nAttr].nStart) + { + EECharAttrib aEEAttr(nIndex2, aTempCharAttribs[nAttr].nStart); + aCharAttribs.insert(aCharAttribs.begin() + nAttr, aEEAttr); + } + nIndex2 = aTempCharAttribs[nAttr].nEnd; + aCharAttribs.push_back(aTempCharAttribs[nAttr]); + } + if ( nIndex2 != nParaLen ) + { + EECharAttrib aEEAttr(nIndex2, nParaLen); + aCharAttribs.push_back(aEEAttr); + } + } + // find closest index in front of nIndex + sal_Int32 nCurrIndex; + sal_Int32 nClosestStartIndex_s = 0, nClosestStartIndex_e = 0; + for (auto const& charAttrib : aCharAttribs) + { + nCurrIndex = charAttrib.nStart; + + if( nCurrIndex > nClosestStartIndex_s && + nCurrIndex <= nIndex) + { + nClosestStartIndex_s = nCurrIndex; + } + nCurrIndex = charAttrib.nEnd; + if ( nCurrIndex > nClosestStartIndex_e && + nCurrIndex < nIndex ) + { + nClosestStartIndex_e = nCurrIndex; + } + } + sal_Int32 nClosestStartIndex = std::max(nClosestStartIndex_s, nClosestStartIndex_e); + + // find closest index behind of nIndex + sal_Int32 nClosestEndIndex_s, nClosestEndIndex_e; + nClosestEndIndex_s = nClosestEndIndex_e = rEE.GetTextLen(nPara); + for (auto const& charAttrib : aCharAttribs) + { + nCurrIndex = charAttrib.nEnd; + + if( nCurrIndex > nIndex && + nCurrIndex < nClosestEndIndex_e ) + { + nClosestEndIndex_e = nCurrIndex; + } + nCurrIndex = charAttrib.nStart; + if ( nCurrIndex > nIndex && + nCurrIndex < nClosestEndIndex_s) + { + nClosestEndIndex_s = nCurrIndex; + } + } + sal_Int32 nClosestEndIndex = std::min(nClosestEndIndex_s, nClosestEndIndex_e); + + nStartIndex = nClosestStartIndex; + nEndIndex = nClosestEndIndex; + + if ( !bInCell ) + return; + + EPosition aStartPos( nPara, nStartIndex ), aEndPos( nPara, nEndIndex ); + sal_Int32 nParaCount = rEE.GetParagraphCount(); + sal_Int32 nCrrntParaLen = rEE.GetTextLen(nPara); + //need to find closest index in front of nIndex in the previous paragraphs + if ( aStartPos.nIndex == 0 ) + { + SfxItemSet aCrrntSet = rEE.GetAttribs( nPara, 0, 1, GetAttribsFlags::CHARATTRIBS ); + for ( sal_Int32 nParaIdx = nPara-1; nParaIdx >= 0; nParaIdx-- ) + { + sal_uInt32 nLen = rEE.GetTextLen(nParaIdx); + if ( nLen ) + { + sal_Int32 nStartIdx, nEndIdx; + GetAttributeRun( nStartIdx, nEndIdx, rEE, nParaIdx, nLen ); + SfxItemSet aSet = rEE.GetAttribs( nParaIdx, nLen-1, nLen, GetAttribsFlags::CHARATTRIBS ); + if ( aSet == aCrrntSet ) + { + aStartPos.nPara = nParaIdx; + aStartPos.nIndex = nStartIdx; + if ( aStartPos.nIndex != 0 ) + { + break; + } + } + } + } + } + //need find closest index behind nIndex in the following paragraphs + if ( aEndPos.nIndex == nCrrntParaLen ) + { + SfxItemSet aCrrntSet = rEE.GetAttribs( nPara, nCrrntParaLen-1, nCrrntParaLen, GetAttribsFlags::CHARATTRIBS ); + for ( sal_Int32 nParaIdx = nPara+1; nParaIdx < nParaCount; nParaIdx++ ) + { + sal_Int32 nLen = rEE.GetTextLen( nParaIdx ); + if ( nLen ) + { + sal_Int32 nStartIdx, nEndIdx; + GetAttributeRun( nStartIdx, nEndIdx, rEE, nParaIdx, 0 ); + SfxItemSet aSet = rEE.GetAttribs( nParaIdx, 0, 1, GetAttribsFlags::CHARATTRIBS ); + if ( aSet == aCrrntSet ) + { + aEndPos.nPara = nParaIdx; + aEndPos.nIndex = nEndIdx; + if ( aEndPos.nIndex != nLen ) + { + break; + } + } + } + } + } + nStartIndex = 0; + if ( aStartPos.nPara > 0 ) + { + for ( sal_Int32 i = 0; i < aStartPos.nPara; i++ ) + { + nStartIndex += rEE.GetTextLen(i)+1; + } + } + nStartIndex += aStartPos.nIndex; + nEndIndex = 0; + if ( aEndPos.nPara > 0 ) + { + for ( sal_Int32 i = 0; i < aEndPos.nPara; i++ ) + { + nEndIndex += rEE.GetTextLen(i)+1; + } + } + nEndIndex += aEndPos.nIndex; +} + +Point SvxEditSourceHelper::EEToUserSpace( const Point& rPoint, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? Point( -rPoint.Y() + rEESize.Height(), rPoint.X() ) : rPoint; +} + +Point SvxEditSourceHelper::UserSpaceToEE( const Point& rPoint, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? Point( rPoint.Y(), -rPoint.X() + rEESize.Height() ) : rPoint; +} + +tools::Rectangle SvxEditSourceHelper::EEToUserSpace( const tools::Rectangle& rRect, const Size& rEESize, bool bIsVertical ) +{ + return bIsVertical ? tools::Rectangle( EEToUserSpace(rRect.BottomLeft(), rEESize, bIsVertical), + EEToUserSpace(rRect.TopRight(), rEESize, bIsVertical) ) : rRect; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedprx.cxx b/editeng/source/uno/unoedprx.cxx new file mode 100644 index 0000000000..20d5df281b --- /dev/null +++ b/editeng/source/uno/unoedprx.cxx @@ -0,0 +1,1209 @@ +/* -*- 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 . + */ + + +// Global header + + +#include <utility> +#include <memory> +#include <vector> +#include <algorithm> +#include <osl/diagnose.h> +#include <svl/itemset.hxx> +#include <tools/debug.hxx> + + +// Project-local header + + +#include <editeng/unoedprx.hxx> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <AccessibleStringWrap.hxx> +#include <editeng/outliner.hxx> + +using namespace ::com::sun::star; + +namespace { + +class SvxAccessibleTextIndex +{ +public: + SvxAccessibleTextIndex() : + mnPara(0), + mnIndex(0), + mnEEIndex(0), + mnFieldOffset(0), + mnFieldLen(0), + mnBulletOffset(0), + mnBulletLen(0), + mbInField(false), + mbInBullet(false) {}; + + // Get/Set current paragraph + void SetParagraph( sal_Int32 nPara ) + { + mnPara = nPara; + } + sal_Int32 GetParagraph() const { return mnPara; } + + /** Set the index in the UAA semantic + + @param nIndex + The index from the UA API (fields and bullets are expanded) + + @param rTF + The text forwarder to use in the calculations + */ + void SetIndex( sal_Int32 nIndex, const SvxTextForwarder& rTF ); + void SetIndex( sal_Int32 nPara, sal_Int32 nIndex, const SvxTextForwarder& rTF ) { SetParagraph(nPara); SetIndex(nIndex, rTF); } + sal_Int32 GetIndex() const { return mnIndex; } + + /** Set the index in the edit engine semantic + + Update the object state to reflect the given index position in + EditEngine/Outliner index values + + @param nEEIndex + The index from the edit engine (fields span exactly one index increment) + + @param rTF + The text forwarder to use in the calculations + */ + void SetEEIndex( sal_Int32 nEEIndex, const SvxTextForwarder& rTF ); + void SetEEIndex( sal_Int32 nPara, sal_Int32 nEEIndex, const SvxTextForwarder& rTF ) { SetParagraph(nPara); SetEEIndex(nEEIndex, rTF); } + sal_Int32 GetEEIndex() const; + + void SetFieldOffset( sal_Int32 nOffset, sal_Int32 nLen ) { mnFieldOffset = nOffset; mnFieldLen = nLen; } + sal_Int32 GetFieldOffset() const { return mnFieldOffset; } + sal_Int32 GetFieldLen() const { return mnFieldLen; } + void AreInField() { mbInField = true; } + bool InField() const { return mbInField; } + + void SetBulletOffset( sal_Int32 nOffset, sal_Int32 nLen ) { mnBulletOffset = nOffset; mnBulletLen = nLen; } + sal_Int32 GetBulletOffset() const { return mnBulletOffset; } + sal_Int32 GetBulletLen() const { return mnBulletLen; } + bool InBullet() const { return mbInBullet; } + + /// returns false if the given range is non-editable (e.g. contains bullets or _parts_ of fields) + bool IsEditableRange( const SvxAccessibleTextIndex& rEnd ) const; + +private: + sal_Int32 mnPara; + sal_Int32 mnIndex; + sal_Int32 mnEEIndex; + sal_Int32 mnFieldOffset; + sal_Int32 mnFieldLen; + sal_Int32 mnBulletOffset; + sal_Int32 mnBulletLen; + bool mbInField; + bool mbInBullet; +}; + +} + +static ESelection MakeEESelection( const SvxAccessibleTextIndex& rStart, const SvxAccessibleTextIndex& rEnd ) +{ + // deal with field special case: to really get a field contained + // within a selection, the start index must be before or on the + // field, the end index after it. + + // The SvxAccessibleTextIndex.GetEEIndex method gives the index on + // the field, as long the input index is on the field. Thus, + // correction necessary for the end index + + // Therefore, for _ranges_, if part of the field is touched, all + // of the field must be selected + if( rStart.GetParagraph() <= rEnd.GetParagraph() || + (rStart.GetParagraph() == rEnd.GetParagraph() && + rStart.GetEEIndex() <= rEnd.GetEEIndex()) ) + { + if( rEnd.InField() && rEnd.GetFieldOffset() ) + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex(), + rEnd.GetParagraph(), rEnd.GetEEIndex()+1 ); + } + else if( rStart.GetParagraph() > rEnd.GetParagraph() || + (rStart.GetParagraph() == rEnd.GetParagraph() && + rStart.GetEEIndex() > rEnd.GetEEIndex()) ) + { + if( rStart.InField() && rStart.GetFieldOffset() ) + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex()+1, + rEnd.GetParagraph(), rEnd.GetEEIndex() ); + } + + return ESelection( rStart.GetParagraph(), rStart.GetEEIndex(), + rEnd.GetParagraph(), rEnd.GetEEIndex() ); +} + +static ESelection MakeEESelection( const SvxAccessibleTextIndex& rIndex ) +{ + return ESelection( rIndex.GetParagraph(), rIndex.GetEEIndex(), + rIndex.GetParagraph(), rIndex.GetEEIndex() + 1 ); +} + +sal_Int32 SvxAccessibleTextIndex::GetEEIndex() const +{ + DBG_ASSERT(mnEEIndex >= 0, + "SvxAccessibleTextIndex::GetEEIndex: index value overflow"); + + return mnEEIndex; +} + +void SvxAccessibleTextIndex::SetEEIndex( sal_Int32 nEEIndex, const SvxTextForwarder& rTF ) +{ + // reset + mnFieldOffset = 0; + mbInField = false; + mnFieldLen = 0; + mnBulletOffset = 0; + mbInBullet = false; + mnBulletLen = 0; + + // set known values + mnEEIndex = nEEIndex; + + // calculate unknowns + sal_Int32 nCurrField, nFieldCount = rTF.GetFieldCount( GetParagraph() ); + + mnIndex = nEEIndex; + + EBulletInfo aBulletInfo = rTF.GetBulletInfo( GetParagraph() ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + mnIndex += aBulletInfo.aText.getLength(); + } + + for( nCurrField=0; nCurrField < nFieldCount; ++nCurrField ) + { + EFieldInfo aFieldInfo( rTF.GetFieldInfo( GetParagraph(), nCurrField ) ); + + if( aFieldInfo.aPosition.nIndex > nEEIndex ) + break; + + if( aFieldInfo.aPosition.nIndex == nEEIndex ) + { + AreInField(); + break; + } + + mnIndex += std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)); + } +} + +void SvxAccessibleTextIndex::SetIndex( sal_Int32 nIndex, const SvxTextForwarder& rTF ) +{ + // reset + mnFieldOffset = 0; + mbInField = false; + mnFieldLen = 0; + mnBulletOffset = 0; + mbInBullet = false; + mnBulletLen = 0; + + // set known values + mnIndex = nIndex; + + // calculate unknowns + sal_Int32 nCurrField, nFieldCount = rTF.GetFieldCount( GetParagraph() ); + + DBG_ASSERT(nIndex >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + mnEEIndex = nIndex; + + EBulletInfo aBulletInfo = rTF.GetBulletInfo( GetParagraph() ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + sal_Int32 nBulletLen = aBulletInfo.aText.getLength(); + + if( nIndex < nBulletLen ) + { + mbInBullet = true; + SetBulletOffset( nIndex, nBulletLen ); + mnEEIndex = 0; + return; + } + + mnEEIndex = mnEEIndex - nBulletLen; + } + + for( nCurrField=0; nCurrField < nFieldCount; ++nCurrField ) + { + EFieldInfo aFieldInfo( rTF.GetFieldInfo( GetParagraph(), nCurrField ) ); + + // we're before a field + if( aFieldInfo.aPosition.nIndex > mnEEIndex ) + break; + + mnEEIndex -= std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)); + + // we're within a field + if( aFieldInfo.aPosition.nIndex >= mnEEIndex ) + { + AreInField(); + SetFieldOffset( std::max(aFieldInfo.aCurrentText.getLength()-1, sal_Int32(0)) - (aFieldInfo.aPosition.nIndex - mnEEIndex), + aFieldInfo.aCurrentText.getLength() ); + mnEEIndex = aFieldInfo.aPosition.nIndex ; + break; + } + } +} + +bool SvxAccessibleTextIndex::IsEditableRange( const SvxAccessibleTextIndex& rEnd ) const +{ + if( GetIndex() > rEnd.GetIndex() ) + return rEnd.IsEditableRange( *this ); + + if( InBullet() || rEnd.InBullet() ) + return false; + + if( InField() && GetFieldOffset() ) + return false; // within field + + if( rEnd.InField() && rEnd.GetFieldOffset() >= rEnd.GetFieldLen() - 1 ) + return false; // within field + + return true; +} + + +SvxEditSourceAdapter::SvxEditSourceAdapter() : mbEditSourceValid( false ) +{ +} + +SvxEditSourceAdapter::~SvxEditSourceAdapter() +{ +} + +std::unique_ptr<SvxEditSource> SvxEditSourceAdapter::Clone() const +{ + if( mbEditSourceValid && mpAdaptee ) + { + std::unique_ptr< SvxEditSource > pClonedAdaptee( mpAdaptee->Clone() ); + + if (pClonedAdaptee) + { + std::unique_ptr<SvxEditSourceAdapter> pClone(new SvxEditSourceAdapter()); + pClone->SetEditSource( std::move(pClonedAdaptee) ); + return std::unique_ptr< SvxEditSource >(pClone.release()); + } + } + + return nullptr; +} + +SvxAccessibleTextAdapter* SvxEditSourceAdapter::GetTextForwarderAdapter() +{ + if( mbEditSourceValid && mpAdaptee ) + { + SvxTextForwarder* pTextForwarder = mpAdaptee->GetTextForwarder(); + + if( pTextForwarder ) + { + maTextAdapter.SetForwarder(*pTextForwarder); + + return &maTextAdapter; + } + } + + return nullptr; +} + +SvxTextForwarder* SvxEditSourceAdapter::GetTextForwarder() +{ + return GetTextForwarderAdapter(); +} + +SvxViewForwarder* SvxEditSourceAdapter::GetViewForwarder() +{ + if( mbEditSourceValid && mpAdaptee ) + return mpAdaptee->GetViewForwarder(); + + return nullptr; +} + +SvxAccessibleTextEditViewAdapter* SvxEditSourceAdapter::GetEditViewForwarderAdapter( bool bCreate ) +{ + if( mbEditSourceValid && mpAdaptee ) + { + SvxEditViewForwarder* pEditViewForwarder = mpAdaptee->GetEditViewForwarder(bCreate); + + if( pEditViewForwarder ) + { + SvxAccessibleTextAdapter* pTextAdapter = GetTextForwarderAdapter(); + + if( pTextAdapter ) + { + maEditViewAdapter.SetForwarder(*pEditViewForwarder, *pTextAdapter); + + return &maEditViewAdapter; + } + } + } + + return nullptr; +} + +SvxEditViewForwarder* SvxEditSourceAdapter::GetEditViewForwarder( bool bCreate ) +{ + return GetEditViewForwarderAdapter( bCreate ); +} + +void SvxEditSourceAdapter::UpdateData() +{ + if( mbEditSourceValid && mpAdaptee ) + mpAdaptee->UpdateData(); +} + +SfxBroadcaster& SvxEditSourceAdapter::GetBroadcaster() const +{ + if( mbEditSourceValid && mpAdaptee ) + return mpAdaptee->GetBroadcaster(); + + return maDummyBroadcaster; +} + +void SvxEditSourceAdapter::SetEditSource( std::unique_ptr< SvxEditSource > && pAdaptee ) +{ + if (pAdaptee) + { + mpAdaptee = std::move(pAdaptee); + mbEditSourceValid = true; + } + else + { + // do a lazy delete (prevents us from deleting the broadcaster + // from within a broadcast in + // AccessibleTextHelper_Impl::Notify) + mbEditSourceValid = false; + } +} + +SvxAccessibleTextAdapter::SvxAccessibleTextAdapter() + : mpTextForwarder(nullptr) +{ +} + +SvxAccessibleTextAdapter::~SvxAccessibleTextAdapter() +{ +} + +sal_Int32 SvxAccessibleTextAdapter::GetParagraphCount() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetParagraphCount(); +} + +sal_Int32 SvxAccessibleTextAdapter::GetTextLen( sal_Int32 nParagraph ) const +{ + SvxAccessibleTextIndex aIndex; + aIndex.SetEEIndex( nParagraph, mpTextForwarder->GetTextLen( nParagraph ), *this ); + + return aIndex.GetIndex(); +} + +OUString SvxAccessibleTextAdapter::GetText( const ESelection& rSel ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + // normalize selection + if( rSel.nStartPara > rSel.nEndPara || + (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos > rSel.nEndPos) ) + { + std::swap( aStartIndex, aEndIndex ); + } + + OUString sStr = mpTextForwarder->GetText( MakeEESelection(aStartIndex, aEndIndex) ); + + // trim field text, if necessary + if( aStartIndex.InField() ) + { + DBG_ASSERT(aStartIndex.GetFieldOffset() >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy( aStartIndex.GetFieldOffset() ); + } + if( aEndIndex.InField() && aEndIndex.GetFieldOffset() ) + { + DBG_ASSERT(sStr.getLength() - (aEndIndex.GetFieldLen() - aEndIndex.GetFieldOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy(0, sStr.getLength() - (aEndIndex.GetFieldLen() - aEndIndex.GetFieldOffset()) ); + } + + EBulletInfo aBulletInfo2 = GetBulletInfo( aEndIndex.GetParagraph() ); + + if( aEndIndex.InBullet() ) + { + // append trailing bullet + sStr += aBulletInfo2.aText; + + DBG_ASSERT(sStr.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sStr = sStr.copy(0, sStr.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) ); + } + else if( aStartIndex.GetParagraph() != aEndIndex.GetParagraph() && + HaveTextBullet( aEndIndex.GetParagraph() ) ) + { + OUString sBullet = aBulletInfo2.aText; + + DBG_ASSERT(sBullet.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) >= 0, + "SvxAccessibleTextIndex::GetText: index value overflow"); + + sBullet = sBullet.copy(0, sBullet.getLength() - (aEndIndex.GetBulletLen() - aEndIndex.GetBulletOffset()) ); + + // insert bullet + sStr = sStr.replaceAt( GetTextLen(aStartIndex.GetParagraph()) - aStartIndex.GetIndex(), 0, sBullet ); + } + + return sStr; +} + +SfxItemSet SvxAccessibleTextAdapter::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->GetAttribs( MakeEESelection(aStartIndex, aEndIndex), nOnlyHardAttrib ); +} + +SfxItemSet SvxAccessibleTextAdapter::GetParaAttribs( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetParaAttribs( nPara ); +} + +void SvxAccessibleTextAdapter::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->SetParaAttribs( nPara, rSet ); +} + +void SvxAccessibleTextAdapter::RemoveAttribs( const ESelection& ) +{ +} + +void SvxAccessibleTextAdapter::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->GetPortions( nPara, rList ); +} + +OUString SvxAccessibleTextAdapter::GetStyleSheet(sal_Int32 nPara) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetStyleSheet(nPara); +} + +void SvxAccessibleTextAdapter::SetStyleSheet(sal_Int32 nPara, const OUString& rStyleName) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->SetStyleSheet(nPara, rStyleName); +} + +SfxItemState SvxAccessibleTextAdapter::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->GetItemState( MakeEESelection(aStartIndex, aEndIndex), + nWhich ); +} + +SfxItemState SvxAccessibleTextAdapter::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetItemState( nPara, nWhich ); +} + +void SvxAccessibleTextAdapter::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertText( rText, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertField( rFld, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickSetAttribs( rSet, + MakeEESelection(aStartIndex, aEndIndex) ); +} + +void SvxAccessibleTextAdapter::QuickInsertLineBreak( const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + mpTextForwarder->QuickInsertLineBreak( MakeEESelection(aStartIndex, aEndIndex) ); +} + +SfxItemPool* SvxAccessibleTextAdapter::GetPool() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetPool(); +} + +OUString SvxAccessibleTextAdapter::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor, rpFldLineStyle ); +} + +void SvxAccessibleTextAdapter::FieldClicked( const SvxFieldItem& rField ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + mpTextForwarder->FieldClicked( rField ); +} + +sal_Int32 SvxAccessibleTextAdapter::CalcEditEngineIndex( sal_Int32 nPara, sal_Int32 nLogicalIndex ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nLogicalIndex, *mpTextForwarder); + return aIndex.GetEEIndex(); +} + +bool SvxAccessibleTextAdapter::IsValid() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + if( mpTextForwarder ) + return mpTextForwarder->IsValid(); + else + return false; +} + +LanguageType SvxAccessibleTextAdapter::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + + aIndex.SetIndex( nPara, nPos, *this ); + + return mpTextForwarder->GetLanguage( nPara, aIndex.GetEEIndex() ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetFieldCount( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetFieldCount( nPara ); +} + +EFieldInfo SvxAccessibleTextAdapter::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxAccessibleTextAdapter::GetBulletInfo( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetBulletInfo( nPara ); +} + +tools::Rectangle SvxAccessibleTextAdapter::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex( nPara, nIndex, *this ); + + // preset if anything goes wrong below + // n-th char in GetParagraphIndex's paragraph + tools::Rectangle aRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + + if( aIndex.InBullet() ) + { + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetCharBounds: No ref device"); + + // preset if anything goes wrong below + aRect = aBulletInfo.aBounds; // better than nothing + if( pOutDev ) + { + AccessibleStringWrap aStringWrap( *pOutDev, aBulletInfo.aFont, aBulletInfo.aText ); + + aStringWrap.GetCharacterBounds( aIndex.GetBulletOffset(), aRect ); + aRect.Move( aBulletInfo.aBounds.Left(), aBulletInfo.aBounds.Top() ); + } + } + else + { + // handle field content manually + if( aIndex.InField() ) + { + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetCharBounds: No ref device"); + + if( pOutDev ) + { + ESelection aSel = MakeEESelection( aIndex ); + + SvxFont aFont = EditEngine::CreateSvxFontFromItemSet( mpTextForwarder->GetAttribs( aSel ) ); + AccessibleStringWrap aStringWrap( *pOutDev, + aFont, + mpTextForwarder->GetText( aSel ) ); + + tools::Rectangle aStartRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + + aStringWrap.GetCharacterBounds( aIndex.GetFieldOffset(), aRect ); + aRect.Move( aStartRect.Left(), aStartRect.Top() ); + } + } + } + + return aRect; +} + +tools::Rectangle SvxAccessibleTextAdapter::GetParaBounds( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + // include bullet in para bounding box + tools::Rectangle aRect( mpTextForwarder->GetParaBounds( nPara ) ); + + aRect.Union( aBulletInfo.aBounds ); + + return aRect; + } + + return mpTextForwarder->GetParaBounds( nPara ); +} + +MapMode SvxAccessibleTextAdapter::GetMapMode() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetMapMode(); +} + +OutputDevice* SvxAccessibleTextAdapter::GetRefDevice() const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetRefDevice(); +} + +bool SvxAccessibleTextAdapter::GetIndexAtPoint( const Point& rPoint, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + if( !mpTextForwarder->GetIndexAtPoint( rPoint, nPara, nIndex ) ) + return false; + + SvxAccessibleTextIndex aIndex; + aIndex.SetEEIndex(nPara, nIndex, *this); + + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = aIndex.GetIndex(); + + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + // any text bullets? + if( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ) + { + if( aBulletInfo.aBounds.Contains( rPoint) ) + { + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetIndexAtPoint: No ref device"); + + if( !pOutDev ) + return false; + + AccessibleStringWrap aStringWrap( *pOutDev, aBulletInfo.aFont, aBulletInfo.aText ); + + Point aPoint = rPoint; + aPoint.Move( -aBulletInfo.aBounds.Left(), -aBulletInfo.aBounds.Top() ); + + DBG_ASSERT(aStringWrap.GetIndexAtPoint( aPoint ) >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = aStringWrap.GetIndexAtPoint( aPoint ); + return true; + } + } + + if( !aIndex.InField() ) + return true; + + OutputDevice* pOutDev = GetRefDevice(); + + DBG_ASSERT(pOutDev!=nullptr, "SvxAccessibleTextAdapter::GetIndexAtPoint: No ref device"); + + if( !pOutDev ) + return false; + + ESelection aSelection = MakeEESelection( aIndex ); + SvxFont aFont = EditEngine::CreateSvxFontFromItemSet( mpTextForwarder->GetAttribs( aSelection ) ); + AccessibleStringWrap aStringWrap( *pOutDev, + aFont, + mpTextForwarder->GetText( aSelection ) ); + + tools::Rectangle aRect = mpTextForwarder->GetCharBounds( nPara, aIndex.GetEEIndex() ); + Point aPoint = rPoint; + aPoint.Move( -aRect.Left(), -aRect.Top() ); + + DBG_ASSERT(aIndex.GetIndex() + aStringWrap.GetIndexAtPoint( rPoint ) >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + nIndex = (aIndex.GetIndex() + aStringWrap.GetIndexAtPoint( aPoint )); + return true; +} + +bool SvxAccessibleTextAdapter::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nIndex, *this); + nIndex = aIndex.GetEEIndex(); + + if( aIndex.InBullet() ) + { + DBG_ASSERT(aIndex.GetBulletLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat bullet as separate word + nStart = 0; + nEnd = aIndex.GetBulletLen(); + + return true; + } + + if( aIndex.InField() ) + { + DBG_ASSERT(aIndex.GetIndex() - aIndex.GetFieldOffset() >= 0 && + nStart + aIndex.GetFieldLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat field as separate word + // TODO: to circumvent this, _we_ would have to do the break iterator stuff! + nStart = aIndex.GetIndex() - aIndex.GetFieldOffset(); + nEnd = nStart + aIndex.GetFieldLen(); + + return true; + } + + if( !mpTextForwarder->GetWordIndices( nPara, nIndex, nStart, nEnd ) ) + return false; + + aIndex.SetEEIndex( nPara, nStart, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nStart = aIndex.GetIndex(); + + aIndex.SetEEIndex( nPara, nEnd, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nEnd = aIndex.GetIndex(); + + return true; +} + +bool SvxAccessibleTextAdapter::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool /* bInCell */ ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aIndex; + aIndex.SetIndex(nPara, nIndex, *this); + nIndex = aIndex.GetEEIndex(); + + if( aIndex.InBullet() ) + { + DBG_ASSERT(aIndex.GetBulletLen() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat bullet as distinct attribute + nStartIndex = 0; + nEndIndex = aIndex.GetBulletLen(); + + return true; + } + + if( aIndex.InField() ) + { + DBG_ASSERT(aIndex.GetIndex() - aIndex.GetFieldOffset() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + + // always treat field as distinct attribute + nStartIndex = aIndex.GetIndex() - aIndex.GetFieldOffset(); + nEndIndex = nStartIndex + aIndex.GetFieldLen(); + + return true; + } + + if( !mpTextForwarder->GetAttributeRun( nStartIndex, nEndIndex, nPara, nIndex ) ) + return false; + + aIndex.SetEEIndex( nPara, nStartIndex, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nStartIndex = aIndex.GetIndex(); + + aIndex.SetEEIndex( nPara, nEndIndex, *this ); + DBG_ASSERT(aIndex.GetIndex() >= 0, + "SvxAccessibleTextIndex::SetIndex: index value overflow"); + nEndIndex = aIndex.GetIndex(); + + return true; +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineCount( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetLineCount( nPara ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aEndIndex; + sal_Int32 nCurrLine; + sal_Int32 nCurrIndex, nLastIndex; + for( nCurrLine=0, nCurrIndex=0, nLastIndex=0; nCurrLine<=nLine; ++nCurrLine ) + { + nLastIndex = nCurrIndex; + nCurrIndex = + nCurrIndex + mpTextForwarder->GetLineLen( nPara, nCurrLine ); + } + + aEndIndex.SetEEIndex( nPara, nCurrIndex, *this ); + if( nLine > 0 ) + { + SvxAccessibleTextIndex aStartIndex; + aStartIndex.SetEEIndex( nPara, nLastIndex, *this ); + + return aEndIndex.GetIndex() - aStartIndex.GetIndex(); + } + else + return aEndIndex.GetIndex(); +} + +void SvxAccessibleTextAdapter::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const +{ + mpTextForwarder->GetLineBoundaries( rStart, rEnd, nParagraph, nLine ); +} + +sal_Int32 SvxAccessibleTextAdapter::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return mpTextForwarder->GetLineNumberAtIndex( nPara, nIndex ); +} + +bool SvxAccessibleTextAdapter::Delete( const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->Delete( MakeEESelection(aStartIndex, aEndIndex ) ); +} + +bool SvxAccessibleTextAdapter::InsertText( const OUString& rStr, const ESelection& rSel ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + return mpTextForwarder->InsertText( rStr, MakeEESelection(aStartIndex, aEndIndex) ); +} + +bool SvxAccessibleTextAdapter::QuickFormatDoc( bool bFull ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->QuickFormatDoc( bFull ); +} + +sal_Int16 SvxAccessibleTextAdapter::GetDepth( sal_Int32 nPara ) const +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->GetDepth( nPara ); +} + +bool SvxAccessibleTextAdapter::SetDepth( sal_Int32 nPara, sal_Int16 nNewDepth ) +{ + assert(mpTextForwarder && "SvxAccessibleTextAdapter: no forwarder"); + + return mpTextForwarder->SetDepth( nPara, nNewDepth ); +} + +void SvxAccessibleTextAdapter::SetForwarder( SvxTextForwarder& rForwarder ) +{ + mpTextForwarder = &rForwarder; +} + +bool SvxAccessibleTextAdapter::HaveImageBullet( sal_Int32 nPara ) const +{ + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + return ( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType == SVX_NUM_BITMAP ); +} + +bool SvxAccessibleTextAdapter::HaveTextBullet( sal_Int32 nPara ) const +{ + EBulletInfo aBulletInfo = GetBulletInfo( nPara ); + + return ( aBulletInfo.nParagraph != EE_PARA_NOT_FOUND && + aBulletInfo.bVisible && + aBulletInfo.nType != SVX_NUM_BITMAP ); +} + +bool SvxAccessibleTextAdapter::IsEditable( const ESelection& rSel ) const +{ + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *this ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *this ); + + // normalize selection + if( rSel.nStartPara > rSel.nEndPara || + (rSel.nStartPara == rSel.nEndPara && rSel.nStartPos > rSel.nEndPos) ) + { + std::swap( aStartIndex, aEndIndex ); + } + + return aStartIndex.IsEditableRange( aEndIndex ); +} + +const SfxItemSet * SvxAccessibleTextAdapter::GetEmptyItemSetPtr() +{ + OSL_FAIL( "not implemented" ); + return nullptr; +} + +void SvxAccessibleTextAdapter::AppendParagraph() +{ + OSL_FAIL( "not implemented" ); +} + +sal_Int32 SvxAccessibleTextAdapter::AppendTextPortion( sal_Int32, const OUString &, const SfxItemSet & ) +{ + OSL_FAIL( "not implemented" ); + return 0; +} +void SvxAccessibleTextAdapter::CopyText(const SvxTextForwarder&) +{ + OSL_FAIL( "not implemented" ); +} + +SvxAccessibleTextEditViewAdapter::SvxAccessibleTextEditViewAdapter() + : mpViewForwarder(nullptr) + , mpTextForwarder(nullptr) +{ +} + +SvxAccessibleTextEditViewAdapter::~SvxAccessibleTextEditViewAdapter() +{ +} + +bool SvxAccessibleTextEditViewAdapter::IsValid() const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + if( mpViewForwarder ) + return mpViewForwarder->IsValid(); + else + return false; +} + +Point SvxAccessibleTextEditViewAdapter::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->LogicToPixel(rPoint, rMapMode); +} + +Point SvxAccessibleTextEditViewAdapter::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->PixelToLogic(rPoint, rMapMode); +} + +bool SvxAccessibleTextEditViewAdapter::GetSelection( ESelection& rSel ) const +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + ESelection aSelection; + + if( !mpViewForwarder->GetSelection( aSelection ) ) + return false; + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetEEIndex( aSelection.nStartPara, aSelection.nStartPos, *mpTextForwarder ); + aEndIndex.SetEEIndex( aSelection.nEndPara, aSelection.nEndPos, *mpTextForwarder ); + + DBG_ASSERT(aStartIndex.GetIndex() >= 0 && + aEndIndex.GetIndex() >= 0, + "SvxAccessibleTextEditViewAdapter::GetSelection: index value overflow"); + + rSel = ESelection( aStartIndex.GetParagraph(), aStartIndex.GetIndex(), + aEndIndex.GetParagraph(), aEndIndex.GetIndex() ); + + return true; +} + +bool SvxAccessibleTextEditViewAdapter::SetSelection( const ESelection& rSel ) +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + SvxAccessibleTextIndex aStartIndex; + SvxAccessibleTextIndex aEndIndex; + + aStartIndex.SetIndex( rSel.nStartPara, rSel.nStartPos, *mpTextForwarder ); + aEndIndex.SetIndex( rSel.nEndPara, rSel.nEndPos, *mpTextForwarder ); + + return mpViewForwarder->SetSelection( MakeEESelection(aStartIndex, aEndIndex) ); +} + +bool SvxAccessibleTextEditViewAdapter::Copy() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Copy(); +} + +bool SvxAccessibleTextEditViewAdapter::Cut() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Cut(); +} + +bool SvxAccessibleTextEditViewAdapter::Paste() +{ + DBG_ASSERT(mpViewForwarder, "SvxAccessibleTextEditViewAdapter: no forwarder"); + + return mpViewForwarder->Paste(); +} + +void SvxAccessibleTextEditViewAdapter::SetForwarder( SvxEditViewForwarder& rForwarder, + SvxAccessibleTextAdapter& rTextForwarder ) +{ + mpViewForwarder = &rForwarder; + mpTextForwarder = &rTextForwarder; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoedsrc.cxx b/editeng/source/uno/unoedsrc.cxx new file mode 100644 index 0000000000..1c827472d1 --- /dev/null +++ b/editeng/source/uno/unoedsrc.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <svl/SfxBroadcaster.hxx> + +#include <editeng/unoedsrc.hxx> + +#include <osl/diagnose.h> + + +void SvxEditSource::addRange( SvxUnoTextRangeBase* ) +{ +} + + +void SvxEditSource::removeRange( SvxUnoTextRangeBase* ) +{ +} + + +const SvxUnoTextRangeBaseVec& SvxEditSource::getRanges() const +{ + static SvxUnoTextRangeBaseVec gList; + return gList; +} + + +SvxTextForwarder::~SvxTextForwarder() COVERITY_NOEXCEPT_FALSE +{ +} + + +SvxViewForwarder::~SvxViewForwarder() +{ +} + + +SvxEditSource::~SvxEditSource() +{ +} + +SvxViewForwarder* SvxEditSource::GetViewForwarder() +{ + return nullptr; +} + +SvxEditViewForwarder* SvxEditSource::GetEditViewForwarder( bool ) +{ + return nullptr; +} + +SfxBroadcaster& SvxEditSource::GetBroadcaster() const +{ + OSL_FAIL("SvxEditSource::GetBroadcaster called for implementation missing this feature!"); + + static SfxBroadcaster aBroadcaster; + + return aBroadcaster; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofdesc.cxx b/editeng/source/uno/unofdesc.cxx new file mode 100644 index 0000000000..722ae7d7f9 --- /dev/null +++ b/editeng/source/uno/unofdesc.cxx @@ -0,0 +1,229 @@ +/* -*- 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 <editeng/eeitem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/awt/FontDescriptor.hpp> + +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/memberids.h> +#include <svl/itempool.hxx> +#include <vcl/font.hxx> +#include <vcl/unohelp.hxx> +#include <tools/gen.hxx> + +#include <editeng/unofdesc.hxx> + +using namespace ::com::sun::star; + + +void SvxUnoFontDescriptor::ConvertToFont( const awt::FontDescriptor& rDesc, vcl::Font& rFont ) +{ + rFont.SetFamilyName( rDesc.Name ); + rFont.SetStyleName( rDesc.StyleName ); + rFont.SetFontSize( Size( rDesc.Width, rDesc.Height ) ); + rFont.SetFamily( static_cast<FontFamily>(rDesc.Family) ); + rFont.SetCharSet( static_cast<rtl_TextEncoding>(rDesc.CharSet) ); + rFont.SetPitch( static_cast<FontPitch>(rDesc.Pitch) ); + rFont.SetOrientation( Degree10(static_cast<sal_Int16>(rDesc.Orientation*10)) ); + rFont.SetKerning( rDesc.Kerning ? FontKerning::FontSpecific : FontKerning::NONE ); + rFont.SetWeight( vcl::unohelper::ConvertFontWeight(rDesc.Weight) ); + rFont.SetItalic( static_cast<FontItalic>(rDesc.Slant) ); + rFont.SetUnderline( static_cast<FontLineStyle>(rDesc.Underline) ); + rFont.SetStrikeout( static_cast<FontStrikeout>(rDesc.Strikeout) ); + rFont.SetWordLineMode( rDesc.WordLineMode ); +} + +void SvxUnoFontDescriptor::ConvertFromFont( const vcl::Font& rFont, awt::FontDescriptor& rDesc ) +{ + rDesc.Name = rFont.GetFamilyName(); + rDesc.StyleName = rFont.GetStyleName(); + rDesc.Width = sal::static_int_cast< sal_Int16 >(rFont.GetFontSize().Width()); + rDesc.Height = sal::static_int_cast< sal_Int16 >(rFont.GetFontSize().Height()); + rDesc.Family = sal::static_int_cast< sal_Int16 >(rFont.GetFamilyType()); + rDesc.CharSet = rFont.GetCharSet(); + rDesc.Pitch = sal::static_int_cast< sal_Int16 >(rFont.GetPitch()); + rDesc.Orientation = static_cast< float >(rFont.GetOrientation().get() / 10); + rDesc.Kerning = rFont.IsKerning(); + rDesc.Weight = vcl::unohelper::ConvertFontWeight( rFont.GetWeight() ); + rDesc.Slant = vcl::unohelper::ConvertFontSlant( rFont.GetItalic() ); + rDesc.Underline = sal::static_int_cast< sal_Int16 >(rFont.GetUnderline()); + rDesc.Strikeout = sal::static_int_cast< sal_Int16 >(rFont.GetStrikeout()); + rDesc.WordLineMode = rFont.IsWordLineMode(); +} + +void SvxUnoFontDescriptor::FillItemSet( const awt::FontDescriptor& rDesc, SfxItemSet& rSet ) +{ + uno::Any aTemp; + + { + SvxFontItem aFontItem( EE_CHAR_FONTINFO ); + aFontItem.SetFamilyName( rDesc.Name); + aFontItem.SetStyleName( rDesc.StyleName); + aFontItem.SetFamily( static_cast<FontFamily>(rDesc.Family)); + aFontItem.SetCharSet( rDesc.CharSet ); + aFontItem.SetPitch( static_cast<FontPitch>(rDesc.Pitch)); + rSet.Put(aFontItem); + } + + { + SvxFontHeightItem aFontHeightItem( 0, 100, EE_CHAR_FONTHEIGHT ); + aTemp <<= static_cast<float>(rDesc.Height); + static_cast<SfxPoolItem*>(&aFontHeightItem)->PutValue( aTemp, MID_FONTHEIGHT|CONVERT_TWIPS ); + rSet.Put(aFontHeightItem); + } + + { + SvxPostureItem aPostureItem( ITALIC_NONE, EE_CHAR_ITALIC ); + aTemp <<= rDesc.Slant; + static_cast<SfxPoolItem*>(&aPostureItem)->PutValue( aTemp, MID_POSTURE ); + rSet.Put(aPostureItem); + } + + { + SvxUnderlineItem aUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE ); + aTemp <<= rDesc.Underline; + static_cast<SfxPoolItem*>(&aUnderlineItem)->PutValue( aTemp, MID_TL_STYLE ); + rSet.Put( aUnderlineItem ); + } + + { + SvxWeightItem aWeightItem( WEIGHT_DONTKNOW, EE_CHAR_WEIGHT ); + aTemp <<= rDesc.Weight; + static_cast<SfxPoolItem*>(&aWeightItem)->PutValue( aTemp, MID_WEIGHT ); + rSet.Put( aWeightItem ); + } + + { + SvxCrossedOutItem aCrossedOutItem( STRIKEOUT_NONE, EE_CHAR_STRIKEOUT ); + aTemp <<= rDesc.Strikeout; + static_cast<SfxPoolItem*>(&aCrossedOutItem)->PutValue( aTemp, MID_CROSS_OUT ); + rSet.Put( aCrossedOutItem ); + } + + { + SvxWordLineModeItem aWLMItem( rDesc.WordLineMode, EE_CHAR_WLM ); + rSet.Put( aWLMItem ); + } +} + +void SvxUnoFontDescriptor::FillFromItemSet( const SfxItemSet& rSet, awt::FontDescriptor& rDesc ) +{ + const SfxPoolItem* pItem = nullptr; + { + const SvxFontItem* pFontItem = &rSet.Get( EE_CHAR_FONTINFO ); + rDesc.Name = pFontItem->GetFamilyName(); + rDesc.StyleName = pFontItem->GetStyleName(); + rDesc.Family = sal::static_int_cast< sal_Int16 >( + pFontItem->GetFamily()); + rDesc.CharSet = pFontItem->GetCharSet(); + rDesc.Pitch = sal::static_int_cast< sal_Int16 >( + pFontItem->GetPitch()); + } + { + pItem = &rSet.Get( EE_CHAR_FONTHEIGHT ); + uno::Any aHeight; + if( pItem->QueryValue( aHeight, MID_FONTHEIGHT ) ) + aHeight >>= rDesc.Height; + } + { + pItem = &rSet.Get( EE_CHAR_ITALIC ); + uno::Any aFontSlant; + if(pItem->QueryValue( aFontSlant, MID_POSTURE )) + aFontSlant >>= rDesc.Slant; + } + { + pItem = &rSet.Get( EE_CHAR_UNDERLINE ); + uno::Any aUnderline; + if(pItem->QueryValue( aUnderline, MID_TL_STYLE )) + aUnderline >>= rDesc.Underline; + } + { + pItem = &rSet.Get( EE_CHAR_WEIGHT ); + uno::Any aWeight; + if(pItem->QueryValue( aWeight, MID_WEIGHT )) + aWeight >>= rDesc.Weight; + } + { + pItem = &rSet.Get( EE_CHAR_STRIKEOUT ); + uno::Any aStrikeOut; + if(pItem->QueryValue( aStrikeOut, MID_CROSS_OUT )) + aStrikeOut >>= rDesc.Strikeout; + } + { + const SvxWordLineModeItem* pWLMItem = &rSet.Get( EE_CHAR_WLM ); + rDesc.WordLineMode = pWLMItem->GetValue(); + } +} + +void SvxUnoFontDescriptor::setPropertyToDefault( SfxItemSet& rSet ) +{ + rSet.InvalidateItem( EE_CHAR_FONTINFO ); + rSet.InvalidateItem( EE_CHAR_FONTHEIGHT ); + rSet.InvalidateItem( EE_CHAR_ITALIC ); + rSet.InvalidateItem( EE_CHAR_UNDERLINE ); + rSet.InvalidateItem( EE_CHAR_WEIGHT ); + rSet.InvalidateItem( EE_CHAR_STRIKEOUT ); + rSet.InvalidateItem( EE_CHAR_WLM ); +} + +uno::Any SvxUnoFontDescriptor::getPropertyDefault( SfxItemPool* pPool ) +{ + SfxItemSetFixed< + EE_CHAR_FONTINFO, EE_CHAR_FONTHEIGHT, + EE_CHAR_WEIGHT, EE_CHAR_ITALIC, + EE_CHAR_WLM, EE_CHAR_WLM> aSet(*pPool); + + uno::Any aAny; + + if(!SfxItemPool::IsWhich(EE_CHAR_FONTINFO)|| + !SfxItemPool::IsWhich(EE_CHAR_FONTHEIGHT)|| + !SfxItemPool::IsWhich(EE_CHAR_ITALIC)|| + !SfxItemPool::IsWhich(EE_CHAR_UNDERLINE)|| + !SfxItemPool::IsWhich(EE_CHAR_WEIGHT)|| + !SfxItemPool::IsWhich(EE_CHAR_STRIKEOUT)|| + !SfxItemPool::IsWhich(EE_CHAR_WLM)) + return aAny; + + aSet.Put(pPool->GetDefaultItem(EE_CHAR_FONTINFO)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_FONTHEIGHT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_ITALIC)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_UNDERLINE)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_WEIGHT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_STRIKEOUT)); + aSet.Put(pPool->GetDefaultItem(EE_CHAR_WLM)); + + awt::FontDescriptor aDesc; + + FillFromItemSet( aSet, aDesc ); + + aAny <<= aDesc; + + return aAny; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofield.cxx b/editeng/source/uno/unofield.cxx new file mode 100644 index 0000000000..6f2e84a957 --- /dev/null +++ b/editeng/source/uno/unofield.cxx @@ -0,0 +1,941 @@ +/* -*- 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 <string_view> + +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/text/FilenameDisplayFormat.hpp> +#include <o3tl/string_view.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <tools/debug.hxx> +#include <svl/itemprop.hxx> + +#include <editeng/flditem.hxx> +#include <editeng/CustomPropertyField.hxx> +#include <editeng/measfld.hxx> +#include <editeng/unofield.hxx> +#include <editeng/unotext.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <sal/log.hxx> + +#include <editeng/unonames.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star; + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + aAny <<= uno::Reference< xint >(this) + + +#define WID_DATE 0 +#define WID_BOOL1 1 +#define WID_BOOL2 2 +#define WID_INT32 3 +#define WID_INT16 4 +#define WID_STRING1 5 +#define WID_STRING2 6 +#define WID_STRING3 7 + +class SvxUnoFieldData_Impl +{ +public: + bool mbBoolean1; + bool mbBoolean2; + sal_Int32 mnInt32; + sal_Int16 mnInt16; + OUString msString1; + OUString msString2; + OUString msString3; + util::DateTime maDateTime; + + OUString msPresentation; +}; + +static const SfxItemPropertySet* ImplGetFieldItemPropertySet( sal_Int32 mnId ) +{ + static const SfxItemPropertyMapEntry aExDateTimeFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_DATE_TIME, WID_DATE, ::cppu::UnoType<util::DateTime>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_IS_DATE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_NUMFORMAT, WID_INT32, ::cppu::UnoType<sal_Int32>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aExDateTimeFieldPropertySet_Impl(aExDateTimeFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aDateTimeFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_DATE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aDateTimeFieldPropertySet_Impl(aDateTimeFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aUrlFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_URL_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_URL_REPRESENTATION, WID_STRING1, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_URL_TARGET, WID_STRING2, ::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_URL, WID_STRING3, ::cppu::UnoType<OUString>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aUrlFieldPropertySet_Impl(aUrlFieldPropertyMap_Impl); + + static const SfxItemPropertySet aEmptyPropertySet_Impl({}); + + static const SfxItemPropertyMapEntry aExtFileFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_FILE_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING1, ::cppu::UnoType<OUString>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aExtFileFieldPropertySet_Impl(aExtFileFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aAuthorFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING1,::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_CONTENT, WID_STRING2,::cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_FORMAT, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + { UNO_TC_PROP_AUTHOR_FULLNAME, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aAuthorFieldPropertySet_Impl(aAuthorFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aMeasureFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_MEASURE_KIND, WID_INT16, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aMeasureFieldPropertySet_Impl(aMeasureFieldPropertyMap_Impl); + + static const SfxItemPropertyMapEntry aDocInfoCustomFieldPropertyMap_Impl[] = + { + { UNO_TC_PROP_NAME, WID_STRING1, cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_CURRENT_PRESENTATION, WID_STRING2, cppu::UnoType<OUString>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED, WID_BOOL1, cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_TC_PROP_NUMFORMAT, WID_INT32, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + { UNO_TC_PROP_IS_FIXED_LANGUAGE, WID_BOOL2, cppu::UnoType<bool>::get(), 0, 0 }, + }; + static const SfxItemPropertySet aDocInfoCustomFieldPropertySet_Impl(aDocInfoCustomFieldPropertyMap_Impl); + + switch( mnId ) + { + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + return &aExDateTimeFieldPropertySet_Impl; + case text::textfield::Type::URL: + return &aUrlFieldPropertySet_Impl; + case text::textfield::Type::TIME: + return &aDateTimeFieldPropertySet_Impl; + case text::textfield::Type::EXTENDED_FILE: + return &aExtFileFieldPropertySet_Impl; + case text::textfield::Type::AUTHOR: + return &aAuthorFieldPropertySet_Impl; + case text::textfield::Type::MEASURE: + return &aMeasureFieldPropertySet_Impl; + case text::textfield::Type::DOCINFO_CUSTOM: + return &aDocInfoCustomFieldPropertySet_Impl; + default: + return &aEmptyPropertySet_Impl; + } +} + +/* conversion routines */ + +static sal_Int16 getFileNameDisplayFormat( SvxFileFormat nFormat ) +{ + switch( nFormat ) + { + case SvxFileFormat::NameAndExt: return text::FilenameDisplayFormat::NAME_AND_EXT; + case SvxFileFormat::PathFull: return text::FilenameDisplayFormat::FULL; + case SvxFileFormat::PathOnly: return text::FilenameDisplayFormat::PATH; +// case SvxFileFormat::NameOnly: + default: return text::FilenameDisplayFormat::NAME; + } +} + +static SvxFileFormat setFileNameDisplayFormat( sal_Int16 nFormat ) +{ + switch( nFormat ) + { + case text::FilenameDisplayFormat::FULL: return SvxFileFormat::PathFull; + case text::FilenameDisplayFormat::PATH: return SvxFileFormat::PathOnly; + case text::FilenameDisplayFormat::NAME: return SvxFileFormat::NameOnly; +// case text::FilenameDisplayFormat::NAME_AND_EXT: + default: + return SvxFileFormat::NameAndExt; + } +} + +static util::DateTime getDate( sal_Int32 nDate ) +{ + util::DateTime aDate; + + Date aTempDate( nDate ); + + aDate.Day = aTempDate.GetDay(); + aDate.Month = aTempDate.GetMonth(); + aDate.Year = aTempDate.GetYear(); + + return aDate; +} + +static Date setDate( util::DateTime const & rDate ) +{ + return Date( rDate.Day, rDate.Month, rDate.Year ); +} + +static util::DateTime getTime(sal_Int64 const nTime) +{ + util::DateTime aTime; + + tools::Time aTempTime( nTime ); + + aTime.NanoSeconds = aTempTime.GetNanoSec(); + aTime.Seconds = aTempTime.GetSec(); + aTime.Minutes = aTempTime.GetMin(); + aTime.Hours = aTempTime.GetHour(); + + return aTime; +} + +static tools::Time setTime( util::DateTime const & rDate ) +{ + return tools::Time( rDate ); +} + + + +SvxUnoTextField::SvxUnoTextField( sal_Int32 nServiceId ) noexcept +: OComponentHelper( m_aMutex ) +, mpPropSet(nullptr) +, mnServiceId(nServiceId) +, mpImpl( new SvxUnoFieldData_Impl ) +{ + mpPropSet = ImplGetFieldItemPropertySet(mnServiceId); + + mpImpl->maDateTime.NanoSeconds = 0; + mpImpl->maDateTime.Seconds = 0; + mpImpl->maDateTime.Minutes = 0; + mpImpl->maDateTime.Hours = 0; + mpImpl->maDateTime.Day = 0; + mpImpl->maDateTime.Month = 0; + mpImpl->maDateTime.Year = 0; + mpImpl->maDateTime.IsUTC = false; + + switch( nServiceId ) + { + case text::textfield::Type::DATE: + mpImpl->mbBoolean2 = true; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxDateFormat::StdSmall); + mpImpl->mbBoolean1 = false; + break; + + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::TIME: + mpImpl->mbBoolean2 = false; + mpImpl->mbBoolean1 = false; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxTimeFormat::Standard); + break; + + case text::textfield::Type::URL: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SvxURLFormat::Repr); + break; + + case text::textfield::Type::EXTENDED_FILE: + mpImpl->mbBoolean1 = false; + mpImpl->mnInt16 = text::FilenameDisplayFormat::FULL; + break; + + case text::textfield::Type::AUTHOR: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SvxAuthorFormat::FullName); + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = true; + break; + + case text::textfield::Type::MEASURE: + mpImpl->mnInt16 = static_cast<sal_uInt16>(SdrMeasureFieldKind::Value); + break; + + case text::textfield::Type::DOCINFO_CUSTOM: + mpImpl->mbBoolean1 = true; + mpImpl->mbBoolean2 = true; + mpImpl->mnInt32 = 0; + break; + + default: + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = false; + mpImpl->mnInt32 = 0; + mpImpl->mnInt16 = 0; + + } +} + +SvxUnoTextField::SvxUnoTextField( uno::Reference< text::XTextRange > xAnchor, const OUString& rPresentation, const SvxFieldData* pData ) noexcept +: OComponentHelper( m_aMutex ) +, mxAnchor(std::move( xAnchor )) +, mpPropSet(nullptr) +, mnServiceId(text::textfield::Type::UNSPECIFIED) +, mpImpl( new SvxUnoFieldData_Impl ) +{ + DBG_ASSERT(pData, "pFieldData == NULL! [CL]" ); + + mpImpl->msPresentation = rPresentation; + + if(pData) + { + mnServiceId = pData->GetClassId(); + DBG_ASSERT(mnServiceId != text::textfield::Type::UNSPECIFIED, "unknown SvxFieldData! [CL]"); + if (mnServiceId != text::textfield::Type::UNSPECIFIED) + { + // extract field properties from data class + switch( mnServiceId ) + { + case text::textfield::Type::DATE: + { + mpImpl->mbBoolean2 = true; + // #i35416# for variable date field, don't use invalid "0000-00-00" date, + // use current date instead + bool bFixed = static_cast<const SvxDateField*>(pData)->GetType() == SvxDateType::Fix; + mpImpl->maDateTime = getDate( bFixed ? + static_cast<const SvxDateField*>(pData)->GetFixDate() : + Date( Date::SYSTEM ).GetDate() ); + mpImpl->mnInt32 = static_cast<sal_Int32>(static_cast<const SvxDateField*>(pData)->GetFormat()); + mpImpl->mbBoolean1 = bFixed; + } + break; + + case text::textfield::Type::TIME: + mpImpl->mbBoolean2 = false; + mpImpl->mbBoolean1 = false; + mpImpl->mnInt32 = static_cast<sal_Int32>(SvxTimeFormat::Standard); + break; + + case text::textfield::Type::EXTENDED_TIME: + mpImpl->mbBoolean2 = false; + mpImpl->maDateTime = getTime( static_cast<const SvxExtTimeField*>(pData)->GetFixTime() ); + mpImpl->mbBoolean1 = static_cast<const SvxExtTimeField*>(pData)->GetType() == SvxTimeType::Fix; + mpImpl->mnInt32 = static_cast<sal_Int32>(static_cast<const SvxExtTimeField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::URL: + mpImpl->msString1 = static_cast<const SvxURLField*>(pData)->GetRepresentation(); + mpImpl->msString2 = static_cast<const SvxURLField*>(pData)->GetTargetFrame(); + mpImpl->msString3 = static_cast<const SvxURLField*>(pData)->GetURL(); + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >( + static_cast<const SvxURLField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::EXTENDED_FILE: + mpImpl->msString1 = static_cast<const SvxExtFileField*>(pData)->GetFile(); + mpImpl->mbBoolean1 = static_cast<const SvxExtFileField*>(pData)->GetType() == SvxFileType::Fix; + mpImpl->mnInt16 = getFileNameDisplayFormat(static_cast<const SvxExtFileField*>(pData)->GetFormat()); + break; + + case text::textfield::Type::AUTHOR: + mpImpl->msString1 = static_cast<const SvxAuthorField*>(pData)->GetFormatted(); + mpImpl->msString2 = static_cast<const SvxAuthorField*>(pData)->GetFormatted(); + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >( + static_cast<const SvxAuthorField*>(pData)->GetFormat()); + mpImpl->mbBoolean1 = static_cast<const SvxAuthorField*>(pData)->GetType() == SvxAuthorType::Fix; + mpImpl->mbBoolean2 = static_cast<const SvxAuthorField*>(pData)->GetFormat() != SvxAuthorFormat::ShortName; + break; + + case text::textfield::Type::MEASURE: + mpImpl->mnInt16 = sal::static_int_cast< sal_Int16 >(static_cast<const SdrMeasureField*>(pData)->GetMeasureFieldKind()); + break; + + case text::textfield::Type::DOCINFO_CUSTOM: + mpImpl->msString1 = static_cast<const editeng::CustomPropertyField*>(pData)->GetName(); + mpImpl->msString2 = static_cast<const editeng::CustomPropertyField*>(pData)->GetCurrentPresentation(); + mpImpl->mbBoolean1 = false; + mpImpl->mbBoolean2 = false; + mpImpl->mnInt32 = 0; + break; + + default: + SAL_INFO("editeng", "Id service unknown: " << mnServiceId); + break; + } + } + } + + mpPropSet = ImplGetFieldItemPropertySet(mnServiceId); +} + +SvxUnoTextField::~SvxUnoTextField() noexcept +{ +} + +std::unique_ptr<SvxFieldData> SvxUnoTextField::CreateFieldData() const noexcept +{ + std::unique_ptr<SvxFieldData> pData; + + switch( mnServiceId ) + { + case text::textfield::Type::TIME: + case text::textfield::Type::EXTENDED_TIME: + case text::textfield::Type::DATE: + { + if( mpImpl->mbBoolean2 ) // IsDate? + { + Date aDate( setDate( mpImpl->maDateTime ) ); + pData.reset( new SvxDateField( aDate, mpImpl->mbBoolean1?SvxDateType::Fix:SvxDateType::Var ) ); + if( mpImpl->mnInt32 >= static_cast<sal_Int32>(SvxDateFormat::AppDefault) && + mpImpl->mnInt32 <= static_cast<sal_Int32>(SvxDateFormat::F) ) + static_cast<SvxDateField*>(pData.get())->SetFormat( static_cast<SvxDateFormat>(mpImpl->mnInt32) ); + } + else + { + if( mnServiceId != text::textfield::Type::TIME && mnServiceId != text::textfield::Type::DATE ) + { + tools::Time aTime( setTime( mpImpl->maDateTime ) ); + pData.reset( new SvxExtTimeField( aTime, mpImpl->mbBoolean1?SvxTimeType::Fix:SvxTimeType::Var ) ); + + if( static_cast<SvxTimeFormat>(mpImpl->mnInt32) >= SvxTimeFormat::AppDefault && + static_cast<SvxTimeFormat>(mpImpl->mnInt32) <= SvxTimeFormat::HH12_MM_SS_00_AMPM ) + static_cast<SvxExtTimeField*>(pData.get())->SetFormat( static_cast<SvxTimeFormat>(mpImpl->mnInt32) ); + } + else + { + pData.reset( new SvxTimeField() ); + } + } + + } + break; + + case text::textfield::Type::URL: + pData.reset( new SvxURLField( mpImpl->msString3, mpImpl->msString1, !mpImpl->msString1.isEmpty() ? SvxURLFormat::Repr : SvxURLFormat::Url ) ); + static_cast<SvxURLField*>(pData.get())->SetTargetFrame( mpImpl->msString2 ); + if( static_cast<SvxURLFormat>(mpImpl->mnInt16) >= SvxURLFormat::AppDefault && + static_cast<SvxURLFormat>(mpImpl->mnInt16) <= SvxURLFormat::Repr ) + static_cast<SvxURLField*>(pData.get())->SetFormat( static_cast<SvxURLFormat>(mpImpl->mnInt16) ); + break; + + case text::textfield::Type::PAGE: + pData.reset( new SvxPageField() ); + break; + + case text::textfield::Type::PAGES: + pData.reset( new SvxPagesField() ); + break; + + case text::textfield::Type::DOCINFO_TITLE: + pData.reset( new SvxFileField() ); + break; + + case text::textfield::Type::TABLE: + pData.reset( new SvxTableField() ); + break; + + case text::textfield::Type::EXTENDED_FILE: + { + // #92009# pass fixed attribute to constructor + pData.reset( new SvxExtFileField( mpImpl->msString1, + mpImpl->mbBoolean1 ? SvxFileType::Fix : SvxFileType::Var, + setFileNameDisplayFormat(mpImpl->mnInt16 ) ) ); + break; + } + + case text::textfield::Type::AUTHOR: + { + OUString aContent; + OUString aFirstName; + OUString aLastName; + + // do we have CurrentPresentation given? + // mimic behaviour of writer, which means: + // prefer CurrentPresentation over Content + // if both are given. + if( !mpImpl->msString1.isEmpty() ) + aContent = mpImpl->msString1; + else + aContent = mpImpl->msString2; + + sal_Int32 nPos = aContent.lastIndexOf( ' ', 0 ); + if( nPos > 0 ) + { + aFirstName = aContent.copy( 0, nPos ); + aLastName = aContent.copy( nPos + 1 ); + } + else + { + aLastName = aContent; + } + + // #92009# pass fixed attribute to constructor + pData.reset( new SvxAuthorField( aFirstName, aLastName, "", + mpImpl->mbBoolean1 ? SvxAuthorType::Fix : SvxAuthorType::Var ) ); + + if( !mpImpl->mbBoolean2 ) + { + static_cast<SvxAuthorField*>(pData.get())->SetFormat( SvxAuthorFormat::ShortName ); + } + else if( static_cast<SvxAuthorFormat>(mpImpl->mnInt16) >= SvxAuthorFormat::FullName && + static_cast<SvxAuthorFormat>(mpImpl->mnInt16) <= SvxAuthorFormat::ShortName ) + { + static_cast<SvxAuthorField*>(pData.get())->SetFormat( static_cast<SvxAuthorFormat>(mpImpl->mnInt16) ); + } + + break; + } + + case text::textfield::Type::MEASURE: + { + SdrMeasureFieldKind eKind = SdrMeasureFieldKind::Value; + if( mpImpl->mnInt16 == sal_Int16(SdrMeasureFieldKind::Unit) || mpImpl->mnInt16 == sal_Int16(SdrMeasureFieldKind::Rotate90Blanks) ) + eKind = static_cast<SdrMeasureFieldKind>(mpImpl->mnInt16); + pData.reset( new SdrMeasureField( eKind) ); + break; + } + case text::textfield::Type::PRESENTATION_HEADER: + pData.reset( new SvxHeaderField() ); + break; + case text::textfield::Type::PRESENTATION_FOOTER: + pData.reset( new SvxFooterField() ); + break; + case text::textfield::Type::PRESENTATION_DATE_TIME: + pData.reset( new SvxDateTimeField() ); + break; + case text::textfield::Type::PAGE_NAME: + pData.reset( new SvxPageTitleField() ); + break; + case text::textfield::Type::DOCINFO_CUSTOM: + pData.reset( new editeng::CustomPropertyField(mpImpl->msString1, mpImpl->msString2) ); + break; + } + + return pData; +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoTextField::queryAggregation( const uno::Type & rType ) +{ + uno::Any aAny; + + QUERYINT( beans::XPropertySet ); + else QUERYINT( text::XTextContent ); + else QUERYINT( text::XTextField ); + else QUERYINT( lang::XServiceInfo ); + else + return OComponentHelper::queryAggregation( rType ); + + return aAny; +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextField::getTypes() +{ + if( !maTypeSequence.hasElements() ) + { + maTypeSequence = comphelper::concatSequences( + OComponentHelper::getTypes(), + uno::Sequence { + cppu::UnoType<text::XTextField>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }); + } + return maTypeSequence; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextField::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Any SAL_CALL SvxUnoTextField::queryInterface( const uno::Type & rType ) +{ + return OComponentHelper::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextField::acquire() noexcept +{ + OComponentHelper::acquire(); +} + +void SAL_CALL SvxUnoTextField::release() noexcept +{ + OComponentHelper::release(); +} + +// Interface text::XTextField +OUString SAL_CALL SvxUnoTextField::getPresentation( sal_Bool bShowCommand ) +{ + SolarMutexGuard aGuard; + if (bShowCommand) + { + switch (mnServiceId) + { + case text::textfield::Type::DATE: + return "Date"; + case text::textfield::Type::URL: + return "URL"; + case text::textfield::Type::PAGE: + return "Page"; + case text::textfield::Type::PAGES: + return "Pages"; + case text::textfield::Type::TIME: + return "Time"; + case text::textfield::Type::DOCINFO_TITLE: + return "File"; + case text::textfield::Type::TABLE: + return "Table"; + case text::textfield::Type::EXTENDED_TIME: + return "ExtTime"; + case text::textfield::Type::EXTENDED_FILE: + return "ExtFile"; + case text::textfield::Type::AUTHOR: + return "Author"; + case text::textfield::Type::MEASURE: + return "Measure"; + case text::textfield::Type::PRESENTATION_HEADER: + return "Header"; + case text::textfield::Type::PRESENTATION_FOOTER: + return "Footer"; + case text::textfield::Type::PRESENTATION_DATE_TIME: + return "DateTime"; + case text::textfield::Type::PAGE_NAME: + return "PageName"; + case text::textfield::Type::DOCINFO_CUSTOM: + return "Custom"; + default: + return "Unknown"; + } + } + else + { + return mpImpl->msPresentation; + } +} + +// Interface text::XTextContent +void SAL_CALL SvxUnoTextField::attach( const uno::Reference< text::XTextRange >& xTextRange ) +{ + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xTextRange ); + if(pRange == nullptr) + throw lang::IllegalArgumentException(); + + std::unique_ptr<SvxFieldData> pData = CreateFieldData(); + if( pData ) + pRange->attachField( std::move(pData) ); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextField::getAnchor() +{ + return mxAnchor; +} + +// lang::XComponent +void SAL_CALL SvxUnoTextField::dispose() +{ + OComponentHelper::dispose(); + mxAnchor.clear(); +} + +void SAL_CALL SvxUnoTextField::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + OComponentHelper::addEventListener(xListener); +} + +void SAL_CALL SvxUnoTextField::removeEventListener( const uno::Reference< lang::XEventListener >& aListener ) +{ + OComponentHelper::removeEventListener(aListener); +} + + +// Interface beans::XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL SvxUnoTextField::getPropertySetInfo( ) +{ + SolarMutexGuard aGuard; + return mpPropSet->getPropertySetInfo(); +} + +void SAL_CALL SvxUnoTextField::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + SolarMutexGuard aGuard; + + if( mpImpl == nullptr ) + throw uno::RuntimeException(); + + if (aPropertyName == UNO_TC_PROP_ANCHOR) + { + aValue >>= mxAnchor; + return; + } + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMap().getByName( aPropertyName ); + if ( !pMap ) + throw beans::UnknownPropertyException(aPropertyName); + + switch( pMap->nWID ) + { + case WID_DATE: + if(aValue >>= mpImpl->maDateTime) + return; + break; + case WID_BOOL1: + if(aValue >>= mpImpl->mbBoolean1) + return; + break; + case WID_BOOL2: + if(aValue >>= mpImpl->mbBoolean2) + return; + break; + case WID_INT16: + if(aValue >>= mpImpl->mnInt16) + return; + break; + case WID_INT32: + if(aValue >>= mpImpl->mnInt32) + return; + break; + case WID_STRING1: + if(aValue >>= mpImpl->msString1) + return; + break; + case WID_STRING2: + if(aValue >>= mpImpl->msString2) + return; + break; + case WID_STRING3: + if(aValue >>= mpImpl->msString3) + return; + break; + } + + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SvxUnoTextField::getPropertyValue( const OUString& PropertyName ) +{ + SolarMutexGuard aGuard; + + if (PropertyName == UNO_TC_PROP_ANCHOR) + return uno::Any(mxAnchor); + + if (PropertyName == UNO_TC_PROP_TEXTFIELD_TYPE) + return uno::Any(mnServiceId); + + uno::Any aValue; + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMap().getByName( PropertyName ); + if ( !pMap ) + throw beans::UnknownPropertyException(PropertyName); + + switch( pMap->nWID ) + { + case WID_DATE: + aValue <<= mpImpl->maDateTime; + break; + case WID_BOOL1: + aValue <<= mpImpl->mbBoolean1; + break; + case WID_BOOL2: + aValue <<= mpImpl->mbBoolean2; + break; + case WID_INT16: + aValue <<= mpImpl->mnInt16; + break; + case WID_INT32: + aValue <<= mpImpl->mnInt32; + break; + case WID_STRING1: + aValue <<= mpImpl->msString1; + break; + case WID_STRING2: + aValue <<= mpImpl->msString2; + break; + case WID_STRING3: + aValue <<= mpImpl->msString3; + break; + } + + return aValue; +} + +void SAL_CALL SvxUnoTextField::addPropertyChangeListener( const OUString&, const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::removePropertyChangeListener( const OUString&, const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::addVetoableChangeListener( const OUString&, const uno::Reference< beans::XVetoableChangeListener >& ) {} +void SAL_CALL SvxUnoTextField::removeVetoableChangeListener( const OUString&, const uno::Reference< beans::XVetoableChangeListener >& ) {} + +// OComponentHelper +void SvxUnoTextField::disposing() +{ + // nothing to do +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextField::getImplementationName() +{ + return "SvxUnoTextField"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextField::getSupportedServiceNames() +{ + uno::Sequence<OUString> aSeq(4); + OUString* pServices = aSeq.getArray(); + pServices[0] = "com.sun.star.text.TextContent"; + pServices[1] = "com.sun.star.text.TextField"; + + switch (mnServiceId) + { + case text::textfield::Type::DATE: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::URL: + pServices[2] = "com.sun.star.text.TextField.URL"; + pServices[3] = "com.sun.star.text.textfield.URL"; + break; + case text::textfield::Type::PAGE: + pServices[2] = "com.sun.star.text.TextField.PageNumber"; + pServices[3] = "com.sun.star.text.textfield.PageNumber"; + break; + case text::textfield::Type::PAGES: + pServices[2] = "com.sun.star.text.TextField.PageCount"; + pServices[3] = "com.sun.star.text.textfield.PageCount"; + break; + case text::textfield::Type::TIME: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::DOCINFO_TITLE: + pServices[2] = "com.sun.star.text.TextField.docinfo.Title"; + pServices[3] = "com.sun.star.text.textfield.docinfo.Title"; + break; + case text::textfield::Type::TABLE: + pServices[2] = "com.sun.star.text.TextField.SheetName"; + pServices[3] = "com.sun.star.text.textfield.SheetName"; + break; + case text::textfield::Type::EXTENDED_TIME: + pServices[2] = "com.sun.star.text.TextField.DateTime"; + pServices[3] = "com.sun.star.text.textfield.DateTime"; + break; + case text::textfield::Type::EXTENDED_FILE: + pServices[2] = "com.sun.star.text.TextField.FileName"; + pServices[3] = "com.sun.star.text.textfield.FileName"; + break; + case text::textfield::Type::AUTHOR: + pServices[2] = "com.sun.star.text.TextField.Author"; + pServices[3] = "com.sun.star.text.textfield.Author"; + break; + case text::textfield::Type::MEASURE: + pServices[2] = "com.sun.star.text.TextField.Measure"; + pServices[3] = "com.sun.star.text.textfield.Measure"; + break; + case text::textfield::Type::PRESENTATION_HEADER: + pServices[2] = "com.sun.star.presentation.TextField.Header"; + pServices[3] = "com.sun.star.presentation.textfield.Header"; + break; + case text::textfield::Type::PRESENTATION_FOOTER: + pServices[2] = "com.sun.star.presentation.TextField.Footer"; + pServices[3] = "com.sun.star.presentation.textfield.Footer"; + break; + case text::textfield::Type::PRESENTATION_DATE_TIME: + pServices[2] = "com.sun.star.presentation.TextField.DateTime"; + pServices[3] = "com.sun.star.presentation.textfield.DateTime"; + break; + case text::textfield::Type::PAGE_NAME: + pServices[2] = "com.sun.star.text.TextField.PageName"; + pServices[3] = "com.sun.star.text.textfield.PageName"; + break; + case text::textfield::Type::DOCINFO_CUSTOM: + pServices[2] = "com.sun.star.text.TextField.DocInfo.Custom"; + pServices[3] = "com.sun.star.text.textfield.DocInfo.Custom"; + break; + default: + aSeq.realloc(0); + } + + return aSeq; +} + +sal_Bool SAL_CALL SvxUnoTextField::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Reference< uno::XInterface > SvxUnoTextCreateTextField( std::u16string_view ServiceSpecifier ) +{ + uno::Reference< uno::XInterface > xRet; + + // #i93308# up to OOo 3.2 we used this wrong namespace name with the capital T & F. This is + // fixed since OOo 3.2 but for compatibility we will still provide support for the wrong notation. + + std::u16string_view aFieldType; + if( (o3tl::starts_with( ServiceSpecifier, u"com.sun.star.text.textfield.", &aFieldType )) || + (o3tl::starts_with( ServiceSpecifier, u"com.sun.star.text.TextField.", &aFieldType )) ) + { + sal_Int32 nId = text::textfield::Type::UNSPECIFIED; + + if ( aFieldType == u"DateTime" ) + { + nId = text::textfield::Type::DATE; + } + else if ( aFieldType == u"URL" ) + { + nId = text::textfield::Type::URL; + } + else if ( aFieldType == u"PageNumber" ) + { + nId = text::textfield::Type::PAGE; + } + else if ( aFieldType == u"PageCount" ) + { + nId = text::textfield::Type::PAGES; + } + else if ( aFieldType == u"SheetName" ) + { + nId = text::textfield::Type::TABLE; + } + else if ( aFieldType == u"FileName" ) + { + nId = text::textfield::Type::EXTENDED_FILE; + } + else if (aFieldType == u"docinfo.Title" || + aFieldType == u"DocInfo.Title" ) + { + nId = text::textfield::Type::DOCINFO_TITLE; + } + else if ( aFieldType == u"Author" ) + { + nId = text::textfield::Type::AUTHOR; + } + else if ( aFieldType == u"Measure" ) + { + nId = text::textfield::Type::MEASURE; + } + else if (aFieldType == u"DocInfo.Custom") + { + nId = text::textfield::Type::DOCINFO_CUSTOM; + } + + if (nId != text::textfield::Type::UNSPECIFIED) + xRet = getXWeak(new SvxUnoTextField( nId )); + } + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofored.cxx b/editeng/source/uno/unofored.cxx new file mode 100644 index 0000000000..66f4fde2bf --- /dev/null +++ b/editeng/source/uno/unofored.cxx @@ -0,0 +1,520 @@ +/* -*- 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 <osl/diagnose.h> +#include <tools/debug.hxx> +#include <editeng/eeitem.hxx> +#include <com/sun/star/i18n/WordType.hpp> + +#include <svl/itemset.hxx> +#include <editeng/editeng.hxx> +#include <editeng/unoedhlp.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editobj.hxx> + +#include <editeng/unofored.hxx> +#include "unofored_internal.hxx" + +using namespace ::com::sun::star; + + +SvxEditEngineForwarder::SvxEditEngineForwarder( EditEngine& rEngine ) : + rEditEngine( rEngine ) +{ +} + +SvxEditEngineForwarder::~SvxEditEngineForwarder() +{ + // the EditEngine may need to be deleted from the outside +} + +sal_Int32 SvxEditEngineForwarder::GetParagraphCount() const +{ + return rEditEngine.GetParagraphCount(); +} + +sal_Int32 SvxEditEngineForwarder::GetTextLen( sal_Int32 nParagraph ) const +{ + return rEditEngine.GetTextLen( nParagraph ); +} + +OUString SvxEditEngineForwarder::GetText( const ESelection& rSel ) const +{ + return convertLineEnd(rEditEngine.GetText(rSel), GetSystemLineEnd()); +} + +SfxItemSet SvxEditEngineForwarder::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + if( rSel.nStartPara == rSel.nEndPara ) + { + GetAttribsFlags nFlags = GetAttribsFlags::NONE; + switch( nOnlyHardAttrib ) + { + case EditEngineAttribs::All: + nFlags = GetAttribsFlags::ALL; + break; + case EditEngineAttribs::OnlyHard: + nFlags = GetAttribsFlags::CHARATTRIBS; + break; + default: + OSL_FAIL("unknown flags for SvxOutlinerForwarder::GetAttribs"); + } + + return rEditEngine.GetAttribs( rSel.nStartPara, rSel.nStartPos, rSel.nEndPos, nFlags ); + } + else + { + return rEditEngine.GetAttribs( rSel, nOnlyHardAttrib ); + } +} + +SfxItemSet SvxEditEngineForwarder::GetParaAttribs( sal_Int32 nPara ) const +{ + SfxItemSet aSet( rEditEngine.GetParaAttribs( nPara ) ); + + sal_uInt16 nWhich = EE_PARA_START; + while( nWhich <= EE_PARA_END ) + { + if( aSet.GetItemState( nWhich ) != SfxItemState::SET ) + { + if( rEditEngine.HasParaAttrib( nPara, nWhich ) ) + aSet.Put( rEditEngine.GetParaAttrib( nPara, nWhich ) ); + } + nWhich++; + } + + return aSet; +} + +void SvxEditEngineForwarder::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + rEditEngine.SetParaAttribs( nPara, rSet ); +} + +void SvxEditEngineForwarder::RemoveAttribs( const ESelection& rSelection ) +{ + rEditEngine.RemoveAttribs( rSelection, false/*bRemoveParaAttribs*/, 0 ); +} + +SfxItemPool* SvxEditEngineForwarder::GetPool() const +{ + return rEditEngine.GetEmptyItemSet().GetPool(); +} + +void SvxEditEngineForwarder::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + rEditEngine.GetPortions( nPara, rList ); +} + +OUString SvxEditEngineForwarder::GetStyleSheet(sal_Int32 nPara) const +{ + if (auto pStyle = rEditEngine.GetStyleSheet(nPara)) + return pStyle->GetName(); + return OUString(); +} + +void SvxEditEngineForwarder::SetStyleSheet(sal_Int32 nPara, const OUString& rStyleName) +{ + auto pStyleSheetPool = rEditEngine.GetStyleSheetPool(); + if (auto pStyle = pStyleSheetPool ? pStyleSheetPool->Find(rStyleName, SfxStyleFamily::Para) : nullptr) + rEditEngine.SetStyleSheet(nPara, static_cast<SfxStyleSheet*>(pStyle)); +} + +void SvxEditEngineForwarder::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + rEditEngine.QuickInsertText( rText, rSel ); +} + +void SvxEditEngineForwarder::QuickInsertLineBreak( const ESelection& rSel ) +{ + rEditEngine.QuickInsertLineBreak( rSel ); +} + +void SvxEditEngineForwarder::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + rEditEngine.QuickInsertField( rFld, rSel ); +} + +void SvxEditEngineForwarder::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + rEditEngine.QuickSetAttribs( rSet, rSel ); +} + +bool SvxEditEngineForwarder::IsValid() const +{ + // cannot reliably query EditEngine state + // while in the middle of an update + return rEditEngine.IsUpdateLayout(); +} + +OUString SvxEditEngineForwarder::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle ) +{ + return rEditEngine.CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor, rpFldLineStyle ); +} + +void SvxEditEngineForwarder::FieldClicked( const SvxFieldItem& rField ) +{ + rEditEngine.FieldClicked( rField ); +} + +SfxItemState GetSvxEditEngineItemState( EditEngine const & rEditEngine, const ESelection& rSel, sal_uInt16 nWhich ) +{ + std::vector<EECharAttrib> aAttribs; + + const SfxPoolItem* pLastItem = nullptr; + + SfxItemState eState = SfxItemState::DEFAULT; + + // check all paragraphs inside the selection + for( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ ) + { + SfxItemState eParaState = SfxItemState::DEFAULT; + + // calculate start and endpos for this paragraph + sal_Int32 nPos = 0; + if( rSel.nStartPara == nPara ) + nPos = rSel.nStartPos; + + sal_Int32 nEndPos = rSel.nEndPos; + if( rSel.nEndPara != nPara ) + nEndPos = rEditEngine.GetTextLen( nPara ); + + + // get list of char attribs + rEditEngine.GetCharAttribs( nPara, aAttribs ); + + bool bEmpty = true; // we found no item inside the selection of this paragraph + bool bGaps = false; // we found items but there are gaps between them + sal_Int32 nLastEnd = nPos; + + const SfxPoolItem* pParaItem = nullptr; + + for (auto const& attrib : aAttribs) + { + DBG_ASSERT(attrib.pAttr, "GetCharAttribs gives corrupt data"); + + const bool bEmptyPortion = attrib.nStart == attrib.nEnd; + if((!bEmptyPortion && attrib.nStart >= nEndPos) || + (bEmptyPortion && attrib.nStart > nEndPos)) + break; // break if we are already behind our selection + + if((!bEmptyPortion && attrib.nEnd <= nPos) || + (bEmptyPortion && attrib.nEnd < nPos)) + continue; // or if the attribute ends before our selection + + if(attrib.pAttr->Which() != nWhich) + continue; // skip if is not the searched item + + // if we already found an item + if( pParaItem ) + { + // ... and its different to this one than the state is don't care + if(*pParaItem != *(attrib.pAttr)) + return SfxItemState::DONTCARE; + } + else + pParaItem = attrib.pAttr; + + if( bEmpty ) + bEmpty = false; + + if(!bGaps && attrib.nStart > nLastEnd) + bGaps = true; + + nLastEnd = attrib.nEnd; + } + + if( !bEmpty && !bGaps && nLastEnd < ( nEndPos - 1 ) ) + bGaps = true; + + if( bEmpty ) + eParaState = SfxItemState::DEFAULT; + else if( bGaps ) + eParaState = SfxItemState::DONTCARE; + else + eParaState = SfxItemState::SET; + + // if we already found an item check if we found the same + if( pLastItem ) + { + if( (pParaItem == nullptr) || (*pLastItem != *pParaItem) ) + return SfxItemState::DONTCARE; + } + else + { + pLastItem = pParaItem; + eState = eParaState; + } + } + + return eState; +} + +SfxItemState SvxEditEngineForwarder::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + return GetSvxEditEngineItemState( rEditEngine, rSel, nWhich ); +} + +SfxItemState SvxEditEngineForwarder::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const SfxItemSet& rSet = rEditEngine.GetParaAttribs( nPara ); + return rSet.GetItemState( nWhich ); +} + +LanguageType SvxEditEngineForwarder::GetLanguage( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rEditEngine.GetLanguage(nPara, nIndex).nLang; +} + +sal_Int32 SvxEditEngineForwarder::GetFieldCount( sal_Int32 nPara ) const +{ + return rEditEngine.GetFieldCount(nPara); +} + +EFieldInfo SvxEditEngineForwarder::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + return rEditEngine.GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxEditEngineForwarder::GetBulletInfo( sal_Int32 ) const +{ + return EBulletInfo(); +} + +tools::Rectangle SvxEditEngineForwarder::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + // EditEngine's 'internal' methods like GetCharacterBounds() + // don't rotate for vertical text. + Size aSize( rEditEngine.CalcTextWidth(), rEditEngine.GetTextHeight() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + bool bIsVertical( rEditEngine.IsEffectivelyVertical() ); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= rEditEngine.GetTextLen(nPara) ) + { + tools::Rectangle aLast; + + if( nIndex ) + { + // use last character, if possible + aLast = rEditEngine.GetCharacterBounds( EPosition(nPara, nIndex-1) ); + + // move at end of this last character, make one pixel wide + aLast.Move( aLast.Right() - aLast.Left(), 0 ); + aLast.SetSize( Size(1, aLast.GetHeight()) ); + + // take care for CTL + aLast = SvxEditSourceHelper::EEToUserSpace( aLast, aSize, bIsVertical ); + } + else + { + // #109864# Bounds must lie within the paragraph + aLast = GetParaBounds( nPara ); + + // #109151# Don't use paragraph height, but line height + // instead. aLast is already CTL-correct + if( bIsVertical) + aLast.SetSize( Size( rEditEngine.GetLineHeight(nPara), 1 ) ); + else + aLast.SetSize( Size( 1, rEditEngine.GetLineHeight(nPara) ) ); + } + + return aLast; + } + else + { + return SvxEditSourceHelper::EEToUserSpace( rEditEngine.GetCharacterBounds( EPosition(nPara, nIndex) ), + aSize, bIsVertical ); + } +} + +tools::Rectangle SvxEditEngineForwarder::GetParaBounds( sal_Int32 nPara ) const +{ + const Point aPnt = rEditEngine.GetDocPosTopLeft( nPara ); + sal_uInt32 nWidth; + sal_uInt32 nHeight; + + if( rEditEngine.IsEffectivelyVertical() ) + { + // Hargl. EditEngine's 'external' methods return the rotated + // dimensions, 'internal' methods like GetTextHeight( n ) + // don't rotate. + nWidth = rEditEngine.GetTextHeight( nPara ); + nHeight = rEditEngine.GetTextHeight(); + sal_uInt32 nTextWidth = rEditEngine.GetTextHeight(); + + return tools::Rectangle( nTextWidth - aPnt.Y() - nWidth, 0, nTextWidth - aPnt.Y(), nHeight ); + } + else + { + nWidth = rEditEngine.CalcTextWidth(); + nHeight = rEditEngine.GetTextHeight( nPara ); + + return tools::Rectangle( 0, aPnt.Y(), nWidth, aPnt.Y() + nHeight ); + } +} + +MapMode SvxEditEngineForwarder::GetMapMode() const +{ + return rEditEngine.GetRefMapMode(); +} + +OutputDevice* SvxEditEngineForwarder::GetRefDevice() const +{ + return rEditEngine.GetRefDevice(); +} + +bool SvxEditEngineForwarder::GetIndexAtPoint( const Point& rPos, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + Size aSize( rEditEngine.CalcTextWidth(), rEditEngine.GetTextHeight() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + Point aEEPos( SvxEditSourceHelper::UserSpaceToEE( rPos, + aSize, + rEditEngine.IsEffectivelyVertical() )); + + EPosition aDocPos = rEditEngine.FindDocPosition( aEEPos ); + + nPara = aDocPos.nPara; + nIndex = aDocPos.nIndex; + + return true; +} + +bool SvxEditEngineForwarder::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + ESelection aRes = rEditEngine.GetWord( ESelection(nPara, nIndex, nPara, nIndex), css::i18n::WordType::DICTIONARY_WORD ); + + if( aRes.nStartPara == nPara && + aRes.nStartPara == aRes.nEndPara ) + { + nStart = aRes.nStartPos; + nEnd = aRes.nEndPos; + + return true; + } + + return false; +} + +bool SvxEditEngineForwarder::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) const +{ + SvxEditSourceHelper::GetAttributeRun( nStartIndex, nEndIndex, rEditEngine, nPara, nIndex, bInCell ); + return true; +} + +sal_Int32 SvxEditEngineForwarder::GetLineCount( sal_Int32 nPara ) const +{ + return rEditEngine.GetLineCount(nPara); +} + +sal_Int32 SvxEditEngineForwarder::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rEditEngine.GetLineLen(nPara, nLine); +} + +void SvxEditEngineForwarder::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nPara, sal_Int32 nLine ) const +{ + rEditEngine.GetLineBoundaries(rStart, rEnd, nPara, nLine); +} + +sal_Int32 SvxEditEngineForwarder::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rEditEngine.GetLineNumberAtIndex(nPara, nIndex); +} + + +bool SvxEditEngineForwarder::QuickFormatDoc( bool ) +{ + rEditEngine.QuickFormatDoc(); + + return true; +} + +bool SvxEditEngineForwarder::Delete( const ESelection& rSelection ) +{ + rEditEngine.QuickDelete( rSelection ); + rEditEngine.QuickFormatDoc(); + + return true; +} + +bool SvxEditEngineForwarder::InsertText( const OUString& rStr, const ESelection& rSelection ) +{ + rEditEngine.QuickInsertText( rStr, rSelection ); + rEditEngine.QuickFormatDoc(); + + return true; +} + +sal_Int16 SvxEditEngineForwarder::GetDepth( sal_Int32 ) const +{ + // EditEngine does not support outline depth + return -1; +} + +bool SvxEditEngineForwarder::SetDepth( sal_Int32, sal_Int16 nNewDepth ) +{ + // EditEngine does not support outline depth + return nNewDepth == -1; +} + +const SfxItemSet * SvxEditEngineForwarder::GetEmptyItemSetPtr() +{ + return &rEditEngine.GetEmptyItemSet(); +} + +void SvxEditEngineForwarder::AppendParagraph() +{ + rEditEngine.InsertParagraph( rEditEngine.GetParagraphCount(), OUString() ); +} + +sal_Int32 SvxEditEngineForwarder::AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet & /*rSet*/ ) +{ + sal_Int32 nLen = 0; + + sal_Int32 nParaCount = rEditEngine.GetParagraphCount(); + DBG_ASSERT( nPara < nParaCount, "paragraph index out of bounds" ); + if (0 <= nPara && nPara < nParaCount) + { + nLen = rEditEngine.GetTextLen( nPara ); + rEditEngine.QuickInsertText( rText, ESelection( nPara, nLen, nPara, nLen ) ); + } + + return nLen; +} + +void SvxEditEngineForwarder::CopyText(const SvxTextForwarder& rSource) +{ + const SvxEditEngineForwarder* pSourceForwarder = dynamic_cast< const SvxEditEngineForwarder* >( &rSource ); + if( !pSourceForwarder ) + return; + std::unique_ptr<EditTextObject> pNewTextObject = pSourceForwarder->rEditEngine.CreateTextObject(); + rEditEngine.SetText( *pNewTextObject ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unofored_internal.hxx b/editeng/source/uno/unofored_internal.hxx new file mode 100644 index 0000000000..cadb194159 --- /dev/null +++ b/editeng/source/uno/unofored_internal.hxx @@ -0,0 +1,28 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/editeng.hxx> +#include <svl/poolitem.hxx> + +SfxItemState GetSvxEditEngineItemState( EditEngine const & rEditEngine, const ESelection& rSel, sal_uInt16 nWhich ); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoforou.cxx b/editeng/source/uno/unoforou.cxx new file mode 100644 index 0000000000..8772ff9a77 --- /dev/null +++ b/editeng/source/uno/unoforou.cxx @@ -0,0 +1,582 @@ +/* -*- 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 <osl/diagnose.h> +#include <tools/debug.hxx> +#include <svl/style.hxx> +#include <com/sun/star/i18n/WordType.hpp> + +#include <svl/itemset.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editdata.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoedhlp.hxx> +#include <svl/poolitem.hxx> + +#include <editeng/unoforou.hxx> +#include <editeng/outlobj.hxx> +#include "unofored_internal.hxx" + +using namespace ::com::sun::star; + + +SvxOutlinerForwarder::SvxOutlinerForwarder( Outliner& rOutl, bool bOutlText /* = false */ ) : + rOutliner( rOutl ), + bOutlinerText( bOutlText ), + mnParaAttribsCache( 0 ) +{ +} + +SvxOutlinerForwarder::~SvxOutlinerForwarder() +{ + flushCache(); +} + +sal_Int32 SvxOutlinerForwarder::GetParagraphCount() const +{ + return rOutliner.GetParagraphCount(); +} + +sal_Int32 SvxOutlinerForwarder::GetTextLen( sal_Int32 nParagraph ) const +{ + return rOutliner.GetEditEngine().GetTextLen( nParagraph ); +} + +OUString SvxOutlinerForwarder::GetText( const ESelection& rSel ) const +{ + //! GetText (ESelection) should probably also be in the Outliner + // in the time being use as the hack for the EditEngine: + EditEngine* pEditEngine = const_cast<EditEngine*>(&rOutliner.GetEditEngine()); + return pEditEngine->GetText( rSel ); +} + +static SfxItemSet ImplOutlinerForwarderGetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib, EditEngine& rEditEngine ) +{ + if( rSel.nStartPara == rSel.nEndPara ) + { + GetAttribsFlags nFlags = GetAttribsFlags::NONE; + + switch( nOnlyHardAttrib ) + { + case EditEngineAttribs::All: + nFlags = GetAttribsFlags::ALL; + break; + case EditEngineAttribs::OnlyHard: + nFlags = GetAttribsFlags::CHARATTRIBS; + break; + default: + OSL_FAIL("unknown flags for SvxOutlinerForwarder::GetAttribs"); + } + return rEditEngine.GetAttribs( rSel.nStartPara, rSel.nStartPos, rSel.nEndPos, nFlags ); + } + else + { + return rEditEngine.GetAttribs( rSel, nOnlyHardAttrib ); + } +} + +SfxItemSet SvxOutlinerForwarder::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib ) const +{ + if( moAttribsCache && ( EditEngineAttribs::All == nOnlyHardAttrib ) ) + { + // have we the correct set in cache? + if( maAttribCacheSelection == rSel ) + { + // yes! just return the cache + return *moAttribsCache; + } + else + { + // no, we need delete the old cache + moAttribsCache.reset(); + } + } + + //! Does it not exist on the Outliner? + //! and why is the GetAttribs on the EditEngine not a const? + EditEngine& rEditEngine = const_cast<EditEngine&>(rOutliner.GetEditEngine()); + + SfxItemSet aSet( ImplOutlinerForwarderGetAttribs( rSel, nOnlyHardAttrib, rEditEngine ) ); + + if( EditEngineAttribs::All == nOnlyHardAttrib ) + { + moAttribsCache.emplace( aSet ); + maAttribCacheSelection = rSel; + } + + SfxStyleSheet* pStyle = rEditEngine.GetStyleSheet( rSel.nStartPara ); + if( pStyle ) + aSet.SetParent( &(pStyle->GetItemSet() ) ); + + return aSet; +} + +SfxItemSet SvxOutlinerForwarder::GetParaAttribs( sal_Int32 nPara ) const +{ + if( moParaAttribsCache ) + { + // have we the correct set in cache? + if( nPara == mnParaAttribsCache ) + { + // yes! just return the cache + return *moParaAttribsCache; + } + else + { + // no, we need delete the old cache + moParaAttribsCache.reset(); + } + } + + moParaAttribsCache.emplace( rOutliner.GetParaAttribs( nPara ) ); + mnParaAttribsCache = nPara; + + EditEngine& rEditEngine = const_cast<EditEngine&>(rOutliner.GetEditEngine()); + + SfxStyleSheet* pStyle = rEditEngine.GetStyleSheet( nPara ); + if( pStyle ) + moParaAttribsCache->SetParent( &(pStyle->GetItemSet() ) ); + + return *moParaAttribsCache; +} + +void SvxOutlinerForwarder::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet ) +{ + flushCache(); + + const SfxItemSet* pOldParent = rSet.GetParent(); + if( pOldParent ) + const_cast<SfxItemSet*>(&rSet)->SetParent( nullptr ); + + rOutliner.SetParaAttribs( nPara, rSet ); + + if( pOldParent ) + const_cast<SfxItemSet*>(&rSet)->SetParent( pOldParent ); +} + +void SvxOutlinerForwarder::RemoveAttribs( const ESelection& rSelection ) +{ + rOutliner.RemoveAttribs( rSelection, false/*bRemoveParaAttribs*/, 0 ); +} + +SfxItemPool* SvxOutlinerForwarder::GetPool() const +{ + return rOutliner.GetEmptyItemSet().GetPool(); +} + +void SvxOutlinerForwarder::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList ) const +{ + const_cast<EditEngine&>(rOutliner.GetEditEngine()).GetPortions( nPara, rList ); +} + +OUString SvxOutlinerForwarder::GetStyleSheet(sal_Int32 nPara) const +{ + if (auto pStyle = rOutliner.GetStyleSheet(nPara)) + return pStyle->GetName(); + return OUString(); +} + +void SvxOutlinerForwarder::SetStyleSheet(sal_Int32 nPara, const OUString& rStyleName) +{ + auto pStyleSheetPool = rOutliner.GetStyleSheetPool(); + if (auto pStyle = pStyleSheetPool ? pStyleSheetPool->Find(rStyleName, SfxStyleFamily::Para) : nullptr) + rOutliner.SetStyleSheet(nPara, static_cast<SfxStyleSheet*>(pStyle)); +} + +void SvxOutlinerForwarder::QuickInsertText( const OUString& rText, const ESelection& rSel ) +{ + flushCache(); + if( rText.isEmpty() ) + { + rOutliner.QuickDelete( rSel ); + } + else + { + rOutliner.QuickInsertText( rText, rSel ); + } +} + +void SvxOutlinerForwarder::QuickInsertLineBreak( const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickInsertLineBreak( rSel ); +} + +void SvxOutlinerForwarder::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickInsertField( rFld, rSel ); +} + +void SvxOutlinerForwarder::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel ) +{ + flushCache(); + rOutliner.QuickSetAttribs( rSet, rSel ); +} + +OUString SvxOutlinerForwarder::CalcFieldValue( const SvxFieldItem& rField, sal_Int32 nPara, sal_Int32 nPos, std::optional<Color>& rpTxtColor, std::optional<Color>& rpFldColor, std::optional<FontLineStyle>& rpFldLineStyle ) +{ + return rOutliner.CalcFieldValue( rField, nPara, nPos, rpTxtColor, rpFldColor, rpFldLineStyle ); +} + +void SvxOutlinerForwarder::FieldClicked( const SvxFieldItem& /*rField*/ ) +{ +} + +bool SvxOutlinerForwarder::IsValid() const +{ + // cannot reliably query outliner state + // while in the middle of an update + return rOutliner.IsUpdateLayout(); +} + +SfxItemState SvxOutlinerForwarder::GetItemState( const ESelection& rSel, sal_uInt16 nWhich ) const +{ + return GetSvxEditEngineItemState( rOutliner.GetEditEngine(), rSel, nWhich ); +} + +SfxItemState SvxOutlinerForwarder::GetItemState( sal_Int32 nPara, sal_uInt16 nWhich ) const +{ + const SfxItemSet& rSet = rOutliner.GetParaAttribs( nPara ); + return rSet.GetItemState( nWhich ); +} + + +void SvxOutlinerForwarder::flushCache() +{ + moAttribsCache.reset(); + moParaAttribsCache.reset(); +} + +LanguageType SvxOutlinerForwarder::GetLanguage( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rOutliner.GetLanguage(nPara, nIndex); +} + +sal_Int32 SvxOutlinerForwarder::GetFieldCount( sal_Int32 nPara ) const +{ + return rOutliner.GetEditEngine().GetFieldCount(nPara); +} + +EFieldInfo SvxOutlinerForwarder::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const +{ + return rOutliner.GetEditEngine().GetFieldInfo( nPara, nField ); +} + +EBulletInfo SvxOutlinerForwarder::GetBulletInfo( sal_Int32 nPara ) const +{ + return rOutliner.GetBulletInfo( nPara ); +} + +tools::Rectangle SvxOutlinerForwarder::GetCharBounds( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + // EditEngine's 'internal' methods like GetCharacterBounds() + // don't rotate for vertical text. + Size aSize( rOutliner.CalcTextSize() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + bool bIsVertical( rOutliner.IsVertical() ); + + // #108900# Handle virtual position one-past-the end of the string + if( nIndex >= GetTextLen(nPara) ) + { + tools::Rectangle aLast; + + if( nIndex ) + { + // use last character, if possible + aLast = rOutliner.GetEditEngine().GetCharacterBounds( EPosition(nPara, nIndex-1) ); + + // move at end of this last character, make one pixel wide + aLast.Move( aLast.Right() - aLast.Left(), 0 ); + aLast.SetSize( Size(1, aLast.GetHeight()) ); + + // take care for CTL + aLast = SvxEditSourceHelper::EEToUserSpace( aLast, aSize, bIsVertical ); + } + else + { + // #109864# Bounds must lie within the paragraph + aLast = GetParaBounds( nPara ); + + // #109151# Don't use paragraph height, but line height + // instead. aLast is already CTL-correct + if( bIsVertical) + aLast.SetSize( Size( rOutliner.GetLineHeight(nPara), 1 ) ); + else + aLast.SetSize( Size( 1, rOutliner.GetLineHeight(nPara) ) ); + } + + return aLast; + } + else + { + return SvxEditSourceHelper::EEToUserSpace( rOutliner.GetEditEngine().GetCharacterBounds( EPosition(nPara, nIndex) ), + aSize, bIsVertical ); + } +} + +tools::Rectangle SvxOutlinerForwarder::GetParaBounds( sal_Int32 nPara ) const +{ + return rOutliner.GetParaBounds( nPara ); +} + +MapMode SvxOutlinerForwarder::GetMapMode() const +{ + return rOutliner.GetRefMapMode(); +} + +OutputDevice* SvxOutlinerForwarder::GetRefDevice() const +{ + return rOutliner.GetRefDevice(); +} + +bool SvxOutlinerForwarder::GetIndexAtPoint( const Point& rPos, sal_Int32& nPara, sal_Int32& nIndex ) const +{ + Size aSize( rOutliner.CalcTextSize() ); + // swap width and height + tools::Long tmp = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(tmp); + Point aEEPos( SvxEditSourceHelper::UserSpaceToEE( rPos, + aSize, + rOutliner.IsVertical() )); + + EPosition aDocPos = rOutliner.GetEditEngine().FindDocPosition( aEEPos ); + + nPara = aDocPos.nPara; + nIndex = aDocPos.nIndex; + + return true; +} + +bool SvxOutlinerForwarder::GetWordIndices( sal_Int32 nPara, sal_Int32 nIndex, sal_Int32& nStart, sal_Int32& nEnd ) const +{ + ESelection aRes = rOutliner.GetEditEngine().GetWord( ESelection(nPara, nIndex, nPara, nIndex), css::i18n::WordType::DICTIONARY_WORD ); + + if( aRes.nStartPara == nPara && + aRes.nStartPara == aRes.nEndPara ) + { + nStart = aRes.nStartPos; + nEnd = aRes.nEndPos; + + return true; + } + + return false; +} + +bool SvxOutlinerForwarder::GetAttributeRun( sal_Int32& nStartIndex, sal_Int32& nEndIndex, sal_Int32 nPara, sal_Int32 nIndex, bool bInCell ) const +{ + SvxEditSourceHelper::GetAttributeRun( nStartIndex, nEndIndex, rOutliner.GetEditEngine(), nPara, nIndex, bInCell ); + return true; +} + +sal_Int32 SvxOutlinerForwarder::GetLineCount( sal_Int32 nPara ) const +{ + return rOutliner.GetLineCount(nPara); +} + +sal_Int32 SvxOutlinerForwarder::GetLineLen( sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rOutliner.GetLineLen(nPara, nLine); +} + +void SvxOutlinerForwarder::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nPara, sal_Int32 nLine ) const +{ + return rOutliner.GetEditEngine().GetLineBoundaries( rStart, rEnd, nPara, nLine ); +} + +sal_Int32 SvxOutlinerForwarder::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const +{ + return rOutliner.GetEditEngine().GetLineNumberAtIndex( nPara, nIndex ); +} + +bool SvxOutlinerForwarder::QuickFormatDoc( bool ) +{ + rOutliner.QuickFormatDoc(); + + return true; +} + +bool SvxOutlinerForwarder::Delete( const ESelection& rSelection ) +{ + flushCache(); + rOutliner.QuickDelete( rSelection ); + rOutliner.QuickFormatDoc(); + + return true; +} + +bool SvxOutlinerForwarder::InsertText( const OUString& rStr, const ESelection& rSelection ) +{ + flushCache(); + rOutliner.QuickInsertText( rStr, rSelection ); + rOutliner.QuickFormatDoc(); + + return true; +} + +sal_Int16 SvxOutlinerForwarder::GetDepth( sal_Int32 nPara ) const +{ + DBG_ASSERT( 0 <= nPara && nPara < GetParagraphCount(), "SvxOutlinerForwarder::GetDepth: Invalid paragraph index"); + + Paragraph* pPara = rOutliner.GetParagraph( nPara ); + + sal_Int16 nLevel = -1; + + if( pPara ) + nLevel = rOutliner.GetDepth( nPara ); + + return nLevel; +} + +bool SvxOutlinerForwarder::SetDepth( sal_Int32 nPara, sal_Int16 nNewDepth ) +{ + DBG_ASSERT( 0 <= nPara && nPara < GetParagraphCount(), "SvxOutlinerForwarder::SetDepth: Invalid paragraph index"); + + if( (nNewDepth >= -1) && (nNewDepth <= 9) && (0 <= nPara && nPara < GetParagraphCount()) ) + { + Paragraph* pPara = rOutliner.GetParagraph( nPara ); + if( pPara ) + { + rOutliner.SetDepth( pPara, nNewDepth ); + +// const bool bOutlinerText = pSdrObject && (pSdrObject->GetObjInventor() == SdrInventor::Default) && (pSdrObject->GetObjIdentifier() == OBJ_OUTLINETEXT); + if( bOutlinerText ) + rOutliner.SetLevelDependentStyleSheet( nPara ); + + return true; + } + } + + return false; +} + +sal_Int32 SvxOutlinerForwarder::GetNumberingStartValue( sal_Int32 nPara ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + return rOutliner.GetNumberingStartValue( nPara ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::GetNumberingStartValue)(), Invalid paragraph index"); + return -1; + } +} + +void SvxOutlinerForwarder::SetNumberingStartValue( sal_Int32 nPara, sal_Int32 nNumberingStartValue ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + rOutliner.SetNumberingStartValue( nPara, nNumberingStartValue ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::SetNumberingStartValue)(), Invalid paragraph index"); + } +} + +bool SvxOutlinerForwarder::IsParaIsNumberingRestart( sal_Int32 nPara ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + return rOutliner.IsParaIsNumberingRestart( nPara ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::IsParaIsNumberingRestart)(), Invalid paragraph index"); + return false; + } +} + +void SvxOutlinerForwarder::SetParaIsNumberingRestart( sal_Int32 nPara, bool bParaIsNumberingRestart ) +{ + if( 0 <= nPara && nPara < GetParagraphCount() ) + { + rOutliner.SetParaIsNumberingRestart( nPara, bParaIsNumberingRestart ); + } + else + { + OSL_FAIL( "SvxOutlinerForwarder::SetParaIsNumberingRestart)(), Invalid paragraph index"); + } +} + +const SfxItemSet * SvxOutlinerForwarder::GetEmptyItemSetPtr() +{ + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + return &rEditEngine.GetEmptyItemSet(); +} + +void SvxOutlinerForwarder::AppendParagraph() +{ + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + rEditEngine.InsertParagraph( rEditEngine.GetParagraphCount(), OUString() ); +} + +sal_Int32 SvxOutlinerForwarder::AppendTextPortion( sal_Int32 nPara, const OUString &rText, const SfxItemSet & /*rSet*/ ) +{ + sal_Int32 nLen = 0; + + EditEngine& rEditEngine = const_cast< EditEngine& >( rOutliner.GetEditEngine() ); + sal_Int32 nParaCount = rEditEngine.GetParagraphCount(); + DBG_ASSERT( 0 <= nPara && nPara < nParaCount, "paragraph index out of bounds" ); + if (0 <= nPara && nPara < nParaCount) + { + nLen = rEditEngine.GetTextLen( nPara ); + rEditEngine.QuickInsertText( rText, ESelection( nPara, nLen, nPara, nLen ) ); + } + + return nLen; +} + +void SvxOutlinerForwarder::CopyText(const SvxTextForwarder& rSource) +{ + const SvxOutlinerForwarder* pSourceForwarder = dynamic_cast< const SvxOutlinerForwarder* >( &rSource ); + if( !pSourceForwarder ) + return; + std::optional<OutlinerParaObject> pNewOutlinerParaObject = pSourceForwarder->rOutliner.CreateParaObject(); + rOutliner.SetText( *pNewOutlinerParaObject ); +} + + +sal_Int32 SvxTextForwarder::GetNumberingStartValue( sal_Int32 ) +{ + return -1; +} + +void SvxTextForwarder::SetNumberingStartValue( sal_Int32, sal_Int32 ) +{ +} + +bool SvxTextForwarder::IsParaIsNumberingRestart( sal_Int32 ) +{ + return false; +} + +void SvxTextForwarder::SetParaIsNumberingRestart( sal_Int32, bool ) +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoipset.cxx b/editeng/source/uno/unoipset.cxx new file mode 100644 index 0000000000..4a4dd9f5e5 --- /dev/null +++ b/editeng/source/uno/unoipset.cxx @@ -0,0 +1,333 @@ +/* -*- 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 <com/sun/star/beans/XPropertySet.hpp> +#include <svl/itemprop.hxx> +#include <tools/UnitConversion.hxx> +#include <editeng/unoipset.hxx> +#include <svl/itempool.hxx> +#include <svl/solar.hrc> +#include <o3tl/any.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <algorithm> + +using namespace ::com::sun::star; + + +SvxItemPropertySet::SvxItemPropertySet( std::span<const SfxItemPropertyMapEntry> pMap, SfxItemPool& rItemPool ) +: m_aPropertyMap( pMap ), + mrItemPool( rItemPool ) +{ +} + + +SvxItemPropertySet::~SvxItemPropertySet() +{ +} + + +static bool SvxUnoCheckForPositiveValue( const uno::Any& rVal ) +{ + bool bConvert = true; // the default is that all metric items must be converted + sal_Int32 nValue = 0; + if( rVal >>= nValue ) + bConvert = (nValue > 0); + return bConvert; +} + + +uno::Any SvxItemPropertySet::getPropertyValue( const SfxItemPropertyMapEntry* pMap, const SfxItemSet& rSet, bool bSearchInParent, bool bDontConvertNegativeValues ) +{ + uno::Any aVal; + if(!pMap || !pMap->nWID) + return aVal; + + const SfxPoolItem* pItem = nullptr; + SfxItemPool* pPool = rSet.GetPool(); + (void)rSet.GetItemState( pMap->nWID, bSearchInParent, &pItem ); + if( nullptr == pItem && pPool ) + pItem = &(pPool->GetDefaultItem( pMap->nWID )); + + const MapUnit eMapUnit = pPool ? pPool->GetMetric(pMap->nWID) : MapUnit::Map100thMM; + sal_uInt8 nMemberId = pMap->nMemberId; + if( eMapUnit == MapUnit::Map100thMM ) + nMemberId &= (~CONVERT_TWIPS); + + if(pItem) + { + pItem->QueryValue( aVal, nMemberId ); + if( pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM ) + { + if( eMapUnit != MapUnit::Map100thMM ) + { + if ( !bDontConvertNegativeValues || SvxUnoCheckForPositiveValue( aVal ) ) + SvxUnoConvertToMM( eMapUnit, aVal ); + } + } + else if ( pMap->aType.getTypeClass() == uno::TypeClass_ENUM && + aVal.getValueType() == ::cppu::UnoType<sal_Int32>::get() ) + { + // convert typeless SfxEnumItem to enum type + sal_Int32 nEnum; + aVal >>= nEnum; + aVal.setValue( &nEnum, pMap->aType ); + } + } + else + { + OSL_FAIL( "No SfxPoolItem found for property!" ); + } + + return aVal; +} + + +void SvxItemPropertySet::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rVal, SfxItemSet& rSet, bool bDontConvertNegativeValues ) +{ + if(!pMap || !pMap->nWID) + return; + + // Get item + const SfxPoolItem* pItem = nullptr; + SfxItemState eState = rSet.GetItemState( pMap->nWID, true, &pItem ); + SfxItemPool* pPool = rSet.GetPool(); + + // Put UnoAny in the item value + if(eState < SfxItemState::DEFAULT || pItem == nullptr) + { + if( pPool == nullptr ) + { + OSL_FAIL( "No default item and no pool?" ); + return; + } + + pItem = &pPool->GetDefaultItem( pMap->nWID ); + } + + uno::Any aValue(rVal); + + const MapUnit eMapUnit = pPool ? pPool->GetMetric(pMap->nWID) : MapUnit::Map100thMM; + + // check for needed metric translation + if ((pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM) && eMapUnit != MapUnit::Map100thMM) + { + if (!bDontConvertNegativeValues || SvxUnoCheckForPositiveValue(aValue)) + SvxUnoConvertFromMM(eMapUnit, aValue); + } + + std::unique_ptr<SfxPoolItem> pNewItem(pItem->Clone()); + + sal_uInt8 nMemberId = pMap->nMemberId; + if (eMapUnit == MapUnit::Map100thMM) + nMemberId &= (~CONVERT_TWIPS); + + if (pNewItem->PutValue(aValue, nMemberId)) + { + // Set new item in item set + pNewItem->SetWhich(pMap->nWID); + rSet.Put(std::move(pNewItem)); + } +} + + +uno::Any SvxItemPropertySet::getPropertyValue( const SfxItemPropertyMapEntry* pMap, SvxItemPropertySetUsrAnys& rAnys ) const +{ + // Already entered a value? Then finish quickly + uno::Any* pUsrAny = rAnys.GetUsrAnyForID(*pMap); + if(pUsrAny) + return *pUsrAny; + + // No UsrAny detected yet, generate Default entry and return this + const MapUnit eMapUnit = mrItemPool.GetMetric(pMap->nWID); + sal_uInt8 nMemberId = pMap->nMemberId; + if( eMapUnit == MapUnit::Map100thMM ) + nMemberId &= (~CONVERT_TWIPS); + uno::Any aVal; + SfxItemSet aSet( mrItemPool, pMap->nWID, pMap->nWID); + + if( (pMap->nWID < OWN_ATTR_VALUE_START) || (pMap->nWID > OWN_ATTR_VALUE_END ) ) + { + // Get Default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + aSet.Put(mrItemPool.GetDefaultItem(pMap->nWID)); + } + + if(aSet.Count()) + { + const SfxPoolItem* pItem = nullptr; + SfxItemState eState = aSet.GetItemState( pMap->nWID, true, &pItem ); + if(eState >= SfxItemState::DEFAULT && pItem) + { + pItem->QueryValue( aVal, nMemberId ); + rAnys.AddUsrAnyForID(aVal, *pMap); + } + } + + // check for needed metric translation + if(pMap->nMoreFlags & PropertyMoreFlags::METRIC_ITEM && eMapUnit != MapUnit::Map100thMM) + { + SvxUnoConvertToMM( eMapUnit, aVal ); + } + + if ( pMap->aType.getTypeClass() == uno::TypeClass_ENUM && + aVal.getValueType() == ::cppu::UnoType<sal_Int32>::get() ) + { + sal_Int32 nEnum; + aVal >>= nEnum; + + aVal.setValue( &nEnum, pMap->aType ); + } + + return aVal; +} + + +void SvxItemPropertySet::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rVal, SvxItemPropertySetUsrAnys& rAnys ) +{ + uno::Any* pUsrAny = rAnys.GetUsrAnyForID(*pMap); + if(!pUsrAny) + rAnys.AddUsrAnyForID(rVal, *pMap); + else + *pUsrAny = rVal; +} + + +const SfxItemPropertyMapEntry* SvxItemPropertySet::getPropertyMapEntry(std::u16string_view rName) const +{ + return m_aPropertyMap.getByName( rName ); + } + + +uno::Reference< beans::XPropertySetInfo > const & SvxItemPropertySet::getPropertySetInfo() const +{ + if( !m_xInfo.is() ) + m_xInfo = new SfxItemPropertySetInfo( m_aPropertyMap ); + return m_xInfo; +} + + +/** converts the given any with a metric to 100th/mm if needed */ +void SvxUnoConvertToMM( const MapUnit eSourceMapUnit, uno::Any & rMetric ) noexcept +{ + // map the metric of the itempool to 100th mm + switch(eSourceMapUnit) + { + case MapUnit::MapTwip : + { + switch( rMetric.getValueTypeClass() ) + { + case uno::TypeClass_BYTE: + rMetric <<= static_cast<sal_Int8>(convertTwipToMm100(*o3tl::forceAccess<sal_Int8>(rMetric))); + break; + case uno::TypeClass_SHORT: + rMetric <<= static_cast<sal_Int16>(convertTwipToMm100(*o3tl::forceAccess<sal_Int16>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_SHORT: + rMetric <<= static_cast<sal_uInt16>(convertTwipToMm100(*o3tl::forceAccess<sal_uInt16>(rMetric))); + break; + case uno::TypeClass_LONG: + rMetric <<= static_cast<sal_Int32>(convertTwipToMm100(*o3tl::forceAccess<sal_Int32>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_LONG: + rMetric <<= static_cast<sal_uInt32>(convertTwipToMm100(*o3tl::forceAccess<sal_uInt32>(rMetric))); + break; + default: + SAL_WARN("editeng", "AW: Missing unit translation to 100th mm, " << OString::number(static_cast<sal_Int32>(rMetric.getValueTypeClass()))); + assert(false); + } + break; + } + default: + { + OSL_FAIL("AW: Missing unit translation to 100th mm!"); + } + } +} + + +/** converts the given any with a metric from 100th/mm to the given metric if needed */ +void SvxUnoConvertFromMM( const MapUnit eDestinationMapUnit, uno::Any & rMetric ) noexcept +{ + switch(eDestinationMapUnit) + { + case MapUnit::MapTwip : + { + switch( rMetric.getValueTypeClass() ) + { + case uno::TypeClass_BYTE: + rMetric <<= static_cast<sal_Int8>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int8>(rMetric))); + break; + case uno::TypeClass_SHORT: + rMetric <<= static_cast<sal_Int16>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int16>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_SHORT: + rMetric <<= static_cast<sal_uInt16>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_uInt16>(rMetric))); + break; + case uno::TypeClass_LONG: + rMetric <<= static_cast<sal_Int32>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_Int32>(rMetric))); + break; + case uno::TypeClass_UNSIGNED_LONG: + rMetric <<= static_cast<sal_uInt32>(sanitiseMm100ToTwip(*o3tl::forceAccess<sal_uInt32>(rMetric))); + break; + default: + OSL_FAIL("AW: Missing unit translation to 100th mm!"); + } + break; + } + default: + { + OSL_FAIL("AW: Missing unit translation to PoolMetrics!"); + } + } +} + +SvxItemPropertySetUsrAnys::SvxItemPropertySetUsrAnys() = default; + +SvxItemPropertySetUsrAnys::~SvxItemPropertySetUsrAnys() +{ + ClearAllUsrAny(); +} + +uno::Any* SvxItemPropertySetUsrAnys::GetUsrAnyForID(SfxItemPropertyMapEntry const & entry) const +{ + for (auto const & rActual : aCombineList) + { + if( rActual.nWID == entry.nWID && rActual.memberId == entry.nMemberId ) + return const_cast<uno::Any*>(&rActual.aAny); + } + return nullptr; +} + +void SvxItemPropertySetUsrAnys::AddUsrAnyForID( + const uno::Any& rAny, SfxItemPropertyMapEntry const & entry) +{ + SvxIDPropertyCombine aNew; + aNew.nWID = entry.nWID; + aNew.memberId = entry.nMemberId; + aNew.aAny = rAny; + aCombineList.push_back( std::move(aNew) ); +} + +void SvxItemPropertySetUsrAnys::ClearAllUsrAny() +{ + aCombineList.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unonrule.cxx b/editeng/source/uno/unonrule.cxx new file mode 100644 index 0000000000..5083f4d8e8 --- /dev/null +++ b/editeng/source/uno/unonrule.cxx @@ -0,0 +1,544 @@ +/* -*- 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 <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <cppuhelper/implbase1.hxx> +#include <utility> +#include <vcl/font.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graph.hxx> +#include <vcl/GraphicObject.hxx> +#include <vcl/GraphicLoader.hxx> +#include <tools/debug.hxx> + +#include <editeng/brushitem.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/numitem.hxx> +#include <editeng/unofdesc.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/editids.hrc> +#include <o3tl/enumarray.hxx> +#include <o3tl/temporary.hxx> +#include <memory> + +using ::com::sun::star::util::XCloneable; +using ::com::sun::star::ucb::XAnyCompare; + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; + +const SvxAdjust aUnoToSvxAdjust[] = +{ + SvxAdjust::Left, + SvxAdjust::Right, + SvxAdjust::Center, + SvxAdjust::Left, + SvxAdjust::Left, + SvxAdjust::Left, + SvxAdjust::Block +}; + +const o3tl::enumarray<SvxAdjust, sal_Int16> aSvxToUnoAdjust +{ + text::HoriOrientation::LEFT, + text::HoriOrientation::RIGHT, + text::HoriOrientation::FULL, + text::HoriOrientation::CENTER, + text::HoriOrientation::FULL, + text::HoriOrientation::LEFT +}; + +static SvxAdjust ConvertUnoAdjust( unsigned short nAdjust ) +{ + DBG_ASSERT( nAdjust <= 7, "Enum has changed! [CL]" ); + return aUnoToSvxAdjust[nAdjust]; +} + +static unsigned short ConvertUnoAdjust( SvxAdjust eAdjust ) +{ + DBG_ASSERT( static_cast<int>(eAdjust) <= 6, "Enum has changed! [CL]" ); + return aSvxToUnoAdjust[eAdjust]; +} + +SvxUnoNumberingRules::SvxUnoNumberingRules(SvxNumRule aRule) +: maRule(std::move( aRule )) +{ +} + +SvxUnoNumberingRules::~SvxUnoNumberingRules() noexcept +{ +} + +//XIndexReplace +void SAL_CALL SvxUnoNumberingRules::replaceByIndex( sal_Int32 Index, const uno::Any& Element ) +{ + SolarMutexGuard aGuard; + + if( Index < 0 || Index >= maRule.GetLevelCount() ) + throw IndexOutOfBoundsException(); + + Sequence< beans::PropertyValue > aSeq; + + if( !( Element >>= aSeq) ) + throw IllegalArgumentException(); + setNumberingRuleByIndex( aSeq, Index ); +} + +// XIndexAccess +sal_Int32 SAL_CALL SvxUnoNumberingRules::getCount() +{ + SolarMutexGuard aGuard; + + return maRule.GetLevelCount(); +} + +Any SAL_CALL SvxUnoNumberingRules::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard aGuard; + + if( Index < 0 || Index >= maRule.GetLevelCount() ) + throw IndexOutOfBoundsException(); + + return Any( getNumberingRuleByIndex(Index) ); +} + +//XElementAccess +Type SAL_CALL SvxUnoNumberingRules::getElementType() +{ + return cppu::UnoType<Sequence< beans::PropertyValue >>::get(); +} + +sal_Bool SAL_CALL SvxUnoNumberingRules::hasElements() +{ + return true; +} + +// XAnyCompare +sal_Int16 SAL_CALL SvxUnoNumberingRules::compare( const Any& rAny1, const Any& rAny2 ) +{ + return SvxUnoNumberingRules::Compare( rAny1, rAny2 ); +} + +// XCloneable +Reference< XCloneable > SAL_CALL SvxUnoNumberingRules::createClone( ) +{ + return new SvxUnoNumberingRules(maRule); +} + +OUString SAL_CALL SvxUnoNumberingRules::getImplementationName( ) +{ + return "SvxUnoNumberingRules"; +} + +sal_Bool SAL_CALL SvxUnoNumberingRules::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService(this, ServiceName); +} + +Sequence< OUString > SAL_CALL SvxUnoNumberingRules::getSupportedServiceNames( ) +{ + return { "com.sun.star.text.NumberingRules" }; +} + +Sequence<beans::PropertyValue> SvxUnoNumberingRules::getNumberingRuleByIndex(sal_Int32 nIndex) const +{ + // NumberingRule aRule; + const SvxNumberFormat& rFmt = maRule.GetLevel(static_cast<sal_uInt16>(nIndex)); + sal_uInt16 nIdx = 0; + + const int nProps = 15; + std::unique_ptr<beans::PropertyValue[]> pArray(new beans::PropertyValue[nProps]); + + Any aVal; + { + aVal <<= static_cast<sal_uInt16>(rFmt.GetNumberingType()); + beans::PropertyValue aAlignProp( UNO_NAME_NRULE_NUMBERINGTYPE, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aAlignProp; + } + + { + SvxAdjust eAdj = rFmt.GetNumAdjust(); + aVal <<= ConvertUnoAdjust(eAdj); + pArray[nIdx++] = beans::PropertyValue( UNO_NAME_NRULE_ADJUST, -1, aVal, beans::PropertyState_DIRECT_VALUE); + } + + { + aVal <<= rFmt.GetPrefix(); + beans::PropertyValue aPrefixProp( UNO_NAME_NRULE_PREFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aPrefixProp; + } + + { + aVal <<= rFmt.GetSuffix(); + beans::PropertyValue aSuffixProp( UNO_NAME_NRULE_SUFFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aSuffixProp; + } + + if(SVX_NUM_CHAR_SPECIAL == rFmt.GetNumberingType()) + { + sal_UCS4 nCode = rFmt.GetBulletChar(); + OUString aStr( &nCode, 1 ); + aVal <<= aStr; + pArray[nIdx++] = beans::PropertyValue("BulletChar", -1, aVal, beans::PropertyState_DIRECT_VALUE); + } + + if( rFmt.GetBulletFont() ) + { + awt::FontDescriptor aDesc; + SvxUnoFontDescriptor::ConvertFromFont( *rFmt.GetBulletFont(), aDesc ); + aVal <<= aDesc; + pArray[nIdx++] = beans::PropertyValue( UNO_NAME_NRULE_BULLET_FONT, -1, aVal, beans::PropertyState_DIRECT_VALUE); + } + + { + const SvxBrushItem* pBrush = rFmt.GetBrush(); + const Graphic* pGraphic = nullptr; + if (pBrush) + pGraphic = pBrush->GetGraphic(); + if (pGraphic) + { + uno::Reference<awt::XBitmap> xBitmap(pGraphic->GetXGraphic(), uno::UNO_QUERY); + aVal <<= xBitmap; + + const beans::PropertyValue aGraphicProp("GraphicBitmap", -1, aVal, beans::PropertyState_DIRECT_VALUE); + pArray[nIdx++] = aGraphicProp; + } + } + + { + const Size aSize( rFmt.GetGraphicSize() ); + const awt::Size aUnoSize( aSize.Width(), aSize.Height() ); + aVal <<= aUnoSize; + const beans::PropertyValue aGraphicSizeProp("GraphicSize", -1, aVal, beans::PropertyState_DIRECT_VALUE ); + pArray[nIdx++] = aGraphicSizeProp; + } + + aVal <<= static_cast<sal_Int16>(rFmt.GetStart()); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_START_WITH, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetAbsLSpace(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_LEFT_MARGIN, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetFirstLineOffset(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_FIRST_LINE_OFFSET, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + pArray[nIdx++] = beans::PropertyValue("SymbolTextDistance", -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= rFmt.GetBulletColor(); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_COLOR, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + aVal <<= static_cast<sal_Int16>(rFmt.GetBulletRelSize()); + pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_RELSIZE, -1, aVal, beans::PropertyState_DIRECT_VALUE); + + DBG_ASSERT( nIdx <= nProps, "FixMe: overflow in Array!!! [CL]" ); + Sequence< beans::PropertyValue> aSeq(pArray.get(), nIdx); + + return aSeq; +} + +void SvxUnoNumberingRules::setNumberingRuleByIndex(const Sequence<beans::PropertyValue >& rProperties, sal_Int32 nIndex) +{ + SvxNumberFormat aFmt(maRule.GetLevel( static_cast<sal_uInt16>(nIndex) )); + for(const beans::PropertyValue& rProp : rProperties) + { + const OUString& rPropName = rProp.Name; + const Any& aVal = rProp.Value; + + if ( rPropName == UNO_NAME_NRULE_NUMBERINGTYPE ) + { + sal_Int16 nSet = sal_Int16(); + aVal >>= nSet; + + // There is no reason to limit numbering types. + if ( nSet>=0 ) + { + aFmt.SetNumberingType(static_cast<SvxNumType>(nSet)); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_PREFIX ) + { + OUString aPrefix; + if( aVal >>= aPrefix ) + { + aFmt.SetPrefix(aPrefix); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_SUFFIX ) + { + OUString aSuffix; + if( aVal >>= aSuffix ) + { + aFmt.SetSuffix(aSuffix); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLETID ) + { + sal_Int16 nSet = sal_Int16(); + if( aVal >>= nSet ) + { + if(nSet < 0x100) + { + aFmt.SetBulletChar(nSet); + continue; + } + } + } + else if ( rPropName == "BulletChar" ) + { + OUString aStr; + if( aVal >>= aStr ) + { + if(!aStr.isEmpty()) + { + aFmt.SetBulletChar(aStr.iterateCodePoints(&o3tl::temporary(sal_Int32(0)))); + } + else + { + aFmt.SetBulletChar(0); + } + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_ADJUST ) + { + sal_Int16 nAdjust = sal_Int16(); + if( aVal >>= nAdjust ) + { + aFmt.SetNumAdjust(ConvertUnoAdjust( static_cast<unsigned short>(nAdjust) )); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_FONT ) + { + awt::FontDescriptor aDesc; + if( aVal >>= aDesc ) + { + vcl::Font aFont; + SvxUnoFontDescriptor::ConvertToFont( aDesc, aFont ); + aFmt.SetBulletFont(&aFont); + continue; + } + } + else if ( rPropName == "GraphicURL" ) + { + OUString aURL; + if (aVal >>= aURL) + { + Graphic aGraphic = vcl::graphic::loadFromURL(aURL); + if (!aGraphic.IsNone()) + { + SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH); + aFmt.SetGraphicBrush(&aBrushItem); + } + continue; + } + } + else if ( rPropName == "GraphicBitmap" ) + { + uno::Reference<awt::XBitmap> xBitmap; + if (aVal >>= xBitmap) + { + uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY); + Graphic aGraphic(xGraphic); + SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH); + aFmt.SetGraphicBrush( &aBrushItem ); + continue; + } + } + else if ( rPropName == "GraphicSize" ) + { + awt::Size aUnoSize; + if( aVal >>= aUnoSize ) + { + aFmt.SetGraphicSize( Size( aUnoSize.Width, aUnoSize.Height ) ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_START_WITH ) + { + sal_Int16 nStart = sal_Int16(); + if( aVal >>= nStart ) + { + aFmt.SetStart( nStart ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_LEFT_MARGIN ) + { + sal_Int32 nMargin = 0; + if( aVal >>= nMargin ) + { + aFmt.SetAbsLSpace(nMargin); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_FIRST_LINE_OFFSET ) + { + sal_Int32 nMargin = 0; + if( aVal >>= nMargin ) + { + aFmt.SetFirstLineOffset(nMargin); + continue; + } + } + else if ( rPropName == "SymbolTextDistance" ) + { + sal_Int32 nTextDistance = 0; + if( aVal >>= nTextDistance ) + { + aFmt.SetCharTextDistance(static_cast<sal_uInt16>(nTextDistance)); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_COLOR ) + { + Color aColor; + if( aVal >>= aColor ) + { + aFmt.SetBulletColor( aColor ); + continue; + } + } + else if ( rPropName == UNO_NAME_NRULE_BULLET_RELSIZE ) + { + sal_Int16 nSize = sal_Int16(); + if( aVal >>= nSize ) + { + // [AOO Bug 120650] the slide content corrupt when open in Aoo + // [TDF# 126234] when MS Office document being imported, the value of the relative size + // of the bullet could be as high as 400% + if ((nSize>400)||(nSize<=0)) + { + nSize = 100; + } + + aFmt.SetBulletRelSize( static_cast<short>(nSize) ); + continue; + } + } + else + { + continue; + } + + throw IllegalArgumentException(); + } + + // check that we always have a brush item for bitmap numbering + if( aFmt.GetNumberingType() == SVX_NUM_BITMAP ) + { + if( nullptr == aFmt.GetBrush() ) + { + GraphicObject aGrafObj; + SvxBrushItem aBrushItem( aGrafObj, GPOS_AREA, SID_ATTR_BRUSH ); + aFmt.SetGraphicBrush( &aBrushItem ); + } + } + maRule.SetLevel( static_cast<sal_uInt16>(nIndex), aFmt ); +} + +const SvxNumRule& SvxGetNumRule( Reference< XIndexReplace > const & xRule ) +{ + SvxUnoNumberingRules* pRule = dynamic_cast<SvxUnoNumberingRules*>( xRule.get() ); + if( pRule == nullptr ) + throw IllegalArgumentException(); + + return pRule->getNumRule(); +} + +css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule(const SvxNumRule& rRule) +{ + return new SvxUnoNumberingRules( rRule ); +} + +namespace { + +class SvxUnoNumberingRulesCompare : public ::cppu::WeakImplHelper< XAnyCompare > +{ +public: + virtual sal_Int16 SAL_CALL compare( const Any& Any1, const Any& Any2 ) override; +}; + +} + +sal_Int16 SAL_CALL SvxUnoNumberingRulesCompare::compare( const Any& Any1, const Any& Any2 ) +{ + return SvxUnoNumberingRules::Compare( Any1, Any2 ); +} + +sal_Int16 SvxUnoNumberingRules::Compare( const Any& Any1, const Any& Any2 ) +{ + Reference< XIndexReplace > x1( Any1, UNO_QUERY ), x2( Any2, UNO_QUERY ); + if( !x1 || !x2 ) + return -1; + + if( x1.get() == x2.get() ) + return 0; + + SvxUnoNumberingRules* pRule1 = dynamic_cast<SvxUnoNumberingRules*>( x1.get() ); + if( !pRule1 ) + return -1; + SvxUnoNumberingRules* pRule2 = dynamic_cast<SvxUnoNumberingRules*>( x2.get() ); + if( !pRule2 ) + return -1; + + const SvxNumRule& rRule1 = pRule1->getNumRule(); + const SvxNumRule& rRule2 = pRule2->getNumRule(); + + const sal_uInt16 nLevelCount1 = rRule1.GetLevelCount(); + const sal_uInt16 nLevelCount2 = rRule2.GetLevelCount(); + + if( nLevelCount1 == 0 || nLevelCount2 == 0 ) + return -1; + + for( sal_uInt16 i = 0; (i < nLevelCount1) && (i < nLevelCount2); i++ ) + { + if( rRule1.GetLevel(i) != rRule2.GetLevel(i) ) + return -1; + } + return 0; +} + +Reference< XAnyCompare > SvxCreateNumRuleCompare() noexcept +{ + return new SvxUnoNumberingRulesCompare; +} + +css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule() +{ + SvxNumRule aTempRule( SvxNumRuleFlags::NONE, 10, false ); + return SvxCreateNumRule( aTempRule ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unopracc.cxx b/editeng/source/uno/unopracc.cxx new file mode 100644 index 0000000000..c36fc152e2 --- /dev/null +++ b/editeng/source/uno/unopracc.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <cppuhelper/typeprovider.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <unopracc.hxx> +#include <editeng/unoedsrc.hxx> + +using namespace ::com::sun::star; + + +SvxAccessibleTextPropertySet::SvxAccessibleTextPropertySet( const SvxEditSource* pEditSrc, const SvxItemPropertySet* pPropSet ) + : SvxUnoTextRangeBase( pEditSrc, pPropSet ) +{ +} + +SvxAccessibleTextPropertySet::~SvxAccessibleTextPropertySet() noexcept +{ +} + +uno::Reference< text::XText > SAL_CALL SvxAccessibleTextPropertySet::getText() +{ + // TODO (empty?) + return uno::Reference< text::XText > (); +} + +uno::Any SAL_CALL SvxAccessibleTextPropertySet::queryInterface( const uno::Type & rType ) +{ + return OWeakObject::queryInterface(rType); +} + +void SAL_CALL SvxAccessibleTextPropertySet::acquire() + noexcept +{ + OWeakObject::acquire(); +} + +void SAL_CALL SvxAccessibleTextPropertySet::release() + noexcept +{ + OWeakObject::release(); +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxAccessibleTextPropertySet::getTypes() +{ + static ::cppu::OTypeCollection ourTypeCollection( + ::cppu::UnoType<beans::XPropertySet>::get(), + ::cppu::UnoType<beans::XMultiPropertySet>::get(), + ::cppu::UnoType<beans::XPropertyState>::get(), + ::cppu::UnoType<lang::XServiceInfo>::get(), + ::cppu::UnoType<lang::XTypeProvider>::get() ); + + return ourTypeCollection.getTypes() ; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxAccessibleTextPropertySet::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XServiceInfo +OUString SAL_CALL SAL_CALL SvxAccessibleTextPropertySet::getImplementationName() +{ + return "SvxAccessibleTextPropertySet"; +} + +sal_Bool SAL_CALL SvxAccessibleTextPropertySet::supportsService (const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unotext.cxx b/editeng/source/uno/unotext.cxx new file mode 100644 index 0000000000..f74d7f67c3 --- /dev/null +++ b/editeng/source/uno/unotext.cxx @@ -0,0 +1,2561 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <com/sun/star/text/ControlCharacter.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/TextRangeSelection.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XNameContainer.hpp> + +#include <svl/itemset.hxx> +#include <svl/itempool.hxx> +#include <svl/eitem.hxx> +#include <tools/debug.hxx> + +#include <editeng/unoprnms.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoedsrc.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/unofdesc.hxx> +#include <editeng/unofield.hxx> +#include <editeng/flditem.hxx> +#include <editeng/numitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unoipset.hxx> +#include <editeng/colritem.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <editeng/unonames.hxx> + +#include <initializer_list> +#include <memory> +#include <string_view> + +using namespace ::cppu; +using namespace ::com::sun::star; + +namespace { + +ESelection toESelection(const text::TextRangeSelection& rSel) +{ + ESelection aESel; + aESel.nStartPara = rSel.Start.Paragraph; + aESel.nStartPos = rSel.Start.PositionInParagraph; + aESel.nEndPara = rSel.End.Paragraph; + aESel.nEndPos = rSel.End.PositionInParagraph; + return aESel; +} + +} + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + return uno::Any(uno::Reference< xint >(this)) + +const SvxItemPropertySet* ImplGetSvxUnoOutlinerTextCursorSvxPropertySet() +{ + static SvxItemPropertySet aTextCursorSvxPropertySet( ImplGetSvxUnoOutlinerTextCursorPropertyMap(), EditEngine::GetGlobalItemPool() ); + return &aTextCursorSvxPropertySet; +} + +std::span<const SfxItemPropertyMapEntry> ImplGetSvxTextPortionPropertyMap() +{ + // Propertymap for an Outliner Text + static const SfxItemPropertyMapEntry aSvxTextPortionPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"TextField"_ustr, EE_FEATURE_FIELD, cppu::UnoType<text::XTextField>::get(), beans::PropertyAttribute::READONLY, 0 }, + { u"TextPortionType"_ustr, WID_PORTIONTYPE, ::cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 }, + { u"TextUserDefinedAttributes"_ustr, EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes"_ustr, EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + }; + return aSvxTextPortionPropertyMap; +} +const SvxItemPropertySet* ImplGetSvxTextPortionSvxPropertySet() +{ + static SvxItemPropertySet aSvxTextPortionPropertySet( ImplGetSvxTextPortionPropertyMap(), EditEngine::GetGlobalItemPool() ); + return &aSvxTextPortionPropertySet; +} + +static const SfxItemPropertySet* ImplGetSvxTextPortionSfxPropertySet() +{ + static SfxItemPropertySet aSvxTextPortionSfxPropertySet( ImplGetSvxTextPortionPropertyMap() ); + return &aSvxTextPortionSfxPropertySet; +} + +std::span<const SfxItemPropertyMapEntry> ImplGetSvxUnoOutlinerTextCursorPropertyMap() +{ + // Propertymap for an Outliner Text + static const SfxItemPropertyMapEntry aSvxUnoOutlinerTextCursorPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"TextUserDefinedAttributes"_ustr, EE_CHAR_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + { u"ParaUserDefinedAttributes"_ustr, EE_PARA_XMLATTRIBS, cppu::UnoType<css::container::XNameContainer>::get(), 0, 0}, + }; + + return aSvxUnoOutlinerTextCursorPropertyMap; +} +static const SfxItemPropertySet* ImplGetSvxUnoOutlinerTextCursorSfxPropertySet() +{ + static SfxItemPropertySet aTextCursorSfxPropertySet( ImplGetSvxUnoOutlinerTextCursorPropertyMap() ); + return &aTextCursorSfxPropertySet; +} + + +// helper for Item/Property conversion + + +void GetSelection( struct ESelection& rSel, SvxTextForwarder const * pForwarder ) noexcept +{ + DBG_ASSERT( pForwarder, "I need a valid SvxTextForwarder!" ); + if( pForwarder ) + { + sal_Int32 nParaCount = pForwarder->GetParagraphCount(); + if(nParaCount>0) + nParaCount--; + + rSel = ESelection( 0,0, nParaCount, pForwarder->GetTextLen( nParaCount )); + } +} + +void CheckSelection( struct ESelection& rSel, SvxTextForwarder const * pForwarder ) noexcept +{ + DBG_ASSERT( pForwarder, "I need a valid SvxTextForwarder!" ); + if( !pForwarder ) + return; + + if( rSel.nStartPara == EE_PARA_MAX_COUNT ) + { + ::GetSelection( rSel, pForwarder ); + } + else + { + ESelection aMaxSelection; + GetSelection( aMaxSelection, pForwarder ); + + // check start position + if( rSel.nStartPara < aMaxSelection.nStartPara ) + { + rSel.nStartPara = aMaxSelection.nStartPara; + rSel.nStartPos = aMaxSelection.nStartPos; + } + else if( rSel.nStartPara > aMaxSelection.nEndPara ) + { + rSel.nStartPara = aMaxSelection.nEndPara; + rSel.nStartPos = aMaxSelection.nEndPos; + } + else if( rSel.nStartPos > pForwarder->GetTextLen( rSel.nStartPara ) ) + { + rSel.nStartPos = pForwarder->GetTextLen( rSel.nStartPara ); + } + + // check end position + if( rSel.nEndPara < aMaxSelection.nStartPara ) + { + rSel.nEndPara = aMaxSelection.nStartPara; + rSel.nEndPos = aMaxSelection.nStartPos; + } + else if( rSel.nEndPara > aMaxSelection.nEndPara ) + { + rSel.nEndPara = aMaxSelection.nEndPara; + rSel.nEndPos = aMaxSelection.nEndPos; + } + else if( rSel.nEndPos > pForwarder->GetTextLen( rSel.nEndPara ) ) + { + rSel.nEndPos = pForwarder->GetTextLen( rSel.nEndPara ); + } + } +} + +static void CheckSelection( struct ESelection& rSel, SvxEditSource *pEdit ) noexcept +{ + if (!pEdit) + return; + CheckSelection( rSel, pEdit->GetTextForwarder() ); +} + + + + +UNO3_GETIMPLEMENTATION_IMPL( SvxUnoTextRangeBase ); + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxItemPropertySet* _pSet) + : mpPropSet(_pSet) +{ +} + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxEditSource* pSource, const SvxItemPropertySet* _pSet) +: mpPropSet(_pSet) +{ + SolarMutexGuard aGuard; + + DBG_ASSERT(pSource,"SvxUnoTextRangeBase: I need a valid SvxEditSource!"); + + mpEditSource = pSource->Clone(); + if (mpEditSource != nullptr) + { + ESelection aSelection; + ::GetSelection( aSelection, mpEditSource->GetTextForwarder() ); + SetSelection( aSelection ); + + mpEditSource->addRange( this ); + } +} + +SvxUnoTextRangeBase::SvxUnoTextRangeBase(const SvxUnoTextRangeBase& rRange) +: text::XTextRange() +, beans::XPropertySet() +, beans::XMultiPropertySet() +, beans::XMultiPropertyStates() +, beans::XPropertyState() +, lang::XServiceInfo() +, text::XTextRangeCompare() +, lang::XUnoTunnel() +, osl::DebugBase<SvxUnoTextRangeBase>() +, mpPropSet(rRange.getPropertySet()) +{ + SolarMutexGuard aGuard; + + if (rRange.mpEditSource) + mpEditSource = rRange.mpEditSource->Clone(); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + maSelection = rRange.maSelection; + CheckSelection( maSelection, pForwarder ); + } + + if( mpEditSource ) + mpEditSource->addRange( this ); +} + +SvxUnoTextRangeBase::~SvxUnoTextRangeBase() noexcept +{ + if( mpEditSource ) + mpEditSource->removeRange( this ); +} + +void SvxUnoTextRangeBase::SetEditSource( SvxEditSource* pSource ) noexcept +{ + DBG_ASSERT(pSource,"SvxUnoTextRangeBase: I need a valid SvxEditSource!"); + DBG_ASSERT(mpEditSource==nullptr,"SvxUnoTextRangeBase::SetEditSource called while SvxEditSource already set" ); + + mpEditSource.reset( pSource ); + + maSelection.nStartPara = EE_PARA_MAX_COUNT; + + if( mpEditSource ) + mpEditSource->addRange( this ); +} + +/** puts a field item with a copy of the given FieldData into the itemset + corresponding with this range */ +void SvxUnoTextRangeBase::attachField( std::unique_ptr<SvxFieldData> pData ) noexcept +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + SvxFieldItem aField( std::move(pData), EE_FEATURE_FIELD ); + pForwarder->QuickInsertField( std::move(aField), maSelection ); + } +} + +void SvxUnoTextRangeBase::SetSelection( const ESelection& rSelection ) noexcept +{ + SolarMutexGuard aGuard; + + maSelection = rSelection; + CheckSelection( maSelection, mpEditSource.get() ); +} + +// Interface XTextRange ( XText ) + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextRangeBase::getStart() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRange; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + SvxUnoTextBase* pText = comphelper::getFromUnoTunnel<SvxUnoTextBase>( getText() ); + + if(pText == nullptr) + throw uno::RuntimeException(); + + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *pText ); + xRange = pRange; + + ESelection aNewSel = maSelection; + aNewSel.nEndPara = aNewSel.nStartPara; + aNewSel.nEndPos = aNewSel.nStartPos; + pRange->SetSelection( aNewSel ); + } + + return xRange; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextRangeBase::getEnd() +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + SvxUnoTextBase* pText = comphelper::getFromUnoTunnel<SvxUnoTextBase>( getText() ); + + if(pText == nullptr) + throw uno::RuntimeException(); + + rtl::Reference<SvxUnoTextRange> pNew = new SvxUnoTextRange( *pText ); + xRet = pNew; + + ESelection aNewSel = maSelection; + aNewSel.nStartPara = aNewSel.nEndPara; + aNewSel.nStartPos = aNewSel.nEndPos; + pNew->SetSelection( aNewSel ); + } + return xRet; +} + +OUString SAL_CALL SvxUnoTextRangeBase::getString() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + return pForwarder->GetText( maSelection ); + } + else + { + return OUString(); + } +} + +void SAL_CALL SvxUnoTextRangeBase::setString(const OUString& aString) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + CheckSelection( maSelection, pForwarder ); + + OUString aConverted(convertLineEnd(aString, LINEEND_LF)); // Simply count the number of line endings + + pForwarder->QuickInsertText( aConverted, maSelection ); + mpEditSource->UpdateData(); + + // Adapt selection + //! It would be easier if the EditEngine would return the selection + //! on QuickInsertText... + CollapseToStart(); + + sal_Int32 nLen = aConverted.getLength(); + if (nLen) + GoRight( nLen, true ); +} + +// Interface beans::XPropertySet +uno::Reference< beans::XPropertySetInfo > SAL_CALL SvxUnoTextRangeBase::getPropertySetInfo() +{ + return mpPropSet->getPropertySetInfo(); +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertyValue(const OUString& PropertyName, const uno::Any& aValue) +{ + if (PropertyName == UNO_TR_PROP_SELECTION) + { + text::TextRangeSelection aSel = aValue.get<text::TextRangeSelection>(); + SetSelection(toESelection(aSel)); + + return; + } + + _setPropertyValue( PropertyName, aValue ); +} + +void SvxUnoTextRangeBase::_setPropertyValue( const OUString& PropertyName, const uno::Any& aValue, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + CheckSelection( maSelection, pForwarder ); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName ); + if ( pMap ) + { + ESelection aSel( GetSelection() ); + bool bParaAttrib = (pMap->nWID >= EE_PARA_START) && ( pMap->nWID <= EE_PARA_END ); + + if (pMap->nWID == WID_PARASTYLENAME) + { + OUString aStyle = aValue.get<OUString>(); + + sal_Int32 nEndPara; + + if( nPara == -1 ) + { + nPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + // only one paragraph + nEndPara = nPara; + } + + while( nPara <= nEndPara ) + { + pForwarder->SetStyleSheet(nPara, aStyle); + nPara++; + } + } + else if ( nPara == -1 && !bParaAttrib ) + { + SfxItemSet aOldSet( pForwarder->GetAttribs( aSel ) ); + // we have a selection and no para attribute + SfxItemSet aNewSet( *aOldSet.GetPool(), aOldSet.GetRanges() ); + + setPropertyValue( pMap, aValue, maSelection, aOldSet, aNewSet ); + + + pForwarder->QuickSetAttribs( aNewSet, GetSelection() ); + } + else + { + sal_Int32 nEndPara; + + if( nPara == -1 ) + { + nPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + else + { + // only one paragraph + nEndPara = nPara; + } + + while( nPara <= nEndPara ) + { + // we have a paragraph + SfxItemSet aSet( pForwarder->GetParaAttribs( nPara ) ); + setPropertyValue( pMap, aValue, maSelection, aSet, aSet ); + pForwarder->SetParaAttribs( nPara, aSet ); + nPara++; + } + } + + GetEditSource()->UpdateData(); + return; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::setPropertyValue( const SfxItemPropertyMapEntry* pMap, const uno::Any& rValue, const ESelection& rSelection, const SfxItemSet& rOldSet, SfxItemSet& rNewSet ) +{ + if(!SetPropertyValueHelper( pMap, rValue, rNewSet, &rSelection, GetEditSource() )) + { + // For parts of composite items with multiple properties (eg background) + // must be taken from the document before the old item. + rNewSet.Put(rOldSet.Get(pMap->nWID)); // Old Item in new Set + SvxItemPropertySet::setPropertyValue(pMap, rValue, rNewSet, false ); + } +} + +bool SvxUnoTextRangeBase::SetPropertyValueHelper( const SfxItemPropertyMapEntry* pMap, const uno::Any& aValue, SfxItemSet& rNewSet, const ESelection* pSelection /* = NULL */, SvxEditSource* pEditSource /* = NULL*/ ) +{ + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + awt::FontDescriptor aDesc; + if(aValue >>= aDesc) + { + SvxUnoFontDescriptor::FillItemSet( aDesc, rNewSet ); + return true; + } + } + break; + + case EE_PARA_NUMBULLET: + { + uno::Reference< container::XIndexReplace > xRule; + return !aValue.hasValue() || ((aValue >>= xRule) && !xRule.is()); + } + + case EE_PARA_OUTLLEVEL: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nLevel = sal_Int16(); + if( aValue >>= nLevel ) + { + // #101004# Call interface method instead of unsafe cast + if(! pForwarder->SetDepth( pSelection->nStartPara, nLevel ) ) + throw lang::IllegalArgumentException(); + + // If valid, then not yet finished. Also needs to be added to paragraph props. + return nLevel < -1 || nLevel > 9; + } + } + } + break; + case WID_NUMBERINGSTARTVALUE: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nStartValue = -1; + if( aValue >>= nStartValue ) + { + pForwarder->SetNumberingStartValue( pSelection->nStartPara, nStartValue ); + return true; + } + } + } + break; + case WID_PARAISNUMBERINGRESTART: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + bool bParaIsNumberingRestart = false; + if( aValue >>= bParaIsNumberingRestart ) + { + pForwarder->SetParaIsNumberingRestart( pSelection->nStartPara, bParaIsNumberingRestart ); + return true; + } + } + } + break; + case EE_PARA_BULLETSTATE: + { + bool bBullet = true; + if( aValue >>= bBullet ) + { + SfxBoolItem aItem( EE_PARA_BULLETSTATE, bBullet ); + rNewSet.Put(aItem); + return true; + } + } + break; + + default: + return false; + } + + throw lang::IllegalArgumentException(); +} + +uno::Any SAL_CALL SvxUnoTextRangeBase::getPropertyValue(const OUString& PropertyName) +{ + if (PropertyName == UNO_TR_PROP_SELECTION) + { + const ESelection& rSel = GetSelection(); + text::TextRangeSelection aSel; + aSel.Start.Paragraph = rSel.nStartPara; + aSel.Start.PositionInParagraph = rSel.nStartPos; + aSel.End.Paragraph = rSel.nEndPara; + aSel.End.PositionInParagraph = rSel.nEndPos; + return uno::Any(aSel); + } + + return _getPropertyValue( PropertyName ); +} + +uno::Any SvxUnoTextRangeBase::_getPropertyValue(const OUString& PropertyName, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + uno::Any aAny; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName ); + if( pMap ) + { + std::optional<SfxItemSet> oAttribs; + if( nPara != -1 ) + oAttribs.emplace(pForwarder->GetParaAttribs( nPara ).CloneAsValue()); + else + oAttribs.emplace(pForwarder->GetAttribs( GetSelection() ).CloneAsValue()); + + // Replace Dontcare with Default, so that one always has a mirror + oAttribs->ClearInvalidItems(); + + getPropertyValue( pMap, aAny, *oAttribs ); + + return aAny; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::getPropertyValue( const SfxItemPropertyMapEntry* pMap, uno::Any& rAny, const SfxItemSet& rSet ) +{ + switch( pMap->nWID ) + { + case EE_FEATURE_FIELD: + if ( rSet.GetItemState( EE_FEATURE_FIELD, false ) == SfxItemState::SET ) + { + const SvxFieldItem* pItem = rSet.GetItem<SvxFieldItem>( EE_FEATURE_FIELD ); + const SvxFieldData* pData = pItem->GetField(); + uno::Reference< text::XTextRange > xAnchor( this ); + + // get presentation string for field + std::optional<Color> pTColor; + std::optional<Color> pFColor; + std::optional<FontLineStyle> pFldLineStyle; + + SvxTextForwarder* pForwarder = mpEditSource->GetTextForwarder(); + OUString aPresentation( pForwarder->CalcFieldValue( SvxFieldItem(*pData, EE_FEATURE_FIELD), maSelection.nStartPara, maSelection.nStartPos, pTColor, pFColor, pFldLineStyle ) ); + + uno::Reference< text::XTextField > xField( new SvxUnoTextField( xAnchor, aPresentation, pData ) ); + rAny <<= xField; + } + break; + + case WID_PORTIONTYPE: + if ( rSet.GetItemState( EE_FEATURE_FIELD, false ) == SfxItemState::SET ) + { + rAny <<= OUString("TextField"); + } + else + { + rAny <<= OUString("Text"); + } + break; + + case WID_PARASTYLENAME: + { + rAny <<= GetEditSource()->GetTextForwarder()->GetStyleSheet(maSelection.nStartPara); + } + break; + + default: + if(!GetPropertyValueHelper( *const_cast<SfxItemSet*>(&rSet), pMap, rAny, &maSelection, GetEditSource() )) + rAny = SvxItemPropertySet::getPropertyValue(pMap, rSet, true, false ); + } +} + +bool SvxUnoTextRangeBase::GetPropertyValueHelper( SfxItemSet const & rSet, const SfxItemPropertyMapEntry* pMap, uno::Any& aAny, const ESelection* pSelection /* = NULL */, SvxEditSource* pEditSource /* = NULL */ ) +{ + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + awt::FontDescriptor aDesc; + SvxUnoFontDescriptor::FillFromItemSet( rSet, aDesc ); + aAny <<= aDesc; + } + break; + + case EE_PARA_NUMBULLET: + { + SfxItemState eState = rSet.GetItemState( EE_PARA_NUMBULLET ); + if( eState != SfxItemState::SET && eState != SfxItemState::DEFAULT) + throw uno::RuntimeException(); + + const SvxNumBulletItem* pBulletItem = rSet.GetItem( EE_PARA_NUMBULLET ); + + if( pBulletItem == nullptr ) + throw uno::RuntimeException(); + + aAny <<= SvxCreateNumRule( pBulletItem->GetNumRule() ); + } + break; + + case EE_PARA_OUTLLEVEL: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + { + sal_Int16 nLevel = pForwarder->GetDepth( pSelection->nStartPara ); + if( nLevel >= 0 ) + aAny <<= nLevel; + } + } + break; + case WID_NUMBERINGSTARTVALUE: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + aAny <<= pForwarder->GetNumberingStartValue( pSelection->nStartPara ); + } + break; + case WID_PARAISNUMBERINGRESTART: + { + SvxTextForwarder* pForwarder = pEditSource? pEditSource->GetTextForwarder() : nullptr; + if(pForwarder && pSelection) + aAny <<= pForwarder->IsParaIsNumberingRestart( pSelection->nStartPara ); + } + break; + + case EE_PARA_BULLETSTATE: + { + bool bState = false; + SfxItemState eState = rSet.GetItemState( EE_PARA_BULLETSTATE ); + if( eState == SfxItemState::SET || eState == SfxItemState::DEFAULT ) + { + const SfxBoolItem* pItem = rSet.GetItem<SfxBoolItem>( EE_PARA_BULLETSTATE ); + bState = pItem->GetValue(); + } + + aAny <<= bState; + } + break; + default: + + return false; + } + + return true; +} + +// is not (yet) supported +void SAL_CALL SvxUnoTextRangeBase::addPropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::removePropertyChangeListener( const OUString& , const uno::Reference< beans::XPropertyChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::addVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >& ) {} +void SAL_CALL SvxUnoTextRangeBase::removeVetoableChangeListener( const OUString& , const uno::Reference< beans::XVetoableChangeListener >& ) {} + +// XMultiPropertySet +void SAL_CALL SvxUnoTextRangeBase::setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) +{ + _setPropertyValues( aPropertyNames, aValues ); +} + +void SvxUnoTextRangeBase::_setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues, sal_Int32 nPara ) +{ + if (aPropertyNames.getLength() != aValues.getLength()) + throw lang::IllegalArgumentException("lengths do not match", + static_cast<css::beans::XPropertySet*>(this), -1); + + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + CheckSelection( maSelection, pForwarder ); + + ESelection aSel( GetSelection() ); + + const OUString* pPropertyNames = aPropertyNames.getConstArray(); + const uno::Any* pValues = aValues.getConstArray(); + sal_Int32 nCount = aPropertyNames.getLength(); + + sal_Int32 nEndPara = nPara; + sal_Int32 nTempPara = nPara; + + if( nTempPara == -1 ) + { + nTempPara = aSel.nStartPara; + nEndPara = aSel.nEndPara; + } + + std::optional<SfxItemSet> pOldAttrSet; + std::optional<SfxItemSet> pNewAttrSet; + + std::optional<SfxItemSet> pOldParaSet; + std::optional<SfxItemSet> pNewParaSet; + + std::optional<OUString> aStyleName; + + for( ; nCount; nCount--, pPropertyNames++, pValues++ ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( *pPropertyNames ); + + if( pMap ) + { + bool bParaAttrib = (pMap->nWID >= EE_PARA_START) && ( pMap->nWID <= EE_PARA_END ); + + if (pMap->nWID == WID_PARASTYLENAME) + { + aStyleName.emplace((*pValues).get<OUString>()); + } + else if( (nPara == -1) && !bParaAttrib ) + { + if( !pNewAttrSet ) + { + pOldAttrSet.emplace( pForwarder->GetAttribs( aSel ) ); + pNewAttrSet.emplace( *pOldAttrSet->GetPool(), pOldAttrSet->GetRanges() ); + } + + setPropertyValue( pMap, *pValues, GetSelection(), *pOldAttrSet, *pNewAttrSet ); + + if( pMap->nWID >= EE_ITEMS_START && pMap->nWID <= EE_ITEMS_END ) + { + const SfxPoolItem* pItem; + if( pNewAttrSet->GetItemState( pMap->nWID, true, &pItem ) == SfxItemState::SET ) + { + pOldAttrSet->Put( *pItem ); + } + } + } + else + { + if( !pNewParaSet ) + { + const SfxItemSet & rSet = pForwarder->GetParaAttribs( nTempPara ); + pOldParaSet.emplace( rSet ); + pNewParaSet.emplace( *pOldParaSet->GetPool(), pOldParaSet->GetRanges() ); + } + + setPropertyValue( pMap, *pValues, GetSelection(), *pOldParaSet, *pNewParaSet ); + + if( pMap->nWID >= EE_ITEMS_START && pMap->nWID <= EE_ITEMS_END ) + { + const SfxPoolItem* pItem; + if( pNewParaSet->GetItemState( pMap->nWID, true, &pItem ) == SfxItemState::SET ) + { + pOldParaSet->Put( *pItem ); + } + } + + } + } + } + + bool bNeedsUpdate = false; + + if( pNewParaSet || aStyleName ) + { + if( pNewParaSet->Count() ) + { + while( nTempPara <= nEndPara ) + { + SfxItemSet aSet( pForwarder->GetParaAttribs( nTempPara ) ); + aSet.Put( *pNewParaSet ); + pForwarder->SetParaAttribs( nTempPara, aSet ); + if (aStyleName) + pForwarder->SetStyleSheet(nTempPara, *aStyleName); + nTempPara++; + } + bNeedsUpdate = true; + } + + pNewParaSet.reset(); + pOldParaSet.reset(); + } + + if( pNewAttrSet ) + { + if( pNewAttrSet->Count() ) + { + pForwarder->QuickSetAttribs( *pNewAttrSet, GetSelection() ); + bNeedsUpdate = true; + } + pNewAttrSet.reset(); + pOldAttrSet.reset(); + } + + if( bNeedsUpdate ) + GetEditSource()->UpdateData(); +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextRangeBase::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames ) +{ + return _getPropertyValues( aPropertyNames ); +} + +uno::Sequence< uno::Any > SvxUnoTextRangeBase::_getPropertyValues( const uno::Sequence< OUString >& aPropertyNames, sal_Int32 nPara ) +{ + SolarMutexGuard aGuard; + + sal_Int32 nCount = aPropertyNames.getLength(); + + + uno::Sequence< uno::Any > aValues( nCount ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::optional<SfxItemSet> oAttribs; + if( nPara != -1 ) + oAttribs.emplace(pForwarder->GetParaAttribs( nPara ).CloneAsValue()); + else + oAttribs.emplace(pForwarder->GetAttribs( GetSelection() ).CloneAsValue() ); + + oAttribs->ClearInvalidItems(); + + const OUString* pPropertyNames = aPropertyNames.getConstArray(); + uno::Any* pValues = aValues.getArray(); + + for( ; nCount; nCount--, pPropertyNames++, pValues++ ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( *pPropertyNames ); + if( pMap ) + { + getPropertyValue( pMap, *pValues, *oAttribs ); + } + } + } + + return aValues; +} + +void SAL_CALL SvxUnoTextRangeBase::addPropertiesChangeListener( const uno::Sequence< OUString >& , const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +void SAL_CALL SvxUnoTextRangeBase::removePropertiesChangeListener( const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +void SAL_CALL SvxUnoTextRangeBase::firePropertiesChangeEvent( const uno::Sequence< OUString >& , const uno::Reference< beans::XPropertiesChangeListener >& ) +{ +} + +// beans::XPropertyState +beans::PropertyState SAL_CALL SvxUnoTextRangeBase::getPropertyState( const OUString& PropertyName ) +{ + return _getPropertyState( PropertyName ); +} + +const sal_uInt16 aSvxUnoFontDescriptorWhichMap[] = { EE_CHAR_FONTINFO, EE_CHAR_FONTHEIGHT, EE_CHAR_ITALIC, + EE_CHAR_UNDERLINE, EE_CHAR_WEIGHT, EE_CHAR_STRIKEOUT, EE_CHAR_CASEMAP, + EE_CHAR_WLM, 0 }; + +beans::PropertyState SvxUnoTextRangeBase::_getPropertyState(const SfxItemPropertyMapEntry* pMap, sal_Int32 nPara) +{ + if ( pMap ) + { + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + SfxItemState eItemState(SfxItemState::DEFAULT); + bool bItemStateSet(false); + + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + const sal_uInt16* pWhichId = aSvxUnoFontDescriptorWhichMap; + while( *pWhichId ) + { + const SfxItemState eTempItemState(nPara != -1 + ? pForwarder->GetItemState( nPara, *pWhichId ) + : pForwarder->GetItemState( GetSelection(), *pWhichId )); + + switch( eTempItemState ) + { + case SfxItemState::DISABLED: + case SfxItemState::DONTCARE: + eItemState = SfxItemState::DONTCARE; + bItemStateSet = true; + break; + + case SfxItemState::DEFAULT: + if( !bItemStateSet ) + { + eItemState = SfxItemState::DEFAULT; + bItemStateSet = true; + } + break; + + case SfxItemState::SET: + if( !bItemStateSet ) + { + eItemState = SfxItemState::SET; + bItemStateSet = true; + } + break; + default: + throw beans::UnknownPropertyException(); + } + + pWhichId++; + } + } + break; + + case WID_NUMBERINGSTARTVALUE: + case WID_PARAISNUMBERINGRESTART: + case WID_PARASTYLENAME: + eItemState = SfxItemState::SET; + bItemStateSet = true; + break; + + default: + if(0 != pMap->nWID) + { + if( nPara != -1 ) + eItemState = pForwarder->GetItemState( nPara, pMap->nWID ); + else + eItemState = pForwarder->GetItemState( GetSelection(), pMap->nWID ); + + bItemStateSet = true; + } + break; + } + + if(bItemStateSet) + { + switch( eItemState ) + { + case SfxItemState::DONTCARE: + case SfxItemState::DISABLED: + return beans::PropertyState_AMBIGUOUS_VALUE; + case SfxItemState::SET: + return beans::PropertyState_DIRECT_VALUE; + case SfxItemState::DEFAULT: + return beans::PropertyState_DEFAULT_VALUE; + default: break; + } + } + } + } + throw beans::UnknownPropertyException(); +} + +beans::PropertyState SvxUnoTextRangeBase::_getPropertyState(std::u16string_view PropertyName, sal_Int32 nPara /* = -1 */) +{ + SolarMutexGuard aGuard; + + return _getPropertyState( mpPropSet->getPropertyMapEntry( PropertyName ), nPara); +} + +uno::Sequence< beans::PropertyState > SAL_CALL SvxUnoTextRangeBase::getPropertyStates( const uno::Sequence< OUString >& aPropertyName ) +{ + return _getPropertyStates( aPropertyName ); +} + +uno::Sequence< beans::PropertyState > SvxUnoTextRangeBase::_getPropertyStates(const uno::Sequence< OUString >& PropertyName, sal_Int32 nPara /* = -1 */) +{ + uno::Sequence< beans::PropertyState > aRet( PropertyName.getLength() ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::optional<SfxItemSet> pSet; + if( nPara != -1 ) + { + pSet.emplace( pForwarder->GetParaAttribs( nPara ) ); + } + else + { + ESelection aSel( GetSelection() ); + CheckSelection( aSel, pForwarder ); + pSet.emplace( pForwarder->GetAttribs( aSel, EditEngineAttribs::OnlyHard ) ); + } + + beans::PropertyState* pState = aRet.getArray(); + for( const OUString& rName : PropertyName ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( rName ); + if( !_getOnePropertyStates(*pSet, pMap, *pState++) ) + { + throw beans::UnknownPropertyException(rName); + } + } + } + + return aRet; +} + +bool SvxUnoTextRangeBase::_getOnePropertyStates(const SfxItemSet& rSet, const SfxItemPropertyMapEntry* pMap, beans::PropertyState& rState) +{ + if (!pMap) + return true; + SfxItemState eItemState = SfxItemState::DEFAULT; + bool bItemStateSet(false); + + bool bUnknownPropertyFound = false; + switch( pMap->nWID ) + { + case WID_FONTDESC: + { + const sal_uInt16* pWhichId = aSvxUnoFontDescriptorWhichMap; + while( *pWhichId ) + { + const SfxItemState eTempItemState(rSet.GetItemState( *pWhichId )); + + switch( eTempItemState ) + { + case SfxItemState::DISABLED: + case SfxItemState::DONTCARE: + eItemState = SfxItemState::DONTCARE; + bItemStateSet = true; + break; + + case SfxItemState::DEFAULT: + if( !bItemStateSet ) + { + eItemState = SfxItemState::DEFAULT; + bItemStateSet = true; + } + break; + + case SfxItemState::SET: + if( !bItemStateSet ) + { + eItemState = SfxItemState::SET; + bItemStateSet = true; + } + break; + default: + bUnknownPropertyFound = true; + break; + } + + pWhichId++; + } + } + break; + + case WID_NUMBERINGSTARTVALUE: + case WID_PARAISNUMBERINGRESTART: + case WID_PARASTYLENAME: + eItemState = SfxItemState::SET; + bItemStateSet = true; + break; + + default: + if(0 != pMap->nWID) + { + eItemState = rSet.GetItemState( pMap->nWID, false ); + bItemStateSet = true; + } + break; + } + + if( bUnknownPropertyFound ) + return false; + + if(bItemStateSet) + { + if (pMap->nWID == EE_CHAR_COLOR) + { + // Theme & effects can be DEFAULT_VALUE, even if the same pool item has a color + // which is a DIRECT_VALUE. + const SvxColorItem* pColor = rSet.GetItem<SvxColorItem>(EE_CHAR_COLOR); + if (!pColor) + { + SAL_WARN("editeng", "Missing EE_CHAR_COLOR SvxColorItem"); + return false; + } + switch (pMap->nMemberId) + { + case MID_COLOR_THEME_INDEX: + if (!pColor->getComplexColor().isValidThemeType()) + { + eItemState = SfxItemState::DEFAULT; + } + break; + case MID_COLOR_LUM_MOD: + { + sal_Int16 nLumMod = 10000; + for (auto const& rTransform : pColor->getComplexColor().getTransformations()) + { + if (rTransform.meType == model::TransformationType::LumMod) + nLumMod = rTransform.mnValue; + } + if (nLumMod == 10000) + { + eItemState = SfxItemState::DEFAULT; + } + break; + } + case MID_COLOR_LUM_OFF: + { + sal_Int16 nLumOff = 0; + for (auto const& rTransform : pColor->getComplexColor().getTransformations()) + { + if (rTransform.meType == model::TransformationType::LumOff) + nLumOff = rTransform.mnValue; + } + if (nLumOff == 0) + { + eItemState = SfxItemState::DEFAULT; + } + break; + } + case MID_COMPLEX_COLOR: + if (pColor->getComplexColor().getType() == model::ColorType::Unused) + { + eItemState = SfxItemState::DEFAULT; + } + break; + } + } + + switch( eItemState ) + { + case SfxItemState::SET: + rState = beans::PropertyState_DIRECT_VALUE; + break; + case SfxItemState::DEFAULT: + rState = beans::PropertyState_DEFAULT_VALUE; + break; +// case SfxItemState::DONTCARE: +// case SfxItemState::DISABLED: + default: + rState = beans::PropertyState_AMBIGUOUS_VALUE; + } + } + else + { + rState = beans::PropertyState_AMBIGUOUS_VALUE; + } + return true; +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertyToDefault( const OUString& PropertyName ) +{ + _setPropertyToDefault( PropertyName ); +} + +void SvxUnoTextRangeBase::_setPropertyToDefault(const OUString& PropertyName, sal_Int32 nPara /* = -1 */) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( PropertyName ); + if ( pMap ) + { + CheckSelection( maSelection, mpEditSource->GetTextForwarder() ); + _setPropertyToDefault( pForwarder, pMap, nPara ); + return; + } + } + + throw beans::UnknownPropertyException(PropertyName); +} + +void SvxUnoTextRangeBase::_setPropertyToDefault(SvxTextForwarder* pForwarder, const SfxItemPropertyMapEntry* pMap, sal_Int32 nPara ) +{ + do + { + SfxItemSet aSet(*pForwarder->GetPool()); + + if( pMap->nWID == WID_FONTDESC ) + { + SvxUnoFontDescriptor::setPropertyToDefault( aSet ); + } + else if( pMap->nWID == WID_NUMBERINGSTARTVALUE ) + { + pForwarder->SetNumberingStartValue( maSelection.nStartPara, -1 ); + } + else if( pMap->nWID == WID_PARAISNUMBERINGRESTART ) + { + pForwarder->SetParaIsNumberingRestart( maSelection.nStartPara, false ); + } + else + { + aSet.InvalidateItem( pMap->nWID ); + } + + if(nPara != -1) + pForwarder->SetParaAttribs( nPara, aSet ); + else + pForwarder->QuickSetAttribs( aSet, GetSelection() ); + + GetEditSource()->UpdateData(); + + return; + } + while(false); +} + +uno::Any SAL_CALL SvxUnoTextRangeBase::getPropertyDefault( const OUString& aPropertyName ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( pForwarder ) + { + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry( aPropertyName ); + if( pMap ) + { + SfxItemPool* pPool = pForwarder->GetPool(); + + switch( pMap->nWID ) + { + case WID_FONTDESC: + return SvxUnoFontDescriptor::getPropertyDefault( pPool ); + + case EE_PARA_OUTLLEVEL: + { + uno::Any aAny; + return aAny; + } + + case WID_NUMBERINGSTARTVALUE: + return uno::Any( sal_Int16(-1) ); + + case WID_PARAISNUMBERINGRESTART: + return uno::Any( false ); + + default: + { + // Get Default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + { + SfxItemSet aSet( *pPool, pMap->nWID, pMap->nWID ); + aSet.Put(pPool->GetDefaultItem(pMap->nWID)); + return SvxItemPropertySet::getPropertyValue(pMap, aSet, true, false ); + } + } + } + } + } + throw beans::UnknownPropertyException(aPropertyName); +} + +// beans::XMultiPropertyStates +void SAL_CALL SvxUnoTextRangeBase::setAllPropertiesToDefault() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + + if( pForwarder ) + { + for (const SfxItemPropertyMapEntry* entry : mpPropSet->getPropertyMap().getPropertyEntries()) + { + _setPropertyToDefault( pForwarder, entry, -1 ); + } + } +} + +void SAL_CALL SvxUnoTextRangeBase::setPropertiesToDefault( const uno::Sequence< OUString >& aPropertyNames ) +{ + for( const OUString& rName : aPropertyNames ) + { + setPropertyToDefault( rName ); + } +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextRangeBase::getPropertyDefaults( const uno::Sequence< OUString >& aPropertyNames ) +{ + uno::Sequence< uno::Any > ret( aPropertyNames.getLength() ); + uno::Any* pDefaults = ret.getArray(); + + for( const OUString& rName : aPropertyNames ) + { + *pDefaults++ = getPropertyDefault( rName ); + } + + return ret; +} + +// internal +void SvxUnoTextRangeBase::CollapseToStart() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + maSelection.nEndPara = maSelection.nStartPara; + maSelection.nEndPos = maSelection.nStartPos; +} + +void SvxUnoTextRangeBase::CollapseToEnd() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + maSelection.nStartPara = maSelection.nEndPara; + maSelection.nStartPos = maSelection.nEndPos; +} + +bool SvxUnoTextRangeBase::IsCollapsed() noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + return ( maSelection.nStartPara == maSelection.nEndPara && + maSelection.nStartPos == maSelection.nEndPos ); +} + +bool SvxUnoTextRangeBase::GoLeft(sal_Int32 nCount, bool Expand) noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + // #75098# use end position, as in Writer (start is anchor, end is cursor) + sal_Int32 nNewPos = maSelection.nEndPos; + sal_Int32 nNewPar = maSelection.nEndPara; + + bool bOk = true; + SvxTextForwarder* pForwarder = nullptr; + while ( nCount > nNewPos && bOk ) + { + if ( nNewPar == 0 ) + bOk = false; + else + { + if ( !pForwarder ) + pForwarder = mpEditSource->GetTextForwarder(); // first here, it is necessary... + + --nNewPar; + nCount -= nNewPos + 1; + nNewPos = pForwarder->GetTextLen( nNewPar ); + } + } + + if ( bOk ) + { + nNewPos = nNewPos - nCount; + maSelection.nStartPara = nNewPar; + maSelection.nStartPos = nNewPos; + } + + if (!Expand) + CollapseToStart(); + + return bOk; +} + +bool SvxUnoTextRangeBase::GoRight(sal_Int32 nCount, bool Expand) noexcept +{ + if (!mpEditSource) + return false; + SvxTextForwarder* pForwarder = mpEditSource->GetTextForwarder(); + if( !pForwarder ) + return false; + + CheckSelection( maSelection, pForwarder ); + + sal_Int32 nNewPos = maSelection.nEndPos + nCount; + sal_Int32 nNewPar = maSelection.nEndPara; + + bool bOk = true; + sal_Int32 nParCount = pForwarder->GetParagraphCount(); + sal_Int32 nThisLen = pForwarder->GetTextLen( nNewPar ); + while ( nNewPos > nThisLen && bOk ) + { + if ( nNewPar + 1 >= nParCount ) + bOk = false; + else + { + nNewPos -= nThisLen+1; + ++nNewPar; + nThisLen = pForwarder->GetTextLen( nNewPar ); + } + } + + if (bOk) + { + maSelection.nEndPara = nNewPar; + maSelection.nEndPos = nNewPos; + } + + if (!Expand) + CollapseToEnd(); + + return bOk; +} + +void SvxUnoTextRangeBase::GotoStart(bool Expand) noexcept +{ + maSelection.nStartPara = 0; + maSelection.nStartPos = 0; + + if (!Expand) + CollapseToStart(); +} + +void SvxUnoTextRangeBase::GotoEnd(bool Expand) noexcept +{ + CheckSelection( maSelection, mpEditSource.get() ); + + SvxTextForwarder* pForwarder = mpEditSource ? mpEditSource->GetTextForwarder() : nullptr; + if( !pForwarder ) + return; + + sal_Int32 nPar = pForwarder->GetParagraphCount(); + if (nPar) + --nPar; + + maSelection.nEndPara = nPar; + maSelection.nEndPos = pForwarder->GetTextLen( nPar ); + + if (!Expand) + CollapseToEnd(); +} + +// lang::XServiceInfo +sal_Bool SAL_CALL SvxUnoTextRangeBase::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextRangeBase::getSupportedServiceNames() +{ + return getSupportedServiceNames_Static(); +} + +uno::Sequence< OUString > SvxUnoTextRangeBase::getSupportedServiceNames_Static() +{ + return { "com.sun.star.style.CharacterProperties", + "com.sun.star.style.CharacterPropertiesComplex", + "com.sun.star.style.CharacterPropertiesAsian" }; +} + +// XTextRangeCompare +sal_Int16 SAL_CALL SvxUnoTextRangeBase::compareRegionStarts( const uno::Reference< text::XTextRange >& xR1, const uno::Reference< text::XTextRange >& xR2 ) +{ + SvxUnoTextRangeBase* pR1 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR1 ); + SvxUnoTextRangeBase* pR2 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR2 ); + + if( (pR1 == nullptr) || (pR2 == nullptr) ) + throw lang::IllegalArgumentException(); + + const ESelection& r1 = pR1->maSelection; + const ESelection& r2 = pR2->maSelection; + + if( r1.nStartPara == r2.nStartPara ) + { + if( r1.nStartPos == r2.nStartPos ) + return 0; + else + return r1.nStartPos < r2.nStartPos ? 1 : -1; + } + else + { + return r1.nStartPara < r2.nStartPara ? 1 : -1; + } +} + +sal_Int16 SAL_CALL SvxUnoTextRangeBase::compareRegionEnds( const uno::Reference< text::XTextRange >& xR1, const uno::Reference< text::XTextRange >& xR2 ) +{ + SvxUnoTextRangeBase* pR1 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR1 ); + SvxUnoTextRangeBase* pR2 = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xR2 ); + + if( (pR1 == nullptr) || (pR2 == nullptr) ) + throw lang::IllegalArgumentException(); + + const ESelection& r1 = pR1->maSelection; + const ESelection& r2 = pR2->maSelection; + + if( r1.nEndPara == r2.nEndPara ) + { + if( r1.nEndPos == r2.nEndPos ) + return 0; + else + return r1.nEndPos < r2.nEndPos ? 1 : -1; + } + else + { + return r1.nEndPara < r2.nEndPara ? 1 : -1; + } +} + +SvxUnoTextRange::SvxUnoTextRange(const SvxUnoTextBase& rParent, bool bPortion /* = false */) +:SvxUnoTextRangeBase( rParent.GetEditSource(), bPortion ? ImplGetSvxTextPortionSvxPropertySet() : rParent.getPropertySet() ), + mbPortion( bPortion ) +{ + xParentText = static_cast<text::XText*>(const_cast<SvxUnoTextBase *>(&rParent)); +} + +SvxUnoTextRange::~SvxUnoTextRange() noexcept +{ +} + +uno::Any SAL_CALL SvxUnoTextRange::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XTextRange ); + else if( rType == cppu::UnoType<beans::XMultiPropertyStates>::get()) + return uno::Any(uno::Reference< beans::XMultiPropertyStates >(this)); + else if( rType == cppu::UnoType<beans::XPropertySet>::get()) + return uno::Any(uno::Reference< beans::XPropertySet >(this)); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextRangeCompare ); + else if( rType == cppu::UnoType<beans::XMultiPropertySet>::get()) + return uno::Any(uno::Reference< beans::XMultiPropertySet >(this)); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextRange::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextRange::acquire() + noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextRange::release() + noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextRange::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get(), + cppu::UnoType<text::XTextRangeCompare>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextRange::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XTextRange +uno::Reference< text::XText > SAL_CALL SvxUnoTextRange::getText() +{ + return xParentText; +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextRange::getImplementationName() +{ + return "SvxUnoTextRange"; +} + + + + +SvxUnoTextBase::SvxUnoTextBase(const SvxItemPropertySet* _pSet) + : SvxUnoTextRangeBase(_pSet) +{ +} + +SvxUnoTextBase::SvxUnoTextBase(const SvxEditSource* pSource, const SvxItemPropertySet* _pSet, uno::Reference < text::XText > const & xParent) + : SvxUnoTextRangeBase(pSource, _pSet) +{ + xParentText = xParent; + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); +} + +SvxUnoTextBase::SvxUnoTextBase(const SvxUnoTextBase& rText) +: SvxUnoTextRangeBase( rText ) +, text::XTextAppend() +, text::XTextCopy() +, container::XEnumerationAccess() +, text::XTextRangeMover() +, lang::XTypeProvider() +{ + xParentText = rText.xParentText; +} + +SvxUnoTextBase::~SvxUnoTextBase() noexcept +{ +} + +// XInterface +uno::Any SAL_CALL SvxUnoTextBase::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XText ); + QUERYINT( text::XSimpleText ); + if( rType == cppu::UnoType<text::XTextRange>::get()) + return uno::Any(uno::Reference< text::XTextRange >(static_cast<text::XText*>(this))); + QUERYINT(container::XEnumerationAccess ); + QUERYINT( container::XElementAccess ); + QUERYINT( beans::XMultiPropertyStates ); + QUERYINT( beans::XPropertySet ); + QUERYINT( beans::XMultiPropertySet ); + QUERYINT( beans::XPropertyState ); + QUERYINT( text::XTextRangeCompare ); + QUERYINT( lang::XServiceInfo ); + QUERYINT( text::XTextRangeMover ); + QUERYINT( text::XTextCopy ); + QUERYINT( text::XTextAppend ); + QUERYINT( text::XParagraphAppend ); + QUERYINT( text::XTextPortionAppend ); + QUERYINT( lang::XTypeProvider ); + QUERYINT( lang::XUnoTunnel ); + + return uno::Any(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextBase::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XText>::get(), + cppu::UnoType<container::XEnumerationAccess>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeMover>::get(), + cppu::UnoType<text::XTextAppend>::get(), + cppu::UnoType<text::XTextCopy>::get(), + cppu::UnoType<text::XParagraphAppend>::get(), + cppu::UnoType<text::XTextPortionAppend>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get(), + cppu::UnoType<text::XTextRangeCompare>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextBase::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +uno::Reference< text::XTextCursor > SvxUnoTextBase::createTextCursorBySelection( const ESelection& rSel ) +{ + rtl::Reference<SvxUnoTextCursor> pCursor = new SvxUnoTextCursor( *this ); + pCursor->SetSelection( rSel ); + return pCursor; +} + +// XSimpleText + +uno::Reference< text::XTextCursor > SAL_CALL SvxUnoTextBase::createTextCursor() +{ + SolarMutexGuard aGuard; + return new SvxUnoTextCursor( *this ); +} + +uno::Reference< text::XTextCursor > SAL_CALL SvxUnoTextBase::createTextCursorByRange( const uno::Reference< text::XTextRange >& aTextPosition ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextCursor > xCursor; + + if( aTextPosition.is() ) + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( aTextPosition ); + if(pRange) + xCursor = createTextCursorBySelection( pRange->GetSelection() ); + } + + return xCursor; +} + +void SAL_CALL SvxUnoTextBase::insertString( const uno::Reference< text::XTextRange >& xRange, const OUString& aString, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + if( !xRange.is() ) + return; + + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(!pRange) + return; + + // setString on SvxUnoTextRangeBase instead of itself QuickInsertText + // and UpdateData, so that the selection will be adjusted to + // SvxUnoTextRangeBase. Actually all cursor objects of this Text must + // to be statement to be adapted! + + if (!bAbsorb) // do not replace -> append on tail + pRange->CollapseToEnd(); + + pRange->setString( aString ); + + pRange->CollapseToEnd(); + + if (GetEditSource()) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); + } +} + +void SAL_CALL SvxUnoTextBase::insertControlCharacter( const uno::Reference< text::XTextRange >& xRange, sal_Int16 nControlCharacter, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + + if( !pForwarder ) + return; + + ESelection aSelection; + ::GetSelection( aSelection, pForwarder ); + SetSelection( aSelection ); + + switch( nControlCharacter ) + { + case text::ControlCharacter::PARAGRAPH_BREAK: + { + insertString( xRange, "\x0D", bAbsorb ); + + return; + } + case text::ControlCharacter::LINE_BREAK: + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(pRange) + { + ESelection aRange = pRange->GetSelection(); + + if( bAbsorb ) + { + pForwarder->QuickInsertText( "", aRange ); + + aRange.nEndPos = aRange.nStartPos; + aRange.nEndPara = aRange.nStartPara; + } + else + { + aRange.nStartPara = aRange.nEndPara; + aRange.nStartPos = aRange.nEndPos; + } + + pForwarder->QuickInsertLineBreak( aRange ); + GetEditSource()->UpdateData(); + + aRange.nEndPos += 1; + if( !bAbsorb ) + aRange.nStartPos += 1; + + pRange->SetSelection( aRange ); + } + return; + } + case text::ControlCharacter::APPEND_PARAGRAPH: + { + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>( xRange ); + if(pRange) + { + ESelection aRange = pRange->GetSelection(); +// ESelection aOldSelection = aRange; + + aRange.nStartPos = pForwarder->GetTextLen( aRange.nStartPara ); + + aRange.nEndPara = aRange.nStartPara; + aRange.nEndPos = aRange.nStartPos; + + pRange->SetSelection( aRange ); + static constexpr OUStringLiteral CR = u"\x0D"; + pRange->setString( CR ); + + aRange.nStartPos = 0; + aRange.nStartPara += 1; + aRange.nEndPos = 0; + aRange.nEndPara += 1; + + pRange->SetSelection( aRange ); + + return; + } + [[fallthrough]]; + } + default: + throw lang::IllegalArgumentException(); + } +} + +// XText +void SAL_CALL SvxUnoTextBase::insertTextContent( const uno::Reference< text::XTextRange >& xRange, const uno::Reference< text::XTextContent >& xContent, sal_Bool bAbsorb ) +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + if (!pForwarder) + return; + + uno::Reference<beans::XPropertySet> xPropSet(xRange, uno::UNO_QUERY); + if (!xPropSet.is()) + throw lang::IllegalArgumentException(); + + uno::Any aAny = xPropSet->getPropertyValue(UNO_TR_PROP_SELECTION); + text::TextRangeSelection aSel = aAny.get<text::TextRangeSelection>(); + if (!bAbsorb) + aSel.Start = aSel.End; + + std::unique_ptr<SvxFieldData> pFieldData(SvxFieldData::Create(xContent)); + if (!pFieldData) + throw lang::IllegalArgumentException(); + + SvxFieldItem aField( *pFieldData, EE_FEATURE_FIELD ); + pForwarder->QuickInsertField(aField, toESelection(aSel)); + GetEditSource()->UpdateData(); + + uno::Reference<beans::XPropertySet> xPropSetContent(xContent, uno::UNO_QUERY); + if (!xPropSetContent.is()) + throw lang::IllegalArgumentException(); + + xPropSetContent->setPropertyValue(UNO_TC_PROP_ANCHOR, uno::Any(xRange)); + + aSel.End.PositionInParagraph += 1; + aSel.Start.PositionInParagraph = aSel.End.PositionInParagraph; + xPropSet->setPropertyValue(UNO_TR_PROP_SELECTION, uno::Any(aSel)); +} + +void SAL_CALL SvxUnoTextBase::removeTextContent( const uno::Reference< text::XTextContent >& ) +{ +} + +// XTextRange + +uno::Reference< text::XText > SAL_CALL SvxUnoTextBase::getText() +{ + SolarMutexGuard aGuard; + + if (GetEditSource()) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + SetSelection( aSelection ); + } + + return static_cast<text::XText*>(this); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::getStart() +{ + return SvxUnoTextRangeBase::getStart(); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::getEnd() +{ + return SvxUnoTextRangeBase::getEnd(); +} + +OUString SAL_CALL SvxUnoTextBase::getString() +{ + return SvxUnoTextRangeBase::getString(); +} + +void SAL_CALL SvxUnoTextBase::setString( const OUString& aString ) +{ + SvxUnoTextRangeBase::setString(aString); +} + + +// XEnumerationAccess +uno::Reference< container::XEnumeration > SAL_CALL SvxUnoTextBase::createEnumeration() +{ + SolarMutexGuard aGuard; + + if (!GetEditSource()) + return uno::Reference< container::XEnumeration >(); + + if( maSelection == ESelection(0,0,0,0) || maSelection == ESelection(EE_PARA_MAX_COUNT,0,0,0) ) + { + ESelection aSelection; + ::GetSelection( aSelection, GetEditSource()->GetTextForwarder() ); + return new SvxUnoTextContentEnumeration(*this, aSelection); + } + else + { + return new SvxUnoTextContentEnumeration(*this, maSelection); + } +} + +// XElementAccess ( container::XEnumerationAccess ) +uno::Type SAL_CALL SvxUnoTextBase::getElementType( ) +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SvxUnoTextBase::hasElements( ) +{ + SolarMutexGuard aGuard; + + if(GetEditSource()) + { + SvxTextForwarder* pForwarder = GetEditSource()->GetTextForwarder(); + if(pForwarder) + return pForwarder->GetParagraphCount() != 0; + } + + return false; +} + +// text::XTextRangeMover +void SAL_CALL SvxUnoTextBase::moveTextRange( const uno::Reference< text::XTextRange >&, sal_Int16 ) +{ +} + +/// @throws lang::IllegalArgumentException +/// @throws beans::UnknownPropertyException +/// @throws uno::RuntimeException +static void SvxPropertyValuesToItemSet( + SfxItemSet &rItemSet, + const uno::Sequence< beans::PropertyValue >& rPropertyValues, + const SfxItemPropertySet *pPropSet, + SvxTextForwarder *pForwarder, + sal_Int32 nPara) +{ + for (const beans::PropertyValue& rProp : rPropertyValues) + { + const SfxItemPropertyMapEntry *pEntry = pPropSet->getPropertyMap().getByName( rProp.Name ); + if (!pEntry) + throw beans::UnknownPropertyException( "Unknown property: " + rProp.Name ); + // Note: there is no need to take special care of the properties + // TextField (EE_FEATURE_FIELD) and + // TextPortionType (WID_PORTIONTYPE) + // since they are read-only and thus are already taken care of below. + + if (pEntry->nFlags & beans::PropertyAttribute::READONLY) + // should be PropertyVetoException which is not yet defined for the new import API's functions + throw uno::RuntimeException("Property is read-only: " + rProp.Name ); + //throw PropertyVetoException ("Property is read-only: " + rProp.Name ); + + if (pEntry->nWID == WID_FONTDESC) + { + awt::FontDescriptor aDesc; + if (rProp.Value >>= aDesc) + SvxUnoFontDescriptor::FillItemSet( aDesc, rItemSet ); + } + else if (pEntry->nWID == WID_NUMBERINGSTARTVALUE ) + { + if( pForwarder ) + { + sal_Int16 nStartValue = -1; + if( !(rProp.Value >>= nStartValue) ) + throw lang::IllegalArgumentException(); + + pForwarder->SetNumberingStartValue( nPara, nStartValue ); + } + } + else if (pEntry->nWID == WID_PARAISNUMBERINGRESTART ) + { + if( pForwarder ) + { + bool bParaIsNumberingRestart = false; + if( !(rProp.Value >>= bParaIsNumberingRestart) ) + throw lang::IllegalArgumentException(); + + pForwarder->SetParaIsNumberingRestart( nPara, bParaIsNumberingRestart ); + } + } + else + pPropSet->setPropertyValue( rProp.Name, rProp.Value, rItemSet ); + } +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::finishParagraphInsert( + const uno::Sequence< beans::PropertyValue >& /*rCharAndParaProps*/, + const uno::Reference< text::XTextRange >& /*rTextRange*/ ) +{ + uno::Reference< text::XTextRange > xRet; + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::finishParagraph( + const uno::Sequence< beans::PropertyValue >& rCharAndParaProps ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + if (pTextForwarder) + { + sal_Int32 nParaCount = pTextForwarder->GetParagraphCount(); + DBG_ASSERT( nParaCount > 0, "paragraph count is 0 or negative" ); + pTextForwarder->AppendParagraph(); + + // set properties for the previously last paragraph + sal_Int32 nPara = nParaCount - 1; + ESelection aSel( nPara, 0, nPara, 0 ); + SfxItemSet aItemSet( *pTextForwarder->GetEmptyItemSetPtr() ); + SvxPropertyValuesToItemSet( aItemSet, rCharAndParaProps, + ImplGetSvxUnoOutlinerTextCursorSfxPropertySet(), pTextForwarder, nPara ); + pTextForwarder->QuickSetAttribs( aItemSet, aSel ); + pEditSource->UpdateData(); + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *this ); + xRet = pRange; + pRange->SetSelection( aSel ); + } + return xRet; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::insertTextPortion( + const OUString& rText, + const uno::Sequence< beans::PropertyValue >& rCharAndParaProps, + const uno::Reference< text::XTextRange>& rTextRange ) +{ + SolarMutexGuard aGuard; + + uno::Reference< text::XTextRange > xRet; + + if (!rTextRange.is()) + return xRet; + + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRange>(rTextRange); + if (!pRange) + return xRet; + + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + + if (pTextForwarder) + { + pRange->setString(rText); + + ESelection aSelection(pRange->GetSelection()); + + pTextForwarder->RemoveAttribs(aSelection); + pEditSource->UpdateData(); + + SfxItemSet aItemSet( *pTextForwarder->GetEmptyItemSetPtr() ); + SvxPropertyValuesToItemSet( aItemSet, rCharAndParaProps, + ImplGetSvxTextPortionSfxPropertySet(), pTextForwarder, aSelection.nStartPara ); + pTextForwarder->QuickSetAttribs( aItemSet, aSelection); + rtl::Reference<SvxUnoTextRange> pNewRange = new SvxUnoTextRange( *this ); + xRet = pNewRange; + pNewRange->SetSelection(aSelection); + for( const beans::PropertyValue& rProp : rCharAndParaProps ) + pNewRange->setPropertyValue( rProp.Name, rProp.Value ); + } + return xRet; +} + +// css::text::XTextPortionAppend (new import API) +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextBase::appendTextPortion( + const OUString& rText, + const uno::Sequence< beans::PropertyValue >& rCharAndParaProps ) +{ + SolarMutexGuard aGuard; + + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + uno::Reference< text::XTextRange > xRet; + if (pTextForwarder) + { + sal_Int32 nParaCount = pTextForwarder->GetParagraphCount(); + DBG_ASSERT( nParaCount > 0, "paragraph count is 0 or negative" ); + sal_Int32 nPara = nParaCount - 1; + SfxItemSet aSet( pTextForwarder->GetParaAttribs( nPara ) ); + sal_Int32 nStart = pTextForwarder->AppendTextPortion( nPara, rText, aSet ); + pEditSource->UpdateData(); + sal_Int32 nEnd = pTextForwarder->GetTextLen( nPara ); + + // set properties for the new text portion + ESelection aSel( nPara, nStart, nPara, nEnd ); + pTextForwarder->RemoveAttribs( aSel ); + pEditSource->UpdateData(); + + SfxItemSet aItemSet( *pTextForwarder->GetEmptyItemSetPtr() ); + SvxPropertyValuesToItemSet( aItemSet, rCharAndParaProps, + ImplGetSvxTextPortionSfxPropertySet(), pTextForwarder, nPara ); + pTextForwarder->QuickSetAttribs( aItemSet, aSel ); + rtl::Reference<SvxUnoTextRange> pRange = new SvxUnoTextRange( *this ); + xRet = pRange; + pRange->SetSelection( aSel ); + for( const beans::PropertyValue& rProp : rCharAndParaProps ) + pRange->setPropertyValue( rProp.Name, rProp.Value ); + } + return xRet; +} + +void SvxUnoTextBase::copyText( + const uno::Reference< text::XTextCopy >& xSource ) +{ + SolarMutexGuard aGuard; + uno::Reference< lang::XUnoTunnel > xUT( xSource, uno::UNO_QUERY ); + SvxEditSource *pEditSource = GetEditSource(); + SvxTextForwarder *pTextForwarder = pEditSource ? pEditSource->GetTextForwarder() : nullptr; + if( !pTextForwarder ) + return; + if (auto pSource = comphelper::getFromUnoTunnel<SvxUnoTextBase>(xUT)) + { + SvxEditSource *pSourceEditSource = pSource->GetEditSource(); + SvxTextForwarder *pSourceTextForwarder = pSourceEditSource ? pSourceEditSource->GetTextForwarder() : nullptr; + if( pSourceTextForwarder ) + { + pTextForwarder->CopyText( *pSourceTextForwarder ); + pEditSource->UpdateData(); + } + } + else + { + uno::Reference< text::XText > xSourceText( xSource, uno::UNO_QUERY ); + if( xSourceText.is() ) + { + setString( xSourceText->getString() ); + } + } +} + +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextBase::getImplementationName() +{ + return "SvxUnoTextBase"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextBase::getSupportedServiceNames( ) +{ + return getSupportedServiceNames_Static(); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextBase::getSupportedServiceNames_Static( ) +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames_Static(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.text.Text" }); +} + +const uno::Sequence< sal_Int8 > & SvxUnoTextBase::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSvxUnoTextBaseUnoTunnelId; + return theSvxUnoTextBaseUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SvxUnoTextBase::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl( + rId, this, comphelper::FallbackToGetSomethingOf<SvxUnoTextRangeBase>{}); +} + +SvxUnoText::SvxUnoText( const SvxItemPropertySet* _pSet ) noexcept +: SvxUnoTextBase( _pSet ) +{ +} + +SvxUnoText::SvxUnoText( const SvxEditSource* pSource, const SvxItemPropertySet* _pSet, uno::Reference < text::XText > const & xParent ) noexcept +: SvxUnoTextBase( pSource, _pSet, xParent ) +{ +} + +SvxUnoText::SvxUnoText( const SvxUnoText& rText ) noexcept +: SvxUnoTextBase( rText ) +, cppu::OWeakAggObject() +{ +} + +SvxUnoText::~SvxUnoText() noexcept +{ +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoText::queryAggregation( const uno::Type & rType ) +{ + uno::Any aAny( SvxUnoTextBase::queryAggregation( rType ) ); + if( !aAny.hasValue() ) + aAny = OWeakAggObject::queryAggregation( rType ); + + return aAny; +} + +uno::Any SAL_CALL SvxUnoText::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface( rType ); +} + +void SAL_CALL SvxUnoText::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoText::release() noexcept +{ + OWeakAggObject::release(); +} + +// lang::XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxUnoText::getTypes( ) +{ + return SvxUnoTextBase::getTypes(); +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoText::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +const uno::Sequence< sal_Int8 > & SvxUnoText::getUnoTunnelId() noexcept +{ + static const comphelper::UnoIdInit theSvxUnoTextUnoTunnelId; + return theSvxUnoTextUnoTunnelId.getSeq(); +} + +sal_Int64 SAL_CALL SvxUnoText::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + return comphelper::getSomethingImpl(rId, this, + comphelper::FallbackToGetSomethingOf<SvxUnoTextBase>{}); +} + + +SvxDummyTextSource::~SvxDummyTextSource() +{ +}; + +std::unique_ptr<SvxEditSource> SvxDummyTextSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SvxDummyTextSource); +} + +SvxTextForwarder* SvxDummyTextSource::GetTextForwarder() +{ + return this; +} + +void SvxDummyTextSource::UpdateData() +{ +} + +sal_Int32 SvxDummyTextSource::GetParagraphCount() const +{ + return 0; +} + +sal_Int32 SvxDummyTextSource::GetTextLen( sal_Int32 ) const +{ + return 0; +} + +OUString SvxDummyTextSource::GetText( const ESelection& ) const +{ + return OUString(); +} + +SfxItemSet SvxDummyTextSource::GetAttribs( const ESelection&, EditEngineAttribs ) const +{ + // Very dangerous: The former implementation used a SfxItemPool created on the + // fly which of course was deleted again ASAP. Thus, the returned SfxItemSet was using + // a deleted Pool by design. + return SfxItemSet(EditEngine::GetGlobalItemPool()); +} + +SfxItemSet SvxDummyTextSource::GetParaAttribs( sal_Int32 ) const +{ + return GetAttribs(ESelection()); +} + +void SvxDummyTextSource::SetParaAttribs( sal_Int32, const SfxItemSet& ) +{ +} + +void SvxDummyTextSource::RemoveAttribs( const ESelection& ) +{ +} + +void SvxDummyTextSource::GetPortions( sal_Int32, std::vector<sal_Int32>& ) const +{ +} + +OUString SvxDummyTextSource::GetStyleSheet(sal_Int32) const +{ + return OUString(); +} + +void SvxDummyTextSource::SetStyleSheet(sal_Int32, const OUString&) +{ +} + +SfxItemState SvxDummyTextSource::GetItemState( const ESelection&, sal_uInt16 ) const +{ + return SfxItemState::UNKNOWN; +} + +SfxItemState SvxDummyTextSource::GetItemState( sal_Int32, sal_uInt16 ) const +{ + return SfxItemState::UNKNOWN; +} + +SfxItemPool* SvxDummyTextSource::GetPool() const +{ + return nullptr; +} + +void SvxDummyTextSource::QuickInsertText( const OUString&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickInsertField( const SvxFieldItem&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickSetAttribs( const SfxItemSet&, const ESelection& ) +{ +} + +void SvxDummyTextSource::QuickInsertLineBreak( const ESelection& ) +{ +}; + +OUString SvxDummyTextSource::CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>&, std::optional<FontLineStyle>& ) +{ + return OUString(); +} + +void SvxDummyTextSource::FieldClicked( const SvxFieldItem& ) +{ +} + +bool SvxDummyTextSource::IsValid() const +{ + return false; +} + +LanguageType SvxDummyTextSource::GetLanguage( sal_Int32, sal_Int32 ) const +{ + return LANGUAGE_DONTKNOW; +} + +sal_Int32 SvxDummyTextSource::GetFieldCount( sal_Int32 ) const +{ + return 0; +} + +EFieldInfo SvxDummyTextSource::GetFieldInfo( sal_Int32, sal_uInt16 ) const +{ + return EFieldInfo(); +} + +EBulletInfo SvxDummyTextSource::GetBulletInfo( sal_Int32 ) const +{ + return EBulletInfo(); +} + +tools::Rectangle SvxDummyTextSource::GetCharBounds( sal_Int32, sal_Int32 ) const +{ + return tools::Rectangle(); +} + +tools::Rectangle SvxDummyTextSource::GetParaBounds( sal_Int32 ) const +{ + return tools::Rectangle(); +} + +MapMode SvxDummyTextSource::GetMapMode() const +{ + return MapMode(); +} + +OutputDevice* SvxDummyTextSource::GetRefDevice() const +{ + return nullptr; +} + +bool SvxDummyTextSource::GetIndexAtPoint( const Point&, sal_Int32&, sal_Int32& ) const +{ + return false; +} + +bool SvxDummyTextSource::GetWordIndices( sal_Int32, sal_Int32, sal_Int32&, sal_Int32& ) const +{ + return false; +} + +bool SvxDummyTextSource::GetAttributeRun( sal_Int32&, sal_Int32&, sal_Int32, sal_Int32, bool ) const +{ + return false; +} + +sal_Int32 SvxDummyTextSource::GetLineCount( sal_Int32 ) const +{ + return 0; +} + +sal_Int32 SvxDummyTextSource::GetLineLen( sal_Int32, sal_Int32 ) const +{ + return 0; +} + +void SvxDummyTextSource::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 /*nParagraph*/, sal_Int32 /*nLine*/ ) const +{ + rStart = rEnd = 0; +} + +sal_Int32 SvxDummyTextSource::GetLineNumberAtIndex( sal_Int32 /*nPara*/, sal_Int32 /*nIndex*/ ) const +{ + return 0; +} + +bool SvxDummyTextSource::QuickFormatDoc( bool ) +{ + return false; +} + +sal_Int16 SvxDummyTextSource::GetDepth( sal_Int32 ) const +{ + return -1; +} + +bool SvxDummyTextSource::SetDepth( sal_Int32, sal_Int16 nNewDepth ) +{ + return nNewDepth == 0; +} + +bool SvxDummyTextSource::Delete( const ESelection& ) +{ + return false; +} + +bool SvxDummyTextSource::InsertText( const OUString&, const ESelection& ) +{ + return false; +} + +const SfxItemSet * SvxDummyTextSource::GetEmptyItemSetPtr() +{ + return nullptr; +} + +void SvxDummyTextSource::AppendParagraph() +{ +} + +sal_Int32 SvxDummyTextSource::AppendTextPortion( sal_Int32, const OUString &, const SfxItemSet & ) +{ + return 0; +} + +void SvxDummyTextSource::CopyText(const SvxTextForwarder& ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unotext2.cxx b/editeng/source/uno/unotext2.cxx new file mode 100644 index 0000000000..54714027b3 --- /dev/null +++ b/editeng/source/uno/unotext2.cxx @@ -0,0 +1,629 @@ +/* -*- 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 <initializer_list> +#include <string_view> + +#include <o3tl/safeint.hxx> +#include <vcl/svapp.hxx> + +#include <editeng/unotext.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::cppu; +using namespace ::com::sun::star; + +#define QUERYINT( xint ) \ + if( rType == cppu::UnoType<xint>::get() ) \ + return uno::Any(uno::Reference< xint >(this)) + + +// SvxUnoTextContentEnumeration + + +SvxUnoTextContentEnumeration::SvxUnoTextContentEnumeration( const SvxUnoTextBase& rText, const ESelection& rSel ) noexcept +{ + mxParentText = const_cast<SvxUnoTextBase*>(&rText); + if( rText.GetEditSource() ) + mpEditSource = rText.GetEditSource()->Clone(); + mnNextParagraph = 0; + + if (!mpEditSource) + return; + + const SvxTextForwarder* pTextForwarder = rText.GetEditSource()->GetTextForwarder(); + const sal_Int32 maxParaIndex = std::min( rSel.nEndPara + 1, pTextForwarder->GetParagraphCount() ); + + for( sal_Int32 currentPara = rSel.nStartPara; currentPara < maxParaIndex; currentPara++ ) + { + const SvxUnoTextRangeBaseVec& rRanges( mpEditSource->getRanges() ); + rtl::Reference<SvxUnoTextContent> pContent; + sal_Int32 nStartPos = 0; + sal_Int32 nEndPos = pTextForwarder->GetTextLen( currentPara ); + if( currentPara == rSel.nStartPara ) + nStartPos = std::max(nStartPos, rSel.nStartPos); + if( currentPara == rSel.nEndPara ) + nEndPos = std::min(nEndPos, rSel.nEndPos); + ESelection aCurrentParaSel( currentPara, nStartPos, currentPara, nEndPos ); + for (auto const& elemRange : rRanges) + { + if (pContent) + break; + SvxUnoTextContent* pIterContent = dynamic_cast< SvxUnoTextContent* >( elemRange ); + if( pIterContent && (pIterContent->mnParagraph == currentPara) ) + { + ESelection aIterSel = pIterContent->GetSelection(); + if( aIterSel == aCurrentParaSel ) + { + pContent = pIterContent; + maContents.emplace_back(pContent ); + } + } + } + if( pContent == nullptr ) + { + pContent = new SvxUnoTextContent( rText, currentPara ); + pContent->SetSelection( aCurrentParaSel ); + maContents.emplace_back(pContent ); + } + } +} + +SvxUnoTextContentEnumeration::~SvxUnoTextContentEnumeration() noexcept +{ +} + +// container::XEnumeration +sal_Bool SAL_CALL SvxUnoTextContentEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + if( mpEditSource && !maContents.empty() ) + return o3tl::make_unsigned(mnNextParagraph) < maContents.size(); + else + return false; +} + +uno::Any SvxUnoTextContentEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if(!hasMoreElements()) + throw container::NoSuchElementException(); + + uno::Reference< text::XTextContent > xRef( maContents.at(mnNextParagraph) ); + mnNextParagraph++; + return uno::Any( xRef ); +} + + + + +SvxUnoTextContent::SvxUnoTextContent( const SvxUnoTextBase& rText, sal_Int32 nPara ) noexcept +: SvxUnoTextRangeBase(rText) +, mnParagraph(nPara) +, mrParentText(rText) +, mbDisposing( false ) +{ + mxParentText = const_cast<SvxUnoTextBase*>(&rText); +} + +SvxUnoTextContent::SvxUnoTextContent( const SvxUnoTextContent& rContent ) noexcept +: SvxUnoTextRangeBase(rContent) +, text::XTextContent() +, container::XEnumerationAccess() +, lang::XTypeProvider() +, cppu::OWeakAggObject() +, mrParentText(rContent.mrParentText) +, mbDisposing( false ) +{ + mxParentText = rContent.mxParentText; + mnParagraph = rContent.mnParagraph; + SetSelection( rContent.GetSelection() ); +} + +SvxUnoTextContent::~SvxUnoTextContent() noexcept +{ +} + +// uno::XInterface +uno::Any SAL_CALL SvxUnoTextContent::queryAggregation( const uno::Type & rType ) +{ + QUERYINT( text::XTextRange ); + else QUERYINT( beans::XMultiPropertyStates ); + else QUERYINT( beans::XPropertySet ); + else QUERYINT( beans::XMultiPropertySet ); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextContent ); + else QUERYINT( text::XTextRangeCompare ); + else QUERYINT( lang::XComponent ); + else QUERYINT( container::XEnumerationAccess ); + else QUERYINT( container::XElementAccess ); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextContent::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextContent::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextContent::release() noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider + +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextContent::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeCompare>::get(), + cppu::UnoType<text::XTextContent>::get(), + cppu::UnoType<container::XEnumerationAccess>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextContent::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// text::XTextRange + +uno::Reference< text::XText > SAL_CALL SvxUnoTextContent::getText() +{ + return mxParentText; +} + +// text::XTextContent +void SAL_CALL SvxUnoTextContent::attach( const uno::Reference< text::XTextRange >& ) +{ +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextContent::getAnchor() +{ + return mxParentText; +} + +// XComponent + +void SAL_CALL SvxUnoTextContent::dispose() +{ + SolarMutexGuard aGuard; + + if( mbDisposing ) + return; // caught a recursion + + mbDisposing = true; + + lang::EventObject aEvt; + aEvt.Source = *static_cast<OWeakAggObject*>(this); + { + std::unique_lock aMutexGuard(maDisposeContainerMutex); + maDisposeListeners.disposeAndClear(aMutexGuard, aEvt); + } + + if( mxParentText.is() ) + { + mxParentText->removeTextContent( this ); + mxParentText.clear(); + } +} + +void SAL_CALL SvxUnoTextContent::addEventListener( const uno::Reference< lang::XEventListener >& xListener ) +{ + std::unique_lock aGuard(maDisposeContainerMutex); + maDisposeListeners.addInterface(aGuard, xListener); +} + +void SAL_CALL SvxUnoTextContent::removeEventListener( const uno::Reference< lang::XEventListener >& aListener ) +{ + std::unique_lock aGuard(maDisposeContainerMutex); + maDisposeListeners.removeInterface(aGuard, aListener); +} + +// XEnumerationAccess + +uno::Reference< container::XEnumeration > SAL_CALL SvxUnoTextContent::createEnumeration() +{ + SolarMutexGuard aGuard; + + return new SvxUnoTextRangeEnumeration( mrParentText, mnParagraph, maSelection ); +} + +// XElementAccess ( container::XEnumerationAccess ) + +uno::Type SAL_CALL SvxUnoTextContent::getElementType() +{ + return cppu::UnoType<text::XTextRange>::get(); +} + +sal_Bool SAL_CALL SvxUnoTextContent::hasElements() +{ + SolarMutexGuard aGuard; + + SvxTextForwarder* pForwarder = GetEditSource() ? GetEditSource()->GetTextForwarder() : nullptr; + if( pForwarder ) + { + std::vector<sal_Int32> aPortions; + pForwarder->GetPortions( mnParagraph, aPortions ); + return !aPortions.empty(); + } + else + { + return false; + } +} + +// XPropertySet + +void SAL_CALL SvxUnoTextContent::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue ) +{ + _setPropertyValue( aPropertyName, aValue, mnParagraph ); +} + +uno::Any SAL_CALL SvxUnoTextContent::getPropertyValue( const OUString& PropertyName ) +{ + return _getPropertyValue( PropertyName, mnParagraph ); +} + +// XMultiPropertySet +void SAL_CALL SvxUnoTextContent::setPropertyValues( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) +{ + _setPropertyValues( aPropertyNames, aValues, mnParagraph ); +} + +uno::Sequence< uno::Any > SAL_CALL SvxUnoTextContent::getPropertyValues( const uno::Sequence< OUString >& aPropertyNames ) +{ + return _getPropertyValues( aPropertyNames, mnParagraph ); +} + +/*// XTolerantMultiPropertySet +uno::Sequence< beans::SetPropertyTolerantFailed > SAL_CALL SvxUnoTextContent::setPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames, const uno::Sequence< uno::Any >& aValues ) throw (lang::IllegalArgumentException, uno::RuntimeException) +{ + return _setPropertyValuesTolerant(aPropertyNames, aValues, mnParagraph); +} + +uno::Sequence< beans::GetPropertyTolerantResult > SAL_CALL SvxUnoTextContent::getPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames ) throw (uno::RuntimeException) +{ + return _getPropertyValuesTolerant(aPropertyNames, mnParagraph); +} + +uno::Sequence< beans::GetDirectPropertyTolerantResult > SAL_CALL SvxUnoTextContent::getDirectPropertyValuesTolerant( const uno::Sequence< OUString >& aPropertyNames ) + throw (uno::RuntimeException) +{ + return _getDirectPropertyValuesTolerant(aPropertyNames, mnParagraph); +}*/ + +// beans::XPropertyState +beans::PropertyState SAL_CALL SvxUnoTextContent::getPropertyState( const OUString& PropertyName ) +{ + return _getPropertyState( PropertyName, mnParagraph ); +} + +uno::Sequence< beans::PropertyState > SAL_CALL SvxUnoTextContent::getPropertyStates( const uno::Sequence< OUString >& aPropertyName ) +{ + return _getPropertyStates( aPropertyName, mnParagraph ); +} + +void SAL_CALL SvxUnoTextContent::setPropertyToDefault( const OUString& PropertyName ) +{ + _setPropertyToDefault( PropertyName, mnParagraph ); +} + +// lang::XServiceInfo + +OUString SAL_CALL SvxUnoTextContent::getImplementationName() +{ + return "SvxUnoTextContent"; +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextContent::getSupportedServiceNames() +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.style.ParagraphProperties", + u"com.sun.star.style.ParagraphPropertiesComplex", + u"com.sun.star.style.ParagraphPropertiesAsian", + u"com.sun.star.text.TextContent", + u"com.sun.star.text.Paragraph" }); +} + + + + +SvxUnoTextRangeEnumeration::SvxUnoTextRangeEnumeration(const SvxUnoTextBase& rParentText, sal_Int32 nParagraph, const ESelection& rSel) +: mxParentText( const_cast<SvxUnoTextBase*>(&rParentText) ), + mnNextPortion( 0 ) +{ + if (rParentText.GetEditSource()) + mpEditSource = rParentText.GetEditSource()->Clone(); + + if( !(mpEditSource && mpEditSource->GetTextForwarder() && (nParagraph == rSel.nStartPara && nParagraph == rSel.nEndPara)) ) + return; + + std::vector<sal_Int32> aPortions; + mpEditSource->GetTextForwarder()->GetPortions( nParagraph, aPortions ); + for( size_t aPortionIndex = 0; aPortionIndex < aPortions.size(); aPortionIndex++ ) + { + sal_uInt16 nStartPos = 0; + if ( aPortionIndex > 0 ) + nStartPos = aPortions.at( aPortionIndex - 1 ); + if( nStartPos > rSel.nEndPos ) + continue; + sal_uInt16 nEndPos = aPortions.at( aPortionIndex ); + if( nEndPos < rSel.nStartPos ) + continue; + + nStartPos = std::max<int>(nStartPos, rSel.nStartPos); + nEndPos = std::min<sal_uInt16>(nEndPos, rSel.nEndPos); + ESelection aSel( nParagraph, nStartPos, nParagraph, nEndPos ); + + const SvxUnoTextRangeBaseVec& rRanges( mpEditSource->getRanges() ); + rtl::Reference<SvxUnoTextRange> pRange; + for (auto const& elemRange : rRanges) + { + if (pRange) + break; + SvxUnoTextRange* pIterRange = dynamic_cast< SvxUnoTextRange* >( elemRange ); + if( pIterRange && pIterRange->mbPortion && (aSel == pIterRange->maSelection) ) + pRange = pIterRange; + } + if( pRange == nullptr ) + { + pRange = new SvxUnoTextRange( rParentText, true ); + pRange->SetSelection( aSel ); + } + maPortions.emplace_back(pRange ); + } +} + +SvxUnoTextRangeEnumeration::~SvxUnoTextRangeEnumeration() noexcept +{ +} + +// container::XEnumeration + +sal_Bool SAL_CALL SvxUnoTextRangeEnumeration::hasMoreElements() +{ + SolarMutexGuard aGuard; + + return !maPortions.empty() && mnNextPortion < maPortions.size(); +} + +uno::Any SAL_CALL SvxUnoTextRangeEnumeration::nextElement() +{ + SolarMutexGuard aGuard; + + if( maPortions.empty() || mnNextPortion >= maPortions.size() ) + throw container::NoSuchElementException(); + + uno::Reference< text::XTextRange > xRange = maPortions.at(mnNextPortion); + mnNextPortion++; + return uno::Any( xRange ); +} + +SvxUnoTextCursor::SvxUnoTextCursor( const SvxUnoTextBase& rText ) noexcept +: SvxUnoTextRangeBase(rText), + mxParentText( const_cast<SvxUnoTextBase*>(&rText) ) +{ +} + +SvxUnoTextCursor::SvxUnoTextCursor( const SvxUnoTextCursor& rCursor ) noexcept +: SvxUnoTextRangeBase(rCursor) +, text::XTextCursor() +, lang::XTypeProvider() +, cppu::OWeakAggObject() +, mxParentText(rCursor.mxParentText) +{ +} + +SvxUnoTextCursor::~SvxUnoTextCursor() noexcept +{ +} + +// Comment out automatically - [getIdlClass(es) or queryInterface] +// Please use the XTypeProvider! +//sal_Bool SvxUnoTextCursor::queryInterface( uno::Uik aUIK, Reference< uno::XInterface > & xRef) +uno::Any SAL_CALL SvxUnoTextCursor::queryAggregation( const uno::Type & rType ) +{ + if( rType == cppu::UnoType<text::XTextRange>::get()) + return uno::Any(uno::Reference< text::XTextRange >(static_cast<SvxUnoTextRangeBase *>(this))); + else QUERYINT( text::XTextCursor ); + else QUERYINT( beans::XMultiPropertyStates ); + else QUERYINT( beans::XPropertySet ); + else QUERYINT( beans::XMultiPropertySet ); + else QUERYINT( beans::XPropertyState ); + else QUERYINT( text::XTextRangeCompare ); + else QUERYINT( lang::XServiceInfo ); + else QUERYINT( lang::XTypeProvider ); + else QUERYINT( lang::XUnoTunnel ); + else + return OWeakAggObject::queryAggregation( rType ); +} + +uno::Any SAL_CALL SvxUnoTextCursor::queryInterface( const uno::Type & rType ) +{ + return OWeakAggObject::queryInterface(rType); +} + +void SAL_CALL SvxUnoTextCursor::acquire() noexcept +{ + OWeakAggObject::acquire(); +} + +void SAL_CALL SvxUnoTextCursor::release() noexcept +{ + OWeakAggObject::release(); +} + +// XTypeProvider +uno::Sequence< uno::Type > SAL_CALL SvxUnoTextCursor::getTypes() +{ + static const uno::Sequence< uno::Type > TYPES { + cppu::UnoType<text::XTextRange>::get(), + cppu::UnoType<text::XTextCursor>::get(), + cppu::UnoType<beans::XPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertySet>::get(), + cppu::UnoType<beans::XMultiPropertyStates>::get(), + cppu::UnoType<beans::XPropertyState>::get(), + cppu::UnoType<text::XTextRangeCompare>::get(), + cppu::UnoType<lang::XServiceInfo>::get(), + cppu::UnoType<lang::XTypeProvider>::get(), + cppu::UnoType<lang::XUnoTunnel>::get() }; + return TYPES; +} + +uno::Sequence< sal_Int8 > SAL_CALL SvxUnoTextCursor::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +// text::XTextCursor +void SAL_CALL SvxUnoTextCursor::collapseToStart() +{ + SolarMutexGuard aGuard; + CollapseToStart(); +} + +void SAL_CALL SvxUnoTextCursor::collapseToEnd() +{ + SolarMutexGuard aGuard; + CollapseToEnd(); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::isCollapsed() +{ + SolarMutexGuard aGuard; + return IsCollapsed(); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::goLeft( sal_Int16 nCount, sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + return GoLeft( nCount, bExpand ); +} + +sal_Bool SAL_CALL SvxUnoTextCursor::goRight( sal_Int16 nCount, sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + return GoRight( nCount, bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoStart( sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + GotoStart( bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoEnd( sal_Bool bExpand ) +{ + SolarMutexGuard aGuard; + GotoEnd( bExpand ); +} + +void SAL_CALL SvxUnoTextCursor::gotoRange( const uno::Reference< text::XTextRange >& xRange, sal_Bool bExpand ) +{ + if( !xRange.is() ) + return; + + SvxUnoTextRangeBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextRangeBase>( xRange ); + + if( !pRange ) + return; + + ESelection aNewSel = pRange->GetSelection(); + + if( bExpand ) + { + const ESelection& rOldSel = GetSelection(); + aNewSel.nStartPara = rOldSel.nStartPara; + aNewSel.nStartPos = rOldSel.nStartPos; + } + + SetSelection( aNewSel ); +} + +// text::XTextRange (rest in SvxTextRange) +uno::Reference< text::XText > SAL_CALL SvxUnoTextCursor::getText() +{ + return mxParentText; +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextCursor::getStart() +{ + return SvxUnoTextRangeBase::getStart(); +} + +uno::Reference< text::XTextRange > SAL_CALL SvxUnoTextCursor::getEnd() +{ + return SvxUnoTextRangeBase::getEnd(); +} + +OUString SAL_CALL SvxUnoTextCursor::getString() +{ + return SvxUnoTextRangeBase::getString(); +} + +void SAL_CALL SvxUnoTextCursor::setString( const OUString& aString ) +{ + SvxUnoTextRangeBase::setString(aString); +} +// lang::XServiceInfo +OUString SAL_CALL SvxUnoTextCursor::getImplementationName() +{ + return "SvxUnoTextCursor"; +} + +sal_Bool SAL_CALL SvxUnoTextCursor::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +uno::Sequence< OUString > SAL_CALL SvxUnoTextCursor::getSupportedServiceNames() +{ + return comphelper::concatSequences( + SvxUnoTextRangeBase::getSupportedServiceNames(), + std::initializer_list<std::u16string_view>{ u"com.sun.star.style.ParagraphProperties", + u"com.sun.star.style.ParagraphPropertiesComplex", + u"com.sun.star.style.ParagraphPropertiesAsian", + u"com.sun.star.text.TextCursor" }); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/uno/unoviwou.cxx b/editeng/source/uno/unoviwou.cxx new file mode 100644 index 0000000000..19f38794e8 --- /dev/null +++ b/editeng/source/uno/unoviwou.cxx @@ -0,0 +1,128 @@ +/* -*- 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 <vcl/outdev.hxx> +#include <vcl/window.hxx> + +#include <editeng/unoviwou.hxx> +#include <editeng/outliner.hxx> + +SvxDrawOutlinerViewForwarder::SvxDrawOutlinerViewForwarder( OutlinerView& rOutl ) : + mrOutlinerView ( rOutl ) +{ +} + +SvxDrawOutlinerViewForwarder::SvxDrawOutlinerViewForwarder( OutlinerView& rOutl, const Point& rShapePosTopLeft ) : + mrOutlinerView ( rOutl ), maTextShapeTopLeft( rShapePosTopLeft ) +{ +} + +SvxDrawOutlinerViewForwarder::~SvxDrawOutlinerViewForwarder() +{ +} + +Point SvxDrawOutlinerViewForwarder::GetTextOffset() const +{ + // calc text offset from shape anchor + tools::Rectangle aOutputRect( mrOutlinerView.GetOutputArea() ); + + return aOutputRect.TopLeft() - maTextShapeTopLeft; +} + +bool SvxDrawOutlinerViewForwarder::IsValid() const +{ + return true; +} + +Point SvxDrawOutlinerViewForwarder::LogicToPixel( const Point& rPoint, const MapMode& rMapMode ) const +{ + OutputDevice* pOutDev = mrOutlinerView.GetWindow()->GetOutDev(); + + if( pOutDev ) + { + Point aPoint1( rPoint ); + Point aTextOffset( GetTextOffset() ); + + aPoint1.AdjustX(aTextOffset.X() ); + aPoint1.AdjustY(aTextOffset.Y() ); + + MapMode aMapMode(pOutDev->GetMapMode()); + Point aPoint2( OutputDevice::LogicToLogic( aPoint1, rMapMode, + MapMode(aMapMode.GetMapUnit()))); + aMapMode.SetOrigin(Point()); + return pOutDev->LogicToPixel( aPoint2, aMapMode ); + } + + return Point(); +} + +Point SvxDrawOutlinerViewForwarder::PixelToLogic( const Point& rPoint, const MapMode& rMapMode ) const +{ + OutputDevice* pOutDev = mrOutlinerView.GetWindow()->GetOutDev(); + + if( pOutDev ) + { + MapMode aMapMode(pOutDev->GetMapMode()); + aMapMode.SetOrigin(Point()); + Point aPoint1( pOutDev->PixelToLogic( rPoint, aMapMode ) ); + Point aPoint2( OutputDevice::LogicToLogic( aPoint1, + MapMode(aMapMode.GetMapUnit()), + rMapMode ) ); + Point aTextOffset( GetTextOffset() ); + + aPoint2.AdjustX( -(aTextOffset.X()) ); + aPoint2.AdjustY( -(aTextOffset.Y()) ); + + return aPoint2; + } + + return Point(); +} + +bool SvxDrawOutlinerViewForwarder::GetSelection( ESelection& rSelection ) const +{ + rSelection = mrOutlinerView.GetSelection(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::SetSelection( const ESelection& rSelection ) +{ + mrOutlinerView.SetSelection( rSelection ); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Copy() +{ + mrOutlinerView.Copy(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Cut() +{ + mrOutlinerView.Cut(); + return true; +} + +bool SvxDrawOutlinerViewForwarder::Paste() +{ + mrOutlinerView.PasteSpecial(); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/editsource.hxx b/editeng/source/xml/editsource.hxx new file mode 100644 index 0000000000..726ddabc8c --- /dev/null +++ b/editeng/source/xml/editsource.hxx @@ -0,0 +1,43 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/unoedsrc.hxx> + +class EditEngine; +class SvxEditEngineSourceImpl; + +class SvxEditEngineSource : public SvxEditSource +{ +public: + explicit SvxEditEngineSource( EditEngine* pEditEngine ); + virtual ~SvxEditEngineSource() override; + + virtual std::unique_ptr<SvxEditSource> Clone() const override; + virtual SvxTextForwarder* GetTextForwarder() override; + virtual void UpdateData() override; + +private: + explicit SvxEditEngineSource( SvxEditEngineSourceImpl* pImpl ); + + rtl::Reference<SvxEditEngineSourceImpl> mxImpl; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/xmltxtexp.cxx b/editeng/source/xml/xmltxtexp.cxx new file mode 100644 index 0000000000..444a435c3d --- /dev/null +++ b/editeng/source/xml/xmltxtexp.cxx @@ -0,0 +1,354 @@ +/* -*- 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 . + */ + + +/** this file implements an export of a selected EditEngine content into + a xml stream. See editeng/source/inc/xmledit.hxx for interface */ +#include <memory> +#include <com/sun/star/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <svl/itemprop.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <xmloff/xmlmetae.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <unotools/streamwrap.hxx> +#include <xmloff/xmlexp.hxx> +#include <editeng/unoedsrc.hxx> +#include <editeng/unofored.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unofield.hxx> +#include <editeng/editeng.hxx> +#include "editsource.hxx" +#include <editxml.hxx> +#include <editeng/unonrule.hxx> +#include <editeng/unoipset.hxx> +#include <unomodel.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::container; +using namespace com::sun::star::document; +using namespace com::sun::star::uno; +using namespace com::sun::star::awt; +using namespace com::sun::star::lang; +using namespace com::sun::star::xml::sax; +using namespace cppu; + +class SvxEditEngineSourceImpl; + +class SvxEditEngineSourceImpl : public salhelper::SimpleReferenceObject +{ +private: + EditEngine* mpEditEngine; + std::unique_ptr<SvxTextForwarder> mpTextForwarder; + + virtual ~SvxEditEngineSourceImpl() override; + +public: + explicit SvxEditEngineSourceImpl( EditEngine* pEditEngine ); + + SvxTextForwarder* GetTextForwarder(); +}; + +SvxEditEngineSourceImpl::SvxEditEngineSourceImpl( EditEngine* pEditEngine ) +: mpEditEngine( pEditEngine ) +{ +} + +SvxEditEngineSourceImpl::~SvxEditEngineSourceImpl() +{ +} + +SvxTextForwarder* SvxEditEngineSourceImpl::GetTextForwarder() +{ + if (!mpTextForwarder) + mpTextForwarder.reset( new SvxEditEngineForwarder( *mpEditEngine ) ); + + return mpTextForwarder.get(); +} + +// SvxTextEditSource +SvxEditEngineSource::SvxEditEngineSource( EditEngine* pEditEngine ) + : mxImpl( new SvxEditEngineSourceImpl( pEditEngine ) ) +{ +} + +SvxEditEngineSource::SvxEditEngineSource( SvxEditEngineSourceImpl* pImpl ) + : mxImpl(pImpl) +{ +} + +SvxEditEngineSource::~SvxEditEngineSource() +{ +} + +std::unique_ptr<SvxEditSource> SvxEditEngineSource::Clone() const +{ + return std::unique_ptr<SvxEditSource>(new SvxEditEngineSource( mxImpl.get() )); +} + +SvxTextForwarder* SvxEditEngineSource::GetTextForwarder() +{ + return mxImpl->GetTextForwarder(); +} + + +void SvxEditEngineSource::UpdateData() +{ +} + + +SvxSimpleUnoModel::SvxSimpleUnoModel() +{ +} + +// XMultiServiceFactory ( SvxFmMSFactory ) +uno::Reference< uno::XInterface > SAL_CALL SvxSimpleUnoModel::createInstance( const OUString& aServiceSpecifier ) +{ + if( aServiceSpecifier == "com.sun.star.text.NumberingRules" ) + { + return uno::Reference< uno::XInterface >( + SvxCreateNumRule(), uno::UNO_QUERY ); + } + if ( aServiceSpecifier == "com.sun.star.text.textfield.DateTime" + || aServiceSpecifier == "com.sun.star.text.TextField.DateTime" + ) + { + return cppu::getXWeak(new SvxUnoTextField( text::textfield::Type::DATE )); + } + + if( aServiceSpecifier == "com.sun.star.text.TextField.URL" ) + { + return cppu::getXWeak(new SvxUnoTextField(text::textfield::Type::URL)); + } + + return SvxUnoTextCreateTextField( aServiceSpecifier ); + +} + +uno::Reference< css::uno::XInterface > SAL_CALL SvxSimpleUnoModel::createInstanceWithArguments( const OUString& ServiceSpecifier, const css::uno::Sequence< css::uno::Any >& ) +{ + return createInstance( ServiceSpecifier ); +} + +Sequence< OUString > SAL_CALL SvxSimpleUnoModel::getAvailableServiceNames( ) +{ + Sequence< OUString > aSeq; + return aSeq; +} + +// XAnyCompareFactory +uno::Reference< css::ucb::XAnyCompare > SAL_CALL SvxSimpleUnoModel::createAnyCompareByName( const OUString& ) +{ + return SvxCreateNumRuleCompare(); +} + +// XStyleFamiliesSupplier +uno::Reference< container::XNameAccess > SAL_CALL SvxSimpleUnoModel::getStyleFamilies( ) +{ + uno::Reference< container::XNameAccess > xStyles; + return xStyles; +} + +// XModel +sal_Bool SAL_CALL SvxSimpleUnoModel::attachResource( const OUString&, const css::uno::Sequence< css::beans::PropertyValue >& ) +{ + return false; +} + +OUString SAL_CALL SvxSimpleUnoModel::getURL( ) +{ + return OUString(); +} + +css::uno::Sequence< css::beans::PropertyValue > SAL_CALL SvxSimpleUnoModel::getArgs( ) +{ + Sequence< beans::PropertyValue > aSeq; + return aSeq; +} + +void SAL_CALL SvxSimpleUnoModel::connectController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::disconnectController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::lockControllers( ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::unlockControllers( ) +{ +} + +sal_Bool SAL_CALL SvxSimpleUnoModel::hasControllersLocked( ) +{ + return true; +} + +css::uno::Reference< css::frame::XController > SAL_CALL SvxSimpleUnoModel::getCurrentController( ) +{ + uno::Reference< frame::XController > xRet; + return xRet; +} + +void SAL_CALL SvxSimpleUnoModel::setCurrentController( const css::uno::Reference< css::frame::XController >& ) +{ +} + +css::uno::Reference< css::uno::XInterface > SAL_CALL SvxSimpleUnoModel::getCurrentSelection( ) +{ + uno::Reference< XInterface > xRet; + return xRet; +} + + +// XComponent +void SAL_CALL SvxSimpleUnoModel::dispose( ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::addEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +void SAL_CALL SvxSimpleUnoModel::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& ) +{ +} + +namespace { + +class SvxXMLTextExportComponent : public SvXMLExport +{ +public: + SvxXMLTextExportComponent( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + EditEngine* pEditEngine, + const ESelection& rSel, + const css::uno::Reference< css::xml::sax::XDocumentHandler >& rHandler ); + + // methods without content: + virtual void ExportAutoStyles_() override; + virtual void ExportMasterStyles_() override; + virtual void ExportContent_() override; + +private: + css::uno::Reference< css::text::XText > mxText; +}; + +} + +SvxXMLTextExportComponent::SvxXMLTextExportComponent( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + EditEngine* pEditEngine, + const ESelection& rSel, + const css::uno::Reference< css::xml::sax::XDocumentHandler > & xHandler) +: SvXMLExport( xContext, "", /*rFileName*/"", xHandler, static_cast<frame::XModel*>(new SvxSimpleUnoModel()), FieldUnit::CM, + SvXMLExportFlags::OASIS | SvXMLExportFlags::AUTOSTYLES | SvXMLExportFlags::CONTENT ) +{ + SvxEditEngineSource aEditSource( pEditEngine ); + + static const SfxItemPropertyMapEntry SvxXMLTextExportComponentPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, + { UNO_NAME_NUMBERING_RULES, EE_PARA_NUMBULLET, cppu::UnoType<css::container::XIndexReplace>::get(), 0, 0 }, + { UNO_NAME_NUMBERING, EE_PARA_BULLETSTATE,cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_NAME_NUMBERING_LEVEL, EE_PARA_OUTLLEVEL, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + SVX_UNOEDIT_PARA_PROPERTIES, + }; + static SvxItemPropertySet aSvxXMLTextExportComponentPropertySet( SvxXMLTextExportComponentPropertyMap, EditEngine::GetGlobalItemPool() ); + + rtl::Reference<SvxUnoText> pUnoText = new SvxUnoText( &aEditSource, &aSvxXMLTextExportComponentPropertySet, mxText ); + pUnoText->SetSelection( rSel ); + mxText = pUnoText; + +} + +void SvxWriteXML( EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel ) +{ + try + { + do + { + // create service factory + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + + // create document handler + uno::Reference< xml::sax::XWriter > xWriter = xml::sax::Writer::create( xContext ); + + // create output stream and active data source + uno::Reference<io::XOutputStream> xOut( new utl::OOutputStreamWrapper( rStream ) ); + +/* testcode + static constexpr OUStringLiteral aURL( u"file:///e:/test.xml" ); + SvFileStream aStream(aURL, StreamMode::WRITE | StreamMode::TRUNC); + xOut = new utl::OOutputStreamWrapper(aStream); +*/ + + + xWriter->setOutputStream( xOut ); + + // export text + + // SvxXMLTextExportComponent aExporter( &rEditEngine, rSel, aName, xHandler ); + uno::Reference< xml::sax::XDocumentHandler > xHandler(xWriter, UNO_QUERY_THROW); + rtl::Reference< SvxXMLTextExportComponent > xExporter( new SvxXMLTextExportComponent( xContext, &rEditEngine, rSel, xHandler ) ); + + xExporter->exportDoc(); + +/* testcode + aStream.Close(); +*/ + + } + while( false ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("editeng", "exception during xml export"); + } +} + +// methods without content: +void SvxXMLTextExportComponent::ExportAutoStyles_() +{ + rtl::Reference< XMLTextParagraphExport > xTextExport( GetTextParagraphExport() ); + + xTextExport->collectTextAutoStyles( mxText ); + xTextExport->exportTextAutoStyles(); +} + +void SvxXMLTextExportComponent::ExportContent_() +{ + rtl::Reference< XMLTextParagraphExport > xTextExport( GetTextParagraphExport() ); + + xTextExport->exportText( mxText ); +} + +void SvxXMLTextExportComponent::ExportMasterStyles_() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/editeng/source/xml/xmltxtimp.cxx b/editeng/source/xml/xmltxtimp.cxx new file mode 100644 index 0000000000..c752a0eb70 --- /dev/null +++ b/editeng/source/xml/xmltxtimp.cxx @@ -0,0 +1,233 @@ +/* -*- 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 <com/sun/star/text/XText.hpp> +#include <comphelper/processfactory.hxx> +#include <unotools/streamwrap.hxx> +#include <svl/itemprop.hxx> +#include <utility> +#include <xmloff/xmlimp.hxx> +#include <xmloff/xmlictxt.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmlstyle.hxx> +#include "editsource.hxx" +#include <editxml.hxx> +#include <editdoc.hxx> +#include <editeng/editeng.hxx> +#include <editeng/unotext.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/unoipset.hxx> +#include <cassert> +#include <unomodel.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::document; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::xml::sax; +using namespace com::sun::star::text; +using namespace cppu; +using namespace xmloff::token; + +namespace { + +class SvxXMLTextImportContext : public SvXMLImportContext +{ +public: + SvxXMLTextImportContext( SvXMLImport& rImport, uno::Reference< XText > xText ); + + virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& xAttrList) override; + +private: + const uno::Reference< XText > mxText; +}; + +} + +SvxXMLTextImportContext::SvxXMLTextImportContext( SvXMLImport& rImport, uno::Reference< XText > xText ) +: SvXMLImportContext( rImport ), mxText(std::move( xText )) +{ +} + +css::uno::Reference< css::xml::sax::XFastContextHandler > SvxXMLTextImportContext::createFastChildContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& xAttrList) +{ + SvXMLImportContext* pContext = nullptr; + if(nElement == XML_ELEMENT(OFFICE, XML_BODY )) + { + pContext = new SvxXMLTextImportContext( GetImport(), mxText ); + } + else if( nElement == XML_ELEMENT(OFFICE, XML_AUTOMATIC_STYLES ) ) + { + pContext = new SvXMLStylesContext( GetImport() ); + GetImport().GetTextImport()->SetAutoStyles( static_cast<SvXMLStylesContext*>(pContext) ); + } + else + pContext = GetImport().GetTextImport()->CreateTextChildContext( GetImport(), nElement, xAttrList ); + return pContext; +} + +namespace { + +class SvxXMLXTextImportComponent : public SvXMLImport +{ +public: + SvxXMLXTextImportComponent( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + uno::Reference< XText > xText ); + + virtual SvXMLImportContext* CreateFastContext(sal_Int32 nElement, + const ::css::uno::Reference< ::css::xml::sax::XFastAttributeList >& xAttrList ) override; + +private: + const uno::Reference< XText > mxText; +}; + +} + +SvXMLImportContext *SvxXMLXTextImportComponent::CreateFastContext( + sal_Int32 nElement, + const uno::Reference< xml::sax::XFastAttributeList >& /*xAttrList*/) +{ + SvXMLImportContext* pContext = nullptr; + + if(nElement == XML_ELEMENT(OFFICE, XML_DOCUMENT_CONTENT ) ) + { + pContext = new SvxXMLTextImportContext( *this, mxText ); + } + + return pContext; +} + +SvxXMLXTextImportComponent::SvxXMLXTextImportComponent( + const css::uno::Reference< css::uno::XComponentContext >& xContext, + uno::Reference< XText > xText ) +: SvXMLImport(xContext, ""), + mxText(std::move( xText )) +{ + GetTextImport()->SetCursor( mxText->createTextCursor() ); + SvXMLImport::setTargetDocument(new SvxSimpleUnoModel); +} + +EditPaM SvxReadXML( EditEngine& rEditEngine, SvStream& rStream, const ESelection& rSel ) +{ + SvxEditEngineSource aEditSource( &rEditEngine ); + + static const SfxItemPropertyMapEntry SvxXMLTextImportComponentPropertyMap[] = + { + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_FONT_PROPERTIES, +// bullets & numbering props, tdf#128046 + { UNO_NAME_NUMBERING_RULES, EE_PARA_NUMBULLET, cppu::UnoType<css::container::XIndexReplace>::get(), 0, 0 }, + { UNO_NAME_NUMBERING, EE_PARA_BULLETSTATE,cppu::UnoType<bool>::get(), 0, 0 }, + { UNO_NAME_NUMBERING_LEVEL, EE_PARA_OUTLLEVEL, ::cppu::UnoType<sal_Int16>::get(), 0, 0 }, + SVX_UNOEDIT_PARA_PROPERTIES, + }; + static SvxItemPropertySet aSvxXMLTextImportComponentPropertySet( SvxXMLTextImportComponentPropertyMap, EditEngine::GetGlobalItemPool() ); + + assert(!rSel.HasRange()); + //get the initial para count before paste + sal_uInt32 initialParaCount = rEditEngine.GetEditDoc().Count(); + //insert para breaks before inserting the copied text + rEditEngine.InsertParaBreak( rEditEngine.CreateSelection( rSel ).Max() ); + rEditEngine.InsertParaBreak( rEditEngine.CreateSelection( rSel ).Max() ); + + // Init return PaM. + EditPaM aPaM( rEditEngine.CreateSelection( rSel ).Max()); + + ESelection aSel(rSel.nStartPara+1, 0, rSel.nEndPara+1, 0); + uno::Reference<text::XText > xParent; + rtl::Reference<SvxUnoText> pUnoText = new SvxUnoText( &aEditSource, &aSvxXMLTextImportComponentPropertySet, xParent ); + pUnoText->SetSelection( aSel ); + + try + { + do + { + uno::Reference<uno::XComponentContext> xContext( ::comphelper::getProcessComponentContext() ); + + uno::Reference<io::XInputStream> xInputStream = new utl::OInputStreamWrapper( rStream ); + +/* testcode + static constexpr OUStringLiteral aURL( u"file:///e:/test.xml" ); + SfxMedium aMedium( aURL, StreamMode::READ | STREAM_NOCREATE, sal_True ); + uno::Reference<io::XOutputStream> xOut( new utl::OOutputStreamWrapper( *aMedium.GetOutStream() ) ); + + aMedium.GetInStream()->Seek( 0 ); + uno::Reference< io::XActiveDataSource > xSource( aMedium.GetDataSource() ); + + if( !xSource.is() ) + { + OSL_FAIL( "got no data source from medium" ); + break; + } + + uno::Reference< XInterface > xPipe( Pipe::create(comphelper::getComponentContext(xServiceFactory)), UNO_QUERY ); + + // connect pipe's output stream to the data source + xSource->setOutputStream( uno::Reference< io::XOutputStream >::query( xPipe ) ); + + xml::sax::InputSource aParserInput; + aParserInput.aInputStream.set( xPipe, UNO_QUERY ); + aParserInput.sSystemId = aMedium.GetName(); + + + if( xSource.is() ) + { + uno::Reference< io::XActiveDataControl > xSourceControl( xSource, UNO_QUERY ); + xSourceControl->start(); + } + +*/ + + // uno::Reference< XDocumentHandler > xHandler( new SvxXMLXTextImportComponent( xText ) ); + rtl::Reference< SvxXMLXTextImportComponent > xImport( new SvxXMLXTextImportComponent( xContext, pUnoText ) ); + + xml::sax::InputSource aParserInput; + aParserInput.aInputStream = xInputStream; +// aParserInput.sSystemId = aMedium.GetName(); + xImport->parseStream( aParserInput ); + } + while(false); + + //remove the extra para breaks + EditDoc& pDoc = rEditEngine.GetEditDoc(); + rEditEngine.ParaAttribsToCharAttribs( pDoc.GetObject( rSel.nEndPara ) ); + rEditEngine.ConnectParagraphs( pDoc.GetObject( rSel.nEndPara ), + pDoc.GetObject( rSel.nEndPara + 1 ), true ); + rEditEngine.ParaAttribsToCharAttribs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ) ); + rEditEngine.ConnectParagraphs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ), + pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara -1 ), true ); + + // The final join is to be returned. + aPaM = rEditEngine.ConnectParagraphs( pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara - 2 ), + pDoc.GetObject( pDoc.Count() - initialParaCount + aSel.nEndPara -1 ), true ); + } + catch( const uno::Exception& ) + { + } + + return aPaM; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |