diff options
Diffstat (limited to 'svx/source/table')
32 files changed, 17744 insertions, 0 deletions
diff --git a/svx/source/table/accessiblecell.cxx b/svx/source/table/accessiblecell.cxx new file mode 100644 index 000000000..1052ab5eb --- /dev/null +++ b/svx/source/table/accessiblecell.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 <sal/config.h> +#include <sal/log.hxx> + +#include <memory> + +#include "accessiblecell.hxx" +#include <cell.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> + +#include <editeng/unoedsrc.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> + +#include <unotools/accessiblestatesethelper.hxx> +#include <comphelper/string.hxx> +#include <comphelper/sequence.hxx> +#include <svx/IAccessibleViewForwarder.hxx> +#include <svx/unoshtxt.hxx> +#include <svx/svdotext.hxx> +#include <tools/debug.hxx> + +using namespace sdr::table; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; + +namespace accessibility { + +AccessibleCell::AccessibleCell( const css::uno::Reference< css::accessibility::XAccessible>& rxParent, const sdr::table::CellRef& rCell, sal_Int32 nIndex, const AccessibleShapeTreeInfo& rShapeTreeInfo ) +: AccessibleCellBase( rxParent, AccessibleRole::TABLE_CELL ) +, maShapeTreeInfo( rShapeTreeInfo ) +, mnIndexInParent( nIndex ) +, mxCell( rCell ) +{ + //Init the pAccTable var + pAccTable = dynamic_cast <AccessibleTableShape *> (rxParent.get()); +} + + +AccessibleCell::~AccessibleCell() +{ + DBG_ASSERT( mpText == nullptr, "svx::AccessibleCell::~AccessibleCell(), not disposed!?" ); +} + + +void AccessibleCell::Init() +{ + SdrView* pView = maShapeTreeInfo.GetSdrView(); + const vcl::Window* pWindow = maShapeTreeInfo.GetWindow (); + if( !((pView != nullptr) && (pWindow != nullptr) && mxCell.is())) + return; + + // create AccessibleTextHelper to handle this shape's text + if( mxCell->CanCreateEditOutlinerParaObject() || mxCell->GetOutlinerParaObject() != nullptr ) + { + // non-empty text -> use full-fledged edit source right away + + mpText.reset( new AccessibleTextHelper( std::make_unique<SvxTextEditSource>(mxCell->GetObject(), mxCell.get(), *pView, *pWindow->GetOutDev()) ) ); + if( mxCell.is() && mxCell->IsActiveCell() ) + mpText->SetFocus(); + mpText->SetEventSource(this); + } +} + + +bool AccessibleCell::SetState (sal_Int16 aState) +{ + bool bStateHasChanged = false; + + if (aState == AccessibleStateType::FOCUSED && mpText != nullptr) + { + // Offer FOCUSED state to edit engine and detect whether the state + // changes. + bool bIsFocused = mpText->HaveFocus (); + mpText->SetFocus(); + bStateHasChanged = (bIsFocused != mpText->HaveFocus ()); + } + else + bStateHasChanged = AccessibleContextBase::SetState (aState); + + return bStateHasChanged; +} + + +bool AccessibleCell::ResetState (sal_Int16 aState) +{ + bool bStateHasChanged = false; + + if (aState == AccessibleStateType::FOCUSED && mpText != nullptr) + { + // Try to remove FOCUSED state from the edit engine and detect + // whether the state changes. + bool bIsFocused = mpText->HaveFocus (); + mpText->SetFocus (false); + bStateHasChanged = (bIsFocused != mpText->HaveFocus ()); + } + else + bStateHasChanged = AccessibleContextBase::ResetState (aState); + + return bStateHasChanged; +} + + +// XInterface + + +Any SAL_CALL AccessibleCell::queryInterface( const Type& aType ) +{ + return AccessibleCellBase::queryInterface( aType ); +} + + +void SAL_CALL AccessibleCell::acquire( ) noexcept +{ + AccessibleCellBase::acquire(); +} + + +void SAL_CALL AccessibleCell::release( ) noexcept +{ + AccessibleCellBase::release(); +} + + +// XAccessibleContext + + +/** The children of this cell come from the paragraphs of text. +*/ +sal_Int32 SAL_CALL AccessibleCell::getAccessibleChildCount() +{ + SolarMutexGuard aSolarGuard; + ThrowIfDisposed (); + return mpText != nullptr ? mpText->GetChildCount () : 0; +} + + +/** Forward the request to the shape. Return the requested shape or throw + an exception for a wrong index. +*/ +Reference<XAccessible> SAL_CALL AccessibleCell::getAccessibleChild (sal_Int32 nIndex) +{ + SolarMutexGuard aSolarGuard; + ThrowIfDisposed (); + + // todo: does GetChild throw IndexOutOfBoundsException? + return mpText->GetChild (nIndex); +} + + +/** Return a copy of the state set. + Possible states are: + ENABLED + SHOWING + VISIBLE +*/ +Reference<XAccessibleStateSet> SAL_CALL AccessibleCell::getAccessibleStateSet() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + Reference<XAccessibleStateSet> xStateSet; + + if (rBHelper.bDisposed || mpText == nullptr) + { + // Return a minimal state set that only contains the DEFUNC state. + xStateSet = AccessibleContextBase::getAccessibleStateSet (); + } + else + { + if(mxStateSet) + { + // Merge current FOCUSED state from edit engine. + if (mpText != nullptr) + { + if (mpText->HaveFocus()) + mxStateSet->AddState (AccessibleStateType::FOCUSED); + else + mxStateSet->RemoveState (AccessibleStateType::FOCUSED); + } + // Set the invisible state for merged cell + if (mxCell.is() && mxCell->isMerged()) + mxStateSet->RemoveState(AccessibleStateType::VISIBLE); + else + mxStateSet->AddState(AccessibleStateType::VISIBLE); + + + //Just when the parent table is not read-only,set states EDITABLE,RESIZABLE,MOVEABLE + css::uno::Reference<XAccessible> xTempAcc = getAccessibleParent(); + if( xTempAcc.is() ) + { + css::uno::Reference<XAccessibleContext> + xTempAccContext = xTempAcc->getAccessibleContext(); + if( xTempAccContext.is() ) + { + css::uno::Reference<XAccessibleStateSet> rState = + xTempAccContext->getAccessibleStateSet(); + if( rState.is() ) + { + const css::uno::Sequence<short> aStates = rState->getStates(); + if (std::find(aStates.begin(), aStates.end(), AccessibleStateType::EDITABLE) != aStates.end()) + { + mxStateSet->AddState (AccessibleStateType::EDITABLE); + mxStateSet->AddState (AccessibleStateType::RESIZABLE); + mxStateSet->AddState (AccessibleStateType::MOVEABLE); + } + } + } + } + // Create a copy of the state set that may be modified by the + // caller without affecting the current state set. + xStateSet.set(new ::utl::AccessibleStateSetHelper (*mxStateSet)); + } + } + + return xStateSet; +} + + +// XAccessibleComponent + + +sal_Bool SAL_CALL AccessibleCell::containsPoint( const css::awt::Point& aPoint) +{ + return AccessibleComponentBase::containsPoint( aPoint ); +} + +/** The implementation below is at the moment straightforward. It iterates + over all children (and thereby instances all children which have not + been already instantiated) until a child covering the specified point is + found. + This leaves room for improvement. For instance, first iterate only over + the already instantiated children and only if no match is found + instantiate the remaining ones. +*/ +Reference<XAccessible > SAL_CALL AccessibleCell::getAccessibleAtPoint ( const css::awt::Point& aPoint) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + sal_Int32 nChildCount = getAccessibleChildCount (); + for (sal_Int32 i=0; i<nChildCount; ++i) + { + Reference<XAccessible> xChild (getAccessibleChild (i)); + if (xChild.is()) + { + Reference<XAccessibleComponent> xChildComponent (xChild->getAccessibleContext(), uno::UNO_QUERY); + if (xChildComponent.is()) + { + awt::Rectangle aBBox (xChildComponent->getBounds()); + if ( (aPoint.X >= aBBox.X) + && (aPoint.Y >= aBBox.Y) + && (aPoint.X < aBBox.X+aBBox.Width) + && (aPoint.Y < aBBox.Y+aBBox.Height) ) + return xChild; + } + } + } + + // Have not found a child under the given point. Returning empty + // reference to indicate this. + return uno::Reference<XAccessible>(); +} + + +css::awt::Rectangle SAL_CALL AccessibleCell::getBounds() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + ThrowIfDisposed (); + css::awt::Rectangle aBoundingBox; + if( mxCell.is() ) + { + // Get the cell's bounding box in internal coordinates (in 100th of mm) + const ::tools::Rectangle aCellRect( mxCell->getCellRect() ); + + // Transform coordinates from internal to pixel. + if (maShapeTreeInfo.GetViewForwarder() == nullptr) + throw uno::RuntimeException ("AccessibleCell has no valid view forwarder",static_cast<uno::XWeak*>(this)); + + ::Size aPixelSize( maShapeTreeInfo.GetViewForwarder()->LogicToPixel(::Size(aCellRect.GetWidth(), aCellRect.GetHeight())) ); + ::Point aPixelPosition( maShapeTreeInfo.GetViewForwarder()->LogicToPixel( aCellRect.TopLeft() )); + + // Clip the shape's bounding box with the bounding box of its parent. + Reference<XAccessibleComponent> xParentComponent ( getAccessibleParent(), uno::UNO_QUERY); + if (xParentComponent.is()) + { + // Make the coordinates relative to the parent. + awt::Point aParentLocation (xParentComponent->getLocationOnScreen()); + int x = aPixelPosition.getX() - aParentLocation.X; + int y = aPixelPosition.getY() - aParentLocation.Y; + + // Clip with parent (with coordinates relative to itself). + ::tools::Rectangle aBBox ( x, y, x + aPixelSize.getWidth(), y + aPixelSize.getHeight()); + awt::Size aParentSize (xParentComponent->getSize()); + ::tools::Rectangle aParentBBox (0,0, aParentSize.Width, aParentSize.Height); + aBBox = aBBox.GetIntersection (aParentBBox); + aBoundingBox = awt::Rectangle ( aBBox.Left(), aBBox.Top(), aBBox.getWidth(), aBBox.getHeight()); + } + else + { + SAL_INFO("svx", "parent does not support component"); + aBoundingBox = awt::Rectangle (aPixelPosition.getX(), aPixelPosition.getY(),aPixelSize.getWidth(), aPixelSize.getHeight()); + } + } + + return aBoundingBox; +} + + +css::awt::Point SAL_CALL AccessibleCell::getLocation() +{ + ThrowIfDisposed (); + css::awt::Rectangle aBoundingBox(getBounds()); + return css::awt::Point(aBoundingBox.X, aBoundingBox.Y); +} + + +css::awt::Point SAL_CALL AccessibleCell::getLocationOnScreen() +{ + ThrowIfDisposed (); + + // Get relative position... + css::awt::Point aLocation(getLocation ()); + + // ... and add absolute position of the parent. + Reference<XAccessibleComponent> xParentComponent( getAccessibleParent(), uno::UNO_QUERY); + if(xParentComponent.is()) + { + css::awt::Point aParentLocation(xParentComponent->getLocationOnScreen()); + aLocation.X += aParentLocation.X; + aLocation.Y += aParentLocation.Y; + } + else + { + SAL_WARN("svx", "parent does not support XAccessibleComponent"); + } + + return aLocation; +} + + +awt::Size SAL_CALL AccessibleCell::getSize() +{ + ThrowIfDisposed (); + awt::Rectangle aBoundingBox (getBounds()); + return awt::Size (aBoundingBox.Width, aBoundingBox.Height); +} + + +void SAL_CALL AccessibleCell::grabFocus() +{ + AccessibleComponentBase::grabFocus(); +} + + +sal_Int32 SAL_CALL AccessibleCell::getForeground() +{ + ThrowIfDisposed (); + + // todo + return sal_Int32(0x0ffffffL); +} + + +sal_Int32 SAL_CALL AccessibleCell::getBackground() +{ + ThrowIfDisposed (); + + // todo + return 0; +} + + +// XAccessibleExtendedComponent + + +css::uno::Reference< css::awt::XFont > SAL_CALL AccessibleCell::getFont() +{ +//todo + return AccessibleComponentBase::getFont(); +} + + +OUString SAL_CALL AccessibleCell::getTitledBorderText() +{ + return AccessibleComponentBase::getTitledBorderText(); +} + + +OUString SAL_CALL AccessibleCell::getToolTipText() +{ + return AccessibleComponentBase::getToolTipText(); +} + + +// XAccessibleEventBroadcaster + + +void SAL_CALL AccessibleCell::addAccessibleEventListener( const Reference<XAccessibleEventListener >& rxListener) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + Reference<XInterface> xSource( static_cast<XComponent *>(this) ); + lang::EventObject aEventObj(xSource); + rxListener->disposing(aEventObj); + } + else + { + AccessibleContextBase::addAccessibleEventListener (rxListener); + if (mpText != nullptr) + mpText->AddEventListener (rxListener); + } +} + + +void SAL_CALL AccessibleCell::removeAccessibleEventListener( const Reference<XAccessibleEventListener >& rxListener) +{ + SolarMutexGuard aSolarGuard; + AccessibleContextBase::removeAccessibleEventListener(rxListener); + if (mpText != nullptr) + mpText->RemoveEventListener (rxListener); +} + + +// XServiceInfo + + +OUString SAL_CALL AccessibleCell::getImplementationName() +{ + return "AccessibleCell"; +} + + +Sequence<OUString> SAL_CALL AccessibleCell::getSupportedServiceNames() +{ + ThrowIfDisposed (); + const css::uno::Sequence<OUString> vals { "com.sun.star.drawing.AccessibleCell" }; + return comphelper::concatSequences(AccessibleContextBase::getSupportedServiceNames(), vals); +} + + +// IAccessibleViewForwarderListener + + +void AccessibleCell::ViewForwarderChanged() +{ + // Inform all listeners that the graphical representation (i.e. size + // and/or position) of the shape has changed. + CommitChange(AccessibleEventId::VISIBLE_DATA_CHANGED, Any(), Any()); + + // update our children that our screen position might have changed + if( mpText ) + mpText->UpdateChildren(); +} + + +// protected + + +void AccessibleCell::disposing() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + + // Make sure to send an event that this object loses the focus in the + // case that it has the focus. + if (mxStateSet != nullptr) + mxStateSet->RemoveState(AccessibleStateType::FOCUSED); + + if (mpText != nullptr) + { + mpText->Dispose(); + mpText.reset(); + } + + // Cleanup. Remove references to objects to allow them to be + // destroyed. + mxCell.clear(); + maShapeTreeInfo.dispose(); + + // Call base classes. + AccessibleContextBase::dispose (); +} + +sal_Int32 SAL_CALL AccessibleCell::getAccessibleIndexInParent() +{ + ThrowIfDisposed (); + return mnIndexInParent; +} + + +OUString AccessibleCell::getCellName( sal_Int32 nCol, sal_Int32 nRow ) +{ + OUStringBuffer aBuf; + + if (nCol < 26*26) + { + if (nCol < 26) + aBuf.append( static_cast<sal_Unicode>( 'A' + + static_cast<sal_uInt16>(nCol))); + else + { + aBuf.append( static_cast<sal_Unicode>( 'A' + + (static_cast<sal_uInt16>(nCol) / 26) - 1)); + aBuf.append( static_cast<sal_Unicode>( 'A' + + (static_cast<sal_uInt16>(nCol) % 26))); + } + } + else + { + OUStringBuffer aStr; + while (nCol >= 26) + { + sal_Int32 nC = nCol % 26; + aStr.append(static_cast<sal_Unicode>( 'A' + + static_cast<sal_uInt16>(nC))); + nCol = nCol - nC; + nCol = nCol / 26 - 1; + } + aStr.append(static_cast<sal_Unicode>( 'A' + + static_cast<sal_uInt16>(nCol))); + aBuf.append(comphelper::string::reverseString(aStr)); + } + aBuf.append(nRow+1); + return aBuf.makeStringAndClear(); +} + +OUString SAL_CALL AccessibleCell::getAccessibleName() +{ + ThrowIfDisposed (); + SolarMutexGuard aSolarGuard; + + if( pAccTable ) + { + try + { + sal_Int32 nRow = 0, nCol = 0; + pAccTable->getColumnAndRow(mnIndexInParent, nCol, nRow); + return getCellName( nCol, nRow ); + } + catch(const Exception&) + { + } + } + + return AccessibleCellBase::getAccessibleName(); +} + +void AccessibleCell::UpdateChildren() +{ + if (mpText) + mpText->UpdateChildren(); +} + +/* MT: Above getAccessibleName was introduced with IA2 CWS, while below was introduce in 3.3 meanwhile. Check which one is correct ++If this is correct, we also don't need sdr::table::CellRef getCellRef(), UpdateChildren(), getCellName( sal_Int32 nCol, sal_Int32 nRow ) above ++ + +OUString SAL_CALL AccessibleCell::getAccessibleName() throw (css::uno::RuntimeException) +{ + ThrowIfDisposed (); + SolarMutexGuard aSolarGuard; + + if( mxCell.is() ) + return mxCell->getName(); + + return AccessibleCellBase::getAccessibleName(); +} +*/ + +} // end of namespace accessibility + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/accessiblecell.hxx b/svx/source/table/accessiblecell.hxx new file mode 100644 index 000000000..65b9665ab --- /dev/null +++ b/svx/source/table/accessiblecell.hxx @@ -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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_ACCESSIBLECELL_HXX +#define INCLUDED_SVX_SOURCE_TABLE_ACCESSIBLECELL_HXX + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleExtendedComponent.hpp> + +#include <editeng/AccessibleContextBase.hxx> +#include <editeng/AccessibleComponentBase.hxx> +#include <svx/IAccessibleViewForwarderListener.hxx> +#include <svx/AccessibleTextHelper.hxx> +#include <svx/AccessibleShapeTreeInfo.hxx> +#include <AccessibleTableShape.hxx> + +#include <cppuhelper/implbase.hxx> + +#include <celltypes.hxx> + + +namespace accessibility +{ + +class AccessibleShapeTreeInfo; + +typedef ::cppu::ImplInheritanceHelper< AccessibleContextBase, css::accessibility::XAccessibleExtendedComponent > AccessibleCellBase; + +class AccessibleCell : public AccessibleCellBase + , public AccessibleComponentBase + , public IAccessibleViewForwarderListener +{ +public: + AccessibleCell( const css::uno::Reference< css::accessibility::XAccessible>& rxParent, const sdr::table::CellRef& rCell, sal_Int32 nIndex, const AccessibleShapeTreeInfo& rShapeTreeInfo); + virtual ~AccessibleCell() override; + AccessibleCell(const AccessibleCell&) = delete; + AccessibleCell& operator=(const AccessibleCell&) = delete; + + void Init(); + + virtual bool SetState (sal_Int16 aState) override; + virtual bool ResetState (sal_Int16 aState) override; + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& aType ) override; + virtual void SAL_CALL acquire( ) noexcept override; + virtual void SAL_CALL release( ) noexcept override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL getAccessibleChild(sal_Int32 nIndex) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet> SAL_CALL getAccessibleStateSet() override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent() override; + virtual OUString SAL_CALL getAccessibleName() override; + const sdr::table::CellRef& getCellRef() const { return mxCell;} + void UpdateChildren(); + static OUString getCellName( sal_Int32 nCol, sal_Int32 nRow ); + + // 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; + + // XAccessibleExtendedComponent + virtual css::uno::Reference< css::awt::XFont > SAL_CALL getFont() override; + virtual OUString SAL_CALL getTitledBorderText() override; + virtual OUString SAL_CALL getToolTipText() override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& rxListener) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& rxListener) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString> SAL_CALL getSupportedServiceNames() override; + + // IAccessibleViewForwarderListener + virtual void ViewForwarderChanged() override; + + // Misc + + /** set the index _nIndex at the accessible cell param _nIndex The new index in parent. + */ + void setIndexInParent(sal_Int32 _nIndex) { mnIndexInParent = _nIndex; } + + //Get the parent table + AccessibleTableShape* GetParentTable() { return pAccTable; } +protected: + /// Bundle of information passed to all shapes in a document tree. + AccessibleShapeTreeInfo maShapeTreeInfo; + + /// the index in parent. + sal_Int32 mnIndexInParent; + + /// The accessible text engine. May be NULL if it can not be created. + std::unique_ptr<AccessibleTextHelper> mpText; + + sdr::table::CellRef mxCell; + + /// This method is called from the component helper base class while disposing. + virtual void SAL_CALL disposing() override; + + AccessibleTableShape *pAccTable; +}; + +} // end of namespace accessibility + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/accessibletableshape.cxx b/svx/source/table/accessibletableshape.cxx new file mode 100644 index 000000000..540ae3116 --- /dev/null +++ b/svx/source/table/accessibletableshape.cxx @@ -0,0 +1,1324 @@ +/* -*- 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/table/XMergeableCell.hpp> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <tools/diagnose_ex.h> +#include <vcl/svapp.hxx> + +#include <AccessibleTableShape.hxx> +#include <svx/sdr/table/tablecontroller.hxx> +#include "accessiblecell.hxx" +#include <cell.hxx> + +#include <algorithm> +#include <unordered_map> + +#include <cppuhelper/implbase.hxx> +#include <svx/svdotable.hxx> +#include <com/sun/star/view/XSelectionSupplier.hpp> + + +using namespace ::accessibility; +using namespace sdr::table; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::container; + +namespace accessibility +{ + +typedef std::unordered_map< Reference< XCell >, rtl::Reference< AccessibleCell > > AccessibleCellMap; + +class AccessibleTableShapeImpl : public cppu::WeakImplHelper< XModifyListener > +{ +public: + explicit AccessibleTableShapeImpl( AccessibleShapeTreeInfo& rShapeTreeInfo ); + + void init( const Reference< XAccessible>& xAccessible, const Reference< XTable >& xTable ); + void dispose(); + + /// @throws IndexOutOfBoundsException + /// @throws RuntimeException + Reference< XAccessible > getAccessibleChild(sal_Int32 i); + /// @throws IndexOutOfBoundsException + void getColumnAndRow( sal_Int32 nChildIndex, sal_Int32& rnColumn, sal_Int32& rnRow ); + + // XModifyListener + virtual void SAL_CALL modified( const EventObject& aEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; + + AccessibleShapeTreeInfo& mrShapeTreeInfo; + Reference< XTable > mxTable; + AccessibleCellMap maChildMap; + Reference< XAccessible> mxAccessible; + sal_Int32 mRowCount, mColCount; + //get the cached AccessibleCell from XCell + rtl::Reference< AccessibleCell > getAccessibleCell (const Reference< XCell >& xCell); + /// @throws IndexOutOfBoundsException + /// @throws RuntimeException + rtl::Reference< AccessibleCell > getAccessibleCell (sal_Int32 nRow, sal_Int32 nColumn); +}; + + +AccessibleTableShapeImpl::AccessibleTableShapeImpl( AccessibleShapeTreeInfo& rShapeTreeInfo ) +: mrShapeTreeInfo( rShapeTreeInfo ) +, mRowCount(0) +, mColCount(0) +{ +} + + +void AccessibleTableShapeImpl::init( const Reference< XAccessible>& xAccessible, const Reference< XTable >& xTable ) +{ + mxAccessible = xAccessible; + mxTable = xTable; + + if( mxTable.is() ) + { + Reference< XModifyListener > xListener( this ); + mxTable->addModifyListener( xListener ); + //register the listener with table model + Reference< css::view::XSelectionSupplier > xSelSupplier(xTable, UNO_QUERY); + Reference< css::view::XSelectionChangeListener > xSelListener( xAccessible, UNO_QUERY ); + if (xSelSupplier.is()) + xSelSupplier->addSelectionChangeListener(xSelListener); + mRowCount = mxTable->getRowCount(); + mColCount = mxTable->getColumnCount(); + } +} + + +void AccessibleTableShapeImpl::dispose() +{ + if( mxTable.is() ) + { + //remove all the cell's acc object in table's dispose. + for( auto& rEntry : maChildMap ) + { + rEntry.second->dispose(); + } + maChildMap.clear(); + Reference< XModifyListener > xListener( this ); + mxTable->removeModifyListener( xListener ); + mxTable.clear(); + } + mxAccessible.clear(); +} + + +//get the cached AccessibleCell from XCell +rtl::Reference< AccessibleCell > AccessibleTableShapeImpl::getAccessibleCell (const Reference< XCell >& xCell) +{ + AccessibleCellMap::iterator iter( maChildMap.find( xCell ) ); + + if( iter != maChildMap.end() ) + { + rtl::Reference< AccessibleCell > xChild( (*iter).second ); + return xChild; + } + return rtl::Reference< AccessibleCell >(); +} + +rtl::Reference< AccessibleCell > AccessibleTableShapeImpl::getAccessibleCell (sal_Int32 nRow, sal_Int32 nColumn) +{ + Reference< XCell > xCell( mxTable->getCellByPosition( nColumn, nRow ) ); + rtl::Reference< AccessibleCell > xChild = getAccessibleCell( xCell ); + + if( !xChild.is() && mxTable.is() ) + { + sal_Int32 nChildIndex = mxTable->getColumnCount() * nRow + nColumn; + CellRef xCellRef( dynamic_cast< Cell* >( xCell.get() ) ); + + rtl::Reference< AccessibleCell > xAccessibleCell( new AccessibleCell( mxAccessible, xCellRef, nChildIndex, mrShapeTreeInfo ) ); + + xAccessibleCell->Init(); + maChildMap[xCell] = xAccessibleCell; + + xChild = xAccessibleCell; + } + return xChild; +} + + +Reference< XAccessible > AccessibleTableShapeImpl::getAccessibleChild(sal_Int32 nChildIndex) +{ + sal_Int32 nColumn = 0, nRow = 0; + getColumnAndRow( nChildIndex, nColumn, nRow ); + + Reference< XCell > xCell( mxTable->getCellByPosition( nColumn, nRow ) ); + AccessibleCellMap::iterator iter( maChildMap.find( xCell ) ); + + if( iter != maChildMap.end() ) + { + Reference< XAccessible > xChild( (*iter).second ); + return xChild; + } + else + { + CellRef xCellRef( dynamic_cast< Cell* >( xCell.get() ) ); + + rtl::Reference< AccessibleCell > xAccessibleCell( new AccessibleCell( mxAccessible, xCellRef, nChildIndex, mrShapeTreeInfo ) ); + + xAccessibleCell->Init(); + maChildMap[xCell] = xAccessibleCell; + + return xAccessibleCell; + } +} + + +void AccessibleTableShapeImpl::getColumnAndRow( sal_Int32 nChildIndex, sal_Int32& rnColumn, sal_Int32& rnRow ) +{ + rnRow = 0; + rnColumn = nChildIndex; + + if( mxTable.is() ) + { + const sal_Int32 nColumnCount = mxTable->getColumnCount(); + while( rnColumn >= nColumnCount ) + { + rnRow++; + rnColumn -= nColumnCount; + } + + if( rnRow < mxTable->getRowCount() ) + return; + } + + throw IndexOutOfBoundsException(); +} + +// XModifyListener +void SAL_CALL AccessibleTableShapeImpl::modified( const EventObject& /*aEvent*/ ) +{ + if( !mxTable.is() ) + return; + + try + { + // structural changes may have happened to the table, validate all accessible cell instances + AccessibleCellMap aTempChildMap; + aTempChildMap.swap( maChildMap ); + + // first move all still existing cells to maChildMap again and update their index + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + + bool bRowOrColumnChanged = false; + if (mRowCount != nRowCount || mColCount != nColCount ) + { + bRowOrColumnChanged = true; + mRowCount = nRowCount; + mColCount = nColCount; + } + sal_Int32 nChildIndex = 0; + + for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow ) + { + for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol ) + { + Reference< XCell > xCell( mxTable->getCellByPosition( nCol, nRow ) ); + AccessibleCellMap::iterator iter( aTempChildMap.find( xCell ) ); + + if( iter != aTempChildMap.end() ) + { + rtl::Reference< AccessibleCell > xAccessibleCell( (*iter).second ); + xAccessibleCell->setIndexInParent( nChildIndex ); + xAccessibleCell->UpdateChildren(); + // If row or column count is changed, there is split or merge, so all cell's acc name should be updated + if (bRowOrColumnChanged) + { + xAccessibleCell->SetAccessibleName(xAccessibleCell->getAccessibleName(), AccessibleContextBase::ManuallySet); + } + // For merged cell, add invisible & disabled state. + Reference< XMergeableCell > xMergedCell( mxTable->getCellByPosition( nCol, nRow ), UNO_QUERY ); + if (xMergedCell.is() && xMergedCell->isMerged()) + { + xAccessibleCell->ResetState(AccessibleStateType::VISIBLE); + xAccessibleCell->ResetState(AccessibleStateType::ENABLED); + // IA2 CWS. MT: OFFSCREEN == !SHOWING, should stay consistent + // xAccessibleCell->SetState(AccessibleStateType::OFFSCREEN); + xAccessibleCell->ResetState(AccessibleStateType::SHOWING); + } + else + { + xAccessibleCell->SetState(AccessibleStateType::VISIBLE); + xAccessibleCell->SetState(AccessibleStateType::ENABLED); + // IA2 CWS. MT: OFFSCREEN == !SHOWING, should stay consistent + // xAccessibleCell->ResetState(AccessibleStateType::OFFSCREEN); + xAccessibleCell->SetState(AccessibleStateType::SHOWING); + } + + // move still existing cell from temporary child map to our child map + maChildMap[xCell] = xAccessibleCell; + aTempChildMap.erase( iter ); + } + else + { + CellRef xCellRef( dynamic_cast< Cell* >( xCell.get() ) ); + + rtl::Reference< AccessibleCell > xAccessibleCell( new AccessibleCell( mxAccessible, xCellRef, nChildIndex, mrShapeTreeInfo ) ); + + xAccessibleCell->Init(); + maChildMap[xCell] = xAccessibleCell; + } + + ++nChildIndex; + } + } + + // all accessible cell instances still left in aTempChildMap must be disposed + // as they are no longer part of the table + + for( auto& rEntry : aTempChildMap ) + { + rEntry.second->dispose(); + } + //notify bridge to update the acc cache. + AccessibleTableShape *pAccTable = dynamic_cast <AccessibleTableShape *> (mxAccessible.get()); + if (pAccTable) + pAccTable->CommitChange(AccessibleEventId::INVALIDATE_ALL_CHILDREN, Any(), Any()); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + +// XEventListener +void SAL_CALL AccessibleTableShapeImpl::disposing( const EventObject& /*Source*/ ) +{ +} + +AccessibleTableShape::AccessibleTableShape( const AccessibleShapeInfo& rShapeInfo, const AccessibleShapeTreeInfo& rShapeTreeInfo) +: AccessibleTableShape_Base(rShapeInfo, rShapeTreeInfo) +, mnPreviousSelectionCount(0) +, mxImpl( new AccessibleTableShapeImpl( maShapeTreeInfo ) ) +{ +} + + +AccessibleTableShape::~AccessibleTableShape() +{ +} + + +void AccessibleTableShape::Init() +{ + try + { + Reference< XPropertySet > xSet( mxShape, UNO_QUERY_THROW ); + Reference< XTable > xTable( xSet->getPropertyValue("Model"), UNO_QUERY_THROW ); + + mxImpl->init( this, xTable ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + + AccessibleTableShape_Base::Init(); +} + + +SvxTableController* AccessibleTableShape::getTableController() +{ + SdrView* pView = maShapeTreeInfo.GetSdrView (); + if( pView ) + return dynamic_cast< SvxTableController* >( pView->getSelectionController().get() ); + else + return nullptr; +} + + +// XInterface + + +Any SAL_CALL AccessibleTableShape::queryInterface( const Type& aType ) +{ + if ( aType == cppu::UnoType<XAccessibleTableSelection>::get()) + { + Reference<XAccessibleTableSelection> xThis( this ); + Any aRet; + aRet <<= xThis; + return aRet; + } + else + return AccessibleTableShape_Base::queryInterface( aType ); +} + + +void SAL_CALL AccessibleTableShape::acquire( ) noexcept +{ + AccessibleTableShape_Base::acquire(); +} + + +void SAL_CALL AccessibleTableShape::release( ) noexcept +{ + AccessibleTableShape_Base::release(); +} + + +// XAccessible + + +OUString SAL_CALL AccessibleTableShape::getImplementationName() +{ + return "com.sun.star.comp.accessibility.AccessibleTableShape"; +} + + +OUString AccessibleTableShape::CreateAccessibleBaseName() +{ + return "TableShape"; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleChildCount( ) +{ + SolarMutexGuard aSolarGuard; + return mxImpl->mxTable.is() ? mxImpl->mxTable->getRowCount() * mxImpl->mxTable->getColumnCount() : 0; +} + + +Reference< XAccessible > SAL_CALL AccessibleTableShape::getAccessibleChild( sal_Int32 i ) +{ + SolarMutexGuard aSolarGuard; + ThrowIfDisposed(); + + return mxImpl->getAccessibleChild( i ); +} + + +sal_Int16 SAL_CALL AccessibleTableShape::getAccessibleRole() +{ + return AccessibleRole::TABLE; +} + + +void SAL_CALL AccessibleTableShape::disposing() +{ + mxImpl->dispose(); + + // let the base do its stuff + AccessibleShape::disposing(); +} + + +// XAccessibleTable + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleRowCount() +{ + SolarMutexGuard aSolarGuard; + return mxImpl->mxTable.is() ? mxImpl->mxTable->getRowCount() : 0; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleColumnCount( ) +{ + SolarMutexGuard aSolarGuard; + return mxImpl->mxTable.is() ? mxImpl->mxTable->getColumnCount() : 0; +} + + +OUString SAL_CALL AccessibleTableShape::getAccessibleRowDescription( sal_Int32 nRow ) +{ + checkCellPosition( 0, nRow ); + return OUString(); +} + + +OUString SAL_CALL AccessibleTableShape::getAccessibleColumnDescription( sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, 0 ); + return OUString(); +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleRowExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, nRow ); + if( mxImpl->mxTable.is() ) + { + Reference< XMergeableCell > xCell( mxImpl->mxTable->getCellByPosition( nColumn, nRow ), UNO_QUERY ); + if( xCell.is() ) + return xCell->getRowSpan(); + } + return 1; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleColumnExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, nRow ); + if( mxImpl->mxTable.is() ) + { + Reference< XMergeableCell > xCell( mxImpl->mxTable->getCellByPosition( nColumn, nRow ), UNO_QUERY ); + if( xCell.is() ) + return xCell->getColumnSpan(); + } + return 1; +} + + +Reference< XAccessibleTable > SAL_CALL AccessibleTableShape::getAccessibleRowHeaders( ) +{ + Reference< XAccessibleTable > xRet; + SvxTableController* pController = getTableController(); + if( pController ) + { + if( pController->isRowHeader() ) + { + xRet = new AccessibleTableHeaderShape( this, true ); + } + } + return xRet; +} + + +Reference< XAccessibleTable > SAL_CALL AccessibleTableShape::getAccessibleColumnHeaders( ) +{ + Reference< XAccessibleTable > xRet; + SvxTableController* pController = getTableController(); + if( pController ) + { + if( pController->isColumnHeader() ) + { + xRet = new AccessibleTableHeaderShape( this, false ); + } + } + return xRet; +} + + +Sequence< sal_Int32 > SAL_CALL AccessibleTableShape::getSelectedAccessibleRows( ) +{ + sal_Int32 nRow = getAccessibleRowCount(); + ::std::vector<bool> aSelected( nRow, true ); + sal_Int32 nCount = nRow; + for( sal_Int32 i = 0; i < nRow; i++ ) + { + try + { + aSelected[i] = isAccessibleRowSelected( i ); + } + catch( ... ) + { + return Sequence< sal_Int32 >(); + } + + if( !aSelected[i] ) + nCount--; + } + Sequence < sal_Int32 > aRet( nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = aSelected.size(); + for( size_t i=0; i < nSize && nPos < nCount; i++ ) + { + if( aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + return aRet; +} + + +Sequence< sal_Int32 > SAL_CALL AccessibleTableShape::getSelectedAccessibleColumns( ) +{ + sal_Int32 nColumn = getAccessibleColumnCount(); + ::std::vector<bool> aSelected( nColumn, true ); + sal_Int32 nCount = nColumn; + for( sal_Int32 i = 0; i < nColumn; i++ ) + { + try + { + aSelected[i] = isAccessibleColumnSelected( i ); + } + catch( ... ) + { + return Sequence< sal_Int32 >(); + } + + if( !aSelected[i] ) + nCount--; + } + Sequence < sal_Int32 > aRet( nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = aSelected.size(); + for( size_t i=0; i < nSize && nPos < nCount; i++ ) + { + if( aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + return aRet; +} + + +sal_Bool SAL_CALL AccessibleTableShape::isAccessibleRowSelected( sal_Int32 nRow ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( 0, nRow ); + SvxTableController* pController = getTableController(); + if( pController ) + { + return pController->isRowSelected( nRow ); + } + return false; +} + + +sal_Bool SAL_CALL AccessibleTableShape::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, 0 ); + SvxTableController* pController = getTableController(); + if( pController ) + { + return pController->isColumnSelected( nColumn ); + } + return false; +} + + +Reference< XAccessible > SAL_CALL AccessibleTableShape::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, nRow ); + + sal_Int32 nChildIndex = 0; + if( mxImpl->mxTable.is() ) + nChildIndex = mxImpl->mxTable->getColumnCount() * nRow + nColumn; + + return getAccessibleChild( nChildIndex ); +} + + +Reference< XAccessible > SAL_CALL AccessibleTableShape::getAccessibleCaption( ) +{ + Reference< XAccessible > xRet; + return xRet; +} + + +Reference< XAccessible > SAL_CALL AccessibleTableShape::getAccessibleSummary( ) +{ + Reference< XAccessible > xRet; + return xRet; +} + + +sal_Bool SAL_CALL AccessibleTableShape::isAccessibleSelected( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, nRow ); + + SvxTableController* pController = getTableController(); + if( pController && pController->hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + pController->getSelectedCells( aFirstPos, aLastPos ); + if( (aFirstPos.mnRow <= nRow) && (aFirstPos.mnCol <= nColumn) && (nRow <= aLastPos.mnRow) && (nColumn <= aLastPos.mnCol) ) + return true; + } + + return false; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + SolarMutexGuard aSolarGuard; + checkCellPosition( nColumn, nRow ); + return mxImpl->mxTable.is() ? (nRow * mxImpl->mxTable->getColumnCount() + nColumn) : 0; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleRow( sal_Int32 nChildIndex ) +{ + SolarMutexGuard aSolarGuard; + sal_Int32 nColumn = 0, nRow = 0; + mxImpl->getColumnAndRow( nChildIndex, nColumn, nRow ); + return nRow; +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getAccessibleColumn( sal_Int32 nChildIndex ) +{ + SolarMutexGuard aSolarGuard; + sal_Int32 nColumn = 0, nRow = 0; + mxImpl->getColumnAndRow( nChildIndex, nColumn, nRow ); + return nColumn; +} + + +// XAccessibleSelection + + +void SAL_CALL AccessibleTableShape::selectAccessibleChild( sal_Int32 nChildIndex ) +{ + SolarMutexGuard aSolarGuard; + CellPos aPos; + mxImpl->getColumnAndRow( nChildIndex, aPos.mnCol, aPos.mnRow ); + + // todo, select table shape?!? + SvxTableController* pController = getTableController(); + if( !pController ) + return; + + CellPos aFirstPos( aPos ), aLastPos( aPos ); + if( pController->hasSelectedCells() ) + { + pController->getSelectedCells( aFirstPos, aLastPos ); + + aFirstPos.mnRow = std::min( aFirstPos.mnRow, aPos.mnRow ); + aFirstPos.mnCol = std::min( aFirstPos.mnCol, aPos.mnCol ); + aLastPos.mnRow = std::max( aLastPos.mnRow, aPos.mnRow ); + aLastPos.mnCol = std::max( aLastPos.mnCol, aPos.mnCol ); + } + pController->setSelectedCells( aFirstPos, aLastPos ); +} + + +sal_Bool SAL_CALL AccessibleTableShape::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + SolarMutexGuard aSolarGuard; + CellPos aPos; + mxImpl->getColumnAndRow( nChildIndex, aPos.mnCol, aPos.mnRow ); + + return isAccessibleSelected(aPos.mnRow, aPos.mnCol); +} + + +void SAL_CALL AccessibleTableShape::clearAccessibleSelection() +{ + SolarMutexGuard aSolarGuard; + + SvxTableController* pController = getTableController(); + if( pController ) + pController->clearSelection(); +} + + +void SAL_CALL AccessibleTableShape::selectAllAccessibleChildren() +{ + SolarMutexGuard aSolarGuard; + + // todo: force selection of shape? + SvxTableController* pController = getTableController(); + if( pController ) + pController->selectAll(); +} + + +sal_Int32 SAL_CALL AccessibleTableShape::getSelectedAccessibleChildCount() +{ + SolarMutexGuard aSolarGuard; + + SvxTableController* pController = getTableController(); + if( pController && pController->hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + pController->getSelectedCells( aFirstPos, aLastPos ); + + const sal_Int32 nSelectedColumns = std::max( sal_Int32(0), aLastPos.mnCol - aFirstPos.mnCol ) + 1; + const sal_Int32 nSelectedRows = std::max( sal_Int32(0), aLastPos.mnRow - aFirstPos.mnRow ) + 1; + return nSelectedRows * nSelectedColumns; + } + + return 0; +} + + +Reference< XAccessible > SAL_CALL AccessibleTableShape::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + SolarMutexGuard aSolarGuard; + + if( nSelectedChildIndex < 0 ) + throw IndexOutOfBoundsException(); + + sal_Int32 nChildIndex = GetIndexOfSelectedChild( nSelectedChildIndex ); + + if( nChildIndex < 0 ) + throw IndexOutOfBoundsException(); + + if ( nChildIndex >= getAccessibleChildCount() ) + { + throw IndexOutOfBoundsException(); + } + + return getAccessibleChild( nChildIndex ); +} + + +void SAL_CALL AccessibleTableShape::deselectAccessibleChild( sal_Int32 nChildIndex ) +{ + SolarMutexGuard aSolarGuard; + CellPos aPos; + mxImpl->getColumnAndRow( nChildIndex, aPos.mnCol, aPos.mnRow ); + + // todo, select table shape?!? + SvxTableController* pController = getTableController(); + if( !(pController && pController->hasSelectedCells()) ) + return; + + CellPos aFirstPos, aLastPos; + pController->getSelectedCells( aFirstPos, aLastPos ); + + // create a selection where aPos is not part of anymore + aFirstPos.mnRow = std::min( aFirstPos.mnRow, aPos.mnRow+1 ); + aFirstPos.mnCol = std::min( aFirstPos.mnCol, aPos.mnCol+1 ); + aLastPos.mnRow = std::max( aLastPos.mnRow, aPos.mnRow-1 ); + aLastPos.mnCol = std::max( aLastPos.mnCol, aPos.mnCol-1 ); + + // new selection may be invalid (child to deselect is not at a border of the selection but in between) + if( (aFirstPos.mnRow > aLastPos.mnRow) || (aFirstPos.mnCol > aLastPos.mnCol) ) + pController->clearSelection(); // if selection is invalid, clear all + else + pController->setSelectedCells( aFirstPos, aLastPos ); +} + +// XAccessibleTableSelection +sal_Bool SAL_CALL AccessibleTableShape::selectRow( sal_Int32 row ) +{ + SolarMutexGuard aSolarGuard; + SvxTableController* pController = getTableController(); + if( !pController ) + return false; + return pController->selectRow( row ); +} + +sal_Bool SAL_CALL AccessibleTableShape::selectColumn( sal_Int32 column ) +{ + SolarMutexGuard aSolarGuard; + SvxTableController* pController = getTableController(); + if( !pController ) + return false; + return pController->selectColumn( column ); +} + +sal_Bool SAL_CALL AccessibleTableShape::unselectRow( sal_Int32 row ) +{ + SolarMutexGuard aSolarGuard; + SvxTableController* pController = getTableController(); + if( !pController ) + return false; + return pController->deselectRow( row ); +} + +sal_Bool SAL_CALL AccessibleTableShape::unselectColumn( sal_Int32 column ) +{ + SolarMutexGuard aSolarGuard; + SvxTableController* pController = getTableController(); + if( !pController ) + return false; + return pController->deselectColumn( column ); +} + +sal_Int32 AccessibleTableShape::GetIndexOfSelectedChild( + sal_Int32 nSelectedChildIndex ) const +{ + sal_Int32 nChildren = const_cast<AccessibleTableShape*>(this)->getAccessibleChildCount(); + + if( nSelectedChildIndex >= nChildren ) + return -1; + + sal_Int32 n = 0; + while( n < nChildren ) + { + if( const_cast<AccessibleTableShape*>(this)->isAccessibleChildSelected( n ) ) + { + if( 0 == nSelectedChildIndex ) + break; + else + --nSelectedChildIndex; + } + ++n; + } + + return n < nChildren ? n : -1; +} +void AccessibleTableShape::getColumnAndRow( sal_Int32 nChildIndex, sal_Int32& rnColumn, sal_Int32& rnRow ) +{ + mxImpl->getColumnAndRow(nChildIndex, rnColumn, rnRow); +} + +// XSelectionChangeListener +void SAL_CALL + AccessibleTableShape::disposing (const EventObject& aEvent) +{ + AccessibleShape::disposing(aEvent); +} +void SAL_CALL AccessibleTableShape::selectionChanged (const EventObject& rEvent) +{ + //sdr::table::CellRef xCellRef = static_cast< sdr::table::CellRef > (rEvent.Source); + Reference< XCell > xCell(rEvent.Source, UNO_QUERY); + if (!xCell.is()) + return; + + rtl::Reference< AccessibleCell > xAccCell = mxImpl->getAccessibleCell( xCell ); + if (!xAccCell.is()) + return; + + sal_Int32 nIndex = xAccCell->getAccessibleIndexInParent(), + nCount = getSelectedAccessibleChildCount(); + bool bSelected = isAccessibleChildSelected(nIndex); + if (mnPreviousSelectionCount == 0 && nCount > 0 && bSelected) + { + xAccCell->SetState(AccessibleStateType::SELECTED); + xAccCell->CommitChange(AccessibleEventId::SELECTION_CHANGED, Any(), Any()); + } + else if (bSelected) + { + xAccCell->SetState(AccessibleStateType::SELECTED); + xAccCell->CommitChange(AccessibleEventId::SELECTION_CHANGED_ADD, Any(), Any()); + } + else + { + xAccCell->ResetState(AccessibleStateType::SELECTED); + xAccCell->CommitChange(AccessibleEventId::SELECTION_CHANGED_REMOVE, Any(), Any()); + } + mnPreviousSelectionCount = nCount; +} +// Get the currently active cell which is text editing +AccessibleCell* AccessibleTableShape::GetActiveAccessibleCell() +{ + rtl::Reference< AccessibleCell > xAccCell; + AccessibleCell* pAccCell = nullptr; + SvxTableController* pController = getTableController(); + if (pController) + { + sdr::table::SdrTableObj* pTableObj = pController->GetTableObj(); + if ( pTableObj ) + { + const sdr::table::CellRef& xCellRef (pTableObj->getActiveCell()); + if ( xCellRef.is() ) + { + try + { + CellPos rPos; + pTableObj->getActiveCellPos( rPos ); + xAccCell = mxImpl->getAccessibleCell( rPos.mnRow, rPos.mnCol ); + if ( xAccCell.is() ) + pAccCell = xAccCell.get(); + } + catch ( IndexOutOfBoundsException& ) {} + } + } + } + return pAccCell; +} + +//If current active cell is in editing, the focus state should be set to internal text +bool AccessibleTableShape::SetState (sal_Int16 aState) +{ + if( aState == AccessibleStateType::FOCUSED ) + { + AccessibleCell* pActiveAccessibleCell = GetActiveAccessibleCell(); + if( pActiveAccessibleCell != nullptr ) + return pActiveAccessibleCell->SetState(aState); + } + + return AccessibleShape::SetState (aState); +} + +//If current active cell is in editing, the focus state should be reset to internal text +bool AccessibleTableShape::ResetState (sal_Int16 aState) +{ + if( aState == AccessibleStateType::FOCUSED ) + { + AccessibleCell* pActiveAccessibleCell = GetActiveAccessibleCell(); + if( pActiveAccessibleCell != nullptr ) + return pActiveAccessibleCell->ResetState(aState); + } + + return AccessibleShape::ResetState (aState); +} + +bool AccessibleTableShape::SetStateDirectly (sal_Int16 aState) +{ + return AccessibleContextBase::SetState (aState); +} + +bool AccessibleTableShape::ResetStateDirectly (sal_Int16 aState) +{ + return AccessibleContextBase::ResetState (aState); +} + +void AccessibleTableShape::checkCellPosition( sal_Int32 nCol, sal_Int32 nRow ) +{ + if( (nCol >= 0) && (nRow >= 0) && mxImpl->mxTable.is() && (nCol < mxImpl->mxTable->getColumnCount()) && (nRow < mxImpl->mxTable->getRowCount()) ) + return; + + throw IndexOutOfBoundsException(); +} + +AccessibleTableHeaderShape::AccessibleTableHeaderShape( AccessibleTableShape* pTable, bool bRow ) +{ + mpTable = pTable; + mbRow = bRow; +} + +AccessibleTableHeaderShape::~AccessibleTableHeaderShape() +{ + mpTable = nullptr; +} + +// XAccessible +Reference< XAccessibleContext > SAL_CALL AccessibleTableHeaderShape::getAccessibleContext() +{ + return this; +} + +// XAccessibleContext +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleChildCount( ) +{ + return getAccessibleRowCount() * getAccessibleColumnCount(); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleChild( sal_Int32 i ) +{ + return mpTable->getAccessibleChild( i ); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleParent() +{ + Reference< XAccessible > XParent; + return XParent; +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleIndexInParent() +{ + return -1; +} + +sal_Int16 SAL_CALL AccessibleTableHeaderShape::getAccessibleRole() +{ + return mpTable->getAccessibleRole(); +} + +OUString SAL_CALL AccessibleTableHeaderShape::getAccessibleDescription() +{ + return mpTable->getAccessibleDescription(); +} + +OUString SAL_CALL AccessibleTableHeaderShape::getAccessibleName() +{ + return mpTable->getAccessibleName(); +} + +Reference< XAccessibleStateSet > SAL_CALL AccessibleTableHeaderShape::getAccessibleStateSet() +{ + return mpTable->getAccessibleStateSet(); +} + +Reference< XAccessibleRelationSet > SAL_CALL AccessibleTableHeaderShape::getAccessibleRelationSet() +{ + return mpTable->getAccessibleRelationSet(); +} + +Locale SAL_CALL AccessibleTableHeaderShape::getLocale() +{ + return mpTable->getLocale(); +} + +//XAccessibleComponent +sal_Bool SAL_CALL AccessibleTableHeaderShape::containsPoint ( const css::awt::Point& aPoint ) +{ + return mpTable->containsPoint( aPoint ); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleAtPoint ( const css::awt::Point& aPoint) +{ + return mpTable->getAccessibleAtPoint( aPoint ); +} + +css::awt::Rectangle SAL_CALL AccessibleTableHeaderShape::getBounds() +{ + return mpTable->getBounds(); +} + +css::awt::Point SAL_CALL AccessibleTableHeaderShape::getLocation() +{ + return mpTable->getLocation(); +} + +css::awt::Point SAL_CALL AccessibleTableHeaderShape::getLocationOnScreen() +{ + return mpTable->getLocationOnScreen(); +} + +css::awt::Size SAL_CALL AccessibleTableHeaderShape::getSize() +{ + return mpTable->getSize(); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getForeground() +{ + return mpTable->getForeground(); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getBackground() +{ + return mpTable->getBackground(); +} + +void SAL_CALL AccessibleTableHeaderShape::grabFocus() +{ + mpTable->grabFocus(); +} +// XAccessibleTable +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleRowCount() +{ + return mbRow ? 1 : mpTable->getAccessibleRowCount(); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleColumnCount() +{ + return !mbRow ? 1 : mpTable->getAccessibleColumnCount(); +} + +OUString SAL_CALL AccessibleTableHeaderShape::getAccessibleRowDescription( sal_Int32 nRow ) +{ + return mpTable->getAccessibleRowDescription( nRow ); +} + +OUString SAL_CALL AccessibleTableHeaderShape::getAccessibleColumnDescription( sal_Int32 nColumn ) +{ + return mpTable->getAccessibleColumnDescription( nColumn ); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleRowExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return mpTable->getAccessibleRowExtentAt( nRow, nColumn ); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleColumnExtentAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return mpTable->getAccessibleColumnExtentAt( nRow, nColumn ); +} + +Reference< XAccessibleTable > SAL_CALL AccessibleTableHeaderShape::getAccessibleRowHeaders( ) +{ + Reference< XAccessibleTable > xRet; + return xRet; +} + +Reference< XAccessibleTable > SAL_CALL AccessibleTableHeaderShape::getAccessibleColumnHeaders( ) +{ + Reference< XAccessibleTable > xRet; + return xRet; +} + +Sequence< sal_Int32 > SAL_CALL AccessibleTableHeaderShape::getSelectedAccessibleRows( ) +{ + sal_Int32 nRow = getAccessibleRowCount(); + ::std::vector<bool> aSelected( nRow, true ); + sal_Int32 nCount = nRow; + for( sal_Int32 i = 0; i < nRow; i++ ) + { + try + { + aSelected[i] = isAccessibleRowSelected( i ); + } + catch( ... ) + { + return Sequence< sal_Int32 >(); + } + + if( !aSelected[i] ) + nCount--; + } + Sequence < sal_Int32 > aRet( nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = aSelected.size(); + for( size_t i=0; i < nSize && nPos < nCount; i++ ) + { + if( aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + return aRet; +} + +Sequence< sal_Int32 > SAL_CALL AccessibleTableHeaderShape::getSelectedAccessibleColumns( ) +{ + sal_Int32 nColumn = getAccessibleColumnCount(); + ::std::vector<bool> aSelected( nColumn, true ); + sal_Int32 nCount = nColumn; + for( sal_Int32 i = 0; i < nColumn; i++ ) + { + try + { + aSelected[i] = isAccessibleColumnSelected( i ); + } + catch( ... ) + { + return Sequence< sal_Int32 >(); + } + + if( !aSelected[i] ) + nCount--; + } + Sequence < sal_Int32 > aRet( nCount ); + sal_Int32 *pRet = aRet.getArray(); + sal_Int32 nPos = 0; + size_t nSize = aSelected.size(); + for( size_t i=0; i < nSize && nPos < nCount; i++ ) + { + if( aSelected[i] ) + { + *pRet++ = i; + nPos++; + } + } + + return aRet; +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::isAccessibleRowSelected( sal_Int32 nRow ) +{ + return mpTable->isAccessibleRowSelected( nRow ); +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::isAccessibleColumnSelected( sal_Int32 nColumn ) +{ + return mpTable->isAccessibleColumnSelected( nColumn ); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleCellAt( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return mpTable->getAccessibleCellAt( nRow, nColumn ); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleCaption( ) +{ + return mpTable->getAccessibleCaption(); +} + +Reference< XAccessible > SAL_CALL AccessibleTableHeaderShape::getAccessibleSummary( ) +{ + return mpTable->getAccessibleSummary(); +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::isAccessibleSelected( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return mpTable->isAccessibleSelected( nRow, nColumn ); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleIndex( sal_Int32 nRow, sal_Int32 nColumn ) +{ + return mpTable->getAccessibleIndex( nRow, nColumn ); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleRow( sal_Int32 nChildIndex ) +{ + return mpTable->getAccessibleRow( nChildIndex ); +} + +sal_Int32 SAL_CALL AccessibleTableHeaderShape::getAccessibleColumn( sal_Int32 nChildIndex ) +{ + return mpTable->getAccessibleColumn( nChildIndex ); +} + +// XAccessibleTableSelection +sal_Bool SAL_CALL AccessibleTableHeaderShape::selectRow( sal_Int32 row ) +{ + if( mbRow ) + return mpTable->selectRow( row ); + else + { + mpTable->clearAccessibleSelection(); + sal_Int32 nIndex = mpTable->getAccessibleIndex( row, 0 ); + mpTable->selectAccessibleChild( nIndex ); + return true; + } +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::selectColumn( sal_Int32 column ) +{ + if( !mbRow ) + return mpTable->selectColumn( column ); + else + { + mpTable->clearAccessibleSelection(); + sal_Int32 nIndex = mpTable->getAccessibleIndex( 0, column ); + mpTable->selectAccessibleChild( nIndex ); + return true; + } +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::unselectRow( sal_Int32 row ) +{ + if( mbRow ) + return mpTable->unselectRow( row ); + else + { + sal_Int32 nIndex = mpTable->getAccessibleIndex( row, 0 ); + mpTable->deselectAccessibleChild( nIndex ); + return true; + } +} + +sal_Bool SAL_CALL AccessibleTableHeaderShape::unselectColumn( sal_Int32 column ) +{ + if( !mbRow ) + return mpTable->unselectColumn( column ); + else + { + sal_Int32 nIndex = mpTable->getAccessibleIndex( 0, column ); + mpTable->deselectAccessibleChild( nIndex ); + return true; + } +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/cell.cxx b/svx/source/table/cell.cxx new file mode 100644 index 000000000..ba335ca27 --- /dev/null +++ b/svx/source/table/cell.cxx @@ -0,0 +1,1712 @@ +/* -*- 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/drawing/BitmapMode.hpp> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/table/TableBorder.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/lang/Locale.hpp> + +#include <comphelper/sequence.hxx> +#include <o3tl/any.hxx> +#include <svl/style.hxx> +#include <svl/itemset.hxx> + +#include <vcl/svapp.hxx> +#include <libxml/xmlwriter.h> + +#include <sdr/properties/textproperties.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/writingmodeitem.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdoutl.hxx> +#include <svx/unoshtxt.hxx> +#include <svx/svdmodel.hxx> +#include <svx/sdooitm.hxx> +#include <svx/sdtagitm.hxx> +#include <svx/sdmetitm.hxx> +#include <svx/xit.hxx> +#include <getallcharpropids.hxx> +#include "tableundo.hxx" +#include <cell.hxx> +#include <svx/unoshprp.hxx> +#include <svx/unoshape.hxx> +#include <editeng/editobj.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <svx/xflbstit.hxx> +#include <svx/xflbmtit.hxx> +#include <svx/svdpool.hxx> +#include <svx/xflclit.hxx> +#include <tools/diagnose_ex.h> + + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::style; +using namespace ::com::sun::star::container; + + +static const SvxItemPropertySet* ImplGetSvxCellPropertySet() +{ + // property map for an outliner text + static const SfxItemPropertyMapEntry aSvxCellPropertyMap[] = + { + FILL_PROPERTIES +// { "HasLevels", OWN_ATTR_HASLEVELS, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::READONLY, 0}, + { u"Style", OWN_ATTR_STYLE, cppu::UnoType< css::style::XStyle >::get(), css::beans::PropertyAttribute::MAYBEVOID, 0}, + { UNO_NAME_TEXT_WRITINGMODE, SDRATTR_TEXTDIRECTION, cppu::UnoType<css::text::WritingMode>::get(), 0, 0}, + { UNO_NAME_TEXT_HORZADJUST, SDRATTR_TEXT_HORZADJUST, cppu::UnoType<css::drawing::TextHorizontalAdjust>::get(), 0, 0}, + { UNO_NAME_TEXT_LEFTDIST, SDRATTR_TEXT_LEFTDIST, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { UNO_NAME_TEXT_LOWERDIST, SDRATTR_TEXT_LOWERDIST, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { UNO_NAME_TEXT_RIGHTDIST, SDRATTR_TEXT_RIGHTDIST, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { UNO_NAME_TEXT_UPPERDIST, SDRATTR_TEXT_UPPERDIST, cppu::UnoType<sal_Int32>::get(), 0, 0, PropertyMoreFlags::METRIC_ITEM}, + { UNO_NAME_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST, cppu::UnoType<css::drawing::TextVerticalAdjust>::get(), 0, 0}, + { UNO_NAME_TEXT_WORDWRAP, SDRATTR_TEXT_WORDWRAP, cppu::UnoType<bool>::get(), 0, 0}, + + { u"TableBorder", OWN_ATTR_TABLEBORDER, cppu::UnoType<TableBorder>::get(), 0, 0 }, + { u"TopBorder", SDRATTR_TABLE_BORDER, cppu::UnoType<BorderLine>::get(), 0, TOP_BORDER }, + { u"BottomBorder", SDRATTR_TABLE_BORDER, cppu::UnoType<BorderLine>::get(), 0, BOTTOM_BORDER }, + { u"LeftBorder", SDRATTR_TABLE_BORDER, cppu::UnoType<BorderLine>::get(), 0, LEFT_BORDER }, + { u"RightBorder", SDRATTR_TABLE_BORDER, cppu::UnoType<BorderLine>::get(), 0, RIGHT_BORDER }, + { u"RotateAngle", SDRATTR_TABLE_TEXT_ROTATION, cppu::UnoType<sal_Int32>::get(), 0, 0 }, + + SVX_UNOEDIT_OUTLINER_PROPERTIES, + SVX_UNOEDIT_CHAR_PROPERTIES, + SVX_UNOEDIT_PARA_PROPERTIES, + { u"", 0, css::uno::Type(), 0, 0 } + }; + + static SvxItemPropertySet aSvxCellPropertySet( aSvxCellPropertyMap, SdrObject::GetGlobalDrawObjectItemPool() ); + return &aSvxCellPropertySet; +} + +namespace +{ + +class CellTextProvider : public svx::ITextProvider +{ +public: + explicit CellTextProvider(const sdr::table::CellRef& rCell); + virtual ~CellTextProvider(); + +private: + virtual sal_Int32 getTextCount() const override; + virtual SdrText* getText(sal_Int32 nIndex) const override; + +private: + const sdr::table::CellRef m_xCell; +}; + +CellTextProvider::CellTextProvider(const sdr::table::CellRef& rCell) + : m_xCell(rCell) +{ +} + +CellTextProvider::~CellTextProvider() +{ +} + +sal_Int32 CellTextProvider::getTextCount() const +{ + return 1; +} + +SdrText* CellTextProvider::getText(sal_Int32 nIndex) const +{ + (void) nIndex; + assert(nIndex == 0); + return m_xCell.get(); +} + +} + +namespace sdr::properties +{ + class CellProperties : public TextProperties + { + protected: + // create a new itemset + SfxItemSet CreateObjectSpecificItemSet(SfxItemPool& rPool) override; + + const svx::ITextProvider& getTextProvider() const override; + + public: + // basic constructor + CellProperties(SdrObject& rObj, sdr::table::Cell* pCell ); + + // constructor for copying, but using new object + CellProperties(const CellProperties& rProps, SdrObject& rObj, sdr::table::Cell* pCell); + + // Clone() operator, normally just calls the local copy constructor + std::unique_ptr<BaseProperties> Clone(SdrObject& rObj) const override; + + void ForceDefaultAttributes() override; + + void ItemSetChanged(o3tl::span< const SfxPoolItem* const > aChangedItems, sal_uInt16 nDeletedWhich) override; + + void ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem = nullptr) override; + + sdr::table::CellRef mxCell; + + private: + const CellTextProvider maTextProvider; + }; + + // create a new itemset + SfxItemSet CellProperties::CreateObjectSpecificItemSet(SfxItemPool& rPool) + { + return SfxItemSet(rPool, + + // range from SdrAttrObj + svl::Items<SDRATTR_START, SDRATTR_SHADOW_LAST, + SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, + + // range for SdrTableObj + SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST, + + // range from SdrTextObj + EE_ITEMS_START, EE_ITEMS_END>); + } + + const svx::ITextProvider& CellProperties::getTextProvider() const + { + return maTextProvider; + } + + CellProperties::CellProperties(SdrObject& rObj, sdr::table::Cell* pCell) + : TextProperties(rObj) + , mxCell(pCell) + , maTextProvider(mxCell) + { + } + + CellProperties::CellProperties(const CellProperties& rProps, SdrObject& rObj, sdr::table::Cell* pCell) + : TextProperties(rProps, rObj) + , mxCell( pCell ) + , maTextProvider(mxCell) + { + } + + std::unique_ptr<BaseProperties> CellProperties::Clone(SdrObject& rObj) const + { + OSL_FAIL("CellProperties::Clone(), does not work yet!"); + return std::unique_ptr<BaseProperties>(new CellProperties(*this, rObj,nullptr)); + } + + void CellProperties::ForceDefaultAttributes() + { + } + + void CellProperties::ItemSetChanged(o3tl::span< const SfxPoolItem* const > aChangedItems, sal_uInt16 nDeletedWhich) + { + SdrTextObj& rObj = static_cast<SdrTextObj&>(GetSdrObject()); + + if( mxCell.is() ) + { + std::optional<OutlinerParaObject> pParaObj = mxCell->CreateEditOutlinerParaObject(); + + if( !pParaObj && mxCell->GetOutlinerParaObject()) + pParaObj = *mxCell->GetOutlinerParaObject(); + + if(pParaObj) + { + // handle outliner attributes + Outliner* pOutliner = nullptr; + + if(mxCell->IsTextEditActive()) + { + pOutliner = rObj.GetTextEditOutliner(); + } + else + { + pOutliner = &rObj.ImpGetDrawOutliner(); + pOutliner->SetText(*pParaObj); + } + + sal_Int32 nParaCount(pOutliner->GetParagraphCount()); + + // if the user sets character attributes to the complete + // cell we want to remove all hard set character attributes + // with same which ids from the text + std::vector<sal_uInt16> aCharWhichIds(GetAllCharPropIds(aChangedItems)); + + for(sal_Int32 nPara = 0; nPara < nParaCount; nPara++) + { + SfxItemSet aSet(pOutliner->GetParaAttribs(nPara)); + for (const SfxPoolItem* pItem : aChangedItems) + aSet.Put(*pItem); + if (nDeletedWhich) + aSet.ClearItem(nDeletedWhich); + + for (const auto& rWhichId : aCharWhichIds) + { + pOutliner->RemoveCharAttribs(nPara, rWhichId); + } + + pOutliner->SetParaAttribs(nPara, aSet); + } + + if(!mxCell->IsTextEditActive()) + { + if(nParaCount) + { + // force ItemSet + GetObjectItemSet(); + + SfxItemSet aNewSet(pOutliner->GetParaAttribs(0)); + mxItemSet->Put(aNewSet); + } + + std::optional<OutlinerParaObject> pTemp = pOutliner->CreateParaObject(0, nParaCount); + pOutliner->Clear(); + mxCell->SetOutlinerParaObject(std::move(pTemp)); + } + + } + } + + // call parent + AttributeProperties::ItemSetChanged(aChangedItems, nDeletedWhich); + + if( mxCell.is() ) + mxCell->notifyModified(); + } + + void CellProperties::ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem) + { + if(pNewItem && (SDRATTR_TEXTDIRECTION == nWhich)) + { + bool bVertical(css::text::WritingMode_TB_RL == static_cast<const SvxWritingModeItem*>(pNewItem)->GetValue()); + + sdr::table::SdrTableObj& rObj = static_cast<sdr::table::SdrTableObj&>(GetSdrObject()); + rObj.SetVerticalWriting(bVertical); + + // Set a cell vertical property + std::optional<OutlinerParaObject> pEditParaObj = mxCell->CreateEditOutlinerParaObject(); + + if( !pEditParaObj && mxCell->GetOutlinerParaObject() ) + { + OutlinerParaObject* pParaObj = mxCell->GetOutlinerParaObject(); + if(pParaObj) + pParaObj->SetVertical(bVertical); + } + } + + if (pNewItem && (SDRATTR_TABLE_TEXT_ROTATION == nWhich)) + { + const SvxTextRotateItem* pRotateItem = static_cast<const SvxTextRotateItem*>(pNewItem); + + // Set a cell vertical property + std::optional<OutlinerParaObject> pEditParaObj = mxCell->CreateEditOutlinerParaObject(); + + if (!pEditParaObj && mxCell->GetOutlinerParaObject()) + { + OutlinerParaObject* pParaObj = mxCell->GetOutlinerParaObject(); + if (pParaObj) + { + if(pRotateItem->IsVertical() && pRotateItem->IsTopToBottom()) + pParaObj->SetRotation(TextRotation::TOPTOBOTTOM); + else if (pRotateItem->IsVertical()) + pParaObj->SetRotation(TextRotation::BOTTOMTOTOP); + else + pParaObj->SetRotation(TextRotation::NONE); + } + } + + // Change autogrow direction + SdrTextObj& rObj = static_cast<SdrTextObj&>(GetSdrObject()); + + // rescue object size + tools::Rectangle aObjectRect = rObj.GetSnapRect(); + + const SfxItemSet& rSet = rObj.GetObjectItemSet(); + bool bAutoGrowWidth = rSet.Get(SDRATTR_TEXT_AUTOGROWWIDTH).GetValue(); + bool bAutoGrowHeight = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue(); + + // prepare ItemSet to set exchanged width and height items + SfxItemSetFixed<SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT> aNewSet(*rSet.GetPool()); + + aNewSet.Put(rSet); + aNewSet.Put(makeSdrTextAutoGrowWidthItem(bAutoGrowHeight)); + aNewSet.Put(makeSdrTextAutoGrowHeightItem(bAutoGrowWidth)); + rObj.SetObjectItemSet(aNewSet); + + // restore object size + rObj.SetSnapRect(aObjectRect); + } + + // call parent + AttributeProperties::ItemChange( nWhich, pNewItem ); + } + +} // end of namespace sdr::properties + +namespace sdr::table { + + +// Cell + + +rtl::Reference< Cell > Cell::create( SdrTableObj& rTableObj ) +{ + rtl::Reference< Cell > xCell( new Cell( rTableObj ) ); + if( xCell->mxTable.is() ) + { + xCell->mxTable->addEventListener( xCell ); + } + return xCell; +} + + +Cell::Cell( + SdrTableObj& rTableObj) +: SdrText(rTableObj) + ,SvxUnoTextBase( ImplGetSvxUnoOutlinerTextCursorSvxPropertySet() ) + ,mpPropSet( ImplGetSvxCellPropertySet() ) + ,mpProperties( new sdr::properties::CellProperties( rTableObj, this ) ) + ,mnCellContentType( CellContentType_EMPTY ) + ,mfValue( 0.0 ) + ,mnError( 0 ) + ,mbMerged( false ) + ,mnRowSpan( 1 ) + ,mnColSpan( 1 ) + ,mxTable( rTableObj.getTable() ) +{ + // Caution: Old SetModel() indirectly did a very necessary thing here, + // it created a valid SvxTextEditSource which is needed to bind contained + // Text to the UNO API and thus to save/load and more. Added version without + // model change. + // Also done was (not needed, for reference): + // SetStyleSheet( nullptr, true ); + // ForceOutlinerParaObject( OutlinerMode::TextObject ); + if(nullptr == GetEditSource()) + { + SetEditSource(new SvxTextEditSource(&GetObject(), this)); + } +} + +Cell::~Cell() COVERITY_NOEXCEPT_FALSE +{ + dispose(); +} + +void Cell::dispose() +{ + if( mxTable.is() ) + { + try + { + Reference< XEventListener > xThis( this ); + mxTable->removeEventListener( xThis ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + mxTable.clear(); + } + + // tdf#118199 avoid double dispose, detect by using mpProperties + // as indicator. Only use SetOutlinerParaObject once + if( mpProperties ) + { + mpProperties.reset(); + SetOutlinerParaObject( std::nullopt ); + } +} + +void Cell::merge( sal_Int32 nColumnSpan, sal_Int32 nRowSpan ) +{ + if ((mnColSpan != nColumnSpan) || (mnRowSpan != nRowSpan) || mbMerged) + { + mnColSpan = nColumnSpan; + mnRowSpan = nRowSpan; + mbMerged = false; + notifyModified(); + } +} + + +void Cell::mergeContent( const CellRef& xSourceCell ) +{ + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + + if( !xSourceCell->hasText() ) + return; + + SdrOutliner& rOutliner=rTableObj.ImpGetDrawOutliner(); + rOutliner.SetUpdateLayout(true); + + if( hasText() ) + { + rOutliner.SetText(*GetOutlinerParaObject()); + rOutliner.AddText(*xSourceCell->GetOutlinerParaObject()); + } + else + { + rOutliner.SetText(*xSourceCell->GetOutlinerParaObject()); + } + + SetOutlinerParaObject( rOutliner.CreateParaObject() ); + rOutliner.Clear(); + xSourceCell->SetOutlinerParaObject(rOutliner.CreateParaObject()); + rOutliner.Clear(); + SetStyleSheet( GetStyleSheet(), true ); +} + + +void Cell::cloneFrom( const CellRef& xCell ) +{ + if( xCell.is() ) + { + replaceContentAndFormatting( xCell ); + + mnCellContentType = xCell->mnCellContentType; + + msFormula = xCell->msFormula; + mfValue = xCell->mfValue; + mnError = xCell->mnError; + + mbMerged = xCell->mbMerged; + mnRowSpan = xCell->mnRowSpan; + mnColSpan = xCell->mnColSpan; + + } + notifyModified(); +} + +void Cell::replaceContentAndFormatting( const CellRef& xSourceCell ) +{ + if( !(xSourceCell.is() && mpProperties) ) + return; + + mpProperties->SetMergedItemSet( xSourceCell->GetObjectItemSet() ); + + // tdf#118354 OutlinerParaObject may be nullptr, do not dereference when + // not set (!) + if(xSourceCell->GetOutlinerParaObject()) + { + SetOutlinerParaObject( *xSourceCell->GetOutlinerParaObject() ); + } + + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + SdrTableObj& rSourceTableObj = dynamic_cast< SdrTableObj& >( xSourceCell->GetObject() ); + + if(&rSourceTableObj.getSdrModelFromSdrObject() != &rTableObj.getSdrModelFromSdrObject()) + { + // TTTT should not happen - if, then a clone may be needed + // Maybe add an assertion here later + SetStyleSheet( nullptr, true ); + } +} + + +void Cell::setMerged() +{ + if( !mbMerged ) + { + mbMerged = true; + notifyModified(); + } +} + + +void Cell::copyFormatFrom( const CellRef& xSourceCell ) +{ + if( !(xSourceCell.is() && mpProperties) ) + return; + + mpProperties->SetMergedItemSet( xSourceCell->GetObjectItemSet() ); + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + SdrTableObj& rSourceTableObj = dynamic_cast< SdrTableObj& >( xSourceCell->GetObject() ); + + if(&rSourceTableObj.getSdrModelFromSdrObject() != &rTableObj.getSdrModelFromSdrObject()) + { + // TTTT should not happen - if, then a clone may be needed + // Maybe add an assertion here later + SetStyleSheet( nullptr, true ); + } + + notifyModified(); +} + + +void Cell::notifyModified() +{ + if( mxTable.is() ) + mxTable->setModified( true ); +} + + +// SdrTextShape proxy + + +bool Cell::IsActiveCell() const +{ + bool isActive = false; + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + if( rTableObj.getActiveCell().get() == this ) + isActive = true; + + return isActive; +} + +bool Cell::IsTextEditActive() const +{ + bool isActive = false; + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + if(rTableObj.getActiveCell().get() == this ) + { + if( rTableObj.CanCreateEditOutlinerParaObject() ) + { + isActive = true; + } + } + return isActive; +} + + +bool Cell::hasText() const +{ + const OutlinerParaObject* pParaObj = GetOutlinerParaObject(); + if( pParaObj ) + { + const EditTextObject& rTextObj = pParaObj->GetTextObject(); + if( rTextObj.GetParagraphCount() >= 1 ) + { + if( rTextObj.GetParagraphCount() == 1 ) + { + if( rTextObj.GetText(0).isEmpty() ) + return false; + } + return true; + } + } + + return false; +} + +bool Cell::CanCreateEditOutlinerParaObject() const +{ + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + if( rTableObj.getActiveCell().get() == this ) + return rTableObj.CanCreateEditOutlinerParaObject(); + return false; +} + +std::optional<OutlinerParaObject> Cell::CreateEditOutlinerParaObject() const +{ + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + if( rTableObj.getActiveCell().get() == this ) + return rTableObj.CreateEditOutlinerParaObject(); + return std::nullopt; +} + + +void Cell::SetStyleSheet( SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr ) +{ + // only allow cell styles for cells + if( pStyleSheet && pStyleSheet->GetFamily() != SfxStyleFamily::Frame ) + return; + + if( mpProperties && (mpProperties->GetStyleSheet() != pStyleSheet) ) + { + mpProperties->SetStyleSheet( pStyleSheet, bDontRemoveHardAttr, true ); + } +} + + +const SfxItemSet& Cell::GetObjectItemSet() +{ + if( mpProperties ) + { + return mpProperties->GetObjectItemSet(); + } + else + { + OSL_FAIL("Cell::GetObjectItemSet(), called without properties!"); + return GetObject().GetObjectItemSet(); + } +} + +void Cell::SetObjectItem(const SfxPoolItem& rItem) +{ + if( mpProperties ) + { + mpProperties->SetObjectItem( rItem ); + notifyModified(); + } +} + +void Cell::SetMergedItem(const SfxPoolItem& rItem) +{ + SetObjectItem(rItem); +} + +SfxStyleSheet* Cell::GetStyleSheet() const +{ + if( mpProperties ) + return mpProperties->GetStyleSheet(); + else + return nullptr; +} + +void Cell::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const +{ + rAnchorRect.SetLeft( maCellRect.Left() + GetTextLeftDistance() ); + rAnchorRect.SetRight( maCellRect.Right() - GetTextRightDistance() ); + rAnchorRect.SetTop( maCellRect.Top() + GetTextUpperDistance() ); + rAnchorRect.SetBottom( maCellRect.Bottom() - GetTextLowerDistance() ); +} + + +void Cell::SetMergedItemSetAndBroadcast(const SfxItemSet& rSet, bool bClearAllItems) +{ + if( mpProperties ) + { + mpProperties->SetMergedItemSetAndBroadcast(rSet, bClearAllItems); + notifyModified(); + } +} + + +sal_Int32 Cell::calcPreferredWidth( const Size aSize ) +{ + if ( !hasText() ) + return getMinimumWidth(); + + Outliner& rOutliner=static_cast< SdrTableObj& >( GetObject() ).ImpGetDrawOutliner(); + rOutliner.SetPaperSize(aSize); + rOutliner.SetUpdateLayout(true); + ForceOutlinerParaObject( OutlinerMode::TextObject ); + + if( GetOutlinerParaObject() ) + rOutliner.SetText(*GetOutlinerParaObject()); + + sal_Int32 nPreferredWidth = const_cast<EditEngine&>(rOutliner.GetEditEngine()).CalcTextWidth(); + rOutliner.Clear(); + + return GetTextLeftDistance() + GetTextRightDistance() + nPreferredWidth; +} + +sal_Int32 Cell::getMinimumWidth() const +{ + return GetTextLeftDistance() + GetTextRightDistance() + 100; +} + + +sal_Int32 Cell::getMinimumHeight() +{ + if( !mpProperties ) + return 0; + + SdrTableObj& rTableObj = dynamic_cast< SdrTableObj& >( GetObject() ); + sal_Int32 nMinimumHeight = 0; + + tools::Rectangle aTextRect; + TakeTextAnchorRect( aTextRect ); + Size aSize( aTextRect.GetSize() ); + aSize.setHeight(0x0FFFFFFF ); + + SdrOutliner* pEditOutliner = rTableObj.GetCellTextEditOutliner( *this ); + if(pEditOutliner) + { + pEditOutliner->SetMaxAutoPaperSize(aSize); + nMinimumHeight = pEditOutliner->GetTextHeight()+1; + } + else + { + Outliner& rOutliner=rTableObj.ImpGetDrawOutliner(); + rOutliner.SetPaperSize(aSize); + rOutliner.SetUpdateLayout(true); + ForceOutlinerParaObject( OutlinerMode::TextObject ); + + if( GetOutlinerParaObject() ) + { + rOutliner.SetText(*GetOutlinerParaObject()); + } + nMinimumHeight=rOutliner.GetTextHeight()+1; + rOutliner.Clear(); + } + + nMinimumHeight += GetTextUpperDistance() + GetTextLowerDistance(); + return nMinimumHeight; +} + + +tools::Long Cell::GetTextLeftDistance() const +{ + return GetItemSet().Get(SDRATTR_TEXT_LEFTDIST).GetValue(); +} + + +tools::Long Cell::GetTextRightDistance() const +{ + return GetItemSet().Get(SDRATTR_TEXT_RIGHTDIST).GetValue(); +} + + +tools::Long Cell::GetTextUpperDistance() const +{ + return GetItemSet().Get(SDRATTR_TEXT_UPPERDIST).GetValue(); +} + + +tools::Long Cell::GetTextLowerDistance() const +{ + return GetItemSet().Get(SDRATTR_TEXT_LOWERDIST).GetValue(); +} + + +SdrTextVertAdjust Cell::GetTextVerticalAdjust() const +{ + return GetItemSet().Get(SDRATTR_TEXT_VERTADJUST).GetValue(); +} + + +SdrTextHorzAdjust Cell::GetTextHorizontalAdjust() const +{ + return GetItemSet().Get(SDRATTR_TEXT_HORZADJUST).GetValue(); +} + + +void Cell::SetOutlinerParaObject( std::optional<OutlinerParaObject> pTextObject ) +{ + bool bNullTextObject = !pTextObject; + SdrText::SetOutlinerParaObject( std::move(pTextObject) ); + maSelection.nStartPara = EE_PARA_MAX_COUNT; + + if( bNullTextObject ) + ForceOutlinerParaObject( OutlinerMode::TextObject ); +} + + +void Cell::AddUndo() +{ + SdrObject& rObj = GetObject(); + + if( rObj.IsInserted() && rObj.getSdrModelFromSdrObject().IsUndoEnabled() ) + { + CellRef xCell( this ); + rObj.getSdrModelFromSdrObject().AddUndo( std::make_unique<CellUndo>( &rObj, xCell ) ); + + // Undo action for the after-text-edit-ended stack. + SdrTableObj* pTableObj = dynamic_cast<sdr::table::SdrTableObj*>(&rObj); + if (pTableObj && pTableObj->IsTextEditActive()) + pTableObj->AddUndo(new CellUndo(pTableObj, xCell)); + } +} + + +sdr::properties::TextProperties* Cell::CloneProperties( sdr::properties::TextProperties const * pProperties, SdrObject& rNewObj, Cell& rNewCell ) +{ + if( pProperties ) + return new sdr::properties::CellProperties( *static_cast<sdr::properties::CellProperties const *>(pProperties), rNewObj, &rNewCell ); + else + return nullptr; +} + + +sdr::properties::TextProperties* Cell::CloneProperties( SdrObject& rNewObj, Cell& rNewCell ) +{ + return CloneProperties(mpProperties.get(),rNewObj,rNewCell); +} + + +// XInterface + + +Any SAL_CALL Cell::queryInterface( const Type & rType ) +{ + if( rType == cppu::UnoType<XMergeableCell>::get() ) + return Any( Reference< XMergeableCell >( this ) ); + + if( rType == cppu::UnoType<XCell>::get() ) + return Any( Reference< XCell >( this ) ); + + if( rType == cppu::UnoType<XLayoutConstrains>::get() ) + return Any( Reference< XLayoutConstrains >( this ) ); + + if( rType == cppu::UnoType<XEventListener>::get() ) + return Any( Reference< XEventListener >( this ) ); + + Any aRet( SvxUnoTextBase::queryAggregation( rType ) ); + if( aRet.hasValue() ) + return aRet; + + return ::cppu::OWeakObject::queryInterface( rType ); +} + + +void SAL_CALL Cell::acquire() noexcept +{ + ::cppu::OWeakObject::acquire(); +} + + +void SAL_CALL Cell::release() noexcept +{ + ::cppu::OWeakObject::release(); +} + + +// XTypeProvider + + +Sequence< Type > SAL_CALL Cell::getTypes( ) +{ + return comphelper::concatSequences( SvxUnoTextBase::getTypes(), + Sequence { + cppu::UnoType<XMergeableCell>::get(), + cppu::UnoType<XLayoutConstrains>::get() }); +} + + +Sequence< sal_Int8 > SAL_CALL Cell::getImplementationId( ) +{ + return css::uno::Sequence<sal_Int8>(); +} + +// XLayoutConstrains +css::awt::Size SAL_CALL Cell::getMinimumSize() +{ + return css::awt::Size( getMinimumWidth(), getMinimumHeight() ); +} + + +css::awt::Size SAL_CALL Cell::getPreferredSize() +{ + return getMinimumSize(); +} + + +css::awt::Size SAL_CALL Cell::calcAdjustedSize( const css::awt::Size& aNewSize ) +{ + return aNewSize; +} + + +// XMergeableCell + + +sal_Int32 SAL_CALL Cell::getRowSpan() +{ + return mnRowSpan; +} + + +sal_Int32 SAL_CALL Cell::getColumnSpan() +{ + return mnColSpan; +} + + +sal_Bool SAL_CALL Cell::isMerged() +{ + return mbMerged; +} + + +// XCell + + +OUString SAL_CALL Cell::getFormula( ) +{ + return msFormula; +} + + +void SAL_CALL Cell::setFormula( const OUString& aFormula ) +{ + if( msFormula != aFormula ) + { + msFormula = aFormula; + } +} + + +double SAL_CALL Cell::getValue( ) +{ + return mfValue; +} + + +void SAL_CALL Cell::setValue( double nValue ) +{ + if( mfValue != nValue ) + { + mfValue = nValue; + mnCellContentType = CellContentType_VALUE; + } +} + + +CellContentType SAL_CALL Cell::getType() +{ + return mnCellContentType; +} + + +sal_Int32 SAL_CALL Cell::getError( ) +{ + return mnError; +} + + +// XPropertySet + + +Any Cell::GetAnyForItem( SfxItemSet const & aSet, const SfxItemPropertyMapEntry* pMap ) +{ + Any aAny( SvxItemPropertySet_getPropertyValue( pMap, aSet ) ); + + if( pMap->aType != aAny.getValueType() ) + { + // since the sfx uint16 item now exports a sal_Int32, we may have to fix this here + if( ( pMap->aType == ::cppu::UnoType<sal_Int16>::get()) && aAny.getValueType() == ::cppu::UnoType<sal_Int32>::get() ) + { + sal_Int32 nValue = 0; + aAny >>= nValue; + aAny <<= static_cast<sal_Int16>(nValue); + } + else + { + OSL_FAIL("GetAnyForItem() Returnvalue has wrong Type!" ); + } + } + + return aAny; +} + +Reference< XPropertySetInfo > SAL_CALL Cell::getPropertySetInfo() +{ + return mpPropSet->getPropertySetInfo(); +} + + +void SAL_CALL Cell::setPropertyValue( const OUString& rPropertyName, const Any& rValue ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(rPropertyName); + if( pMap ) + { + if( (pMap->nFlags & PropertyAttribute::READONLY ) != 0 ) + throw PropertyVetoException(); + + switch( pMap->nWID ) + { + case OWN_ATTR_STYLE: + { + Reference< XStyle > xStyle; + if( !( rValue >>= xStyle ) ) + throw IllegalArgumentException(); + + SfxUnoStyleSheet* pStyle = SfxUnoStyleSheet::getUnoStyleSheet(xStyle); + SetStyleSheet( pStyle, true ); + return; + } + case OWN_ATTR_TABLEBORDER: + { + auto pBorder = o3tl::tryAccess<TableBorder>(rValue); + if(!pBorder) + break; + + SvxBoxItem aBox( SDRATTR_TABLE_BORDER ); + SvxBoxInfoItem aBoxInfo( SDRATTR_TABLE_BORDER_INNER ); + SvxBorderLine aLine; + + bool bSet = SvxBoxItem::LineToSvxLine(pBorder->TopLine, aLine, false); + aBox.SetLine(bSet ? &aLine : nullptr, SvxBoxItemLine::TOP); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::TOP, pBorder->IsTopLineValid); + + bSet = SvxBoxItem::LineToSvxLine(pBorder->BottomLine, aLine, false); + aBox.SetLine(bSet ? &aLine : nullptr, SvxBoxItemLine::BOTTOM); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::BOTTOM, pBorder->IsBottomLineValid); + + bSet = SvxBoxItem::LineToSvxLine(pBorder->LeftLine, aLine, false); + aBox.SetLine(bSet ? &aLine : nullptr, SvxBoxItemLine::LEFT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::LEFT, pBorder->IsLeftLineValid); + + bSet = SvxBoxItem::LineToSvxLine(pBorder->RightLine, aLine, false); + aBox.SetLine(bSet ? &aLine : nullptr, SvxBoxItemLine::RIGHT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::RIGHT, pBorder->IsRightLineValid); + + bSet = SvxBoxItem::LineToSvxLine(pBorder->HorizontalLine, aLine, false); + aBoxInfo.SetLine(bSet ? &aLine : nullptr, SvxBoxInfoItemLine::HORI); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI, pBorder->IsHorizontalLineValid); + + bSet = SvxBoxItem::LineToSvxLine(pBorder->VerticalLine, aLine, false); + aBoxInfo.SetLine(bSet ? &aLine : nullptr, SvxBoxInfoItemLine::VERT); + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT, pBorder->IsVerticalLineValid); + + aBox.SetAllDistances(pBorder->Distance); //TODO + aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE, pBorder->IsDistanceValid); + + mpProperties->SetObjectItem(aBox); + mpProperties->SetObjectItem(aBoxInfo); + return; + } + case OWN_ATTR_FILLBMP_MODE: + { + BitmapMode eMode; + if(!(rValue >>= eMode) ) + { + sal_Int32 nMode = 0; + if(!(rValue >>= nMode)) + throw IllegalArgumentException(); + + eMode = static_cast<BitmapMode>(nMode); + } + + mpProperties->SetObjectItem( XFillBmpStretchItem( eMode == BitmapMode_STRETCH ) ); + mpProperties->SetObjectItem( XFillBmpTileItem( eMode == BitmapMode_REPEAT ) ); + return; + } + case SDRATTR_TABLE_TEXT_ROTATION: + { + sal_Int32 nRotVal = 0; + if (!(rValue >>= nRotVal)) + throw IllegalArgumentException(); + + if (nRotVal != 27000 && nRotVal != 9000 && nRotVal != 0) + throw IllegalArgumentException(); + + mpProperties->SetObjectItem(SvxTextRotateItem(Degree10(nRotVal/10), SDRATTR_TABLE_TEXT_ROTATION)); + return; + } + default: + { + SfxItemSet aSet(GetObject().getSdrModelFromSdrObject().GetItemPool(), pMap->nWID, pMap->nWID); + aSet.Put(mpProperties->GetItem(pMap->nWID)); + + bool bSpecial = false; + + switch( pMap->nWID ) + { + case XATTR_FILLBITMAP: + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_FILLFLOATTRANSPARENCE: + case XATTR_LINEEND: + case XATTR_LINESTART: + case XATTR_LINEDASH: + { + if( pMap->nMemberId == MID_NAME ) + { + OUString aApiName; + if( rValue >>= aApiName ) + { + if(SvxShape::SetFillAttribute(pMap->nWID, aApiName, aSet, &GetObject().getSdrModelFromSdrObject())) + bSpecial = true; + } + } + } + break; + } + + if( !bSpecial ) + { + + if( !SvxUnoTextRangeBase::SetPropertyValueHelper( pMap, rValue, aSet )) + { + if( aSet.GetItemState( pMap->nWID ) != SfxItemState::SET ) + { + // fetch the default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + aSet.Put(GetObject().getSdrModelFromSdrObject().GetItemPool().GetDefaultItem(pMap->nWID)); + } + + if( aSet.GetItemState( pMap->nWID ) == SfxItemState::SET ) + { + SvxItemPropertySet_setPropertyValue( pMap, rValue, aSet ); + } + } + } + + GetObject().getSdrModelFromSdrObject().SetChanged(); + mpProperties->SetMergedItemSetAndBroadcast( aSet ); + return; + } + } + } + throw UnknownPropertyException( rPropertyName, static_cast<cppu::OWeakObject*>(this)); +} + + +Any SAL_CALL Cell::getPropertyValue( const OUString& PropertyName ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName); + if( pMap ) + { + switch( pMap->nWID ) + { + case OWN_ATTR_STYLE: + { + return Any( Reference< XStyle >( dynamic_cast< SfxUnoStyleSheet* >( GetStyleSheet() ) ) ); + } + case OWN_ATTR_TABLEBORDER: + { + const SvxBoxInfoItem& rBoxInfoItem = mpProperties->GetItem(SDRATTR_TABLE_BORDER_INNER); + const SvxBoxItem& rBox = mpProperties->GetItem(SDRATTR_TABLE_BORDER); + + TableBorder aTableBorder; + aTableBorder.TopLine = SvxBoxItem::SvxLineToLine(rBox.GetTop(), false); + aTableBorder.IsTopLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::TOP); + aTableBorder.BottomLine = SvxBoxItem::SvxLineToLine(rBox.GetBottom(), false); + aTableBorder.IsBottomLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::BOTTOM); + aTableBorder.LeftLine = SvxBoxItem::SvxLineToLine(rBox.GetLeft(), false); + aTableBorder.IsLeftLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::LEFT); + aTableBorder.RightLine = SvxBoxItem::SvxLineToLine(rBox.GetRight(), false); + aTableBorder.IsRightLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::RIGHT ); + aTableBorder.HorizontalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetHori(), false); + aTableBorder.IsHorizontalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::HORI); + aTableBorder.VerticalLine = SvxBoxItem::SvxLineToLine(rBoxInfoItem.GetVert(), false); + aTableBorder.IsVerticalLineValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::VERT); + aTableBorder.Distance = rBox.GetSmallestDistance(); + aTableBorder.IsDistanceValid = rBoxInfoItem.IsValid(SvxBoxInfoItemValidFlags::DISTANCE); + + return Any( aTableBorder ); + } + case OWN_ATTR_FILLBMP_MODE: + { + const XFillBmpStretchItem& rStretchItem = mpProperties->GetItem(XATTR_FILLBMP_STRETCH); + const XFillBmpTileItem& rTileItem = mpProperties->GetItem(XATTR_FILLBMP_TILE); + if( rTileItem.GetValue() ) + { + return Any( BitmapMode_REPEAT ); + } + else if( rStretchItem.GetValue() ) + { + return Any( BitmapMode_STRETCH ); + } + else + { + return Any( BitmapMode_NO_REPEAT ); + } + } + case SDRATTR_TABLE_TEXT_ROTATION: + { + const SvxTextRotateItem& rTextRotate = mpProperties->GetItem(SDRATTR_TABLE_TEXT_ROTATION); + return Any(sal_Int32(to<Degree100>(rTextRotate.GetValue()))); + } + default: + { + SfxItemSet aSet(GetObject().getSdrModelFromSdrObject().GetItemPool(), pMap->nWID, pMap->nWID); + aSet.Put(mpProperties->GetItem(pMap->nWID)); + + Any aAny; + if(!SvxUnoTextRangeBase::GetPropertyValueHelper( aSet, pMap, aAny )) + { + if(!aSet.Count()) + { + // fetch the default from ItemPool + if(SfxItemPool::IsWhich(pMap->nWID)) + aSet.Put(GetObject().getSdrModelFromSdrObject().GetItemPool().GetDefaultItem(pMap->nWID)); + } + + if( aSet.Count() ) + aAny = GetAnyForItem( aSet, pMap ); + } + + return aAny; + } + } + } + throw UnknownPropertyException( PropertyName, static_cast<cppu::OWeakObject*>(this)); +} + + +void SAL_CALL Cell::addPropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL Cell::removePropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*aListener*/ ) +{ +} + + +void SAL_CALL Cell::addVetoableChangeListener( const OUString& /*PropertyName*/, const Reference< XVetoableChangeListener >& /*aListener*/ ) +{ +} + + +void SAL_CALL Cell::removeVetoableChangeListener( const OUString& /*PropertyName*/, const Reference< XVetoableChangeListener >& /*aListener*/ ) +{ +} + + +// XMultiPropertySet + + +void SAL_CALL Cell::setPropertyValues( const Sequence< OUString >& aPropertyNames, const Sequence< Any >& aValues ) +{ + ::SolarMutexGuard aSolarGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const sal_Int32 nCount = aPropertyNames.getLength(); + + const OUString* pNames = aPropertyNames.getConstArray(); + const Any* pValues = aValues.getConstArray(); + + for( sal_Int32 nIdx = 0; nIdx < nCount; nIdx++, pNames++, pValues++ ) + { + try + { + setPropertyValue( *pNames, *pValues ); + } + catch( UnknownPropertyException& ) + { + TOOLS_WARN_EXCEPTION("svx.table", "unknown property!"); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + } +} + + +Sequence< Any > SAL_CALL Cell::getPropertyValues( const Sequence< OUString >& aPropertyNames ) +{ + ::SolarMutexGuard aSolarGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const sal_Int32 nCount = aPropertyNames.getLength(); + Sequence< Any > aRet( nCount ); + Any* pValue = aRet.getArray(); + + for( const OUString& rName : aPropertyNames ) + { + try + { + *pValue = getPropertyValue( rName ); + } + catch( UnknownPropertyException& ) + { + TOOLS_WARN_EXCEPTION("svx.table", "unknown property!"); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + pValue++; + } + + return aRet; +} + + +void SAL_CALL Cell::addPropertiesChangeListener( const Sequence< OUString >& /*aPropertyNames*/, const Reference< XPropertiesChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL Cell::removePropertiesChangeListener( const Reference< XPropertiesChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL Cell::firePropertiesChangeEvent( const Sequence< OUString >& /*aPropertyNames*/, const Reference< XPropertiesChangeListener >& /*xListener*/ ) +{ +} + + +// XPropertyState + + +PropertyState SAL_CALL Cell::getPropertyState( const OUString& PropertyName ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName); + + if( pMap ) + { + PropertyState eState; + switch( pMap->nWID ) + { + case OWN_ATTR_FILLBMP_MODE: + { + const SfxItemSet& rSet = mpProperties->GetMergedItemSet(); + + const bool bStretch = rSet.GetItemState( XATTR_FILLBMP_STRETCH, false ) == SfxItemState::SET; + const bool bTile = rSet.GetItemState( XATTR_FILLBMP_TILE, false ) == SfxItemState::SET; + if( bStretch || bTile ) + { + eState = PropertyState_DIRECT_VALUE; + } + else + { + eState = PropertyState_DEFAULT_VALUE; + } + break; + } + case OWN_ATTR_STYLE: + { + return PropertyState_DIRECT_VALUE; + } + case OWN_ATTR_TABLEBORDER: + { + const SfxItemSet& rSet = mpProperties->GetMergedItemSet(); + if( (rSet.GetItemState( SDRATTR_TABLE_BORDER_INNER, false ) == SfxItemState::DEFAULT) && (rSet.GetItemState( SDRATTR_TABLE_BORDER, false ) == SfxItemState::DEFAULT) ) + return PropertyState_DEFAULT_VALUE; + + return PropertyState_DIRECT_VALUE; + } + default: + { + const SfxItemSet& rSet = mpProperties->GetMergedItemSet(); + + switch( rSet.GetItemState( pMap->nWID, false ) ) + { + case SfxItemState::SET: + eState = PropertyState_DIRECT_VALUE; + break; + case SfxItemState::DEFAULT: + eState = PropertyState_DEFAULT_VALUE; + break; + default: + eState = PropertyState_AMBIGUOUS_VALUE; + break; + } + + // if an item is set, this doesn't mean we want it :) + if( PropertyState_DIRECT_VALUE == eState ) + { + switch( pMap->nWID ) + { + // the following items are disabled by changing the + // fill style or the line style. so there is no need + // to export items without names which should be empty + case XATTR_FILLBITMAP: + case XATTR_FILLGRADIENT: + case XATTR_FILLHATCH: + case XATTR_LINEDASH: + { + const NameOrIndex* pItem = rSet.GetItem<NameOrIndex>(pMap->nWID); + if( ( pItem == nullptr ) || pItem->GetName().isEmpty() ) + eState = PropertyState_DEFAULT_VALUE; + } + break; + + // #i36115# + // If e.g. the LineStart is on NONE and thus the string has length 0, it still + // may be a hard attribute covering the set LineStart of the parent (Style). + // #i37644# + // same is for fill float transparency + case XATTR_LINEEND: + case XATTR_LINESTART: + case XATTR_FILLFLOATTRANSPARENCE: + { + const NameOrIndex* pItem = rSet.GetItem<NameOrIndex>(pMap->nWID); + if( pItem == nullptr ) + eState = PropertyState_DEFAULT_VALUE; + } + break; + case XATTR_FILLCOLOR: + if (pMap->nMemberId == MID_COLOR_THEME_INDEX) + { + const XFillColorItem* pColor = rSet.GetItem<XFillColorItem>(pMap->nWID); + if (pColor->GetThemeColor().GetThemeIndex() == -1) + { + eState = PropertyState_DEFAULT_VALUE; + } + } + else if (pMap->nMemberId == MID_COLOR_LUM_MOD) + { + const XFillColorItem* pColor = rSet.GetItem<XFillColorItem>(pMap->nWID); + if (pColor->GetThemeColor().GetLumMod() == 10000) + { + eState = PropertyState_DEFAULT_VALUE; + } + } + else if (pMap->nMemberId == MID_COLOR_LUM_OFF) + { + const XFillColorItem* pColor = rSet.GetItem<XFillColorItem>(pMap->nWID); + if (pColor->GetThemeColor().GetLumOff() == 0) + { + eState = PropertyState_DEFAULT_VALUE; + } + } + } + } + } + } + return eState; + } + throw UnknownPropertyException(PropertyName); +} + + +Sequence< PropertyState > SAL_CALL Cell::getPropertyStates( const Sequence< OUString >& aPropertyName ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const sal_Int32 nCount = aPropertyName.getLength(); + Sequence< PropertyState > aRet( nCount ); + + std::transform(aPropertyName.begin(), aPropertyName.end(), aRet.getArray(), + [this](const OUString& rName) -> PropertyState { + try + { + return getPropertyState( rName ); + } + catch( Exception& ) + { + return PropertyState_AMBIGUOUS_VALUE; + } + }); + + return aRet; +} + + +void SAL_CALL Cell::setPropertyToDefault( const OUString& PropertyName ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(PropertyName); + if( pMap ) + { + switch( pMap->nWID ) + { + case OWN_ATTR_FILLBMP_MODE: + { + mpProperties->ClearObjectItem( XATTR_FILLBMP_STRETCH ); + mpProperties->ClearObjectItem( XATTR_FILLBMP_TILE ); + break; + } + case OWN_ATTR_STYLE: + break; + + case OWN_ATTR_TABLEBORDER: + { + mpProperties->ClearObjectItem( SDRATTR_TABLE_BORDER_INNER ); + mpProperties->ClearObjectItem( SDRATTR_TABLE_BORDER ); + break; + } + + default: + { + mpProperties->ClearObjectItem( pMap->nWID ); + } + } + + GetObject().getSdrModelFromSdrObject().SetChanged(); + return; + } + throw UnknownPropertyException( PropertyName, static_cast<cppu::OWeakObject*>(this)); +} + + +Any SAL_CALL Cell::getPropertyDefault( const OUString& aPropertyName ) +{ + ::SolarMutexGuard aGuard; + + if(mpProperties == nullptr) + throw DisposedException(); + + const SfxItemPropertyMapEntry* pMap = mpPropSet->getPropertyMapEntry(aPropertyName); + if( pMap ) + { + switch( pMap->nWID ) + { + case OWN_ATTR_FILLBMP_MODE: + return Any( BitmapMode_NO_REPEAT ); + + case OWN_ATTR_STYLE: + { + Reference< XStyle > xStyle; + return Any( xStyle ); + } + + case OWN_ATTR_TABLEBORDER: + { + TableBorder aBorder; + return Any( aBorder ); + } + + default: + { + if( SfxItemPool::IsWhich(pMap->nWID) ) + { + SfxItemSet aSet(GetObject().getSdrModelFromSdrObject().GetItemPool(), pMap->nWID, pMap->nWID); + aSet.Put(GetObject().getSdrModelFromSdrObject().GetItemPool().GetDefaultItem(pMap->nWID)); + return GetAnyForItem( aSet, pMap ); + } + } + } + } + throw UnknownPropertyException( aPropertyName, static_cast<cppu::OWeakObject*>(this)); +} + + +// XMultiPropertyStates + + +void SAL_CALL Cell::setAllPropertiesToDefault() +{ + mpProperties.reset(new sdr::properties::CellProperties( static_cast< SdrTableObj& >( GetObject() ), this )); + + SdrOutliner& rOutliner = GetObject().ImpGetDrawOutliner(); + + OutlinerParaObject* pParaObj = GetOutlinerParaObject(); + if( !pParaObj ) + return; + + rOutliner.SetText(*pParaObj); + sal_Int32 nParaCount(rOutliner.GetParagraphCount()); + + if(nParaCount) + { + ESelection aSelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL); + rOutliner.RemoveAttribs(aSelection, true, 0); + + std::optional<OutlinerParaObject> pTemp = rOutliner.CreateParaObject(0, nParaCount); + rOutliner.Clear(); + + SetOutlinerParaObject(std::move(pTemp)); + } +} + + +void SAL_CALL Cell::setPropertiesToDefault( const Sequence< OUString >& aPropertyNames ) +{ + for(const OUString& rName : aPropertyNames) + setPropertyToDefault( rName ); +} + + +Sequence< Any > SAL_CALL Cell::getPropertyDefaults( const Sequence< OUString >& aPropertyNames ) +{ + sal_Int32 nCount = aPropertyNames.getLength(); + Sequence< Any > aDefaults( nCount ); + + std::transform(aPropertyNames.begin(), aPropertyNames.end(), aDefaults.getArray(), + [this](const OUString& rName) -> Any { return getPropertyDefault(rName); }); + + return aDefaults; +} + + +// XText + + +void SAL_CALL Cell::insertTextContent( const Reference< XTextRange >& xRange, const Reference< XTextContent >& xContent, sal_Bool bAbsorb ) +{ + SvxUnoTextBase::insertTextContent( xRange, xContent, bAbsorb ); + notifyModified(); +} + + +void SAL_CALL Cell::removeTextContent( const Reference< XTextContent >& xContent ) +{ + SvxUnoTextBase::removeTextContent( xContent ); + notifyModified(); +} + + +// XSimpleText + + +void SAL_CALL Cell::insertString( const Reference< XTextRange >& xRange, const OUString& aString, sal_Bool bAbsorb ) +{ + SvxUnoTextBase::insertString( xRange, aString, bAbsorb ); + notifyModified(); +} + + +void SAL_CALL Cell::insertControlCharacter( const Reference< XTextRange >& xRange, sal_Int16 nControlCharacter, sal_Bool bAbsorb ) +{ + SvxUnoTextBase::insertControlCharacter( xRange, nControlCharacter, bAbsorb ); + notifyModified(); +} + + +// XTextRange + + +OUString SAL_CALL Cell::getString( ) +{ + maSelection.nStartPara = EE_PARA_MAX_COUNT; + return SvxUnoTextBase::getString(); +} + + +void SAL_CALL Cell::setString( const OUString& aString ) +{ + SvxUnoTextBase::setString( aString ); + notifyModified(); +} + +// XEventListener +void SAL_CALL Cell::disposing( const EventObject& /*Source*/ ) +{ + mxTable.clear(); + dispose(); +} + +void Cell::dumpAsXml(xmlTextWriterPtr pWriter, sal_Int32 nRow, sal_Int32 nCol) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("Cell")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("row"), "%" SAL_PRIdINT32, nRow); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("col"), "%" SAL_PRIdINT32, nCol); + SdrText::dumpAsXml(pWriter); + //SvxUnoTextBase::dumpAsXml(pWriter); + //mpPropSet->dumpAsXml(pWriter); + mpProperties->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/cellcursor.cxx b/svx/source/table/cellcursor.cxx new file mode 100644 index 000000000..313ec63df --- /dev/null +++ b/svx/source/table/cellcursor.cxx @@ -0,0 +1,546 @@ +/* -*- 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/lang/NoSupportException.hpp> +#include <svx/svdotable.hxx> +#include "cellcursor.hxx" +#include "tablelayouter.hxx" +#include <cell.hxx> +#include <svx/svdmodel.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::table; + + +namespace sdr::table { + +CellCursor::CellCursor( const TableModelRef & xTable, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +: CellCursorBase( xTable, nLeft, nTop, nRight, nBottom ) +{ +} + + +CellCursor::~CellCursor() +{ +} + + +// XCellCursor + + +Reference< XCell > SAL_CALL CellCursor::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) +{ + return CellRange::getCellByPosition( nColumn, nRow ); +} + + +Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +{ + return CellRange::getCellRangeByPosition( nLeft, nTop, nRight, nBottom ); +} + + +Reference< XCellRange > SAL_CALL CellCursor::getCellRangeByName( const OUString& aRange ) +{ + return CellRange::getCellRangeByName( aRange ); +} + + +// XCellCursor + + +void SAL_CALL CellCursor::gotoStart( ) +{ + mnRight = mnLeft; + mnBottom = mnTop; +} + + +void SAL_CALL CellCursor::gotoEnd( ) +{ + mnLeft = mnRight; + mnTop = mnBottom; +} + + +void SAL_CALL CellCursor::gotoNext( ) +{ + if( mxTable.is() ) + { + mnRight++; + if( mnRight >= mxTable->getColumnCount() ) + { + // if we past the last column, try skip to the row line + mnTop++; + if( mnTop >= mxTable->getRowCount() ) + { + // if we past the last row, do not move cursor at all + mnTop--; + mnRight--; + } + else + { + // restart at the first column on the next row + mnRight = 0; + } + } + } + + mnLeft = mnRight; + mnTop = mnBottom; +} + + +void SAL_CALL CellCursor::gotoPrevious( ) +{ + if( mxTable.is() ) + { + if( mnLeft > 0 ) + { + --mnLeft; + } + else if( mnTop > 0 ) + { + --mnTop; + mnLeft = mxTable->getColumnCount() - 1; + } + } + + mnRight = mnLeft; + mnBottom = mnTop; +} + + +void SAL_CALL CellCursor::gotoOffset( ::sal_Int32 nColumnOffset, ::sal_Int32 nRowOffset ) +{ + if( mxTable.is() ) + { + const sal_Int32 nLeft = mnLeft + nColumnOffset; + if( (nLeft >= 0) && (nLeft < mxTable->getColumnCount() ) ) + mnRight = mnLeft = nLeft; + + const sal_Int32 nTop = mnTop + nRowOffset; + if( (nTop >= 0) && (nTop < mxTable->getRowCount()) ) + mnTop = mnBottom = nTop; + } +} + + +// XMergeableCellCursor + + +/** returns true and the merged cell positions if a merge is valid or false if a merge is + not valid for that range */ +bool CellCursor::GetMergedSelection( CellPos& rStart, CellPos& rEnd ) +{ + rStart.mnCol = mnLeft; rStart.mnRow = mnTop; + rEnd.mnCol = mnRight; rEnd.mnRow = mnBottom; + + // single cell merge is never valid + if( mxTable.is() && ((mnLeft != mnRight) || (mnTop != mnBottom)) ) try + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( mnLeft, mnTop ).get() ) ); + + // check if first cell is merged + if( xCell.is() && xCell->isMerged() ) + findMergeOrigin( mxTable, mnLeft, mnTop, rStart.mnCol, rStart.mnRow ); + + // check if last cell is merged + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( mnRight, mnBottom ).get() ) ); + if( xCell.is() ) + { + if( xCell->isMerged() ) + { + findMergeOrigin( mxTable, mnRight, mnBottom, rEnd.mnCol, rEnd.mnRow ); + // merge not possible if selection is only one cell and all its merges + if( rEnd == rStart ) + return false; + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rEnd.mnCol, rEnd.mnRow ).get() ) ); + } + } + if( xCell.is() ) + { + rEnd.mnCol += xCell->getColumnSpan()-1; + rEnd.mnRow += xCell->getRowSpan()-1; + } + + // now check if everything is inside the given bounds + sal_Int32 nRow, nCol; + for( nRow = rStart.mnRow; nRow <= rEnd.mnRow; nRow++ ) + { + for( nCol = rStart.mnCol; nCol <= rEnd.mnCol; nCol++ ) + { + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() ) + continue; + + if( xCell->isMerged() ) + { + sal_Int32 nOriginCol, nOriginRow; + if( findMergeOrigin( mxTable, nCol, nRow, nOriginCol, nOriginRow ) ) + { + if( (nOriginCol < rStart.mnCol) || (nOriginRow < rStart.mnRow) ) + return false; + + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( nOriginCol, nOriginRow ).get() ) ); + if( xCell.is() ) + { + nOriginCol += xCell->getColumnSpan()-1; + nOriginRow += xCell->getRowSpan()-1; + + if( (nOriginCol > rEnd.mnCol) || (nOriginRow > rEnd.mnRow) ) + return false; + } + } + } + else if( ((nCol + xCell->getColumnSpan() - 1) > rEnd.mnCol) || ((nRow + xCell->getRowSpan() - 1 ) > rEnd.mnRow) ) + { + return false; + } + } + } + return true; + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + return false; +} + + +void SAL_CALL CellCursor::merge( ) +{ + CellPos aStart, aEnd; + if( !GetMergedSelection( aStart, aEnd ) ) + throw NoSupportException(); + + if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) ) + throw DisposedException(); + + SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject()); + const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo( SvxResId(STR_TABLE_MERGE) ); + + try + { + mxTable->merge( aStart.mnCol, aStart.mnRow, aEnd.mnCol - aStart.mnCol + 1, aEnd.mnRow - aStart.mnRow + 1 ); + mxTable->optimize(); + mxTable->setModified(true); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); +} + + +void CellCursor::split_column( sal_Int32 nCol, sal_Int32 nColumns, std::vector< sal_Int32 >& rLeftOvers ) +{ + const sal_Int32 nRowCount = mxTable->getRowCount(); + + sal_Int32 nNewCols = 0, nRow; + + // first check how many columns we need to add + for( nRow = mnTop; nRow <= mnBottom; ++nRow ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() && !xCell->isMerged() ) + nNewCols = std::max( nNewCols, nColumns - xCell->getColumnSpan() + 1 - rLeftOvers[nRow] ); + } + + if( nNewCols > 0 ) + { + static const OUStringLiteral sWidth(u"Width"); + Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW ); + Reference< XPropertySet > xRefColumn( xCols->getByIndex( nCol ), UNO_QUERY_THROW ); + sal_Int32 nWidth = 0; + xRefColumn->getPropertyValue( sWidth ) >>= nWidth; + const sal_Int32 nNewWidth = nWidth / (nNewCols + 1); + + // reference column gets new width + rounding errors + xRefColumn->setPropertyValue( sWidth, Any( nWidth - (nNewWidth * nNewCols) ) ); + + xCols->insertByIndex( nCol + 1, nNewCols ); + mnRight += nNewCols; + + // distribute new width + for( sal_Int32 nNewCol = nCol + nNewCols; nNewCol > nCol; --nNewCol ) + { + Reference< XPropertySet > xNewCol( xCols->getByIndex( nNewCol ), UNO_QUERY_THROW ); + xNewCol->setPropertyValue( sWidth, Any( nNewWidth ) ); + } + } + + for( nRow = 0; nRow < nRowCount; ++nRow ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() || xCell->isMerged() ) + { + if( nNewCols > 0 ) + { + // merged cells are ignored, but newly added columns will be added to leftovers + xCell.set( dynamic_cast< Cell* >(mxTable->getCellByPosition( nCol+1, nRow ).get() ) ); + if( !xCell.is() || !xCell->isMerged() ) + rLeftOvers[nRow] += nNewCols; + } + } + else + { + sal_Int32 nRowSpan = xCell->getRowSpan() - 1; + sal_Int32 nColSpan = xCell->getColumnSpan() - 1; + + if( (nRow >= mnTop) && (nRow <= mnBottom) ) + { + sal_Int32 nCellsAvailable = 1 + nColSpan + rLeftOvers[nRow]; + if( nColSpan == 0 ) + nCellsAvailable += nNewCols; + + DBG_ASSERT( nCellsAvailable > nColumns, "sdr::table::CellCursor::split_column(), somethings wrong" ); + + sal_Int32 nSplitSpan = (nCellsAvailable / (nColumns + 1)) - 1; + + sal_Int32 nSplitCol = nCol; + sal_Int32 nSplits = nColumns + 1; + while( nSplits-- ) + { + // last split eats rounding cells + if( nSplits == 0 ) + nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nColumns) - 1; + + mxTable->merge( nSplitCol, nRow, nSplitSpan + 1, nRowSpan + 1); + if( nSplits > 0 ) + nSplitCol += nSplitSpan + 1; + } + + do + { + rLeftOvers[nRow++] = 0; + } + while( nRowSpan-- ); + --nRow; + } + else + { + // cope with outside cells, merge if needed + if( nColSpan < (rLeftOvers[nRow] + nNewCols) ) + mxTable->merge( nCol, nRow, (rLeftOvers[nRow] + nNewCols) + 1, nRowSpan + 1 ); + + do + { + rLeftOvers[nRow++] = 0; // consumed + } + while( nRowSpan-- ); + --nRow; + } + } + } +} + + +void CellCursor::split_horizontal( sal_Int32 nColumns ) +{ + const sal_Int32 nRowCount = mxTable->getRowCount(); + + std::vector< sal_Int32 > aLeftOvers( nRowCount ); + + for( sal_Int32 nCol = mnRight; nCol >= mnLeft; --nCol ) + split_column( nCol, nColumns, aLeftOvers ); +} + + +void CellCursor::split_row( sal_Int32 nRow, sal_Int32 nRows, std::vector< sal_Int32 >& rLeftOvers ) +{ + const sal_Int32 nColCount = mxTable->getColumnCount(); + + sal_Int32 nNewRows = 0, nCol; + + // first check how many columns we need to add + for( nCol = mnLeft; nCol <= mnRight; ++nCol ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() && !xCell->isMerged() ) + nNewRows = std::max( nNewRows, nRows - xCell->getRowSpan() + 1 - rLeftOvers[nCol] ); + } + + if( nNewRows > 0 ) + { + static const OUStringLiteral sHeight(u"Height"); + Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW ); + Reference< XPropertySet > xRefRow( xRows->getByIndex( nRow ), UNO_QUERY_THROW ); + sal_Int32 nHeight = 0; + xRefRow->getPropertyValue( sHeight ) >>= nHeight; + const sal_Int32 nNewHeight = nHeight / (nNewRows + 1); + + // reference row gets new height + rounding errors + xRefRow->setPropertyValue( sHeight, Any( nHeight - (nNewHeight * nNewRows) ) ); + + xRows->insertByIndex( nRow + 1, nNewRows ); + mnBottom += nNewRows; + + // distribute new width + for( sal_Int32 nNewRow = nRow + nNewRows; nNewRow > nRow; --nNewRow ) + { + Reference< XPropertySet > xNewRow( xRows->getByIndex( nNewRow ), UNO_QUERY_THROW ); + xNewRow->setPropertyValue( sHeight, Any( nNewHeight ) ); + } + } + + for( nCol = 0; nCol < nColCount; ++nCol ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() || xCell->isMerged() ) + { + if( nNewRows ) + { + // merged cells are ignored, but newly added columns will be added to leftovers + xCell.set( dynamic_cast< Cell* >(mxTable->getCellByPosition( nCol, nRow+1 ).get() ) ); + if( !xCell.is() || !xCell->isMerged() ) + rLeftOvers[nCol] += nNewRows; + } + } + else + { + sal_Int32 nRowSpan = xCell->getRowSpan() - 1; + sal_Int32 nColSpan = xCell->getColumnSpan() - 1; + + if( (nCol >= mnLeft) && (nCol <= mnRight) ) + { + sal_Int32 nCellsAvailable = 1 + nRowSpan + rLeftOvers[nCol]; + if( nRowSpan == 0 ) + nCellsAvailable += nNewRows; + + DBG_ASSERT( nCellsAvailable > nRows, "sdr::table::CellCursor::split_row(), somethings wrong" ); + + sal_Int32 nSplitSpan = (nCellsAvailable / (nRows + 1)) - 1; + + sal_Int32 nSplitRow = nRow; + sal_Int32 nSplits = nRows + 1; + while( nSplits-- ) + { + // last split eats rounding cells + if( nSplits == 0 ) + nSplitSpan = nCellsAvailable - ((nSplitSpan+1) * nRows) - 1; + + mxTable->merge( nCol, nSplitRow, nColSpan + 1, nSplitSpan + 1 ); + if( nSplits > 0 ) + nSplitRow += nSplitSpan + 1; + } + + do + { + rLeftOvers[nCol++] = 0; + } + while( nColSpan-- ); + --nCol; + } + else + { + // cope with outside cells, merge if needed + if( nRowSpan < (rLeftOvers[nCol] + nNewRows) ) + mxTable->merge( nCol, nRow, nColSpan + 1, (rLeftOvers[nCol] + nNewRows) + 1 ); + + do + { + rLeftOvers[nCol++] = 0; // consumed + } + while( nColSpan-- ); + --nCol; + } + } + } +} + + +void CellCursor::split_vertical( sal_Int32 nRows ) +{ + const sal_Int32 nColCount = mxTable->getColumnCount(); + + std::vector< sal_Int32 > aLeftOvers( nColCount ); + + for( sal_Int32 nRow = mnBottom; nRow >= mnTop; --nRow ) + split_row( nRow, nRows, aLeftOvers ); +} + + +void SAL_CALL CellCursor::split( sal_Int32 nColumns, sal_Int32 nRows ) +{ + if( (nColumns < 0) || (nRows < 0) ) + throw IllegalArgumentException(); + + if( !mxTable.is() || (mxTable->getSdrTableObj() == nullptr) ) + throw DisposedException(); + + SdrModel& rModel(mxTable->getSdrTableObj()->getSdrModelFromSdrObject()); + const bool bUndo(mxTable->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo( SvxResId(STR_TABLE_SPLIT) ); + + try + { + if( nColumns > 0 ) + split_horizontal( nColumns ); + + if( nRows > 0 ) + split_vertical( nRows ); + + if( nColumns > 0 ||nRows > 0 ) + mxTable->setModified(true); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + throw NoSupportException(); + } + + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); +} + + +sal_Bool SAL_CALL CellCursor::isMergeable( ) +{ + CellPos aStart, aEnd; + return GetMergedSelection( aStart, aEnd ); +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/cellcursor.hxx b/svx/source/table/cellcursor.hxx new file mode 100644 index 000000000..e6756c926 --- /dev/null +++ b/svx/source/table/cellcursor.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_CELLCURSOR_HXX +#define INCLUDED_SVX_SOURCE_TABLE_CELLCURSOR_HXX + +#include <com/sun/star/table/XMergeableCellRange.hpp> +#include <com/sun/star/table/XCellCursor.hpp> +#include <cppuhelper/implbase.hxx> +#include "cellrange.hxx" + + +namespace sdr::table { + +struct CellPos; + +typedef ::cppu::ImplInheritanceHelper< CellRange, css::table::XCellCursor, css::table::XMergeableCellRange > CellCursorBase; + +class CellCursor final : public CellCursorBase +{ +public: + CellCursor( const TableModelRef& xTableModel, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ); + virtual ~CellCursor() override; + + // XCellRange + virtual css::uno::Reference< css::table::XCell > SAL_CALL getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByName( const OUString& aRange ) override; + + // XCellCursor + virtual void SAL_CALL gotoStart( ) override; + virtual void SAL_CALL gotoEnd( ) override; + virtual void SAL_CALL gotoNext( ) override; + virtual void SAL_CALL gotoPrevious( ) override; + virtual void SAL_CALL gotoOffset( ::sal_Int32 nColumnOffset, ::sal_Int32 nRowOffset ) override; + + // XMergeableCellRange + virtual void SAL_CALL merge( ) override; + virtual void SAL_CALL split( ::sal_Int32 Columns, ::sal_Int32 Rows ) override; + virtual sal_Bool SAL_CALL isMergeable( ) override; + +private: + bool GetMergedSelection( CellPos& rStart, CellPos& rEnd ); + + void split_column( sal_Int32 nCol, sal_Int32 nColumns, std::vector< sal_Int32 >& rLeftOvers ); + void split_horizontal( sal_Int32 nColumns ); + void split_row( sal_Int32 nRow, sal_Int32 nRows, std::vector< sal_Int32 >& rLeftOvers ); + void split_vertical( sal_Int32 nRows ); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/cellrange.cxx b/svx/source/table/cellrange.cxx new file mode 100644 index 000000000..f9941fd24 --- /dev/null +++ b/svx/source/table/cellrange.cxx @@ -0,0 +1,116 @@ +/* -*- 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/lang/IndexOutOfBoundsException.hpp> + +#include "cellrange.hxx" + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::table; + + +namespace sdr::table { + +CellRange::CellRange( const TableModelRef & xTable, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +: mxTable( xTable ) +, mnLeft(nLeft) +, mnTop(nTop) +, mnRight(nRight) +, mnBottom(nBottom) +{ +} + + +CellRange::~CellRange() +{ +} + + +// ICellRange + + +sal_Int32 CellRange::getLeft() +{ + return mnLeft; +} + +sal_Int32 CellRange::getTop() +{ + return mnTop; +} + +sal_Int32 CellRange::getRight() +{ + return mnRight; +} + +sal_Int32 CellRange::getBottom() +{ + return mnBottom; +} + +Reference< XTable > CellRange::getTable() +{ + return mxTable; +} + + +// XCellRange + + +Reference< XCell > SAL_CALL CellRange::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) +{ + return mxTable->getCellByPosition( mnLeft + nColumn, mnTop + nRow ); +} + + +Reference< XCellRange > SAL_CALL CellRange::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +{ + if( (nLeft >= 0 ) && (nTop >= 0) && (nRight >= nLeft) && (nBottom >= nTop) ) + { + nLeft += mnLeft; + nTop += mnTop; + nRight += mnLeft; + nBottom += mnTop; + + const sal_Int32 nMaxColumns = (mnRight == -1) ? mxTable->getColumnCount() : mnLeft; + const sal_Int32 nMaxRows = (mnBottom == -1) ? mxTable->getRowCount() : mnBottom; + if( (nLeft < nMaxColumns) && (nRight < nMaxColumns) && (nTop < nMaxRows) && (nBottom < nMaxRows) ) + { + return mxTable->getCellRangeByPosition( nLeft, nTop, nRight, nBottom ); + } + } + throw IndexOutOfBoundsException(); +} + + +Reference< XCellRange > SAL_CALL CellRange::getCellRangeByName( const OUString& /*aRange*/ ) +{ + return Reference< XCellRange >(); +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/cellrange.hxx b/svx/source/table/cellrange.hxx new file mode 100644 index 000000000..b4802eff0 --- /dev/null +++ b/svx/source/table/cellrange.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_CELLRANGE_HXX +#define INCLUDED_SVX_SOURCE_TABLE_CELLRANGE_HXX + +#include <com/sun/star/table/XCellRange.hpp> +#include <cppuhelper/implbase1.hxx> + +#include <tablemodel.hxx> + + +namespace sdr::table { + +class CellRange : public ::cppu::WeakAggImplHelper1< css::table::XCellRange >, public ICellRange +{ +public: + CellRange( const TableModelRef & xTable, sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ); + virtual ~CellRange() override; + + // ICellRange + virtual sal_Int32 getLeft() override; + virtual sal_Int32 getTop() override; + virtual sal_Int32 getRight() override; + virtual sal_Int32 getBottom() override; + virtual css::uno::Reference< css::table::XTable > getTable() override; + + // XCellRange + virtual css::uno::Reference< css::table::XCell > SAL_CALL getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByName( const OUString& aRange ) override; + +protected: + TableModelRef mxTable; + sal_Int32 mnLeft; + sal_Int32 mnTop; + sal_Int32 mnRight; + sal_Int32 mnBottom; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/propertyset.cxx b/svx/source/table/propertyset.cxx new file mode 100644 index 000000000..0dc90358c --- /dev/null +++ b/svx/source/table/propertyset.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 "propertyset.hxx" + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; + +namespace sdr::table { + +FastPropertySetInfo::FastPropertySetInfo( const PropertyVector& rProps ) +{ + addProperties( rProps ); +} + + +FastPropertySetInfo::~FastPropertySetInfo() +{ +} + + +void FastPropertySetInfo::addProperties( const PropertyVector& rProps ) +{ + sal_uInt32 nIndex = maProperties.size(); + sal_uInt32 nCount = rProps.size(); + maProperties.resize( nIndex + nCount ); + for( const Property& rProperty : rProps ) + { + maProperties[nIndex] = rProperty; + maMap[ rProperty.Name ] = nIndex++; + } +} + + +const Property& FastPropertySetInfo::getProperty( const OUString& aName ) +{ + PropertyMap::iterator aIter( maMap.find( aName ) ); + if( aIter == maMap.end() ) + throw UnknownPropertyException( aName, static_cast<cppu::OWeakObject*>(this)); + return maProperties[(*aIter).second]; +} + + +const Property* FastPropertySetInfo::hasProperty( const OUString& aName ) +{ + PropertyMap::iterator aIter( maMap.find( aName ) ); + if( aIter == maMap.end() ) + return nullptr; + else + return &maProperties[(*aIter).second]; +} + + +// XPropertySetInfo + + +Sequence< Property > SAL_CALL FastPropertySetInfo::getProperties() +{ + return Sequence< Property >( maProperties.data(), maProperties.size() ); +} + + +Property SAL_CALL FastPropertySetInfo::getPropertyByName( const OUString& aName ) +{ + return getProperty( aName ); +} + + +sal_Bool SAL_CALL FastPropertySetInfo::hasPropertyByName( const OUString& aName ) +{ + return hasProperty( aName ) != nullptr; +} + +FastPropertySet::FastPropertySet( const rtl::Reference< FastPropertySetInfo >& xInfo ) +: mxInfo( xInfo ) +{ +} + + +FastPropertySet::~FastPropertySet() +{ +} + + +// XPropertySet + + +Reference< XPropertySetInfo > SAL_CALL FastPropertySet::getPropertySetInfo( ) +{ + return mxInfo; +} + + +void SAL_CALL FastPropertySet::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + setFastPropertyValue( mxInfo->getProperty( aPropertyName ).Handle, aValue ); +} + + +Any SAL_CALL FastPropertySet::getPropertyValue( const OUString& aPropertyName ) +{ + return getFastPropertyValue( mxInfo->getProperty( aPropertyName ).Handle ); +} + + +void SAL_CALL FastPropertySet::addPropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ +} + + +void SAL_CALL FastPropertySet::removePropertyChangeListener( const OUString&, const Reference< XPropertyChangeListener >& ) +{ +} + + +void SAL_CALL FastPropertySet::addVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ +} + + +void SAL_CALL FastPropertySet::removeVetoableChangeListener( const OUString&, const Reference< XVetoableChangeListener >& ) +{ +} + + +// XMultiPropertySet + + +void SAL_CALL FastPropertySet::setPropertyValues( const Sequence< OUString >& aPropertyNames, const Sequence< Any >& aValues ) +{ + if( aPropertyNames.getLength() != aValues.getLength() ) + throw IllegalArgumentException(); + + const Any* pValues = aValues.getConstArray(); + for( const OUString& rPropertyName : aPropertyNames ) + { + const Property* pProperty = mxInfo->hasProperty( rPropertyName ); + if( pProperty ) try + { + setFastPropertyValue( pProperty->Handle, *pValues ); + } + catch( UnknownPropertyException& ) + { + } + pValues++; + } +} + + +Sequence< Any > SAL_CALL FastPropertySet::getPropertyValues( const Sequence< OUString >& aPropertyNames ) +{ + sal_Int32 nCount = aPropertyNames.getLength(); + Sequence< Any > aValues( nCount ); + + Any* pValues = aValues.getArray(); + for( const OUString& rPropertyName : aPropertyNames ) + { + const Property* pProperty = mxInfo->hasProperty( rPropertyName ); + if( pProperty ) try + { + *pValues = getFastPropertyValue( pProperty->Handle ); + } + catch( UnknownPropertyException& ) + { + } + pValues++; + } + return aValues; +} + + +void SAL_CALL FastPropertySet::addPropertiesChangeListener( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ +} + + +void SAL_CALL FastPropertySet::removePropertiesChangeListener( const Reference< XPropertiesChangeListener >& ) +{ +} + + +void SAL_CALL FastPropertySet::firePropertiesChangeEvent( const Sequence< OUString >&, const Reference< XPropertiesChangeListener >& ) +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/propertyset.hxx b/svx/source/table/propertyset.hxx new file mode 100644 index 000000000..69500afe0 --- /dev/null +++ b/svx/source/table/propertyset.hxx @@ -0,0 +1,96 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_PROPERTYSET_HXX +#define INCLUDED_SVX_SOURCE_TABLE_PROPERTYSET_HXX + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <rtl/ref.hxx> +#include <unordered_map> +#include <vector> + +#include <cppuhelper/implbase1.hxx> +#include <cppuhelper/implbase3.hxx> + +namespace sdr::table { + +typedef std::vector< css::beans::Property > PropertyVector; +typedef std::unordered_map< OUString, ::sal_uInt32 > PropertyMap; + +class FastPropertySetInfo : public ::cppu::WeakAggImplHelper1< css::beans::XPropertySetInfo > +{ +public: + explicit FastPropertySetInfo( const PropertyVector& rProps ); + virtual ~FastPropertySetInfo() override; + + void addProperties( const PropertyVector& rProps ); + + /// @throws css::beans::UnknownPropertyException + const css::beans::Property& getProperty( const OUString& aName ); + const css::beans::Property* hasProperty( const OUString& aName ); + + // XPropertySetInfo + virtual css::uno::Sequence< css::beans::Property > SAL_CALL getProperties( ) override; + virtual css::beans::Property SAL_CALL getPropertyByName( const OUString& aName ) override; + virtual sal_Bool SAL_CALL hasPropertyByName( const OUString& Name ) override; + +private: + PropertyVector maProperties; + PropertyMap maMap; +}; + + +class FastPropertySet : public ::cppu::WeakAggImplHelper3< css::beans::XPropertySet, css::beans::XMultiPropertySet, css::beans::XFastPropertySet > +{ +public: + explicit FastPropertySet( const rtl::Reference< FastPropertySetInfo >& xInfo ); + virtual ~FastPropertySet() override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // XMultiPropertySet +// virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) throw (css::uno::RuntimeException); + virtual void SAL_CALL setPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Sequence< css::uno::Any >& aValues ) override; + virtual css::uno::Sequence< css::uno::Any > SAL_CALL getPropertyValues( const css::uno::Sequence< OUString >& aPropertyNames ) override; + virtual void SAL_CALL addPropertiesChangeListener( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertiesChangeListener( const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + virtual void SAL_CALL firePropertiesChangeEvent( const css::uno::Sequence< OUString >& aPropertyNames, const css::uno::Reference< css::beans::XPropertiesChangeListener >& xListener ) override; + + // XFastPropertySet + virtual void SAL_CALL setFastPropertyValue( ::sal_Int32 nHandle, const css::uno::Any& aValue ) override = 0; + virtual css::uno::Any SAL_CALL getFastPropertyValue( ::sal_Int32 nHandle ) override = 0; + +private: + rtl::Reference< FastPropertySetInfo > mxInfo; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/svdotable.cxx b/svx/source/table/svdotable.cxx new file mode 100644 index 000000000..8b46feb29 --- /dev/null +++ b/svx/source/table/svdotable.cxx @@ -0,0 +1,2534 @@ +/* -*- 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/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <unotools/configmgr.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> +#include <com/sun/star/style/XStyle.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <svl/style.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outlobj.hxx> +#include <sdr/properties/textproperties.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdhdl.hxx> +#include "viewcontactoftableobj.hxx" +#include <svx/svdoutl.hxx> +#include <svx/svddrag.hxx> +#include <tablemodel.hxx> +#include <cell.hxx> +#include "tablelayouter.hxx" +#include "tablehandles.hxx" +#include <svx/sdr/table/tabledesign.hxx> +#include <svx/svdundo.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <editeng/writingmodeitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <cppuhelper/implbase.hxx> +#include <libxml/xmlwriter.h> +#include <tools/diagnose_ex.h> + +#include <boost/property_tree/ptree.hpp> + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::container::XIndexAccess; +using ::com::sun::star::style::XStyle; +using ::com::sun::star::table::XTableRows; +using ::com::sun::star::table::XTableColumns; +using ::com::sun::star::table::XTable; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::util::XModifyBroadcaster; +using sdr::properties::TextProperties; +using sdr::properties::BaseProperties; +using namespace ::com::sun::star; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::style; + +namespace sdr::table { + +namespace { + +class TableProperties : public TextProperties +{ +protected: + // create a new itemset + SfxItemSet CreateObjectSpecificItemSet(SfxItemPool& rPool) override; + +public: + // basic constructor + explicit TableProperties(SdrObject& rObj ); + + // constructor for copying, but using new object + TableProperties(const TableProperties& rProps, SdrObject& rObj ); + + // Clone() operator, normally just calls the local copy constructor + std::unique_ptr<BaseProperties> Clone(SdrObject& rObj) const override; + + virtual void ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem = nullptr) override; +}; + +} + +TableProperties::TableProperties(SdrObject& rObj) +: TextProperties(rObj) +{ +} + +TableProperties::TableProperties(const TableProperties& rProps, SdrObject& rObj) +: TextProperties(rProps, rObj) +{ +} + +std::unique_ptr<BaseProperties> TableProperties::Clone(SdrObject& rObj) const +{ + return std::unique_ptr<BaseProperties>(new TableProperties(*this, rObj)); +} + +void TableProperties::ItemChange(const sal_uInt16 nWhich, const SfxPoolItem* pNewItem) +{ + if( nWhich == SDRATTR_TEXTDIRECTION ) + AttributeProperties::ItemChange( nWhich, pNewItem ); + else + TextProperties::ItemChange( nWhich, pNewItem ); +} + +// create a new itemset +SfxItemSet TableProperties::CreateObjectSpecificItemSet(SfxItemPool& rPool) +{ + return SfxItemSet(rPool, + + // range from SdrAttrObj + svl::Items<SDRATTR_START, SDRATTR_SHADOW_LAST, + SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, + + // range for SdrTableObj + SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST, + + // range from SdrTextObj + EE_ITEMS_START, EE_ITEMS_END>); +} + +namespace { + +class TableObjectGeoData : public SdrTextObjGeoData +{ +public: + tools::Rectangle maLogicRect; +}; + +} + +TableStyleSettings::TableStyleSettings() +: mbUseFirstRow(true) +, mbUseLastRow(false) +, mbUseFirstColumn(false) +, mbUseLastColumn(false) +, mbUseRowBanding(true) +, mbUseColumnBanding(false) +{ +} + +TableStyleSettings::TableStyleSettings( const TableStyleSettings& rStyle ) +{ + (*this) = rStyle; +} + +TableStyleSettings& TableStyleSettings::operator=(const TableStyleSettings& rStyle) +{ + mbUseFirstRow = rStyle.mbUseFirstRow; + mbUseLastRow = rStyle.mbUseLastRow; + mbUseFirstColumn = rStyle.mbUseFirstColumn; + mbUseLastColumn = rStyle.mbUseLastColumn; + mbUseRowBanding = rStyle.mbUseRowBanding; + mbUseColumnBanding = rStyle.mbUseColumnBanding; + return *this; +} + +bool TableStyleSettings::operator==( const TableStyleSettings& rStyle ) const +{ + return + (mbUseFirstRow == rStyle.mbUseFirstRow) && + (mbUseLastRow == rStyle.mbUseLastRow) && + (mbUseFirstColumn == rStyle.mbUseFirstColumn) && + (mbUseLastColumn == rStyle.mbUseLastColumn) && + (mbUseRowBanding == rStyle.mbUseRowBanding) && + (mbUseColumnBanding == rStyle.mbUseColumnBanding); +} + + +class SdrTableObjImpl : public TableDesignUser, public ::cppu::WeakImplHelper< css::util::XModifyListener > +{ +public: + CellRef mxActiveCell; + TableModelRef mxTable; + SdrTableObj* mpTableObj; + std::unique_ptr<TableLayouter> mpLayouter; + CellPos maEditPos; + TableStyleSettings maTableStyle; + Reference< XIndexAccess > mxTableStyle; + std::vector<std::unique_ptr<SdrUndoAction>> maUndos; + bool mbSkipChangeLayout; + + void CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd); + + CellRef getCell( const CellPos& rPos ) const; + void LayoutTable( tools::Rectangle& rArea, bool bFitWidth, bool bFitHeight ); + + void ApplyCellStyles(); + void UpdateCells( tools::Rectangle const & rArea ); + + SdrTableObjImpl(); + virtual ~SdrTableObjImpl() override; + + void init( SdrTableObj* pTable, sal_Int32 nColumns, sal_Int32 nRows ); + void dispose(); + + sal_Int32 getColumnCount() const; + /// Get widths of the columns in the table. + std::vector<sal_Int32> getColumnWidths() const; + sal_Int32 getRowCount() const; + + void DragEdge( bool mbHorizontal, int nEdge, sal_Int32 nOffset ); + + SdrTableObjImpl& operator=( const SdrTableObjImpl& rSource ); + + // XModifyListener + virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + void update(); + + void connectTableStyle(); + void disconnectTableStyle(); + virtual bool isInUse() override; + void dumpAsXml(xmlTextWriterPtr pWriter) const; +private: + static SdrTableObjImpl* lastLayoutTable; + static tools::Rectangle lastLayoutInputRectangle; + static tools::Rectangle lastLayoutResultRectangle; + static bool lastLayoutFitWidth; + static bool lastLayoutFitHeight; + static WritingMode lastLayoutMode; + static sal_Int32 lastRowCount; + static sal_Int32 lastColCount; + static std::vector<sal_Int32> lastColWidths; + static bool rowSizeChanged; +}; + +SdrTableObjImpl* SdrTableObjImpl::lastLayoutTable = nullptr; +tools::Rectangle SdrTableObjImpl::lastLayoutInputRectangle; +tools::Rectangle SdrTableObjImpl::lastLayoutResultRectangle; +bool SdrTableObjImpl::lastLayoutFitWidth; +bool SdrTableObjImpl::lastLayoutFitHeight; +WritingMode SdrTableObjImpl::lastLayoutMode; +sal_Int32 SdrTableObjImpl::lastRowCount; +sal_Int32 SdrTableObjImpl::lastColCount; +bool SdrTableObjImpl::rowSizeChanged = false; +std::vector<sal_Int32> SdrTableObjImpl::lastColWidths; + +SdrTableObjImpl::SdrTableObjImpl() +: mpTableObj( nullptr ) +, mbSkipChangeLayout(false) +{ +} + + +SdrTableObjImpl::~SdrTableObjImpl() +{ + if( lastLayoutTable == this ) + lastLayoutTable = nullptr; +} + + +void SdrTableObjImpl::CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd) +{ + if(!mxTable.is()) + { + return; + } + + const sal_Int32 nColumns(rEnd.mnCol - rStart.mnCol + 1); + const sal_Int32 nRows(rEnd.mnRow - rStart.mnRow + 1); + + if(nColumns < 1 || nRows < 1 || nColumns > getColumnCount() || nRows > getRowCount()) + { + return; + } + + // tdf#116977 First thought was to create the new TableModel, copy data to it and then exchange + // mxTable and dispose old one. This does *not* work, even when all stuff looks nicely referenced + // and safe *because* Cell::create gets handed over the current SdrTableObj, hands it to + // ::Cell and there the local mxTable is initialized using rTableObj.getTable() (!). Due to This, + // the new created Cells in a new created TableModel based on given mpTableObj *will be disposed* + // when the old mxTable gets disposed - ARGH! + // To avoid, change strategy: Remember old TableModel, reset mxTable immediately - this is the + // SdrTableObjImpl of the current SdrTableObj anyways. Luckily, this works as intended... + + // remember old TableModel + TableModelRef xOldTable(mxTable); + + // immediately create new one and initialize. This creates ::Cell's which then will use + // the correct TableModel (accessed through SdrTableObj, but using local mxTable) + mxTable = new TableModel(mpTableObj); + mxTable->init(nColumns, nRows); + + // copy cells + for( sal_Int32 nRow = 0; nRow < nRows; ++nRow ) + { + for( sal_Int32 nCol = 0; nCol < nColumns; ++nCol ) try + { + CellRef xTargetCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xTargetCell.is() ) + xTargetCell->cloneFrom( dynamic_cast< Cell* >( xOldTable->getCellByPosition( rStart.mnCol + nCol, rStart.mnRow + nRow ).get() ) ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + } + + // copy row heights + Reference< XTableRows > xNewRows(mxTable->getRows(), css::uno::UNO_SET_THROW ); + static const OUStringLiteral sHeight( u"Height" ); + for( sal_Int32 nRow = 0; nRow < nRows; ++nRow ) + { + Reference< XPropertySet > xNewSet( xNewRows->getByIndex( nRow ), UNO_QUERY_THROW ); + xNewSet->setPropertyValue( sHeight, Any( mpLayouter->getRowHeight( rStart.mnRow + nRow ) ) ); + } + + // copy column widths + Reference< XTableColumns > xNewColumns( mxTable->getColumns(), css::uno::UNO_SET_THROW ); + static const OUStringLiteral sWidth( u"Width" ); + for( sal_Int32 nCol = 0; nCol < nColumns; ++nCol ) + { + Reference< XPropertySet > xNewSet( xNewColumns->getByIndex( nCol ), UNO_QUERY_THROW ); + xNewSet->setPropertyValue( sWidth, Any( mpLayouter->getColumnWidth( rStart.mnCol + nCol ) ) ); + } + + // reset layouter which still holds a copy to old TableModel + mpLayouter.reset(); + + // cleanup old TableModel + { + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + xOldTable->removeModifyListener( xListener ); + xOldTable->dispose(); + xOldTable.clear(); + } + + // create and hand over to new TableLayouter + mpLayouter.reset(new TableLayouter( mxTable )); + + // add needed listener to react on changes + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + mxTable->addModifyListener( xListener ); + + // Apply Style to Cells + ApplyCellStyles(); + + // layout cropped table + LayoutTable( mpTableObj->maRect, false, false ); +} + +void SdrTableObjImpl::init( SdrTableObj* pTable, sal_Int32 nColumns, sal_Int32 nRows ) +{ + mpTableObj = pTable; + mxTable = new TableModel( pTable ); + mxTable->init( nColumns, nRows ); + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + mxTable->addModifyListener( xListener ); + mpLayouter.reset(new TableLayouter( mxTable )); + LayoutTable( mpTableObj->maRect, true, true ); + mpTableObj->maLogicRect = mpTableObj->maRect; +} + + +SdrTableObjImpl& SdrTableObjImpl::operator=( const SdrTableObjImpl& rSource ) +{ + if(this == &rSource) + { + return *this; + } + + if(nullptr == mpTableObj || nullptr == rSource.mpTableObj) + { + // error: need both SdrObjects to successfully copy data + return *this; + } + + // remove evtl. listeners from local + disconnectTableStyle(); + + // reset layouter which holds a copy + mpLayouter.reset(); + + // cleanup local mxTable if used + if( mxTable.is() ) + { + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + mxTable->removeModifyListener( xListener ); + mxTable->dispose(); + mxTable.clear(); + } + + // tdf#127481: reset active cell reference + mxActiveCell.clear(); + + // copy TableStyle (short internal data) + maTableStyle = rSource.maTableStyle; + + // create/copy new mxTable. This will copy all needed cells, too + mxTable = new TableModel( mpTableObj, rSource.mxTable ); + + // create and hand over to new TableLayouter + mpLayouter.reset(new TableLayouter( mxTable )); + + // add needed listener to react on changes + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + mxTable->addModifyListener( xListener ); + + // handle TableStyle + Reference< XIndexAccess > xNewTableStyle; + SdrModel& rSourceSdrModel(rSource.mpTableObj->getSdrModelFromSdrObject()); + SdrModel& rTargetSdrModel(mpTableObj->getSdrModelFromSdrObject()); + + if(rSource.mxTableStyle.is() && &rSourceSdrModel == &rTargetSdrModel) + { + // source and target model the same -> keep current TableStyle + xNewTableStyle = rSource.mxTableStyle; + } + + if(!xNewTableStyle.is() && rSource.mxTableStyle.is()) try + { + // search in target SdrModel for that TableStyle + const OUString sStyleName( Reference< XNamed >( rSource.mxTableStyle, UNO_QUERY_THROW )->getName() ); + Reference< XStyleFamiliesSupplier > xSFS(rTargetSdrModel.getUnoModel(), UNO_QUERY_THROW ); + Reference< XNameAccess > xFamilyNameAccess( xSFS->getStyleFamilies(), css::uno::UNO_SET_THROW ); + Reference< XNameAccess > xTableFamilyAccess( xFamilyNameAccess->getByName( "table" ), UNO_QUERY_THROW ); + + if( xTableFamilyAccess->hasByName( sStyleName ) ) + { + // found table style with the same name + xTableFamilyAccess->getByName( sStyleName ) >>= xNewTableStyle; + } + else + { + // copy or? Not found, use 1st existing TableStyle (or none) + Reference< XIndexAccess > xIndexAccess( xTableFamilyAccess, UNO_QUERY_THROW ); + xIndexAccess->getByIndex( 0 ) >>= xNewTableStyle; + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + + // set that TableStyle + mxTableStyle = xNewTableStyle; + + // Apply Style to Cells + ApplyCellStyles(); + + // copy geometry + mpTableObj->maRect = mpTableObj->maLogicRect; + + // layout cloned table + LayoutTable( mpTableObj->maRect, false, false ); + + // re-connect to styles (evtl. in new SdrModel) + connectTableStyle(); + + return *this; +} + +void SdrTableObjImpl::ApplyCellStyles() +{ + if( !mxTable.is() || !mxTableStyle.is() ) + return; + + const sal_Int32 nColCount = getColumnCount(); + const sal_Int32 nRowCount = getRowCount(); + + const TableStyleSettings& rStyle = maTableStyle; + + CellPos aPos; + for( aPos.mnRow = 0; aPos.mnRow < nRowCount; ++aPos.mnRow ) + { + const bool bFirstRow = (aPos.mnRow == 0) && rStyle.mbUseFirstRow; + const bool bLastRow = (aPos.mnRow == nRowCount-1) && rStyle.mbUseLastRow; + + for( aPos.mnCol = 0; aPos.mnCol < nColCount; ++aPos.mnCol ) + { + Reference< XStyle > xStyle; + + // first and last row win first, if used and available + if( bFirstRow ) + { + mxTableStyle->getByIndex(first_row_style) >>= xStyle; + } + else if( bLastRow ) + { + mxTableStyle->getByIndex(last_row_style) >>= xStyle; + } + + if( !xStyle.is() ) + { + // next come first and last column, if used and available + if( rStyle.mbUseFirstColumn && (aPos.mnCol == 0) ) + { + mxTableStyle->getByIndex(first_column_style) >>= xStyle; + } + else if( rStyle.mbUseLastColumn && (aPos.mnCol == nColCount-1) ) + { + mxTableStyle->getByIndex(last_column_style) >>= xStyle; + } + } + + if( !xStyle.is() && rStyle.mbUseRowBanding ) + { + if( (aPos.mnRow & 1) == 0 ) + { + mxTableStyle->getByIndex(even_rows_style) >>= xStyle; + } + else + { + mxTableStyle->getByIndex(odd_rows_style) >>= xStyle; + } + } + + if( !xStyle.is() && rStyle.mbUseColumnBanding ) + { + if( (aPos.mnCol & 1) == 0 ) + { + mxTableStyle->getByIndex(even_columns_style) >>= xStyle; + } + else + { + mxTableStyle->getByIndex(odd_columns_style) >>= xStyle; + } + } + + if( !xStyle.is() ) + { + // use default cell style if non found yet + mxTableStyle->getByIndex(body_style) >>= xStyle; + } + + + if( xStyle.is() ) + { + SfxUnoStyleSheet* pStyle = SfxUnoStyleSheet::getUnoStyleSheet(xStyle); + + if( pStyle ) + { + CellRef xCell( getCell( aPos ) ); + if( xCell.is() && ( xCell->GetStyleSheet() != pStyle ) ) + { + xCell->SetStyleSheet( pStyle, true ); + } + } + } + } + } +} + + +void SdrTableObjImpl::dispose() +{ + disconnectTableStyle(); + mxTableStyle.clear(); + + mpLayouter.reset(); + + if( mxTable.is() ) + { + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + mxTable->removeModifyListener( xListener ); + mxTable->dispose(); + mxTable.clear(); + } +} + + +void SdrTableObjImpl::DragEdge( bool mbHorizontal, int nEdge, sal_Int32 nOffset ) +{ + if( !((nEdge >= 0) && mxTable.is())) + return; + + try + { + static const OUStringLiteral sSize( u"Size" ); + if( mbHorizontal ) + { + if (nEdge <= getRowCount()) + { + sal_Int32 nHeight = mpLayouter->getRowHeight( (!nEdge)?nEdge:(nEdge-1) ); + if(nEdge==0) + nHeight -= nOffset; + else + nHeight += nOffset; + Reference< XIndexAccess > xRows( mxTable->getRows(), UNO_QUERY_THROW ); + Reference< XPropertySet > xRowSet( xRows->getByIndex( (!nEdge)?nEdge:(nEdge-1) ), UNO_QUERY_THROW ); + xRowSet->setPropertyValue( sSize, Any( nHeight ) ); + rowSizeChanged = true; + } + } + else + { + /* + fixes fdo#59889 and resizing of table in edge dragging + Total vertical edges in a NxN table is N+1, indexed from 0 to N and total Columns is N, indexed from 0 to N-1 + In LTR table vertical edge responsible for dragging of column x(x=0 to N-1) is, Edge x+1 + But in RTL table vertical edge responsible for dragging of column x(x=0 to N-1, but from right to left)is, Edge x + In LTR table dragging of edge 0(for RTL table edge N) does nothing. + */ + //Todo: Implement Dragging functionality for leftmost edge of table. + if (nEdge <= getColumnCount()) + { + const bool bRTL = mpTableObj != nullptr && (mpTableObj->GetWritingMode() == WritingMode_RL_TB); + sal_Int32 nWidth; + if(bRTL) + { + nWidth = mpLayouter->getColumnWidth( nEdge ); + } + else + { + nWidth = mpLayouter->getColumnWidth( (!nEdge)?nEdge:(nEdge-1) ); + } + Reference< XIndexAccess > xCols( mxTable->getColumns(), UNO_QUERY_THROW ); + nWidth += nOffset; + if(bRTL && nEdge<getColumnCount()) + { + Reference< XPropertySet > xColSet( xCols->getByIndex( nEdge ), UNO_QUERY_THROW ); + xColSet->setPropertyValue( sSize, Any( nWidth ) ); + } + else if(!bRTL && nEdge>0) + { + Reference< XPropertySet > xColSet( xCols->getByIndex( nEdge-1 ), UNO_QUERY_THROW ); + xColSet->setPropertyValue( sSize, Any( nWidth ) ); + } + /* To prevent the table resizing on edge dragging */ + if( nEdge > 0 && nEdge < mxTable->getColumnCount() ) + { + if( bRTL ) + nEdge--; + + nWidth = mpLayouter->getColumnWidth(nEdge); + nWidth = std::max(static_cast<sal_Int32>(nWidth - nOffset), sal_Int32(0)); + + Reference<XPropertySet> xColSet(xCols->getByIndex(nEdge), UNO_QUERY_THROW); + xColSet->setPropertyValue(sSize, Any(nWidth)); + } + } + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + + +// XModifyListener + + +void SAL_CALL SdrTableObjImpl::modified( const css::lang::EventObject& /*aEvent*/ ) +{ + update(); +} + +void SdrTableObjImpl::update() +{ + // source can be the table model itself or the assigned table template + TableModelNotifyGuard aGuard( mxTable.get() ); + if( !mpTableObj ) + return; + + if( (maEditPos.mnRow >= getRowCount()) || (maEditPos.mnCol >= getColumnCount()) || (getCell( maEditPos ) != mxActiveCell) ) + { + if(maEditPos.mnRow >= getRowCount()) + maEditPos.mnRow = getRowCount()-1; + + if(maEditPos.mnCol >= getColumnCount()) + maEditPos.mnCol = getColumnCount()-1; + + mpTableObj->setActiveCell( maEditPos ); + } + + ApplyCellStyles(); + + mpTableObj->maRect = mpTableObj->maLogicRect; + LayoutTable( mpTableObj->maRect, false, false ); + + mpTableObj->SetBoundAndSnapRectsDirty(); + mpTableObj->ActionChanged(); + mpTableObj->BroadcastObjectChange(); +} + + +void SdrTableObjImpl::connectTableStyle() +{ + if( mxTableStyle.is() ) + { + Reference< XModifyBroadcaster > xBroadcaster( mxTableStyle, UNO_QUERY ); + if( xBroadcaster.is() ) + { + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + xBroadcaster->addModifyListener( xListener ); + } + } +} + + +void SdrTableObjImpl::disconnectTableStyle() +{ + if( mxTableStyle.is() ) + { + Reference< XModifyBroadcaster > xBroadcaster( mxTableStyle, UNO_QUERY ); + if( xBroadcaster.is() ) + { + Reference< XModifyListener > xListener( static_cast< css::util::XModifyListener* >(this) ); + xBroadcaster->removeModifyListener( xListener ); + } + } +} + + +bool SdrTableObjImpl::isInUse() +{ + return mpTableObj && mpTableObj->IsInserted(); +} + +void SdrTableObjImpl::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrTableObjImpl")); + if (mpLayouter) + mpLayouter->dumpAsXml(pWriter); + mxTable->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + + +// XEventListener + + +void SAL_CALL SdrTableObjImpl::disposing( const css::lang::EventObject& /*Source*/ ) +{ + mxActiveCell.clear(); + mxTable.clear(); + mpLayouter.reset(); + mpTableObj = nullptr; +} + + +CellRef SdrTableObjImpl::getCell( const CellPos& rPos ) const +{ + CellRef xCell; + if( mxTable.is() ) try + { + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ).get() ) ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + return xCell; +} + + +sal_Int32 SdrTableObjImpl::getColumnCount() const +{ + return mxTable.is() ? mxTable->getColumnCount() : 0; +} + +std::vector<sal_Int32> SdrTableObjImpl::getColumnWidths() const +{ + std::vector<sal_Int32> aRet; + + if (mxTable.is()) + aRet = mxTable->getColumnWidths(); + + return aRet; +} + +sal_Int32 SdrTableObjImpl::getRowCount() const +{ + return mxTable.is() ? mxTable->getRowCount() : 0; +} + +void SdrTableObjImpl::LayoutTable( tools::Rectangle& rArea, bool bFitWidth, bool bFitHeight ) +{ + if (utl::ConfigManager::IsFuzzing()) + return; + if(!mpLayouter) + return; + + // Optimization: SdrTableObj::SetChanged() can call this very often, repeatedly + // with the same settings, noticeably increasing load time. Skip if already done. + bool bInteractiveMightGrowBecauseTextChanged = + mpTableObj->IsReallyEdited() && (mpTableObj->IsAutoGrowHeight() || mpTableObj->IsAutoGrowWidth()); + WritingMode writingMode = mpTableObj->GetWritingMode(); + if( bInteractiveMightGrowBecauseTextChanged + || lastLayoutTable != this || lastLayoutInputRectangle != rArea + || lastLayoutFitWidth != bFitWidth || lastLayoutFitHeight != bFitHeight + || lastLayoutMode != writingMode + || lastRowCount != getRowCount() + || lastColCount != getColumnCount() + || lastColWidths != getColumnWidths() + || rowSizeChanged ) + { + lastLayoutTable = this; + lastLayoutInputRectangle = rArea; + lastLayoutFitWidth = bFitWidth; + lastLayoutFitHeight = bFitHeight; + lastLayoutMode = writingMode; + lastRowCount = getRowCount(); + lastColCount = getColumnCount(); + // Column resize, when the total width and column count of the + // table is unchanged, but re-layout is still needed. + lastColWidths = getColumnWidths(); + TableModelNotifyGuard aGuard( mxTable.get() ); + mpLayouter->LayoutTable( rArea, bFitWidth, bFitHeight ); + lastLayoutResultRectangle = rArea; + rowSizeChanged = false; + } + else + { + rArea = lastLayoutResultRectangle; + mpLayouter->UpdateBorderLayout(); + } +} + +void SdrTableObjImpl::UpdateCells( tools::Rectangle const & rArea ) +{ + if( mpLayouter && mxTable.is() ) + { + TableModelNotifyGuard aGuard( mxTable.get() ); + mpLayouter->updateCells( rArea ); + mxTable->setModified(true); + } +} + + +// BaseProperties section + + +std::unique_ptr<sdr::properties::BaseProperties> SdrTableObj::CreateObjectSpecificProperties() +{ + return std::make_unique<TableProperties>(*this); +} + + +// DrawContact section + + +std::unique_ptr<sdr::contact::ViewContact> SdrTableObj::CreateObjectSpecificViewContact() +{ + return std::make_unique<sdr::contact::ViewContactOfTableObj>(*this); +} + +SdrTableObj::SdrTableObj(SdrModel& rSdrModel) +: SdrTextObj(rSdrModel) +{ + init( 1, 1 ); +} + +SdrTableObj::SdrTableObj(SdrModel& rSdrModel, SdrTableObj const & rSource) +: SdrTextObj(rSdrModel, rSource) +{ + init( 1, 1 ); + + TableModelNotifyGuard aGuard( mpImpl.is() ? mpImpl->mxTable.get() : nullptr ); + + maLogicRect = rSource.maLogicRect; + maRect = rSource.maRect; + maGeo = rSource.maGeo; + meTextKind = rSource.meTextKind; + mbTextFrame = rSource.mbTextFrame; + maTextSize = rSource.maTextSize; + mbTextSizeDirty = rSource.mbTextSizeDirty; + mbNoShear = rSource.mbNoShear; + mbDisableAutoWidthOnDragging = rSource.mbDisableAutoWidthOnDragging; + + // use SdrTableObjImpl::operator= now to + // copy model data and other stuff (see there) + *mpImpl = *rSource.mpImpl; +} + +SdrTableObj::SdrTableObj( + SdrModel& rSdrModel, + const ::tools::Rectangle& rNewRect, + sal_Int32 nColumns, + sal_Int32 nRows) +: SdrTextObj(rSdrModel, rNewRect) + ,maLogicRect(rNewRect) +{ + if( nColumns <= 0 ) + nColumns = 1; + + if( nRows <= 0 ) + nRows = 1; + + init( nColumns, nRows ); +} + + +void SdrTableObj::init( sal_Int32 nColumns, sal_Int32 nRows ) +{ + m_bClosedObj = true; + + mpImpl = new SdrTableObjImpl; + mpImpl->init( this, nColumns, nRows ); + + // Stuff done from old SetModel: + if( !maLogicRect.IsEmpty() ) + { + maRect = maLogicRect; + mpImpl->LayoutTable( maRect, false, false ); + } +} + + +SdrTableObj::~SdrTableObj() +{ + mpImpl->dispose(); +} + + +// table stuff + + +Reference< XTable > SdrTableObj::getTable() const +{ + return mpImpl->mxTable; +} + + +bool SdrTableObj::isValid( const CellPos& rPos ) const +{ + return (rPos.mnCol >= 0) && (rPos.mnCol < mpImpl->getColumnCount()) && (rPos.mnRow >= 0) && (rPos.mnRow < mpImpl->getRowCount()); +} + + +CellPos SdrTableObj::getFirstCell() +{ + return CellPos( 0,0 ); +} + + +CellPos SdrTableObj::getLastCell() const +{ + CellPos aPos; + if( mpImpl->mxTable.is() ) + { + aPos.mnCol = mpImpl->getColumnCount()-1; + aPos.mnRow = mpImpl->getRowCount()-1; + } + return aPos; +} + + +CellPos SdrTableObj::getLeftCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + switch( GetWritingMode() ) + { + default: + case WritingMode_LR_TB: + return getPreviousCell( rPos, bEdgeTravel ); + case WritingMode_RL_TB: + return getNextCell( rPos, bEdgeTravel ); + case WritingMode_TB_RL: + return getPreviousRow( rPos, bEdgeTravel ); + } +} + + +CellPos SdrTableObj::getRightCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + switch( GetWritingMode() ) + { + default: + case WritingMode_LR_TB: + return getNextCell( rPos, bEdgeTravel ); + case WritingMode_RL_TB: + return getPreviousCell( rPos, bEdgeTravel ); + case WritingMode_TB_RL: + return getNextRow( rPos, bEdgeTravel ); + } +} + + +CellPos SdrTableObj::getUpCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + switch( GetWritingMode() ) + { + default: + case WritingMode_LR_TB: + case WritingMode_RL_TB: + return getPreviousRow( rPos, bEdgeTravel ); + case WritingMode_TB_RL: + return getPreviousCell( rPos, bEdgeTravel ); + } +} + + +CellPos SdrTableObj::getDownCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + switch( GetWritingMode() ) + { + default: + case WritingMode_LR_TB: + case WritingMode_RL_TB: + return getNextRow( rPos, bEdgeTravel ); + case WritingMode_TB_RL: + return getNextCell( rPos, bEdgeTravel ); + } +} + + +CellPos SdrTableObj::getPreviousCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + CellPos aPos( rPos ); + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( aPos ) ); + if( xCell.is() && xCell->isMerged() ) + { + sal_Int32 nTemp = 0; + findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, nTemp ); + } + + if( aPos.mnCol > 0 ) + { + --aPos.mnCol; + } + + else if( bEdgeTravel && (aPos.mnRow > 0) ) + { + aPos.mnCol = mpImpl->mxTable->getColumnCount()-1; + --aPos.mnRow; + } + } + return aPos; +} + + +CellPos SdrTableObj::getNextCell( const CellPos& rPos, bool bEdgeTravel ) const +{ + CellPos aPos( rPos ); + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( aPos ) ); + if( xCell.is() ) + { + if( xCell->isMerged() ) + { + findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, aPos.mnRow ); + + xCell = mpImpl->getCell(aPos); + + if( xCell.is() ) + { + aPos.mnCol += xCell->getColumnSpan(); + aPos.mnRow = rPos.mnRow; + } + } + else + { + aPos.mnCol += xCell->getColumnSpan(); + } + + if( aPos.mnCol < mpImpl->mxTable->getColumnCount() ) + return aPos; + + if( bEdgeTravel && ((aPos.mnRow + 1) < mpImpl->getRowCount()) ) + { + aPos.mnCol = 0; + aPos.mnRow += 1; + return aPos; + } + } + } + + // last cell reached, no traveling possible + return rPos; +} + + +CellPos SdrTableObj::getPreviousRow( const CellPos& rPos, bool bEdgeTravel ) const +{ + CellPos aPos( rPos ); + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( aPos ) ); + if( xCell.is() && xCell->isMerged() ) + { + sal_Int32 nTemp = 0; + findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, nTemp, aPos.mnRow ); + } + + if( aPos.mnRow > 0 ) + { + --aPos.mnRow; + } + else if( bEdgeTravel && (aPos.mnCol > 0) ) + { + aPos.mnRow = mpImpl->mxTable->getRowCount()-1; + --aPos.mnCol; + } + } + return aPos; +} + + +CellPos SdrTableObj::getNextRow( const CellPos& rPos, bool bEdgeTravel ) const +{ + CellPos aPos( rPos ); + + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( rPos ) ); + if( xCell.is() ) + { + if( xCell->isMerged() ) + { + findMergeOrigin( mpImpl->mxTable, aPos.mnCol, aPos.mnRow, aPos.mnCol, aPos.mnRow ); + xCell = mpImpl->getCell(aPos); + aPos.mnCol = rPos.mnCol; + } + + if( xCell.is() ) + aPos.mnRow += xCell->getRowSpan(); + + if( aPos.mnRow < mpImpl->mxTable->getRowCount() ) + return aPos; + + if( bEdgeTravel && (aPos.mnCol + 1) < mpImpl->mxTable->getColumnCount() ) + { + aPos.mnRow = 0; + aPos.mnCol += 1; + + while( aPos.mnCol < mpImpl->mxTable->getColumnCount() ) + { + xCell = mpImpl->getCell( aPos ); + if( xCell.is() && !xCell->isMerged() ) + return aPos; + aPos.mnCol += 1; + } + } + } + } + + // last position reached, no more traveling possible + return rPos; +} + + +const TableStyleSettings& SdrTableObj::getTableStyleSettings() const +{ + if( mpImpl.is()) + { + return mpImpl->maTableStyle; + } + else + { + static TableStyleSettings aTmp; + return aTmp; + } +} + + +void SdrTableObj::setTableStyleSettings( const TableStyleSettings& rStyle ) +{ + if( mpImpl.is() ) + { + mpImpl->maTableStyle = rStyle; + mpImpl->update(); + } +} + + +TableHitKind SdrTableObj::CheckTableHit( const Point& rPos, sal_Int32& rnX, sal_Int32& rnY, const sal_uInt16 aTol ) const +{ + if( !mpImpl.is() || !mpImpl->mxTable.is() ) + return TableHitKind::NONE; + + rnX = 0; + rnY = 0; + + const sal_Int32 nColCount = mpImpl->getColumnCount(); + const sal_Int32 nRowCount = mpImpl->getRowCount(); + + sal_Int32 nX = rPos.X() - maRect.Left(); + sal_Int32 nY = rPos.Y() - maRect.Top(); + + if( (nX < 0) || (nX > maRect.GetWidth()) || (nY < 0) || (nY > maRect.GetHeight() ) ) + return TableHitKind::NONE; + + // get vertical edge number and check for a hit + const bool bRTL = (GetWritingMode() == WritingMode_RL_TB); + bool bVrtHit = false; + if( !bRTL ) + { + while( rnX <= nColCount ) + { + if( nX - aTol <= 0 ) + { + bVrtHit = true; + break; + } + + if( rnX == nColCount ) + break; + + nX -= mpImpl->mpLayouter->getColumnWidth( rnX ); + if( nX < 0 ) + break; + rnX++; + } + } + else + { + rnX = nColCount; + while( rnX >= 0 ) + { + if( nX - aTol <= 0 ) + { + bVrtHit = true; + break; + } + + if( rnX == 0 ) + break; + + rnX--; + nX -= mpImpl->mpLayouter->getColumnWidth( rnX ); + if( nX < 0 ) + break; + } + } + + // rnX is now the edge number left to the pointer, if it was hit bHrzHit is also true + + // get vertical edge number and check for a hit + bool bHrzHit = false; + while( rnY <= nRowCount ) + { + if( nY - aTol <= 0 ) + { + bHrzHit = true; + break; + } + + if( rnY == nRowCount ) + break; + + nY -= mpImpl->mpLayouter->getRowHeight(rnY); + if( nY < 0 ) + break; + rnY++; + } + + // rnY is now the edge number above the pointer, if it was hit bVrtHit is also true + + if( bVrtHit && mpImpl->mpLayouter->isEdgeVisible( rnX, rnY, false ) ) + return TableHitKind::VerticallBorder; + + if( bHrzHit && mpImpl->mpLayouter->isEdgeVisible( rnX, rnY, true ) ) + return TableHitKind::HorizontalBorder; + + CellRef xCell( mpImpl->getCell( CellPos( rnX, rnY ) ) ); + if( xCell.is() && xCell->isMerged() ) + findMergeOrigin( mpImpl->mxTable, rnX, rnY, rnX, rnY ); + + if( xCell.is() ) + { + nX += mpImpl->mpLayouter->getColumnWidth( rnX ); + //Fix for fdo#62673 : non-editable cell in table on cell merge + sal_Int32 i=0; + while(xCell.is() && xCell->isMerged()) + { + nX += mpImpl->mpLayouter->getColumnWidth( rnX+i ); + i++; + if(rnX+i < nColCount) + xCell=mpImpl->getCell( CellPos( rnX+i, rnY) ); + else + break; + } + + if( nX < xCell->GetTextLeftDistance() ) + return TableHitKind::Cell; + } + + return TableHitKind::CellTextArea; +} + +const SfxItemSet& SdrTableObj::GetActiveCellItemSet() const +{ + return getActiveCell()->GetItemSet(); +} + +void SdrTableObj::setTableStyle( const Reference< XIndexAccess >& xTableStyle ) +{ + if( mpImpl.is() && (mpImpl->mxTableStyle != xTableStyle) ) + { + mpImpl->disconnectTableStyle(); + mpImpl->mxTableStyle = xTableStyle; + mpImpl->connectTableStyle(); + mpImpl->update(); + } +} + + +const Reference< XIndexAccess >& SdrTableObj::getTableStyle() const +{ + if( mpImpl.is() ) + { + return mpImpl->mxTableStyle; + } + else + { + static Reference< XIndexAccess > aTmp; + return aTmp; + } +} + + +// text stuff + + +/** returns the currently active text. */ +SdrText* SdrTableObj::getActiveText() const +{ + return getActiveCell().get(); +} + + +/** returns the nth available text. */ +SdrText* SdrTableObj::getText( sal_Int32 nIndex ) const +{ + if( mpImpl->mxTable.is() ) + { + const sal_Int32 nColCount = mpImpl->getColumnCount(); + if( nColCount ) + { + CellPos aPos( nIndex % nColCount, nIndex / nColCount ); + + CellRef xCell( mpImpl->getCell( aPos ) ); + return xCell.get(); + } + } + return nullptr; +} + + +/** returns the number of texts available for this object. */ +sal_Int32 SdrTableObj::getTextCount() const +{ + if( mpImpl->mxTable.is() ) + { + const sal_Int32 nColCount = mpImpl->getColumnCount(); + const sal_Int32 nRowCount = mpImpl->getRowCount(); + + return nColCount * nRowCount; + } + else + { + return 0; + } +} + + +/** changes the current active text */ +void SdrTableObj::setActiveText( sal_Int32 nIndex ) +{ + if( mpImpl.is() && mpImpl->mxTable.is() ) + { + const sal_Int32 nColCount = mpImpl->mxTable->getColumnCount(); + if( nColCount ) + { + CellPos aPos( nIndex % nColCount, nIndex / nColCount ); + if( isValid( aPos ) ) + setActiveCell( aPos ); + } + } +} + + +/** returns the index of the text that contains the given point or -1 */ +sal_Int32 SdrTableObj::CheckTextHit(const Point& rPnt) const +{ + if( mpImpl.is() && mpImpl->mxTable.is() ) + { + CellPos aPos; + if( CheckTableHit( rPnt, aPos.mnCol, aPos.mnRow ) == TableHitKind::CellTextArea ) + return aPos.mnRow * mpImpl->mxTable->getColumnCount() + aPos.mnCol; + } + + return 0; +} + +SdrOutliner* SdrTableObj::GetCellTextEditOutliner( const Cell& rCell ) const +{ + if( mpImpl.is() && (mpImpl->getCell( mpImpl->maEditPos ).get() == &rCell) ) + return mpEditingOutliner; + else + return nullptr; +} + +const TableLayouter& SdrTableObj::getTableLayouter() const +{ + assert(mpImpl.is() && mpImpl->mpLayouter && "getTableLayouter() error: no mpImpl or mpLayouter (!)"); + return *(mpImpl->mpLayouter); +} + +bool SdrTableObj::IsAutoGrowHeight() const +{ + return true; +} + +bool SdrTableObj::IsAutoGrowWidth() const +{ + return true; +} + +bool SdrTableObj::HasText() const +{ + return true; +} + +bool SdrTableObj::IsTextEditActive( const CellPos& rPos ) +{ + return mpEditingOutliner && mpImpl.is() && (rPos == mpImpl->maEditPos); +} + + +void SdrTableObj::onEditOutlinerStatusEvent( EditStatus* pEditStatus ) +{ + if( (pEditStatus->GetStatusWord() & EditStatusFlags::TextHeightChanged) && mpImpl.is() && mpImpl->mpLayouter ) + { + tools::Rectangle aRect0( maRect ); + maRect = maLogicRect; + mpImpl->LayoutTable( maRect, false, false ); + SetBoundAndSnapRectsDirty(); + ActionChanged(); + BroadcastObjectChange(); + if (aRect0 != maRect) + SendUserCall(SdrUserCallType::Resize,aRect0); + } +} + + +void SdrTableObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const +{ + rInfo.bResizeFreeAllowed=true; + rInfo.bResizePropAllowed=true; + rInfo.bRotateFreeAllowed=false; + rInfo.bRotate90Allowed =false; + rInfo.bMirrorFreeAllowed=false; + rInfo.bMirror45Allowed =false; + rInfo.bMirror90Allowed =false; + + // allow transparence + rInfo.bTransparenceAllowed = true; + + rInfo.bShearAllowed =false; + rInfo.bEdgeRadiusAllowed=false; + rInfo.bCanConvToPath =false; + rInfo.bCanConvToPoly =false; + rInfo.bCanConvToPathLineToArea=false; + rInfo.bCanConvToPolyLineToArea=false; + rInfo.bCanConvToContour = false; +} + +SdrObjKind SdrTableObj::GetObjIdentifier() const +{ + return SdrObjKind::Table; +} + +void SdrTableObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, tools::Rectangle* pAnchorRect, bool /*bLineWidth*/ ) const +{ + if( mpImpl.is() ) + TakeTextRect( mpImpl->maEditPos, rOutliner, rTextRect, bNoEditText, pAnchorRect ); +} + + +void SdrTableObj::TakeTextRect( const CellPos& rPos, SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText, tools::Rectangle* pAnchorRect ) const +{ + if( !mpImpl.is()) + return; + + CellRef xCell( mpImpl->getCell( rPos ) ); + if( !xCell.is() ) + return; + + tools::Rectangle aAnkRect; + TakeTextAnchorRect( rPos, aAnkRect ); + + SdrTextVertAdjust eVAdj=xCell->GetTextVerticalAdjust(); + + EEControlBits nStat0=rOutliner.GetControlWord(); + nStat0 |= EEControlBits::AUTOPAGESIZE; + rOutliner.SetControlWord(nStat0); + rOutliner.SetMinAutoPaperSize(Size()); + rOutliner.SetMaxAutoPaperSize(aAnkRect.GetSize()); + rOutliner.SetPaperSize(aAnkRect.GetSize()); + + // #103516# New try with _BLOCK for hor and ver after completely + // supporting full width for vertical text. +// if( SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting()) +// { + rOutliner.SetMinAutoPaperSize(Size(aAnkRect.GetWidth(), 0)); +// } +// else if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting()) +// { +// rOutliner.SetMinAutoPaperSize(Size(0, aAnkRect.GetHeight())); +// } + + + // set text at outliner, maybe from edit outliner + std::optional<OutlinerParaObject> pPara; + if (xCell->GetOutlinerParaObject()) + pPara = *xCell->GetOutlinerParaObject(); + if (mpEditingOutliner && !bNoEditText && mpImpl->mxActiveCell == xCell ) + pPara = mpEditingOutliner->CreateParaObject(); + + if (pPara) + { + const bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner); + const SdrTextObj* pTestObj(rOutliner.GetTextObj()); + + if( !pTestObj || !bHitTest || (pTestObj != this) || (pTestObj->GetOutlinerParaObject() != xCell->GetOutlinerParaObject()) ) + { + if( bHitTest ) // #i33696# take back fix #i27510# + rOutliner.SetTextObj( this ); + + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pPara); + } + } + else + { + rOutliner.SetTextObj( nullptr ); + } + + rOutliner.SetUpdateLayout(true); + rOutliner.SetControlWord(nStat0); + + Point aTextPos(aAnkRect.TopLeft()); + Size aTextSiz(rOutliner.GetPaperSize()); + if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM) + { + tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height(); + if (eVAdj==SDRTEXTVERTADJUST_CENTER) + aTextPos.AdjustY(nFreeHgt/2 ); + if (eVAdj==SDRTEXTVERTADJUST_BOTTOM) + aTextPos.AdjustY(nFreeHgt ); + } + + if (pAnchorRect) + *pAnchorRect=aAnkRect; + + rTextRect=tools::Rectangle(aTextPos,aTextSiz); +} + + +const CellRef& SdrTableObj::getActiveCell() const +{ + if( mpImpl.is() ) + { + if( !mpImpl->mxActiveCell.is() ) + { + CellPos aPos; + const_cast< SdrTableObj* >(this)->setActiveCell( aPos ); + } + return mpImpl->mxActiveCell; + } + else + { + static CellRef xCell; + return xCell; + } +} + + +sal_Int32 SdrTableObj::getColumnCount() const +{ + return mpImpl.is() ? mpImpl->getColumnCount() : 0; +} + +sal_Int32 SdrTableObj::getRowCount() const +{ + return mpImpl.is() ? mpImpl->getRowCount() : 0; +} + +void SdrTableObj::changeEdge(bool bHorizontal, int nEdge, sal_Int32 nOffset) +{ + if (mpImpl.is()) + mpImpl->DragEdge(bHorizontal, nEdge, nOffset); +} + +void SdrTableObj::setActiveCell( const CellPos& rPos ) +{ + if( !(mpImpl.is() && mpImpl->mxTable.is()) ) + return; + + try + { + mpImpl->mxActiveCell.set( dynamic_cast< Cell* >( mpImpl->mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ).get() ) ); + if( mpImpl->mxActiveCell.is() && mpImpl->mxActiveCell->isMerged() ) + { + CellPos aOrigin; + findMergeOrigin( mpImpl->mxTable, rPos.mnCol, rPos.mnRow, aOrigin.mnCol, aOrigin.mnRow ); + mpImpl->mxActiveCell.set( dynamic_cast< Cell* >( mpImpl->mxTable->getCellByPosition( aOrigin.mnCol, aOrigin.mnRow ).get() ) ); + mpImpl->maEditPos = aOrigin; + } + else + { + mpImpl->maEditPos = rPos; + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + + +void SdrTableObj::getActiveCellPos( CellPos& rPos ) const +{ + rPos = mpImpl->maEditPos; +} + + +void SdrTableObj::getCellBounds( const CellPos& rPos, ::tools::Rectangle& rCellRect ) +{ + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( rPos ) ); + if( xCell.is() ) + rCellRect = xCell->getCellRect(); + } +} + + +void SdrTableObj::TakeTextAnchorRect(tools::Rectangle& rAnchorRect) const +{ + if( mpImpl.is() ) + TakeTextAnchorRect( mpImpl->maEditPos, rAnchorRect ); +} + + +void SdrTableObj::TakeTextAnchorRect( const CellPos& rPos, tools::Rectangle& rAnchorRect ) const +{ + tools::Rectangle aAnkRect(maRect); + + if( mpImpl.is() ) + { + CellRef xCell( mpImpl->getCell( rPos ) ); + if( xCell.is() ) + xCell->TakeTextAnchorRect( aAnkRect ); + } + + ImpJustifyRect(aAnkRect); + rAnchorRect=aAnkRect; +} + + +void SdrTableObj::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const +{ + if( mpImpl.is() ) + TakeTextEditArea( mpImpl->maEditPos, pPaperMin, pPaperMax, pViewInit, pViewMin ); +} + + +void SdrTableObj::TakeTextEditArea( const CellPos& rPos, Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin ) const +{ + Size aPaperMin,aPaperMax; + tools::Rectangle aViewInit; + TakeTextAnchorRect( rPos, aViewInit ); + + Size aAnkSiz(aViewInit.GetSize()); + aAnkSiz.AdjustWidth( -1 ); aAnkSiz.AdjustHeight( -1 ); // because GetSize() increments by one + + Size aMaxSiz(aAnkSiz.Width(),1000000); + Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize()); + if (aTmpSiz.Height()!=0) + aMaxSiz.setHeight(aTmpSiz.Height() ); + + CellRef xCell( mpImpl->getCell( rPos ) ); + SdrTextVertAdjust eVAdj = xCell.is() ? xCell->GetTextVerticalAdjust() : SDRTEXTVERTADJUST_TOP; + + aPaperMax=aMaxSiz; + + aPaperMin.setWidth( aAnkSiz.Width() ); + + if (pViewMin!=nullptr) + { + *pViewMin=aViewInit; + tools::Long nYFree=aAnkSiz.Height()-aPaperMin.Height(); + + if (eVAdj==SDRTEXTVERTADJUST_TOP) + { + pViewMin->AdjustBottom( -nYFree ); + } + else if (eVAdj==SDRTEXTVERTADJUST_BOTTOM) + { + pViewMin->AdjustTop(nYFree ); + } + else + { + pViewMin->AdjustTop(nYFree/2 ); + pViewMin->SetBottom(pViewMin->Top()+aPaperMin.Height() ); + } + } + + + if(IsVerticalWriting()) + aPaperMin.setWidth( 0 ); + else + aPaperMin.setHeight( 0 ); + + if (pPaperMin!=nullptr) *pPaperMin=aPaperMin; + if (pPaperMax!=nullptr) *pPaperMax=aPaperMax; + if (pViewInit!=nullptr) *pViewInit=aViewInit; +} + + +EEAnchorMode SdrTableObj::GetOutlinerViewAnchorMode() const +{ + EEAnchorMode eRet=EEAnchorMode::TopLeft; + CellRef xCell( getActiveCell() ); + if( xCell.is() ) + { + SdrTextVertAdjust eV=xCell->GetTextVerticalAdjust(); + + { + if (eV==SDRTEXTVERTADJUST_TOP) + { + eRet=EEAnchorMode::TopLeft; + } + else if (eV==SDRTEXTVERTADJUST_BOTTOM) + { + eRet=EEAnchorMode::BottomLeft; + } + else + { + eRet=EEAnchorMode::VCenterLeft; + } + } + } + return eRet; +} + + +OUString SdrTableObj::TakeObjNameSingul() const +{ + OUString sName(SvxResId(STR_ObjNameSingulTable)); + + OUString aName(GetName()); + if (!aName.isEmpty()) + sName += " '" + aName + "'"; + + return sName; +} + + +OUString SdrTableObj::TakeObjNamePlural() const +{ + return SvxResId(STR_ObjNamePluralTable); +} + + +SdrTableObj* SdrTableObj::CloneSdrObject(SdrModel& rTargetModel) const +{ + return new SdrTableObj(rTargetModel, *this); +} + + +const tools::Rectangle& SdrTableObj::GetSnapRect() const +{ + return maRect; +} + + +void SdrTableObj::NbcSetSnapRect(const tools::Rectangle& rRect) +{ + NbcSetLogicRect( rRect ); +} + + +const tools::Rectangle& SdrTableObj::GetLogicRect() const +{ + return maLogicRect; +} + + +void SdrTableObj::RecalcSnapRect() +{ +} + + +bool SdrTableObj::BegTextEdit(SdrOutliner& rOutl) +{ + if( mpEditingOutliner != nullptr ) + return false; + + mpEditingOutliner=&rOutl; + + mbInEditMode = true; + + rOutl.Init( OutlinerMode::TextObject ); + rOutl.SetRefDevice(getSdrModelFromSdrObject().GetRefDevice()); + + bool bUpdateMode = rOutl.SetUpdateLayout(false); + Size aPaperMin; + Size aPaperMax; + tools::Rectangle aEditArea; + TakeTextEditArea(&aPaperMin,&aPaperMax,&aEditArea,nullptr); + + rOutl.SetMinAutoPaperSize(aPaperMin); + rOutl.SetMaxAutoPaperSize(aPaperMax); + rOutl.SetPaperSize(aPaperMax); + + if (bUpdateMode) rOutl.SetUpdateLayout(true); + + EEControlBits nStat=rOutl.GetControlWord(); + nStat |= EEControlBits::AUTOPAGESIZE; + nStat &=~EEControlBits::STRETCHING; + rOutl.SetControlWord(nStat); + + OutlinerParaObject* pPara = GetOutlinerParaObject(); + if(pPara) + rOutl.SetText(*pPara); + + rOutl.UpdateFields(); + rOutl.ClearModifyFlag(); + + return true; +} + + +void SdrTableObj::EndTextEdit(SdrOutliner& rOutl) +{ + + if (getSdrModelFromSdrObject().IsUndoEnabled() && !mpImpl->maUndos.empty()) + { + // These actions should be on the undo stack after text edit. + for (std::unique_ptr<SdrUndoAction>& pAction : mpImpl->maUndos) + getSdrModelFromSdrObject().AddUndo( std::move(pAction)); + mpImpl->maUndos.clear(); + + getSdrModelFromSdrObject().AddUndo(getSdrModelFromSdrObject().GetSdrUndoFactory().CreateUndoGeoObject(*this)); + } + + if(rOutl.IsModified()) + { + std::optional<OutlinerParaObject> pNewText; + Paragraph* p1stPara = rOutl.GetParagraph( 0 ); + sal_Int32 nParaCnt = rOutl.GetParagraphCount(); + + if(p1stPara) + { + // to remove the grey field background + rOutl.UpdateFields(); + + // create new text object + pNewText = rOutl.CreateParaObject( 0, nParaCnt ); + } + SetOutlinerParaObject(std::move(pNewText)); + } + + mpEditingOutliner = nullptr; + rOutl.Clear(); + EEControlBits nStat = rOutl.GetControlWord(); + nStat &= ~EEControlBits::AUTOPAGESIZE; + rOutl.SetControlWord(nStat); + + mbInEditMode = false; +} + + +OutlinerParaObject* SdrTableObj::GetOutlinerParaObject() const +{ + CellRef xCell( getActiveCell() ); + if( xCell.is() ) + return xCell->GetOutlinerParaObject(); + else + return nullptr; +} + + +void SdrTableObj::NbcSetOutlinerParaObject( std::optional<OutlinerParaObject> pTextObject) +{ + CellRef xCell( getActiveCell() ); + if( !xCell.is() ) + return; + + // Update HitTestOutliner + const SdrTextObj* pTestObj(getSdrModelFromSdrObject().GetHitTestOutliner().GetTextObj()); + + if(pTestObj && pTestObj->GetOutlinerParaObject() == xCell->GetOutlinerParaObject()) + { + getSdrModelFromSdrObject().GetHitTestOutliner().SetTextObj(nullptr); + } + + xCell->SetOutlinerParaObject( std::move(pTextObject) ); + SetTextSizeDirty(); + NbcAdjustTextFrameWidthAndHeight(); +} + + +void SdrTableObj::NbcSetLogicRect(const tools::Rectangle& rRect) +{ + maLogicRect=rRect; + ImpJustifyRect(maLogicRect); + const bool bWidth = maLogicRect.getWidth() != maRect.getWidth(); + const bool bHeight = maLogicRect.getHeight() != maRect.getHeight(); + maRect = maLogicRect; + if (mpImpl->mbSkipChangeLayout) + // Avoid distributing newly available space between existing cells. + NbcAdjustTextFrameWidthAndHeight(); + else + NbcAdjustTextFrameWidthAndHeight(!bHeight, !bWidth); + SetBoundAndSnapRectsDirty(); +} + + +void SdrTableObj::AdjustToMaxRect( const tools::Rectangle& rMaxRect, bool /* bShrinkOnly = false */ ) +{ + tools::Rectangle aAdjustRect( rMaxRect ); + aAdjustRect.setHeight( GetLogicRect().getHeight() ); + SetLogicRect( aAdjustRect ); +} + + +void SdrTableObj::NbcMove(const Size& rSiz) +{ + maLogicRect.Move(rSiz); + SdrTextObj::NbcMove( rSiz ); + if( mpImpl.is() ) + mpImpl->UpdateCells( maRect ); +} + + +void SdrTableObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) +{ + tools::Rectangle aOldRect( maLogicRect ); + ResizeRect(maLogicRect,rRef,xFact,yFact); + + maRect = maLogicRect; + NbcAdjustTextFrameWidthAndHeight( maLogicRect.GetHeight() == aOldRect.GetHeight(), maLogicRect.GetWidth() == aOldRect.GetWidth() ); + SetBoundAndSnapRectsDirty(); +} + + +bool SdrTableObj::AdjustTextFrameWidthAndHeight() +{ + tools::Rectangle aNewRect(maLogicRect); + bool bRet=AdjustTextFrameWidthAndHeight(aNewRect); + if (bRet) + { + tools::Rectangle aBoundRect0; + if (m_pUserCall!=nullptr) + aBoundRect0=GetLastBoundRect(); + maRect = aNewRect; + SetBoundAndSnapRectsDirty(); + SetChanged(); + BroadcastObjectChange(); + SendUserCall(SdrUserCallType::Resize,aBoundRect0); + } + return bRet; +} + + +bool SdrTableObj::AdjustTextFrameWidthAndHeight(tools::Rectangle& rR, bool bHeight, bool bWidth) const +{ + if(rR.IsEmpty() || !mpImpl.is() || !mpImpl->mxTable.is()) + return false; + + tools::Rectangle aRectangle( rR ); + mpImpl->LayoutTable( aRectangle, !bWidth, !bHeight ); + + if( aRectangle != rR ) + { + rR = aRectangle; + return true; + } + else + { + return false; + } +} + + +void SdrTableObj::NbcReformatText() +{ + NbcAdjustTextFrameWidthAndHeight(); +} + + +bool SdrTableObj::IsVerticalWriting() const +{ + const SvxWritingModeItem& rModeItem = GetObjectItem( SDRATTR_TEXTDIRECTION ); + return rModeItem.GetValue() == css::text::WritingMode_TB_RL; +} + + +void SdrTableObj::SetVerticalWriting(bool bVertical) +{ + if(bVertical != IsVerticalWriting() ) + { + SvxWritingModeItem aModeItem( css::text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION ); + SetObjectItem( aModeItem ); + } +} + + +WritingMode SdrTableObj::GetWritingMode() const +{ + SfxStyleSheet* pStyle = GetStyleSheet(); + if ( !pStyle ) + return WritingMode_LR_TB; + + WritingMode eWritingMode = WritingMode_LR_TB; + const SfxItemSet &rSet = pStyle->GetItemSet(); + + if ( const SvxWritingModeItem *pItem = rSet.GetItemIfSet( SDRATTR_TEXTDIRECTION )) + eWritingMode = pItem->GetValue(); + + if ( const SvxFrameDirectionItem *pItem; + ( eWritingMode != WritingMode_TB_RL ) && + ( pItem = rSet.GetItemIfSet( EE_PARA_WRITINGDIR, false ) ) ) + { + if ( pItem->GetValue() == SvxFrameDirection::Horizontal_LR_TB ) + eWritingMode = WritingMode_LR_TB; + else + eWritingMode = WritingMode_RL_TB; + } + + return eWritingMode; +} + +void SdrTableObj::AddUndo(SdrUndoAction* pUndo) +{ + mpImpl->maUndos.push_back(std::unique_ptr<SdrUndoAction>(pUndo)); +} + +void SdrTableObj::SetSkipChangeLayout(bool bSkipChangeLayout) +{ + mpImpl->mbSkipChangeLayout = bSkipChangeLayout; +} + +bool SdrTableObj::IsReallyEdited() const +{ + return mpEditingOutliner && mpEditingOutliner->IsModified(); +} + +bool SdrTableObj::IsFontwork() const +{ + return false; +} + +sal_uInt32 SdrTableObj::GetHdlCount() const +{ + sal_uInt32 nCount = SdrTextObj::GetHdlCount(); + const sal_Int32 nRowCount = mpImpl->getRowCount(); + const sal_Int32 nColCount = mpImpl->getColumnCount(); + + if( nRowCount && nColCount ) + nCount += nRowCount + nColCount + 2 + 1; + + return nCount; +} + +void SdrTableObj::AddToHdlList(SdrHdlList& rHdlList) const +{ + const sal_Int32 nRowCount = mpImpl->getRowCount(); + const sal_Int32 nColCount = mpImpl->getColumnCount(); + + // first add row handles + std::vector<TableEdgeHdl*> aRowEdges(nRowCount + 1); + for (auto const & rEdge : mpImpl->mpLayouter->getHorizontalEdges()) + { + Point aPoint(maRect.TopLeft()); + aPoint.AdjustY(rEdge.nPosition); + + std::unique_ptr<TableEdgeHdl> pHdl(new TableEdgeHdl(aPoint, true, rEdge.nMin, rEdge.nMax, nColCount + 1)); + pHdl->SetPointNum(rEdge.nIndex); + aRowEdges[rEdge.nIndex] = pHdl.get(); + rHdlList.AddHdl(std::move(pHdl)); + } + + // second add column handles + std::vector<TableEdgeHdl*> aColEdges(nColCount + 1); + for (auto const & rEdge : mpImpl->mpLayouter->getVerticalEdges()) + { + Point aPoint(maRect.TopLeft()); + aPoint.AdjustX(rEdge.nPosition); + + std::unique_ptr<TableEdgeHdl> pHdl(new TableEdgeHdl(aPoint, false, rEdge.nMin, rEdge.nMax, nRowCount + 1)); + pHdl->SetPointNum(rEdge.nIndex); + aColEdges[rEdge.nIndex] = pHdl.get(); + rHdlList.AddHdl(std::move(pHdl)); + } + + // now add visible edges to row and column handles + if( mpImpl->mpLayouter ) + { + TableLayouter& rLayouter = *mpImpl->mpLayouter; + + sal_Int32 nY = 0; + + for( sal_Int32 nRow = 0; nRow <= nRowCount; ++nRow ) + { + const sal_Int32 nRowHeight = (nRow == nRowCount) ? 0 : rLayouter.getRowHeight(nRow); + sal_Int32 nX = 0; + + for( sal_Int32 nCol = 0; nCol <= nColCount; ++nCol ) + { + const sal_Int32 nColWidth = (nCol == nColCount) ? 0 : rLayouter.getColumnWidth(nCol); + + if( nRowHeight > 0 ) + { + if( rLayouter.isEdgeVisible( nCol, nRow, false ) ) + aColEdges[nCol]->SetEdge( nRow, nY, nY + nRowHeight, (rLayouter.getBorderLine( nCol, nRow, false ) == nullptr) ? Visible : Invisible); + } + + if( nColWidth > 0 ) + { + if( rLayouter.isEdgeVisible( nCol, nRow, true ) ) + aRowEdges[nRow]->SetEdge( nCol, nX, nX + nColWidth, (rLayouter.getBorderLine( nCol, nRow, true ) == nullptr) ? Visible : Invisible); + } + + nX += nColWidth; + } + + nY += nRowHeight; + } + } + + // add remaining handles + SdrHdlList tempList(nullptr); + tempList.AddHdl( std::make_unique<TableBorderHdl>( maRect, !IsTextEditActive() ) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.TopLeft(),SdrHdlKind::UpperLeft) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.TopCenter(),SdrHdlKind::Upper) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.TopRight(),SdrHdlKind::UpperRight) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.LeftCenter(),SdrHdlKind::Left) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.RightCenter(),SdrHdlKind::Right) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.BottomLeft(),SdrHdlKind::LowerLeft) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.BottomCenter(),SdrHdlKind::Lower) ); + tempList.AddHdl( std::make_unique<SdrHdl>(maRect.BottomRight(),SdrHdlKind::LowerRight) ); + for( size_t nHdl = 0; nHdl < tempList.GetHdlCount(); ++nHdl ) + tempList.GetHdl(nHdl)->SetMoveOutside(true); + tempList.MoveTo(rHdlList); + + const size_t nHdlCount = rHdlList.GetHdlCount(); + for( size_t nHdl = 0; nHdl < nHdlCount; ++nHdl ) + rHdlList.GetHdl(nHdl)->SetObj(const_cast<SdrTableObj*>(this)); +} + +// Dragging + +bool SdrTableObj::hasSpecialDrag() const +{ + return true; +} + +bool SdrTableObj::beginSpecialDrag(SdrDragStat& rDrag) const +{ + const SdrHdl* pHdl = rDrag.GetHdl(); + const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind()); + + switch( eHdl ) + { + case SdrHdlKind::UpperLeft: + case SdrHdlKind::Upper: + case SdrHdlKind::UpperRight: + case SdrHdlKind::Left: + case SdrHdlKind::Right: + case SdrHdlKind::LowerLeft: + case SdrHdlKind::Lower: + case SdrHdlKind::LowerRight: + case SdrHdlKind::Move: + { + break; + } + + case SdrHdlKind::User: + { + rDrag.SetEndDragChangesAttributes(false); + rDrag.SetNoSnap(); + break; + } + + default: + { + return false; + } + } + + return true; +} + +bool SdrTableObj::applySpecialDrag(SdrDragStat& rDrag) +{ + bool bRet(true); + const SdrHdl* pHdl = rDrag.GetHdl(); + const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind()); + + switch( eHdl ) + { + case SdrHdlKind::UpperLeft: + case SdrHdlKind::Upper: + case SdrHdlKind::UpperRight: + case SdrHdlKind::Left: + case SdrHdlKind::Right: + case SdrHdlKind::LowerLeft: + case SdrHdlKind::Lower: + case SdrHdlKind::LowerRight: + { + const tools::Rectangle aNewRectangle(ImpDragCalcRect(rDrag)); + + if (aNewRectangle != maRect) + { + NbcSetLogicRect(aNewRectangle); + } + + break; + } + + case SdrHdlKind::Move: + { + NbcMove( Size( rDrag.GetDX(), rDrag.GetDY() ) ); + break; + } + + case SdrHdlKind::User: + { + rDrag.SetEndDragChangesAttributes(false); + rDrag.SetNoSnap(); + const TableEdgeHdl* pEdgeHdl = dynamic_cast< const TableEdgeHdl* >( pHdl ); + + if( pEdgeHdl ) + { + if( IsInserted() ) + { + rDrag.SetEndDragChangesAttributes(true); + rDrag.SetEndDragChangesLayout(true); + } + + mpImpl->DragEdge( pEdgeHdl->IsHorizontalEdge(), pEdgeHdl->GetPointNum(), pEdgeHdl->GetValidDragOffset( rDrag ) ); + } + break; + } + + default: + { + bRet = false; + } + } + + return bRet; +} + +basegfx::B2DPolyPolygon SdrTableObj::getSpecialDragPoly(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aRetval; + const SdrHdl* pHdl = rDrag.GetHdl(); + + if( pHdl && (SdrHdlKind::User == pHdl->GetKind()) ) + { + const TableEdgeHdl* pEdgeHdl = dynamic_cast< const TableEdgeHdl* >( pHdl ); + + if( pEdgeHdl ) + { + aRetval = pEdgeHdl->getSpecialDragPoly( rDrag ); + } + } + + return aRetval; +} + + +// Create + + +bool SdrTableObj::BegCreate(SdrDragStat& rStat) +{ + rStat.SetOrtho4Possible(); + tools::Rectangle aRect1(rStat.GetStart(), rStat.GetNow()); + aRect1.Justify(); + rStat.SetActionRect(aRect1); + maRect = aRect1; + return true; +} + + +bool SdrTableObj::MovCreate(SdrDragStat& rStat) +{ + tools::Rectangle aRect1; + rStat.TakeCreateRect(aRect1); + ImpJustifyRect(aRect1); + rStat.SetActionRect(aRect1); + maRect = aRect1; // for ObjName + SetBoundRectDirty(); + m_bSnapRectDirty=true; + return true; +} + + +bool SdrTableObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) +{ + rStat.TakeCreateRect(maRect); + ImpJustifyRect(maRect); + return (eCmd==SdrCreateCmd::ForceEnd || rStat.GetPointCount()>=2); +} + +void SdrTableObj::BrkCreate(SdrDragStat& /*rStat*/) +{ +} + + +bool SdrTableObj::BckCreate(SdrDragStat& /*rStat*/) +{ + return true; +} + + +basegfx::B2DPolyPolygon SdrTableObj::TakeCreatePoly(const SdrDragStat& rDrag) const +{ + tools::Rectangle aRect1; + rDrag.TakeCreateRect(aRect1); + aRect1.Justify(); + + basegfx::B2DPolyPolygon aRetval; + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aRect1); + aRetval.append(basegfx::utils::createPolygonFromRect(aRange)); + return aRetval; +} + + +PointerStyle SdrTableObj::GetCreatePointer() const +{ + return PointerStyle::Cross; +} + + +void SdrTableObj::createCell( CellRef& xNewCell ) +{ + xNewCell = Cell::create( *this ); +} + + +std::unique_ptr<SdrObjGeoData> SdrTableObj::NewGeoData() const +{ + return std::make_unique<TableObjectGeoData>(); +} + + +void SdrTableObj::SaveGeoData(SdrObjGeoData& rGeo) const +{ + DBG_ASSERT( dynamic_cast< TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::SaveGeoData(), illegal geo data!" ); + SdrTextObj::SaveGeoData (rGeo); + + static_cast<TableObjectGeoData &>(rGeo).maLogicRect = maLogicRect; +} + + +void SdrTableObj::RestoreGeoData(const SdrObjGeoData& rGeo) +{ + DBG_ASSERT( dynamic_cast< const TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::SaveGeoData(), illegal geo data!" ); + + maLogicRect = static_cast<const TableObjectGeoData &>(rGeo).maLogicRect; + + SdrTextObj::RestoreGeoData (rGeo); + + if( mpImpl.is() ) + mpImpl->LayoutTable(maRect, false, false); + ActionChanged(); +} + +void SdrTableObj::CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd) +{ + if(!mpImpl.is()) + { + return; + } + + mpImpl->CropTableModelToSelection(rStart, rEnd); +} + +sal_Int32 SdrTableObj::getHeightWithoutFitting() +{ + tools::Rectangle aRect{}; + if( mpImpl.is() && mpImpl->mpLayouter) + { + mpImpl->mpLayouter->LayoutTableHeight(aRect, /*bFit=*/false); + return aRect.GetHeight(); + } + else + return 0; +} + +void SdrTableObj::DistributeColumns( sal_Int32 nFirstColumn, sal_Int32 nLastColumn, const bool bOptimize, const bool bMinimize ) +{ + if( mpImpl.is() && mpImpl->mpLayouter ) + { + TableModelNotifyGuard aGuard( mpImpl->mxTable.get() ); + mpImpl->mpLayouter->DistributeColumns( maRect, nFirstColumn, nLastColumn, bOptimize, bMinimize ); + } +} + + +void SdrTableObj::DistributeRows( sal_Int32 nFirstRow, sal_Int32 nLastRow, const bool bOptimize, const bool bMinimize ) +{ + if( mpImpl.is() && mpImpl->mpLayouter ) + { + TableModelNotifyGuard aGuard( mpImpl->mxTable.get() ); + mpImpl->mpLayouter->DistributeRows( maRect, nFirstRow, nLastRow, bOptimize, bMinimize ); + } +} + + +void SdrTableObj::SetChanged() +{ + if( mpImpl.is() ) + { + mpImpl->LayoutTable( maRect, false, false ); + } + + ::SdrTextObj::SetChanged(); +} + + +void SdrTableObj::uno_lock() +{ + if( mpImpl.is() && mpImpl->mxTable.is() ) + mpImpl->mxTable->lockBroadcasts(); +} + + +void SdrTableObj::uno_unlock() +{ + if( mpImpl.is() && mpImpl->mxTable.is() ) + mpImpl->mxTable->unlockBroadcasts(); +} + +void SdrTableObj::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SdrTableObj")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + SdrObject::dumpAsXml(pWriter); + + mpImpl->dumpAsXml(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +bool SdrTableObj::createTableEdgesJson(boost::property_tree::ptree & rJsonRoot) +{ + if (!mpImpl.is() || !mpImpl->mxTable.is()) + return false; + + tools::Rectangle aRect = GetCurrentBoundRect(); + boost::property_tree::ptree aTableColumns; + { + aTableColumns.put("tableOffset", o3tl::toTwips(aRect.Left(), o3tl::Length::mm100)); + + boost::property_tree::ptree aEntries; + auto const & aEdges = mpImpl->mpLayouter->getVerticalEdges(); + for (auto & rEdge : aEdges) + { + if (rEdge.nIndex == 0) + { + aTableColumns.put("left", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + } + else if (rEdge.nIndex == sal_Int32(aEdges.size() - 1)) + { + aTableColumns.put("right", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + } + else + { + boost::property_tree::ptree aEntry; + aEntry.put("position", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + aEntry.put("min", o3tl::toTwips(rEdge.nPosition + rEdge.nMin, o3tl::Length::mm100)); + aEntry.put("max", o3tl::toTwips(rEdge.nPosition + rEdge.nMax, o3tl::Length::mm100)); + aEntry.put("hidden", false); + aEntries.push_back(std::make_pair("", aEntry)); + } + } + aTableColumns.push_back(std::make_pair("entries", aEntries)); + } + rJsonRoot.add_child("columns", aTableColumns); + + boost::property_tree::ptree aTableRows; + { + aTableRows.put("tableOffset", o3tl::toTwips(aRect.Top(), o3tl::Length::mm100)); + + boost::property_tree::ptree aEntries; + auto const & aEdges = mpImpl->mpLayouter->getHorizontalEdges(); + for (auto & rEdge : aEdges) + { + if (rEdge.nIndex == 0) + { + aTableRows.put("left", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + } + else if (rEdge.nIndex == sal_Int32(aEdges.size() - 1)) + { + aTableRows.put("right", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + } + else + { + boost::property_tree::ptree aEntry; + aEntry.put("position", o3tl::toTwips(rEdge.nPosition, o3tl::Length::mm100)); + aEntry.put("min", o3tl::toTwips(rEdge.nPosition + rEdge.nMin, o3tl::Length::mm100)); + aEntry.put("max", o3tl::toTwips(rEdge.nPosition + rEdge.nMax, o3tl::Length::mm100)); + aEntry.put("hidden", false); + aEntries.push_back(std::make_pair("", aEntry)); + } + } + aTableRows.push_back(std::make_pair("entries", aEntries)); + } + rJsonRoot.add_child("rows", aTableRows); + return true; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablecolumn.cxx b/svx/source/table/tablecolumn.cxx new file mode 100644 index 000000000..d9e33db47 --- /dev/null +++ b/svx/source/table/tablecolumn.cxx @@ -0,0 +1,289 @@ +/* -*- 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/lang/DisposedException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <tablemodel.hxx> +#include "tablecolumn.hxx" +#include "tableundo.hxx" +#include <svx/svdmodel.hxx> +#include <svx/svdotable.hxx> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::beans; + + +namespace sdr::table { + +const sal_Int32 Property_Width = 0; +const sal_Int32 Property_OptimalWidth = 1; +const sal_Int32 Property_IsVisible = 2; +const sal_Int32 Property_IsStartOfNewPage = 3; + + +// TableRow + + +TableColumn::TableColumn( const TableModelRef& xTableModel, sal_Int32 nColumn ) +: TableColumnBase( getStaticPropertySetInfo() ) +, mxTableModel( xTableModel ) +, mnColumn( nColumn ) +, mnWidth( 0 ) +, mbOptimalWidth( true ) +, mbIsVisible( true ) +, mbIsStartOfNewPage( false ) +{ +} + + +TableColumn::~TableColumn() +{ +} + + +void TableColumn::dispose() +{ + mxTableModel.clear(); +} + + +void TableColumn::throwIfDisposed() const +{ + if( !mxTableModel.is() ) + throw DisposedException(); +} + + +TableColumn& TableColumn::operator=( const TableColumn& r ) +{ + mnWidth = r.mnWidth; + mbOptimalWidth = r.mbOptimalWidth; + mbIsVisible = r.mbIsVisible; + mbIsStartOfNewPage = r.mbIsStartOfNewPage; + maName = r.maName; + mnColumn = r.mnColumn; + + return *this; +} + + +// XCellRange + + +Reference< XCell > SAL_CALL TableColumn::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) +{ + throwIfDisposed(); + if( nColumn != 0 ) + throw IndexOutOfBoundsException(); + + return mxTableModel->getCellByPosition( mnColumn, nRow ); +} + + +Reference< XCellRange > SAL_CALL TableColumn::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +{ + throwIfDisposed(); + if( (nTop >= 0 ) && (nLeft == 0) && (nBottom >= nTop) && (nRight == 0) ) + { + return mxTableModel->getCellRangeByPosition( mnColumn, nTop, mnColumn, nBottom ); + } + throw IndexOutOfBoundsException(); +} + + +Reference< XCellRange > SAL_CALL TableColumn::getCellRangeByName( const OUString& /*aRange*/ ) +{ + return Reference< XCellRange >(); +} + + +// XNamed + + +OUString SAL_CALL TableColumn::getName() +{ + return maName; +} + + +void SAL_CALL TableColumn::setName( const OUString& aName ) +{ + maName = aName; +} + + +// XFastPropertySet + + +void SAL_CALL TableColumn::setFastPropertyValue( sal_Int32 nHandle, const Any& aValue ) +{ + bool bOk = false; + bool bChange = false; + + SdrModel& rModel(mxTableModel->getSdrTableObj()->getSdrModelFromSdrObject()); + std::unique_ptr<TableColumnUndo> pUndo; + + if( mxTableModel.is() && mxTableModel->getSdrTableObj() && mxTableModel->getSdrTableObj()->IsInserted() && rModel.IsUndoEnabled() ) + { + TableColumnRef xThis( this ); + pUndo.reset( new TableColumnUndo( xThis ) ); + } + + switch( nHandle ) + { + case Property_Width: + { + sal_Int32 nWidth = mnWidth; + bOk = aValue >>= nWidth; + if( bOk && (nWidth != mnWidth) ) + { + mnWidth = nWidth; + mbOptimalWidth = mnWidth == 0; + bChange = true; + } + break; + } + case Property_OptimalWidth: + { + bool bOptimalWidth = mbOptimalWidth; + bOk = aValue >>= bOptimalWidth; + if( bOk && (mbOptimalWidth != bOptimalWidth) ) + { + mbOptimalWidth = bOptimalWidth; + if( bOptimalWidth ) + mnWidth = 0; + bChange = true; + } + break; + } + case Property_IsVisible: + { + bool bIsVisible = mbIsVisible; + bOk = aValue >>= bIsVisible; + if( bOk && (mbIsVisible != bIsVisible) ) + { + mbIsVisible = bIsVisible; + bChange = true; + } + break; + } + + case Property_IsStartOfNewPage: + { + bool bIsStartOfNewPage = mbIsStartOfNewPage; + bOk = aValue >>= bIsStartOfNewPage; + if( bOk && (mbIsStartOfNewPage != bIsStartOfNewPage) ) + { + mbIsStartOfNewPage = bIsStartOfNewPage; + bChange = true; + } + break; + } + default: + throw UnknownPropertyException( OUString::number(nHandle), static_cast<cppu::OWeakObject*>(this)); + } + if( !bOk ) + { + throw IllegalArgumentException(); + } + + if( bChange ) + { + if( pUndo ) + { + rModel.AddUndo( std::move(pUndo) ); + } + mxTableModel->setModified(true); + } +} + + +Any SAL_CALL TableColumn::getFastPropertyValue( sal_Int32 nHandle ) +{ + switch( nHandle ) + { + case Property_Width: return Any( mnWidth ); + case Property_OptimalWidth: return Any( mbOptimalWidth ); + case Property_IsVisible: return Any( mbIsVisible ); + case Property_IsStartOfNewPage: return Any( mbIsStartOfNewPage ); + default: throw UnknownPropertyException( OUString::number(nHandle), static_cast<cppu::OWeakObject*>(this)); + } +} + + +rtl::Reference< FastPropertySetInfo > TableColumn::getStaticPropertySetInfo() +{ + static rtl::Reference<FastPropertySetInfo> xInfo = []() { + PropertyVector aProperties(6); + + aProperties[0].Name = "Width"; + aProperties[0].Handle = Property_Width; + aProperties[0].Type = ::cppu::UnoType<sal_Int32>::get(); + aProperties[0].Attributes = 0; + + aProperties[1].Name = "OptimalWidth"; + aProperties[1].Handle = Property_OptimalWidth; + aProperties[1].Type = cppu::UnoType<bool>::get(); + aProperties[1].Attributes = 0; + + aProperties[2].Name = "IsVisible"; + aProperties[2].Handle = Property_IsVisible; + aProperties[2].Type = cppu::UnoType<bool>::get(); + aProperties[2].Attributes = 0; + + aProperties[3].Name = "IsStartOfNewPage"; + aProperties[3].Handle = Property_IsStartOfNewPage; + aProperties[3].Type = cppu::UnoType<bool>::get(); + aProperties[3].Attributes = 0; + + aProperties[4].Name = "Size"; + aProperties[4].Handle = Property_Width; + aProperties[4].Type = ::cppu::UnoType<sal_Int32>::get(); + aProperties[4].Attributes = 0; + + aProperties[5].Name = "OptimalSize"; + aProperties[5].Handle = Property_OptimalWidth; + aProperties[5].Type = cppu::UnoType<bool>::get(); + aProperties[5].Attributes = 0; + + return rtl::Reference<FastPropertySetInfo>(new FastPropertySetInfo(aProperties)); + }(); + + return xInfo; +} + +TableModelRef const & TableColumn::getModel() const +{ + return mxTableModel; +} + +sal_Int32 TableColumn::getWidth() const +{ + return mnWidth; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablecolumn.hxx b/svx/source/table/tablecolumn.hxx new file mode 100644 index 000000000..0d1dbb802 --- /dev/null +++ b/svx/source/table/tablecolumn.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLECOLUMN_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLECOLUMN_HXX + +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <cppuhelper/implbase.hxx> + +#include "propertyset.hxx" +#include <celltypes.hxx> + + +namespace sdr::table { + +typedef ::cppu::ImplInheritanceHelper< FastPropertySet, css::table::XCellRange, css::container::XNamed > TableColumnBase; + +class TableColumn : public TableColumnBase +{ + friend class TableColumnUndo; + friend class TableModel; +public: + TableColumn( const TableModelRef& xTableModel, sal_Int32 nColumn ); + virtual ~TableColumn() override; + + void dispose(); + /// @throws css::uno::RuntimeException + void throwIfDisposed() const; + + TableColumn& operator=( const TableColumn& ); + + // XCellRange + virtual css::uno::Reference< css::table::XCell > SAL_CALL getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByName( const OUString& aRange ) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName( const OUString& aName ) override; + + // XFastPropertySet + virtual void SAL_CALL setFastPropertyValue( ::sal_Int32 nHandle, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getFastPropertyValue( ::sal_Int32 nHandle ) override; + + /// Get the table that owns this column. + TableModelRef const & getModel() const; + /// Get the width of this column. + sal_Int32 getWidth() const; + +private: + static rtl::Reference< FastPropertySetInfo > getStaticPropertySetInfo(); + + TableModelRef mxTableModel; + sal_Int32 mnColumn; + sal_Int32 mnWidth; + bool mbOptimalWidth; + bool mbIsVisible; + bool mbIsStartOfNewPage; + OUString maName; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablecolumns.cxx b/svx/source/table/tablecolumns.cxx new file mode 100644 index 000000000..c202f3a52 --- /dev/null +++ b/svx/source/table/tablecolumns.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 <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <tablemodel.hxx> +#include "tablecolumns.hxx" +#include "tablecolumn.hxx" + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::table; + + +namespace sdr::table { + +TableColumns::TableColumns( const TableModelRef& xTableModel ) +: mxTableModel( xTableModel ) +{ +} + + +TableColumns::~TableColumns() +{ + dispose(); +} + + +void TableColumns::dispose() +{ + mxTableModel.clear(); +} + + +void TableColumns::throwIfDisposed() const +{ + if( !mxTableModel.is() ) + throw DisposedException(); +} + + +// XTableRows + + +void SAL_CALL TableColumns::insertByIndex( sal_Int32 nIndex, sal_Int32 nCount ) +{ + throwIfDisposed(); + mxTableModel->insertColumns( nIndex, nCount ); +} + + +void SAL_CALL TableColumns::removeByIndex( sal_Int32 nIndex, sal_Int32 nCount ) +{ + throwIfDisposed(); + mxTableModel->removeColumns( nIndex, nCount ); +} + + +// XIndexAccess + + +sal_Int32 SAL_CALL TableColumns::getCount() +{ + throwIfDisposed(); + return mxTableModel->getColumnCount(); +} + + +Any SAL_CALL TableColumns::getByIndex( sal_Int32 Index ) +{ + throwIfDisposed(); + + if( ( Index < 0 ) || ( Index >= mxTableModel->getColumnCount() ) ) + throw IndexOutOfBoundsException(); + + return Any( Reference< XCellRange >( mxTableModel->getColumn( Index ) ) ); +} + + +// XElementAccess + + +Type SAL_CALL TableColumns::getElementType() +{ + throwIfDisposed(); + + return cppu::UnoType<XCellRange>::get(); +} + + +sal_Bool SAL_CALL TableColumns::hasElements() +{ + throwIfDisposed(); + + return mxTableModel->getColumnCount() != 0; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablecolumns.hxx b/svx/source/table/tablecolumns.hxx new file mode 100644 index 000000000..696463aac --- /dev/null +++ b/svx/source/table/tablecolumns.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLECOLUMNS_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLECOLUMNS_HXX + +#include <com/sun/star/table/XTableColumns.hpp> +#include <cppuhelper/implbase1.hxx> + +#include <celltypes.hxx> + + +namespace sdr::table { + +class TableColumns : public ::cppu::WeakAggImplHelper1< css::table::XTableColumns > +{ +public: + explicit TableColumns( const TableModelRef& xTableModel ); + virtual ~TableColumns() override; + + void dispose(); + /// @throws css::uno::RuntimeException + void throwIfDisposed() const; + + // XTableColumns + virtual void SAL_CALL insertByIndex( sal_Int32 nIndex, sal_Int32 nCount ) override; + virtual void SAL_CALL removeByIndex( sal_Int32 nIndex, sal_Int32 nCount ) override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override; + virtual css::uno::Any SAL_CALL getByIndex( sal_Int32 Index ) override; + + // Methods + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + +private: + TableModelRef mxTableModel; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablecontroller.cxx b/svx/source/table/tablecontroller.cxx new file mode 100644 index 000000000..1ceaa5141 --- /dev/null +++ b/svx/source/table/tablecontroller.cxx @@ -0,0 +1,3363 @@ +/* -*- 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 <svx/sdr/table/tablecontroller.hxx> +#include <tablemodel.hxx> + +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/table/XMergeableCellRange.hpp> +#include <com/sun/star/table/XMergeableCell.hpp> + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> + +#include <svl/whiter.hxx> +#include <svl/stritem.hxx> + +#include <sfx2/request.hxx> + +#include <svx/svdotable.hxx> +#include <sdr/overlay/overlayobjectcell.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/svxids.hrc> +#include <svx/svdoutl.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdetc.hxx> +#include <svx/selectioncontroller.hxx> +#include <svx/svdmodel.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svxdlg.hxx> +#include <editeng/boxitem.hxx> +#include <cell.hxx> +#include <editeng/borderline.hxx> +#include <editeng/colritem.hxx> +#include <editeng/lineitem.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> +#include <svx/svdpage.hxx> +#include <svx/sdmetitm.hxx> +#include <svx/sdtditm.hxx> +#include "tableundo.hxx" +#include "tablelayouter.hxx" +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <memory> +#include <o3tl/enumarray.hxx> +#include <o3tl/enumrange.hxx> +#include <cppuhelper/implbase.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/viewsh.hxx> +#include <editeng/editview.hxx> +#include <tools/UnitConversion.hxx> +#include <tools/diagnose_ex.h> + +using ::editeng::SvxBorderLine; +using namespace sdr::table; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::style; + +namespace { + +enum class CellPosFlag // signals the relative position of a cell to a selection +{ + NONE = 0x0000, // not set or inside + // row + Before = 0x0001, + Left = 0x0002, + Right = 0x0004, + After = 0x0008, + // column + Upper = 0x0010, + Top = 0x0020, + Bottom = 0x0040, + Lower = 0x0080 +}; + +} + +namespace o3tl +{ template<> struct typed_flags<CellPosFlag> : is_typed_flags<CellPosFlag, 0xff> {}; } + +namespace sdr::table { + +namespace { + +class SvxTableControllerModifyListener : public ::cppu::WeakImplHelper< css::util::XModifyListener > +{ +public: + explicit SvxTableControllerModifyListener( SvxTableController* pController ) + : mpController( pController ) {} + + // XModifyListener + virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + SvxTableController* mpController; +}; + +} + +// XModifyListener + + +void SAL_CALL SvxTableControllerModifyListener::modified( const css::lang::EventObject& ) +{ + if( mpController ) + mpController->onTableModified(); +} + + +// XEventListener + + +void SAL_CALL SvxTableControllerModifyListener::disposing( const css::lang::EventObject& ) +{ + mpController = nullptr; +} + + + + +rtl::Reference< sdr::SelectionController > CreateTableController( + SdrView& rView, + const SdrTableObj& rObj, + const rtl::Reference< sdr::SelectionController >& xRefController ) +{ + return SvxTableController::create(rView, rObj, xRefController); +} + + +rtl::Reference< sdr::SelectionController > SvxTableController::create( + SdrView& rView, + const SdrTableObj& rObj, + const rtl::Reference< sdr::SelectionController >& xRefController ) +{ + if( xRefController.is() ) + { + SvxTableController* pController = dynamic_cast< SvxTableController* >( xRefController.get() ); + + if(pController && (pController->mxTableObj.get() == &rObj) && (&pController->mrView == &rView)) + { + return xRefController; + } + } + + return new SvxTableController(rView, rObj); +} + + +SvxTableController::SvxTableController( + SdrView& rView, + const SdrTableObj& rObj) +: mbCellSelectionMode(false) + ,mbHasJustMerged(false) + ,mbLeftButtonDown(false) + ,mrView(rView) + ,mxTableObj(const_cast< SdrTableObj* >(&rObj)) + ,mnUpdateEvent( nullptr ) +{ + mxTableObj->getActiveCellPos( maCursorFirstPos ); + maCursorLastPos = maCursorFirstPos; + + Reference< XTable > xTable( mxTableObj->getTable() ); + if( xTable.is() ) + { + mxModifyListener = new SvxTableControllerModifyListener( this ); + xTable->addModifyListener( mxModifyListener ); + + mxTable.set( dynamic_cast< TableModel* >( xTable.get() ) ); + } +} + +SvxTableController::~SvxTableController() +{ + if( mnUpdateEvent ) + { + Application::RemoveUserEvent( mnUpdateEvent ); + } + + if( mxModifyListener.is() && mxTableObj ) + { + Reference< XTable > xTable( mxTableObj->getTable() ); + if( xTable.is() ) + { + xTable->removeModifyListener( mxModifyListener ); + mxModifyListener.clear(); + } + } +} + +bool SvxTableController::onKeyInput(const KeyEvent& rKEvt, vcl::Window* pWindow ) +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + // check if we are read only + if( rModel.IsReadOnly()) + { + switch( rKEvt.GetKeyCode().GetCode() ) + { + case awt::Key::DOWN: + case awt::Key::UP: + case awt::Key::LEFT: + case awt::Key::RIGHT: + case awt::Key::TAB: + case awt::Key::HOME: + case awt::Key::END: + case awt::Key::NUM2: + case awt::Key::NUM4: + case awt::Key::NUM6: + case awt::Key::NUM8: + case awt::Key::ESCAPE: + case awt::Key::F2: + break; + default: + // tell the view we eat the event, no further processing needed + return true; + } + } + + TblAction nAction = getKeyboardAction(rKEvt); + + return executeAction( nAction, rKEvt.GetKeyCode().IsShift(), pWindow ); +} + +namespace { + +Point pixelToLogic(const Point& rPoint, vcl::Window const * pWindow) +{ + if (!pWindow) + return rPoint; + + return pWindow->PixelToLogic(rPoint); +} + +} + +bool SvxTableController::onMouseButtonDown(const MouseEvent& rMEvt, vcl::Window* pWindow ) +{ + if (comphelper::LibreOfficeKit::isActive() && !pWindow) + { + // Tiled rendering: get the window that has the disabled map mode. + if (OutputDevice* pOutputDevice = mrView.GetFirstOutputDevice()) + { + if (pOutputDevice->GetOutDevType() == OUTDEV_WINDOW) + pWindow = pOutputDevice->GetOwnerWindow(); + } + } + + if( !pWindow || !checkTableObject() ) + return false; + + SdrViewEvent aVEvt; + if( !rMEvt.IsRight() && mrView.PickAnything(rMEvt,SdrMouseEventKind::BUTTONDOWN, aVEvt) == SdrHitKind::Handle ) + return false; + + TableHitKind eHit = mxTableObj->CheckTableHit(pixelToLogic(rMEvt.GetPosPixel(), pWindow), maMouseDownPos.mnCol, maMouseDownPos.mnRow); + + mbLeftButtonDown = (rMEvt.GetClicks() == 1) && rMEvt.IsLeft(); + + if( eHit == TableHitKind::Cell ) + { + StartSelection( maMouseDownPos ); + return true; + } + + if( rMEvt.IsRight() && eHit != TableHitKind::NONE ) + return true; // right click will become context menu + + // for cell selection with the mouse remember our first hit + if( mbLeftButtonDown ) + { + RemoveSelection(); + + SdrHdl* pHdl = mrView.PickHandle(pixelToLogic(rMEvt.GetPosPixel(), pWindow)); + + if( pHdl ) + { + mbLeftButtonDown = false; + } + else + { + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + + if (!pTableObj || eHit == TableHitKind::NONE) + { + mbLeftButtonDown = false; + } + } + } + + if (comphelper::LibreOfficeKit::isActive() && rMEvt.GetClicks() == 2 && rMEvt.IsLeft() && eHit == TableHitKind::CellTextArea) + { + bool bEmptyOutliner = false; + if (Outliner* pOutliner = mrView.GetTextEditOutliner()) + { + if (pOutliner->GetParagraphCount() == 1) + { + if (Paragraph* pParagraph = pOutliner->GetParagraph(0)) + bEmptyOutliner = pOutliner->GetText(pParagraph).isEmpty(); + } + } + if (bEmptyOutliner) + { + // Tiled rendering: a left double-click in an empty cell: select it. + StartSelection(maMouseDownPos); + setSelectedCells(maMouseDownPos, maMouseDownPos); + // Update graphic selection, should be hidden now. + mrView.AdjustMarkHdl(); + return true; + } + } + + return false; +} + + +bool SvxTableController::onMouseButtonUp(const MouseEvent& rMEvt, vcl::Window* /*pWin*/) +{ + if( !checkTableObject() ) + return false; + + mbLeftButtonDown = false; + + return rMEvt.GetClicks() == 2; +} + + +bool SvxTableController::onMouseMove(const MouseEvent& rMEvt, vcl::Window* pWindow ) +{ + if( !checkTableObject() ) + return false; + + SdrTableObj* pTableObj = mxTableObj.get(); + CellPos aPos; + if (mbLeftButtonDown && pTableObj && pTableObj->CheckTableHit(pixelToLogic(rMEvt.GetPosPixel(), pWindow), aPos.mnCol, aPos.mnRow ) != TableHitKind::NONE) + { + if(aPos != maMouseDownPos) + { + if( mbCellSelectionMode ) + { + setSelectedCells( maMouseDownPos, aPos ); + return true; + } + else + { + StartSelection( maMouseDownPos ); + } + } + else if( mbCellSelectionMode ) + { + UpdateSelection( aPos ); + return true; + } + } + return false; +} + + +void SvxTableController::onSelectionHasChanged() +{ + bool bSelected = false; + + SdrTableObj* pTableObj = mxTableObj.get(); + if( pTableObj && pTableObj->IsTextEditActive() ) + { + pTableObj->getActiveCellPos( maCursorFirstPos ); + maCursorLastPos = maCursorFirstPos; + mbCellSelectionMode = false; + } + else + { + const SdrMarkList& rMarkList= mrView.GetMarkedObjectList(); + if( rMarkList.GetMarkCount() == 1 ) + bSelected = mxTableObj.get() == rMarkList.GetMark(0)->GetMarkedSdrObj(); + } + + if( bSelected ) + { + updateSelectionOverlay(); + } + else + { + destroySelectionOverlay(); + } +} +void SvxTableController::onSelectAll() +{ + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if ( pTableObj && !pTableObj->IsTextEditActive()) + { + selectAll(); + } +} + + +void SvxTableController::GetState( SfxItemSet& rSet ) +{ + if(!mxTable.is() || !mxTableObj.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + std::optional<SfxItemSet> oSet; + bool bVertDone(false); + + // Iterate over all requested items in the set. + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + switch (nWhich) + { + case SID_TABLE_VERT_BOTTOM: + case SID_TABLE_VERT_CENTER: + case SID_TABLE_VERT_NONE: + { + if(!bVertDone) + { + if (!oSet) + { + oSet.emplace(rModel.GetItemPool()); + MergeAttrFromSelectedCells(*oSet, false); + } + + SdrTextVertAdjust eAdj = SDRTEXTVERTADJUST_BLOCK; + + if (oSet->GetItemState( SDRATTR_TEXT_VERTADJUST ) != SfxItemState::DONTCARE) + eAdj = oSet->Get(SDRATTR_TEXT_VERTADJUST).GetValue(); + + rSet.Put(SfxBoolItem(SID_TABLE_VERT_BOTTOM, eAdj == SDRTEXTVERTADJUST_BOTTOM)); + rSet.Put(SfxBoolItem(SID_TABLE_VERT_CENTER, eAdj == SDRTEXTVERTADJUST_CENTER)); + rSet.Put(SfxBoolItem(SID_TABLE_VERT_NONE, eAdj == SDRTEXTVERTADJUST_TOP)); + bVertDone = true; + } + break; + } + case SID_TABLE_DELETE_ROW: + if( !mxTable.is() || !hasSelectedCells() || (!comphelper::LibreOfficeKit::isActive() && mxTable->getRowCount() <= 1) ) + rSet.DisableItem(SID_TABLE_DELETE_ROW); + break; + case SID_TABLE_DELETE_COL: + if( !mxTable.is() || !hasSelectedCells() || (!comphelper::LibreOfficeKit::isActive() && mxTable->getColumnCount() <= 1) ) + rSet.DisableItem(SID_TABLE_DELETE_COL); + break; + case SID_TABLE_DELETE_TABLE: + if( !mxTable.is() ) + rSet.DisableItem(SID_TABLE_DELETE_TABLE); + break; + case SID_TABLE_MERGE_CELLS: + if( !mxTable.is() || !hasSelectedCells() ) + rSet.DisableItem(SID_TABLE_MERGE_CELLS); + break; + case SID_TABLE_SPLIT_CELLS: + if( !hasSelectedCells() || !mxTable.is() ) + rSet.DisableItem(SID_TABLE_SPLIT_CELLS); + break; + + case SID_TABLE_OPTIMAL_ROW_HEIGHT: + case SID_TABLE_DISTRIBUTE_COLUMNS: + case SID_TABLE_DISTRIBUTE_ROWS: + { + bool bDistributeColumns = false; + bool bDistributeRows = false; + if( mxTable.is() ) + { + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + bDistributeColumns = aStart.mnCol != aEnd.mnCol; + bDistributeRows = aStart.mnRow != aEnd.mnRow; + } + if( !bDistributeColumns ) + rSet.DisableItem(SID_TABLE_DISTRIBUTE_COLUMNS); + if( !bDistributeRows ) + { + rSet.DisableItem(SID_TABLE_OPTIMAL_ROW_HEIGHT); + rSet.DisableItem(SID_TABLE_DISTRIBUTE_ROWS); + } + break; + } + + default: + break; + } + nWhich = aIter.NextWhich(); + } +} + + +void SvxTableController::onInsert( sal_uInt16 nSId, const SfxItemSet* pArgs ) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + bool bInsertAfter = true; + sal_uInt16 nCount = 0; + + if( pArgs ) + { + const SfxPoolItem* pItem = nullptr; + pArgs->GetItemState(nSId, false, &pItem); + if (pItem) + { + nCount = static_cast<const SfxInt16Item*>(pItem)->GetValue(); + if(const SfxBoolItem* pItem2 = pArgs->GetItemIfSet(SID_TABLE_PARAM_INSERT_AFTER)) + bInsertAfter = pItem2->GetValue(); + } + } + + CellPos aStart, aEnd; + if( hasSelectedCells() ) + { + getSelectedCells( aStart, aEnd ); + } + else + { + if( bInsertAfter ) + { + aStart.mnCol = mxTable->getColumnCount() - 1; + aStart.mnRow = mxTable->getRowCount() - 1; + aEnd = aStart; + } + } + + if( rTableObj.IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + RemoveSelection(); + + static const OUStringLiteral sSize( u"Size" ); + const bool bUndo(rModel.IsUndoEnabled()); + + switch( nSId ) + { + case SID_TABLE_INSERT_COL: + { + TableModelNotifyGuard aGuard( mxTable.get() ); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_INSCOL) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + Reference< XTableColumns > xCols( mxTable->getColumns() ); + const sal_Int32 nNewColumns = (nCount == 0) ? (aEnd.mnCol - aStart.mnCol + 1) : nCount; + const sal_Int32 nNewStartColumn = aEnd.mnCol + (bInsertAfter ? 1 : 0); + xCols->insertByIndex( nNewStartColumn, nNewColumns ); + + for( sal_Int32 nOffset = 0; nOffset < nNewColumns; nOffset++ ) + { + // Resolves fdo#61540 + // On Insert before, the reference column whose size is going to be + // used for newly created column(s) is wrong. As the new columns are + // inserted before the reference column, the reference column moved + // to the new position by no., of new columns i.e (earlier+newcolumns). + Reference< XPropertySet >(xCols->getByIndex(nNewStartColumn+nOffset), UNO_QUERY_THROW )-> + setPropertyValue( sSize, + Reference< XPropertySet >(xCols->getByIndex( bInsertAfter?nNewStartColumn-1:nNewStartColumn+nNewColumns ), UNO_QUERY_THROW )-> + getPropertyValue( sSize ) ); + } + + // Copy cell properties + sal_Int32 nPropSrcCol = (bInsertAfter ? aEnd.mnCol : aStart.mnCol + nNewColumns); + sal_Int32 nRowSpan = 0; + bool bNewSpan = false; + + for( sal_Int32 nRow = 0; nRow < mxTable->getRowCount(); ++nRow ) + { + CellRef xSourceCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nPropSrcCol, nRow ).get() ) ); + + // When we insert new COLUMNs, we want to copy ROW spans. + if (xSourceCell.is() && nRowSpan == 0) + { + // we are not in a span yet. Let's find out if the current cell is in a span. + sal_Int32 nColSpan = sal_Int32(); + sal_Int32 nSpanInfoCol = sal_Int32(); + + if( xSourceCell->getRowSpan() > 1 ) + { + // The current cell is the top-left cell in a span. + // Get the span info and propagate it to the target. + nRowSpan = xSourceCell->getRowSpan(); + nColSpan = xSourceCell->getColumnSpan(); + nSpanInfoCol = nPropSrcCol; + } + else if( xSourceCell->isMerged() ) + { + // The current cell is a middle cell in a 2D span. + // Look for the top-left cell in the span. + for( nSpanInfoCol = nPropSrcCol - 1; nSpanInfoCol >= 0; --nSpanInfoCol ) + { + CellRef xMergeInfoCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nSpanInfoCol, nRow ).get() ) ); + if (xMergeInfoCell.is() && !xMergeInfoCell->isMerged()) + { + nRowSpan = xMergeInfoCell->getRowSpan(); + nColSpan = xMergeInfoCell->getColumnSpan(); + break; + } + } + if( nRowSpan == 1 ) + nRowSpan = 0; + } + + // The target columns are outside the span; Start a new span. + if( nRowSpan > 0 && ( nNewStartColumn < nSpanInfoCol || nSpanInfoCol + nColSpan <= nNewStartColumn ) ) + bNewSpan = true; + } + + // Now copy the properties from the source to the targets + for( sal_Int32 nOffset = 0; nOffset < nNewColumns; nOffset++ ) + { + CellRef xTargetCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nNewStartColumn + nOffset, nRow ).get() ) ); + if( xTargetCell.is() ) + { + if( nRowSpan > 0 ) + { + if( bNewSpan ) + xTargetCell->merge( 1, nRowSpan ); + else + xTargetCell->setMerged(); + } + xTargetCell->copyFormatFrom( xSourceCell ); + } + } + + if( nRowSpan > 0 ) + { + --nRowSpan; + bNewSpan = false; + } + } + + if( bUndo ) + rModel.EndUndo(); + + aStart.mnCol = nNewStartColumn; + aStart.mnRow = 0; + aEnd.mnCol = aStart.mnCol + nNewColumns - 1; + aEnd.mnRow = mxTable->getRowCount() - 1; + break; + } + + case SID_TABLE_INSERT_ROW: + { + TableModelNotifyGuard aGuard( mxTable.get() ); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_INSROW ) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + Reference< XTableRows > xRows( mxTable->getRows() ); + const sal_Int32 nNewRows = (nCount == 0) ? (aEnd.mnRow - aStart.mnRow + 1) : nCount; + const sal_Int32 nNewRowStart = aEnd.mnRow + (bInsertAfter ? 1 : 0); + xRows->insertByIndex( nNewRowStart, nNewRows ); + + for( sal_Int32 nOffset = 0; nOffset < nNewRows; nOffset++ ) + { + Reference< XPropertySet >( xRows->getByIndex( aEnd.mnRow + nOffset + 1 ), UNO_QUERY_THROW )-> + setPropertyValue( sSize, + Reference< XPropertySet >( xRows->getByIndex( aStart.mnRow + nOffset ), UNO_QUERY_THROW )-> + getPropertyValue( sSize ) ); + } + + // Copy the cell properties + sal_Int32 nPropSrcRow = (bInsertAfter ? aEnd.mnRow : aStart.mnRow + nNewRows); + sal_Int32 nColSpan = 0; + bool bNewSpan = false; + + for( sal_Int32 nCol = 0; nCol < mxTable->getColumnCount(); ++nCol ) + { + CellRef xSourceCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nPropSrcRow ).get() ) ); + + if (!xSourceCell.is()) + continue; + + // When we insert new ROWs, we want to copy COLUMN spans. + if( nColSpan == 0 ) + { + // we are not in a span yet. Let's find out if the current cell is in a span. + sal_Int32 nRowSpan = sal_Int32(); + sal_Int32 nSpanInfoRow = sal_Int32(); + + if( xSourceCell->getColumnSpan() > 1 ) + { + // The current cell is the top-left cell in a span. + // Get the span info and propagate it to the target. + nColSpan = xSourceCell->getColumnSpan(); + nRowSpan = xSourceCell->getRowSpan(); + nSpanInfoRow = nPropSrcRow; + } + else if( xSourceCell->isMerged() ) + { + // The current cell is a middle cell in a 2D span. + // Look for the top-left cell in the span. + for( nSpanInfoRow = nPropSrcRow - 1; nSpanInfoRow >= 0; --nSpanInfoRow ) + { + CellRef xMergeInfoCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nSpanInfoRow ).get() ) ); + if (xMergeInfoCell.is() && !xMergeInfoCell->isMerged()) + { + nColSpan = xMergeInfoCell->getColumnSpan(); + nRowSpan = xMergeInfoCell->getRowSpan(); + break; + } + } + if( nColSpan == 1 ) + nColSpan = 0; + } + + // Inserted rows are outside the span; Start a new span. + if( nColSpan > 0 && ( nNewRowStart < nSpanInfoRow || nSpanInfoRow + nRowSpan <= nNewRowStart ) ) + bNewSpan = true; + } + + // Now copy the properties from the source to the targets + for( sal_Int32 nOffset = 0; nOffset < nNewRows; ++nOffset ) + { + CellRef xTargetCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nNewRowStart + nOffset ).get() ) ); + if( xTargetCell.is() ) + { + if( nColSpan > 0 ) + { + if( bNewSpan ) + xTargetCell->merge( nColSpan, 1 ); + else + xTargetCell->setMerged(); + } + xTargetCell->copyFormatFrom( xSourceCell ); + } + } + + if( nColSpan > 0 ) + { + --nColSpan; + bNewSpan = false; + } + } + + if( bUndo ) + rModel.EndUndo(); + + aStart.mnCol = 0; + aStart.mnRow = nNewRowStart; + aEnd.mnCol = mxTable->getColumnCount() - 1; + aEnd.mnRow = aStart.mnRow + nNewRows - 1; + break; + } + } + + StartSelection( aStart ); + UpdateSelection( aEnd ); +} + + +void SvxTableController::onDelete( sal_uInt16 nSId ) +{ + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if( !pTableObj || !mxTable.is() ) + return; + + if( nSId == SID_TABLE_DELETE_TABLE ) + { + if( pTableObj->IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + mrView.DeleteMarkedObj(); + } + else if( hasSelectedCells() ) + { + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + if( pTableObj->IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + RemoveSelection(); + + bool bDeleteTable = false; + switch( nSId ) + { + case SID_TABLE_DELETE_COL: + { + const sal_Int32 nRemovedColumns = aEnd.mnCol - aStart.mnCol + 1; + if( nRemovedColumns == mxTable->getColumnCount() ) + { + bDeleteTable = true; + } + else + { + Reference< XTableColumns > xCols( mxTable->getColumns() ); + xCols->removeByIndex( aStart.mnCol, nRemovedColumns ); + EditCell(aStart, nullptr, TblAction::NONE); + } + break; + } + + case SID_TABLE_DELETE_ROW: + { + const sal_Int32 nRemovedRows = aEnd.mnRow - aStart.mnRow + 1; + if( nRemovedRows == mxTable->getRowCount() ) + { + bDeleteTable = true; + } + else + { + Reference< XTableRows > xRows( mxTable->getRows() ); + xRows->removeByIndex( aStart.mnRow, nRemovedRows ); + EditCell(aStart, nullptr, TblAction::NONE); + } + break; + } + } + + if( bDeleteTable ) + mrView.DeleteMarkedObj(); + else + UpdateTableShape(); + } +} + + +void SvxTableController::onSelect( sal_uInt16 nSId ) +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + if( !(nRowCount && nColCount) ) + return; + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + switch( nSId ) + { + case SID_TABLE_SELECT_ALL: + aEnd.mnCol = 0; aEnd.mnRow = 0; + aStart.mnCol = nColCount - 1; aStart.mnRow = nRowCount - 1; + break; + case SID_TABLE_SELECT_COL: + aEnd.mnRow = nRowCount - 1; + aStart.mnRow = 0; + break; + case SID_TABLE_SELECT_ROW: + aEnd.mnCol = nColCount - 1; + aStart.mnCol = 0; + break; + } + + StartSelection( aEnd ); + gotoCell( aStart, true, nullptr ); +} + +namespace +{ + SvxBoxItem mergeDrawinglayerTextDistancesAndSvxBoxItem(const SfxItemSet& rAttrSet) + { + // merge drawing layer text distance items into SvxBoxItem used by the dialog + SvxBoxItem aBoxItem( rAttrSet.Get( SDRATTR_TABLE_BORDER ) ); + aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_LEFTDIST).GetValue()), SvxBoxItemLine::LEFT ); + aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_RIGHTDIST).GetValue()), SvxBoxItemLine::RIGHT ); + aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_UPPERDIST).GetValue()), SvxBoxItemLine::TOP ); + aBoxItem.SetDistance( sal::static_int_cast< sal_uInt16 >( rAttrSet.Get(SDRATTR_TEXT_LOWERDIST).GetValue()), SvxBoxItemLine::BOTTOM ); + return aBoxItem; + } +} + +void SvxTableController::onFormatTable(const SfxRequest& rReq) +{ + if(!mxTableObj.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const SfxItemSet* pArgs = rReq.GetArgs(); + + if(pArgs) + return; + + SfxItemSet aNewAttr(rModel.GetItemPool()); + + // merge drawing layer text distance items into SvxBoxItem used by the dialog + SvxBoxItem aBoxItem(mergeDrawinglayerTextDistancesAndSvxBoxItem(aNewAttr)); + + SvxBoxInfoItem aBoxInfoItem( aNewAttr.Get( SDRATTR_TABLE_BORDER_INNER ) ); + + MergeAttrFromSelectedCells(aNewAttr, false); + FillCommonBorderAttrFromSelectedCells( aBoxItem, aBoxInfoItem ); + aNewAttr.Put( aBoxItem ); + aNewAttr.Put( aBoxInfoItem ); + + // Fill in shadow properties. + const SfxItemSet& rTableItemSet = rTableObj.GetMergedItemSet(); + for (sal_uInt16 nWhich = SDRATTR_SHADOW_FIRST; nWhich <= SDRATTR_SHADOW_LAST; ++nWhich) + { + if (rTableItemSet.GetItemState(nWhich, false) != SfxItemState::SET) + { + continue; + } + + aNewAttr.Put(rTableItemSet.Get(nWhich)); + } + + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + VclPtr<SfxAbstractTabDialog> xDlg( pFact->CreateSvxFormatCellsDialog( + rReq.GetFrameWeld(), + &aNewAttr, + rModel) ); + + // Even Cancel Button is returning positive(101) value, + xDlg->StartExecuteAsync([xDlg, this, aBoxItem, aBoxInfoItem](int nResult){ + if (nResult == RET_OK) + { + SfxItemSet aNewSet(*(xDlg->GetOutputItemSet())); + + //Only properties that were unchanged by the dialog appear in this + //itemset. We had constructed these two properties from other + //ones, so if they were not changed, then forcible set them back to + //their originals in the new result set so we can decompose that + //unchanged state back to their input properties + if (aNewSet.GetItemState(SDRATTR_TABLE_BORDER, false) != SfxItemState::SET) + { + aNewSet.Put(aBoxItem); + } + if (aNewSet.GetItemState(SDRATTR_TABLE_BORDER_INNER, false) != SfxItemState::SET) + { + aNewSet.Put(aBoxInfoItem); + } + + SvxBoxItem aNewBoxItem( aNewSet.Get( SDRATTR_TABLE_BORDER ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::LEFT ) != aBoxItem.GetDistance( SvxBoxItemLine::LEFT ) ) + aNewSet.Put(makeSdrTextLeftDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::LEFT ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) != aBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) ) + aNewSet.Put(makeSdrTextRightDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::RIGHT ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::TOP ) != aBoxItem.GetDistance( SvxBoxItemLine::TOP ) ) + aNewSet.Put(makeSdrTextUpperDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::TOP ) ) ); + + if( aNewBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) != aBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) ) + aNewSet.Put(makeSdrTextLowerDistItem( aNewBoxItem.GetDistance( SvxBoxItemLine::BOTTOM ) ) ); + + if (checkTableObject() && mxTable.is()) + { + // Create a single undo action when applying the result of the dialog. + SdrTableObj& rTableObject(*mxTableObj); + SdrModel& rSdrModel(rTableObject.getSdrModelFromSdrObject()); + bool bUndo = rSdrModel.IsUndoEnabled() && !mrView.IsTextEdit(); + if (bUndo) + { + rSdrModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT)); + } + + this->SetAttrToSelectedCells(aNewSet, false); + + this->SetAttrToSelectedShape(aNewSet); + + if (bUndo) + { + rSdrModel.EndUndo(); + } + } + } + + xDlg->disposeOnce(); + }); +} + +void SvxTableController::Execute( SfxRequest& rReq ) +{ + const sal_uInt16 nSId = rReq.GetSlot(); + switch( nSId ) + { + case SID_TABLE_INSERT_ROW: + case SID_TABLE_INSERT_COL: + onInsert( nSId, rReq.GetArgs() ); + break; + case SID_TABLE_DELETE_ROW: + case SID_TABLE_DELETE_COL: + case SID_TABLE_DELETE_TABLE: + onDelete( nSId ); + break; + case SID_TABLE_SELECT_ALL: + case SID_TABLE_SELECT_COL: + case SID_TABLE_SELECT_ROW: + onSelect( nSId ); + break; + case SID_FORMAT_TABLE_DLG: + onFormatTable( rReq ); + break; + + case SID_FRAME_LINESTYLE: + case SID_FRAME_LINECOLOR: + case SID_ATTR_BORDER: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if( pArgs ) + ApplyBorderAttr( *pArgs ); + } + break; + + case SID_ATTR_FILL_STYLE: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if( pArgs ) + SetAttributes( *pArgs, false ); + } + break; + + case SID_TABLE_MERGE_CELLS: + MergeMarkedCells(); + break; + + case SID_TABLE_SPLIT_CELLS: + SplitMarkedCells(rReq); + break; + + case SID_TABLE_MINIMAL_COLUMN_WIDTH: + DistributeColumns(/*bOptimize=*/true, /*bMinimize=*/true); + break; + + case SID_TABLE_OPTIMAL_COLUMN_WIDTH: + DistributeColumns(/*bOptimize=*/true, /*bMinimize=*/false); + break; + + case SID_TABLE_DISTRIBUTE_COLUMNS: + DistributeColumns(/*bOptimize=*/false, /*bMinimize=*/false); + break; + + case SID_TABLE_MINIMAL_ROW_HEIGHT: + DistributeRows(/*bOptimize=*/true, /*bMinimize=*/true); + break; + + case SID_TABLE_OPTIMAL_ROW_HEIGHT: + DistributeRows(/*bOptimize=*/true, /*bMinimize=*/false); + break; + + case SID_TABLE_DISTRIBUTE_ROWS: + DistributeRows(/*bOptimize=*/false, /*bMinimize=*/false); + break; + + case SID_TABLE_VERT_BOTTOM: + case SID_TABLE_VERT_CENTER: + case SID_TABLE_VERT_NONE: + SetVertical( nSId ); + break; + + case SID_AUTOFORMAT: + case SID_TABLE_SORT_DIALOG: + case SID_TABLE_AUTOSUM: + default: + break; + + case SID_TABLE_STYLE: + SetTableStyle( rReq.GetArgs() ); + break; + + case SID_TABLE_STYLE_SETTINGS: + SetTableStyleSettings( rReq.GetArgs() ); + break; + case SID_TABLE_CHANGE_CURRENT_BORDER_POSITION: + changeTableEdge(rReq); + break; + } +} + +void SvxTableController::SetTableStyle( const SfxItemSet* pArgs ) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + if(!pArgs || (SfxItemState::SET != pArgs->GetItemState(SID_TABLE_STYLE, false))) + return; + + const SfxStringItem* pArg = dynamic_cast< const SfxStringItem* >( &pArgs->Get( SID_TABLE_STYLE ) ); + if( !(pArg && mxTable.is()) ) + return; + + try + { + Reference< XStyleFamiliesSupplier > xSFS( rModel.getUnoModel(), UNO_QUERY_THROW ); + Reference< XNameAccess > xFamilyNameAccess( xSFS->getStyleFamilies(), UNO_SET_THROW ); + Reference< XNameAccess > xTableFamilyAccess( xFamilyNameAccess->getByName( "table" ), UNO_QUERY_THROW ); + + if( xTableFamilyAccess->hasByName( pArg->GetValue() ) ) + { + // found table style with the same name + Reference< XIndexAccess > xNewTableStyle( xTableFamilyAccess->getByName( pArg->GetValue() ), UNO_QUERY_THROW ); + + const bool bUndo = rModel.IsUndoEnabled(); + + if( bUndo ) + { + rModel.BegUndo(SvxResId(STR_TABLE_STYLE)); + rModel.AddUndo(std::make_unique<TableStyleUndo>(rTableObj)); + } + + rTableObj.setTableStyle( xNewTableStyle ); + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) + { + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) try + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + SfxItemSet aSet( xCell->GetItemSet() ); + bool bChanges = false; + SfxStyleSheet *pStyleSheet = xCell->GetStyleSheet(); + SAL_WARN_IF(!pStyleSheet, "svx", "no stylesheet for table cell?"); + if (pStyleSheet) + { + const SfxItemSet& rStyleAttribs = pStyleSheet->GetItemSet(); + + for ( sal_uInt16 nWhich = SDRATTR_START; nWhich <= SDRATTR_TABLE_LAST; nWhich++ ) + { + if( (rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET) && (aSet.GetItemState( nWhich ) == SfxItemState::SET) ) + { + aSet.ClearItem( nWhich ); + bChanges = true; + } + } + } + + if( bChanges ) + { + if( bUndo ) + xCell->AddUndo(); + + xCell->SetMergedItemSetAndBroadcast( aSet, true ); + } + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } + } + + if( bUndo ) + rModel.EndUndo(); + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + +void SvxTableController::SetTableStyleSettings( const SfxItemSet* pArgs ) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + TableStyleSettings aSettings(rTableObj.getTableStyleSettings() ); + const SfxBoolItem *pPoolItem=nullptr; + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEFIRSTROWSTYLE, false)) ) + aSettings.mbUseFirstRow = pPoolItem->GetValue(); + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USELASTROWSTYLE, false)) ) + aSettings.mbUseLastRow = pPoolItem->GetValue(); + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEBANDINGROWSTYLE, false)) ) + aSettings.mbUseRowBanding = pPoolItem->GetValue(); + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEFIRSTCOLUMNSTYLE, false)) ) + aSettings.mbUseFirstColumn = pPoolItem->GetValue(); + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USELASTCOLUMNSTYLE, false)) ) + aSettings.mbUseLastColumn = pPoolItem->GetValue(); + + if( (pPoolItem = pArgs->GetItemIfSet(ID_VAL_USEBANDINGCOLUMNSTYLE, false)) ) + aSettings.mbUseColumnBanding = pPoolItem->GetValue(); + + if( aSettings == rTableObj.getTableStyleSettings() ) + return; + + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_STYLE_SETTINGS) ); + rModel.AddUndo(std::make_unique<TableStyleUndo>(rTableObj)); + } + + rTableObj.setTableStyleSettings( aSettings ); + + if( bUndo ) + rModel.EndUndo(); +} + +void SvxTableController::SetVertical( sal_uInt16 nSId ) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + TableModelNotifyGuard aGuard( mxTable.get() ); + const bool bUndo(rModel.IsUndoEnabled()); + + if (bUndo) + { + rModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT)); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoAttrObject(rTableObj)); + } + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + SdrTextVertAdjust eAdj = SDRTEXTVERTADJUST_TOP; + + switch( nSId ) + { + case SID_TABLE_VERT_BOTTOM: + eAdj = SDRTEXTVERTADJUST_BOTTOM; + break; + case SID_TABLE_VERT_CENTER: + eAdj = SDRTEXTVERTADJUST_CENTER; + break; + //case SID_TABLE_VERT_NONE: + default: + break; + } + + SdrTextVertAdjustItem aItem( eAdj ); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + if (bUndo) + xCell->AddUndo(); + SfxItemSet aSet(xCell->GetItemSet()); + aSet.Put(aItem); + xCell->SetMergedItemSetAndBroadcast(aSet, /*bClearAllItems=*/false); + } + } + } + + UpdateTableShape(); + + if (bUndo) + rModel.EndUndo(); +} + +void SvxTableController::MergeMarkedCells() +{ + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + SdrTableObj* pTableObj = mxTableObj.get(); + if( pTableObj ) + { + if( pTableObj->IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + TableModelNotifyGuard aGuard( mxTable.get() ); + MergeRange( aStart.mnCol, aStart.mnRow, aEnd.mnCol, aEnd.mnRow ); + } +} + +void SvxTableController::SplitMarkedCells(const SfxRequest& rReq) +{ + if(!checkTableObject() || !mxTable.is()) + return; + + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + VclPtr<SvxAbstractSplitTableDialog> xDlg(pFact->CreateSvxSplitTableDialog(rReq.GetFrameWeld(), false, 99)); + + xDlg->StartExecuteAsync([xDlg, this](int) { + const sal_Int32 nCount = xDlg->GetCount() - 1; + + if( nCount < 1 ) + return; + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + Reference< XMergeableCellRange > xRange( mxTable->createCursorByRange( mxTable->getCellRangeByPosition( aStart.mnCol, aStart.mnRow, aEnd.mnCol, aEnd.mnRow ) ), UNO_QUERY_THROW ); + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + SdrTableObj& rTableObj(*mxTableObj); + + if( rTableObj.IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + TableModelNotifyGuard aGuard( mxTable.get() ); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_SPLIT) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + if( xDlg->IsHorizontal() ) + { + xRange->split( 0, nCount ); + } + else + { + xRange->split( nCount, 0 ); + } + + if( bUndo ) + rModel.EndUndo(); + + aEnd.mnRow += mxTable->getRowCount() - nRowCount; + aEnd.mnCol += mxTable->getColumnCount() - nColCount; + + setSelectedCells( aStart, aEnd ); + + xDlg->disposeOnce(); + }); +} + +void SvxTableController::DistributeColumns(const bool bOptimize, const bool bMinimize) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_DISTRIBUTE_COLUMNS) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + rTableObj.DistributeColumns( aStart.mnCol, aEnd.mnCol, bOptimize, bMinimize ); + + if( bUndo ) + rModel.EndUndo(); +} + +void SvxTableController::DistributeRows(const bool bOptimize, const bool bMinimize) +{ + if(!checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_DISTRIBUTE_ROWS) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + rTableObj.DistributeRows( aStart.mnRow, aEnd.mnRow, bOptimize, bMinimize ); + + if( bUndo ) + rModel.EndUndo(); +} + +bool SvxTableController::HasMarked() const +{ + return mbCellSelectionMode && mxTable.is(); +} + +bool SvxTableController::DeleteMarked() +{ + if(!checkTableObject() || !HasMarked()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + bool bDeleteTable = false; + + if (bUndo) + rModel.BegUndo(SvxResId(STR_TABLE_DELETE_CELL_CONTENTS)); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + const sal_Int32 nRemovedColumns = aEnd.mnCol - aStart.mnCol + 1; + const sal_Int32 nRemovedRows = aEnd.mnRow - aStart.mnRow + 1; + if( nRemovedColumns == mxTable->getColumnCount() && nRemovedRows == mxTable->getRowCount()) + { + bDeleteTable = true; + } + else + { + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if (xCell.is() && xCell->hasText()) + { + if (bUndo) + xCell->AddUndo(); + xCell->SetOutlinerParaObject(std::nullopt); + } + } + } + } + + if (bDeleteTable) + mrView.DeleteMarkedObj(); + + if (bUndo) + rModel.EndUndo(); + + if (!bDeleteTable) + UpdateTableShape(); + return true; +} + +bool SvxTableController::GetStyleSheet( SfxStyleSheet*& rpStyleSheet ) const +{ + if( hasSelectedCells() ) + { + rpStyleSheet = nullptr; + + if( mxTable.is() ) + { + SfxStyleSheet* pRet=nullptr; + bool b1st=true; + + CellPos aStart, aEnd; + const_cast<SvxTableController&>(*this).getSelectedCells( aStart, aEnd ); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + SfxStyleSheet* pSS=xCell->GetStyleSheet(); + if(b1st) + { + pRet=pSS; + } + else if(pRet != pSS) + { + return true; + } + b1st=false; + } + } + } + rpStyleSheet = pRet; + return true; + } + } + return false; +} + +bool SvxTableController::SetStyleSheet( SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr ) +{ + if( hasSelectedCells() && (!pStyleSheet || pStyleSheet->GetFamily() == SfxStyleFamily::Frame) ) + { + if( mxTable.is() ) + { + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + xCell->SetStyleSheet(pStyleSheet,bDontRemoveHardAttr); + } + } + + UpdateTableShape(); + return true; + } + } + return false; +} + +void SvxTableController::changeTableEdge(const SfxRequest& rReq) +{ + if (!checkTableObject()) + return; + + const auto* pType = rReq.GetArg<SfxStringItem>(SID_TABLE_BORDER_TYPE); + const auto* pIndex = rReq.GetArg<SfxUInt16Item>(SID_TABLE_BORDER_INDEX); + const auto* pOffset = rReq.GetArg<SfxInt32Item>(SID_TABLE_BORDER_OFFSET); + + if (!(pType && pIndex && pOffset)) + return; + + const OUString sType = pType->GetValue(); + const sal_uInt16 nIndex = pIndex->GetValue(); + const sal_Int32 nOffset = convertTwipToMm100(pOffset->GetValue()); + + SdrTableObj& rTableObj(*mxTableObj); + + sal_Int32 nEdgeIndex = -1; + bool bHorizontal = sType.startsWith("row"); + + if (sType == "column-left" || sType == "row-left") + { + nEdgeIndex = 0; + } + else if (sType == "column-right") + { + // Number of edges = number of columns + 1 + nEdgeIndex = rTableObj.getColumnCount(); + } + else if (sType == "row-right") + { + // Number of edges = number of rows + 1 + nEdgeIndex = rTableObj.getRowCount(); + } + else if (sType == "column-middle" || sType == "row-middle") + { + nEdgeIndex = nIndex + 1; + } + + if (nEdgeIndex < 0) + return; + + TableModelNotifyGuard aGuard(mxTable.get()); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + if (bUndo) + { + auto pUndoObject = rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj); + rModel.BegUndo(pUndoObject->GetComment()); + + auto* pGeoUndo = static_cast<SdrUndoGeoObj*>(pUndoObject.get()); + if (pGeoUndo) + pGeoUndo->SetSkipChangeLayout(true); + + rModel.AddUndo(std::move(pUndoObject)); + } + tools::Rectangle aBoundRect; + if (rTableObj.GetUserCall()) + aBoundRect = rTableObj.GetLastBoundRect(); + rTableObj.changeEdge(bHorizontal, nEdgeIndex, nOffset); + rTableObj.SetChanged(); + rTableObj.SendUserCall(SdrUserCallType::Resize, aBoundRect); + if (bUndo) + rModel.EndUndo(); +} + +// internals + + +bool SvxTableController::checkTableObject() +{ + return mxTableObj.is(); +} + + +SvxTableController::TblAction SvxTableController::getKeyboardAction(const KeyEvent& rKEvt) +{ + const bool bMod1 = rKEvt.GetKeyCode().IsMod1(); // ctrl + const bool bMod2 = rKEvt.GetKeyCode().IsMod2(); // Alt + const bool bTextEdit = mrView.IsTextEdit(); + + TblAction nAction = TblAction::HandledByView; + + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if( !pTableObj ) + return nAction; + + // handle special keys + const sal_Int16 nCode = rKEvt.GetKeyCode().GetCode(); + switch( nCode ) + { + case awt::Key::ESCAPE: // handle escape + { + if( bTextEdit ) + { + // escape during text edit ends text edit + nAction = TblAction::StopTextEdit; + } + if( mbCellSelectionMode ) + { + // escape with selected cells removes selection + nAction = TblAction::RemoveSelection; + } + break; + } + case awt::Key::RETURN: // handle return + { + if( !bMod1 && !bMod2 && !bTextEdit ) + { + // when not already editing, return starts text edit + setSelectionStart( SdrTableObj::getFirstCell() ); + nAction = TblAction::EditCell; + } + break; + } + case awt::Key::F2: // f2 toggles text edit + { + if( bMod1 || bMod2 ) // f2 with modifiers is handled by the view + { + } + else if( bTextEdit ) + { + // f2 during text edit stops text edit + nAction = TblAction::StopTextEdit; + } + else if( mbCellSelectionMode ) + { + // f2 with selected cells removes selection + nAction = TblAction::RemoveSelection; + } + else + { + // f2 with no selection and no text edit starts text edit + setSelectionStart( SdrTableObj::getFirstCell() ); + nAction = TblAction::EditCell; + } + break; + } + case awt::Key::HOME: + case awt::Key::NUM7: + { + if( (bMod1 || bMod2) && (bTextEdit || mbCellSelectionMode) ) + { + if( bMod1 && !bMod2 ) + { + // ctrl + home jumps to first cell + nAction = TblAction::GotoFirstCell; + } + else if( !bMod1 && bMod2 ) + { + // alt + home jumps to first column + nAction = TblAction::GotoFirstColumn; + } + } + break; + } + case awt::Key::END: + case awt::Key::NUM1: + { + if( (bMod1 || bMod2) && (bTextEdit || mbCellSelectionMode) ) + { + if( bMod1 && !bMod2 ) + { + // ctrl + end jumps to last cell + nAction = TblAction::GotoLastCell; + } + else if( !bMod1 && bMod2 ) + { + // alt + home jumps to last column + nAction = TblAction::GotoLastColumn; + } + } + break; + } + + case awt::Key::TAB: + { + if( bTextEdit || mbCellSelectionMode ) + nAction = TblAction::Tab; + break; + } + + case awt::Key::UP: + case awt::Key::NUM8: + case awt::Key::DOWN: + case awt::Key::NUM2: + case awt::Key::LEFT: + case awt::Key::NUM4: + case awt::Key::RIGHT: + case awt::Key::NUM6: + { + + if( !bMod1 && bMod2 ) + { + if(bTextEdit || mbCellSelectionMode) + { + if( (nCode == awt::Key::UP) || (nCode == awt::Key::NUM8) ) + { + nAction = TblAction::GotoLeftCell; + break; + } + else if( (nCode == awt::Key::DOWN) || (nCode == awt::Key::NUM2) ) + { + nAction = TblAction::GotoRightCell; + break; + } + } + } + + bool bTextMove = false; + OutlinerView* pOLV = mrView.GetTextEditOutlinerView(); + if( pOLV ) + { + RemoveSelection(); + // during text edit, check if we navigate out of the cell + ESelection aOldSelection = pOLV->GetSelection(); + pOLV->PostKeyEvent(rKEvt); + bTextMove = aOldSelection == pOLV->GetSelection(); + if( !bTextMove ) + { + nAction = TblAction::NONE; + } + } + + if( mbCellSelectionMode || bTextMove ) + { + // no text edit, navigate in cells if selection active + switch( nCode ) + { + case awt::Key::LEFT: + case awt::Key::NUM4: + nAction = TblAction::GotoLeftCell; + break; + case awt::Key::RIGHT: + case awt::Key::NUM6: + nAction = TblAction::GotoRightCell; + break; + case awt::Key::DOWN: + case awt::Key::NUM2: + nAction = TblAction::GotoDownCell; + break; + case awt::Key::UP: + case awt::Key::NUM8: + nAction = TblAction::GotoUpCell; + break; + } + } + break; + } + case awt::Key::PAGEUP: + if( bMod2 ) + nAction = TblAction::GotoFirstRow; + break; + + case awt::Key::PAGEDOWN: + if( bMod2 ) + nAction = TblAction::GotoLastRow; + break; + } + return nAction; +} + +bool SvxTableController::executeAction(TblAction nAction, bool bSelect, vcl::Window* pWindow) +{ + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if( !pTableObj ) + return false; + + switch( nAction ) + { + case TblAction::GotoFirstCell: + { + gotoCell( SdrTableObj::getFirstCell(), bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoLeftCell: + { + gotoCell( pTableObj->getLeftCell( getSelectionEnd(), !bSelect ), bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoRightCell: + { + gotoCell( pTableObj->getRightCell( getSelectionEnd(), !bSelect ), bSelect, pWindow, nAction); + break; + } + + case TblAction::GotoLastCell: + { + gotoCell( pTableObj->getLastCell(), bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoFirstColumn: + { + CellPos aPos( SdrTableObj::getFirstCell().mnCol, getSelectionEnd().mnRow ); + gotoCell( aPos, bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoLastColumn: + { + CellPos aPos( pTableObj->getLastCell().mnCol, getSelectionEnd().mnRow ); + gotoCell( aPos, bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoFirstRow: + { + CellPos aPos( getSelectionEnd().mnCol, SdrTableObj::getFirstCell().mnRow ); + gotoCell( aPos, bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoUpCell: + { + gotoCell( pTableObj->getUpCell(getSelectionEnd(), !bSelect), bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoDownCell: + { + gotoCell( pTableObj->getDownCell(getSelectionEnd(), !bSelect), bSelect, pWindow, nAction ); + break; + } + + case TblAction::GotoLastRow: + { + CellPos aPos( getSelectionEnd().mnCol, pTableObj->getLastCell().mnRow ); + gotoCell( aPos, bSelect, pWindow, nAction ); + break; + } + + case TblAction::EditCell: + EditCell( getSelectionStart(), pWindow, nAction ); + break; + + case TblAction::StopTextEdit: + StopTextEdit(); + break; + + case TblAction::RemoveSelection: + RemoveSelection(); + break; + + case TblAction::Tab: + { + if( bSelect ) + gotoCell( pTableObj->getPreviousCell( getSelectionEnd(), true ), false, pWindow, nAction ); + else + { + CellPos aSelectionEnd( getSelectionEnd() ); + CellPos aNextCell( pTableObj->getNextCell( aSelectionEnd, true ) ); + if( aSelectionEnd == aNextCell ) + { + onInsert( SID_TABLE_INSERT_ROW ); + aNextCell = pTableObj->getNextCell( aSelectionEnd, true ); + } + gotoCell( aNextCell, false, pWindow, nAction ); + } + break; + } + default: + break; + } + + return nAction != TblAction::HandledByView; +} + + +void SvxTableController::gotoCell(const CellPos& rPos, bool bSelect, vcl::Window* pWindow, TblAction nAction /*= TblAction::NONE */) +{ + if( mxTableObj.is() && mxTableObj->IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + if( bSelect ) + { + maCursorLastPos = rPos; + if( mxTableObj.is() ) + mxTableObj->setActiveCell( rPos ); + + if( !mbCellSelectionMode ) + { + setSelectedCells( maCursorFirstPos, rPos ); + } + else + { + UpdateSelection( rPos ); + } + } + else + { + RemoveSelection(); + EditCell( rPos, pWindow, nAction ); + } +} + + +const CellPos& SvxTableController::getSelectionStart() +{ + checkCell( maCursorFirstPos ); + return maCursorFirstPos; +} + + +void SvxTableController::setSelectionStart( const CellPos& rPos ) +{ + maCursorFirstPos = rPos; +} + + +const CellPos& SvxTableController::getSelectionEnd() +{ + checkCell( maCursorLastPos ); + return maCursorLastPos; +} + + +void SvxTableController::MergeRange( sal_Int32 nFirstCol, sal_Int32 nFirstRow, sal_Int32 nLastCol, sal_Int32 nLastRow ) +{ + if(!checkTableObject() || !mxTable.is()) + return; + + try + { + Reference< XMergeableCellRange > xRange( mxTable->createCursorByRange( mxTable->getCellRangeByPosition( nFirstCol, nFirstRow,nLastCol, nLastRow ) ), UNO_QUERY_THROW ); + + if( xRange->isMergeable() ) + { + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_MERGE) ); + rModel.AddUndo(rModel.GetSdrUndoFactory().CreateUndoGeoObject(rTableObj)); + } + + xRange->merge(); + mbHasJustMerged = true; + setSelectedCells( maCursorFirstPos, maCursorFirstPos ); + + if( bUndo ) + rModel.EndUndo(); + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION( "svx.table", "" ); + } +} + + +void SvxTableController::checkCell( CellPos& rPos ) const +{ + if( !mxTable.is() ) + return; + + try + { + if( rPos.mnCol >= mxTable->getColumnCount() ) + rPos.mnCol = mxTable->getColumnCount()-1; + + if( rPos.mnRow >= mxTable->getRowCount() ) + rPos.mnRow = mxTable->getRowCount()-1; + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + + +void SvxTableController::findMergeOrigin( CellPos& rPos ) +{ + if( !mxTable.is() ) + return; + + try + { + Reference< XMergeableCell > xCell( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ), UNO_QUERY_THROW ); + if( xCell->isMerged() ) + { + ::findMergeOrigin( mxTable, rPos.mnCol, rPos.mnRow, rPos.mnCol, rPos.mnRow ); + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx.table", ""); + } +} + + +void SvxTableController::EditCell(const CellPos& rPos, vcl::Window* pWindow, TblAction nAction /*= TblAction::NONE */) +{ + SdrPageView* pPV(mrView.GetSdrPageView()); + + if(nullptr == pPV || !checkTableObject()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + + if(rTableObj.getSdrPageFromSdrObject() != pPV->GetPage()) + return; + + bool bEmptyOutliner = false; + + if(!rTableObj.GetOutlinerParaObject() && mrView.GetTextEditOutliner()) + { + ::Outliner* pOutl = mrView.GetTextEditOutliner(); + sal_Int32 nParaCnt = pOutl->GetParagraphCount(); + Paragraph* p1stPara = pOutl->GetParagraph( 0 ); + + if(nParaCnt==1 && p1stPara) + { + // with only one paragraph + if (pOutl->GetText(p1stPara).isEmpty()) + { + bEmptyOutliner = true; + } + } + } + + CellPos aPos( rPos ); + findMergeOrigin( aPos ); + + if( &rTableObj == mrView.GetTextEditObject() && !bEmptyOutliner && rTableObj.IsTextEditActive( aPos ) ) + return; + + if( rTableObj.IsTextEditActive() ) + mrView.SdrEndTextEdit(true); + + rTableObj.setActiveCell( aPos ); + + // create new outliner, owner will be the SdrObjEditView + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + std::unique_ptr<SdrOutliner> pOutl(SdrMakeOutliner(OutlinerMode::OutlineObject, rModel)); + + if (pOutl && rTableObj.IsVerticalWriting()) + pOutl->SetVertical( true ); + + if (!mrView.SdrBeginTextEdit(&rTableObj, pPV, pWindow, true, pOutl.release())) + return; + + maCursorLastPos = maCursorFirstPos = rPos; + + OutlinerView* pOLV = mrView.GetTextEditOutlinerView(); + + // Move cursor to end of text + ESelection aNewSelection; + + const WritingMode eMode = rTableObj.GetWritingMode(); + if (((nAction == TblAction::GotoLeftCell) || (nAction == TblAction::GotoRightCell)) && (eMode != WritingMode_TB_RL)) + { + const bool bLast = ((nAction == TblAction::GotoLeftCell) && (eMode == WritingMode_LR_TB)) || + ((nAction == TblAction::GotoRightCell) && (eMode == WritingMode_RL_TB)); + + if( bLast ) + aNewSelection = ESelection(EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND, EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND); + } + pOLV->SetSelection(aNewSelection); +} + + +void SvxTableController::StopTextEdit() +{ + if(mrView.IsTextEdit()) + { + mrView.SdrEndTextEdit(); + mrView.SetCurrentObj(SdrObjKind::Table); + mrView.SetEditMode(SdrViewEditMode::Edit); + } +} + + +void SvxTableController::getSelectedCells( CellPos& rFirst, CellPos& rLast ) +{ + if( mbCellSelectionMode ) + { + checkCell( maCursorFirstPos ); + checkCell( maCursorLastPos ); + + rFirst.mnCol = std::min( maCursorFirstPos.mnCol, maCursorLastPos.mnCol ); + rFirst.mnRow = std::min( maCursorFirstPos.mnRow, maCursorLastPos.mnRow ); + rLast.mnCol = std::max( maCursorFirstPos.mnCol, maCursorLastPos.mnCol ); + rLast.mnRow = std::max( maCursorFirstPos.mnRow, maCursorLastPos.mnRow ); + + if( !mxTable.is() ) + return; + + bool bExt = false; + do + { + bExt = false; + for( sal_Int32 nRow = rFirst.mnRow; nRow <= rLast.mnRow && !bExt; nRow++ ) + { + for( sal_Int32 nCol = rFirst.mnCol; nCol <= rLast.mnCol && !bExt; nCol++ ) + { + Reference< XMergeableCell > xCell( mxTable->getCellByPosition( nCol, nRow ), UNO_QUERY ); + if( !xCell.is() ) + continue; + + if( xCell->isMerged() ) + { + CellPos aPos( nCol, nRow ); + findMergeOrigin( aPos ); + if( (aPos.mnCol < rFirst.mnCol) || (aPos.mnRow < rFirst.mnRow) ) + { + rFirst.mnCol = std::min( rFirst.mnCol, aPos.mnCol ); + rFirst.mnRow = std::min( rFirst.mnRow, aPos.mnRow ); + bExt = true; + } + } + else + { + if( ((nCol + xCell->getColumnSpan() - 1) > rLast.mnCol) || (nRow + xCell->getRowSpan() - 1 ) > rLast.mnRow ) + { + rLast.mnCol = std::max( rLast.mnCol, nCol + xCell->getColumnSpan() - 1 ); + rLast.mnRow = std::max( rLast.mnRow, nRow + xCell->getRowSpan() - 1 ); + bExt = true; + } + } + } + } + } + while(bExt); + } + else if(mrView.IsTextEdit()) + { + rFirst = getSelectionStart(); + findMergeOrigin( rFirst ); + rLast = rFirst; + + if( mxTable.is() ) + { + Reference< XMergeableCell > xCell( mxTable->getCellByPosition( rLast.mnCol, rLast.mnRow ), UNO_QUERY ); + if( xCell.is() ) + { + rLast.mnCol += xCell->getColumnSpan() - 1; + rLast.mnRow += xCell->getRowSpan() - 1; + } + } + } + else + { + rFirst.mnCol = 0; + rFirst.mnRow = 0; + if( mxTable.is() ) + { + rLast.mnRow = mxTable->getRowCount()-1; + rLast.mnCol = mxTable->getColumnCount()-1; + } + else + { + rLast.mnRow = 0; + rLast.mnCol = 0; + } + } +} + + +void SvxTableController::StartSelection( const CellPos& rPos ) +{ + StopTextEdit(); + mbCellSelectionMode = true; + maCursorLastPos = maCursorFirstPos = rPos; + mrView.MarkListHasChanged(); +} + + +void SvxTableController::setSelectedCells( const CellPos& rStart, const CellPos& rEnd ) +{ + StopTextEdit(); + mbCellSelectionMode = true; + maCursorFirstPos = rStart; + UpdateSelection( rEnd ); +} + + +bool SvxTableController::ChangeFontSize(bool bGrow, const FontList* pFontList) +{ + if(!checkTableObject() || !mxTable.is()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + + if (mrView.IsTextEdit()) + return true; + + CellPos aStart, aEnd; + + if(hasSelectedCells()) + { + getSelectedCells(aStart, aEnd); + } + else + { + aStart.mnRow = 0; + aStart.mnCol = 0; + aEnd.mnRow = mxTable->getRowCount() - 1; + aEnd.mnCol = mxTable->getColumnCount() - 1; + } + + for (sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++) + { + for (sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++) + { + CellRef xCell(dynamic_cast< Cell* >(mxTable->getCellByPosition(nCol, nRow).get())); + if (xCell.is()) + { + if (rModel.IsUndoEnabled()) + xCell->AddUndo(); + + SfxItemSet aCellSet(xCell->GetItemSet()); + if (EditView::ChangeFontSize(bGrow, aCellSet, pFontList)) + { + xCell->SetMergedItemSetAndBroadcast(aCellSet, false); + } + } + } + } + + UpdateTableShape(); + + return true; +} + + +void SvxTableController::UpdateSelection( const CellPos& rPos ) +{ + maCursorLastPos = rPos; + mrView.MarkListHasChanged(); +} + + +void SvxTableController::clearSelection() +{ + RemoveSelection(); +} + + +void SvxTableController::selectAll() +{ + if( mxTable.is() ) + { + CellPos aPos2( mxTable->getColumnCount()-1, mxTable->getRowCount()-1 ); + if( (aPos2.mnCol >= 0) && (aPos2.mnRow >= 0) ) + { + CellPos aPos1; + setSelectedCells( aPos1, aPos2 ); + } + } +} + + +void SvxTableController::RemoveSelection() +{ + if( mbCellSelectionMode ) + { + mbCellSelectionMode = false; + mrView.MarkListHasChanged(); + } +} + + +void SvxTableController::onTableModified() +{ + if( mnUpdateEvent == nullptr ) + mnUpdateEvent = Application::PostUserEvent( LINK( this, SvxTableController, UpdateHdl ) ); +} + + +void SvxTableController::updateSelectionOverlay() +{ + // There is no need to update selection overlay after merging cells + // since the selection overlay should remain the same + if ( mbHasJustMerged ) + return; + + destroySelectionOverlay(); + if( !mbCellSelectionMode ) + return; + + sdr::table::SdrTableObj* pTableObj = mxTableObj.get(); + if( !pTableObj ) + return; + + sdr::overlay::OverlayObjectCell::RangeVector aRanges; + + tools::Rectangle aStartRect, aEndRect; + CellPos aStart,aEnd; + getSelectedCells( aStart, aEnd ); + pTableObj->getCellBounds( aStart, aStartRect ); + + basegfx::B2DRange a2DRange( basegfx::B2DPoint(aStartRect.Left(), aStartRect.Top()) ); + a2DRange.expand( basegfx::B2DPoint(aStartRect.Right(), aStartRect.Bottom()) ); + + findMergeOrigin( aEnd ); + pTableObj->getCellBounds( aEnd, aEndRect ); + a2DRange.expand( basegfx::B2DPoint(aEndRect.Left(), aEndRect.Top()) ); + a2DRange.expand( basegfx::B2DPoint(aEndRect.Right(), aEndRect.Bottom()) ); + aRanges.push_back( a2DRange ); + + ::Color aHighlight( COL_BLUE ); + OutputDevice* pOutDev = mrView.GetFirstOutputDevice(); + if( pOutDev ) + aHighlight = pOutDev->GetSettings().GetStyleSettings().GetHighlightColor(); + + const sal_uInt32 nCount = mrView.PaintWindowCount(); + for( sal_uInt32 nIndex = 0; nIndex < nCount; nIndex++ ) + { + SdrPaintWindow* pPaintWindow = mrView.GetPaintWindow(nIndex); + if( pPaintWindow ) + { + const rtl::Reference < sdr::overlay::OverlayManager >& xOverlayManager = pPaintWindow->GetOverlayManager(); + if( xOverlayManager.is() ) + { + std::unique_ptr<sdr::overlay::OverlayObjectCell> pOverlay(new sdr::overlay::OverlayObjectCell( aHighlight, std::vector(aRanges) )); + + xOverlayManager->add(*pOverlay); + mpSelectionOverlay.emplace(); + mpSelectionOverlay->append(std::move(pOverlay)); + } + } + } + + // If tiled rendering, emit callbacks for sdr table selection. + if (!(pOutDev && comphelper::LibreOfficeKit::isActive())) + return; + + tools::Rectangle aSelection(a2DRange.getMinX(), a2DRange.getMinY(), a2DRange.getMaxX(), a2DRange.getMaxY()); + + if (pOutDev->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + aSelection = o3tl::toTwips(aSelection, o3tl::Length::mm100); + + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, aSelection.toString().getStr()); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aSelection.toString().getStr()); + } +} + + +void SvxTableController::destroySelectionOverlay() +{ + if( !mpSelectionOverlay ) + return; + + mpSelectionOverlay.reset(); + + if (comphelper::LibreOfficeKit::isActive()) + { + // Clear the LOK text selection so far provided by this table. + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, "EMPTY"); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, "EMPTY"); + } + } +} + + +void SvxTableController::MergeAttrFromSelectedCells(SfxItemSet& rAttr, bool bOnlyHardAttr) const +{ + if( !mxTable.is() ) + return; + + CellPos aStart, aEnd; + const_cast<SvxTableController&>(*this).getSelectedCells( aStart, aEnd ); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() && !xCell->isMerged() ) + { + const SfxItemSet& rSet = xCell->GetItemSet(); + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich(aIter.FirstWhich()); + while(nWhich) + { + SfxItemState nState = aIter.GetItemState(false); + if(!bOnlyHardAttr) + { + if(SfxItemState::DONTCARE == nState) + rAttr.InvalidateItem(nWhich); + else + rAttr.MergeValue(rSet.Get(nWhich), true); + } + else if(SfxItemState::SET == nState) + { + const SfxPoolItem& rItem = rSet.Get(nWhich); + rAttr.MergeValue(rItem, true); + } + + nWhich = aIter.NextWhich(); + } + } + } + } +} + + +static void ImplSetLinePreserveColor( SvxBoxItem& rNewFrame, const SvxBorderLine* pNew, SvxBoxItemLine nLine ) +{ + if( pNew ) + { + const SvxBorderLine* pOld = rNewFrame.GetLine(nLine); + if( pOld ) + { + SvxBorderLine aNewLine( *pNew ); + aNewLine.SetColor( pOld->GetColor() ); + rNewFrame.SetLine( &aNewLine, nLine ); + return; + } + } + rNewFrame.SetLine( pNew, nLine ); +} + + +static void ImplApplyBoxItem( CellPosFlag nCellPosFlags, const SvxBoxItem* pBoxItem, const SvxBoxInfoItem* pBoxInfoItem, SvxBoxItem& rNewFrame ) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + // current cell is outside the selection + + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) ) + rNewFrame.SetLine(nullptr, SvxBoxItemLine::BOTTOM ); + } + else if (nCellPosFlags & CellPosFlag::Lower) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::TOP ); + } + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::RIGHT ); + } + else if (nCellPosFlags & CellPosFlag::After) + { + if( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) ) + rNewFrame.SetLine( nullptr, SvxBoxItemLine::LEFT ); + } + } + } + else + { + // current cell is inside the selection + + if ((nCellPosFlags & CellPosFlag::Left) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) + : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT)) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Left) ? pBoxItem->GetLeft() : pBoxInfoItem->GetVert(), SvxBoxItemLine::LEFT ); + + if( (nCellPosFlags & CellPosFlag::Right) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Right) ? pBoxItem->GetRight() : pBoxInfoItem->GetVert(), SvxBoxItemLine::RIGHT ); + + if( (nCellPosFlags & CellPosFlag::Top) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Top) ? pBoxItem->GetTop() : pBoxInfoItem->GetHori(), SvxBoxItemLine::TOP ); + + if( (nCellPosFlags & CellPosFlag::Bottom) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) ) + rNewFrame.SetLine( (nCellPosFlags & CellPosFlag::Bottom) ? pBoxItem->GetBottom() : pBoxInfoItem->GetHori(), SvxBoxItemLine::BOTTOM ); + + // apply distance to borders + if( pBoxInfoItem->IsValid( SvxBoxInfoItemValidFlags::DISTANCE ) ) + for( SvxBoxItemLine nLine : o3tl::enumrange<SvxBoxItemLine>() ) + rNewFrame.SetDistance( pBoxItem->GetDistance( nLine ), nLine ); + } +} + + +static void ImplSetLineColor( SvxBoxItem& rNewFrame, SvxBoxItemLine nLine, const Color& rColor ) +{ + const SvxBorderLine* pSourceLine = rNewFrame.GetLine( nLine ); + if( pSourceLine ) + { + SvxBorderLine aLine( *pSourceLine ); + aLine.SetColor( rColor ); + rNewFrame.SetLine( &aLine, nLine ); + } +} + + +static void ImplApplyLineColorItem( CellPosFlag nCellPosFlags, const SvxColorItem* pLineColorItem, SvxBoxItem& rNewFrame ) +{ + const Color aColor( pLineColorItem->GetValue() ); + + if (!(nCellPosFlags & (CellPosFlag::Lower|CellPosFlag::Before|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::BOTTOM, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Before|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::TOP, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower|CellPosFlag::After))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::RIGHT, aColor ); + + if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower|CellPosFlag::Before))) + ImplSetLineColor( rNewFrame, SvxBoxItemLine::LEFT, aColor ); +} + + +static void ImplApplyBorderLineItem( CellPosFlag nCellPosFlags, const SvxBorderLine* pBorderLineItem, SvxBoxItem& rNewFrame ) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + { + if( rNewFrame.GetBottom() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::BOTTOM ); + } + else if (nCellPosFlags & CellPosFlag::Lower) + { + if( rNewFrame.GetTop() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::TOP ); + } + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + { + if( rNewFrame.GetRight() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::RIGHT ); + } + else if (nCellPosFlags & CellPosFlag::After) + { + if( rNewFrame.GetLeft() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::LEFT ); + } + } + } + else + { + if( rNewFrame.GetBottom() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::BOTTOM ); + if( rNewFrame.GetTop() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::TOP ); + if( rNewFrame.GetRight() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::RIGHT ); + if( rNewFrame.GetLeft() ) + ImplSetLinePreserveColor( rNewFrame, pBorderLineItem, SvxBoxItemLine::LEFT ); + } +} + + +void SvxTableController::ApplyBorderAttr( const SfxItemSet& rAttr ) +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + if( !(nRowCount && nColCount) ) + return; + + const SvxBoxItem* pBoxItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SDRATTR_TABLE_BORDER, false) ) + pBoxItem = &rAttr.Get( SDRATTR_TABLE_BORDER ); + + const SvxBoxInfoItem* pBoxInfoItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SDRATTR_TABLE_BORDER_INNER, false) ) + pBoxInfoItem = &rAttr.Get( SDRATTR_TABLE_BORDER_INNER ); + + const SvxColorItem* pLineColorItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SID_FRAME_LINECOLOR, false) ) + pLineColorItem = &rAttr.Get( SID_FRAME_LINECOLOR ); + + const SvxBorderLine* pBorderLineItem = nullptr; + if(SfxItemState::SET == rAttr.GetItemState(SID_FRAME_LINESTYLE, false) ) + pBorderLineItem = rAttr.Get( SID_FRAME_LINESTYLE ).GetLine(); + + if( pBoxInfoItem && !pBoxItem ) + { + const static SvxBoxItem gaEmptyBoxItem( SDRATTR_TABLE_BORDER ); + pBoxItem = &gaEmptyBoxItem; + } + else if( pBoxItem && !pBoxInfoItem ) + { + const static SvxBoxInfoItem gaEmptyBoxInfoItem( SDRATTR_TABLE_BORDER_INNER ); + pBoxInfoItem = &gaEmptyBoxInfoItem; + } + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + const sal_Int32 nLastRow = std::min( aEnd.mnRow + 2, nRowCount ); + const sal_Int32 nLastCol = std::min( aEnd.mnCol + 2, nColCount ); + + for( sal_Int32 nRow = std::max( aStart.mnRow - 1, sal_Int32(0) ); nRow < nLastRow; nRow++ ) + { + CellPosFlag nRowFlags = CellPosFlag::NONE; + nRowFlags |= (nRow == aStart.mnRow) ? CellPosFlag::Top : CellPosFlag::NONE; + nRowFlags |= (nRow == aEnd.mnRow) ? CellPosFlag::Bottom : CellPosFlag::NONE; + nRowFlags |= (nRow < aStart.mnRow) ? CellPosFlag::Upper : CellPosFlag::NONE; + nRowFlags |= (nRow > aEnd.mnRow) ? CellPosFlag::Lower : CellPosFlag::NONE; + + for( sal_Int32 nCol = std::max( aStart.mnCol - 1, sal_Int32(0) ); nCol < nLastCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() ) + continue; + + const SfxItemSet& rSet = xCell->GetItemSet(); + const SvxBoxItem* pOldOuter = &rSet.Get( SDRATTR_TABLE_BORDER ); + + SvxBoxItem aNewFrame( *pOldOuter ); + + CellPosFlag nCellPosFlags = nRowFlags; + nCellPosFlags |= (nCol == aStart.mnCol) ? CellPosFlag::Left : CellPosFlag::NONE; + nCellPosFlags |= (nCol == aEnd.mnCol) ? CellPosFlag::Right : CellPosFlag::NONE; + nCellPosFlags |= (nCol < aStart.mnCol) ? CellPosFlag::Before : CellPosFlag::NONE; + nCellPosFlags |= (nCol > aEnd.mnCol) ? CellPosFlag::After : CellPosFlag::NONE; + + if( pBoxItem && pBoxInfoItem ) + ImplApplyBoxItem( nCellPosFlags, pBoxItem, pBoxInfoItem, aNewFrame ); + + if( pLineColorItem ) + ImplApplyLineColorItem( nCellPosFlags, pLineColorItem, aNewFrame ); + + if( pBorderLineItem ) + ImplApplyBorderLineItem( nCellPosFlags, pBorderLineItem, aNewFrame ); + + if (aNewFrame != *pOldOuter) + { + SfxItemSet aAttr(*rSet.GetPool(), rSet.GetRanges()); + aAttr.Put(aNewFrame); + xCell->SetMergedItemSetAndBroadcast( aAttr, false ); + } + } + } +} + + +void SvxTableController::UpdateTableShape() +{ + SdrObject* pTableObj = mxTableObj.get(); + if( pTableObj ) + { + pTableObj->ActionChanged(); + pTableObj->BroadcastObjectChange(); + } + updateSelectionOverlay(); +} + + +void SvxTableController::SetAttrToSelectedCells(const SfxItemSet& rAttr, bool bReplaceAll) +{ + if(!checkTableObject() || !mxTable.is()) + return; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo( SvxResId(STR_TABLE_NUMFORMAT) ); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + SfxItemSet aAttr(*rAttr.GetPool(), rAttr.GetRanges()); + aAttr.Put(rAttr); + + const bool bFrame = (rAttr.GetItemState( SDRATTR_TABLE_BORDER ) == SfxItemState::SET) || (rAttr.GetItemState( SDRATTR_TABLE_BORDER_INNER ) == SfxItemState::SET); + + if( bFrame ) + { + aAttr.ClearItem( SDRATTR_TABLE_BORDER ); + aAttr.ClearItem( SDRATTR_TABLE_BORDER_INNER ); + } + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + if( bUndo ) + xCell->AddUndo(); + xCell->SetMergedItemSetAndBroadcast(aAttr, bReplaceAll); + } + } + } + + if( bFrame ) + { + ApplyBorderAttr( rAttr ); + } + + UpdateTableShape(); + + if( bUndo ) + rModel.EndUndo(); +} + +void SvxTableController::SetAttrToSelectedShape(const SfxItemSet& rAttr) +{ + if (!checkTableObject() || !mxTable.is()) + return; + + // Filter out non-shadow items from rAttr. + SfxItemSetFixed<SDRATTR_SHADOW_FIRST, SDRATTR_SHADOW_LAST> aSet(*rAttr.GetPool()); + aSet.Put(rAttr); + + if (!aSet.Count()) + { + // If there are no items to be applied on the shape, then don't set anything, it would + // terminate text edit without a good reason, which affects undo/redo. + return; + } + + // Set shadow items on the marked shape. + mrView.SetAttrToMarked(aSet, /*bReplaceAll=*/false); +} + +bool SvxTableController::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if( mxTableObj.is() && hasSelectedCells() ) + { + MergeAttrFromSelectedCells( rTargetSet, bOnlyHardAttr ); + + if( mrView.IsTextEdit() ) + { + OutlinerView* pTextEditOutlinerView = mrView.GetTextEditOutlinerView(); + if(pTextEditOutlinerView) + { + // FALSE= consider InvalidItems not as the default, but as "holes" + rTargetSet.Put(pTextEditOutlinerView->GetAttribs(), false); + } + } + + return true; + } + else + { + return false; + } +} + + +bool SvxTableController::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + if( mbCellSelectionMode || mrView.IsTextEdit() ) + { + SetAttrToSelectedCells( rSet, bReplaceAll ); + return true; + } + return false; +} + +SdrObject* SvxTableController::GetMarkedSdrObjClone(SdrModel& rTargetModel) +{ + SdrTableObj* pRetval(nullptr); + sdr::table::SdrTableObj* pCurrentSdrTableObj(GetTableObj()); + + if(nullptr == pCurrentSdrTableObj) + { + return pRetval; + } + + if(!mxTableObj.is()) + { + return pRetval; + } + + // get selection and create full selection + CellPos aStart, aEnd; + const CellPos aFullStart, aFullEnd(mxTable->getColumnCount()-1, mxTable->getRowCount()-1); + + getSelectedCells(aStart, aEnd); + + // compare to see if we have a partial selection + if(aStart != aFullStart || aEnd != aFullEnd) + { + // create full clone + pRetval = pCurrentSdrTableObj->CloneSdrObject(rTargetModel); + + // limit SdrObject's TableModel to partial selection + pRetval->CropTableModelToSelection(aStart, aEnd); + } + + return pRetval; +} + +bool SvxTableController::PasteObjModel( const SdrModel& rModel ) +{ + if( mxTableObj.is() && (rModel.GetPageCount() >= 1) ) + { + const SdrPage* pPastePage = rModel.GetPage(0); + if( pPastePage && pPastePage->GetObjCount() == 1 ) + { + SdrTableObj* pPasteTableObj = dynamic_cast< SdrTableObj* >( pPastePage->GetObj(0) ); + if( pPasteTableObj ) + { + return PasteObject( pPasteTableObj ); + } + } + } + + return false; +} + + +bool SvxTableController::PasteObject( SdrTableObj const * pPasteTableObj ) +{ + if( !pPasteTableObj ) + return false; + + Reference< XTable > xPasteTable( pPasteTableObj->getTable() ); + if( !xPasteTable.is() ) + return false; + + if( !mxTable.is() ) + return false; + + sal_Int32 nPasteColumns = xPasteTable->getColumnCount(); + sal_Int32 nPasteRows = xPasteTable->getRowCount(); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + + if( mrView.IsTextEdit() ) + mrView.SdrEndTextEdit(true); + + sal_Int32 nColumns = mxTable->getColumnCount(); + sal_Int32 nRows = mxTable->getRowCount(); + + const sal_Int32 nMissing = nPasteRows - ( nRows - aStart.mnRow ); + if( nMissing > 0 ) + { + Reference< XTableRows > xRows( mxTable->getRows() ); + xRows->insertByIndex( nRows, nMissing ); + nRows = mxTable->getRowCount(); + } + + nPasteRows = std::min( nPasteRows, nRows - aStart.mnRow ); + nPasteColumns = std::min( nPasteColumns, nColumns - aStart.mnCol ); + + // copy cell contents + for( sal_Int32 nRow = 0; nRow < nPasteRows; ++nRow ) + { + for( sal_Int32 nCol = 0, nTargetCol = aStart.mnCol; nCol < nPasteColumns; ++nCol ) + { + CellRef xTargetCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nTargetCol, aStart.mnRow + nRow ).get() ) ); + if( xTargetCell.is() && !xTargetCell->isMerged() ) + { + CellRef xSourceCell(dynamic_cast<Cell*>(xPasteTable->getCellByPosition(nCol, nRow).get())); + if (xSourceCell.is()) + { + xTargetCell->AddUndo(); + // Prevent cell span exceeding the pasting range. + if (nColumns < nTargetCol + xSourceCell->getColumnSpan()) + xTargetCell->replaceContentAndFormatting(xSourceCell); + else + xTargetCell->cloneFrom(xSourceCell); + + nCol += xSourceCell->getColumnSpan() - 1; + nTargetCol += xTargetCell->getColumnSpan(); + } + } + } + } + + UpdateTableShape(); + + return true; +} + +bool SvxTableController::ApplyFormatPaintBrush( SfxItemSet& rFormatSet, bool bNoCharacterFormats, bool bNoParagraphFormats ) +{ + if(!mbCellSelectionMode) + { + return false; + } + + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + const bool bUndo(rModel.IsUndoEnabled()); + + if( bUndo ) + rModel.BegUndo(SvxResId(STR_TABLE_NUMFORMAT)); + + CellPos aStart, aEnd; + getSelectedCells( aStart, aEnd ); + const bool bFrame = (rFormatSet.GetItemState( SDRATTR_TABLE_BORDER ) == SfxItemState::SET) || (rFormatSet.GetItemState( SDRATTR_TABLE_BORDER_INNER ) == SfxItemState::SET); + + for( sal_Int32 nRow = aStart.mnRow; nRow <= aEnd.mnRow; nRow++ ) + { + for( sal_Int32 nCol = aStart.mnCol; nCol <= aEnd.mnCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() ) + { + if (bUndo) + xCell->AddUndo(); + SdrText* pText = xCell.get(); + SdrObjEditView::ApplyFormatPaintBrushToText( rFormatSet, rTableObj, pText, bNoCharacterFormats, bNoParagraphFormats ); + } + } + } + + if( bFrame ) + { + ApplyBorderAttr( rFormatSet ); + } + + UpdateTableShape(); + + if( bUndo ) + rModel.EndUndo(); + + return true; +} + + +IMPL_LINK_NOARG(SvxTableController, UpdateHdl, void*, void) +{ + mnUpdateEvent = nullptr; + + if( mbCellSelectionMode ) + { + CellPos aStart( maCursorFirstPos ); + CellPos aEnd( maCursorLastPos ); + checkCell(aStart); + checkCell(aEnd); + if( aStart != maCursorFirstPos || aEnd != maCursorLastPos ) + { + setSelectedCells( aStart, aEnd ); + } + } + + updateSelectionOverlay(); + mbHasJustMerged = false; +} + +namespace +{ + +struct LinesState +{ + LinesState(SvxBoxItem& rBoxItem_, SvxBoxInfoItem& rBoxInfoItem_) + : rBoxItem(rBoxItem_) + , rBoxInfoItem(rBoxInfoItem_) + , bDistanceIndeterminate(false) + { + aBorderSet.fill(false); + aInnerLineSet.fill(false); + aBorderIndeterminate.fill(false); + aInnerLineIndeterminate.fill(false); + aDistanceSet.fill(false); + aDistance.fill(0); + } + + SvxBoxItem& rBoxItem; + SvxBoxInfoItem& rBoxInfoItem; + o3tl::enumarray<SvxBoxItemLine, bool> aBorderSet; + o3tl::enumarray<SvxBoxInfoItemLine, bool> aInnerLineSet; + o3tl::enumarray<SvxBoxItemLine, bool> aBorderIndeterminate; + o3tl::enumarray<SvxBoxInfoItemLine, bool> aInnerLineIndeterminate; + o3tl::enumarray<SvxBoxItemLine, bool> aDistanceSet; + o3tl::enumarray<SvxBoxItemLine, sal_uInt16> aDistance; + bool bDistanceIndeterminate; +}; + +class BoxItemWrapper +{ +public: + BoxItemWrapper(SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem, SvxBoxItemLine nBorderLine, SvxBoxInfoItemLine nInnerLine, bool bBorder); + + const SvxBorderLine* getLine() const; + void setLine(const SvxBorderLine* pLine); + +private: + SvxBoxItem& m_rBoxItem; + SvxBoxInfoItem& m_rBoxInfoItem; + const SvxBoxItemLine m_nBorderLine; + const SvxBoxInfoItemLine m_nInnerLine; + const bool m_bBorder; +}; + +BoxItemWrapper::BoxItemWrapper( + SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem, + const SvxBoxItemLine nBorderLine, const SvxBoxInfoItemLine nInnerLine, const bool bBorder) + : m_rBoxItem(rBoxItem) + , m_rBoxInfoItem(rBoxInfoItem) + , m_nBorderLine(nBorderLine) + , m_nInnerLine(nInnerLine) + , m_bBorder(bBorder) +{ +} + +const SvxBorderLine* BoxItemWrapper::getLine() const +{ + if (m_bBorder) + return m_rBoxItem.GetLine(m_nBorderLine); + else + return (m_nInnerLine == SvxBoxInfoItemLine::HORI) ? m_rBoxInfoItem.GetHori() : m_rBoxInfoItem.GetVert(); +} + +void BoxItemWrapper::setLine(const SvxBorderLine* pLine) +{ + if (m_bBorder) + m_rBoxItem.SetLine(pLine, m_nBorderLine); + else + m_rBoxInfoItem.SetLine(pLine, m_nInnerLine); +} + +void lcl_MergeBorderLine( + LinesState& rLinesState, const SvxBorderLine* const pLine, const SvxBoxItemLine nLine, + SvxBoxInfoItemValidFlags nValidFlag, const bool bBorder = true) +{ + const SvxBoxInfoItemLine nInnerLine(bBorder ? SvxBoxInfoItemLine::HORI : ((nValidFlag & SvxBoxInfoItemValidFlags::HORI) ? SvxBoxInfoItemLine::HORI : SvxBoxInfoItemLine::VERT)); + BoxItemWrapper aBoxItem(rLinesState.rBoxItem, rLinesState.rBoxInfoItem, nLine, nInnerLine, bBorder); + bool& rbSet(bBorder ? rLinesState.aBorderSet[nLine] : rLinesState.aInnerLineSet[nInnerLine]); + + if (rbSet) + { + bool& rbIndeterminate(bBorder ? rLinesState.aBorderIndeterminate[nLine] : rLinesState.aInnerLineIndeterminate[nInnerLine]); + if (!rbIndeterminate) + { + const SvxBorderLine* const pMergedLine(aBoxItem.getLine()); + if ((pLine && !pMergedLine) || (!pLine && pMergedLine) || (pLine && (*pLine != *pMergedLine))) + { + aBoxItem.setLine(nullptr); + rbIndeterminate = true; + } + } + } + else + { + aBoxItem.setLine(pLine); + rbSet = true; + } +} + +void lcl_MergeBorderOrInnerLine( + LinesState& rLinesState, const SvxBorderLine* const pLine, const SvxBoxItemLine nLine, + SvxBoxInfoItemValidFlags nValidFlag, const bool bBorder) +{ + if (bBorder) + lcl_MergeBorderLine(rLinesState, pLine, nLine, nValidFlag); + else + { + const bool bVertical = (nLine == SvxBoxItemLine::LEFT) || (nLine == SvxBoxItemLine::RIGHT); + lcl_MergeBorderLine(rLinesState, pLine, nLine, bVertical ? SvxBoxInfoItemValidFlags::VERT : SvxBoxInfoItemValidFlags::HORI, false); + } +} + +void lcl_MergeDistance( + LinesState& rLinesState, const SvxBoxItemLine nIndex, const sal_uInt16 nDistance) +{ + if (rLinesState.aDistanceSet[nIndex]) + { + if (!rLinesState.bDistanceIndeterminate) + rLinesState.bDistanceIndeterminate = nDistance != rLinesState.aDistance[nIndex]; + } + else + { + rLinesState.aDistance[nIndex] = nDistance; + rLinesState.aDistanceSet[nIndex] = true; + } +} + +void lcl_MergeCommonBorderAttr(LinesState& rLinesState, const SvxBoxItem& rCellBoxItem, const CellPosFlag nCellPosFlags) +{ + if (nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After|CellPosFlag::Upper|CellPosFlag::Lower)) + { + // current cell is outside the selection + + if (!(nCellPosFlags & (CellPosFlag::Before|CellPosFlag::After))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Upper) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetBottom(), SvxBoxItemLine::TOP, SvxBoxInfoItemValidFlags::TOP); + else if (nCellPosFlags & CellPosFlag::Lower) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetTop(), SvxBoxItemLine::BOTTOM, SvxBoxInfoItemValidFlags::BOTTOM); + } + else if (!(nCellPosFlags & (CellPosFlag::Upper|CellPosFlag::Lower))) // check if it's not any corner + { + if (nCellPosFlags & CellPosFlag::Before) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetRight(), SvxBoxItemLine::LEFT, SvxBoxInfoItemValidFlags::LEFT); + else if (nCellPosFlags & CellPosFlag::After) + lcl_MergeBorderLine(rLinesState, rCellBoxItem.GetLeft(), SvxBoxItemLine::RIGHT, SvxBoxInfoItemValidFlags::RIGHT); + } + + // NOTE: inner distances for cells outside the selected range + // are not relevant -> we ignore them. + } + else + { + // current cell is inside the selection + + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetTop(), SvxBoxItemLine::TOP, SvxBoxInfoItemValidFlags::TOP, static_cast<bool>(nCellPosFlags & CellPosFlag::Top)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetBottom(), SvxBoxItemLine::BOTTOM, SvxBoxInfoItemValidFlags::BOTTOM, static_cast<bool>(nCellPosFlags & CellPosFlag::Bottom)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetLeft(), SvxBoxItemLine::LEFT, SvxBoxInfoItemValidFlags::LEFT, static_cast<bool>(nCellPosFlags & CellPosFlag::Left)); + lcl_MergeBorderOrInnerLine(rLinesState, rCellBoxItem.GetRight(), SvxBoxItemLine::RIGHT, SvxBoxInfoItemValidFlags::RIGHT, static_cast<bool>(nCellPosFlags & CellPosFlag::Right)); + + lcl_MergeDistance(rLinesState, SvxBoxItemLine::TOP, rCellBoxItem.GetDistance(SvxBoxItemLine::TOP)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::BOTTOM, rCellBoxItem.GetDistance(SvxBoxItemLine::BOTTOM)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::LEFT, rCellBoxItem.GetDistance(SvxBoxItemLine::LEFT)); + lcl_MergeDistance(rLinesState, SvxBoxItemLine::RIGHT, rCellBoxItem.GetDistance(SvxBoxItemLine::RIGHT)); + } +} + +} + +void SvxTableController::FillCommonBorderAttrFromSelectedCells( SvxBoxItem& rBoxItem, SvxBoxInfoItem& rBoxInfoItem ) const +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + if( !(nRowCount && nColCount) ) + return; + + CellPos aStart, aEnd; + const_cast< SvxTableController* >( this )->getSelectedCells( aStart, aEnd ); + + // We are adding one more row/column around the block of selected cells. + // We will be checking the adjoining border of these too. + const sal_Int32 nLastRow = std::min( aEnd.mnRow + 2, nRowCount ); + const sal_Int32 nLastCol = std::min( aEnd.mnCol + 2, nColCount ); + + rBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::ALL, false ); + LinesState aLinesState( rBoxItem, rBoxInfoItem ); + + /* Here we go through all the selected cells (enhanced by + * the adjoining row/column on each side) and determine the + * lines for presentation. The algorithm is simple: + * 1. if a border or inner line is set (or unset) in all + * cells to the same value, it will be used. + * 2. if a border or inner line is set only in some cells, + * it will be set to indeterminate state (SetValid() on + * rBoxInfoItem). + */ + for( sal_Int32 nRow = std::max( aStart.mnRow - 1, sal_Int32(0) ); nRow < nLastRow; nRow++ ) + { + CellPosFlag nRowFlags = CellPosFlag::NONE; + nRowFlags |= (nRow == aStart.mnRow) ? CellPosFlag::Top : CellPosFlag::NONE; + nRowFlags |= (nRow == aEnd.mnRow) ? CellPosFlag::Bottom : CellPosFlag::NONE; + nRowFlags |= (nRow < aStart.mnRow) ? CellPosFlag::Upper : CellPosFlag::NONE; + nRowFlags |= (nRow > aEnd.mnRow) ? CellPosFlag::Lower : CellPosFlag::NONE; + + for( sal_Int32 nCol = std::max( aStart.mnCol - 1, sal_Int32(0) ); nCol < nLastCol; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( !xCell.is() ) + continue; + + CellPosFlag nCellPosFlags = nRowFlags; + nCellPosFlags |= (nCol == aStart.mnCol) ? CellPosFlag::Left : CellPosFlag::NONE; + nCellPosFlags |= (nCol == aEnd.mnCol) ? CellPosFlag::Right : CellPosFlag::NONE; + nCellPosFlags |= (nCol < aStart.mnCol) ? CellPosFlag::Before : CellPosFlag::NONE; + nCellPosFlags |= (nCol > aEnd.mnCol) ? CellPosFlag::After : CellPosFlag::NONE; + + const SfxItemSet& rSet = xCell->GetItemSet(); + SvxBoxItem aCellBoxItem(mergeDrawinglayerTextDistancesAndSvxBoxItem(rSet)); + lcl_MergeCommonBorderAttr( aLinesState, aCellBoxItem, nCellPosFlags ); + } + } + + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::TOP]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::TOP); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::BOTTOM]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::BOTTOM); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::LEFT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::LEFT); + if (!aLinesState.aBorderIndeterminate[SvxBoxItemLine::RIGHT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::RIGHT); + if (!aLinesState.aInnerLineIndeterminate[SvxBoxInfoItemLine::HORI]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::HORI); + if (!aLinesState.aInnerLineIndeterminate[SvxBoxInfoItemLine::VERT]) + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::VERT); + + if (!aLinesState.bDistanceIndeterminate) + { + if (aLinesState.aDistanceSet[SvxBoxItemLine::TOP]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::TOP], SvxBoxItemLine::TOP); + if (aLinesState.aDistanceSet[SvxBoxItemLine::BOTTOM]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::BOTTOM], SvxBoxItemLine::BOTTOM); + if (aLinesState.aDistanceSet[SvxBoxItemLine::LEFT]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::LEFT], SvxBoxItemLine::LEFT); + if (aLinesState.aDistanceSet[SvxBoxItemLine::RIGHT]) + aLinesState.rBoxItem.SetDistance(aLinesState.aDistance[SvxBoxItemLine::RIGHT], SvxBoxItemLine::RIGHT); + aLinesState.rBoxInfoItem.SetValid(SvxBoxInfoItemValidFlags::DISTANCE); + } +} + +bool SvxTableController::selectRow( sal_Int32 row ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( 0, row ), aEnd( mxTable->getColumnCount() - 1, row ); + StartSelection( aEnd ); + gotoCell( aStart, true, nullptr ); + return true; +} + +bool SvxTableController::selectColumn( sal_Int32 column ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( column, 0 ), aEnd( column, mxTable->getRowCount() - 1 ); + StartSelection( aEnd ); + gotoCell( aStart, true, nullptr ); + return true; +} + +bool SvxTableController::deselectRow( sal_Int32 row ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( 0, row ), aEnd( mxTable->getColumnCount() - 1, row ); + StartSelection( aEnd ); + gotoCell( aStart, false, nullptr ); + return true; +} + +bool SvxTableController::deselectColumn( sal_Int32 column ) +{ + if( !mxTable.is() ) + return false; + CellPos aStart( column, 0 ), aEnd( column, mxTable->getRowCount() - 1 ); + StartSelection( aEnd ); + gotoCell( aStart, false, nullptr ); + return true; +} + +bool SvxTableController::isRowSelected( sal_Int32 nRow ) +{ + if( hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + getSelectedCells( aFirstPos, aLastPos ); + if( (aFirstPos.mnCol == 0) && (nRow >= aFirstPos.mnRow && nRow <= aLastPos.mnRow) && (mxTable->getColumnCount() - 1 == aLastPos.mnCol) ) + return true; + } + return false; +} + +bool SvxTableController::isColumnSelected( sal_Int32 nColumn ) +{ + if( hasSelectedCells() ) + { + CellPos aFirstPos, aLastPos; + getSelectedCells( aFirstPos, aLastPos ); + if( (aFirstPos.mnRow == 0) && (nColumn >= aFirstPos.mnCol && nColumn <= aLastPos.mnCol) && (mxTable->getRowCount() - 1 == aLastPos.mnRow) ) + return true; + } + return false; +} + +bool SvxTableController::isRowHeader() +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); + + return aSettings.mbUseFirstRow; +} + +bool SvxTableController::isColumnHeader() +{ + if(!checkTableObject()) + return false; + + SdrTableObj& rTableObj(*mxTableObj); + TableStyleSettings aSettings(rTableObj.getTableStyleSettings()); + + return aSettings.mbUseFirstColumn; +} + +bool SvxTableController::setCursorLogicPosition(const Point& rPosition, bool bPoint) +{ + if (mxTableObj->GetObjIdentifier() != SdrObjKind::Table) + return false; + + SdrTableObj* pTableObj = mxTableObj.get(); + CellPos aCellPos; + if (pTableObj->CheckTableHit(rPosition, aCellPos.mnCol, aCellPos.mnRow) != TableHitKind::NONE) + { + // Position is a table cell. + if (mbCellSelectionMode) + { + // We have a table selection already: adjust the point or the mark. + if (bPoint) + setSelectedCells(maCursorFirstPos, aCellPos); + else + setSelectedCells(aCellPos, maCursorLastPos); + return true; + } + else if (aCellPos != maMouseDownPos) + { + // No selection, but rPosition is at another cell: start table selection. + StartSelection(maMouseDownPos); + // Update graphic selection, should be hidden now. + mrView.AdjustMarkHdl(); + } + } + + return false; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tabledesign.cxx b/svx/source/table/tabledesign.cxx new file mode 100644 index 000000000..03b16250f --- /dev/null +++ b/svx/source/table/tabledesign.cxx @@ -0,0 +1,705 @@ +/* -*- 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/XStyle.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/util/XModifyListener.hpp> + +#include <vcl/svapp.hxx> + +#include <comphelper/compbase.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/sequence.hxx> + +#include <svx/sdr/table/tabledesign.hxx> +#include <svx/dialmgr.hxx> +#include <svx/strings.hrc> + +#include <celltypes.hxx> + +#include <vector> +#include <map> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::style; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::container; + +namespace sdr::table { + +typedef std::map< OUString, sal_Int32 > CellStyleNameMap; + +typedef ::comphelper::WeakComponentImplHelper< XStyle, XNameReplace, XServiceInfo, XIndexAccess, XModifyBroadcaster, XModifyListener > TableDesignStyleBase; + +namespace { + +class TableDesignStyle : public TableDesignStyleBase +{ +public: + TableDesignStyle(); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XStyle + virtual sal_Bool SAL_CALL isUserDefined() override; + virtual sal_Bool SAL_CALL isInUse() override; + virtual OUString SAL_CALL getParentStyle() override; + virtual void SAL_CALL setParentStyle( const OUString& aParentStyle ) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName( const OUString& aName ) override; + + // XNameAccess + virtual Any SAL_CALL getByName( const OUString& aName ) override; + virtual Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override ; + virtual Any SAL_CALL getByIndex( sal_Int32 Index ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const Any& aElement ) override; + + // XModifyBroadcaster + virtual void SAL_CALL addModifyListener( const Reference< XModifyListener >& aListener ) override; + virtual void SAL_CALL removeModifyListener( const Reference< XModifyListener >& aListener ) override; + + // XModifyListener + virtual void SAL_CALL modified( const css::lang::EventObject& aEvent ) override; + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + void notifyModifyListener(); + + // this function is called upon disposing the component + virtual void disposing(std::unique_lock<std::mutex>&) override; + + static const CellStyleNameMap& getCellStyleNameMap(); + + OUString msName; + Reference< XStyle > maCellStyles[style_count]; + comphelper::OInterfaceContainerHelper4<XModifyListener> maModifyListeners; +}; + +} + +typedef std::vector< Reference< XStyle > > TableDesignStyleVector; + +namespace { + +class TableDesignFamily : public ::cppu::WeakImplHelper< XNameContainer, XNamed, XIndexAccess, XSingleServiceFactory, XServiceInfo, XComponent, XPropertySet > +{ +public: + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XNamed + virtual OUString SAL_CALL getName( ) override; + virtual void SAL_CALL setName( const OUString& aName ) override; + + // XNameAccess + virtual Any SAL_CALL getByName( const OUString& aName ) override; + virtual Sequence< OUString > SAL_CALL getElementNames() override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess + virtual Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override ; + virtual Any SAL_CALL getByIndex( sal_Int32 Index ) override; + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const Any& aElement ) override; + virtual void SAL_CALL removeByName( const OUString& Name ) override; + + // XNameReplace + virtual void SAL_CALL replaceByName( const OUString& aName, const Any& aElement ) override; + + // XSingleServiceFactory + virtual Reference< XInterface > SAL_CALL createInstance( ) override; + virtual Reference< XInterface > SAL_CALL createInstanceWithArguments( const Sequence< Any >& aArguments ) override; + + // XComponent + virtual void SAL_CALL dispose( ) override; + virtual void SAL_CALL addEventListener( const Reference< XEventListener >& xListener ) override; + virtual void SAL_CALL removeEventListener( const Reference< XEventListener >& aListener ) override; + + // XPropertySet + virtual Reference<XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const Any& aValue ) override; + virtual Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const Reference<XPropertyChangeListener>& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const Reference<XPropertyChangeListener>& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener(const OUString& PropertyName, const Reference<XVetoableChangeListener>& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener(const OUString& PropertyName,const Reference<XVetoableChangeListener>&aListener ) override; + + TableDesignStyleVector maDesigns; +}; + +} + +TableDesignStyle::TableDesignStyle() +{ +} + +const CellStyleNameMap& TableDesignStyle::getCellStyleNameMap() +{ + static CellStyleNameMap const aMap + { + { OUString( "first-row" ) , first_row_style }, + { OUString( "last-row" ) , last_row_style }, + { OUString( "first-column" ) , first_column_style }, + { OUString( "last-column" ) , last_column_style }, + { OUString( "body" ) , body_style }, + { OUString( "even-rows" ) , even_rows_style }, + { OUString( "odd-rows" ) , odd_rows_style }, + { OUString( "even-columns" ) , even_columns_style }, + { OUString( "odd-columns" ) , odd_columns_style }, + { OUString( "background" ) , background_style }, + }; + + return aMap; +} + +// XServiceInfo +OUString SAL_CALL TableDesignStyle::getImplementationName() +{ + return "TableDesignStyle"; +} + +sal_Bool SAL_CALL TableDesignStyle::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +Sequence< OUString > SAL_CALL TableDesignStyle::getSupportedServiceNames() +{ + return { "com.sun.star.style.Style" }; +} + +// XStyle +sal_Bool SAL_CALL TableDesignStyle::isUserDefined() +{ + return false; +} + +sal_Bool SAL_CALL TableDesignStyle::isInUse() +{ + std::unique_lock aGuard( m_aMutex ); + if (maModifyListeners.getLength(aGuard)) + { + comphelper::OInterfaceIteratorHelper4 it(aGuard, maModifyListeners); + while ( it.hasMoreElements() ) + { + TableDesignUser* pUser = dynamic_cast< TableDesignUser* >( it.next().get() ); + if( pUser && pUser->isInUse() ) + return true; + } + } + return false; +} + + +OUString SAL_CALL TableDesignStyle::getParentStyle() +{ + return OUString(); +} + + +void SAL_CALL TableDesignStyle::setParentStyle( const OUString& ) +{ +} + + +// XNamed + + +OUString SAL_CALL TableDesignStyle::getName() +{ + return msName; +} + + +void SAL_CALL TableDesignStyle::setName( const OUString& rName ) +{ + msName = rName; +} + + +// XNameAccess + + +Any SAL_CALL TableDesignStyle::getByName( const OUString& rName ) +{ + const CellStyleNameMap& rMap = getCellStyleNameMap(); + + CellStyleNameMap::const_iterator iter = rMap.find( rName ); + if( iter == rMap.end() ) + throw NoSuchElementException(); + + return Any( maCellStyles[(*iter).second] ); +} + + +Sequence< OUString > SAL_CALL TableDesignStyle::getElementNames() +{ + return comphelper::mapKeysToSequence( getCellStyleNameMap() ); +} + + +sal_Bool SAL_CALL TableDesignStyle::hasByName( const OUString& rName ) +{ + const CellStyleNameMap& rMap = getCellStyleNameMap(); + + CellStyleNameMap::const_iterator iter = rMap.find( rName ); + return iter != rMap.end(); +} + + +// XElementAccess + + +Type SAL_CALL TableDesignStyle::getElementType() +{ + return cppu::UnoType<XStyle>::get(); +} + + +sal_Bool SAL_CALL TableDesignStyle::hasElements() +{ + return true; +} + + +// XIndexAccess + + +sal_Int32 SAL_CALL TableDesignStyle::getCount() +{ + return style_count; +} + + +Any SAL_CALL TableDesignStyle::getByIndex( sal_Int32 Index ) +{ + if( (Index < 0) || (Index >= style_count) ) + throw IndexOutOfBoundsException(); + + std::unique_lock aGuard( m_aMutex ); + return Any( maCellStyles[Index] ); +} + + +// XNameReplace + + +void SAL_CALL TableDesignStyle::replaceByName( const OUString& rName, const Any& aElement ) +{ + const CellStyleNameMap& rMap = getCellStyleNameMap(); + CellStyleNameMap::const_iterator iter = rMap.find( rName ); + if( iter == rMap.end() ) + throw NoSuchElementException(); + + + Reference< XStyle > xNewStyle; + if( !(aElement >>= xNewStyle) ) + throw IllegalArgumentException(); + + const sal_Int32 nIndex = (*iter).second; + + std::unique_lock aGuard( m_aMutex ); + + Reference< XStyle > xOldStyle( maCellStyles[nIndex] ); + + if( xNewStyle == xOldStyle ) + return; + + Reference< XModifyListener > xListener( this ); + + // end listening to old style, if possible + Reference< XModifyBroadcaster > xOldBroadcaster( xOldStyle, UNO_QUERY ); + if( xOldBroadcaster.is() ) + xOldBroadcaster->removeModifyListener( xListener ); + + // start listening to new style, if possible + Reference< XModifyBroadcaster > xNewBroadcaster( xNewStyle, UNO_QUERY ); + if( xNewBroadcaster.is() ) + xNewBroadcaster->addModifyListener( xListener ); + + maCellStyles[nIndex] = xNewStyle; +} + + +// XComponent + + +void TableDesignStyle::disposing(std::unique_lock<std::mutex>&) +{ + for(Reference<XStyle> & rCellStyle : maCellStyles) + rCellStyle.clear(); +} + + +// XModifyBroadcaster + + +void SAL_CALL TableDesignStyle::addModifyListener( const Reference< XModifyListener >& xListener ) +{ + std::unique_lock aGuard( m_aMutex ); + if (m_bDisposed) + { + aGuard.unlock(); + EventObject aEvt( static_cast< OWeakObject * >( this ) ); + xListener->disposing( aEvt ); + } + else + { + maModifyListeners.addInterface( aGuard, xListener ); + } +} + + +void SAL_CALL TableDesignStyle::removeModifyListener( const Reference< XModifyListener >& xListener ) +{ + std::unique_lock aGuard( m_aMutex ); + maModifyListeners.removeInterface( aGuard, xListener ); +} + + +void TableDesignStyle::notifyModifyListener() +{ + std::unique_lock aGuard( m_aMutex ); + + if( maModifyListeners.getLength(aGuard) ) + { + EventObject aEvt( static_cast< OWeakObject * >( this ) ); + maModifyListeners.forEach(aGuard, + [&] (Reference<XModifyListener> const& xListener) + { return xListener->modified(aEvt); }); + } +} + + +// XModifyListener + + +// if we get a modify hint from a style, notify all registered XModifyListener +void SAL_CALL TableDesignStyle::modified( const css::lang::EventObject& ) +{ + notifyModifyListener(); +} + + +void SAL_CALL TableDesignStyle::disposing( const css::lang::EventObject& ) +{ +} + + +// TableStyle + + +// XServiceInfo +OUString SAL_CALL TableDesignFamily::getImplementationName() +{ + return "TableDesignFamily"; +} + +sal_Bool SAL_CALL TableDesignFamily::supportsService( const OUString& ServiceName ) +{ + return cppu::supportsService( this, ServiceName ); +} + +Sequence< OUString > SAL_CALL TableDesignFamily::getSupportedServiceNames() +{ + return { "com.sun.star.style.StyleFamily" }; +} + +// XNamed +OUString SAL_CALL TableDesignFamily::getName() +{ + return "table"; +} + +void SAL_CALL TableDesignFamily::setName( const OUString& ) +{ +} + +// XNameAccess +Any SAL_CALL TableDesignFamily::getByName( const OUString& rName ) +{ + SolarMutexGuard aGuard; + + auto iter = std::find_if(maDesigns.begin(), maDesigns.end(), + [&rName](const Reference<XStyle>& rpStyle) { return rpStyle->getName() == rName; }); + if (iter != maDesigns.end()) + return Any( (*iter) ); + + throw NoSuchElementException(); +} + + +Sequence< OUString > SAL_CALL TableDesignFamily::getElementNames() +{ + SolarMutexGuard aGuard; + + Sequence< OUString > aRet( maDesigns.size() ); + OUString* pNames = aRet.getArray(); + + for( const auto& rpStyle : maDesigns ) + *pNames++ = rpStyle->getName(); + + return aRet; +} + + +sal_Bool SAL_CALL TableDesignFamily::hasByName( const OUString& aName ) +{ + SolarMutexGuard aGuard; + + return std::any_of(maDesigns.begin(), maDesigns.end(), + [&aName](const Reference<XStyle>& rpStyle) { return rpStyle->getName() == aName; }); +} + + +// XElementAccess + + +Type SAL_CALL TableDesignFamily::getElementType() +{ + return cppu::UnoType<XStyle>::get(); +} + + +sal_Bool SAL_CALL TableDesignFamily::hasElements() +{ + SolarMutexGuard aGuard; + + return !maDesigns.empty(); +} + + +// XIndexAccess + + +sal_Int32 SAL_CALL TableDesignFamily::getCount() +{ + SolarMutexGuard aGuard; + + return sal::static_int_cast< sal_Int32 >( maDesigns.size() ); +} + + +Any SAL_CALL TableDesignFamily::getByIndex( sal_Int32 Index ) +{ + SolarMutexGuard aGuard; + + if( (Index >= 0) && (Index < sal::static_int_cast< sal_Int32 >( maDesigns.size() ) ) ) + return Any( maDesigns[Index] ); + + throw IndexOutOfBoundsException(); +} + + +// XNameContainer + + +void SAL_CALL TableDesignFamily::insertByName( const OUString& rName, const Any& rElement ) +{ + SolarMutexGuard aGuard; + + Reference< XStyle > xStyle( rElement, UNO_QUERY ); + if( !xStyle.is() ) + throw IllegalArgumentException(); + + xStyle->setName( rName ); + if (std::any_of(maDesigns.begin(), maDesigns.end(), + [&rName](const Reference<XStyle>& rpStyle) { return rpStyle->getName() == rName; })) + throw ElementExistException(); + + maDesigns.push_back( xStyle ); +} + + +void SAL_CALL TableDesignFamily::removeByName( const OUString& rName ) +{ + SolarMutexGuard aGuard; + + auto iter = std::find_if(maDesigns.begin(), maDesigns.end(), + [&rName](const Reference<XStyle>& rpStyle) { return rpStyle->getName() == rName; }); + if (iter != maDesigns.end()) + { + maDesigns.erase( iter ); + return; + } + + throw NoSuchElementException(); +} + + +// XNameReplace + + +void SAL_CALL TableDesignFamily::replaceByName( const OUString& rName, const Any& aElement ) +{ + SolarMutexGuard aGuard; + + Reference< XStyle > xStyle( aElement, UNO_QUERY ); + if( !xStyle.is() ) + throw IllegalArgumentException(); + + auto iter = std::find_if(maDesigns.begin(), maDesigns.end(), + [&rName](const Reference<XStyle>& rpStyle) { return rpStyle->getName() == rName; }); + if (iter != maDesigns.end()) + { + (*iter) = xStyle; + xStyle->setName( rName ); + return; + } + + throw NoSuchElementException(); +} + + +// XSingleServiceFactory + + +Reference< XInterface > SAL_CALL TableDesignFamily::createInstance() +{ + return Reference< XInterface >( static_cast< XStyle* >( new TableDesignStyle ) ); +} + + +Reference< XInterface > SAL_CALL TableDesignFamily::createInstanceWithArguments( const Sequence< Any >& ) +{ + return createInstance(); +} + + +// XComponent + + +void SAL_CALL TableDesignFamily::dispose( ) +{ + TableDesignStyleVector aDesigns; + aDesigns.swap( maDesigns ); + + for( const auto& rStyle : aDesigns ) + { + Reference< XComponent > xComp( rStyle, UNO_QUERY ); + if( xComp.is() ) + xComp->dispose(); + } +} + + +void SAL_CALL TableDesignFamily::addEventListener( const Reference< XEventListener >& ) +{ +} + + +void SAL_CALL TableDesignFamily::removeEventListener( const Reference< XEventListener >& ) +{ +} + + +// XPropertySet + + +Reference<XPropertySetInfo> TableDesignFamily::getPropertySetInfo() +{ + OSL_FAIL( "###unexpected!" ); + return Reference<XPropertySetInfo>(); +} + + +void TableDesignFamily::setPropertyValue( const OUString& , const Any& ) +{ + OSL_FAIL( "###unexpected!" ); +} + + +Any TableDesignFamily::getPropertyValue( const OUString& PropertyName ) +{ + if ( PropertyName != "DisplayName" ) + { + throw UnknownPropertyException( "unknown property: " + PropertyName, static_cast<OWeakObject *>(this) ); + } + + OUString sDisplayName( SvxResId( RID_SVXSTR_STYLEFAMILY_TABLEDESIGN ) ); + return Any( sDisplayName ); +} + + +void TableDesignFamily::addPropertyChangeListener( const OUString& , const Reference<XPropertyChangeListener>& ) +{ + OSL_FAIL( "###unexpected!" ); +} + + +void TableDesignFamily::removePropertyChangeListener( const OUString& , const Reference<XPropertyChangeListener>& ) +{ + OSL_FAIL( "###unexpected!" ); +} + + +void TableDesignFamily::addVetoableChangeListener( const OUString& , const Reference<XVetoableChangeListener>& ) +{ + OSL_FAIL( "###unexpected!" ); +} + + +void TableDesignFamily::removeVetoableChangeListener( const OUString& , const Reference<XVetoableChangeListener>& ) +{ + OSL_FAIL( "###unexpected!" ); +} + + +Reference< XNameAccess > CreateTableDesignFamily() +{ + return new TableDesignFamily; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablehandles.cxx b/svx/source/table/tablehandles.cxx new file mode 100644 index 000000000..a776c579e --- /dev/null +++ b/svx/source/table/tablehandles.cxx @@ -0,0 +1,313 @@ +/* -*- 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 "tablehandles.hxx" + +#include <vcl/outdev.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/ptrstyle.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <svx/sdr/overlay/overlayobject.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svdmrkv.hxx> +#include <svx/svdpagv.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <sdr/overlay/overlayrectangle.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <osl/diagnose.h> + +namespace sdr::table { + +namespace { + +class OverlayTableEdge : public sdr::overlay::OverlayObject +{ +protected: + basegfx::B2DPolyPolygon maPolyPolygon; + bool mbVisible; + + // geometry creation for OverlayObject + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + +public: + OverlayTableEdge( const basegfx::B2DPolyPolygon& rPolyPolygon, bool bVisible ); +}; + +} + +TableEdgeHdl::TableEdgeHdl( const Point& rPnt, bool bHorizontal, sal_Int32 nMin, sal_Int32 nMax, sal_Int32 nEdges ) +: SdrHdl( rPnt, SdrHdlKind::User ) +, mbHorizontal( bHorizontal ) +, mnMin( nMin ) +, mnMax( nMax ) +, maEdges(nEdges) +{ +} + +void TableEdgeHdl::SetEdge( sal_Int32 nEdge, sal_Int32 nStart, sal_Int32 nEnd, TableEdgeState eState ) +{ + if( (nEdge >= 0) && (nEdge <= sal::static_int_cast<sal_Int32>(maEdges.size())) ) + { + maEdges[nEdge].mnStart = nStart; + maEdges[nEdge].mnEnd = nEnd; + maEdges[nEdge].meState = eState; + } + else + { + OSL_FAIL( "sdr::table::TableEdgeHdl::SetEdge(), invalid edge!" ); + } +} + +PointerStyle TableEdgeHdl::GetPointer() const +{ + if( mbHorizontal ) + return PointerStyle::VSplit; + else + return PointerStyle::HSplit; +} + +sal_Int32 TableEdgeHdl::GetValidDragOffset( const SdrDragStat& rDrag ) const +{ + return std::clamp( static_cast<sal_Int32>(mbHorizontal ? rDrag.GetDY() : rDrag.GetDX()), mnMin, mnMax ); +} + +basegfx::B2DPolyPolygon TableEdgeHdl::getSpecialDragPoly(const SdrDragStat& rDrag) const +{ + basegfx::B2DPolyPolygon aVisible; + basegfx::B2DPolyPolygon aInvisible; + + // create and return visible and non-visible parts for drag + getPolyPolygon(aVisible, aInvisible, &rDrag); + aVisible.append(aInvisible); + + return aVisible; +} + +void TableEdgeHdl::getPolyPolygon(basegfx::B2DPolyPolygon& rVisible, basegfx::B2DPolyPolygon& rInvisible, const SdrDragStat* pDrag) const +{ + // changed method to create visible and invisible partial polygons in one run in + // separate PolyPolygons; both kinds are used + basegfx::B2DPoint aOffset(aPos.X(), aPos.Y()); + rVisible.clear(); + rInvisible.clear(); + + if( pDrag ) + { + basegfx::Axis2D eDragAxis = mbHorizontal ? basegfx::Axis2D::Y : basegfx::Axis2D::X; + aOffset.set(eDragAxis, aOffset.get(eDragAxis) + GetValidDragOffset( *pDrag )); + } + + basegfx::B2DPoint aStart(aOffset), aEnd(aOffset); + basegfx::Axis2D eAxis = mbHorizontal ? basegfx::Axis2D::X : basegfx::Axis2D::Y; + + for( const TableEdge& aEdge : maEdges ) + { + aStart.set(eAxis, aOffset.get(eAxis) + aEdge.mnStart); + aEnd.set(eAxis, aOffset.get(eAxis) + aEdge.mnEnd); + + basegfx::B2DPolygon aPolygon; + aPolygon.append( aStart ); + aPolygon.append( aEnd ); + + if(aEdge.meState == Visible) + { + rVisible.append(aPolygon); + } + else + { + rInvisible.append(aPolygon); + } + } +} + +void TableEdgeHdl::CreateB2dIAObject() +{ + GetRidOfIAObject(); + + if(!pHdlList || !pHdlList->GetView() || pHdlList->GetView()->areMarkHandlesHidden()) + return; + + SdrMarkView* pView = pHdlList->GetView(); + SdrPageView* pPageView = pView->GetSdrPageView(); + + if(!pPageView) + return; + + basegfx::B2DPolyPolygon aVisible; + basegfx::B2DPolyPolygon aInvisible; + + // get visible and invisible parts + getPolyPolygon(aVisible, aInvisible, nullptr); + + if(!(aVisible.count() || aInvisible.count())) + return; + + for(sal_uInt32 nWindow = 0; nWindow < pPageView->PageWindowCount(); nWindow++) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(nWindow); + + if(rPageWindow.GetPaintWindow().OutputToWindow()) + { + const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager(); + if (xManager.is()) + { + if(aVisible.count()) + { + // create overlay object for visible parts + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject(new OverlayTableEdge(aVisible, true)); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + + if(aInvisible.count()) + { + // also create overlay object for invisible parts to allow + // a standard HitTest using the primitives from that overlay object + // (see OverlayTableEdge implementation) + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject(new OverlayTableEdge(aInvisible, false)); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } + } +} + + +OverlayTableEdge::OverlayTableEdge( const basegfx::B2DPolyPolygon& rPolyPolygon, bool bVisible ) +: OverlayObject(COL_GRAY) +, maPolyPolygon( rPolyPolygon ) +, mbVisible(bVisible) +{ +} + +drawinglayer::primitive2d::Primitive2DContainer OverlayTableEdge::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + if(maPolyPolygon.count()) + { + // Discussed with CL. Currently i will leave the transparence out since this + // a little bit expensive. We may check the look with drag polygons later + const drawinglayer::primitive2d::Primitive2DReference aReference( + new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D( + maPolyPolygon, + getBaseColor().getBColor())); + + if(mbVisible) + { + // visible, just return as sequence + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aReference }; + } + else + { + // embed in HiddenGeometryPrimitive2D to support HitTest of this invisible + // overlay object + drawinglayer::primitive2d::Primitive2DContainer aSequence { aReference }; + const drawinglayer::primitive2d::Primitive2DReference aNewReference( + new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(std::move(aSequence))); + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aNewReference }; + } + } + + return aRetval; +} + + +TableBorderHdl::TableBorderHdl( + const tools::Rectangle& rRect, + bool bAnimate) +: SdrHdl(rRect.TopLeft(), SdrHdlKind::Move), + maRectangle(rRect), + mbAnimate(bAnimate) +{ +} + +PointerStyle TableBorderHdl::GetPointer() const +{ + return PointerStyle::Move; +} + +// create marker for this kind +void TableBorderHdl::CreateB2dIAObject() +{ + GetRidOfIAObject(); + + if (!pHdlList || !pHdlList->GetView() || pHdlList->GetView()->areMarkHandlesHidden()) + return; + + SdrMarkView* pView = pHdlList->GetView(); + SdrPageView* pPageView = pView->GetSdrPageView(); + + if (!pPageView) + return; + + for(sal_uInt32 nWindow = 0; nWindow < pPageView->PageWindowCount(); nWindow++) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(nWindow); + + if (rPageWindow.GetPaintWindow().OutputToWindow()) + { + const rtl::Reference<sdr::overlay::OverlayManager>& xManager = rPageWindow.GetOverlayManager(); + + if (xManager.is()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(maRectangle); + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + // make animation dependent from text edit active, because for tables + // this handle is also used when text edit *is* active for it. This + // interferes too much concerning repaint stuff (at least as long as + // text edit is not yet on the overlay) + + OutputDevice& rOutDev = rPageWindow.GetPaintWindow().GetOutputDevice(); + float fScaleFactor = rOutDev.GetDPIScaleFactor(); + double fWidth = fScaleFactor * 6.0; + + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject( + new sdr::overlay::OverlayRectangle(aRange.getMinimum(), aRange.getMaximum(), + aHilightColor, fTransparence, + fWidth, 0.0, 0.0, mbAnimate)); + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } + } +} + + +} // end of namespace sdr::table + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablehandles.hxx b/svx/source/table/tablehandles.hxx new file mode 100644 index 000000000..095edb0b5 --- /dev/null +++ b/svx/source/table/tablehandles.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLEHANDLES_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLEHANDLES_HXX + +#include <svx/svdhdl.hxx> + + +namespace sdr::table { + +enum TableEdgeState { Empty, Invisible, Visible }; + +struct TableEdge +{ + sal_Int32 mnStart; + sal_Int32 mnEnd; + TableEdgeState meState; + + TableEdge() : mnStart(0), mnEnd(0), meState(Empty) {} +}; + +class TableEdgeHdl : public SdrHdl +{ +public: + TableEdgeHdl( const Point& rPnt, bool bHorizontal, sal_Int32 nMin, sal_Int32 nMax, sal_Int32 nEdges ); + + sal_Int32 GetValidDragOffset( const SdrDragStat& rDrag ) const; + + virtual PointerStyle GetPointer() const override; + + void SetEdge( sal_Int32 nEdge, sal_Int32 nStart, sal_Int32 nEnd, TableEdgeState nState ); + + bool IsHorizontalEdge() const { return mbHorizontal; } + + basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const; + void getPolyPolygon(basegfx::B2DPolyPolygon& rVisible, basegfx::B2DPolyPolygon& rInvisible, const SdrDragStat* pDrag) const; + +protected: + // create marker for this kind + virtual void CreateB2dIAObject() override; + +private: + bool mbHorizontal; + sal_Int32 mnMin, mnMax; + std::vector< TableEdge > maEdges; +}; + +class TableBorderHdl : public SdrHdl +{ +public: + TableBorderHdl( + const tools::Rectangle& rRect, + bool bAnimate); + + virtual PointerStyle GetPointer() const override; + +protected: + // create marker for this kind + virtual void CreateB2dIAObject() override; + +private: + tools::Rectangle maRectangle; + + bool mbAnimate : 1; +}; + +} // end of namespace sdr::table + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablelayouter.cxx b/svx/source/table/tablelayouter.cxx new file mode 100644 index 000000000..d846a719c --- /dev/null +++ b/svx/source/table/tablelayouter.cxx @@ -0,0 +1,1311 @@ +/* -*- 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/table/XMergeableCell.hpp> + +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <tools/gen.hxx> +#include <libxml/xmlwriter.h> + +#include <cell.hxx> +#include <o3tl/safeint.hxx> +#include <tablemodel.hxx> +#include "tablelayouter.hxx" +#include <svx/svdotable.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::text; + + +namespace sdr::table { + + +static SvxBorderLine gEmptyBorder; + +constexpr OUStringLiteral gsSize( u"Size" ); + +TableLayouter::TableLayouter( const TableModelRef& xTableModel ) +: mxTable( xTableModel ) +{ +} + + +TableLayouter::~TableLayouter() +{ + ClearBorderLayout(); +} + + +basegfx::B2ITuple TableLayouter::getCellSize( const CellRef& xCell, const CellPos& rPos ) const +{ + sal_Int32 width = 0; + sal_Int32 height = 0; + + try + { + if( xCell.is() && !xCell->isMerged() ) + { + CellPos aPos( rPos ); + + sal_Int32 nRowCount = getRowCount(); + sal_Int32 nRowSpan = std::max( xCell->getRowSpan(), sal_Int32(1) ); + while( nRowSpan && (aPos.mnRow < nRowCount) ) + { + if( static_cast<sal_Int32>(maRows.size()) <= aPos.mnRow ) + break; + + height = o3tl::saturating_add(height, maRows[aPos.mnRow++].mnSize); + nRowSpan--; + } + + sal_Int32 nColCount = getColumnCount(); + sal_Int32 nColSpan = std::max( xCell->getColumnSpan(), sal_Int32(1) ); + while( nColSpan && (aPos.mnCol < nColCount ) ) + { + if( static_cast<sal_Int32>(maColumns.size()) <= aPos.mnCol ) + break; + + width = o3tl::saturating_add(width, maColumns[aPos.mnCol++].mnSize); + nColSpan--; + } + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + return basegfx::B2ITuple( width, height ); +} + + +bool TableLayouter::getCellArea( const CellRef& xCell, const CellPos& rPos, basegfx::B2IRectangle& rArea ) const +{ + try + { + if( xCell.is() && !xCell->isMerged() && isValid(rPos) ) + { + const basegfx::B2ITuple aCellSize( getCellSize( xCell, rPos ) ); + const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB); + + if( (rPos.mnCol < static_cast<sal_Int32>(maColumns.size())) && (rPos.mnRow < static_cast<sal_Int32>(maRows.size()) ) ) + { + const sal_Int32 y = maRows[rPos.mnRow].mnPos; + + sal_Int32 endy; + if (o3tl::checked_add(y, aCellSize.getY(), endy)) + return false; + + if(bRTL) + { + ///For RTL Table Calculate the Right End of cell instead of Left + const sal_Int32 x = maColumns[rPos.mnCol].mnPos + maColumns[rPos.mnCol].mnSize; + sal_Int32 startx; + if (o3tl::checked_sub(x, aCellSize.getX(), startx)) + return false; + rArea = basegfx::B2IRectangle(startx, y, x, endy); + } + else + { + const sal_Int32 x = maColumns[rPos.mnCol].mnPos; + sal_Int32 endx; + if (o3tl::checked_add(x, aCellSize.getX(), endx)) + return false; + rArea = basegfx::B2IRectangle(x, y, endx, endy); + } + return true; + } + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + return false; +} + + +sal_Int32 TableLayouter::getRowHeight( sal_Int32 nRow ) const +{ + if( isValidRow(nRow) ) + return maRows[nRow].mnSize; + else + return 0; +} + + +sal_Int32 TableLayouter::getColumnWidth( sal_Int32 nColumn ) const +{ + if( isValidColumn(nColumn) ) + return maColumns[nColumn].mnSize; + else + return 0; +} + +sal_Int32 TableLayouter::calcPreferredColumnWidth( sal_Int32 nColumn, Size aSize ) const +{ + sal_Int32 nRet = 0; + for ( sal_uInt32 nRow = 0; nRow < static_cast<sal_uInt32>(maRows.size()); ++nRow ) + { + // Account for the space desired by spanned columns. + // Only the last spanned cell will try to ensure sufficient space, + // by looping through the previous columns, subtracting their portion. + sal_Int32 nWish = 0; + sal_Int32 nSpannedColumn = nColumn; + bool bFindSpan = true; + while ( bFindSpan && isValidColumn(nSpannedColumn) ) + { + // recursive function call gets earlier portion of spanned column. + if ( nSpannedColumn < nColumn ) + nWish -= calcPreferredColumnWidth( nSpannedColumn, aSize ); + + CellRef xCell( getCell( CellPos( nSpannedColumn, nRow ) ) ); + if ( xCell.is() && !xCell->isMerged() + && (xCell->getColumnSpan() == 1 || nSpannedColumn < nColumn) ) + { + nWish += xCell->calcPreferredWidth(aSize); + bFindSpan = false; + } + else if ( xCell.is() && xCell->isMerged() + && nColumn == nSpannedColumn + && isValidColumn(nColumn + 1) ) + { + xCell = getCell( CellPos( nColumn + 1, nRow ) ); + bFindSpan = xCell.is() && !xCell->isMerged(); + } + nSpannedColumn--; + } + nRet = std::max( nRet, nWish ); + } + return nRet; +} + + +bool TableLayouter::isEdgeVisible( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal ) const +{ + const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders; + + if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) && + (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) ) + { + return rMap[nEdgeX][nEdgeY] != nullptr; + } + else + { + OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" ); + } + + return false; +} + + +/** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */ +SvxBorderLine* TableLayouter::getBorderLine( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal )const +{ + SvxBorderLine* pLine = nullptr; + + const BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders; + + if( (nEdgeX >= 0) && (nEdgeX < sal::static_int_cast<sal_Int32>(rMap.size())) && + (nEdgeY >= 0) && (nEdgeY < sal::static_int_cast<sal_Int32>(rMap[nEdgeX].size())) ) + { + pLine = rMap[nEdgeX][nEdgeY]; + if( pLine == &gEmptyBorder ) + pLine = nullptr; + } + else + { + OSL_FAIL( "sdr::table::TableLayouter::getBorderLine(), invalid edge!" ); + } + + return pLine; +} + +std::vector<EdgeInfo> TableLayouter::getHorizontalEdges() +{ + std::vector<EdgeInfo> aReturn; + sal_Int32 nRowSize = sal_Int32(maRows.size()); + for (sal_Int32 i = 0; i <= nRowSize; i++) + { + sal_Int32 nEdgeMin = 0; + sal_Int32 nEdgeMax = 0; + sal_Int32 nEdge = getHorizontalEdge(i, &nEdgeMin, &nEdgeMax); + nEdgeMin -= nEdge; + nEdgeMax -= nEdge; + aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax); + } + return aReturn; +} + +std::vector<EdgeInfo> TableLayouter::getVerticalEdges() +{ + std::vector<EdgeInfo> aReturn; + sal_Int32 nColumnSize = sal_Int32(maColumns.size()); + for (sal_Int32 i = 0; i <= nColumnSize; i++) + { + sal_Int32 nEdgeMin = 0; + sal_Int32 nEdgeMax = 0; + sal_Int32 nEdge = getVerticalEdge(i, &nEdgeMin, &nEdgeMax); + nEdgeMin -= nEdge; + nEdgeMax -= nEdge; + aReturn.emplace_back(i, nEdge, nEdgeMin, nEdgeMax); + } + return aReturn; +} + +sal_Int32 TableLayouter::getHorizontalEdge( int nEdgeY, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ ) +{ + sal_Int32 nRet = 0; + const sal_Int32 nRowCount = getRowCount(); + if( (nEdgeY >= 0) && (nEdgeY <= nRowCount ) ) + nRet = maRows[std::min(static_cast<sal_Int32>(nEdgeY),nRowCount-1)].mnPos; + + if( nEdgeY == nRowCount ) + nRet += maRows[nEdgeY - 1].mnSize; + + if( pnMin ) + { + if( (nEdgeY > 0) && (nEdgeY <= nRowCount ) ) + { + *pnMin = maRows[nEdgeY-1].mnPos + 600; // todo + } + else + { + *pnMin = nRet; + } + } + + if( pnMax ) + { + *pnMax = 0x0fffffff; + } + return nRet; +} + + +sal_Int32 TableLayouter::getVerticalEdge( int nEdgeX, sal_Int32* pnMin /*= 0*/, sal_Int32* pnMax /*= 0*/ ) +{ + sal_Int32 nRet = 0; + + const sal_Int32 nColCount = getColumnCount(); + if( (nEdgeX >= 0) && (nEdgeX <= nColCount ) ) + nRet = maColumns[std::min(static_cast<sal_Int32>(nEdgeX),nColCount-1)].mnPos; + + const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB); + if( bRTL ) + { + if( (nEdgeX >= 0) && (nEdgeX < nColCount) ) + nRet += maColumns[nEdgeX].mnSize; + } + else + { + if( nEdgeX == nColCount ) + nRet += maColumns[nEdgeX - 1].mnSize; + } + + if( pnMin ) + { + *pnMin = nRet; + if( bRTL ) + { + if( nEdgeX < nColCount ) + *pnMin = nRet - maColumns[nEdgeX].mnSize + getMinimumColumnWidth(nEdgeX); + } + else + { + if( (nEdgeX > 0) && (nEdgeX <= nColCount ) ) + *pnMin = maColumns[nEdgeX-1].mnPos + getMinimumColumnWidth( nEdgeX-1 ); + } + } + + if( pnMax ) + { + *pnMax = 0x0fffffff; // todo + if( bRTL ) + { + if( nEdgeX > 0 ) + *pnMax = nRet + maColumns[nEdgeX-1].mnSize - getMinimumColumnWidth( nEdgeX-1 ); + } + else + { + if( (nEdgeX >= 0) && (nEdgeX < nColCount ) ) + *pnMax = maColumns[nEdgeX].mnPos + maColumns[nEdgeX].mnSize - getMinimumColumnWidth( nEdgeX ); + } + } + + return nRet; +} + + +static bool checkMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32 nCellX, sal_Int32 nCellY, bool& bRunning ) +{ + Reference< XMergeableCell > xCell( xTable->getCellByPosition( nCellX, nCellY ), UNO_QUERY ); + if( xCell.is() && !xCell->isMerged() ) + { + const sal_Int32 nRight = xCell->getColumnSpan() + nCellX; + const sal_Int32 nBottom = xCell->getRowSpan() + nCellY; + if( (nMergedX < nRight) && (nMergedY < nBottom) ) + return true; + + bRunning = false; + } + return false; +} + +/** returns true if the cell(nMergedX,nMergedY) is merged with other cells. + the returned cell( rOriginX, rOriginY ) is the origin( top left cell ) of the merge. +*/ +bool findMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedX, sal_Int32 nMergedY, sal_Int32& rOriginX, sal_Int32& rOriginY ) +{ + rOriginX = nMergedX; + rOriginY = nMergedY; + + if( xTable.is() ) try + { + // check if this cell already the origin or not merged at all + Reference< XMergeableCell > xCell( xTable->getCellByPosition( nMergedX, nMergedY ), UNO_QUERY_THROW ); + if( !xCell->isMerged() ) + return true; + + bool bCheckVert = true; + bool bCheckHorz = true; + + sal_Int32 nMinCol = 0; + sal_Int32 nMinRow = 0; + + sal_Int32 nStep = 1, i; + + sal_Int32 nRow, nCol; + do + { + if( bCheckVert ) + { + nRow = nMergedY - nStep; + if( nRow >= nMinRow ) + { + nCol = nMergedX; + for( i = 0; (i <= nStep) && (nCol >= nMinCol); i++, nCol-- ) + { + if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckVert ) ) + { + rOriginX = nCol; rOriginY = nRow; + return true; + } + + if( !bCheckVert ) + { + if( nCol == nMergedX ) + { + nMinRow = nRow+1; + } + else + { + bCheckVert = true; + } + break; + } + } + } + else + { + bCheckVert = false; + } + } + + if( bCheckHorz ) + { + nCol = nMergedX - nStep; + if( nCol >= nMinCol ) + { + nRow = nMergedY; + for( i = 0; (i < nStep) && (nRow >= nMinRow); i++, nRow-- ) + { + if( checkMergeOrigin( xTable, nMergedX, nMergedY, nCol, nRow, bCheckHorz ) ) + { + rOriginX = nCol; rOriginY = nRow; + return true; + } + + if( !bCheckHorz ) + { + if( nRow == nMergedY ) + { + nMinCol = nCol+1; + } + else + { + bCheckHorz = true; + } + break; + } + } + } + else + { + bCheckHorz = false; + } + } + nStep++; + } + while( bCheckVert || bCheckHorz ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + return false; +} + + +sal_Int32 TableLayouter::getMinimumColumnWidth( sal_Int32 nColumn ) +{ + if( isValidColumn( nColumn ) ) + { + return maColumns[nColumn].mnMinSize; + } + else + { + OSL_FAIL( "TableLayouter::getMinimumColumnWidth(), column out of range!" ); + return 0; + } +} + + +sal_Int32 TableLayouter::distribute( LayoutVector& rLayouts, sal_Int32 nDistribute ) +{ + // break loops after 100 runs to avoid freezing office due to developer error + sal_Int32 nSafe = 100; + + const std::size_t nCount = rLayouts.size(); + std::size_t nIndex; + + bool bConstrainsBroken = false; + + do + { + bConstrainsBroken = false; + + // first enforce minimum size constrains on all entities + for( nIndex = 0; nIndex < nCount; ++nIndex ) + { + Layout& rLayout = rLayouts[nIndex]; + if( rLayout.mnSize < rLayout.mnMinSize ) + { + sal_Int32 nDiff(0); + bConstrainsBroken |= o3tl::checked_sub(rLayout.mnMinSize, rLayout.mnSize, nDiff); + bConstrainsBroken |= o3tl::checked_sub(nDistribute, nDiff, nDistribute); + rLayout.mnSize = rLayout.mnMinSize; + } + } + + // calculate current width + // if nDistribute is < 0 (shrinking), entities that are already + // at minimum width are not counted + sal_Int32 nCurrentWidth = 0; + for( nIndex = 0; nIndex < nCount; ++nIndex ) + { + Layout& rLayout = rLayouts[nIndex]; + if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) ) + nCurrentWidth = o3tl::saturating_add(nCurrentWidth, rLayout.mnSize); + } + + // now distribute over entities + if( (nCurrentWidth != 0) && (nDistribute != 0) ) + { + sal_Int32 nDistributed = nDistribute; + for( nIndex = 0; nIndex < nCount; ++nIndex ) + { + Layout& rLayout = rLayouts[nIndex]; + if( (nDistribute > 0) || (rLayout.mnSize > rLayout.mnMinSize) ) + { + sal_Int32 n(nDistributed); // for last entity use up rest + if (nIndex != (nCount-1)) + { + bConstrainsBroken |= o3tl::checked_multiply(nDistribute, rLayout.mnSize, n); + n /= nCurrentWidth; + } + + bConstrainsBroken |= o3tl::checked_add(rLayout.mnSize, n, rLayout.mnSize); + nDistributed -= n; + + if( rLayout.mnSize < rLayout.mnMinSize ) + bConstrainsBroken = true; + } + } + } + } while( bConstrainsBroken && --nSafe ); + + sal_Int32 nSize = 0; + for( nIndex = 0; nIndex < nCount; ++nIndex ) + nSize = o3tl::saturating_add(nSize, rLayouts[nIndex].mnSize); + + return nSize; +} + + +typedef std::vector< CellRef > MergeableCellVector; +typedef std::vector< MergeableCellVector > MergeVector; + + +void TableLayouter::LayoutTableWidth( tools::Rectangle& rArea, bool bFit ) +{ + const sal_Int32 nColCount = getColumnCount(); + const sal_Int32 nRowCount = getRowCount(); + if( nColCount == 0 ) + return; + + MergeVector aMergedCells( nColCount ); + std::vector<sal_Int32> aOptimalColumns; + + static const OUStringLiteral sOptimalSize(u"OptimalSize"); + + if( sal::static_int_cast< sal_Int32 >( maColumns.size() ) != nColCount ) + maColumns.resize( nColCount ); + + Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW ); + + // first calculate current width and initial minimum width per column, + // merged cells will be counted later + sal_Int32 nCurrentWidth = 0; + sal_Int32 nCol = 0, nRow = 0; + for( nCol = 0; nCol < nColCount; nCol++ ) + { + sal_Int32 nMinWidth = 0; + + bool bIsEmpty = true; // check if all cells in this column are merged + + for( nRow = 0; nRow < nRowCount; ++nRow ) + { + CellRef xCell( getCell( CellPos( nCol, nRow ) ) ); + if( xCell.is() && !xCell->isMerged() ) + { + bIsEmpty = false; + + sal_Int32 nColSpan = xCell->getColumnSpan(); + if( nColSpan > 1 ) + { + // merged cells will be evaluated later + aMergedCells[nCol+nColSpan-1].push_back( xCell ); + } + else + { + nMinWidth = std::max( nMinWidth, xCell->getMinimumWidth() ); + } + } + } + + maColumns[nCol].mnMinSize = nMinWidth; + + if( bIsEmpty ) + { + maColumns[nCol].mnSize = 0; + } + else + { + sal_Int32 nColWidth = 0; + Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW ); + bool bOptimal = false; + xColSet->getPropertyValue( sOptimalSize ) >>= bOptimal; + if( bOptimal ) + { + aOptimalColumns.push_back(nCol); + } + else + { + xColSet->getPropertyValue( gsSize ) >>= nColWidth; + } + + maColumns[nCol].mnSize = std::max( nColWidth, nMinWidth); + + nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize); + } + } + + // if we have optimal sized rows, distribute what is given (left) + if( !bFit && !aOptimalColumns.empty() && (nCurrentWidth < rArea.getWidth()) ) + { + sal_Int32 nLeft = rArea.getWidth() - nCurrentWidth; + sal_Int32 nDistribute = nLeft / aOptimalColumns.size(); + + auto iter( aOptimalColumns.begin() ); + while( iter != aOptimalColumns.end() ) + { + sal_Int32 nOptCol = *iter++; + if( iter == aOptimalColumns.end() ) + nDistribute = nLeft; + + maColumns[nOptCol].mnSize += nDistribute; + nLeft -= nDistribute; + } + + DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableWidtht(), layouting failed!" ); + } + + // now check if merged cells fit + for( nCol = 1; nCol < nColCount; ++nCol ) + { + bool bChanges = false; + + const sal_Int32 nOldSize = maColumns[nCol].mnSize; + + for( const CellRef& xCell : aMergedCells[nCol] ) + { + sal_Int32 nMinWidth = xCell->getMinimumWidth(); + + for( sal_Int32 nMCol = nCol - xCell->getColumnSpan() + 1; (nMCol > 0) && (nMCol < nCol); ++nMCol ) + nMinWidth -= maColumns[nMCol].mnSize; + + if( nMinWidth > maColumns[nCol].mnMinSize ) + maColumns[nCol].mnMinSize = nMinWidth; + + if( nMinWidth > maColumns[nCol].mnSize ) + { + maColumns[nCol].mnSize = nMinWidth; + bChanges = true; + } + } + + if( bChanges ) + { + nCurrentWidth = o3tl::saturating_add(nCurrentWidth, maColumns[nCol].mnSize - nOldSize); + } + } + + // now scale if wanted and needed + if( bFit && (nCurrentWidth != rArea.getWidth()) ) + distribute( maColumns, rArea.getWidth() - nCurrentWidth ); + + // last step, update left edges + sal_Int32 nNewWidth = 0; + + const bool bRTL = (mxTable->getSdrTableObj()->GetWritingMode() == WritingMode_RL_TB); + RangeIterator<sal_Int32> coliter( 0, nColCount, !bRTL ); + while( coliter.next(nCol ) ) + { + maColumns[nCol].mnPos = nNewWidth; + nNewWidth = o3tl::saturating_add(nNewWidth, maColumns[nCol].mnSize); + if( bFit ) + { + Reference< XPropertySet > xColSet( xCols->getByIndex(nCol), UNO_QUERY_THROW ); + xColSet->setPropertyValue( gsSize, Any( maColumns[nCol].mnSize ) ); + } + } + + rArea.SetSize( Size( nNewWidth, rArea.GetHeight() ) ); + updateCells( rArea ); +} + + +void TableLayouter::LayoutTableHeight( tools::Rectangle& rArea, bool bFit ) +{ + const sal_Int32 nColCount = getColumnCount(); + const sal_Int32 nRowCount = getRowCount(); + + if( nRowCount == 0 ) + return; + + Reference< XTableRows > xRows( mxTable->getRows() ); + + MergeVector aMergedCells( nRowCount ); + std::vector<sal_Int32> aOptimalRows; + + static const OUStringLiteral sOptimalSize(u"OptimalSize"); + + // first calculate current height and initial minimum size per column, + // merged cells will be counted later + sal_Int32 nCurrentHeight = 0; + sal_Int32 nCol, nRow; + for( nRow = 0; nRow < nRowCount; ++nRow ) + { + sal_Int32 nMinHeight = 0; + + bool bIsEmpty = true; // check if all cells in this row are merged + + for( nCol = 0; nCol < nColCount; ++nCol ) + { + CellRef xCell( getCell( CellPos( nCol, nRow ) ) ); + if( xCell.is() && !xCell->isMerged() ) + { + bIsEmpty = false; + + sal_Int32 nRowSpan = xCell->getRowSpan(); + if( nRowSpan > 1 ) + { + // merged cells will be evaluated later + aMergedCells[nRow+nRowSpan-1].push_back( xCell ); + } + else + { + nMinHeight = std::max( nMinHeight, xCell->getMinimumHeight() ); + } + } + } + + maRows[nRow].mnMinSize = nMinHeight; + + if( bIsEmpty ) + { + maRows[nRow].mnSize = 0; + } + else + { + sal_Int32 nRowHeight = 0; + Reference<XPropertySet> xRowSet(xRows->getByIndex(nRow), UNO_QUERY_THROW); + + bool bOptimal = false; + xRowSet->getPropertyValue( sOptimalSize ) >>= bOptimal; + if( bOptimal ) + { + aOptimalRows.push_back( nRow ); + } + else + { + xRowSet->getPropertyValue( gsSize ) >>= nRowHeight; + } + + maRows[nRow].mnSize = nRowHeight; + + if( maRows[nRow].mnSize < nMinHeight ) + maRows[nRow].mnSize = nMinHeight; + + nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize); + } + } + + // if we have optimal sized rows, distribute what is given (left) + if( !bFit && !aOptimalRows.empty() && (nCurrentHeight < rArea.getHeight()) ) + { + sal_Int32 nLeft = rArea.getHeight() - nCurrentHeight; + sal_Int32 nDistribute = nLeft / aOptimalRows.size(); + + auto iter( aOptimalRows.begin() ); + while( iter != aOptimalRows.end() ) + { + sal_Int32 nOptRow = *iter++; + if( iter == aOptimalRows.end() ) + nDistribute = nLeft; + + maRows[nOptRow].mnSize += nDistribute; + nLeft -= nDistribute; + + } + + DBG_ASSERT( nLeft == 0, "svx::TableLayouter::LayoutTableHeight(), layouting failed!" ); + } + + // now check if merged cells fit + for( nRow = 1; nRow < nRowCount; ++nRow ) + { + bool bChanges = false; + sal_Int32 nOldSize = maRows[nRow].mnSize; + + for( const CellRef& xCell : aMergedCells[nRow] ) + { + sal_Int32 nMinHeight = xCell->getMinimumHeight(); + + for( sal_Int32 nMRow = nRow - xCell->getRowSpan() + 1; (nMRow > 0) && (nMRow < nRow); ++nMRow ) + nMinHeight -= maRows[nMRow].mnSize; + + if( nMinHeight > maRows[nRow].mnMinSize ) + maRows[nRow].mnMinSize = nMinHeight; + + if( nMinHeight > maRows[nRow].mnSize ) + { + maRows[nRow].mnSize = nMinHeight; + bChanges = true; + } + } + if( bChanges ) + nCurrentHeight = o3tl::saturating_add(nCurrentHeight, maRows[nRow].mnSize - nOldSize); + } + + // now scale if wanted and needed + if( bFit && nCurrentHeight != rArea.getHeight() ) + distribute(maRows, o3tl::saturating_sub<sal_Int32>(rArea.getHeight(), nCurrentHeight)); + + // last step, update left edges + sal_Int32 nNewHeight = 0; + for( nRow = 0; nRow < nRowCount; ++nRow ) + { + maRows[nRow].mnPos = nNewHeight; + nNewHeight = o3tl::saturating_add(nNewHeight, maRows[nRow].mnSize); + + if( bFit ) + { + Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW ); + xRowSet->setPropertyValue( gsSize, Any( maRows[nRow].mnSize ) ); + } + } + + rArea.SetSize( Size( rArea.GetWidth(), nNewHeight ) ); + updateCells( rArea ); +} + + +/** try to fit the table into the given rectangle. + If the rectangle is too small, it will be grown to fit the table. */ +void TableLayouter::LayoutTable( tools::Rectangle& rRectangle, bool bFitWidth, bool bFitHeight ) +{ + if( !mxTable.is() ) + return; + + const sal_Int32 nRowCount = mxTable->getRowCount(); + const sal_Int32 nColCount = mxTable->getColumnCount(); + + if( (nRowCount != getRowCount()) || (nColCount != getColumnCount()) ) + { + if( static_cast< sal_Int32 >( maRows.size() ) != nRowCount ) + maRows.resize( nRowCount ); + + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) + maRows[nRow].clear(); + + if( static_cast< sal_Int32 >( maColumns.size() ) != nColCount ) + maColumns.resize( nColCount ); + + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) + maColumns[nCol].clear(); + } + + LayoutTableWidth( rRectangle, bFitWidth ); + LayoutTableHeight( rRectangle, bFitHeight ); + UpdateBorderLayout(); +} + + +void TableLayouter::updateCells( tools::Rectangle const & rRectangle ) +{ + const sal_Int32 nColCount = getColumnCount(); + const sal_Int32 nRowCount = getRowCount(); + + CellPos aPos; + for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ ) + { + for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ ) + { + CellRef xCell( getCell( aPos ) ); + if( xCell.is() ) + { + basegfx::B2IRectangle aCellArea; + if( getCellArea( xCell, aPos, aCellArea ) ) + { + tools::Rectangle aCellRect; + aCellRect.SetLeft( aCellArea.getMinX() ); + aCellRect.SetRight( aCellArea.getMaxX() ); + aCellRect.SetTop( aCellArea.getMinY() ); + aCellRect.SetBottom( aCellArea.getMaxY() ); + aCellRect.Move( rRectangle.Left(), rRectangle.Top() ); + xCell->setCellRect( aCellRect ); + } + } + } + } +} + + +CellRef TableLayouter::getCell( const CellPos& rPos ) const +{ + CellRef xCell; + if( mxTable.is() ) try + { + xCell.set( dynamic_cast< Cell* >( mxTable->getCellByPosition( rPos.mnCol, rPos.mnRow ).get() ) ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + return xCell; +} + + +bool TableLayouter::HasPriority( const SvxBorderLine* pThis, const SvxBorderLine* pOther ) +{ + if (!pThis || ((pThis == &gEmptyBorder) && (pOther != nullptr))) + return false; + if (!pOther || (pOther == &gEmptyBorder)) + return true; + + sal_uInt16 nThisSize = pThis->GetScaledWidth(); + sal_uInt16 nOtherSize = pOther->GetScaledWidth(); + + if (nThisSize > nOtherSize) + return true; + + else if (nThisSize < nOtherSize) + { + return false; + } + else + { + if ( pOther->GetInWidth() && !pThis->GetInWidth() ) + { + return true; + } + else if ( pThis->GetInWidth() && !pOther->GetInWidth() ) + { + return false; + } + else + { + return true; //! ??? + } + } +} + +void TableLayouter::SetBorder( sal_Int32 nCol, sal_Int32 nRow, bool bHorizontal, const SvxBorderLine* pLine ) +{ + if (!pLine) + pLine = &gEmptyBorder; + + BorderLineMap& rMap = bHorizontal ? maHorizontalBorders : maVerticalBorders; + + if( (nCol >= 0) && (nCol < sal::static_int_cast<sal_Int32>(rMap.size())) && + (nRow >= 0) && (nRow < sal::static_int_cast<sal_Int32>(rMap[nCol].size())) ) + { + SvxBorderLine *pOld = rMap[nCol][nRow]; + + if (HasPriority(pLine, pOld)) + { + if (pOld && pOld != &gEmptyBorder) + delete pOld; + + SvxBorderLine* pNew = (pLine != &gEmptyBorder) ? new SvxBorderLine(*pLine) : &gEmptyBorder; + + rMap[nCol][nRow] = pNew; + } + } + else + { + OSL_FAIL( "sdr::table::TableLayouter::SetBorder(), invalid border!" ); + } +} + +void TableLayouter::ClearBorderLayout() +{ + ClearBorderLayout(maHorizontalBorders); + ClearBorderLayout(maVerticalBorders); +} + +void TableLayouter::ClearBorderLayout(BorderLineMap& rMap) +{ + const sal_Int32 nColCount = rMap.size(); + + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) + { + const sal_Int32 nRowCount = rMap[nCol].size(); + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) + { + SvxBorderLine* pLine = rMap[nCol][nRow]; + if( pLine ) + { + if( pLine != &gEmptyBorder ) + delete pLine; + + rMap[nCol][nRow] = nullptr; + } + } + } +} + +void TableLayouter::ResizeBorderLayout() +{ + ClearBorderLayout(); + ResizeBorderLayout(maHorizontalBorders); + ResizeBorderLayout(maVerticalBorders); +} + + +void TableLayouter::ResizeBorderLayout( BorderLineMap& rMap ) +{ + const sal_Int32 nColCount = getColumnCount() + 1; + const sal_Int32 nRowCount = getRowCount() + 1; + + if( sal::static_int_cast<sal_Int32>(rMap.size()) != nColCount ) + rMap.resize( nColCount ); + + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) + { + if( sal::static_int_cast<sal_Int32>(rMap[nCol].size()) != nRowCount ) + rMap[nCol].resize( nRowCount ); + } +} + + +void TableLayouter::UpdateBorderLayout() +{ + // make sure old border layout is cleared and border maps have correct size + ResizeBorderLayout(); + + const sal_Int32 nColCount = getColumnCount(); + const sal_Int32 nRowCount = getRowCount(); + + CellPos aPos; + for( aPos.mnRow = 0; aPos.mnRow < nRowCount; aPos.mnRow++ ) + { + for( aPos.mnCol = 0; aPos.mnCol < nColCount; aPos.mnCol++ ) + { + CellRef xCell( getCell( aPos ) ); + if( !xCell.is() ) + continue; + + const SvxBoxItem* pThisAttr = xCell->GetItemSet().GetItem<SvxBoxItem>( SDRATTR_TABLE_BORDER ); + OSL_ENSURE(pThisAttr,"sdr::table::TableLayouter::UpdateBorderLayout(), no border attribute?"); + + if( !pThisAttr ) + continue; + + const sal_Int32 nLastRow = xCell->getRowSpan() + aPos.mnRow; + const sal_Int32 nLastCol = xCell->getColumnSpan() + aPos.mnCol; + + for( sal_Int32 nRow = aPos.mnRow; nRow < nLastRow; nRow++ ) + { + SetBorder( aPos.mnCol, nRow, false, pThisAttr->GetLeft() ); + SetBorder( nLastCol, nRow, false, pThisAttr->GetRight() ); + } + + for( sal_Int32 nCol = aPos.mnCol; nCol < nLastCol; nCol++ ) + { + SetBorder( nCol, aPos.mnRow, true, pThisAttr->GetTop() ); + SetBorder( nCol, nLastRow, true, pThisAttr->GetBottom() ); + } + } + } +} + + +void TableLayouter::DistributeColumns( ::tools::Rectangle& rArea, + sal_Int32 nFirstCol, + sal_Int32 nLastCol, + const bool bOptimize, + const bool bMinimize ) +{ + if( !mxTable.is() ) + return; + + try + { + const sal_Int32 nColCount = getColumnCount(); + Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW ); + const Size aSize(0xffffff, 0xffffff); + + //special case - optimize a single column + if ( (bOptimize || bMinimize) && nFirstCol == nLastCol ) + { + const sal_Int32 nWish = calcPreferredColumnWidth(nFirstCol, aSize); + if ( nWish < getColumnWidth(nFirstCol) ) + { + Reference< XPropertySet > xColSet( xCols->getByIndex(nFirstCol), UNO_QUERY_THROW ); + xColSet->setPropertyValue( gsSize, Any( nWish ) ); + + //FitWidth automatically distributes the new excess space + LayoutTable( rArea, /*bFitWidth=*/!bMinimize, /*bFitHeight=*/false ); + } + } + + if( (nFirstCol < 0) || (nFirstCol>= nLastCol) || (nLastCol >= nColCount) ) + return; + + sal_Int32 nAllWidth = 0; + float fAllWish = 0; + sal_Int32 nUnused = 0; + std::vector<sal_Int32> aWish(nColCount); + + for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol ) + nAllWidth += getColumnWidth(nCol); + + const sal_Int32 nEqualWidth = nAllWidth / (nLastCol-nFirstCol+1); + + //pass 1 - collect unneeded space (from an equal width perspective) + if ( bMinimize || bOptimize ) + { + for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol ) + { + const sal_Int32 nIndex = nCol - nFirstCol; + aWish[nIndex] = calcPreferredColumnWidth(nCol, aSize); + fAllWish += aWish[nIndex]; + if ( aWish[nIndex] < nEqualWidth ) + nUnused += nEqualWidth - aWish[nIndex]; + } + } + const sal_Int32 nDistributeExcess = nAllWidth - fAllWish; + + sal_Int32 nWidth = nEqualWidth; + for( sal_Int32 nCol = nFirstCol; nCol <= nLastCol; ++nCol ) + { + if ( !bMinimize && nCol == nLastCol ) + nWidth = nAllWidth; // last column gets rounding/logic errors + else if ( (bMinimize || bOptimize) && fAllWish ) + { + //pass 2 - first come, first served when requesting from the + // unneeded pool, or proportionally allocate excess. + const sal_Int32 nIndex = nCol - nFirstCol; + if ( aWish[nIndex] > nEqualWidth + nUnused ) + { + nWidth = nEqualWidth + nUnused; + nUnused = 0; + } + else + { + nWidth = aWish[nIndex]; + if ( aWish[nIndex] > nEqualWidth ) + nUnused -= aWish[nIndex] - nEqualWidth; + + if ( !bMinimize && nDistributeExcess > 0 ) + nWidth += nWidth / fAllWish * nDistributeExcess; + } + } + + Reference< XPropertySet > xColSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW ); + xColSet->setPropertyValue( gsSize, Any( nWidth ) ); + + nAllWidth -= nWidth; + } + + LayoutTable( rArea, !bMinimize, false ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } +} + + +void TableLayouter::DistributeRows( ::tools::Rectangle& rArea, + sal_Int32 nFirstRow, + sal_Int32 nLastRow, + const bool bOptimize, + const bool bMinimize ) +{ + if( !mxTable.is() ) + return; + + try + { + const sal_Int32 nRowCount = mxTable->getRowCount(); + Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW ); + sal_Int32 nMinHeight = 0; + + //special case - minimize a single row + if ( bMinimize && nFirstRow == nLastRow ) + { + const sal_Int32 nWish = std::max( maRows[nFirstRow].mnMinSize, nMinHeight ); + if ( nWish < getRowHeight(nFirstRow) ) + { + Reference< XPropertySet > xRowSet( xRows->getByIndex( nFirstRow ), UNO_QUERY_THROW ); + xRowSet->setPropertyValue( gsSize, Any( nWish ) ); + + LayoutTable( rArea, /*bFitWidth=*/false, /*bFitHeight=*/!bMinimize ); + } + } + + if( (nFirstRow < 0) || (nFirstRow>= nLastRow) || (nLastRow >= nRowCount) ) + return; + + sal_Int32 nAllHeight = 0; + sal_Int32 nMaxHeight = 0; + + for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow ) + { + nMinHeight = std::max( maRows[nRow].mnMinSize, nMinHeight ); + nMaxHeight = std::max( maRows[nRow].mnSize, nMaxHeight ); + nAllHeight += maRows[nRow].mnSize; + } + + const sal_Int32 nRows = nLastRow-nFirstRow+1; + sal_Int32 nHeight = nAllHeight / nRows; + + if ( !bMinimize && nHeight < nMaxHeight ) + { + if ( !bOptimize ) + { + sal_Int32 nNeededHeight = nRows * nMaxHeight; + rArea.AdjustBottom(nNeededHeight - nAllHeight ); + nHeight = nMaxHeight; + nAllHeight = nRows * nMaxHeight; + } + else if ( nHeight < nMinHeight ) + { + sal_Int32 nNeededHeight = nRows * nMinHeight; + rArea.AdjustBottom(nNeededHeight - nAllHeight ); + nHeight = nMinHeight; + nAllHeight = nRows * nMinHeight; + } + } + + for( sal_Int32 nRow = nFirstRow; nRow <= nLastRow; ++nRow ) + { + if ( bMinimize ) + nHeight = maRows[nRow].mnMinSize; + else if ( nRow == nLastRow ) + nHeight = nAllHeight; // last row get round errors + + Reference< XPropertySet > xRowSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW ); + xRowSet->setPropertyValue( gsSize, Any( nHeight ) ); + + nAllHeight -= nHeight; + } + + LayoutTable( rArea, false, !bMinimize ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } +} + +void TableLayouter::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter")); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("columns")); + for (const auto& rColumn : maColumns) + rColumn.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("rows")); + for (const auto& rRow : maRows) + rRow.dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +void TableLayouter::Layout::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableLayouter_Layout")); + + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pos"), BAD_CAST(OString::number(mnPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(mnSize).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("minSize"), BAD_CAST(OString::number(mnMinSize).getStr())); + + (void)xmlTextWriterEndElement(pWriter); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablelayouter.hxx b/svx/source/table/tablelayouter.hxx new file mode 100644 index 000000000..2ff69598e --- /dev/null +++ b/svx/source/table/tablelayouter.hxx @@ -0,0 +1,172 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLELAYOUTER_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLELAYOUTER_HXX + +#include <sal/types.h> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/tuple/b2ituple.hxx> +#include <o3tl/safeint.hxx> +#include <vector> + +#include <svx/svdotable.hxx> +#include <celltypes.hxx> + +namespace tools { class Rectangle; } + + +namespace editeng { + class SvxBorderLine; +} + +namespace sdr::table { + +/** returns true if the cell(nMergedCol,nMergedRow) is merged with other cells. + the returned cell( rOriginCol, rOriginRow ) is the origin( top left cell ) of the merge. +*/ +bool findMergeOrigin( const TableModelRef& xTable, sal_Int32 nMergedCol, sal_Int32 nMergedRow, sal_Int32& rOriginCol, sal_Int32& rOriginRow ); + +typedef std::vector< editeng::SvxBorderLine* > BorderLineVector; +typedef std::vector< BorderLineVector > BorderLineMap; + + +// TableModel + +struct EdgeInfo final +{ + sal_Int32 nIndex; + sal_Int32 nPosition; + sal_Int32 nMin; + sal_Int32 nMax; + + EdgeInfo(sal_Int32 nInIndex, sal_Int32 nInPosition, sal_Int32 nInMin, sal_Int32 nInMax) + : nIndex(nInIndex) + , nPosition(nInPosition) + , nMin(nInMin) + , nMax(nInMax) + {} +}; + +class TableLayouter final +{ +public: + explicit TableLayouter( const TableModelRef& xTableModel ); + ~TableLayouter(); + + /** try to fit the table into the given rectangle. + If the rectangle is too small, it will be grown to fit the table. + + if bFitWidth or bFitHeight is set, the layouter tries to scale + the rows and/or columns to the given area. The result my be bigger + to fulfill constrains. + + if bFitWidth or bFitHeight is set, the model is changed. + */ + void LayoutTable( ::tools::Rectangle& rRectangle, bool bFitWidth, bool bFitHeight ); + + void UpdateBorderLayout(); + + bool getCellArea( const CellRef& xCell, const CellPos& rPos, basegfx::B2IRectangle& rArea ) const; + + ::sal_Int32 getRowCount() const { return static_cast< ::sal_Int32 >( maRows.size() ); } + ::sal_Int32 getColumnCount() const { return static_cast< ::sal_Int32 >( maColumns.size() ); } + sal_Int32 getRowHeight( sal_Int32 nRow ) const; + + sal_Int32 getColumnWidth( sal_Int32 nColumn ) const; + sal_Int32 calcPreferredColumnWidth( sal_Int32 nColumn, Size aSize ) const; + + sal_Int32 getMinimumColumnWidth( sal_Int32 nColumn ); + + /** checks if the given edge is visible. + Edges between merged cells are not visible. + */ + bool isEdgeVisible( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal ) const; + + /** returns the requested borderline in rpBorderLine or a null pointer if there is no border at this edge */ + editeng::SvxBorderLine* getBorderLine( sal_Int32 nEdgeX, sal_Int32 nEdgeY, bool bHorizontal )const; + + void updateCells( ::tools::Rectangle const & rRectangle ); + + std::vector<EdgeInfo> getHorizontalEdges(); + sal_Int32 getHorizontalEdge( int nEdgeY, sal_Int32* pnMin, sal_Int32* pnMax ); + + std::vector<EdgeInfo> getVerticalEdges(); + sal_Int32 getVerticalEdge( int nEdgeX , sal_Int32* pnMin, sal_Int32* pnMax); + + + void DistributeColumns( ::tools::Rectangle& rArea, + sal_Int32 nFirstCol, + sal_Int32 nLastCol, + const bool bOptimize, + const bool bMinimize ); + void DistributeRows( ::tools::Rectangle& rArea, + sal_Int32 nFirstRow, + sal_Int32 nLastRow, + const bool bOptimize, + const bool bMinimize ); + void dumpAsXml(xmlTextWriterPtr pWriter) const; + + void LayoutTableWidth(::tools::Rectangle& rArea, bool bFit); + void LayoutTableHeight(::tools::Rectangle& rArea, bool bFit); + +private: + CellRef getCell( const CellPos& rPos ) const; + basegfx::B2ITuple getCellSize( const CellRef& xCell, const CellPos& rPos ) const; + + bool isValidColumn( sal_Int32 nColumn ) const { return (nColumn >= 0) && (o3tl::make_unsigned(nColumn) < maColumns.size()); } + bool isValidRow( sal_Int32 nRow ) const { return (nRow >= 0) && (o3tl::make_unsigned(nRow) < maRows.size()); } + bool isValid( const CellPos& rPos ) const { return isValidColumn( rPos.mnCol ) && isValidRow( rPos.mnRow ); } + + void ClearBorderLayout(); + static void ClearBorderLayout(BorderLineMap& rMap); + void ResizeBorderLayout(); + void ResizeBorderLayout( BorderLineMap& rMap ); + + void SetBorder( sal_Int32 nCol, sal_Int32 nRow, bool bHorizontal, const editeng::SvxBorderLine* pLine ); + + static bool HasPriority( const editeng::SvxBorderLine* pThis, const editeng::SvxBorderLine* pOther ); + + struct Layout + { + sal_Int32 mnPos; + sal_Int32 mnSize; + sal_Int32 mnMinSize; + + Layout() : mnPos( 0 ), mnSize( 0 ), mnMinSize( 0 ) {} + void clear() { mnPos = 0; mnSize = 0; mnMinSize = 0; } + void dumpAsXml(xmlTextWriterPtr pWriter) const; + }; + typedef std::vector< Layout > LayoutVector; + + static sal_Int32 distribute( LayoutVector& rLayouts, sal_Int32 nDistribute ); + + TableModelRef mxTable; + LayoutVector maRows; + LayoutVector maColumns; + + BorderLineMap maHorizontalBorders; + BorderLineMap maVerticalBorders; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablemodel.cxx b/svx/source/table/tablemodel.cxx new file mode 100644 index 000000000..9104a0a2d --- /dev/null +++ b/svx/source/table/tablemodel.cxx @@ -0,0 +1,1127 @@ +/* -*- 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/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/table/XMergeableCell.hpp> + +#include <algorithm> + +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <libxml/xmlwriter.h> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +#include <cell.hxx> +#include "cellcursor.hxx" +#include <tablemodel.hxx> +#include "tablerow.hxx" +#include "tablerows.hxx" +#include "tablecolumn.hxx" +#include "tablecolumns.hxx" +#include "tableundo.hxx" +#include <o3tl/safeint.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdmodel.hxx> +#include <svx/strings.hrc> +#include <svx/dialmgr.hxx> + +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::util; + + +namespace sdr::table { + + +// removes the given range from a vector +template< class Vec, class Iter > static void remove_range( Vec& rVector, sal_Int32 nIndex, sal_Int32 nCount ) +{ + const sal_Int32 nSize = static_cast<sal_Int32>(rVector.size()); + if( nCount && (nIndex >= 0) && (nIndex < nSize) ) + { + if( (nIndex + nCount) >= nSize ) + { + // remove at end + rVector.resize( nIndex ); + } + else + { + rVector.erase(rVector.begin() + nIndex, rVector.begin() + nIndex + nCount); + } + } +} + + +/** inserts a range into a vector */ +template< class Vec, class Iter, class Entry > static sal_Int32 insert_range( Vec& rVector, sal_Int32 nIndex, sal_Int32 nCount ) +{ + if( nCount ) + { + if( nIndex >= static_cast< sal_Int32 >( rVector.size() ) ) + { + // append at end + nIndex = static_cast< sal_Int32 >( rVector.size() ); // cap to end + rVector.resize( nIndex + nCount ); + } + else + { + // insert + Iter aIter( rVector.begin() ); + std::advance( aIter, nIndex ); + + Entry aEmpty; + rVector.insert( aIter, nCount, aEmpty ); + } + } + return nIndex; +} + + +TableModel::TableModel( SdrTableObj* pTableObj ) +: TableModelBase( m_aMutex ) +, mpTableObj( pTableObj ) +, mbModified( false ) +, mbNotifyPending( false ) +, mnNotifyLock( 0 ) +{ +} + +TableModel::TableModel( SdrTableObj* pTableObj, const TableModelRef& xSourceTable ) +: TableModelBase( m_aMutex ) +, mpTableObj( pTableObj ) +, mbModified( false ) +, mbNotifyPending( false ) +, mnNotifyLock( 0 ) +{ + if( !xSourceTable.is() ) + return; + + const sal_Int32 nColCount = xSourceTable->getColumnCountImpl(); + const sal_Int32 nRowCount = xSourceTable->getRowCountImpl(); + + init( nColCount, nRowCount ); + + sal_Int32 nRows = nRowCount; + while( nRows-- ) + (*maRows[nRows]) = *xSourceTable->maRows[nRows]; + + sal_Int32 nColumns = nColCount; + while( nColumns-- ) + (*maColumns[nColumns]) = *xSourceTable->maColumns[nColumns]; + + // copy cells + for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol ) + { + for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow ) + { + CellRef xTargetCell( getCell( nCol, nRow ) ); + if( xTargetCell.is() ) + xTargetCell->cloneFrom( xSourceTable->getCell( nCol, nRow ) ); + } + } +} + + +TableModel::~TableModel() +{ +} + + +void TableModel::init( sal_Int32 nColumns, sal_Int32 nRows ) +{ + if( nRows < 20 ) + maRows.reserve( 20 ); + + if( nColumns < 20 ) + maColumns.reserve( 20 ); + + if( nRows && nColumns ) + { + maColumns.resize( nColumns ); + maRows.resize( nRows ); + + while( nRows-- ) + maRows[nRows].set( new TableRow( this, nRows, nColumns ) ); + + while( nColumns-- ) + maColumns[nColumns].set( new TableColumn( this, nColumns ) ); + } +} + + +// ICellRange + + +sal_Int32 TableModel::getLeft() +{ + return 0; +} + + +sal_Int32 TableModel::getTop() +{ + return 0; +} + + +sal_Int32 TableModel::getRight() +{ + return getColumnCount(); +} + + +sal_Int32 TableModel::getBottom() +{ + return getRowCount(); +} + + +Reference< XTable > TableModel::getTable() +{ + return this; +} + + +void TableModel::UndoInsertRows( sal_Int32 nIndex, sal_Int32 nCount ) +{ + TableModelNotifyGuard aGuard( this ); + + // remove the rows + remove_range<RowVector,RowVector::iterator>( maRows, nIndex, nCount ); + updateRows(); + setModified(true); +} + + +void TableModel::UndoRemoveRows( sal_Int32 nIndex, RowVector& aRows ) +{ + TableModelNotifyGuard aGuard( this ); + + const sal_Int32 nCount = sal::static_int_cast< sal_Int32 >( aRows.size() ); + + nIndex = insert_range<RowVector,RowVector::iterator,TableRowRef>( maRows, nIndex, nCount ); + + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + maRows[nIndex+nOffset] = aRows[nOffset]; + + updateRows(); + setModified(true); +} + + +void TableModel::UndoInsertColumns( sal_Int32 nIndex, sal_Int32 nCount ) +{ + TableModelNotifyGuard aGuard( this ); + + // now remove the columns + remove_range<ColumnVector,ColumnVector::iterator>( maColumns, nIndex, nCount ); + sal_Int32 nRows = getRowCountImpl(); + while( nRows-- ) + maRows[nRows]->removeColumns( nIndex, nCount ); + + updateColumns(); + setModified(true); +} + + +void TableModel::UndoRemoveColumns( sal_Int32 nIndex, ColumnVector& aCols, CellVector& aCells ) +{ + TableModelNotifyGuard aGuard( this ); + + const sal_Int32 nCount = sal::static_int_cast< sal_Int32 >( aCols.size() ); + + // assert if there are not enough cells saved + DBG_ASSERT( (aCols.size() * maRows.size()) == aCells.size(), "sdr::table::TableModel::UndoRemoveColumns(), invalid undo data!" ); + + nIndex = insert_range<ColumnVector,ColumnVector::iterator,TableColumnRef>( maColumns, nIndex, nCount ); + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + maColumns[nIndex+nOffset] = aCols[nOffset]; + + CellVector::iterator aIter( aCells.begin() ); + + sal_Int32 nRows = getRowCountImpl(); + for( sal_Int32 nRow = 0; nRow < nRows; ++nRow ) + { + CellVector::iterator aIter2 = aIter + nRow * nCount; + OSL_ENSURE(aIter2 < aCells.end(), "invalid iterator!"); + maRows[nRow]->insertColumns( nIndex, nCount, &aIter2 ); + } + + updateColumns(); + setModified(true); +} + + +// XTable + + +Reference< XCellCursor > SAL_CALL TableModel::createCursor() +{ + ::SolarMutexGuard aGuard; + return createCursorByRange( Reference< XCellRange >( this ) ); +} + + +Reference< XCellCursor > SAL_CALL TableModel::createCursorByRange( const Reference< XCellRange >& rRange ) +{ + ::SolarMutexGuard aGuard; + + ICellRange* pRange = dynamic_cast< ICellRange* >( rRange.get() ); + if( (pRange == nullptr) || (pRange->getTable().get() != this) ) + throw IllegalArgumentException(); + + TableModelRef xModel( this ); + return new CellCursor( xModel, pRange->getLeft(), pRange->getTop(), pRange->getRight(), pRange->getBottom() ); +} + + +sal_Int32 SAL_CALL TableModel::getRowCount() +{ + ::SolarMutexGuard aGuard; + return getRowCountImpl(); +} + +sal_Int32 SAL_CALL TableModel::getColumnCount() +{ + ::SolarMutexGuard aGuard; + return getColumnCountImpl(); +} + +std::vector<sal_Int32> TableModel::getColumnWidths() +{ + std::vector<sal_Int32> aRet; + for (const TableColumnRef& xColumn : maColumns) + aRet.push_back(xColumn->getWidth()); + return aRet; +} + +// XComponent + + +void TableModel::dispose() +{ + ::SolarMutexGuard aGuard; + TableModelBase::dispose(); +} + + +// XModifiable + + +sal_Bool SAL_CALL TableModel::isModified( ) +{ + ::SolarMutexGuard aGuard; + return mbModified; +} + + +void SAL_CALL TableModel::setModified( sal_Bool bModified ) +{ + { + ::SolarMutexGuard aGuard; + mbModified = bModified; + } + if( bModified ) + notifyModification(); +} + + +// XModifyBroadcaster + + +void SAL_CALL TableModel::addModifyListener( const Reference< XModifyListener >& xListener ) +{ + rBHelper.addListener( cppu::UnoType<XModifyListener>::get() , xListener ); +} + + +void SAL_CALL TableModel::removeModifyListener( const Reference< XModifyListener >& xListener ) +{ + rBHelper.removeListener( cppu::UnoType<XModifyListener>::get() , xListener ); +} + + +// XColumnRowRange + + +Reference< XTableColumns > SAL_CALL TableModel::getColumns() +{ + ::SolarMutexGuard aGuard; + + if( !mxTableColumns.is() ) + mxTableColumns.set( new TableColumns( this ) ); + return mxTableColumns; +} + + +Reference< XTableRows > SAL_CALL TableModel::getRows() +{ + ::SolarMutexGuard aGuard; + + if( !mxTableRows.is() ) + mxTableRows.set( new TableRows( this ) ); + return mxTableRows; +} + + +// XCellRange + + +Reference< XCell > SAL_CALL TableModel::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) +{ + ::SolarMutexGuard aGuard; + + CellRef xCell( getCell( nColumn, nRow ) ); + if( xCell.is() ) + return xCell; + + throw IndexOutOfBoundsException(); +} + + +Reference< XCellRange > SAL_CALL TableModel::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +{ + ::SolarMutexGuard aGuard; + + if( (nLeft >= 0) && (nTop >= 0) && (nRight >= nLeft) && (nBottom >= nTop) && (nRight < getColumnCountImpl()) && (nBottom < getRowCountImpl() ) ) + { + TableModelRef xModel( this ); + return new CellRange( xModel, nLeft, nTop, nRight, nBottom ); + } + + throw IndexOutOfBoundsException(); +} + + +Reference< XCellRange > SAL_CALL TableModel::getCellRangeByName( const OUString& /*aRange*/ ) +{ + return Reference< XCellRange >(); +} + + +// XPropertySet + + +Reference< XPropertySetInfo > SAL_CALL TableModel::getPropertySetInfo( ) +{ + Reference< XPropertySetInfo > xInfo; + return xInfo; +} + + +void SAL_CALL TableModel::setPropertyValue( const OUString& /*aPropertyName*/, const Any& /*aValue*/ ) +{ +} + + +Any SAL_CALL TableModel::getPropertyValue( const OUString& /*PropertyName*/ ) +{ + return Any(); +} + + +void SAL_CALL TableModel::addPropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL TableModel::removePropertyChangeListener( const OUString& /*aPropertyName*/, const Reference< XPropertyChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL TableModel::addVetoableChangeListener( const OUString& /*aPropertyName*/, const Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + + +void SAL_CALL TableModel::removeVetoableChangeListener( const OUString& /*aPropertyName*/, const Reference< XVetoableChangeListener >& /*xListener*/ ) +{ +} + + +// XFastPropertySet + + +void SAL_CALL TableModel::setFastPropertyValue( ::sal_Int32 /*nHandle*/, const Any& /*aValue*/ ) +{ +} + + +Any SAL_CALL TableModel::getFastPropertyValue( ::sal_Int32 /*nHandle*/ ) +{ + Any aAny; + return aAny; +} + + +// internals + + +sal_Int32 TableModel::getRowCountImpl() const +{ + return static_cast< sal_Int32 >( maRows.size() ); +} + + +sal_Int32 TableModel::getColumnCountImpl() const +{ + return static_cast< sal_Int32 >( maColumns.size() ); +} + + +void TableModel::disposing() +{ + if( !maRows.empty() ) + { + for( auto& rpRow : maRows ) + rpRow->dispose(); + RowVector().swap(maRows); + } + + if( !maColumns.empty() ) + { + for( auto& rpCol : maColumns ) + rpCol->dispose(); + ColumnVector().swap(maColumns); + } + + if( mxTableColumns.is() ) + { + mxTableColumns->dispose(); + mxTableColumns.clear(); + } + + if( mxTableRows.is() ) + { + mxTableRows->dispose(); + mxTableRows.clear(); + } + + mpTableObj = nullptr; +} + + +// XBroadcaster + + +void TableModel::lockBroadcasts() +{ + ::SolarMutexGuard aGuard; + ++mnNotifyLock; +} + + +void TableModel::unlockBroadcasts() +{ + ::SolarMutexGuard aGuard; + --mnNotifyLock; + if( mnNotifyLock <= 0 ) + { + mnNotifyLock = 0; + if( mbNotifyPending ) + notifyModification(); + } +} + + +void TableModel::notifyModification() +{ + ::osl::MutexGuard guard( m_aMutex ); + if( (mnNotifyLock == 0) && mpTableObj ) + { + mbNotifyPending = false; + + ::cppu::OInterfaceContainerHelper * pModifyListeners = rBHelper.getContainer( cppu::UnoType<XModifyListener>::get() ); + if( pModifyListeners ) + { + EventObject aSource; + aSource.Source = static_cast< ::cppu::OWeakObject* >(this); + pModifyListeners->notifyEach( &XModifyListener::modified, aSource); + } + } + else + { + mbNotifyPending = true; + } +} + + +CellRef TableModel::getCell( sal_Int32 nCol, sal_Int32 nRow ) const +{ + if( ((nRow >= 0) && (nRow < getRowCountImpl())) && (nCol >= 0) && (nCol < getColumnCountImpl()) ) + { + return maRows[nRow]->maCells[nCol]; + } + else + { + CellRef xRet; + return xRet; + } +} + + +CellRef TableModel::createCell() +{ + CellRef xCell; + if( mpTableObj ) + mpTableObj->createCell( xCell ); + return xCell; +} + + +void TableModel::insertColumns( sal_Int32 nIndex, sal_Int32 nCount ) +{ + if( !(nCount && mpTableObj) ) + return; + + try + { + SdrModel& rModel(mpTableObj->getSdrModelFromSdrObject()); + TableModelNotifyGuard aGuard( this ); + nIndex = insert_range<ColumnVector,ColumnVector::iterator,TableColumnRef>( maColumns, nIndex, nCount ); + + sal_Int32 nRows = getRowCountImpl(); + while( nRows-- ) + maRows[nRows]->insertColumns( nIndex, nCount, nullptr ); + + ColumnVector aNewColumns(nCount); + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + { + TableColumnRef xNewCol( new TableColumn( this, nIndex+nOffset ) ); + maColumns[nIndex+nOffset] = xNewCol; + aNewColumns[nOffset] = xNewCol; + } + + const bool bUndo(mpTableObj->IsInserted() && rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_INSCOL) ); + rModel.AddUndo( rModel.GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) ); + + TableModelRef xThis( this ); + + nRows = getRowCountImpl(); + CellVector aNewCells( nCount * nRows ); + CellVector::iterator aCellIter( aNewCells.begin() ); + + nRows = getRowCountImpl(); + for( sal_Int32 nRow = 0; nRow < nRows; ++nRow ) + { + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + (*aCellIter++) = getCell( nIndex + nOffset, nRow ); + } + + rModel.AddUndo( std::make_unique<InsertColUndo>( xThis, nIndex, aNewColumns, aNewCells ) ); + } + + const sal_Int32 nRowCount = getRowCountImpl(); + // check if cells merge over new columns + for( sal_Int32 nCol = 0; nCol < nIndex; ++nCol ) + { + for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow ) + { + CellRef xCell( getCell( nCol, nRow ) ); + sal_Int32 nColSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getColumnSpan() : 1; + if( (nColSpan != 1) && ((nColSpan + nCol ) > nIndex) ) + { + // cell merges over newly created columns, so add the new columns to the merged cell + const sal_Int32 nRowSpan = xCell->getRowSpan(); + nColSpan += nCount; + merge( nCol, nRow, nColSpan, nRowSpan ); + } + } + } + + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + updateColumns(); + setModified(true); +} + + +void TableModel::removeColumns( sal_Int32 nIndex, sal_Int32 nCount ) +{ + sal_Int32 nColCount = getColumnCountImpl(); + + if( !(mpTableObj && nCount && (nIndex >= 0) && (nIndex < nColCount)) ) + return; + + try + { + TableModelNotifyGuard aGuard( this ); + + // clip removed columns to columns actually available + if( (nIndex + nCount) > nColCount ) + nCount = nColCount - nIndex; + + sal_Int32 nRows = getRowCountImpl(); + SdrModel& rModel(mpTableObj->getSdrModelFromSdrObject()); + const bool bUndo(mpTableObj->IsInserted() && rModel.IsUndoEnabled()); + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_UNDO_COL_DELETE) ); + rModel.AddUndo( rModel.GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) ); + } + + // only rows before and inside the removed rows are considered + nColCount = nIndex + nCount + 1; + + const sal_Int32 nRowCount = getRowCountImpl(); + + // first check merged cells before and inside the removed rows + for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol ) + { + for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow ) + { + CellRef xCell( getCell( nCol, nRow ) ); + sal_Int32 nColSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getColumnSpan() : 1; + if( nColSpan <= 1 ) + continue; + + if( nCol >= nIndex ) + { + // current cell is inside the removed columns + if( (nCol + nColSpan) > ( nIndex + nCount ) ) + { + // current cells merges with columns after the removed columns + const sal_Int32 nRemove = nCount - nCol + nIndex; + + CellRef xTargetCell( getCell( nIndex + nCount, nRow ) ); + if( xTargetCell.is() ) + { + if( bUndo ) + xTargetCell->AddUndo(); + xTargetCell->merge( nColSpan - nRemove, xCell->getRowSpan() ); + xTargetCell->replaceContentAndFormatting( xCell ); + } + } + } + else if( nColSpan > (nIndex - nCol) ) + { + // current cells spans inside the removed columns, so adjust + const sal_Int32 nRemove = ::std::min( nCount, nCol + nColSpan - nIndex ); + if( bUndo ) + xCell->AddUndo(); + xCell->merge( nColSpan - nRemove, xCell->getRowSpan() ); + } + } + } + + // We must not add RemoveColUndo before we make cell spans correct, otherwise we + // get invalid cell span after undo. + if( bUndo ) + { + TableModelRef xThis( this ); + ColumnVector aRemovedCols( nCount ); + sal_Int32 nOffset; + for( nOffset = 0; nOffset < nCount; ++nOffset ) + { + aRemovedCols[nOffset] = maColumns[nIndex+nOffset]; + } + + CellVector aRemovedCells( nCount * nRows ); + CellVector::iterator aCellIter( aRemovedCells.begin() ); + for( sal_Int32 nRow = 0; nRow < nRows; ++nRow ) + { + for( nOffset = 0; nOffset < nCount; ++nOffset ) + (*aCellIter++) = getCell( nIndex + nOffset, nRow ); + } + + rModel.AddUndo( std::make_unique<RemoveColUndo>( xThis, nIndex, aRemovedCols, aRemovedCells ) ); + } + + // now remove the columns + remove_range<ColumnVector,ColumnVector::iterator>( maColumns, nIndex, nCount ); + while( nRows-- ) + maRows[nRows]->removeColumns( nIndex, nCount ); + + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + updateColumns(); + setModified(true); +} + + +void TableModel::insertRows( sal_Int32 nIndex, sal_Int32 nCount ) +{ + if( !(nCount && mpTableObj) ) + return; + + SdrModel& rModel(mpTableObj->getSdrModelFromSdrObject()); + const bool bUndo(mpTableObj->IsInserted() && rModel.IsUndoEnabled()); + + try + { + TableModelNotifyGuard aGuard( this ); + + nIndex = insert_range<RowVector,RowVector::iterator,TableRowRef>( maRows, nIndex, nCount ); + + RowVector aNewRows(nCount); + const sal_Int32 nColCount = getColumnCountImpl(); + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + { + TableRowRef xNewRow( new TableRow( this, nIndex+nOffset, nColCount ) ); + maRows[nIndex+nOffset] = xNewRow; + aNewRows[nOffset] = xNewRow; + } + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_TABLE_INSROW) ); + rModel.AddUndo( rModel.GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) ); + TableModelRef xThis( this ); + rModel.AddUndo( std::make_unique<InsertRowUndo>( xThis, nIndex, aNewRows ) ); + } + + // check if cells merge over new columns + for( sal_Int32 nRow = 0; nRow < nIndex; ++nRow ) + { + for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol ) + { + CellRef xCell( getCell( nCol, nRow ) ); + sal_Int32 nRowSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getRowSpan() : 1; + if( (nRowSpan > 1) && ((nRowSpan + nRow) > nIndex) ) + { + // cell merges over newly created columns, so add the new columns to the merged cell + const sal_Int32 nColSpan = xCell->getColumnSpan(); + nRowSpan += nCount; + merge( nCol, nRow, nColSpan, nRowSpan ); + } + } + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); + + updateRows(); + setModified(true); +} + + +void TableModel::removeRows( sal_Int32 nIndex, sal_Int32 nCount ) +{ + sal_Int32 nRowCount = getRowCountImpl(); + + if( !(mpTableObj && nCount && (nIndex >= 0) && (nIndex < nRowCount)) ) + return; + + SdrModel& rModel(mpTableObj->getSdrModelFromSdrObject()); + const bool bUndo(mpTableObj->IsInserted() && rModel.IsUndoEnabled()); + + try + { + TableModelNotifyGuard aGuard( this ); + + // clip removed rows to rows actually available + if( (nIndex + nCount) > nRowCount ) + nCount = nRowCount - nIndex; + + if( bUndo ) + { + rModel.BegUndo( SvxResId(STR_UNDO_ROW_DELETE) ); + rModel.AddUndo( rModel.GetSdrUndoFactory().CreateUndoGeoObject(*mpTableObj) ); + } + + // only rows before and inside the removed rows are considered + nRowCount = nIndex + nCount + 1; + + const sal_Int32 nColCount = getColumnCountImpl(); + + // first check merged cells before and inside the removed rows + for( sal_Int32 nRow = 0; nRow < nRowCount; ++nRow ) + { + for( sal_Int32 nCol = 0; nCol < nColCount; ++nCol ) + { + CellRef xCell( getCell( nCol, nRow ) ); + sal_Int32 nRowSpan = (xCell.is() && !xCell->isMerged()) ? xCell->getRowSpan() : 1; + if( nRowSpan <= 1 ) + continue; + + if( nRow >= nIndex ) + { + // current cell is inside the removed rows + if( (nRow + nRowSpan) > (nIndex + nCount) ) + { + // current cells merges with rows after the removed rows + const sal_Int32 nRemove = nCount - nRow + nIndex; + + CellRef xTargetCell( getCell( nCol, nIndex + nCount ) ); + if( xTargetCell.is() ) + { + if( bUndo ) + xTargetCell->AddUndo(); + xTargetCell->merge( xCell->getColumnSpan(), nRowSpan - nRemove ); + xTargetCell->replaceContentAndFormatting( xCell ); + } + } + } + else if( nRowSpan > (nIndex - nRow) ) + { + // current cells spans inside the removed rows, so adjust + const sal_Int32 nRemove = ::std::min( nCount, nRow + nRowSpan - nIndex ); + if( bUndo ) + xCell->AddUndo(); + xCell->merge( xCell->getColumnSpan(), nRowSpan - nRemove ); + } + } + } + + if( bUndo ) + { + TableModelRef xThis( this ); + + RowVector aRemovedRows( nCount ); + for( sal_Int32 nOffset = 0; nOffset < nCount; ++nOffset ) + aRemovedRows[nOffset] = maRows[nIndex+nOffset]; + + // We must not RemoveRowUndo before we make cell spans correct, otherwise we + // get invalid cell span after undo. + rModel.AddUndo( std::make_unique<RemoveRowUndo>( xThis, nIndex, aRemovedRows ) ); + } + // now remove the rows + remove_range<RowVector,RowVector::iterator>( maRows, nIndex, nCount ); + + if( bUndo ) + rModel.EndUndo(); + + rModel.SetChanged(); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + updateRows(); + setModified(true); +} + + +TableRowRef const & TableModel::getRow( sal_Int32 nRow ) const +{ + if( (nRow >= 0) && (nRow < getRowCountImpl()) ) + return maRows[nRow]; + + throw IndexOutOfBoundsException(); +} + + +TableColumnRef const & TableModel::getColumn( sal_Int32 nColumn ) const +{ + if( (nColumn >= 0) && (nColumn < getColumnCountImpl()) ) + return maColumns[nColumn]; + + throw IndexOutOfBoundsException(); +} + + +/** deletes rows and columns that are completely merged. Must be called between BegUndo/EndUndo! */ +void TableModel::optimize() +{ + TableModelNotifyGuard aGuard( this ); + + bool bWasModified = false; + + if( !maRows.empty() && !maColumns.empty() ) + { + sal_Int32 nCol = getColumnCountImpl() - 1; + sal_Int32 nRows = getRowCountImpl(); + while( nCol > 0 ) + { + bool bEmpty = true; + for( sal_Int32 nRow = 0; (nRow < nRows) && bEmpty; nRow++ ) + { + Reference< XMergeableCell > xCell( getCellByPosition( nCol, nRow ), UNO_QUERY ); + if( xCell.is() && !xCell->isMerged() ) + bEmpty = false; + } + + if( bEmpty ) + { + try + { + static const OUStringLiteral sWidth(u"Width"); + sal_Int32 nWidth1 = 0, nWidth2 = 0; + Reference< XPropertySet > xSet1( static_cast< XCellRange* >( maColumns[nCol].get() ), UNO_QUERY_THROW ); + Reference< XPropertySet > xSet2( static_cast< XCellRange* >( maColumns[nCol-1].get() ), UNO_QUERY_THROW ); + xSet1->getPropertyValue( sWidth ) >>= nWidth1; + xSet2->getPropertyValue( sWidth ) >>= nWidth2; + nWidth1 = o3tl::saturating_add(nWidth1, nWidth2); + xSet2->setPropertyValue( sWidth, Any( nWidth1 ) ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + removeColumns( nCol, 1 ); + bWasModified = true; + } + + nCol--; + } + + sal_Int32 nRow = getRowCountImpl() - 1; + sal_Int32 nCols = getColumnCountImpl(); + while( nRow > 0 ) + { + bool bEmpty = true; + for( nCol = 0; (nCol < nCols) && bEmpty; nCol++ ) + { + Reference< XMergeableCell > xCell( getCellByPosition( nCol, nRow ), UNO_QUERY ); + if( xCell.is() && !xCell->isMerged() ) + bEmpty = false; + } + + if( bEmpty ) + { + try + { + static const OUStringLiteral sHeight(u"Height"); + sal_Int32 nHeight1 = 0, nHeight2 = 0; + Reference< XPropertySet > xSet1( static_cast< XCellRange* >( maRows[nRow].get() ), UNO_QUERY_THROW ); + Reference< XPropertySet > xSet2( static_cast< XCellRange* >( maRows[nRow-1].get() ), UNO_QUERY_THROW ); + xSet1->getPropertyValue( sHeight ) >>= nHeight1; + xSet2->getPropertyValue( sHeight ) >>= nHeight2; + nHeight1 = o3tl::saturating_add(nHeight1, nHeight2); + xSet2->setPropertyValue( sHeight, Any( nHeight1 ) ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + removeRows( nRow, 1 ); + bWasModified = true; + } + + nRow--; + } + } + if( bWasModified ) + setModified(true); +} + + +void TableModel::merge( sal_Int32 nCol, sal_Int32 nRow, sal_Int32 nColSpan, sal_Int32 nRowSpan ) +{ + if(nullptr == mpTableObj) + return; + + SdrModel& rModel(mpTableObj->getSdrModelFromSdrObject()); + const bool bUndo(mpTableObj->IsInserted() && rModel.IsUndoEnabled()); + const sal_Int32 nLastRow = nRow + nRowSpan; + const sal_Int32 nLastCol = nCol + nColSpan; + + if( (nLastRow > getRowCount()) || (nLastCol > getColumnCount() ) ) + { + OSL_FAIL("TableModel::merge(), merge beyond the table!"); + } + + // merge first cell + CellRef xOriginCell( dynamic_cast< Cell* >( getCellByPosition( nCol, nRow ).get() ) ); + if(!xOriginCell.is()) + return; + + if( bUndo ) + xOriginCell->AddUndo(); + xOriginCell->merge( nColSpan, nRowSpan ); + + sal_Int32 nTempCol = nCol + 1; + + // merge remaining cells + for( ; nRow < nLastRow; nRow++ ) + { + for( ; nTempCol < nLastCol; nTempCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( getCellByPosition( nTempCol, nRow ).get() ) ); + if( xCell.is() && !xCell->isMerged() ) + { + if( bUndo ) + xCell->AddUndo(); + xCell->setMerged(); + xOriginCell->mergeContent( xCell ); + } + } + nTempCol = nCol; + } +} + +void TableModel::updateRows() +{ + sal_Int32 nRow = 0; + for( auto& rpRow : maRows ) + { + rpRow->mnRow = nRow++; + } +} + +void TableModel::updateColumns() +{ + sal_Int32 nColumn = 0; + for( auto& rpCol : maColumns ) + { + rpCol->mnColumn = nColumn++; + } +} + +void TableModel::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("TableModel")); + for (sal_Int32 nRow = 0; nRow < getRowCountImpl(); ++nRow) + for (sal_Int32 nCol = 0; nCol < getColumnCountImpl(); ++nCol) + { + maRows[nRow]->maCells[nCol]->dumpAsXml(pWriter, nRow, nCol); + } + (void)xmlTextWriterEndElement(pWriter); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablerow.cxx b/svx/source/table/tablerow.cxx new file mode 100644 index 000000000..71b51a653 --- /dev/null +++ b/svx/source/table/tablerow.cxx @@ -0,0 +1,351 @@ +/* -*- 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/lang/DisposedException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> + +#include <cell.hxx> +#include "tablerow.hxx" +#include "tableundo.hxx" +#include <svx/svdmodel.hxx> +#include <svx/svdotable.hxx> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::beans; + + +namespace sdr::table { + +const sal_Int32 Property_Height = 0; +const sal_Int32 Property_OptimalHeight = 1; +const sal_Int32 Property_IsVisible = 2; +const sal_Int32 Property_IsStartOfNewPage = 3; + +TableRow::TableRow( const TableModelRef& xTableModel, sal_Int32 nRow, sal_Int32 nColumns ) +: TableRowBase( getStaticPropertySetInfo() ) +, mxTableModel( xTableModel ) +, mnRow( nRow ) +, mnHeight( 0 ) +, mbOptimalHeight( true ) +, mbIsVisible( true ) +, mbIsStartOfNewPage( false ) +{ + if( nColumns < 20 ) + maCells.reserve( 20 ); + + if( nColumns ) + { + maCells.resize( nColumns ); + while( nColumns-- ) + maCells[ nColumns ] = mxTableModel->createCell(); + } +} + + +TableRow::~TableRow() +{ +} + + +void TableRow::dispose() +{ + mxTableModel.clear(); + if( !maCells.empty() ) + { + for( auto& rpCell : maCells ) + rpCell->dispose(); + CellVector().swap(maCells); + } +} + + +void TableRow::throwIfDisposed() const +{ + if( !mxTableModel.is() ) + throw DisposedException(); +} + + +TableRow& TableRow::operator=( const TableRow& r ) +{ + mnHeight = r.mnHeight; + mbOptimalHeight = r.mbOptimalHeight; + mbIsVisible = r.mbIsVisible; + mbIsStartOfNewPage = r.mbIsStartOfNewPage; + maName = r.maName; + mnRow = r.mnRow; + + return *this; +} + + +void TableRow::insertColumns( sal_Int32 nIndex, sal_Int32 nCount, CellVector::iterator const * pIter /* = 0 */ ) +{ + throwIfDisposed(); + if( !nCount ) + return; + + if( nIndex >= static_cast< sal_Int32 >( maCells.size() ) ) + nIndex = static_cast< sal_Int32 >( maCells.size() ); + if ( pIter ) + maCells.insert( maCells.begin() + nIndex, *pIter, (*pIter) + nCount ); + else + { + maCells.reserve( maCells.size() + nCount ); + for ( sal_Int32 i = 0; i < nCount; i++ ) + maCells.insert( maCells.begin() + nIndex + i, mxTableModel->createCell() ); + } +} + + +void TableRow::removeColumns( sal_Int32 nIndex, sal_Int32 nCount ) +{ + throwIfDisposed(); + if( (nCount < 0) || ( nIndex < 0)) + return; + + if( (nIndex + nCount) < static_cast< sal_Int32 >( maCells.size() ) ) + { + CellVector::iterator aBegin( maCells.begin() ); + std::advance(aBegin, nIndex); + + if( nCount > 1 ) + { + CellVector::iterator aEnd( aBegin ); + while( nCount-- && (aEnd != maCells.end()) ) + ++aEnd; + maCells.erase( aBegin, aEnd ); + } + else + { + maCells.erase( aBegin ); + } + } + else + { + maCells.resize( nIndex ); + } +} + +const TableModelRef& TableRow::getModel() const +{ + return mxTableModel; +} + +// XCellRange + + +Reference< XCell > SAL_CALL TableRow::getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) +{ + throwIfDisposed(); + if( nRow != 0 ) + throw IndexOutOfBoundsException(); + + return mxTableModel->getCellByPosition( nColumn, mnRow ); +} + + +Reference< XCellRange > SAL_CALL TableRow::getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) +{ + throwIfDisposed(); + if( (nLeft >= 0 ) && (nTop == 0) && (nRight >= nLeft) && (nBottom == 0) ) + { + return mxTableModel->getCellRangeByPosition( nLeft, mnRow, nRight, mnRow ); + } + throw IndexOutOfBoundsException(); +} + + +Reference< XCellRange > SAL_CALL TableRow::getCellRangeByName( const OUString& /*aRange*/ ) +{ + throwIfDisposed(); + return Reference< XCellRange >(); +} + + +// XNamed + + +OUString SAL_CALL TableRow::getName() +{ + return maName; +} + + +void SAL_CALL TableRow::setName( const OUString& aName ) +{ + maName = aName; +} + + +// XFastPropertySet + + +void SAL_CALL TableRow::setFastPropertyValue( sal_Int32 nHandle, const Any& aValue ) +{ + if(!mxTableModel.is() || nullptr == mxTableModel->getSdrTableObj()) + return; + + SdrTableObj& rTableObj(*mxTableModel->getSdrTableObj()); + SdrModel& rModel(rTableObj.getSdrModelFromSdrObject()); + bool bOk(false); + bool bChange(false); + std::unique_ptr<TableRowUndo> pUndo; + const bool bUndo(rTableObj.IsInserted() && rModel.IsUndoEnabled()); + + if( bUndo ) + { + TableRowRef xThis( this ); + pUndo.reset(new TableRowUndo( xThis )); + } + + switch( nHandle ) + { + case Property_Height: + { + sal_Int32 nHeight = mnHeight; + bOk = aValue >>= nHeight; + if( bOk && (mnHeight != nHeight) ) + { + mnHeight = nHeight; + mbOptimalHeight = mnHeight == 0; + bChange = true; + } + break; + } + + case Property_OptimalHeight: + { + bool bOptimalHeight = mbOptimalHeight; + bOk = aValue >>= bOptimalHeight; + if( bOk && (mbOptimalHeight != bOptimalHeight) ) + { + mbOptimalHeight = bOptimalHeight; + if( bOptimalHeight ) + mnHeight = 0; + bChange = true; + } + break; + } + case Property_IsVisible: + { + bool bIsVisible = mbIsVisible; + bOk = aValue >>= bIsVisible; + if( bOk && (mbIsVisible != bIsVisible) ) + { + mbIsVisible = bIsVisible; + bChange = true; + } + break; + } + + case Property_IsStartOfNewPage: + { + bool bIsStartOfNewPage = mbIsStartOfNewPage; + bOk = aValue >>= bIsStartOfNewPage; + if( bOk && (mbIsStartOfNewPage != bIsStartOfNewPage) ) + { + mbIsStartOfNewPage = bIsStartOfNewPage; + bChange = true; + } + break; + } + default: + throw UnknownPropertyException( OUString::number(nHandle), static_cast<cppu::OWeakObject*>(this)); + } + + if( !bOk ) + { + throw IllegalArgumentException(); + } + + if( bChange ) + { + if( pUndo ) + { + rModel.AddUndo( std::move(pUndo) ); + } + mxTableModel->setModified(true); + } +} + + +Any SAL_CALL TableRow::getFastPropertyValue( sal_Int32 nHandle ) +{ + switch( nHandle ) + { + case Property_Height: return Any( mnHeight ); + case Property_OptimalHeight: return Any( mbOptimalHeight ); + case Property_IsVisible: return Any( mbIsVisible ); + case Property_IsStartOfNewPage: return Any( mbIsStartOfNewPage ); + default: throw UnknownPropertyException( OUString::number(nHandle), static_cast<cppu::OWeakObject*>(this)); + } +} + + +rtl::Reference< FastPropertySetInfo > TableRow::getStaticPropertySetInfo() +{ + static rtl::Reference<FastPropertySetInfo> xInfo = []() { + PropertyVector aProperties(6); + + aProperties[0].Name = "Height"; + aProperties[0].Handle = Property_Height; + aProperties[0].Type = ::cppu::UnoType<sal_Int32>::get(); + aProperties[0].Attributes = 0; + + aProperties[1].Name = "OptimalHeight"; + aProperties[1].Handle = Property_OptimalHeight; + aProperties[1].Type = cppu::UnoType<bool>::get(); + aProperties[1].Attributes = 0; + + aProperties[2].Name = "IsVisible"; + aProperties[2].Handle = Property_IsVisible; + aProperties[2].Type = cppu::UnoType<bool>::get(); + aProperties[2].Attributes = 0; + + aProperties[3].Name = "IsStartOfNewPage"; + aProperties[3].Handle = Property_IsStartOfNewPage; + aProperties[3].Type = cppu::UnoType<bool>::get(); + aProperties[3].Attributes = 0; + + aProperties[4].Name = "Size"; + aProperties[4].Handle = Property_Height; + aProperties[4].Type = ::cppu::UnoType<sal_Int32>::get(); + aProperties[4].Attributes = 0; + + aProperties[5].Name = "OptimalSize"; + aProperties[5].Handle = Property_OptimalHeight; + aProperties[5].Type = cppu::UnoType<bool>::get(); + aProperties[5].Attributes = 0; + + return rtl::Reference<FastPropertySetInfo>(new FastPropertySetInfo(aProperties)); + }(); + + return xInfo; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablerow.hxx b/svx/source/table/tablerow.hxx new file mode 100644 index 000000000..9548e317f --- /dev/null +++ b/svx/source/table/tablerow.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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLEROW_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLEROW_HXX + +#include <com/sun/star/table/XCellRange.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <cppuhelper/implbase.hxx> + +#include "propertyset.hxx" +#include <celltypes.hxx> + + +namespace sdr::table { + +typedef ::cppu::ImplInheritanceHelper< FastPropertySet, css::table::XCellRange, css::container::XNamed > TableRowBase; + +class TableRow : public TableRowBase +{ + friend class TableModel; + friend class TableRowUndo; +public: + TableRow( const TableModelRef& xTableModel, sal_Int32 nRow, sal_Int32 nColumns ); + virtual ~TableRow() override; + + void dispose(); + /// @throws css::uno::RuntimeException + void throwIfDisposed() const; + + TableRow& operator=( const TableRow& ); + + void insertColumns( sal_Int32 nIndex, sal_Int32 nCount, CellVector::iterator const * pIter ); + void removeColumns( sal_Int32 nIndex, sal_Int32 nCount ); + /// Reference to the table model containing this row. + const TableModelRef& getModel() const; + + // XCellRange + virtual css::uno::Reference< css::table::XCell > SAL_CALL getCellByPosition( sal_Int32 nColumn, sal_Int32 nRow ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByPosition( sal_Int32 nLeft, sal_Int32 nTop, sal_Int32 nRight, sal_Int32 nBottom ) override; + virtual css::uno::Reference< css::table::XCellRange > SAL_CALL getCellRangeByName( const OUString& aRange ) override; + + // XNamed + virtual OUString SAL_CALL getName() override; + virtual void SAL_CALL setName( const OUString& aName ) override; + + // XFastPropertySet + virtual void SAL_CALL setFastPropertyValue( ::sal_Int32 nHandle, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getFastPropertyValue( ::sal_Int32 nHandle ) override; + +private: + static rtl::Reference< FastPropertySetInfo > getStaticPropertySetInfo(); + + TableModelRef mxTableModel; + CellVector maCells; + sal_Int32 mnRow; + sal_Int32 mnHeight; + bool mbOptimalHeight; + bool mbIsVisible; + bool mbIsStartOfNewPage; + OUString maName; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablerows.cxx b/svx/source/table/tablerows.cxx new file mode 100644 index 000000000..8698f72f4 --- /dev/null +++ b/svx/source/table/tablerows.cxx @@ -0,0 +1,114 @@ +/* -*- 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/lang/DisposedException.hpp> + +#include <tablemodel.hxx> +#include "tablerow.hxx" +#include "tablerows.hxx" + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::table; + + +namespace sdr::table { + +TableRows::TableRows( const TableModelRef& xTableModel ) +: mxTableModel( xTableModel ) +{ +} + + +TableRows::~TableRows() +{ + dispose(); +} + + +void TableRows::dispose() +{ + mxTableModel.clear(); +} + + +void TableRows::throwIfDisposed() const +{ + if( !mxTableModel.is() ) + throw DisposedException(); +} + + +// XTableRows + + +void SAL_CALL TableRows::insertByIndex( sal_Int32 nIndex, sal_Int32 nCount ) +{ + throwIfDisposed(); + mxTableModel->insertRows( nIndex, nCount ); +} + + +void SAL_CALL TableRows::removeByIndex( sal_Int32 nIndex, sal_Int32 nCount ) +{ + throwIfDisposed(); + mxTableModel->removeRows( nIndex, nCount ); +} + + +// XIndexAccess + + +sal_Int32 SAL_CALL TableRows::getCount() +{ + throwIfDisposed(); + return mxTableModel->getRowCount(); +} + + +Any SAL_CALL TableRows::getByIndex( sal_Int32 Index ) +{ + throwIfDisposed(); + return Any( Reference< XCellRange >( static_cast< XCellRange* >( mxTableModel->getRow( Index ).get() ) ) ); +} + + +// XElementAccess + + +Type SAL_CALL TableRows::getElementType() +{ + throwIfDisposed(); + return cppu::UnoType<XCellRange>::get(); +} + + +sal_Bool SAL_CALL TableRows::hasElements() +{ + throwIfDisposed(); + return mxTableModel->getRowCount() != 0; +} + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablerows.hxx b/svx/source/table/tablerows.hxx new file mode 100644 index 000000000..3b25d3d9e --- /dev/null +++ b/svx/source/table/tablerows.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLEROWS_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLEROWS_HXX + +#include <com/sun/star/table/XTableRows.hpp> +#include <cppuhelper/implbase1.hxx> + +#include <celltypes.hxx> + +namespace sdr::table +{ +class TableRows : public ::cppu::WeakAggImplHelper1<css::table::XTableRows> +{ +public: + explicit TableRows(const TableModelRef& xTableModel); + virtual ~TableRows() override; + + void dispose(); + /// @throws css::uno::RuntimeException + void throwIfDisposed() const; + + // XTableRows + virtual void SAL_CALL insertByIndex(sal_Int32 nIndex, sal_Int32 nCount) override; + virtual void SAL_CALL removeByIndex(sal_Int32 nIndex, sal_Int32 nCount) override; + + // XIndexAccess + virtual sal_Int32 SAL_CALL getCount() override; + virtual css::uno::Any SAL_CALL getByIndex(sal_Int32 Index) override; + + // Methods + virtual css::uno::Type SAL_CALL getElementType() override; + virtual sal_Bool SAL_CALL hasElements() override; + +private: + TableModelRef mxTableModel; +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablertfexporter.cxx b/svx/source/table/tablertfexporter.cxx new file mode 100644 index 000000000..c298d5d1a --- /dev/null +++ b/svx/source/table/tablertfexporter.cxx @@ -0,0 +1,235 @@ +/* -*- 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 <vector> + +#include <com/sun/star/table/XTable.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <tools/diagnose_ex.h> +#include <tools/stream.hxx> +#include <svtools/rtfkeywd.hxx> +#include <svtools/rtfout.hxx> + +#include <editeng/eeitem.hxx> +#include <svx/sdtaitm.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> + +#include <cell.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outlobj.hxx> + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; + +namespace sdr::table { + +class SdrTableRtfExporter +{ +public: + SdrTableRtfExporter( SvStream& rStrmP, SdrTableObj& rObj ); + void Write(); + void WriteRow( const Reference< XPropertySet >& xRowSet, sal_Int32 nRow, const std::vector< sal_Int32 >& aColumnStart ); + void WriteCell( sal_Int32 nCol, sal_Int32 nRow ); + +private: + SvStream& mrStrm; + SdrTableObj& mrObj; + Reference< XTable > mxTable; +}; + +void ExportAsRTF( SvStream& rStrm, SdrTableObj& rObj ) +{ + SdrTableRtfExporter aEx( rStrm, rObj ); + aEx.Write(); +} + +constexpr OUStringLiteral gsSize( u"Size" ); + +SdrTableRtfExporter::SdrTableRtfExporter( SvStream& rStrm, SdrTableObj& rObj ) +: mrStrm( rStrm ) +, mrObj( rObj ) +, mxTable( rObj.getTable() ) +{ +} + +void SdrTableRtfExporter::Write() +{ + mrStrm.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RTF ); + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ANSI ).WriteCharPtr( SAL_NEWLINE_STRING ); + + Reference< XTableColumns > xColumns( mxTable->getColumns() ); + const sal_Int32 nColCount = xColumns->getCount(); + + std::vector< sal_Int32 > aColumnStart; + aColumnStart.reserve( nColCount ); + + // determine right offset of cells + sal_Int32 nPos = 0; + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) try + { + Reference< XPropertySet > xSet( xColumns->getByIndex(nCol), UNO_QUERY_THROW ); + sal_Int32 nWidth = 0; + xSet->getPropertyValue( gsSize ) >>= nWidth; + nPos += o3tl::toTwips(nWidth, o3tl::Length::mm100); + aColumnStart.push_back( nPos ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + // export rows + Reference< XTableRows > xRows( mxTable->getRows() ); + const sal_Int32 nRowCount = xRows->getCount(); + + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) try + { + Reference< XPropertySet > xRowSet( xRows->getByIndex(nRow), UNO_QUERY_THROW ); + WriteRow( xRowSet, nRow, aColumnStart ); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } + + mrStrm.WriteChar( '}' ).WriteCharPtr( SAL_NEWLINE_STRING ); +} + +void SdrTableRtfExporter::WriteRow( const Reference< XPropertySet >& xRowSet, sal_Int32 nRow, const std::vector< sal_Int32 >& aColumnStart ) +{ + sal_Int32 nRowHeight = 0; + xRowSet->getPropertyValue( gsSize ) >>= nRowHeight; + + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TROWD ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TRGAPH ).WriteCharPtr( "30" ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TRLEFT ).WriteCharPtr( "-30" ); + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TRRH ).WriteOString( OString::number(nRowHeight) ); + + const sal_Int32 nColCount = mxTable->getColumnCount(); + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) + { + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + + if( !xCell.is() ) + continue; + + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CELLX ).WriteOString( OString::number(aColumnStart[nCol]) ); + if ( (nCol & 0x0F) == 0x0F ) + mrStrm.WriteCharPtr( SAL_NEWLINE_STRING ); // prevent long lines + } + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PARD ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PLAIN ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_INTBL ).WriteCharPtr( SAL_NEWLINE_STRING ); + + sal_uInt64 nStrmPos = mrStrm.Tell(); + for( sal_Int32 nCol = 0; nCol < nColCount; nCol++ ) + { + WriteCell( nCol, nRow ); + if ( mrStrm.Tell() - nStrmPos > 255 ) + { + mrStrm.WriteCharPtr( SAL_NEWLINE_STRING ); + nStrmPos = mrStrm.Tell(); + } + } + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ROW ).WriteCharPtr( SAL_NEWLINE_STRING ); +} + + +void SdrTableRtfExporter::WriteCell( sal_Int32 nCol, sal_Int32 nRow ) +{ + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + + if( !xCell.is() || xCell->isMerged() ) + { + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CELL ); + return ; + } + + OUString aContent; + + std::optional<OutlinerParaObject> pParaObj = xCell->CreateEditOutlinerParaObject(); + + if( !pParaObj && xCell->GetOutlinerParaObject() ) + pParaObj = *xCell->GetOutlinerParaObject(); + + if(pParaObj) + { + // handle outliner attributes + SdrOutliner& rOutliner = mrObj.ImpGetDrawOutliner(); + rOutliner.SetText(*pParaObj); + + aContent = rOutliner.GetEditEngine().GetText(); + + rOutliner.Clear(); + } + + bool bResetAttr = false; + + SdrTextHorzAdjust eHAdj = xCell->GetTextHorizontalAdjust(); + + const SfxItemSet& rCellSet = xCell->GetItemSet(); + + const SvxWeightItem& rWeightItem = rCellSet.Get( EE_CHAR_WEIGHT ); + const SvxPostureItem& rPostureItem = rCellSet.Get( EE_CHAR_ITALIC ); + const SvxUnderlineItem& rUnderlineItem = rCellSet.Get( EE_CHAR_UNDERLINE ); + + const char* pChar; + + switch( eHAdj ) + { + case SDRTEXTHORZADJUST_CENTER: pChar = OOO_STRING_SVTOOLS_RTF_QC; break; + case SDRTEXTHORZADJUST_BLOCK: pChar = OOO_STRING_SVTOOLS_RTF_QJ; break; + case SDRTEXTHORZADJUST_RIGHT: pChar = OOO_STRING_SVTOOLS_RTF_QR; break; + case SDRTEXTHORZADJUST_LEFT: + default: pChar = OOO_STRING_SVTOOLS_RTF_QL; break; + } + mrStrm.WriteCharPtr( pChar ); + + if ( rWeightItem.GetWeight() >= WEIGHT_BOLD ) + { // bold + bResetAttr = true; + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B ); + } + if ( rPostureItem.GetPosture() != ITALIC_NONE ) + { // italic + bResetAttr = true; + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I ); + } + if ( rUnderlineItem.GetLineStyle() != LINESTYLE_NONE ) + { // underline + bResetAttr = true; + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UL ); + } + + mrStrm.WriteChar( ' ' ); + RTFOutFuncs::Out_String( mrStrm, aContent ); + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CELL ); + + if ( bResetAttr ) + mrStrm.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PLAIN ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tablertfimporter.cxx b/svx/source/table/tablertfimporter.cxx new file mode 100644 index 000000000..8f6e1c649 --- /dev/null +++ b/svx/source/table/tablertfimporter.cxx @@ -0,0 +1,503 @@ +/* -*- 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 <vector> + +#include <com/sun/star/table/XTable.hpp> +#include <com/sun/star/table/XMergeableCellRange.hpp> + +#include <tools/stream.hxx> +#include <tools/UnitConversion.hxx> +#include <svtools/rtftoken.h> + +#include <svx/svdetc.hxx> +#include <editeng/outlobj.hxx> + +#include <cell.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editdata.hxx> +#include <svx/svdmodel.hxx> +#include <editeng/editids.hrc> +#include <editeng/svxrtf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::table; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; + +namespace sdr::table { + +namespace { + +struct RTFCellDefault +{ + SfxItemSet maItemSet; + sal_Int32 mnRowSpan; + sal_Int32 mnColSpan; // MergeCell if >1, merged cells if 0 + sal_Int32 mnCellX; + + explicit RTFCellDefault( SfxItemPool* pPool ) : maItemSet( *pPool ), mnRowSpan(1), mnColSpan(1), mnCellX(0) {} +}; + +} + +typedef std::vector< std::shared_ptr< RTFCellDefault > > RTFCellDefaultVector; + +namespace { + +struct RTFCellInfo +{ + SfxItemSet maItemSet; + sal_Int32 mnStartPara; + sal_Int32 mnParaCount; + sal_Int32 mnCellX; + sal_Int32 mnRowSpan; + std::shared_ptr< RTFCellInfo > mxVMergeCell; + + explicit RTFCellInfo( SfxItemPool& rPool ) : maItemSet( rPool ), mnStartPara(0), mnParaCount(0), mnCellX(0), mnRowSpan(1) {} +}; + +} + +typedef std::shared_ptr< RTFCellInfo > RTFCellInfoPtr; +typedef std::vector< RTFCellInfoPtr > RTFColumnVector; + +typedef std::shared_ptr< RTFColumnVector > RTFColumnVectorPtr; + +class SdrTableRTFParser +{ +public: + explicit SdrTableRTFParser( SdrTableObj& rTableObj ); + + void Read( SvStream& rStream ); + + void ProcToken( RtfImportInfo* pInfo ); + + void NextRow(); + void NextColumn(); + void NewCellRow(); + + void InsertCell( RtfImportInfo const * pInfo ); + void InsertColumnEdge( sal_Int32 nEdge ); + + void FillTable(); + + DECL_LINK( RTFImportHdl, RtfImportInfo&, void ); + +private: + SdrTableObj& mrTableObj; + std::unique_ptr<SdrOutliner> mpOutliner; + SfxItemPool& mrItemPool; + + RTFCellDefaultVector maDefaultList; + RTFCellDefaultVector::iterator maDefaultIterator; + + int mnLastToken; + bool mbNewDef; + + sal_Int32 mnStartPara; + + sal_Int32 mnRowCnt; + sal_Int32 mnLastEdge; + sal_Int32 mnVMergeIdx; + + std::vector< sal_Int32 > maColumnEdges; + std::vector< sal_Int32 >::iterator maLastEdge; + std::vector< RTFColumnVectorPtr > maRows; + + std::unique_ptr<RTFCellDefault> mpInsDefault; + RTFCellDefault* mpActDefault; + RTFCellDefault* mpDefMerge; + + Reference< XTable > mxTable; + + RTFColumnVectorPtr mxLastRow; + // Copy assignment is forbidden and not implemented. + SdrTableRTFParser (const SdrTableRTFParser &) = delete; + SdrTableRTFParser & operator= (const SdrTableRTFParser &) = delete; +}; + +SdrTableRTFParser::SdrTableRTFParser( SdrTableObj& rTableObj ) +: mrTableObj( rTableObj ) +, mpOutliner( SdrMakeOutliner( OutlinerMode::TextObject, rTableObj.getSdrModelFromSdrObject() ) ) +, mrItemPool( rTableObj.getSdrModelFromSdrObject().GetItemPool() ) +, mnLastToken( 0 ) +, mbNewDef( false ) +, mnStartPara( 0 ) +, mnRowCnt( 0 ) +, mnLastEdge( 0 ) +, mnVMergeIdx ( 0 ) +, mpActDefault( nullptr ) +, mpDefMerge( nullptr ) +, mxTable( rTableObj.getTable() ) +{ + mpOutliner->SetUpdateLayout(true); + mpOutliner->SetStyleSheet( 0, mrTableObj.GetStyleSheet() ); + mpInsDefault.reset( new RTFCellDefault( &mrItemPool ) ); +} + +void SdrTableRTFParser::Read( SvStream& rStream ) +{ + EditEngine& rEdit = const_cast< EditEngine& >( mpOutliner->GetEditEngine() ); + + Link<RtfImportInfo&,void> aOldLink( rEdit.GetRtfImportHdl() ); + rEdit.SetRtfImportHdl( LINK( this, SdrTableRTFParser, RTFImportHdl ) ); + mpOutliner->Read( rStream, OUString(), EETextFormat::Rtf ); + rEdit.SetRtfImportHdl( aOldLink ); + + FillTable(); +} + +IMPL_LINK( SdrTableRTFParser, RTFImportHdl, RtfImportInfo&, rInfo, void ) +{ + switch ( rInfo.eState ) + { + case RtfImportState::NextToken: + ProcToken( &rInfo ); + break; + case RtfImportState::UnknownAttr: + ProcToken( &rInfo ); + break; + case RtfImportState::Start: + { + SvxRTFParser* pParser = static_cast<SvxRTFParser*>(rInfo.pParser); + pParser->SetAttrPool( &mrItemPool ); + pParser->SetPardMap(SID_ATTR_BORDER_OUTER, SDRATTR_TABLE_BORDER); + } + break; + case RtfImportState::End: + if ( rInfo.aSelection.nEndPos ) + { + mpActDefault = nullptr; + rInfo.nToken = RTF_PAR; + rInfo.aSelection.nEndPara++; + ProcToken( &rInfo ); + } + break; + case RtfImportState::SetAttr: + case RtfImportState::InsertText: + case RtfImportState::InsertPara: + break; + default: + SAL_WARN( "svx.table","unknown ImportInfo.eState"); + } +} + +void SdrTableRTFParser::NextRow() +{ + mxLastRow = maRows.back(); + mnVMergeIdx = 0; + ++mnRowCnt; +} + +void SdrTableRTFParser::InsertCell( RtfImportInfo const * pInfo ) +{ + + RTFCellInfoPtr xCellInfo = std::make_shared<RTFCellInfo>(mrItemPool); + + xCellInfo->mnStartPara = mnStartPara; + xCellInfo->mnParaCount = pInfo->aSelection.nEndPara - 1 - mnStartPara; + xCellInfo->mnCellX = mpActDefault->mnCellX; + xCellInfo->mnRowSpan = mpActDefault->mnRowSpan; + + + if ( mxLastRow != nullptr ) + { + sal_Int32 nSize = mxLastRow->size(); + while( mnVMergeIdx < nSize && + (*mxLastRow)[mnVMergeIdx]->mnCellX < xCellInfo->mnCellX ) + ++mnVMergeIdx; + + if ( xCellInfo->mnRowSpan == 0 && mnVMergeIdx < nSize ) + { + RTFCellInfoPtr xLastCell( (*mxLastRow)[mnVMergeIdx] ); + if (xLastCell->mnRowSpan) + xCellInfo->mxVMergeCell = xLastCell; + else + xCellInfo->mxVMergeCell = xLastCell->mxVMergeCell; + } + } + + if( !maRows.empty() ) + { + RTFColumnVectorPtr xColumn( maRows.back() ); + if ( xCellInfo->mxVMergeCell ) + { + if ( xColumn->empty() || + xColumn->back()->mxVMergeCell != xCellInfo->mxVMergeCell ) + xCellInfo->mxVMergeCell->mnRowSpan++; + } + + xColumn->push_back( xCellInfo ); + } + + mnStartPara = pInfo->aSelection.nEndPara - 1; +} + +void SdrTableRTFParser::InsertColumnEdge( sal_Int32 nEdge ) +{ + auto aNextEdge = std::lower_bound( maLastEdge, maColumnEdges.end(), nEdge ); + + if ( aNextEdge == maColumnEdges.end() || nEdge != *aNextEdge ) + { + maLastEdge = maColumnEdges.insert( aNextEdge , nEdge ); + mnLastEdge = nEdge; + } +} + +void SdrTableRTFParser::FillTable() +{ + try + { + sal_Int32 nColCount = mxTable->getColumnCount(); + Reference< XTableColumns > xCols( mxTable->getColumns(), UNO_SET_THROW ); + sal_Int32 nColMax = maColumnEdges.size(); + if( nColCount < nColMax ) + { + xCols->insertByIndex( nColCount, nColMax - nColCount ); + nColCount = mxTable->getColumnCount(); + } + + static const OUStringLiteral sWidth(u"Width"); + sal_Int32 nCol, nLastEdge = 0; + for( nCol = 0; nCol < nColCount; nCol++ ) + { + Reference< XPropertySet > xSet( xCols->getByIndex( nCol ), UNO_QUERY_THROW ); + sal_Int32 nWidth = maColumnEdges[nCol] - nLastEdge; + + xSet->setPropertyValue( sWidth, Any( nWidth ) ); + nLastEdge += nWidth; + } + + const sal_Int32 nRowCount = mxTable->getRowCount(); + if( nRowCount < mnRowCnt ) + { + Reference< XTableRows > xRows( mxTable->getRows(), UNO_SET_THROW ); + xRows->insertByIndex( nRowCount, mnRowCnt - nRowCount ); + } + + for( sal_Int32 nRow = 0; nRow < static_cast<sal_Int32>(maRows.size()); nRow++ ) + { + RTFColumnVectorPtr xColumn( maRows[nRow] ); + nCol = 0; + auto aEdge = maColumnEdges.begin(); + for( sal_Int32 nIdx = 0; nCol < nColMax && nIdx < static_cast<sal_Int32>(xColumn->size()); nIdx++ ) + { + RTFCellInfoPtr xCellInfo( (*xColumn)[nIdx] ); + + CellRef xCell( dynamic_cast< Cell* >( mxTable->getCellByPosition( nCol, nRow ).get() ) ); + if( xCell.is() && xCellInfo ) + { + const SfxPoolItem *pPoolItem = nullptr; + if( xCellInfo->maItemSet.GetItemState(SDRATTR_TABLE_BORDER,false,&pPoolItem)==SfxItemState::SET) + xCell->SetMergedItem( *pPoolItem ); + + std::optional<OutlinerParaObject> pTextObject(mpOutliner->CreateParaObject( xCellInfo->mnStartPara, xCellInfo->mnParaCount )); + if( pTextObject ) + { + SdrOutliner& rOutliner=mrTableObj.ImpGetDrawOutliner(); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText( *pTextObject ); + mrTableObj.NbcSetOutlinerParaObjectForText( rOutliner.CreateParaObject(), xCell.get() ); + } + + sal_Int32 nLastRow = nRow; + if ( xCellInfo->mnRowSpan ) + nLastRow += xCellInfo->mnRowSpan - 1; + + aEdge = std::lower_bound( aEdge, maColumnEdges.end(), xCellInfo->mnCellX ); + sal_Int32 nLastCol = nCol; + if ( aEdge != maColumnEdges.end() ) + { + nLastCol = std::distance( maColumnEdges.begin(), aEdge); + ++aEdge; + } + + if ( nLastCol > nCol || nLastRow > nRow ) + { + Reference< XMergeableCellRange > xRange( mxTable->createCursorByRange( mxTable->getCellRangeByPosition( nCol, nRow, nLastCol, nLastRow ) ), UNO_QUERY_THROW ); + if( xRange->isMergeable() ) + xRange->merge(); + } + nCol = nLastCol + 1; + } + } + } + + tools::Rectangle aRect( mrTableObj.GetSnapRect() ); + aRect.SetRight( aRect.Left() + nLastEdge ); + mrTableObj.NbcSetSnapRect( aRect ); + + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("svx", ""); + } +} + +void SdrTableRTFParser::NewCellRow() +{ + if( mbNewDef ) + { + mbNewDef = false; + + maRows.push_back( std::make_shared<std::vector<std::shared_ptr<RTFCellInfo>>>( ) ); + } + mpDefMerge = nullptr; + maDefaultIterator = maDefaultList.begin(); + + NextColumn(); + + DBG_ASSERT( mpActDefault, "NewCellRow: pActDefault==0" ); +} + +void SdrTableRTFParser::NextColumn() +{ + if( maDefaultIterator != maDefaultList.end() ) + mpActDefault = (*maDefaultIterator++).get(); + else + mpActDefault = nullptr; +} + +void SdrTableRTFParser::ProcToken( RtfImportInfo* pInfo ) +{ + switch ( pInfo->nToken ) + { + case RTF_TROWD: // denotes table row default, before RTF_CELLX + { + maDefaultList.clear(); + mpDefMerge = nullptr; + mnLastToken = pInfo->nToken; + maLastEdge = maColumnEdges.begin(); + mnLastEdge = 0; + } + break; + case RTF_CLMGF: // The first cell of cells to be merged + { + mpDefMerge = mpInsDefault.get(); + mnLastToken = pInfo->nToken; + } + break; + case RTF_CLMRG: // A cell to be merged with the preceding cell + { + if ( !mpDefMerge ) + mpDefMerge = maDefaultList.back().get(); + DBG_ASSERT( mpDefMerge, "RTF_CLMRG: pDefMerge==0" ); + if( mpDefMerge ) + mpDefMerge->mnColSpan++; + mpInsDefault->mnColSpan = 0; + mnLastToken = pInfo->nToken; + } + break; + case RTF_CLVMGF: + { + mnLastToken = pInfo->nToken; + } + break; + case RTF_CLVMRG: + { + mpInsDefault->mnRowSpan = 0; + mnLastToken = pInfo->nToken; + } + break; + case RTF_CELLX: // closes cell default + { + mbNewDef = true; + std::shared_ptr<RTFCellDefault> pDefault( mpInsDefault.release() ); + maDefaultList.push_back( pDefault ); + + + const sal_Int32 nSize = convertTwipToMm100(pInfo->nTokenValue); + if ( nSize > mnLastEdge ) + InsertColumnEdge( nSize ); + + pDefault->mnCellX = nSize; + // Record cellx in the first merged cell. + if ( mpDefMerge && pDefault->mnColSpan == 0 ) + mpDefMerge->mnCellX = nSize; + + mpInsDefault.reset( new RTFCellDefault( &mrItemPool ) ); + + mnLastToken = pInfo->nToken; + } + break; + case RTF_INTBL: // before the first RTF_CELL + { + if ( mnLastToken != RTF_INTBL && mnLastToken != RTF_CELL && mnLastToken != RTF_PAR ) + { + NewCellRow(); + mnLastToken = pInfo->nToken; + } + } + break; + case RTF_CELL: // denotes the end of a cell. + { + DBG_ASSERT( mpActDefault, "RTF_CELL: pActDefault==0" ); + if ( mbNewDef || !mpActDefault ) + NewCellRow(); + if ( !mpActDefault ) + mpActDefault = mpInsDefault.get(); + if ( mpActDefault->mnColSpan > 0 ) + { + InsertCell(pInfo); + } + NextColumn(); + mnLastToken = pInfo->nToken; + } + break; + case RTF_ROW: // means the end of a row + { + NextRow(); + mnLastToken = pInfo->nToken; + } + break; + case RTF_PAR: // Paragraph + mnLastToken = pInfo->nToken; + break; + default: + { // do not set nLastToken + switch ( pInfo->nToken & ~(0xff | RTF_TABLEDEF) ) + { + case RTF_SHADINGDEF: +// ((SvxRTFParser*)pInfo->pParser)->ReadBackgroundAttr(pInfo->nToken, mpInsDefault->maItemSet, sal_True ); + break; + case RTF_BRDRDEF: + static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBorderAttr(pInfo->nToken, mpInsDefault->maItemSet, true ); + break; + } + } + } +} + +void ImportAsRTF( SvStream& rStream, SdrTableObj& rObj ) +{ + SdrTableRTFParser aParser( rObj ); + aParser.Read( rStream ); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tableundo.cxx b/svx/source/table/tableundo.cxx new file mode 100644 index 000000000..66641469d --- /dev/null +++ b/svx/source/table/tableundo.cxx @@ -0,0 +1,516 @@ +/* -*- 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 <sdr/properties/textproperties.hxx> +#include <editeng/outlobj.hxx> + +#include <cell.hxx> +#include "tableundo.hxx" +#include <svx/svdotable.hxx> +#include "tablerow.hxx" +#include "tablecolumn.hxx" + + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::table; + + +namespace sdr::table { + +CellUndo::CellUndo( SdrObject* pObjRef, const CellRef& xCell ) +: SdrUndoAction(xCell->GetObject().getSdrModelFromSdrObject()) + ,mxObjRef( pObjRef ) + ,mxCell( xCell ) + ,mbUndo( true ) +{ + if( mxCell.is() && mxObjRef.is() ) + { + getDataFromCell( maUndoData ); + mxObjRef->AddObjectUser( *this ); + } +} + +CellUndo::~CellUndo() +{ + if( mxObjRef.is() ) + mxObjRef->RemoveObjectUser( *this ); + dispose(); +} + +void CellUndo::dispose() +{ + mxCell.clear(); + delete maUndoData.mpProperties; + maUndoData.mpProperties = nullptr; + delete maRedoData.mpProperties; + maRedoData.mpProperties = nullptr; + maUndoData.mpOutlinerParaObject.reset(); + maRedoData.mpOutlinerParaObject.reset(); +} + +void CellUndo::ObjectInDestruction(const SdrObject& ) +{ + dispose(); +} + +void CellUndo::Undo() +{ + if( mxCell.is() && mbUndo ) + { + if( maRedoData.mpProperties == nullptr ) + getDataFromCell( maRedoData ); + + setDataToCell( maUndoData ); + mbUndo = false; + } +} + +void CellUndo::Redo() +{ + if( mxCell.is() && !mbUndo ) + { + setDataToCell( maRedoData ); + mbUndo = true; + } +} + +bool CellUndo::Merge( SfxUndoAction *pNextAction ) +{ + CellUndo* pNext = dynamic_cast< CellUndo* >( pNextAction ); + return pNext && pNext->mxCell.get() == mxCell.get(); +} + +void CellUndo::setDataToCell( const Data& rData ) +{ + if( rData.mpProperties ) + mxCell->mpProperties.reset(Cell::CloneProperties( rData.mpProperties, *mxObjRef, *mxCell )); + else + mxCell->mpProperties.reset(); + + if( rData.mpOutlinerParaObject ) + mxCell->SetOutlinerParaObject( *rData.mpOutlinerParaObject ); + else + mxCell->RemoveOutlinerParaObject(); + + mxCell->msFormula = rData.msFormula; + mxCell->mfValue = rData.mfValue; + mxCell->mnError = rData.mnError; + mxCell->mbMerged = rData.mbMerged; + mxCell->mnRowSpan = rData.mnRowSpan; + mxCell->mnColSpan = rData.mnColSpan; + + if(mxObjRef.is()) + { + // #i120201# ActionChanged is not enough, we need to trigger TableLayouter::UpdateBorderLayout() + // and this is done best using ReformatText() for table objects + mxObjRef->ActionChanged(); + mxObjRef->NbcReformatText(); + } +} + +void CellUndo::getDataFromCell( Data& rData ) +{ + if( !(mxObjRef.is() && mxCell.is()) ) + return; + + if( mxCell->mpProperties ) + rData.mpProperties = mxCell->CloneProperties( *mxObjRef, *mxCell); + + if( mxCell->GetOutlinerParaObject() ) + rData.mpOutlinerParaObject = *mxCell->GetOutlinerParaObject(); + else + rData.mpOutlinerParaObject.reset(); + + rData.msFormula = mxCell->msFormula; + rData.mfValue = mxCell->mfValue; + rData.mnError = mxCell->mnError; + rData.mbMerged = mxCell->mbMerged; + rData.mnRowSpan = mxCell->mnRowSpan; + rData.mnColSpan = mxCell->mnColSpan; +} + + +// class InsertRowUndo : public SdrUndoAction + + +static void Dispose( RowVector& rRows ) +{ + for( auto& rpRow : rRows ) + rpRow->dispose(); +} + + +InsertRowUndo::InsertRowUndo( const TableModelRef& xTable, sal_Int32 nIndex, RowVector& aNewRows ) +: SdrUndoAction(xTable->getSdrTableObj()->getSdrModelFromSdrObject()) + ,mxTable( xTable ) + ,mnIndex( nIndex ) + ,mbUndo( true ) +{ + maRows.swap( aNewRows ); +} + + +InsertRowUndo::~InsertRowUndo() +{ + if( !mbUndo ) + Dispose( maRows ); +} + + +void InsertRowUndo::Undo() +{ + if( mxTable.is() ) + { + mxTable->UndoInsertRows( mnIndex, sal::static_int_cast< sal_Int32 >( maRows.size() ) ); + mbUndo = false; + } +} + + +void InsertRowUndo::Redo() +{ + if( mxTable.is() ) + { + mxTable->UndoRemoveRows( mnIndex, maRows ); + mbUndo = true; + } +} + + +// class RemoveRowUndo : public SdrUndoAction + + +RemoveRowUndo::RemoveRowUndo( const TableModelRef& xTable, sal_Int32 nIndex, RowVector& aRemovedRows ) +: SdrUndoAction(xTable->getSdrTableObj()->getSdrModelFromSdrObject()) + ,mxTable( xTable ) + ,mnIndex( nIndex ) + ,mbUndo( true ) +{ + maRows.swap( aRemovedRows ); +} + + +RemoveRowUndo::~RemoveRowUndo() +{ + if( mbUndo ) + Dispose( maRows ); +} + + +void RemoveRowUndo::Undo() +{ + if( mxTable.is() ) + { + mxTable->UndoRemoveRows( mnIndex, maRows ); + mbUndo = false; + } +} + + +void RemoveRowUndo::Redo() +{ + if( mxTable.is() ) + { + mxTable->UndoInsertRows( mnIndex, sal::static_int_cast< sal_Int32 >( maRows.size() ) ); + mbUndo = true; + } +} + + +// class InsertColUndo : public SdrUndoAction + + +static void Dispose( ColumnVector& rCols ) +{ + for( auto& rpCol : rCols ) + rpCol->dispose(); +} + + +static void Dispose( CellVector& rCells ) +{ + for( auto& rpCell : rCells ) + rpCell->dispose(); +} + + +InsertColUndo::InsertColUndo( const TableModelRef& xTable, sal_Int32 nIndex, ColumnVector& aNewCols, CellVector& aCells ) +: SdrUndoAction(xTable->getSdrTableObj()->getSdrModelFromSdrObject()) + ,mxTable( xTable ) + ,mnIndex( nIndex ) + ,mbUndo( true ) +{ + maColumns.swap( aNewCols ); + maCells.swap( aCells ); +} + + +InsertColUndo::~InsertColUndo() +{ + if( !mbUndo ) + { + Dispose( maColumns ); + Dispose( maCells ); + } +} + + +void InsertColUndo::Undo() +{ + if( mxTable.is() ) + { + mxTable->UndoInsertColumns( mnIndex, sal::static_int_cast< sal_Int32 >( maColumns.size() ) ); + mbUndo = false; + } +} + + +void InsertColUndo::Redo() +{ + if( mxTable.is() ) + { + mxTable->UndoRemoveColumns( mnIndex, maColumns, maCells ); + mbUndo = true; + } +} + + +// class RemoveColUndo : public SdrUndoAction + + +RemoveColUndo::RemoveColUndo( const TableModelRef& xTable, sal_Int32 nIndex, ColumnVector& aNewCols, CellVector& aCells ) +: SdrUndoAction(xTable->getSdrTableObj()->getSdrModelFromSdrObject()) + ,mxTable( xTable ) + ,mnIndex( nIndex ) + ,mbUndo( true ) +{ + maColumns.swap( aNewCols ); + maCells.swap( aCells ); +} + + +RemoveColUndo::~RemoveColUndo() +{ + if( mbUndo ) + { + Dispose( maColumns ); + Dispose( maCells ); + } +} + + +void RemoveColUndo::Undo() +{ + if( mxTable.is() ) + { + mxTable->UndoRemoveColumns( mnIndex, maColumns, maCells ); + mbUndo = false; + } +} + + +void RemoveColUndo::Redo() +{ + if( mxTable.is() ) + { + mxTable->UndoInsertColumns( mnIndex, sal::static_int_cast< sal_Int32 >( maColumns.size() ) ); + mbUndo = true; + } +} + + +// class TableColumnUndo : public SdrUndoAction + + +TableColumnUndo::TableColumnUndo( const TableColumnRef& xCol ) +: SdrUndoAction(xCol->mxTableModel->getSdrTableObj()->getSdrModelFromSdrObject()) + ,mxCol( xCol ) + ,mbHasRedoData( false ) +{ + getData( maUndoData ); +} + + +TableColumnUndo::~TableColumnUndo() +{ +} + + +void TableColumnUndo::Undo() +{ + if( !mbHasRedoData ) + { + getData( maRedoData ); + mbHasRedoData = true; + } + setData( maUndoData ); +} + + +void TableColumnUndo::Redo() +{ + setData( maRedoData ); +} + + +bool TableColumnUndo::Merge( SfxUndoAction *pNextAction ) +{ + TableColumnUndo* pNext = dynamic_cast< TableColumnUndo* >( pNextAction ); + return pNext && pNext->mxCol == mxCol; +} + + +void TableColumnUndo::setData( const Data& rData ) +{ + mxCol->mnColumn = rData.mnColumn; + mxCol->mnWidth = rData.mnWidth; + mxCol->mbOptimalWidth = rData.mbOptimalWidth; + mxCol->mbIsVisible = rData.mbIsVisible; + mxCol->mbIsStartOfNewPage = rData.mbIsStartOfNewPage; + mxCol->maName = rData.maName; + + // Trigger re-layout of the table. + mxCol->getModel()->setModified(true); +} + + +void TableColumnUndo::getData( Data& rData ) +{ + rData.mnColumn = mxCol->mnColumn; + rData.mnWidth = mxCol->mnWidth; + rData.mbOptimalWidth = mxCol->mbOptimalWidth; + rData.mbIsVisible = mxCol->mbIsVisible; + rData.mbIsStartOfNewPage = mxCol->mbIsStartOfNewPage; + rData.maName = mxCol->maName; +} + + +// class TableRowUndo : public SdrUndoAction + + +TableRowUndo::TableRowUndo( const TableRowRef& xRow ) +: SdrUndoAction(xRow->mxTableModel->getSdrTableObj()->getSdrModelFromSdrObject()) + , mxRow( xRow ) + , mbHasRedoData( false ) +{ + getData( maUndoData ); +} + + +TableRowUndo::~TableRowUndo() +{ +} + + +void TableRowUndo::Undo() +{ + if( !mbHasRedoData ) + { + getData( maRedoData ); + mbHasRedoData = true; + } + setData( maUndoData ); +} + + +void TableRowUndo::Redo() +{ + setData( maRedoData ); +} + + +bool TableRowUndo::Merge( SfxUndoAction *pNextAction ) +{ + TableRowUndo* pNext = dynamic_cast< TableRowUndo* >( pNextAction ); + return pNext && pNext->mxRow == mxRow; +} + + +void TableRowUndo::setData( const Data& rData ) +{ + mxRow->mnRow = rData.mnRow; + mxRow->mnHeight = rData.mnHeight; + mxRow->mbOptimalHeight = rData.mbOptimalHeight; + mxRow->mbIsVisible = rData.mbIsVisible; + mxRow->mbIsStartOfNewPage = rData.mbIsStartOfNewPage; + mxRow->maName = rData.maName; + + // Trigger re-layout of the table. + mxRow->getModel()->setModified(true); + } + + +void TableRowUndo::getData( Data& rData ) +{ + rData.mnRow = mxRow->mnRow; + rData.mnHeight = mxRow->mnHeight; + rData.mbOptimalHeight = mxRow->mbOptimalHeight; + rData.mbIsVisible = mxRow->mbIsVisible; + rData.mbIsStartOfNewPage = mxRow->mbIsStartOfNewPage; + rData.maName = mxRow->maName; +} + + +TableStyleUndo::TableStyleUndo( const SdrTableObj& rTableObj ) +: SdrUndoAction(rTableObj.getSdrModelFromSdrObject()) + ,mxObjRef( const_cast< sdr::table::SdrTableObj*>( &rTableObj ) ) + ,mbHasRedoData(false) +{ + getData( maUndoData ); +} + +void TableStyleUndo::Undo() +{ + if( !mbHasRedoData ) + { + getData( maRedoData ); + mbHasRedoData = true; + } + setData( maUndoData ); +} + +void TableStyleUndo::Redo() +{ + setData( maRedoData ); +} + +void TableStyleUndo::setData( const Data& rData ) +{ + SdrTableObj* pTableObj = mxObjRef.get(); + if( pTableObj ) + { + pTableObj->setTableStyle( rData.mxTableStyle ); + pTableObj->setTableStyleSettings( rData.maSettings ); + } +} + +void TableStyleUndo::getData( Data& rData ) +{ + SdrTableObj* pTableObj = mxObjRef.get(); + if( pTableObj ) + { + rData.maSettings = pTableObj->getTableStyleSettings(); + rData.mxTableStyle = pTableObj->getTableStyle(); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/tableundo.hxx b/svx/source/table/tableundo.hxx new file mode 100644 index 000000000..822f5ec64 --- /dev/null +++ b/svx/source/table/tableundo.hxx @@ -0,0 +1,258 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_TABLEUNDO_HXX +#define INCLUDED_SVX_SOURCE_TABLE_TABLEUNDO_HXX + +#include <com/sun/star/container/XIndexAccess.hpp> + +#include <svx/svdotable.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdundo.hxx> +#include <svx/sdrobjectuser.hxx> + +#include <celltypes.hxx> + +namespace sdr::properties { + class TextProperties; +} + +class OutlinerParaObject; + + +namespace sdr::table { + +class CellUndo : public SdrUndoAction, public sdr::ObjectUser +{ +public: + CellUndo( SdrObject* pObj, const CellRef& xCell ); + virtual ~CellUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + virtual bool Merge( SfxUndoAction *pNextAction ) override; + + void dispose(); + virtual void ObjectInDestruction(const SdrObject& rObject) override; + +private: + struct Data + { + sdr::properties::TextProperties* mpProperties; + std::optional<OutlinerParaObject> mpOutlinerParaObject; + + OUString msFormula; + double mfValue; + ::sal_Int32 mnError; + bool mbMerged; + ::sal_Int32 mnRowSpan; + ::sal_Int32 mnColSpan; + + Data() + : mpProperties(nullptr) + , mfValue(0) + , mnError(0) + , mbMerged(false) + , mnRowSpan(0) + , mnColSpan(0) + { + } + }; + + void setDataToCell( const Data& rData ); + void getDataFromCell( Data& rData ); + + tools::WeakReference<SdrObject> mxObjRef; + CellRef mxCell; + Data maUndoData; + Data maRedoData; + bool mbUndo; +}; + + +class InsertRowUndo : public SdrUndoAction +{ +public: + InsertRowUndo( const TableModelRef& xTable, sal_Int32 nIndex, RowVector& aNewRows ); + virtual ~InsertRowUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + +private: + TableModelRef mxTable; + sal_Int32 mnIndex; + RowVector maRows; + bool mbUndo; +}; + + +class RemoveRowUndo : public SdrUndoAction +{ +public: + RemoveRowUndo( const TableModelRef& xTable, sal_Int32 nIndex, RowVector& aRemovedRows ); + virtual ~RemoveRowUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + +private: + TableModelRef mxTable; + sal_Int32 mnIndex; + RowVector maRows; + bool mbUndo; +}; + + +class InsertColUndo : public SdrUndoAction +{ +public: + InsertColUndo( const TableModelRef& xTable, sal_Int32 nIndex, ColumnVector& aNewCols, CellVector& aCells ); + virtual ~InsertColUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + +private: + TableModelRef mxTable; + sal_Int32 mnIndex; + ColumnVector maColumns; + CellVector maCells; + bool mbUndo; +}; + + +class RemoveColUndo : public SdrUndoAction +{ +public: + RemoveColUndo( const TableModelRef& xTable, sal_Int32 nIndex, ColumnVector& aNewCols, CellVector& aCells ); + virtual ~RemoveColUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + +private: + TableModelRef mxTable; + sal_Int32 mnIndex; + ColumnVector maColumns; + CellVector maCells; + bool mbUndo; +}; + + +class TableColumnUndo : public SdrUndoAction +{ +public: + explicit TableColumnUndo( const TableColumnRef& xCol ); + virtual ~TableColumnUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + virtual bool Merge( SfxUndoAction *pNextAction ) override; + +private: + struct Data + { + sal_Int32 mnColumn; + sal_Int32 mnWidth; + bool mbOptimalWidth; + bool mbIsVisible; + bool mbIsStartOfNewPage; + OUString maName; + + Data() + : mnColumn(0) + , mnWidth(0) + , mbOptimalWidth(false) + , mbIsVisible(false) + , mbIsStartOfNewPage(false) + { + } + }; + + void setData( const Data& rData ); + void getData( Data& rData ); + + TableColumnRef mxCol; + Data maUndoData; + Data maRedoData; + bool mbHasRedoData; +}; + + +class TableRowUndo : public SdrUndoAction +{ +public: + explicit TableRowUndo( const TableRowRef& xRow ); + virtual ~TableRowUndo() override; + + virtual void Undo() override; + virtual void Redo() override; + virtual bool Merge( SfxUndoAction *pNextAction ) override; + +private: + struct Data + { + sal_Int32 mnRow; + sal_Int32 mnHeight; + bool mbOptimalHeight; + bool mbIsVisible; + bool mbIsStartOfNewPage; + OUString maName; + }; + + void setData( const Data& rData ); + void getData( Data& rData ); + + TableRowRef mxRow; + Data maUndoData; + Data maRedoData; + bool mbHasRedoData; +}; + +class TableStyleUndo : public SdrUndoAction +{ +public: + explicit TableStyleUndo( const SdrTableObj& rTableObj ); + + virtual void Undo() override; + virtual void Redo() override; + +private: + tools::WeakReference<SdrTableObj> mxObjRef; + + struct Data + { + TableStyleSettings maSettings; + css::uno::Reference< css::container::XIndexAccess > mxTableStyle; + }; + + void setData( const Data& rData ); + void getData( Data& rData ); + + Data maUndoData; + Data maRedoData; + bool mbHasRedoData; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/viewcontactoftableobj.cxx b/svx/source/table/viewcontactoftableobj.cxx new file mode 100644 index 000000000..c550c5ec2 --- /dev/null +++ b/svx/source/table/viewcontactoftableobj.cxx @@ -0,0 +1,547 @@ +/* -*- 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 "viewcontactoftableobj.hxx" +#include <svx/svdotable.hxx> +#include <com/sun/star/table/XTable.hpp> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <sdr/primitive2d/sdrattributecreator.hxx> +#include <sdr/primitive2d/sdrdecompositiontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <sdr/attribute/sdrtextattribute.hxx> +#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx> +#include <editeng/borderline.hxx> +#include <sdr/attribute/sdrfilltextattribute.hxx> +#include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx> +#include <drawinglayer/attribute/sdrlineattribute.hxx> +#include <drawinglayer/attribute/sdrshadowattribute.hxx> +#include <drawinglayer/primitive2d/sdrdecompositiontools2d.hxx> +#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svx/sdr/contact/viewobjectcontactofsdrobj.hxx> +#include <svx/sdr/contact/objectcontact.hxx> +#include <svx/svdpage.hxx> +#include <svx/framelink.hxx> +#include <svx/framelinkarray.hxx> +#include <svx/sdooitm.hxx> +#include <vcl/canvastools.hxx> +#include <o3tl/unit_conversion.hxx> +#include <svx/xfltrit.hxx> + +#include <cell.hxx> +#include "tablelayouter.hxx" + + +using editeng::SvxBorderLine; +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + namespace { + + class SdrCellPrimitive2D : public BufferedDecompositionPrimitive2D + { + private: + basegfx::B2DHomMatrix maTransform; + attribute::SdrFillTextAttribute maSdrFTAttribute; + + protected: + // local decomposition. + virtual void create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& aViewInformation) const override; + + public: + SdrCellPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const attribute::SdrFillTextAttribute& rSdrFTAttribute) + : maTransform(rTransform), + maSdrFTAttribute(rSdrFTAttribute) + { + } + + // data access + const basegfx::B2DHomMatrix& getTransform() const { return maTransform; } + const attribute::SdrFillTextAttribute& getSdrFTAttribute() const { return maSdrFTAttribute; } + + // compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + // provide unique ID + virtual sal_uInt32 getPrimitive2DID() const override; + }; + + } + + void SdrCellPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*aViewInformation*/) const + { + // prepare unit polygon + const basegfx::B2DPolyPolygon aUnitPolyPolygon(basegfx::utils::createUnitPolygon()); + + // add fill + if(!getSdrFTAttribute().getFill().isDefault()) + { + basegfx::B2DPolyPolygon aTransformed(aUnitPolyPolygon); + + aTransformed.transform(getTransform()); + rContainer.push_back( + createPolyPolygonFillPrimitive( + aTransformed, + getSdrFTAttribute().getFill(), + getSdrFTAttribute().getFillFloatTransGradient())); + } + else + { + // if no fill create one for HitTest and BoundRect fallback + rContainer.push_back( + createHiddenGeometryPrimitives2D( + true, + aUnitPolyPolygon, + getTransform())); + } + + // add text + if(!getSdrFTAttribute().getText().isDefault()) + { + rContainer.push_back( + createTextPrimitive( + aUnitPolyPolygon, + getTransform(), + getSdrFTAttribute().getText(), + attribute::SdrLineAttribute(), + true, + false)); + } + } + + bool SdrCellPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const SdrCellPrimitive2D& rCompare = static_cast<const SdrCellPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getSdrFTAttribute() == rCompare.getSdrFTAttribute()); + } + + return false; + } + + // provide unique ID + sal_uInt32 SdrCellPrimitive2D::getPrimitive2DID() const + { + return PRIMITIVE2D_ID_SDRCELLPRIMITIVE2D; + } + +} // end of namespace + +namespace sdr::contact +{ + + namespace { + class ViewObjectContactOfTableObj : public ViewObjectContactOfSdrObj + { + public: + ViewObjectContactOfTableObj(ObjectContact& rObjectContact, ViewContact& rViewContact) + : ViewObjectContactOfSdrObj(rObjectContact, rViewContact) + { + } + + protected: + virtual void createPrimitive2DSequence(DisplayInfo const& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override; + }; + } // namespace + + static svx::frame::Style impGetLineStyle( + const sdr::table::TableLayouter& rLayouter, + sal_Int32 nX, + sal_Int32 nY, + bool bHorizontal, + sal_Int32 nColCount, + sal_Int32 nRowCount, + bool bIsRTL) + { + if(nX >= 0 && nX <= nColCount && nY >= 0 && nY <= nRowCount) + { + const SvxBorderLine* pLine = rLayouter.getBorderLine(nX, nY, bHorizontal); + + if(pLine) + { + // copy line content + SvxBorderLine aLine(*pLine); + + // check for mirroring. This shall always be done when it is + // not a top- or rightmost line + bool bMirror(aLine.isDouble()); + + if(bMirror) + { + if(bHorizontal) + { + // mirror all bottom lines + bMirror = (0 != nY); + } + else + { + // mirror all left lines + bMirror = (bIsRTL ? 0 != nX : nX != nColCount); + } + } + + if(bMirror) + { + aLine.SetMirrorWidths( ); + } + + constexpr double fTwipsToMM( + o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100)); + return svx::frame::Style(&aLine, fTwipsToMM); + } + } + + // no success, copy empty line + return svx::frame::Style(); + } + + static void createPrimitive2DSequenceImpl( + sdr::table::SdrTableObj const& rTableObj, + bool const isTaggedPDF, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) + { + const uno::Reference< css::table::XTable > xTable = rTableObj.getTable(); + + if(xTable.is()) + { + // create primitive representation for table. Cell info goes + // directly to aRetval, Border info to aBorderSequence and added + // later to get the correct overlapping + drawinglayer::primitive2d::Primitive2DContainer aRetval; + drawinglayer::primitive2d::Primitive2DContainer aRetvalForShadow; + const sal_Int32 nRowCount(xTable->getRowCount()); + const sal_Int32 nColCount(xTable->getColumnCount()); + const sal_Int32 nAllCount(nRowCount * nColCount); + SdrPage const*const pPage(rTableObj.getSdrPageFromSdrObject()); + + if(nAllCount) + { + const sdr::table::TableLayouter& rTableLayouter(rTableObj.getTableLayouter()); + const bool bIsRTL(css::text::WritingMode_RL_TB == rTableObj.GetWritingMode()); + sdr::table::CellPos aCellPos; + sdr::table::CellRef xCurrentCell; + basegfx::B2IRectangle aCellArea; + + // create range using the model data directly. This is in SdrTextObj::aRect which i will access using + // GetGeoRect() to not trigger any calculations. It's the unrotated geometry. + const basegfx::B2DRange aObjectRange = vcl::unotools::b2DRectangleFromRectangle(rTableObj.GetGeoRect()); + + // To create the CellBorderPrimitives, use the tooling from svx::frame::Array + // which is capable of creating the needed visualization. Fill it during the + // anyways needed run over the table. + svx::frame::Array aArray; + + // initialize CellBorderArray for primitive creation + aArray.Initialize(nColCount, nRowCount); + + // create single primitives per cell + for(aCellPos.mnRow = 0; aCellPos.mnRow < nRowCount; aCellPos.mnRow++) + { + drawinglayer::primitive2d::Primitive2DContainer row; + // add RowHeight to CellBorderArray for primitive creation + aArray.SetRowHeight(aCellPos.mnRow, rTableLayouter.getRowHeight(aCellPos.mnRow)); + + for(aCellPos.mnCol = 0; aCellPos.mnCol < nColCount; aCellPos.mnCol++) + { + drawinglayer::primitive2d::Primitive2DContainer cell; + // add ColWidth to CellBorderArray for primitive creation, only + // needs to be done in the 1st run + if(0 == aCellPos.mnRow) + { + aArray.SetColWidth(aCellPos.mnCol, rTableLayouter.getColumnWidth(aCellPos.mnCol)); + } + + // access the cell + xCurrentCell.set(dynamic_cast< sdr::table::Cell* >(xTable->getCellByPosition(aCellPos.mnCol, aCellPos.mnRow).get())); + + if(xCurrentCell.is()) + { + // copy styles for current cell to CellBorderArray for primitive creation + aArray.SetCellStyleLeft(aCellPos.mnCol, aCellPos.mnRow, impGetLineStyle(rTableLayouter, aCellPos.mnCol, aCellPos.mnRow, false, nColCount, nRowCount, bIsRTL)); + aArray.SetCellStyleRight(aCellPos.mnCol, aCellPos.mnRow, impGetLineStyle(rTableLayouter, aCellPos.mnCol + 1, aCellPos.mnRow, false, nColCount, nRowCount, bIsRTL)); + aArray.SetCellStyleTop(aCellPos.mnCol, aCellPos.mnRow, impGetLineStyle(rTableLayouter, aCellPos.mnCol, aCellPos.mnRow, true, nColCount, nRowCount, bIsRTL)); + aArray.SetCellStyleBottom(aCellPos.mnCol, aCellPos.mnRow, impGetLineStyle(rTableLayouter, aCellPos.mnCol, aCellPos.mnRow + 1, true, nColCount, nRowCount, bIsRTL)); + + // ignore merged cells (all except the top-left of a merged cell) + if(!xCurrentCell->isMerged()) + { + // check if we are the top-left of a merged cell + const sal_Int32 nXSpan(xCurrentCell->getColumnSpan()); + const sal_Int32 nYSpan(xCurrentCell->getRowSpan()); + + if(nXSpan > 1 || nYSpan > 1) + { + // if merged, set so at CellBorderArray for primitive creation + aArray.SetMergedRange(aCellPos.mnCol, aCellPos.mnRow, aCellPos.mnCol + nXSpan - 1, aCellPos.mnRow + nYSpan - 1); + } + } + } + + if(xCurrentCell.is() && !xCurrentCell->isMerged()) + { + if(rTableLayouter.getCellArea(xCurrentCell, aCellPos, aCellArea)) + { + // create cell transformation matrix + basegfx::B2DHomMatrix aCellMatrix; + aCellMatrix.set(0, 0, static_cast<double>(aCellArea.getWidth())); + aCellMatrix.set(1, 1, static_cast<double>(aCellArea.getHeight())); + aCellMatrix.set(0, 2, static_cast<double>(aCellArea.getMinX()) + aObjectRange.getMinX()); + aCellMatrix.set(1, 2, static_cast<double>(aCellArea.getMinY()) + aObjectRange.getMinY()); + + // handle cell fillings and text + const SfxItemSet& rCellItemSet = xCurrentCell->GetItemSet(); + const sal_uInt32 nTextIndex(nColCount * aCellPos.mnRow + aCellPos.mnCol); + const SdrText* pSdrText = rTableObj.getText(nTextIndex); + drawinglayer::attribute::SdrFillTextAttribute aAttribute; + + if(pSdrText) + { + // #i101508# take cell's local text frame distances into account + const sal_Int32 nLeft(xCurrentCell->GetTextLeftDistance()); + const sal_Int32 nRight(xCurrentCell->GetTextRightDistance()); + const sal_Int32 nUpper(xCurrentCell->GetTextUpperDistance()); + const sal_Int32 nLower(xCurrentCell->GetTextLowerDistance()); + + aAttribute = drawinglayer::primitive2d::createNewSdrFillTextAttribute( + rCellItemSet, + pSdrText, + &nLeft, + &nUpper, + &nRight, + &nLower); + } + else + { + aAttribute = drawinglayer::primitive2d::createNewSdrFillTextAttribute( + rCellItemSet, + pSdrText); + } + + // always create cell primitives for BoundRect and HitTest + { + const drawinglayer::primitive2d::Primitive2DReference xCellReference( + new drawinglayer::primitive2d::SdrCellPrimitive2D( + aCellMatrix, aAttribute)); + cell.append(xCellReference); + } + + // Create cell primitive without text. + aAttribute + = drawinglayer::primitive2d::createNewSdrFillTextAttribute( + rCellItemSet, nullptr); + rtl::Reference pCellReference + = new drawinglayer::primitive2d::SdrCellPrimitive2D( + aCellMatrix, aAttribute); + + sal_uInt16 nTransparence( + rCellItemSet.Get(XATTR_FILLTRANSPARENCE).GetValue()); + if (nTransparence != 0) + { + pCellReference->setTransparenceForShadow(nTransparence); + } + + const drawinglayer::primitive2d::Primitive2DReference + xCellReference(pCellReference); + aRetvalForShadow.append(xCellReference); + } + } + if (isTaggedPDF && pPage) + { + // heuristic: if there's a special formatting on + // first row, assume that it's a header row + auto const eType( + aCellPos.mnRow == 0 && rTableObj.getTableStyleSettings().mbUseFirstRow + ? vcl::PDFWriter::TableHeader + : vcl::PDFWriter::TableData); + cell = drawinglayer::primitive2d::Primitive2DContainer { + new drawinglayer::primitive2d::StructureTagPrimitive2D( + eType, + pPage->IsMasterPage(), + false, + std::move(cell)) }; + } + row.append(cell); + } + + if (isTaggedPDF && pPage) + { + row = drawinglayer::primitive2d::Primitive2DContainer { + new drawinglayer::primitive2d::StructureTagPrimitive2D( + vcl::PDFWriter::TableRow, + pPage->IsMasterPage(), + false, + std::move(row)) }; + } + aRetval.append(row); + } + + // now create all CellBorderPrimitives + drawinglayer::primitive2d::Primitive2DContainer aCellBorderPrimitives(aArray.CreateB2DPrimitiveArray()); + + if(!aCellBorderPrimitives.empty()) + { + // this is already scaled (due to Table in non-uniform coordinates), so + // first transform removing scale + basegfx::B2DHomMatrix aTransform( + basegfx::utils::createScaleB2DHomMatrix( + 1.0 / aObjectRange.getWidth(), + 1.0 / aObjectRange.getHeight())); + + // If RTL, mirror the whole unified table in X and move right. + // This is much easier than taking this into account for the whole + // index calculations + if(bIsRTL) + { + aTransform.scale(-1.0, 1.0); + aTransform.translate(1.0, 0.0); + } + + // create object matrix + const GeoStat& rGeoStat(rTableObj.GetGeoStat()); + const double fShearX(-rGeoStat.mfTanShearAngle); + const double fRotate(rGeoStat.nRotationAngle ? toRadians(36000_deg100 - rGeoStat.nRotationAngle) : 0.0); + const basegfx::B2DHomMatrix aObjectMatrix(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aObjectRange.getWidth(), aObjectRange.getHeight(), fShearX, fRotate, + aObjectRange.getMinX(), aObjectRange.getMinY())); + + // add object matrix to transform. By doing so theoretically + // CellBorders could be also rotated/sheared for the first time ever. + // To completely make that work, the primitives already created in + // aRetval would also have to be based on ObjectMatrix, not only on + // ObjectRange as it currently is. + aTransform *= aObjectMatrix; + + // create a transform primitive with this and embed CellBorders + // and append to retval + aRetval.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + aTransform, + drawinglayer::primitive2d::Primitive2DContainer(aCellBorderPrimitives))); + + // Borders are always the same for shadow as well. + aRetvalForShadow.append(new drawinglayer::primitive2d::TransformPrimitive2D( + aTransform, std::move(aCellBorderPrimitives))); + } + } + + if(!aRetval.empty()) + { + // check and create evtl. shadow for created content + const SfxItemSet& rObjectItemSet = rTableObj.GetMergedItemSet(); + const drawinglayer::attribute::SdrShadowAttribute aNewShadowAttribute( + drawinglayer::primitive2d::createNewSdrShadowAttribute(rObjectItemSet)); + + if(!aNewShadowAttribute.isDefault()) + { + bool bDirectShadow + = rObjectItemSet.Get(SDRATTR_SHADOW, /*bSrchInParent=*/false) + .GetValue(); + if (bDirectShadow) + { + // Shadow as direct formatting: no shadow for text, to be compatible + // with PowerPoint. + basegfx::B2DHomMatrix aMatrix; + aRetval = drawinglayer::primitive2d::createEmbeddedShadowPrimitive( + std::move(aRetval), aNewShadowAttribute, aMatrix, &aRetvalForShadow); + } + else + { + // Shadow as style: shadow for text, to be backwards-compatible. + aRetval = drawinglayer::primitive2d::createEmbeddedShadowPrimitive( + std::move(aRetval), aNewShadowAttribute); + } + } + } + + rVisitor.visit(std::move(aRetval)); + } + else + { + // take unrotated snap rect (direct model data) for position and size + const basegfx::B2DRange aObjectRange = vcl::unotools::b2DRectangleFromRectangle(rTableObj.GetGeoRect()); + + // create object matrix + const GeoStat& rGeoStat(rTableObj.GetGeoStat()); + const double fShearX(-rGeoStat.mfTanShearAngle); + const double fRotate(rGeoStat.nRotationAngle ? toRadians(36000_deg100 - rGeoStat.nRotationAngle) : 0.0); + const basegfx::B2DHomMatrix aObjectMatrix(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aObjectRange.getWidth(), aObjectRange.getHeight(), fShearX, fRotate, + aObjectRange.getMinX(), aObjectRange.getMinY())); + + // created an invisible outline for the cases where no visible content exists + const drawinglayer::primitive2d::Primitive2DReference xReference( + drawinglayer::primitive2d::createHiddenGeometryPrimitives2D( + aObjectMatrix)); + + rVisitor.visit(xReference); + } + } + + void ViewObjectContactOfTableObj::createPrimitive2DSequence( + DisplayInfo const& rDisplayInfo, + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const + { + bool const isTaggedPDF(GetObjectContact().isExportTaggedPDF()); + if (isTaggedPDF) + { + // this will be unbuffered and contain structure tags + const sdr::table::SdrTableObj& rTableObj = + static_cast<const sdr::table::SdrTableObj&>(*GetViewContact().TryToGetSdrObject()); + return createPrimitive2DSequenceImpl(rTableObj, true, rVisitor); + } + else + { + // call it via the base class - this is supposed to be buffered + return sdr::contact::ViewObjectContactOfSdrObj::createPrimitive2DSequence(rDisplayInfo, rVisitor); + } + } + + void ViewContactOfTableObj::createViewIndependentPrimitive2DSequence( + drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const + { + const sdr::table::SdrTableObj& rTableObj = + static_cast<const sdr::table::SdrTableObj&>(GetSdrObject()); + return createPrimitive2DSequenceImpl(rTableObj, false, rVisitor); + } + + ViewObjectContact& ViewContactOfTableObj::CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact) + { + return *new ViewObjectContactOfTableObj(rObjectContact, *this); + } + + ViewContactOfTableObj::ViewContactOfTableObj(sdr::table::SdrTableObj& rTableObj) + : ViewContactOfSdrObj(rTableObj) + { + } + + ViewContactOfTableObj::~ViewContactOfTableObj() + { + } +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svx/source/table/viewcontactoftableobj.hxx b/svx/source/table/viewcontactoftableobj.hxx new file mode 100644 index 000000000..fbdd80538 --- /dev/null +++ b/svx/source/table/viewcontactoftableobj.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/. + * + * 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 . + */ + +#ifndef INCLUDED_SVX_SOURCE_TABLE_VIEWCONTACTOFTABLEOBJ_HXX +#define INCLUDED_SVX_SOURCE_TABLE_VIEWCONTACTOFTABLEOBJ_HXX + +#include <svx/sdr/contact/viewcontactofsdrobj.hxx> +#include <svx/svdotable.hxx> + +namespace sdr::contact + { + class ViewContactOfTableObj : public ViewContactOfSdrObj + { + protected: + // This method is responsible for creating the graphical visualisation data derived ONLY from + // the model data + virtual void createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override; + virtual ViewObjectContact& CreateObjectSpecificViewObjectContact(ObjectContact& rObjectContact) override; + + public: + // basic constructor, used from SdrObject. + explicit ViewContactOfTableObj(sdr::table::SdrTableObj& rTextObj); + virtual ~ViewContactOfTableObj() override; + }; +} // end of namespace sdr::contact + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |