1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sfx2/fcontnr.hxx>
21 #include <sfx2/linkmgr.hxx>
23 #include <vcl/svapp.hxx>
24 #include <vcl/weld.hxx>
25 #include <unotools/charclass.hxx>
26 #include <osl/diagnose.h>
28 #include <arealink.hxx>
30 #include <tablink.hxx>
31 #include <document.hxx>
33 #include <rangenam.hxx>
35 #include <undoblk.hxx>
36 #include <globstr.hrc>
37 #include <scresid.hxx>
38 #include <markdata.hxx>
43 #include <patattr.hxx>
44 #include <docpool.hxx>
46 #include <scabstdlg.hxx>
47 #include <clipparam.hxx>
50 ScAreaLink::ScAreaLink( ScDocShell
* pShell
, OUString aFile
,
51 OUString aFilter
, OUString aOpt
,
52 OUString aArea
, const ScRange
& rDest
,
53 sal_Int32 nRefreshDelaySeconds
) :
54 ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL
,SotClipboardFormatId::SIMPLE_FILE
),
55 ScRefreshTimer ( nRefreshDelaySeconds
),
57 aFileName (std::move(aFile
)),
58 aFilterName (std::move(aFilter
)),
59 aOptions (std::move(aOpt
)),
60 aSourceArea (std::move(aArea
)),
66 SetRefreshHandler( LINK( this, ScAreaLink
, RefreshHdl
) );
67 SetRefreshControl( &m_pDocSh
->GetDocument().GetRefreshTimerControlAddress() );
70 ScAreaLink::~ScAreaLink()
75 void ScAreaLink::Edit(weld::Window
* pParent
, const Link
<SvBaseLink
&,void>& /* rEndEditHdl */ )
77 // use own dialog instead of SvBaseLink::Edit...
78 ScAbstractDialogFactory
* pFact
= ScAbstractDialogFactory::Create();
80 ScopedVclPtr
<AbstractScLinkedAreaDlg
> pDlg(pFact
->CreateScLinkedAreaDlg(pParent
));
81 pDlg
->InitFromOldLink( aFileName
, aFilterName
, aOptions
, aSourceArea
, GetRefreshDelaySeconds() );
82 if ( pDlg
->Execute() == RET_OK
)
84 aOptions
= pDlg
->GetOptions();
85 Refresh( pDlg
->GetURL(), pDlg
->GetFilter(),
86 pDlg
->GetSource(), pDlg
->GetRefreshDelaySeconds() );
88 // copy source data from members (set in Refresh) into link name for dialog
89 OUString aNewLinkName
;
90 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
91 SetName( aNewLinkName
);
95 ::sfx2::SvBaseLink::UpdateResult
ScAreaLink::DataChanged(
96 const OUString
&, const css::uno::Any
& )
98 // Do not do anything at bInCreate so that update can be called to set
99 // the status in the LinkManager without changing the data in the document
104 sfx2::LinkManager
* pLinkManager
=m_pDocSh
->GetDocument().GetLinkManager();
105 if (pLinkManager
!=nullptr)
107 OUString aFile
, aArea
, aFilter
;
108 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile
, &aArea
, &aFilter
);
110 // the file dialog returns the filter name with the application prefix
112 ScDocumentLoader::RemoveAppPrefix( aFilter
);
114 // dialog doesn't set area, so keep old one
120 OUString aNewLinkName
;
121 OUString aTmp
= aFilter
;
122 sfx2::MakeLnkName(aNewLinkName
, nullptr, aFile
, aArea
, &aTmp
);
124 SetName( aNewLinkName
);
127 tools::SvRef
<sfx2::SvBaseLink
> const xThis(this); // keep yourself alive
128 Refresh( aFile
, aFilter
, aArea
, GetRefreshDelaySeconds() );
134 void ScAreaLink::Closed()
138 ScDocument
& rDoc
= m_pDocSh
->GetDocument();
139 bool bUndo (rDoc
.IsUndoEnabled());
140 if (bAddUndo
&& bUndo
)
142 m_pDocSh
->GetUndoManager()->AddUndoAction( std::make_unique
<ScUndoRemoveAreaLink
>( m_pDocSh
,
143 aFileName
, aFilterName
, aOptions
,
144 aSourceArea
, aDestArea
, GetRefreshDelaySeconds() ) );
146 bAddUndo
= false; // only once
149 SCTAB nDestTab
= aDestArea
.aStart
.Tab();
150 rDoc
.SetStreamValid(nDestTab
, false);
152 SvBaseLink::Closed();
155 void ScAreaLink::SetDestArea(const ScRange
& rNew
)
157 aDestArea
= rNew
; // for Undo
160 void ScAreaLink::SetSource(const OUString
& rDoc
, const OUString
& rFlt
, const OUString
& rOpt
,
161 const OUString
& rArea
)
168 // also update link name for dialog
169 OUString aNewLinkName
;
170 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
171 SetName( aNewLinkName
);
174 bool ScAreaLink::IsEqual( std::u16string_view rFile
, std::u16string_view rFilter
, std::u16string_view rOpt
,
175 std::u16string_view rSource
, const ScRange
& rDest
) const
177 return aFileName
== rFile
&& aFilterName
== rFilter
&& aOptions
== rOpt
&&
178 aSourceArea
== rSource
&& aDestArea
.aStart
== rDest
.aStart
;
181 // find a range with name >rAreaName< in >rSrcDoc<, return it in >rRange<
182 bool ScAreaLink::FindExtRange( ScRange
& rRange
, const ScDocument
& rSrcDoc
, const OUString
& rAreaName
)
185 OUString aUpperName
= ScGlobal::getCharClass().uppercase(rAreaName
);
186 ScRangeName
* pNames
= rSrcDoc
.GetRangeName();
187 if (pNames
) // named ranges
189 const ScRangeData
* p
= pNames
->findByUpperName(aUpperName
);
190 if (p
&& p
->IsValidReference(rRange
))
193 if (!bFound
) // database ranges
195 ScDBCollection
* pDBColl
= rSrcDoc
.GetDBCollection();
198 const ScDBData
* pDB
= pDBColl
->getNamedDBs().findByUpperName(aUpperName
);
204 pDB
->GetArea(nTab
,nCol1
,nRow1
,nCol2
,nRow2
);
205 rRange
= ScRange( nCol1
,nRow1
,nTab
, nCol2
,nRow2
,nTab
);
210 if (!bFound
) // direct reference (range or cell)
212 ScAddress::Details
aDetails(rSrcDoc
.GetAddressConvention(), 0, 0);
213 if ( rRange
.ParseAny( rAreaName
, rSrcDoc
, aDetails
) & ScRefFlags::VALID
)
221 bool ScAreaLink::Refresh( const OUString
& rNewFile
, const OUString
& rNewFilter
,
222 const OUString
& rNewArea
, sal_Int32 nNewRefreshDelaySeconds
)
224 // load document - like TabLink
226 if (rNewFile
.isEmpty() || rNewFilter
.isEmpty())
229 if (!m_pDocSh
->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate())
232 OUString
aNewUrl( ScGlobal::GetAbsDocName( rNewFile
, m_pDocSh
) );
233 bool bNewUrlName
= (aNewUrl
!= aFileName
);
235 std::shared_ptr
<const SfxFilter
> pFilter
= m_pDocSh
->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter
);
239 ScDocument
& rDoc
= m_pDocSh
->GetDocument();
241 bool bUndo (rDoc
.IsUndoEnabled());
242 rDoc
.SetInLinkUpdate( true );
244 // if new filter was selected, forget options
245 if ( rNewFilter
!= aFilterName
)
248 SfxMedium
* pMed
= ScDocumentLoader::CreateMedium( aNewUrl
, pFilter
, aOptions
);
250 // aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here
251 rtl::Reference
<ScDocShell
> pSrcShell
= new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT
| SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS
);
252 pSrcShell
->DoLoad(pMed
);
254 ScDocument
& rSrcDoc
= pSrcShell
->GetDocument();
256 // options could have been set
257 OUString aNewOpt
= ScDocumentLoader::GetOptions(*pMed
);
258 if (aNewOpt
.isEmpty())
261 // correct source range name list for web query import
264 if( rNewFilter
== ScDocShell::GetWebQueryFilterName() )
265 aTempArea
= ScFormatFilter::Get().GetHTMLRangeNameList( rSrcDoc
, rNewArea
);
267 aTempArea
= rNewArea
;
269 // find total size of source area
272 ScRangeList aSourceRanges
;
274 if (rNewFilter
== SC_TEXT_CSV_FILTER_NAME
&& aTempArea
== "CSV_all")
276 // The dummy All range. All data, including top/left empty
281 if (rSrcDoc
.GetCellArea( 0, nEndCol
, nEndRow
))
283 aSourceRanges
.push_back( ScRange( 0,0,0, nEndCol
, nEndRow
, 0));
284 nWidth
= nEndCol
+ 1;
285 nHeight
= nEndRow
+ 2;
289 if (!aTempArea
.isEmpty())
295 if( FindExtRange( aTokenRange
, rSrcDoc
, aTempArea
.getToken( 0, ';', nIdx
) ) )
297 aSourceRanges
.push_back( aTokenRange
);
298 // columns: find maximum
299 nWidth
= std::max( nWidth
, static_cast<SCCOL
>(aTokenRange
.aEnd
.Col() - aTokenRange
.aStart
.Col() + 1) );
300 // rows: add row range + 1 empty row
301 nHeight
+= aTokenRange
.aEnd
.Row() - aTokenRange
.aStart
.Row() + 2;
306 // remove the last empty row
310 // delete old data / copy new
312 ScAddress aDestPos
= aDestArea
.aStart
;
313 SCTAB nDestTab
= aDestPos
.Tab();
314 ScRange aOldRange
= aDestArea
;
315 ScRange aNewRange
= aDestArea
; // old range, if file not found or similar
316 if (nWidth
> 0 && nHeight
> 0)
318 aNewRange
.aEnd
.SetCol( aNewRange
.aStart
.Col() + nWidth
- 1 );
319 aNewRange
.aEnd
.SetRow( aNewRange
.aStart
.Row() + nHeight
- 1 );
322 //! check CanFitBlock only if bDoInsert is set?
323 bool bCanDo
= rDoc
.ValidColRow( aNewRange
.aEnd
.Col(), aNewRange
.aEnd
.Row() ) &&
324 rDoc
.CanFitBlock( aOldRange
, aNewRange
);
327 ScDocShellModificator
aModificator( *m_pDocSh
);
329 SCCOL nOldEndX
= aOldRange
.aEnd
.Col();
330 SCROW nOldEndY
= aOldRange
.aEnd
.Row();
331 SCCOL nNewEndX
= aNewRange
.aEnd
.Col();
332 SCROW nNewEndY
= aNewRange
.aEnd
.Row();
333 ScRange
aMaxRange( aDestPos
,
334 ScAddress(std::max(nOldEndX
,nNewEndX
), std::max(nOldEndY
,nNewEndY
), nDestTab
) );
338 ScDocumentUniquePtr pUndoDoc
;
339 if ( bAddUndo
&& bUndo
)
341 pUndoDoc
.reset(new ScDocument( SCDOCMODE_UNDO
));
344 if ( nNewEndX
!= nOldEndX
|| nNewEndY
!= nOldEndY
) // range changed?
346 pUndoDoc
->InitUndo( rDoc
, 0, rDoc
.GetTableCount()-1 );
347 rDoc
.CopyToDocument(0, 0, 0, rDoc
.MaxCol(), rDoc
.MaxRow(), MAXTAB
,
348 InsertDeleteFlags::FORMULA
, false, *pUndoDoc
); // all formulas
351 pUndoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
); // only destination table
352 rDoc
.CopyToDocument(aOldRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pUndoDoc
);
354 else // without insertion
356 pUndoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
); // only destination table
357 rDoc
.CopyToDocument(aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pUndoDoc
);
361 // insert / delete cells
362 // DeleteAreaTab also deletes MERGE_FLAG attributes
365 rDoc
.FitBlock( aOldRange
, aNewRange
); // incl. deletion
367 rDoc
.DeleteAreaTab( aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
);
371 if (nWidth
> 0 && nHeight
> 0)
373 ScDocument
aClipDoc( SCDOCMODE_CLIP
);
374 ScRange
aNewTokenRange( aNewRange
.aStart
);
375 for (size_t nRange
= 0; nRange
< aSourceRanges
.size(); ++nRange
)
377 ScRange
const & rTokenRange( aSourceRanges
[nRange
]);
378 SCTAB nSrcTab
= rTokenRange
.aStart
.Tab();
379 ScMarkData
aSourceMark(rSrcDoc
.GetSheetLimits());
380 aSourceMark
.SelectOneTable( nSrcTab
); // selecting for CopyToClip
381 aSourceMark
.SetMarkArea( rTokenRange
);
383 ScClipParam
aClipParam(rTokenRange
, false);
384 rSrcDoc
.CopyToClip(aClipParam
, &aClipDoc
, &aSourceMark
, false, false);
386 if ( aClipDoc
.HasAttrib( 0,0,nSrcTab
, rDoc
.MaxCol(),rDoc
.MaxRow(),nSrcTab
,
387 HasAttrFlags::Merged
| HasAttrFlags::Overlapped
) )
389 //! ResetAttrib at document !!!
391 ScPatternAttr
aPattern( rSrcDoc
.getCellAttributeHelper() );
392 aPattern
.GetItemSet().Put( ScMergeAttr() ); // Defaults
393 aPattern
.GetItemSet().Put( ScMergeFlagAttr() );
394 aClipDoc
.ApplyPatternAreaTab( 0,0, rDoc
.MaxCol(),rDoc
.MaxRow(), nSrcTab
, aPattern
);
397 aNewTokenRange
.aEnd
.SetCol( aNewTokenRange
.aStart
.Col() + (rTokenRange
.aEnd
.Col() - rTokenRange
.aStart
.Col()) );
398 aNewTokenRange
.aEnd
.SetRow( aNewTokenRange
.aStart
.Row() + (rTokenRange
.aEnd
.Row() - rTokenRange
.aStart
.Row()) );
399 ScMarkData
aDestMark(rDoc
.GetSheetLimits());
400 aDestMark
.SelectOneTable( nDestTab
);
401 aDestMark
.SetMarkArea( aNewTokenRange
);
402 rDoc
.CopyFromClip( aNewTokenRange
, aDestMark
, InsertDeleteFlags::ALL
, nullptr, &aClipDoc
, false );
403 aNewTokenRange
.aStart
.SetRow( aNewTokenRange
.aEnd
.Row() + 2 );
408 OUString aErr
= ScResId(STR_LINKERROR
);
409 rDoc
.SetString( aDestPos
.Col(), aDestPos
.Row(), aDestPos
.Tab(), aErr
);
414 if ( bAddUndo
&& bUndo
)
416 ScDocumentUniquePtr
pRedoDoc(new ScDocument( SCDOCMODE_UNDO
));
417 pRedoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
);
418 rDoc
.CopyToDocument(aNewRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pRedoDoc
);
420 m_pDocSh
->GetUndoManager()->AddUndoAction(
421 std::make_unique
<ScUndoUpdateAreaLink
>( m_pDocSh
,
422 aFileName
, aFilterName
, aOptions
,
423 aSourceArea
, aOldRange
, GetRefreshDelaySeconds(),
424 aNewUrl
, rNewFilter
, aNewOpt
,
425 rNewArea
, aNewRange
, nNewRefreshDelaySeconds
,
426 std::move(pUndoDoc
), std::move(pRedoDoc
), bDoInsert
) );
429 // remember new settings
433 if ( rNewFilter
!= aFilterName
)
434 aFilterName
= rNewFilter
;
435 if ( rNewArea
!= aSourceArea
)
436 aSourceArea
= rNewArea
;
437 if ( aNewOpt
!= aOptions
)
440 if ( aNewRange
!= aDestArea
)
441 aDestArea
= aNewRange
;
443 if ( nNewRefreshDelaySeconds
!= GetRefreshDelaySeconds() )
444 SetRefreshDelay( nNewRefreshDelaySeconds
);
446 SCCOL nPaintEndX
= std::max( aOldRange
.aEnd
.Col(), aNewRange
.aEnd
.Col() );
447 SCROW nPaintEndY
= std::max( aOldRange
.aEnd
.Row(), aNewRange
.aEnd
.Row() );
449 if ( aOldRange
.aEnd
.Col() != aNewRange
.aEnd
.Col() )
450 nPaintEndX
= rDoc
.MaxCol();
451 if ( aOldRange
.aEnd
.Row() != aNewRange
.aEnd
.Row() )
452 nPaintEndY
= rDoc
.MaxRow();
454 if ( !m_pDocSh
->AdjustRowHeight( aDestPos
.Row(), nPaintEndY
, nDestTab
) )
456 ScRange(aDestPos
.Col(), aDestPos
.Row(), nDestTab
, nPaintEndX
, nPaintEndY
, nDestTab
),
457 PaintPartFlags::Grid
);
458 aModificator
.SetDocumentModified();
462 // CanFitBlock sal_False -> Problems with summarized cells or table boundary reached!
463 //! cell protection ???
465 //! Link dialog must set default parent
466 // "cannot insert rows"
467 weld::Window
* pWin
= Application::GetFrameWeld(m_pDocSh
->GetDialogParent());
468 std::unique_ptr
<weld::MessageDialog
> xInfoBox(Application::CreateMessageDialog(pWin
,
469 VclMessageType::Info
, VclButtonsType::Ok
,
470 ScResId(STR_MSSG_DOSUBTOTALS_2
)));
476 pSrcShell
->DoClose();
478 rDoc
.SetInLinkUpdate( false );
482 // notify Uno objects (for XRefreshListener)
483 //! also notify Uno objects if file name was changed!
484 ScLinkRefreshedHint aHint
;
485 aHint
.SetAreaLink( aDestPos
);
486 rDoc
.BroadcastUno( aHint
);
492 IMPL_LINK_NOARG(ScAreaLink
, RefreshHdl
, Timer
*, void)
494 Refresh( aFileName
, aFilterName
, aSourceArea
, GetRefreshDelaySeconds() );
497 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */