office-gobmx/sc/source/ui/docshell/arealink.cxx
Noel Grandin fc1b2a0ab2 tdf#160338 Calc freeze on Text Attribute dialog
This reverts
    commit 76e9023c8a
    Author: Noel Grandin <noelgrandin@gmail.com>
    Date:   Mon Jan 22 08:36:28 2024 +0200
    convert more calc dialogs to async

I fixed some lifetime issues, but even once that was happy,
the Text Attributes dialog was not receiving mouse events
properly, something else is unhappy when we mix async
and modal dialogs.

Change-Id: I4a35d886895c65af2085b606ff5c7a7c02fb5671
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165305
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2024-03-26 17:16:08 +01:00

497 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sfx2/fcontnr.hxx>
#include <sfx2/linkmgr.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>
#include <arealink.hxx>
#include <tablink.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <rangenam.hxx>
#include <dbdata.hxx>
#include <undoblk.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <markdata.hxx>
#include <hints.hxx>
#include <filter.hxx>
#include <attrib.hxx>
#include <patattr.hxx>
#include <docpool.hxx>
#include <scabstdlg.hxx>
#include <clipparam.hxx>
ScAreaLink::ScAreaLink( ScDocShell* pShell, OUString aFile,
OUString aFilter, OUString aOpt,
OUString aArea, const ScRange& rDest,
sal_Int32 nRefreshDelaySeconds ) :
::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE),
ScRefreshTimer ( nRefreshDelaySeconds ),
m_pDocSh(pShell),
aFileName (std::move(aFile)),
aFilterName (std::move(aFilter)),
aOptions (std::move(aOpt)),
aSourceArea (std::move(aArea)),
aDestArea (rDest),
bAddUndo (true),
bInCreate (false),
bDoInsert (true)
{
SetRefreshHandler( LINK( this, ScAreaLink, RefreshHdl ) );
SetRefreshControl( &m_pDocSh->GetDocument().GetRefreshTimerControlAddress() );
}
ScAreaLink::~ScAreaLink()
{
StopRefreshTimer();
}
void ScAreaLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /* rEndEditHdl */ )
{
// use own dialog instead of SvBaseLink::Edit...
ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
ScopedVclPtr<AbstractScLinkedAreaDlg> pDlg(pFact->CreateScLinkedAreaDlg(pParent));
pDlg->InitFromOldLink( aFileName, aFilterName, aOptions, aSourceArea, GetRefreshDelaySeconds() );
if ( pDlg->Execute() == RET_OK )
{
aOptions = pDlg->GetOptions();
Refresh( pDlg->GetURL(), pDlg->GetFilter(),
pDlg->GetSource(), pDlg->GetRefreshDelaySeconds() );
// copy source data from members (set in Refresh) into link name for dialog
OUString aNewLinkName;
sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName );
SetName( aNewLinkName );
}
}
::sfx2::SvBaseLink::UpdateResult ScAreaLink::DataChanged(
const OUString&, const css::uno::Any& )
{
// Do not do anything at bInCreate so that update can be called to set
// the status in the LinkManager without changing the data in the document
if (bInCreate)
return SUCCESS;
sfx2::LinkManager* pLinkManager=m_pDocSh->GetDocument().GetLinkManager();
if (pLinkManager!=nullptr)
{
OUString aFile, aArea, aFilter;
sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, &aArea, &aFilter);
// the file dialog returns the filter name with the application prefix
// -> remove prefix
ScDocumentLoader::RemoveAppPrefix( aFilter );
// dialog doesn't set area, so keep old one
if (aArea.isEmpty())
{
aArea = aSourceArea;
// adjust in dialog:
OUString aNewLinkName;
OUString aTmp = aFilter;
sfx2::MakeLnkName(aNewLinkName, nullptr, aFile, aArea, &aTmp);
aFilter = aTmp;
SetName( aNewLinkName );
}
tools::SvRef<sfx2::SvBaseLink> const xThis(this); // keep yourself alive
Refresh( aFile, aFilter, aArea, GetRefreshDelaySeconds() );
}
return SUCCESS;
}
void ScAreaLink::Closed()
{
// delete link: Undo
ScDocument& rDoc = m_pDocSh->GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
if (bAddUndo && bUndo)
{
m_pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoRemoveAreaLink>( m_pDocSh,
aFileName, aFilterName, aOptions,
aSourceArea, aDestArea, GetRefreshDelaySeconds() ) );
bAddUndo = false; // only once
}
SCTAB nDestTab = aDestArea.aStart.Tab();
rDoc.SetStreamValid(nDestTab, false);
SvBaseLink::Closed();
}
void ScAreaLink::SetDestArea(const ScRange& rNew)
{
aDestArea = rNew; // for Undo
}
void ScAreaLink::SetSource(const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
const OUString& rArea)
{
aFileName = rDoc;
aFilterName = rFlt;
aOptions = rOpt;
aSourceArea = rArea;
// also update link name for dialog
OUString aNewLinkName;
sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName );
SetName( aNewLinkName );
}
bool ScAreaLink::IsEqual( std::u16string_view rFile, std::u16string_view rFilter, std::u16string_view rOpt,
std::u16string_view rSource, const ScRange& rDest ) const
{
return aFileName == rFile && aFilterName == rFilter && aOptions == rOpt &&
aSourceArea == rSource && aDestArea.aStart == rDest.aStart;
}
// find a range with name >rAreaName< in >rSrcDoc<, return it in >rRange<
bool ScAreaLink::FindExtRange( ScRange& rRange, const ScDocument& rSrcDoc, const OUString& rAreaName )
{
bool bFound = false;
OUString aUpperName = ScGlobal::getCharClass().uppercase(rAreaName);
ScRangeName* pNames = rSrcDoc.GetRangeName();
if (pNames) // named ranges
{
const ScRangeData* p = pNames->findByUpperName(aUpperName);
if (p && p->IsValidReference(rRange))
bFound = true;
}
if (!bFound) // database ranges
{
ScDBCollection* pDBColl = rSrcDoc.GetDBCollection();
if (pDBColl)
{
const ScDBData* pDB = pDBColl->getNamedDBs().findByUpperName(aUpperName);
if (pDB)
{
SCTAB nTab;
SCCOL nCol1, nCol2;
SCROW nRow1, nRow2;
pDB->GetArea(nTab,nCol1,nRow1,nCol2,nRow2);
rRange = ScRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab );
bFound = true;
}
}
}
if (!bFound) // direct reference (range or cell)
{
ScAddress::Details aDetails(rSrcDoc.GetAddressConvention(), 0, 0);
if ( rRange.ParseAny( rAreaName, rSrcDoc, aDetails ) & ScRefFlags::VALID )
bFound = true;
}
return bFound;
}
// execute:
bool ScAreaLink::Refresh( const OUString& rNewFile, const OUString& rNewFilter,
const OUString& rNewArea, sal_Int32 nNewRefreshDelaySeconds )
{
// load document - like TabLink
if (rNewFile.isEmpty() || rNewFilter.isEmpty())
return false;
if (!m_pDocSh->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate())
return false;
OUString aNewUrl( ScGlobal::GetAbsDocName( rNewFile, m_pDocSh ) );
bool bNewUrlName = (aNewUrl != aFileName);
std::shared_ptr<const SfxFilter> pFilter = m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter);
if (!pFilter)
return false;
ScDocument& rDoc = m_pDocSh->GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
rDoc.SetInLinkUpdate( true );
// if new filter was selected, forget options
if ( rNewFilter != aFilterName )
aOptions.clear();
SfxMedium* pMed = ScDocumentLoader::CreateMedium( aNewUrl, pFilter, aOptions);
// aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here
rtl::Reference<ScDocShell> pSrcShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS);
pSrcShell->DoLoad(pMed);
ScDocument& rSrcDoc = pSrcShell->GetDocument();
// options could have been set
OUString aNewOpt = ScDocumentLoader::GetOptions(*pMed);
if (aNewOpt.isEmpty())
aNewOpt = aOptions;
// correct source range name list for web query import
OUString aTempArea;
if( rNewFilter == ScDocShell::GetWebQueryFilterName() )
aTempArea = ScFormatFilter::Get().GetHTMLRangeNameList( rSrcDoc, rNewArea );
else
aTempArea = rNewArea;
// find total size of source area
SCCOL nWidth = 0;
SCROW nHeight = 0;
ScRangeList aSourceRanges;
if (rNewFilter == SC_TEXT_CSV_FILTER_NAME && aTempArea == "CSV_all")
{
// The dummy All range. All data, including top/left empty
// rows/columns.
aTempArea.clear();
SCCOL nEndCol = 0;
SCROW nEndRow = 0;
if (rSrcDoc.GetCellArea( 0, nEndCol, nEndRow))
{
aSourceRanges.push_back( ScRange( 0,0,0, nEndCol, nEndRow, 0));
nWidth = nEndCol + 1;
nHeight = nEndRow + 2;
}
}
if (!aTempArea.isEmpty())
{
sal_Int32 nIdx {0};
do
{
ScRange aTokenRange;
if( FindExtRange( aTokenRange, rSrcDoc, aTempArea.getToken( 0, ';', nIdx ) ) )
{
aSourceRanges.push_back( aTokenRange);
// columns: find maximum
nWidth = std::max( nWidth, static_cast<SCCOL>(aTokenRange.aEnd.Col() - aTokenRange.aStart.Col() + 1) );
// rows: add row range + 1 empty row
nHeight += aTokenRange.aEnd.Row() - aTokenRange.aStart.Row() + 2;
}
}
while (nIdx>0);
}
// remove the last empty row
if( nHeight > 0 )
nHeight--;
// delete old data / copy new
ScAddress aDestPos = aDestArea.aStart;
SCTAB nDestTab = aDestPos.Tab();
ScRange aOldRange = aDestArea;
ScRange aNewRange = aDestArea; // old range, if file not found or similar
if (nWidth > 0 && nHeight > 0)
{
aNewRange.aEnd.SetCol( aNewRange.aStart.Col() + nWidth - 1 );
aNewRange.aEnd.SetRow( aNewRange.aStart.Row() + nHeight - 1 );
}
//! check CanFitBlock only if bDoInsert is set?
bool bCanDo = rDoc.ValidColRow( aNewRange.aEnd.Col(), aNewRange.aEnd.Row() ) &&
rDoc.CanFitBlock( aOldRange, aNewRange );
if (bCanDo)
{
ScDocShellModificator aModificator( *m_pDocSh );
SCCOL nOldEndX = aOldRange.aEnd.Col();
SCROW nOldEndY = aOldRange.aEnd.Row();
SCCOL nNewEndX = aNewRange.aEnd.Col();
SCROW nNewEndY = aNewRange.aEnd.Row();
ScRange aMaxRange( aDestPos,
ScAddress(std::max(nOldEndX,nNewEndX), std::max(nOldEndY,nNewEndY), nDestTab) );
// initialise Undo
ScDocumentUniquePtr pUndoDoc;
if ( bAddUndo && bUndo )
{
pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
if ( bDoInsert )
{
if ( nNewEndX != nOldEndX || nNewEndY != nOldEndY ) // range changed?
{
pUndoDoc->InitUndo( rDoc, 0, rDoc.GetTableCount()-1 );
rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB,
InsertDeleteFlags::FORMULA, false, *pUndoDoc); // all formulas
}
else
pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab ); // only destination table
rDoc.CopyToDocument(aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
}
else // without insertion
{
pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab ); // only destination table
rDoc.CopyToDocument(aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
}
}
// insert / delete cells
// DeleteAreaTab also deletes MERGE_FLAG attributes
if (bDoInsert)
rDoc.FitBlock( aOldRange, aNewRange ); // incl. deletion
else
rDoc.DeleteAreaTab( aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
// copy data
if (nWidth > 0 && nHeight > 0)
{
ScDocument aClipDoc( SCDOCMODE_CLIP );
ScRange aNewTokenRange( aNewRange.aStart );
for (size_t nRange = 0; nRange < aSourceRanges.size(); ++nRange)
{
ScRange const & rTokenRange( aSourceRanges[nRange]);
SCTAB nSrcTab = rTokenRange.aStart.Tab();
ScMarkData aSourceMark(rSrcDoc.GetSheetLimits());
aSourceMark.SelectOneTable( nSrcTab ); // selecting for CopyToClip
aSourceMark.SetMarkArea( rTokenRange );
ScClipParam aClipParam(rTokenRange, false);
rSrcDoc.CopyToClip(aClipParam, &aClipDoc, &aSourceMark, false, false);
if ( aClipDoc.HasAttrib( 0,0,nSrcTab, rDoc.MaxCol(),rDoc.MaxRow(),nSrcTab,
HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
{
//! ResetAttrib at document !!!
ScPatternAttr aPattern( rSrcDoc.getCellAttributeHelper() );
aPattern.GetItemSet().Put( ScMergeAttr() ); // Defaults
aPattern.GetItemSet().Put( ScMergeFlagAttr() );
aClipDoc.ApplyPatternAreaTab( 0,0, rDoc.MaxCol(),rDoc.MaxRow(), nSrcTab, aPattern );
}
aNewTokenRange.aEnd.SetCol( aNewTokenRange.aStart.Col() + (rTokenRange.aEnd.Col() - rTokenRange.aStart.Col()) );
aNewTokenRange.aEnd.SetRow( aNewTokenRange.aStart.Row() + (rTokenRange.aEnd.Row() - rTokenRange.aStart.Row()) );
ScMarkData aDestMark(rDoc.GetSheetLimits());
aDestMark.SelectOneTable( nDestTab );
aDestMark.SetMarkArea( aNewTokenRange );
rDoc.CopyFromClip( aNewTokenRange, aDestMark, InsertDeleteFlags::ALL, nullptr, &aClipDoc, false );
aNewTokenRange.aStart.SetRow( aNewTokenRange.aEnd.Row() + 2 );
}
}
else
{
OUString aErr = ScResId(STR_LINKERROR);
rDoc.SetString( aDestPos.Col(), aDestPos.Row(), aDestPos.Tab(), aErr );
}
// enter Undo
if ( bAddUndo && bUndo)
{
ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO ));
pRedoDoc->InitUndo( rDoc, nDestTab, nDestTab );
rDoc.CopyToDocument(aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pRedoDoc);
m_pDocSh->GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoUpdateAreaLink>( m_pDocSh,
aFileName, aFilterName, aOptions,
aSourceArea, aOldRange, GetRefreshDelaySeconds(),
aNewUrl, rNewFilter, aNewOpt,
rNewArea, aNewRange, nNewRefreshDelaySeconds,
std::move(pUndoDoc), std::move(pRedoDoc), bDoInsert ) );
}
// remember new settings
if ( bNewUrlName )
aFileName = aNewUrl;
if ( rNewFilter != aFilterName )
aFilterName = rNewFilter;
if ( rNewArea != aSourceArea )
aSourceArea = rNewArea;
if ( aNewOpt != aOptions )
aOptions = aNewOpt;
if ( aNewRange != aDestArea )
aDestArea = aNewRange;
if ( nNewRefreshDelaySeconds != GetRefreshDelaySeconds() )
SetRefreshDelay( nNewRefreshDelaySeconds );
SCCOL nPaintEndX = std::max( aOldRange.aEnd.Col(), aNewRange.aEnd.Col() );
SCROW nPaintEndY = std::max( aOldRange.aEnd.Row(), aNewRange.aEnd.Row() );
if ( aOldRange.aEnd.Col() != aNewRange.aEnd.Col() )
nPaintEndX = rDoc.MaxCol();
if ( aOldRange.aEnd.Row() != aNewRange.aEnd.Row() )
nPaintEndY = rDoc.MaxRow();
if ( !m_pDocSh->AdjustRowHeight( aDestPos.Row(), nPaintEndY, nDestTab ) )
m_pDocSh->PostPaint(
ScRange(aDestPos.Col(), aDestPos.Row(), nDestTab, nPaintEndX, nPaintEndY, nDestTab),
PaintPartFlags::Grid);
aModificator.SetDocumentModified();
}
else
{
// CanFitBlock sal_False -> Problems with summarized cells or table boundary reached!
//! cell protection ???
//! Link dialog must set default parent
// "cannot insert rows"
weld::Window* pWin = Application::GetFrameWeld(m_pDocSh->GetDialogParent());
std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin,
VclMessageType::Info, VclButtonsType::Ok,
ScResId(STR_MSSG_DOSUBTOTALS_2)));
xInfoBox->run();
}
// clean up
pSrcShell->DoClose();
rDoc.SetInLinkUpdate( false );
if (bCanDo)
{
// notify Uno objects (for XRefreshListener)
//! also notify Uno objects if file name was changed!
ScLinkRefreshedHint aHint;
aHint.SetAreaLink( aDestPos );
rDoc.BroadcastUno( aHint );
}
return bCanDo;
}
IMPL_LINK_NOARG(ScAreaLink, RefreshHdl, Timer *, void)
{
Refresh( aFileName, aFilterName, aSourceArea, GetRefreshDelaySeconds() );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */