/* -*- 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 #include #include #include #include "docshimp.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Redraw - Notifications void ScDocShell::PostEditView( ScEditEngineDefaulter* pEditEngine, const ScAddress& rCursorPos ) { // Broadcast( ScEditViewHint( pEditEngine, rCursorPos ) ); // Test: only active ViewShell ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); if (pViewSh && pViewSh->GetViewData().GetDocShell() == this) { ScEditViewHint aHint( pEditEngine, rCursorPos ); pViewSh->Notify( *this, aHint ); } } void ScDocShell::PostDataChanged() { Broadcast( SfxHint( SfxHintId::ScDataChanged ) ); SfxGetpApp()->Broadcast(SfxHint( SfxHintId::ScAnyDataChanged )); // Navigator m_pDocument->PrepareFormulaCalc(); //! notify navigator directly! } void ScDocShell::PostPaint( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, PaintPartFlags nPart, sal_uInt16 nExtFlags, tools::Long nMaxWidthAffectedHint ) { ScRange aRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); PostPaint(aRange, nPart, nExtFlags, nMaxWidthAffectedHint); } void ScDocShell::PostPaint( const ScRangeList& rRanges, PaintPartFlags nPart, sal_uInt16 nExtFlags, tools::Long nMaxWidthAffectedHint ) { ScRangeList aPaintRanges; std::set aTabsInvalidated; const SCTAB nMaxTab = m_pDocument->GetTableCount() - 1; for (size_t i = 0, n = rRanges.size(); i < n; ++i) { const ScRange& rRange = rRanges[i]; SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = std::min(nMaxTab, rRange.aEnd.Tab()); if (nTab1 < 0 || nTab2 < 0) continue; if (!m_pDocument->ValidCol(nCol1)) { nMaxWidthAffectedHint = -1; // Hint no longer valid nCol1 = m_pDocument->MaxCol(); } if (!m_pDocument->ValidRow(nRow1)) nRow1 = m_pDocument->MaxRow(); if (!m_pDocument->ValidCol(nCol2)) { nMaxWidthAffectedHint = -1; // Hint no longer valid nCol2 = m_pDocument->MaxCol(); } if (!m_pDocument->ValidRow(nRow2)) nRow2 = m_pDocument->MaxRow(); if ( m_pPaintLockData ) { // #i54081# PaintPartFlags::Extras still has to be broadcast because it changes the // current sheet if it's invalid. All other flags added to pPaintLockData. PaintPartFlags nLockPart = nPart & ~PaintPartFlags::Extras; if ( nLockPart != PaintPartFlags::NONE ) { //! nExtFlags ??? m_pPaintLockData->AddRange( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ), nLockPart ); } nPart &= PaintPartFlags::Extras; // for broadcasting if (nPart == PaintPartFlags::NONE) continue; } if (nExtFlags & SC_PF_LINES) // respect space for lines { //! check for hidden columns/rows! if (nCol1 > 0) { nMaxWidthAffectedHint = -1; // Hint no longer valid --nCol1; } if (nCol2 < m_pDocument->MaxCol()) { nMaxWidthAffectedHint = -1; // Hint no longer valid ++nCol2; } if (nRow1>0) --nRow1; if (nRow2MaxRow()) ++nRow2; } // expand for the merged ones if (nExtFlags & SC_PF_TESTMERGE) m_pDocument->ExtendMerge( nCol1, nRow1, nCol2, nRow2, nTab1 ); if ( nCol1 != 0 || nCol2 != m_pDocument->MaxCol() ) { // Extend to whole rows if SC_PF_WHOLEROWS is set, or rotated or non-left // aligned cells are contained (see UpdatePaintExt). // Special handling for RTL text (#i9731#) is unnecessary now with full // support of right-aligned text. if ( ( nExtFlags & SC_PF_WHOLEROWS ) || m_pDocument->HasAttrib( nCol1,nRow1,nTab1, m_pDocument->MaxCol(),nRow2,nTab2, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) ) { nCol1 = 0; nCol2 = m_pDocument->MaxCol(); nMaxWidthAffectedHint = -1; // Hint no longer valid } } aPaintRanges.push_back(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); for (auto nTabNum = nTab1; nTabNum <= nTab2; ++nTabNum) aTabsInvalidated.insert(nTabNum); } Broadcast(ScPaintHint(aPaintRanges.Combine(), nPart, nMaxWidthAffectedHint)); // LOK: we are supposed to update the row / columns headers (and actually // the document size too - cell size affects that, obviously) if ((nPart & (PaintPartFlags::Top | PaintPartFlags::Left)) && comphelper::LibreOfficeKit::isActive()) { ScModelObj* pModel = GetModel(); for (auto nTab : aTabsInvalidated) SfxLokHelper::notifyPartSizeChangedAllViews(pModel, nTab); } } void ScDocShell::PostPaintGridAll() { PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Grid ); } void ScDocShell::PostPaintCell( SCCOL nCol, SCROW nRow, SCTAB nTab, tools::Long nMaxWidthAffectedHint ) { PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, SC_PF_TESTMERGE, nMaxWidthAffectedHint ); } void ScDocShell::PostPaintCell( const ScAddress& rPos, tools::Long nMaxWidthAffectedHint ) { PostPaintCell( rPos.Col(), rPos.Row(), rPos.Tab(), nMaxWidthAffectedHint ); } void ScDocShell::PostPaintExtras() { PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Extras ); } void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, const ScRange& rRange ) { if ( ( rExtFlags & SC_PF_LINES ) == 0 && m_pDocument->HasAttrib( rRange, HasAttrFlags::Lines | HasAttrFlags::Shadow | HasAttrFlags::Conditional ) ) { // If the range contains lines, shadow or conditional formats, // set SC_PF_LINES to include one extra cell in all directions. rExtFlags |= SC_PF_LINES; } if ( ( rExtFlags & SC_PF_WHOLEROWS ) == 0 && ( rRange.aStart.Col() != 0 || rRange.aEnd.Col() != m_pDocument->MaxCol() ) && m_pDocument->HasAttrib( rRange, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) ) { // If the range contains (logically) right- or center-aligned cells, // or rotated cells, set SC_PF_WHOLEROWS to paint the whole rows. // This test isn't needed after the cell changes, because it's also // tested in PostPaint. UpdatePaintExt may later be changed to do this // only if called before the changes. rExtFlags |= SC_PF_WHOLEROWS; } } void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab ) { UpdatePaintExt( rExtFlags, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ) ); } void ScDocShell::LockPaint_Impl(bool bDoc) { if ( !m_pPaintLockData ) m_pPaintLockData.reset( new ScPaintLockData ); m_pPaintLockData->IncLevel(bDoc); } void ScDocShell::UnlockPaint_Impl(bool bDoc) { if ( m_pPaintLockData ) { if ( m_pPaintLockData->GetLevel(bDoc) ) m_pPaintLockData->DecLevel(bDoc); if (!m_pPaintLockData->GetLevel(!bDoc) && !m_pPaintLockData->GetLevel(bDoc)) { // Execute Paint now // don't continue collecting std::unique_ptr pPaint = std::move(m_pPaintLockData); ScRangeListRef xRangeList = pPaint->GetRangeList(); if ( xRangeList.is() ) { PaintPartFlags nParts = pPaint->GetParts(); for ( size_t i = 0, nCount = xRangeList->size(); i < nCount; i++ ) { //! nExtFlags ??? ScRange const & rRange = (*xRangeList)[i]; PostPaint( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), nParts ); } } if ( pPaint->GetModified() ) SetDocumentModified(); } } else { OSL_FAIL("UnlockPaint without LockPaint"); } } void ScDocShell::LockDocument_Impl(sal_uInt16 nNew) { if (!m_nDocumentLock) { ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); if (pDrawLayer) pDrawLayer->setLock(true); } m_nDocumentLock = nNew; } void ScDocShell::UnlockDocument_Impl(sal_uInt16 nNew) { m_nDocumentLock = nNew; if (!m_nDocumentLock) { ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); if (pDrawLayer) pDrawLayer->setLock(false); } } void ScDocShell::SetLockCount(sal_uInt16 nNew) { if (nNew) // set { if ( !m_pPaintLockData ) m_pPaintLockData.reset( new ScPaintLockData ); m_pPaintLockData->SetDocLevel(nNew-1); LockDocument_Impl(nNew); } else if (m_pPaintLockData) // delete { m_pPaintLockData->SetDocLevel(0); // at unlock, execute immediately UnlockPaint_Impl(true); // now UnlockDocument_Impl(0); } } void ScDocShell::LockPaint() { LockPaint_Impl(false); } void ScDocShell::UnlockPaint() { UnlockPaint_Impl(false); } void ScDocShell::LockDocument() { LockPaint_Impl(true); LockDocument_Impl(m_nDocumentLock + 1); } void ScDocShell::UnlockDocument() { if (m_nDocumentLock) { UnlockPaint_Impl(true); UnlockDocument_Impl(m_nDocumentLock - 1); } else { OSL_FAIL("UnlockDocument without LockDocument"); } } void ScDocShell::SetInplace( bool bInplace ) { if (m_bIsInplace != bInplace) { m_bIsInplace = bInplace; CalcOutputFactor(); } } void ScDocShell::CalcOutputFactor() { if (m_bIsInplace) { m_nPrtToScreenFactor = 1.0; // otherwise it does not match the inactive display return; } bool bTextWysiwyg = SC_MOD()->GetInputOptions().GetTextWysiwyg(); if (bTextWysiwyg) { m_nPrtToScreenFactor = 1.0; return; } OUString aTestString( u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789"_ustr); tools::Long nPrinterWidth = 0; const ScPatternAttr& rPattern(m_pDocument->getCellAttributeHelper().getDefaultCellAttribute()); vcl::Font aDefFont; OutputDevice* pRefDev = GetRefDevice(); MapMode aOldMode = pRefDev->GetMapMode(); vcl::Font aOldFont = pRefDev->GetFont(); pRefDev->SetMapMode(MapMode(MapUnit::MapPixel)); rPattern.fillFontOnly(aDefFont, pRefDev); // font color doesn't matter here pRefDev->SetFont(aDefFont); nPrinterWidth = pRefDev->PixelToLogic(Size(pRefDev->GetTextWidth(aTestString), 0), MapMode(MapUnit::Map100thMM)).Width(); pRefDev->SetFont(aOldFont); pRefDev->SetMapMode(aOldMode); ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *Application::GetDefaultDevice() ); pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel)); rPattern.fillFontOnly(aDefFont, pVirtWindow); // font color doesn't matter here pVirtWindow->SetFont(aDefFont); double nWindowWidth = pVirtWindow->GetTextWidth(aTestString) / ScGlobal::nScreenPPTX; nWindowWidth = o3tl::convert(nWindowWidth, o3tl::Length::twip, o3tl::Length::mm100); if (nPrinterWidth && nWindowWidth) m_nPrtToScreenFactor = nPrinterWidth / nWindowWidth; else { OSL_FAIL("GetTextSize returns 0 ??"); m_nPrtToScreenFactor = 1.0; } } void ScDocShell::InitOptions(bool bForLoading) // called from InitNew and Load { // Settings from the SpellCheckCfg get into Doc- and ViewOptions LanguageType nDefLang, nCjkLang, nCtlLang; ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang ); ScModule* pScMod = SC_MOD(); ScDocOptions aDocOpt = pScMod->GetDocOptions(); ScFormulaOptions aFormulaOpt = pScMod->GetFormulaOptions(); ScViewOptions aViewOpt = pScMod->GetViewOptions(); if (!comphelper::IsFuzzing()) { // two-digit year entry from Tools->Options->General aDocOpt.SetYear2000(officecfg::Office::Common::DateFormat::TwoDigitYear::get()); } if (bForLoading) { // #i112123# No style:decimal-places attribute means automatic decimals, not the configured default, // so it must not be taken from the global options. // Calculation settings are handled separately in ScXMLBodyContext::EndElement. aDocOpt.SetStdPrecision( SvNumberFormatter::UNLIMITED_PRECISION ); // fdo#78294 The default null-date if // // is absent is 1899-12-30 regardless what the configuration is set to. // Import filters may override this value. aDocOpt.SetDate( 30, 12, 1899); } m_pDocument->SetDocOptions( aDocOpt ); m_pDocument->SetViewOptions( aViewOpt ); SetFormulaOptions( aFormulaOpt, bForLoading ); // print options are now set directly before the printing m_pDocument->SetLanguage( nDefLang, nCjkLang, nCtlLang ); } Printer* ScDocShell::GetDocumentPrinter() // for OLE { return m_pDocument->GetPrinter(); } SfxPrinter* ScDocShell::GetPrinter(bool bCreateIfNotExist) { return m_pDocument->GetPrinter(bCreateIfNotExist); } void ScDocShell::UpdateFontList() { // pImpl->pFontList = new FontList( GetPrinter(), Application::GetDefaultDevice() ); m_pImpl->pFontList.reset(new FontList(GetRefDevice(), nullptr)); SvxFontListItem aFontListItem( m_pImpl->pFontList.get(), SID_ATTR_CHAR_FONTLIST ); PutItem( aFontListItem ); CalcOutputFactor(); } OutputDevice* ScDocShell::GetRefDevice() { return m_pDocument->GetRefDevice(); } sal_uInt16 ScDocShell::SetPrinter( VclPtr const & pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) { SfxPrinter *pOld = m_pDocument->GetPrinter( false ); if ( pOld && pOld->IsPrinting() ) return SFX_PRINTERROR_BUSY; if (nDiffFlags & SfxPrinterChangeFlags::PRINTER) { if ( m_pDocument->GetPrinter() != pNewPrinter ) { m_pDocument->SetPrinter( pNewPrinter ); m_pDocument->SetPrintOptions(); // MT: Use UpdateFontList: Will use Printer fonts only if needed! /* delete pImpl->pFontList; pImpl->pFontList = new FontList( pNewPrinter, Application::GetDefaultDevice() ); SvxFontListItem aFontListItem( pImpl->pFontList, SID_ATTR_CHAR_FONTLIST ); PutItem( aFontListItem ); CalcOutputFactor(); */ if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() ) UpdateFontList(); ScModule* pScMod = SC_MOD(); SfxViewFrame *pFrame = SfxViewFrame::GetFirst( this ); while (pFrame) { SfxViewShell* pSh = pFrame->GetViewShell(); if (ScTabViewShell* pViewSh = dynamic_cast(pSh)) { ScInputHandler* pInputHdl = pScMod->GetInputHdl(pViewSh); if (pInputHdl) pInputHdl->UpdateRefDevice(); } pFrame = SfxViewFrame::GetNext( *pFrame, this ); } } } else if (nDiffFlags & SfxPrinterChangeFlags::JOBSETUP) { SfxPrinter* pOldPrinter = m_pDocument->GetPrinter(); if (pOldPrinter) { pOldPrinter->SetJobSetup( pNewPrinter->GetJobSetup() ); // #i6706# Call SetPrinter with the old printer again, so the drawing layer // RefDevice is set (calling ReformatAllTextObjects and rebuilding charts), // because the JobSetup (printer device settings) may affect text layout. m_pDocument->SetPrinter( pOldPrinter ); CalcOutputFactor(); // also with the new settings } } if (nDiffFlags & SfxPrinterChangeFlags::OPTIONS) { m_pDocument->SetPrintOptions(); //! from new printer ??? } if (nDiffFlags & (SfxPrinterChangeFlags::CHG_ORIENTATION | SfxPrinterChangeFlags::CHG_SIZE)) { OUString aStyle = m_pDocument->GetPageStyle( GetCurTab() ); ScStyleSheetPool* pStPl = m_pDocument->GetStyleSheetPool(); SfxStyleSheet* pStyleSheet = static_cast(pStPl->Find(aStyle, SfxStyleFamily::Page)); if (pStyleSheet) { SfxItemSet& rSet = pStyleSheet->GetItemSet(); if (nDiffFlags & SfxPrinterChangeFlags::CHG_ORIENTATION) { const SvxPageItem& rOldItem = rSet.Get(ATTR_PAGE); bool bWasLand = rOldItem.IsLandscape(); bool bNewLand = ( pNewPrinter->GetOrientation() == Orientation::Landscape ); if (bNewLand != bWasLand) { SvxPageItem aNewItem( rOldItem ); aNewItem.SetLandscape( bNewLand ); rSet.Put( aNewItem ); // flip size Size aOldSize = rSet.Get(ATTR_PAGE_SIZE).GetSize(); // coverity[swapped_arguments : FALSE] - this is in the correct order Size aNewSize(aOldSize.Height(),aOldSize.Width()); SvxSizeItem aNewSItem(ATTR_PAGE_SIZE,aNewSize); rSet.Put( aNewSItem ); } } if (nDiffFlags & SfxPrinterChangeFlags::CHG_SIZE) { SvxSizeItem aPaperSizeItem( ATTR_PAGE_SIZE, SvxPaperInfo::GetPaperSize(pNewPrinter) ); rSet.Put( aPaperSizeItem ); } } } PostPaint(0,0,0,m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB,PaintPartFlags::All); return 0; } ScChangeAction* ScDocShell::GetChangeAction( const ScAddress& rPos ) { ScChangeTrack* pTrack = GetDocument().GetChangeTrack(); if (!pTrack) return nullptr; SCTAB nTab = rPos.Tab(); const ScChangeAction* pFound = nullptr; const ScChangeAction* pAction = pTrack->GetFirst(); while (pAction) { ScChangeActionType eType = pAction->GetType(); //! ScViewUtil::IsActionShown( *pAction, *pSettings, *pDoc )... if ( pAction->IsVisible() && eType != SC_CAT_DELETE_TABS ) { const ScBigRange& rBig = pAction->GetBigRange(); if ( rBig.aStart.Tab() == nTab ) { ScRange aRange = rBig.MakeRange( GetDocument() ); if ( eType == SC_CAT_DELETE_ROWS ) aRange.aEnd.SetRow( aRange.aStart.Row() ); else if ( eType == SC_CAT_DELETE_COLS ) aRange.aEnd.SetCol( aRange.aStart.Col() ); if ( aRange.Contains( rPos ) ) { pFound = pAction; // the last one wins } } if ( pAction->GetType() == SC_CAT_MOVE ) { ScRange aRange = static_cast(pAction)-> GetFromRange().MakeRange( GetDocument() ); if ( aRange.Contains( rPos ) ) { pFound = pAction; } } } pAction = pAction->GetNext(); } return const_cast(pFound); } void ScDocShell::SetChangeComment( ScChangeAction* pAction, const OUString& rComment ) { if (!pAction) return; pAction->SetComment( rComment ); //! Undo ??? SetDocumentModified(); // Dialog-Notify ScChangeTrack* pTrack = GetDocument().GetChangeTrack(); if (pTrack) { sal_uLong nNumber = pAction->GetActionNumber(); pTrack->NotifyModified( ScChangeTrackMsgType::Change, nNumber, nNumber ); } } void ScDocShell::ExecuteChangeCommentDialog( ScChangeAction* pAction, weld::Window* pParent, bool bPrevNext) { if (!pAction) return; // without action is nothing... OUString aComment = pAction->GetComment(); OUString aAuthor = pAction->GetUser(); DateTime aDT = pAction->GetDateTime(); OUString aDate = ScGlobal::getLocaleData().getDate( aDT ) + " " + ScGlobal::getLocaleData().getTime( aDT, false ); SfxItemSetFixed aSet( GetPool() ); aSet.Put( SvxPostItTextItem ( aComment, SID_ATTR_POSTIT_TEXT ) ); aSet.Put( SvxPostItAuthorItem( aAuthor, SID_ATTR_POSTIT_AUTHOR ) ); aSet.Put( SvxPostItDateItem ( aDate, SID_ATTR_POSTIT_DATE ) ); std::unique_ptr pDlg(new ScRedComDialog( pParent, aSet,this,pAction,bPrevNext)); pDlg->Execute(); } void ScDocShell::CompareDocument( ScDocument& rOtherDoc ) { ScChangeTrack* pTrack = m_pDocument->GetChangeTrack(); if ( pTrack && pTrack->GetFirst() ) { //! there are changes -> inquiry if needs to be deleted } m_pDocument->EndChangeTracking(); m_pDocument->StartChangeTracking(); OUString aOldUser; pTrack = m_pDocument->GetChangeTrack(); if ( pTrack ) { aOldUser = pTrack->GetUser(); // check if comparing to same document OUString aThisFile; const SfxMedium* pThisMed = GetMedium(); if (pThisMed) aThisFile = pThisMed->GetName(); OUString aOtherFile; ScDocShell* pOtherSh = rOtherDoc.GetDocumentShell(); if (pOtherSh) { const SfxMedium* pOtherMed = pOtherSh->GetMedium(); if (pOtherMed) aOtherFile = pOtherMed->GetName(); } bool bSameDoc = ( aThisFile == aOtherFile && !aThisFile.isEmpty() ); if ( !bSameDoc ) { // create change actions from comparing with the name of the user // who last saved the document // (only if comparing different documents) using namespace ::com::sun::star; uno::Reference xDocProps( GetModel()->getDocumentProperties()); OSL_ENSURE(xDocProps.is(), "no DocumentProperties"); OUString aDocUser = xDocProps->getModifiedBy(); if ( !aDocUser.isEmpty() ) pTrack->SetUser( aDocUser ); } } m_pDocument->CompareDocument( rOtherDoc ); pTrack = m_pDocument->GetChangeTrack(); if ( pTrack ) pTrack->SetUser( aOldUser ); PostPaintGridAll(); SetDocumentModified(); } // Merge (combine documents) static bool lcl_Equal( const ScChangeAction* pA, const ScChangeAction* pB, bool bIgnore100Sec ) { return pA && pB && pA->GetActionNumber() == pB->GetActionNumber() && pA->GetType() == pB->GetType() && pA->GetUser() == pB->GetUser() && (bIgnore100Sec ? pA->GetDateTimeUTC().IsEqualIgnoreNanoSec( pB->GetDateTimeUTC() ) : pA->GetDateTimeUTC() == pB->GetDateTimeUTC()); // don't compare state if an old change has been accepted } static bool lcl_FindAction( ScDocument& rDoc, const ScChangeAction* pAction, ScDocument& rSearchDoc, const ScChangeAction* pFirstSearchAction, const ScChangeAction* pLastSearchAction, bool bIgnore100Sec ) { if ( !pAction || !pFirstSearchAction || !pLastSearchAction ) { return false; } sal_uLong nLastSearchAction = pLastSearchAction->GetActionNumber(); const ScChangeAction* pA = pFirstSearchAction; while ( pA && pA->GetActionNumber() <= nLastSearchAction ) { if ( pAction->GetType() == pA->GetType() && pAction->GetUser() == pA->GetUser() && (bIgnore100Sec ? pAction->GetDateTimeUTC().IsEqualIgnoreNanoSec( pA->GetDateTimeUTC() ) : pAction->GetDateTimeUTC() == pA->GetDateTimeUTC() ) && pAction->GetBigRange() == pA->GetBigRange() ) { OUString aActionDesc = pAction->GetDescription(rDoc, true); OUString aADesc = pA->GetDescription(rSearchDoc, true); if (aActionDesc == aADesc) { OSL_FAIL( "lcl_FindAction(): found equal action!" ); return true; } } pA = pA->GetNext(); } return false; } void ScDocShell::MergeDocument( ScDocument& rOtherDoc, bool bShared, bool bCheckDuplicates, sal_uLong nOffset, ScChangeActionMergeMap* pMergeMap, bool bInverseMap ) { ScTabViewShell* pViewSh = GetBestViewShell( false ); //! functions to the DocShell if (!pViewSh) return; ScChangeTrack* pSourceTrack = rOtherDoc.GetChangeTrack(); if (!pSourceTrack) return; //! nothing to do - error notification? ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack(); if ( !pThisTrack ) { // turn on m_pDocument->StartChangeTracking(); pThisTrack = m_pDocument->GetChangeTrack(); OSL_ENSURE(pThisTrack,"ChangeTracking not enabled?"); if ( !bShared ) { // turn on visual RedLining ScChangeViewSettings aChangeViewSet; aChangeViewSet.SetShowChanges(true); m_pDocument->SetChangeViewSettings(aChangeViewSet); } } // include Nano seconds in compare? bool bIgnore100Sec = !pSourceTrack->IsTimeNanoSeconds() || !pThisTrack->IsTimeNanoSeconds(); // find common initial position sal_uLong nFirstNewNumber = 0; const ScChangeAction* pSourceAction = pSourceTrack->GetFirst(); const ScChangeAction* pThisAction = pThisTrack->GetFirst(); // skip identical actions while ( lcl_Equal( pSourceAction, pThisAction, bIgnore100Sec ) ) { nFirstNewNumber = pSourceAction->GetActionNumber() + 1; pSourceAction = pSourceAction->GetNext(); pThisAction = pThisAction->GetNext(); } // pSourceAction and pThisAction now point to the first "own" actions // The common actions before don't interest at all //! Inquiry if the documents where equal before the change tracking !!! const ScChangeAction* pFirstMergeAction = pSourceAction; const ScChangeAction* pFirstSearchAction = pThisAction; // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong const ScChangeAction* pLastSearchAction = pThisTrack->GetLast(); // Create MergeChangeData from the following actions sal_uLong nNewActionCount = 0; const ScChangeAction* pCount = pSourceAction; while ( pCount ) { if ( bShared || !ScChangeTrack::MergeIgnore( *pCount, nFirstNewNumber ) ) ++nNewActionCount; pCount = pCount->GetNext(); } if (!nNewActionCount) return; //! nothing to do - error notification? // from here on no return ScProgress aProgress( this, u"..."_ustr, nNewActionCount, true ); sal_uLong nLastMergeAction = pSourceTrack->GetLast()->GetActionNumber(); // UpdateReference-Undo, valid references for the last common state pSourceTrack->MergePrepare( pFirstMergeAction, bShared ); // adjust MergeChangeData to all yet following actions in this document // -> references valid for this document while ( pThisAction ) { // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly if ( !bShared || !ScChangeTrack::MergeIgnore( *pThisAction, nFirstNewNumber ) ) { ScChangeActionType eType = pThisAction->GetType(); switch ( eType ) { case SC_CAT_INSERT_COLS : case SC_CAT_INSERT_ROWS : case SC_CAT_INSERT_TABS : pSourceTrack->AppendInsert( pThisAction->GetBigRange().MakeRange( GetDocument() ) ); break; case SC_CAT_DELETE_COLS : case SC_CAT_DELETE_ROWS : case SC_CAT_DELETE_TABS : { const ScChangeActionDel* pDel = static_cast(pThisAction); if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() ) { // deleted table contains deleted cols, which are not sal_uLong nStart, nEnd; pSourceTrack->AppendDeleteRange( pDel->GetOverAllRange().MakeRange( GetDocument() ), nullptr, nStart, nEnd ); } } break; case SC_CAT_MOVE : { const ScChangeActionMove* pMove = static_cast(pThisAction); pSourceTrack->AppendMove( pMove->GetFromRange().MakeRange( GetDocument() ), pMove->GetBigRange().MakeRange( GetDocument() ), nullptr ); } break; default: { // added to avoid warnings } } } pThisAction = pThisAction->GetNext(); } LockPaint(); // #i73877# no repainting after each action // take over MergeChangeData into the current document bool bHasRejected = false; OUString aOldUser = pThisTrack->GetUser(); pThisTrack->SetUseFixDateTime( true ); ScMarkData& rMarkData = pViewSh->GetViewData().GetMarkData(); ScMarkData aOldMarkData( rMarkData ); pSourceAction = pFirstMergeAction; while ( pSourceAction && pSourceAction->GetActionNumber() <= nLastMergeAction ) { bool bMergeAction = false; if ( bShared ) { if ( !bCheckDuplicates || !lcl_FindAction( rOtherDoc, pSourceAction, *m_pDocument, pFirstSearchAction, pLastSearchAction, bIgnore100Sec ) ) { bMergeAction = true; } } else { if ( !ScChangeTrack::MergeIgnore( *pSourceAction, nFirstNewNumber ) ) { bMergeAction = true; } } if ( bMergeAction ) { ScChangeActionType eSourceType = pSourceAction->GetType(); if ( !bShared && pSourceAction->IsDeletedIn() ) { //! does it need to be determined yet if really deleted in //! _this_ document? // lies in a range, which was deleted in this document // -> is omitted //! ??? revert deletion action ??? //! ??? save action somewhere else ??? #if OSL_DEBUG_LEVEL > 0 OUString aValue; if ( eSourceType == SC_CAT_CONTENT ) aValue = static_cast(pSourceAction)->GetNewString( m_pDocument.get() ); SAL_WARN( "sc", aValue << " omitted"); #endif } else { //! Take over date/author/comment of the source action! pThisTrack->SetUser( pSourceAction->GetUser() ); pThisTrack->SetFixDateTimeUTC( pSourceAction->GetDateTimeUTC() ); sal_uLong nOldActionMax = pThisTrack->GetActionMax(); bool bExecute = true; sal_uLong nReject = pSourceAction->GetRejectAction(); if ( nReject ) { if ( bShared ) { if ( nReject >= nFirstNewNumber ) { nReject += nOffset; } ScChangeAction* pOldAction = pThisTrack->GetAction( nReject ); if ( pOldAction && pOldAction->IsVirgin() ) { pThisTrack->Reject( pOldAction ); bHasRejected = true; bExecute = false; } } else { // decline old action (of the common ones) ScChangeAction* pOldAction = pThisTrack->GetAction( nReject ); if (pOldAction && pOldAction->GetState() == SC_CAS_VIRGIN) { //! what happens at actions, which were accepted in this document??? //! error notification or what??? //! or execute reject change normally pThisTrack->Reject(pOldAction); bHasRejected = true; // for Paint } bExecute = false; } } if ( bExecute ) { // execute normally ScRange aSourceRange = pSourceAction->GetBigRange().MakeRange( GetDocument() ); rMarkData.SelectOneTable( aSourceRange.aStart.Tab() ); switch ( eSourceType ) { case SC_CAT_CONTENT: { //! Test if it was at the very bottom in the document, then automatic //! row insert ??? OSL_ENSURE( aSourceRange.aStart == aSourceRange.aEnd, "huch?" ); ScAddress aPos = aSourceRange.aStart; OUString aValue = static_cast(pSourceAction)->GetNewString( m_pDocument.get() ); ScMatrixMode eMatrix = ScMatrixMode::NONE; const ScCellValue& rCell = static_cast(pSourceAction)->GetNewCell(); if (rCell.getType() == CELLTYPE_FORMULA) eMatrix = rCell.getFormula()->GetMatrixFlag(); switch ( eMatrix ) { case ScMatrixMode::NONE : pViewSh->EnterData( aPos.Col(), aPos.Row(), aPos.Tab(), aValue ); break; case ScMatrixMode::Formula : { SCCOL nCols; SCROW nRows; rCell.getFormula()->GetMatColsRows(nCols, nRows); aSourceRange.aEnd.SetCol( aPos.Col() + nCols - 1 ); aSourceRange.aEnd.SetRow( aPos.Row() + nRows - 1 ); aValue = aValue.copy(1, aValue.getLength()-2); // remove the 1st and last characters. GetDocFunc().EnterMatrix( aSourceRange, nullptr, nullptr, aValue, false, false, OUString(), formula::FormulaGrammar::GRAM_DEFAULT ); } break; case ScMatrixMode::Reference : // do nothing break; } } break; case SC_CAT_INSERT_TABS : { OUString aName; m_pDocument->CreateValidTabName( aName ); (void)GetDocFunc().InsertTable( aSourceRange.aStart.Tab(), aName, true, false ); } break; case SC_CAT_INSERT_ROWS: (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSROWS_BEFORE, true, false ); break; case SC_CAT_INSERT_COLS: (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSCOLS_BEFORE, true, false ); break; case SC_CAT_DELETE_TABS : (void)GetDocFunc().DeleteTable( aSourceRange.aStart.Tab(), true ); break; case SC_CAT_DELETE_ROWS: { const ScChangeActionDel* pDel = static_cast(pSourceAction); if ( pDel->IsTopDelete() ) { aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() ); (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Rows, false ); // #i101099# [Collaboration] Changes are not correctly shown if ( bShared ) { ScChangeAction* pAct = pThisTrack->GetLast(); if ( pAct && pAct->GetType() == eSourceType && pAct->IsDeletedIn() && !pSourceAction->IsDeletedIn() ) { pAct->RemoveAllDeletedIn(); } } } } break; case SC_CAT_DELETE_COLS: { const ScChangeActionDel* pDel = static_cast(pSourceAction); if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() ) { // deleted table contains deleted cols, which are not aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() ); (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Cols, false ); } } break; case SC_CAT_MOVE : { const ScChangeActionMove* pMove = static_cast(pSourceAction); ScRange aFromRange( pMove->GetFromRange().MakeRange( GetDocument() ) ); (void)GetDocFunc().MoveBlock( aFromRange, aSourceRange.aStart, true, true, false, false ); } break; default: { // added to avoid warnings } } } const OUString& rComment = pSourceAction->GetComment(); if ( !rComment.isEmpty() ) { ScChangeAction* pAct = pThisTrack->GetLast(); if ( pAct && pAct->GetActionNumber() > nOldActionMax ) pAct->SetComment( rComment ); else OSL_FAIL( "MergeDocument: what to do with the comment?!?" ); } // adjust references pSourceTrack->MergeOwn( const_cast(pSourceAction), nFirstNewNumber, bShared ); // merge action state if ( bShared && !pSourceAction->IsRejected() ) { ScChangeAction* pAct = pThisTrack->GetLast(); if ( pAct && pAct->GetActionNumber() > nOldActionMax ) { ScChangeTrack::MergeActionState( pAct, pSourceAction ); } } // fill merge map if ( bShared && pMergeMap ) { ScChangeAction* pAct = pThisTrack->GetLast(); if ( pAct && pAct->GetActionNumber() > nOldActionMax ) { sal_uLong nActionMax = pAct->GetActionNumber(); sal_uLong nActionCount = nActionMax - nOldActionMax; sal_uLong nAction = nActionMax - nActionCount + 1; sal_uLong nSourceAction = pSourceAction->GetActionNumber() - nActionCount + 1; while ( nAction <= nActionMax ) { if ( bInverseMap ) { (*pMergeMap)[ nAction++ ] = nSourceAction++; } else { (*pMergeMap)[ nSourceAction++ ] = nAction++; } } } } } aProgress.SetStateCountDown( --nNewActionCount ); } pSourceAction = pSourceAction->GetNext(); } rMarkData = std::move(aOldMarkData); pThisTrack->SetUser(aOldUser); pThisTrack->SetUseFixDateTime( false ); pSourceTrack->Clear(); //! this one is bungled now if (bHasRejected) PostPaintGridAll(); // Reject() doesn't paint itself UnlockPaint(); } bool ScDocShell::MergeSharedDocument( ScDocShell* pSharedDocShell ) { if ( !pSharedDocShell ) { return false; } ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack(); if ( !pThisTrack ) { return false; } ScDocument& rSharedDoc = pSharedDocShell->GetDocument(); ScChangeTrack* pSharedTrack = rSharedDoc.GetChangeTrack(); if ( !pSharedTrack ) { return false; } // reset show changes ScChangeViewSettings aChangeViewSet; aChangeViewSet.SetShowChanges( false ); m_pDocument->SetChangeViewSettings( aChangeViewSet ); // find first merge action in this document bool bIgnore100Sec = !pThisTrack->IsTimeNanoSeconds() || !pSharedTrack->IsTimeNanoSeconds(); ScChangeAction* pThisAction = pThisTrack->GetFirst(); ScChangeAction* pSharedAction = pSharedTrack->GetFirst(); while ( lcl_Equal( pThisAction, pSharedAction, bIgnore100Sec ) ) { pThisAction = pThisAction->GetNext(); pSharedAction = pSharedAction->GetNext(); } if ( pSharedAction ) { if ( pThisAction ) { // merge own changes into shared document sal_uLong nActStartShared = pSharedAction->GetActionNumber(); sal_uLong nActEndShared = pSharedTrack->GetActionMax(); std::optional pTmpDoc(std::in_place); for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex ) { OUString sTabName; pTmpDoc->CreateValidTabName( sTabName ); pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName ); } m_pDocument->GetChangeTrack()->Clone( &*pTmpDoc ); ScChangeActionMergeMap aOwnInverseMergeMap; pSharedDocShell->MergeDocument( *pTmpDoc, true, true, 0, &aOwnInverseMergeMap, true ); pTmpDoc.reset(); sal_uLong nActStartOwn = nActEndShared + 1; sal_uLong nActEndOwn = pSharedTrack->GetActionMax(); // find conflicts ScConflictsList aConflictsList; ScConflictsFinder aFinder( pSharedTrack, nActStartShared, nActEndShared, nActStartOwn, nActEndOwn, aConflictsList ); if ( aFinder.Find() ) { ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnInverseMergeMap ); bool bLoop = true; while ( bLoop ) { bLoop = false; weld::Window* pWin = GetActiveDialogParent(); ScConflictsDlg aDlg(pWin, GetViewData(), &rSharedDoc, aConflictsList); if (aDlg.run() == RET_CANCEL) { std::unique_ptr xQueryBox(Application::CreateMessageDialog(pWin, VclMessageType::Question, VclButtonsType::YesNo, ScResId(STR_DOC_WILLNOTBESAVED))); xQueryBox->set_default_response(RET_YES); if (xQueryBox->run() == RET_YES) { return false; } else { bLoop = true; } } } } // undo own changes in shared document pSharedTrack->Undo( nActStartOwn, nActEndOwn ); // clone change track for merging into own document pTmpDoc.emplace(); for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex ) { OUString sTabName; pTmpDoc->CreateValidTabName( sTabName ); pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName ); } pThisTrack->Clone( &*pTmpDoc ); // undo own changes since last save in own document sal_uLong nStartShared = pThisAction->GetActionNumber(); ScChangeAction* pAction = pThisTrack->GetLast(); while ( pAction && pAction->GetActionNumber() >= nStartShared ) { pThisTrack->Reject( pAction, true ); pAction = pAction->GetPrev(); } // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong pThisTrack->Undo( nStartShared, pThisTrack->GetActionMax(), true ); // merge shared changes into own document ScChangeActionMergeMap aSharedMergeMap; MergeDocument( rSharedDoc, true, true, 0, &aSharedMergeMap ); sal_uLong nEndShared = pThisTrack->GetActionMax(); // resolve conflicts for shared non-content actions if ( !aConflictsList.empty() ) { ScConflictsListHelper::TransformConflictsList( aConflictsList, &aSharedMergeMap, nullptr ); ScConflictsResolver aResolver( pThisTrack, aConflictsList ); pAction = pThisTrack->GetAction( nEndShared ); while ( pAction && pAction->GetActionNumber() >= nStartShared ) { aResolver.HandleAction( pAction, true /*bIsSharedAction*/, false /*bHandleContentAction*/, true /*bHandleNonContentAction*/ ); pAction = pAction->GetPrev(); } } nEndShared = pThisTrack->GetActionMax(); // only show changes from shared document aChangeViewSet.SetShowChanges( true ); aChangeViewSet.SetShowAccepted( true ); aChangeViewSet.SetHasActionRange(); aChangeViewSet.SetTheActionRange( nStartShared, nEndShared ); m_pDocument->SetChangeViewSettings( aChangeViewSet ); // merge own changes back into own document sal_uLong nStartOwn = nEndShared + 1; ScChangeActionMergeMap aOwnMergeMap; MergeDocument( *pTmpDoc, true, true, nEndShared - nStartShared + 1, &aOwnMergeMap ); pTmpDoc.reset(); sal_uLong nEndOwn = pThisTrack->GetActionMax(); // resolve conflicts for shared content actions and own actions if ( !aConflictsList.empty() ) { ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnMergeMap ); ScConflictsResolver aResolver( pThisTrack, aConflictsList ); pAction = pThisTrack->GetAction( nEndShared ); while ( pAction && pAction->GetActionNumber() >= nStartShared ) { aResolver.HandleAction( pAction, true /*bIsSharedAction*/, true /*bHandleContentAction*/, false /*bHandleNonContentAction*/ ); pAction = pAction->GetPrev(); } pAction = pThisTrack->GetAction( nEndOwn ); while ( pAction && pAction->GetActionNumber() >= nStartOwn ) { aResolver.HandleAction( pAction, false /*bIsSharedAction*/, true /*bHandleContentAction*/, true /*bHandleNonContentAction*/ ); pAction = pAction->GetPrev(); } } } else { // merge shared changes into own document sal_uLong nStartShared = pThisTrack->GetActionMax() + 1; MergeDocument( rSharedDoc, true, true ); sal_uLong nEndShared = pThisTrack->GetActionMax(); // only show changes from shared document aChangeViewSet.SetShowChanges( true ); aChangeViewSet.SetShowAccepted( true ); aChangeViewSet.SetHasActionRange(); aChangeViewSet.SetTheActionRange( nStartShared, nEndShared ); m_pDocument->SetChangeViewSettings( aChangeViewSet ); } // update view PostPaintExtras(); PostPaintGridAll(); std::unique_ptr xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(), VclMessageType::Info, VclButtonsType::Ok, ScResId(STR_DOC_UPDATED))); xInfoBox->run(); } return ( pThisAction != nullptr ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */