/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "viewcontactoftableobj.hxx" #include #include #include #include #include "tablelayouter.hxx" #include "tablehandles.hxx" #include #include #include #include #include #include #include #include #include #include "sdrtableobjimpl.hxx" 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 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 TableProperties::Clone(SdrObject& rObj) const { return std::unique_ptr(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); } 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); } 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 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( mxTable->getCell( nCol, nRow ) ); if( xTargetCell.is() ) xTargetCell->cloneFrom( xOldTable->getCell( rStart.mnCol + nCol, rStart.mnRow + nRow ) ); } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); } } // copy row heights Reference< XTableRows > xNewRows(mxTable->getRows(), css::uno::UNO_SET_THROW ); static constexpr 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 constexpr 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 auto aRectangle = mpTableObj->getRectangle(); LayoutTable(aRectangle, false, false); mpTableObj->setRectangle(aRectangle); } 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 )); auto aRectangle = mpTableObj->getRectangle(); LayoutTable(aRectangle, true, true); mpTableObj->setRectangle(aRectangle); mpTableObj->maLogicRect = aRectangle; } 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( u"table"_ustr ), 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->setRectangle(mpTableObj->maLogicRect); // layout cloned table auto aRectangle = mpTableObj->getRectangle(); LayoutTable(aRectangle, false, false); mpTableObj->setRectangle(aRectangle); // 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 constexpr OUString sSize( u"Size"_ustr ); 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 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(nWidth - nOffset), sal_Int32(0)); Reference 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 ) { if (aEvent.Source == mxTableStyle && mpTableObj) static_cast(mpTableObj->GetProperties()).increaseVersion(); 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->setRectangle(mpTableObj->maLogicRect); auto aRectangle = mpTableObj->getRectangle(); LayoutTable(aRectangle, false, false); mpTableObj->setRectangle(aRectangle); 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 ) { assert(Source.Source == mxTableStyle); (void)Source; Reference xDefaultStyle; try { Reference xSupplier(mpTableObj->getSdrModelFromSdrObject().getUnoModel(), UNO_QUERY_THROW); Reference xTableFamily(xSupplier->getStyleFamilies()->getByName(u"table"_ustr), UNO_QUERY_THROW); xDefaultStyle.set(xTableFamily->getByName(u"default"_ustr), UNO_QUERY_THROW); } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); } mpTableObj->setTableStyle(xDefaultStyle); } CellRef SdrTableObjImpl::getCell( const CellPos& rPos ) const { CellRef xCell; if( mxTable.is() ) try { xCell = mxTable->getCell( rPos.mnCol, rPos.mnRow ); } catch( Exception& ) { TOOLS_WARN_EXCEPTION("svx.table", ""); } return xCell; } sal_Int32 SdrTableObjImpl::getColumnCount() const { return mxTable.is() ? mxTable->getColumnCount() : 0; } std::vector SdrTableObjImpl::getColumnWidths() const { std::vector 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 (comphelper::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 SdrTableObj::CreateObjectSpecificProperties() { return std::make_unique(*this); } // DrawContact section std::unique_ptr SdrTableObj::CreateObjectSpecificViewContact() { return std::make_unique(*this); } SdrTableObj::SdrTableObj(SdrModel& rSdrModel) : SdrTextObj(rSdrModel) { osl_atomic_increment(&m_refCount); // other I get deleted during construction init( 1, 1 ); osl_atomic_decrement(&m_refCount); } SdrTableObj::SdrTableObj(SdrModel& rSdrModel, SdrTableObj const & rSource) : SdrTextObj(rSdrModel, rSource) { osl_atomic_increment(&m_refCount); init( 1, 1 ); TableModelNotifyGuard aGuard( mpImpl.is() ? mpImpl->mxTable.get() : nullptr ); maLogicRect = rSource.maLogicRect; maRectangle = rSource.maRectangle; 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; osl_atomic_decrement(&m_refCount); } SdrTableObj::SdrTableObj( SdrModel& rSdrModel, const ::tools::Rectangle& rNewRect, sal_Int32 nColumns, sal_Int32 nRows) : SdrTextObj(rSdrModel, rNewRect) ,maLogicRect(rNewRect) { osl_atomic_increment(&m_refCount); if( nColumns <= 0 ) nColumns = 1; if( nRows <= 0 ) nRows = 1; init( nColumns, nRows ); osl_atomic_decrement(&m_refCount); } 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() ) { setRectangle(maLogicRect); auto aRectangle = getRectangle(); mpImpl->LayoutTable(aRectangle, false, false); setRectangle(aRectangle); } } SdrTableObj::~SdrTableObj() { mpImpl->dispose(); } // table stuff Reference< XTable > SdrTableObj::getTable() const { return mpImpl->mxTable; } rtl::Reference< TableModel > SdrTableObj::getUnoTable() 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() - getRectangle().Left(); sal_Int32 nY = rPos.Y() - getRectangle().Top(); if( (nX < 0) || (nX > getRectangle().GetWidth()) || (nY < 0) || (nY > getRectangle().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(getRectangle()); setRectangle(maLogicRect); auto aRectangle = getRectangle(); mpImpl->LayoutTable(aRectangle, false, false); setRectangle(aRectangle); SetBoundAndSnapRectsDirty(); ActionChanged(); BroadcastObjectChange(); if (aRect0 != getRectangle()) 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 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 = mpImpl->mxTable->getCell( rPos.mnCol, rPos.mnRow ); if( mpImpl->mxActiveCell.is() && mpImpl->mxActiveCell->isMerged() ) { CellPos aOrigin; findMergeOrigin( mpImpl->mxTable, rPos.mnCol, rPos.mnRow, aOrigin.mnCol, aOrigin.mnRow ); mpImpl->mxActiveCell = mpImpl->mxTable->getCell( aOrigin.mnCol, aOrigin.mnRow ); 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(getRectangle()); 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); } rtl::Reference SdrTableObj::CloneSdrObject(SdrModel& rTargetModel) const { return new SdrTableObj(rTargetModel, *this); } const tools::Rectangle& SdrTableObj::GetSnapRect() const { return getRectangle(); } 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& pAction : mpImpl->maUndos) getSdrModelFromSdrObject().AddUndo( std::move(pAction)); mpImpl->maUndos.clear(); getSdrModelFromSdrObject().AddUndo(getSdrModelFromSdrObject().GetSdrUndoFactory().CreateUndoGeoObject(*this)); } if(rOutl.IsModified()) { std::optional 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 pTextObject, bool bAdjustTextFrameWidthAndHeight ) { 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(); if (bAdjustTextFrameWidthAndHeight) NbcAdjustTextFrameWidthAndHeight(); } void SdrTableObj::NbcSetLogicRect(const tools::Rectangle& rRect, bool /*bAdaptTextMinSize*/) { maLogicRect=rRect; ImpJustifyRect(maLogicRect); const bool bWidth = maLogicRect.getOpenWidth() != getRectangle().getOpenWidth(); const bool bHeight = maLogicRect.getOpenHeight() != getRectangle().getOpenHeight(); setRectangle(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().getOpenHeight() ); SetLogicRect( aAdjustRect ); } void SdrTableObj::NbcMove(const Size& rSiz) { maLogicRect.Move(rSiz); SdrTextObj::NbcMove( rSiz ); if( mpImpl.is() ) mpImpl->UpdateCells(getRectangle()); } void SdrTableObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact) { tools::Rectangle aOldRect( maLogicRect ); ResizeRect(maLogicRect,rRef,xFact,yFact); setRectangle(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(); setRectangle(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(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 aRowEdges(nRowCount + 1); for (auto const & rEdge : mpImpl->mpLayouter->getHorizontalEdges()) { Point aPoint(getRectangle().TopLeft()); aPoint.AdjustY(rEdge.nPosition); std::unique_ptr 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 aColEdges(nColCount + 1); for (auto const & rEdge : mpImpl->mpLayouter->getVerticalEdges()) { Point aPoint(getRectangle().TopLeft()); aPoint.AdjustX(rEdge.nPosition); std::unique_ptr 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); auto aRectangle = getRectangle(); tempList.AddHdl( std::make_unique(aRectangle, !IsTextEditActive() ) ); tempList.AddHdl( std::make_unique(aRectangle.TopLeft(),SdrHdlKind::UpperLeft) ); tempList.AddHdl( std::make_unique(aRectangle.TopCenter(),SdrHdlKind::Upper) ); tempList.AddHdl( std::make_unique(aRectangle.TopRight(),SdrHdlKind::UpperRight) ); tempList.AddHdl( std::make_unique(aRectangle.LeftCenter(),SdrHdlKind::Left) ); tempList.AddHdl( std::make_unique(aRectangle.RightCenter(),SdrHdlKind::Right) ); tempList.AddHdl( std::make_unique(aRectangle.BottomLeft(),SdrHdlKind::LowerLeft) ); tempList.AddHdl( std::make_unique(aRectangle.BottomCenter(),SdrHdlKind::Lower) ); tempList.AddHdl( std::make_unique(aRectangle.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(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 != getRectangle()) { 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.Normalize(); rStat.SetActionRect(aRect1); setRectangle(aRect1); return true; } bool SdrTableObj::MovCreate(SdrDragStat& rStat) { tools::Rectangle aRect1; rStat.TakeCreateRect(aRect1); ImpJustifyRect(aRect1); rStat.SetActionRect(aRect1); setRectangle(aRect1); // for ObjName SetBoundRectDirty(); m_bSnapRectDirty=true; return true; } bool SdrTableObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd) { auto aRectangle = getRectangle(); rStat.TakeCreateRect(aRectangle); ImpJustifyRect(aRectangle); setRectangle(aRectangle); 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.Normalize(); 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 SdrTableObj::NewGeoData() const { return std::make_unique(); } void SdrTableObj::SaveGeoData(SdrObjGeoData& rGeo) const { DBG_ASSERT( dynamic_cast< TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::SaveGeoData(), illegal geo data!" ); SdrTextObj::SaveGeoData (rGeo); static_cast(rGeo).maLogicRect = maLogicRect; } void SdrTableObj::RestoreGeoData(const SdrObjGeoData& rGeo) { DBG_ASSERT( dynamic_cast< const TableObjectGeoData* >( &rGeo ), "svx::SdrTableObj::RestoreGeoData(), illegal geo data!" ); maLogicRect = static_cast(rGeo).maLogicRect; SdrTextObj::RestoreGeoData (rGeo); if( mpImpl.is() ) { auto aRectangle = getRectangle(); mpImpl->LayoutTable(aRectangle, false, false); setRectangle(aRectangle); } ActionChanged(); } void SdrTableObj::CropTableModelToSelection(const CellPos& rStart, const CellPos& rEnd) { if(!mpImpl.is()) { return; } mpImpl->CropTableModelToSelection(rStart, rEnd); } void SdrTableObj::LayoutTableHeight(tools::Rectangle& rArea) { if( mpImpl.is() && mpImpl->mpLayouter) { mpImpl->mpLayouter->LayoutTableHeight(rArea, /*bFit*/false); } } 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() ); auto aRectangle = getRectangle(); mpImpl->mpLayouter->DistributeColumns(aRectangle, nFirstColumn, nLastColumn, bOptimize, bMinimize); setRectangle(aRectangle); } } 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() ); auto aRectangle = getRectangle(); mpImpl->mpLayouter->DistributeRows(aRectangle, nFirstRow, nLastRow, bOptimize, bMinimize); setRectangle(aRectangle); } } void SdrTableObj::SetChanged() { if( mpImpl.is() ) { auto aRectangle = getRectangle(); mpImpl->LayoutTable(aRectangle, false, false); setRectangle(aRectangle); } ::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: */