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( SfxObjectShell
* 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
),
56 m_pDocSh(static_cast<ScDocShell
*>(pShell
)),
57 aFileName (std::move(aFile
)),
58 aFilterName (std::move(aFilter
)),
59 aOptions (std::move(aOpt
)),
60 aSourceArea (std::move(aArea
)),
66 OSL_ENSURE(dynamic_cast< const ScDocShell
*>( pShell
) != nullptr, "ScAreaLink with wrong ObjectShell");
67 SetRefreshHandler( LINK( this, ScAreaLink
, RefreshHdl
) );
68 SetRefreshControl( &m_pDocSh
->GetDocument().GetRefreshTimerControlAddress() );
71 ScAreaLink::~ScAreaLink()
76 void ScAreaLink::Edit(weld::Window
* pParent
, const Link
<SvBaseLink
&,void>& /* rEndEditHdl */ )
78 // use own dialog instead of SvBaseLink::Edit...
79 ScAbstractDialogFactory
* pFact
= ScAbstractDialogFactory::Create();
81 ScopedVclPtr
<AbstractScLinkedAreaDlg
> pDlg(pFact
->CreateScLinkedAreaDlg(pParent
));
82 pDlg
->InitFromOldLink( aFileName
, aFilterName
, aOptions
, aSourceArea
, GetRefreshDelaySeconds() );
83 if ( pDlg
->Execute() == RET_OK
)
85 aOptions
= pDlg
->GetOptions();
86 Refresh( pDlg
->GetURL(), pDlg
->GetFilter(),
87 pDlg
->GetSource(), pDlg
->GetRefreshDelaySeconds() );
89 // copy source data from members (set in Refresh) into link name for dialog
90 OUString aNewLinkName
;
91 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
92 SetName( aNewLinkName
);
96 ::sfx2::SvBaseLink::UpdateResult
ScAreaLink::DataChanged(
97 const OUString
&, const css::uno::Any
& )
99 // Do not do anything at bInCreate so that update can be called to set
100 // the status in the LinkManager without changing the data in the document
105 sfx2::LinkManager
* pLinkManager
=m_pDocSh
->GetDocument().GetLinkManager();
106 if (pLinkManager
!=nullptr)
108 OUString aFile
, aArea
, aFilter
;
109 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile
, &aArea
, &aFilter
);
111 // the file dialog returns the filter name with the application prefix
113 ScDocumentLoader::RemoveAppPrefix( aFilter
);
115 // dialog doesn't set area, so keep old one
121 OUString aNewLinkName
;
122 OUString aTmp
= aFilter
;
123 sfx2::MakeLnkName(aNewLinkName
, nullptr, aFile
, aArea
, &aTmp
);
125 SetName( aNewLinkName
);
128 tools::SvRef
<sfx2::SvBaseLink
> const xThis(this); // keep yourself alive
129 Refresh( aFile
, aFilter
, aArea
, GetRefreshDelaySeconds() );
135 void ScAreaLink::Closed()
139 ScDocument
& rDoc
= m_pDocSh
->GetDocument();
140 bool bUndo (rDoc
.IsUndoEnabled());
141 if (bAddUndo
&& bUndo
)
143 m_pDocSh
->GetUndoManager()->AddUndoAction( std::make_unique
<ScUndoRemoveAreaLink
>( m_pDocSh
,
144 aFileName
, aFilterName
, aOptions
,
145 aSourceArea
, aDestArea
, GetRefreshDelaySeconds() ) );
147 bAddUndo
= false; // only once
150 SCTAB nDestTab
= aDestArea
.aStart
.Tab();
151 rDoc
.SetStreamValid(nDestTab
, false);
153 SvBaseLink::Closed();
156 void ScAreaLink::SetDestArea(const ScRange
& rNew
)
158 aDestArea
= rNew
; // for Undo
161 void ScAreaLink::SetSource(const OUString
& rDoc
, const OUString
& rFlt
, const OUString
& rOpt
,
162 const OUString
& rArea
)
169 // also update link name for dialog
170 OUString aNewLinkName
;
171 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
172 SetName( aNewLinkName
);
175 bool ScAreaLink::IsEqual( std::u16string_view rFile
, std::u16string_view rFilter
, std::u16string_view rOpt
,
176 std::u16string_view rSource
, const ScRange
& rDest
) const
178 return aFileName
== rFile
&& aFilterName
== rFilter
&& aOptions
== rOpt
&&
179 aSourceArea
== rSource
&& aDestArea
.aStart
== rDest
.aStart
;
182 // find a range with name >rAreaName< in >rSrcDoc<, return it in >rRange<
183 bool ScAreaLink::FindExtRange( ScRange
& rRange
, const ScDocument
& rSrcDoc
, const OUString
& rAreaName
)
186 OUString aUpperName
= ScGlobal::getCharClass().uppercase(rAreaName
);
187 ScRangeName
* pNames
= rSrcDoc
.GetRangeName();
188 if (pNames
) // named ranges
190 const ScRangeData
* p
= pNames
->findByUpperName(aUpperName
);
191 if (p
&& p
->IsValidReference(rRange
))
194 if (!bFound
) // database ranges
196 ScDBCollection
* pDBColl
= rSrcDoc
.GetDBCollection();
199 const ScDBData
* pDB
= pDBColl
->getNamedDBs().findByUpperName(aUpperName
);
205 pDB
->GetArea(nTab
,nCol1
,nRow1
,nCol2
,nRow2
);
206 rRange
= ScRange( nCol1
,nRow1
,nTab
, nCol2
,nRow2
,nTab
);
211 if (!bFound
) // direct reference (range or cell)
213 ScAddress::Details
aDetails(rSrcDoc
.GetAddressConvention(), 0, 0);
214 if ( rRange
.ParseAny( rAreaName
, rSrcDoc
, aDetails
) & ScRefFlags::VALID
)
222 bool ScAreaLink::Refresh( const OUString
& rNewFile
, const OUString
& rNewFilter
,
223 const OUString
& rNewArea
, sal_Int32 nNewRefreshDelaySeconds
)
225 // load document - like TabLink
227 if (rNewFile
.isEmpty() || rNewFilter
.isEmpty())
230 if (!m_pDocSh
->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate())
233 OUString
aNewUrl( ScGlobal::GetAbsDocName( rNewFile
, m_pDocSh
) );
234 bool bNewUrlName
= (aNewUrl
!= aFileName
);
236 std::shared_ptr
<const SfxFilter
> pFilter
= m_pDocSh
->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter
);
240 ScDocument
& rDoc
= m_pDocSh
->GetDocument();
242 bool bUndo (rDoc
.IsUndoEnabled());
243 rDoc
.SetInLinkUpdate( true );
245 // if new filter was selected, forget options
246 if ( rNewFilter
!= aFilterName
)
249 SfxMedium
* pMed
= ScDocumentLoader::CreateMedium( aNewUrl
, pFilter
, aOptions
);
251 // aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here
252 ScDocShell
* pSrcShell
= new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT
| SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS
);
253 SfxObjectShellLock aRef
= pSrcShell
;
254 pSrcShell
->DoLoad(pMed
);
256 ScDocument
& rSrcDoc
= pSrcShell
->GetDocument();
258 // options could have been set
259 OUString aNewOpt
= ScDocumentLoader::GetOptions(*pMed
);
260 if (aNewOpt
.isEmpty())
263 // correct source range name list for web query import
266 if( rNewFilter
== ScDocShell::GetWebQueryFilterName() )
267 aTempArea
= ScFormatFilter::Get().GetHTMLRangeNameList( rSrcDoc
, rNewArea
);
269 aTempArea
= rNewArea
;
271 // find total size of source area
274 ScRangeList aSourceRanges
;
276 if (rNewFilter
== SC_TEXT_CSV_FILTER_NAME
&& aTempArea
== "CSV_all")
278 // The dummy All range. All data, including top/left empty
283 if (rSrcDoc
.GetCellArea( 0, nEndCol
, nEndRow
))
285 aSourceRanges
.push_back( ScRange( 0,0,0, nEndCol
, nEndRow
, 0));
286 nWidth
= nEndCol
+ 1;
287 nHeight
= nEndRow
+ 2;
291 if (!aTempArea
.isEmpty())
297 if( FindExtRange( aTokenRange
, rSrcDoc
, aTempArea
.getToken( 0, ';', nIdx
) ) )
299 aSourceRanges
.push_back( aTokenRange
);
300 // columns: find maximum
301 nWidth
= std::max( nWidth
, static_cast<SCCOL
>(aTokenRange
.aEnd
.Col() - aTokenRange
.aStart
.Col() + 1) );
302 // rows: add row range + 1 empty row
303 nHeight
+= aTokenRange
.aEnd
.Row() - aTokenRange
.aStart
.Row() + 2;
308 // remove the last empty row
312 // delete old data / copy new
314 ScAddress aDestPos
= aDestArea
.aStart
;
315 SCTAB nDestTab
= aDestPos
.Tab();
316 ScRange aOldRange
= aDestArea
;
317 ScRange aNewRange
= aDestArea
; // old range, if file not found or similar
318 if (nWidth
> 0 && nHeight
> 0)
320 aNewRange
.aEnd
.SetCol( aNewRange
.aStart
.Col() + nWidth
- 1 );
321 aNewRange
.aEnd
.SetRow( aNewRange
.aStart
.Row() + nHeight
- 1 );
324 //! check CanFitBlock only if bDoInsert is set?
325 bool bCanDo
= rDoc
.ValidColRow( aNewRange
.aEnd
.Col(), aNewRange
.aEnd
.Row() ) &&
326 rDoc
.CanFitBlock( aOldRange
, aNewRange
);
329 ScDocShellModificator
aModificator( *m_pDocSh
);
331 SCCOL nOldEndX
= aOldRange
.aEnd
.Col();
332 SCROW nOldEndY
= aOldRange
.aEnd
.Row();
333 SCCOL nNewEndX
= aNewRange
.aEnd
.Col();
334 SCROW nNewEndY
= aNewRange
.aEnd
.Row();
335 ScRange
aMaxRange( aDestPos
,
336 ScAddress(std::max(nOldEndX
,nNewEndX
), std::max(nOldEndY
,nNewEndY
), nDestTab
) );
340 ScDocumentUniquePtr pUndoDoc
;
341 if ( bAddUndo
&& bUndo
)
343 pUndoDoc
.reset(new ScDocument( SCDOCMODE_UNDO
));
346 if ( nNewEndX
!= nOldEndX
|| nNewEndY
!= nOldEndY
) // range changed?
348 pUndoDoc
->InitUndo( rDoc
, 0, rDoc
.GetTableCount()-1 );
349 rDoc
.CopyToDocument(0, 0, 0, rDoc
.MaxCol(), rDoc
.MaxRow(), MAXTAB
,
350 InsertDeleteFlags::FORMULA
, false, *pUndoDoc
); // all formulas
353 pUndoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
); // only destination table
354 rDoc
.CopyToDocument(aOldRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pUndoDoc
);
356 else // without insertion
358 pUndoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
); // only destination table
359 rDoc
.CopyToDocument(aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pUndoDoc
);
363 // insert / delete cells
364 // DeleteAreaTab also deletes MERGE_FLAG attributes
367 rDoc
.FitBlock( aOldRange
, aNewRange
); // incl. deletion
369 rDoc
.DeleteAreaTab( aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
);
373 if (nWidth
> 0 && nHeight
> 0)
375 ScDocument
aClipDoc( SCDOCMODE_CLIP
);
376 ScRange
aNewTokenRange( aNewRange
.aStart
);
377 for (size_t nRange
= 0; nRange
< aSourceRanges
.size(); ++nRange
)
379 ScRange
const & rTokenRange( aSourceRanges
[nRange
]);
380 SCTAB nSrcTab
= rTokenRange
.aStart
.Tab();
381 ScMarkData
aSourceMark(rSrcDoc
.GetSheetLimits());
382 aSourceMark
.SelectOneTable( nSrcTab
); // selecting for CopyToClip
383 aSourceMark
.SetMarkArea( rTokenRange
);
385 ScClipParam
aClipParam(rTokenRange
, false);
386 rSrcDoc
.CopyToClip(aClipParam
, &aClipDoc
, &aSourceMark
, false, false);
388 if ( aClipDoc
.HasAttrib( 0,0,nSrcTab
, rDoc
.MaxCol(),rDoc
.MaxRow(),nSrcTab
,
389 HasAttrFlags::Merged
| HasAttrFlags::Overlapped
) )
391 //! ResetAttrib at document !!!
393 ScPatternAttr
aPattern( rSrcDoc
.GetPool() );
394 aPattern
.GetItemSet().Put( ScMergeAttr() ); // Defaults
395 aPattern
.GetItemSet().Put( ScMergeFlagAttr() );
396 aClipDoc
.ApplyPatternAreaTab( 0,0, rDoc
.MaxCol(),rDoc
.MaxRow(), nSrcTab
, aPattern
);
399 aNewTokenRange
.aEnd
.SetCol( aNewTokenRange
.aStart
.Col() + (rTokenRange
.aEnd
.Col() - rTokenRange
.aStart
.Col()) );
400 aNewTokenRange
.aEnd
.SetRow( aNewTokenRange
.aStart
.Row() + (rTokenRange
.aEnd
.Row() - rTokenRange
.aStart
.Row()) );
401 ScMarkData
aDestMark(rDoc
.GetSheetLimits());
402 aDestMark
.SelectOneTable( nDestTab
);
403 aDestMark
.SetMarkArea( aNewTokenRange
);
404 rDoc
.CopyFromClip( aNewTokenRange
, aDestMark
, InsertDeleteFlags::ALL
, nullptr, &aClipDoc
, false );
405 aNewTokenRange
.aStart
.SetRow( aNewTokenRange
.aEnd
.Row() + 2 );
410 OUString aErr
= ScResId(STR_LINKERROR
);
411 rDoc
.SetString( aDestPos
.Col(), aDestPos
.Row(), aDestPos
.Tab(), aErr
);
416 if ( bAddUndo
&& bUndo
)
418 ScDocumentUniquePtr
pRedoDoc(new ScDocument( SCDOCMODE_UNDO
));
419 pRedoDoc
->InitUndo( rDoc
, nDestTab
, nDestTab
);
420 rDoc
.CopyToDocument(aNewRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, *pRedoDoc
);
422 m_pDocSh
->GetUndoManager()->AddUndoAction(
423 std::make_unique
<ScUndoUpdateAreaLink
>( m_pDocSh
,
424 aFileName
, aFilterName
, aOptions
,
425 aSourceArea
, aOldRange
, GetRefreshDelaySeconds(),
426 aNewUrl
, rNewFilter
, aNewOpt
,
427 rNewArea
, aNewRange
, nNewRefreshDelaySeconds
,
428 std::move(pUndoDoc
), std::move(pRedoDoc
), bDoInsert
) );
431 // remember new settings
435 if ( rNewFilter
!= aFilterName
)
436 aFilterName
= rNewFilter
;
437 if ( rNewArea
!= aSourceArea
)
438 aSourceArea
= rNewArea
;
439 if ( aNewOpt
!= aOptions
)
442 if ( aNewRange
!= aDestArea
)
443 aDestArea
= aNewRange
;
445 if ( nNewRefreshDelaySeconds
!= GetRefreshDelaySeconds() )
446 SetRefreshDelay( nNewRefreshDelaySeconds
);
448 SCCOL nPaintEndX
= std::max( aOldRange
.aEnd
.Col(), aNewRange
.aEnd
.Col() );
449 SCROW nPaintEndY
= std::max( aOldRange
.aEnd
.Row(), aNewRange
.aEnd
.Row() );
451 if ( aOldRange
.aEnd
.Col() != aNewRange
.aEnd
.Col() )
452 nPaintEndX
= rDoc
.MaxCol();
453 if ( aOldRange
.aEnd
.Row() != aNewRange
.aEnd
.Row() )
454 nPaintEndY
= rDoc
.MaxRow();
456 if ( !m_pDocSh
->AdjustRowHeight( aDestPos
.Row(), nPaintEndY
, nDestTab
) )
458 ScRange(aDestPos
.Col(), aDestPos
.Row(), nDestTab
, nPaintEndX
, nPaintEndY
, nDestTab
),
459 PaintPartFlags::Grid
);
460 aModificator
.SetDocumentModified();
464 // CanFitBlock sal_False -> Problems with summarized cells or table boundary reached!
465 //! cell protection ???
467 //! Link dialog must set default parent
468 // "cannot insert rows"
469 weld::Window
* pWin
= Application::GetFrameWeld(m_pDocSh
->GetDialogParent());
470 std::unique_ptr
<weld::MessageDialog
> xInfoBox(Application::CreateMessageDialog(pWin
,
471 VclMessageType::Info
, VclButtonsType::Ok
,
472 ScResId(STR_MSSG_DOSUBTOTALS_2
)));
480 rDoc
.SetInLinkUpdate( false );
484 // notify Uno objects (for XRefreshListener)
485 //! also notify Uno objects if file name was changed!
486 ScLinkRefreshedHint aHint
;
487 aHint
.SetAreaLink( aDestPos
);
488 rDoc
.BroadcastUno( aHint
);
494 IMPL_LINK_NOARG(ScAreaLink
, RefreshHdl
, Timer
*, void)
496 Refresh( aFileName
, aFilterName
, aSourceArea
, GetRefreshDelaySeconds() );
499 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */