1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <sfx2/fcontnr.hxx>
21 #include <sfx2/linkmgr.hxx>
22 #include <vcl/svapp.hxx>
23 #include <vcl/weld.hxx>
24 #include <unotools/charclass.hxx>
25 #include <osl/diagnose.h>
26
27 #include <arealink.hxx>
28
29 #include <tablink.hxx>
30 #include <document.hxx>
31 #include <docsh.hxx>
32 #include <rangenam.hxx>
33 #include <dbdata.hxx>
34 #include <undoblk.hxx>
35 #include <globstr.hrc>
36 #include <scresid.hxx>
37 #include <markdata.hxx>
38 #include <hints.hxx>
39 #include <filter.hxx>
40
41 #include <attrib.hxx>
42 #include <patattr.hxx>
43 #include <docpool.hxx>
44
45 #include <scabstdlg.hxx>
46 #include <clipparam.hxx>
47
48
ScAreaLink(SfxObjectShell * pShell,const OUString & rFile,const OUString & rFilter,const OUString & rOpt,const OUString & rArea,const ScRange & rDest,sal_uLong nRefresh)49 ScAreaLink::ScAreaLink( SfxObjectShell* pShell, const OUString& rFile,
50 const OUString& rFilter, const OUString& rOpt,
51 const OUString& rArea, const ScRange& rDest,
52 sal_uLong nRefresh ) :
53 ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE),
54 ScRefreshTimer ( nRefresh ),
55 m_pDocSh(static_cast<ScDocShell*>(pShell)),
56 aFileName (rFile),
57 aFilterName (rFilter),
58 aOptions (rOpt),
59 aSourceArea (rArea),
60 aDestArea (rDest),
61 bAddUndo (true),
62 bInCreate (false),
63 bDoInsert (true)
64 {
65 OSL_ENSURE(dynamic_cast< const ScDocShell *>( pShell ) != nullptr, "ScAreaLink with wrong ObjectShell");
66 SetRefreshHandler( LINK( this, ScAreaLink, RefreshHdl ) );
67 SetRefreshControl( &m_pDocSh->GetDocument().GetRefreshTimerControlAddress() );
68 }
69
~ScAreaLink()70 ScAreaLink::~ScAreaLink()
71 {
72 StopRefreshTimer();
73 }
74
Edit(weld::Window * pParent,const Link<SvBaseLink &,void> &)75 void ScAreaLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /* rEndEditHdl */ )
76 {
77 // use own dialog instead of SvBaseLink::Edit...
78 ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();
79
80 ScopedVclPtr<AbstractScLinkedAreaDlg> pDlg(pFact->CreateScLinkedAreaDlg(pParent));
81 pDlg->InitFromOldLink( aFileName, aFilterName, aOptions, aSourceArea, GetRefreshDelay() );
82 if ( pDlg->Execute() == RET_OK )
83 {
84 aOptions = pDlg->GetOptions();
85 Refresh( pDlg->GetURL(), pDlg->GetFilter(),
86 pDlg->GetSource(), pDlg->GetRefresh() );
87
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 );
92 }
93 }
94
DataChanged(const OUString &,const css::uno::Any &)95 ::sfx2::SvBaseLink::UpdateResult ScAreaLink::DataChanged(
96 const OUString&, const css::uno::Any& )
97 {
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
100
101 if (bInCreate)
102 return SUCCESS;
103
104 sfx2::LinkManager* pLinkManager=m_pDocSh->GetDocument().GetLinkManager();
105 if (pLinkManager!=nullptr)
106 {
107 OUString aFile, aArea, aFilter;
108 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, &aArea, &aFilter);
109
110 // the file dialog returns the filter name with the application prefix
111 // -> remove prefix
112 ScDocumentLoader::RemoveAppPrefix( aFilter );
113
114 // dialog doesn't set area, so keep old one
115 if (aArea.isEmpty())
116 {
117 aArea = aSourceArea;
118
119 // adjust in dialog:
120 OUString aNewLinkName;
121 OUString aTmp = aFilter;
122 sfx2::MakeLnkName(aNewLinkName, nullptr, aFile, aArea, &aTmp);
123 aFilter = aTmp;
124 SetName( aNewLinkName );
125 }
126
127 tools::SvRef<sfx2::SvBaseLink> const xThis(this); // keep yourself alive
128 Refresh( aFile, aFilter, aArea, GetRefreshDelay() );
129 }
130
131 return SUCCESS;
132 }
133
Closed()134 void ScAreaLink::Closed()
135 {
136 // delete link: Undo
137
138 ScDocument& rDoc = m_pDocSh->GetDocument();
139 bool bUndo (rDoc.IsUndoEnabled());
140 if (bAddUndo && bUndo)
141 {
142 m_pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoRemoveAreaLink>( m_pDocSh,
143 aFileName, aFilterName, aOptions,
144 aSourceArea, aDestArea, GetRefreshDelay() ) );
145
146 bAddUndo = false; // only once
147 }
148
149 SCTAB nDestTab = aDestArea.aStart.Tab();
150 rDoc.SetStreamValid(nDestTab, false);
151
152 SvBaseLink::Closed();
153 }
154
SetDestArea(const ScRange & rNew)155 void ScAreaLink::SetDestArea(const ScRange& rNew)
156 {
157 aDestArea = rNew; // for Undo
158 }
159
SetSource(const OUString & rDoc,const OUString & rFlt,const OUString & rOpt,const OUString & rArea)160 void ScAreaLink::SetSource(const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
161 const OUString& rArea)
162 {
163 aFileName = rDoc;
164 aFilterName = rFlt;
165 aOptions = rOpt;
166 aSourceArea = rArea;
167
168 // also update link name for dialog
169 OUString aNewLinkName;
170 sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName );
171 SetName( aNewLinkName );
172 }
173
IsEqual(const OUString & rFile,const OUString & rFilter,const OUString & rOpt,const OUString & rSource,const ScRange & rDest) const174 bool ScAreaLink::IsEqual( const OUString& rFile, const OUString& rFilter, const OUString& rOpt,
175 const OUString& rSource, const ScRange& rDest ) const
176 {
177 return aFileName == rFile && aFilterName == rFilter && aOptions == rOpt &&
178 aSourceArea == rSource && aDestArea.aStart == rDest.aStart;
179 }
180
181 // find a range with name >rAreaName< in >pSrcDoc<, return it in >rRange<
FindExtRange(ScRange & rRange,const ScDocument * pSrcDoc,const OUString & rAreaName)182 bool ScAreaLink::FindExtRange( ScRange& rRange, const ScDocument* pSrcDoc, const OUString& rAreaName )
183 {
184 bool bFound = false;
185 OUString aUpperName = ScGlobal::pCharClass->uppercase(rAreaName);
186 ScRangeName* pNames = pSrcDoc->GetRangeName();
187 if (pNames) // named ranges
188 {
189 const ScRangeData* p = pNames->findByUpperName(aUpperName);
190 if (p && p->IsValidReference(rRange))
191 bFound = true;
192 }
193 if (!bFound) // database ranges
194 {
195 ScDBCollection* pDBColl = pSrcDoc->GetDBCollection();
196 if (pDBColl)
197 {
198 const ScDBData* pDB = pDBColl->getNamedDBs().findByUpperName(aUpperName);
199 if (pDB)
200 {
201 SCTAB nTab;
202 SCCOL nCol1, nCol2;
203 SCROW nRow1, nRow2;
204 pDB->GetArea(nTab,nCol1,nRow1,nCol2,nRow2);
205 rRange = ScRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab );
206 bFound = true;
207 }
208 }
209 }
210 if (!bFound) // direct reference (range or cell)
211 {
212 ScAddress::Details aDetails(pSrcDoc->GetAddressConvention(), 0, 0);
213 if ( rRange.ParseAny( rAreaName, pSrcDoc, aDetails ) & ScRefFlags::VALID )
214 bFound = true;
215 }
216 return bFound;
217 }
218
219 // execute:
220
Refresh(const OUString & rNewFile,const OUString & rNewFilter,const OUString & rNewArea,sal_uLong nNewRefresh)221 bool ScAreaLink::Refresh( const OUString& rNewFile, const OUString& rNewFilter,
222 const OUString& rNewArea, sal_uLong nNewRefresh )
223 {
224 // load document - like TabLink
225
226 if (rNewFile.isEmpty() || rNewFilter.isEmpty())
227 return false;
228
229 OUString aNewUrl( ScGlobal::GetAbsDocName( rNewFile, m_pDocSh ) );
230 bool bNewUrlName = (aNewUrl != aFileName);
231
232 std::shared_ptr<const SfxFilter> pFilter = m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter);
233 if (!pFilter)
234 return false;
235
236 ScDocument& rDoc = m_pDocSh->GetDocument();
237
238 bool bUndo (rDoc.IsUndoEnabled());
239 rDoc.SetInLinkUpdate( true );
240
241 // if new filter was selected, forget options
242 if ( rNewFilter != aFilterName )
243 aOptions.clear();
244
245 SfxMedium* pMed = ScDocumentLoader::CreateMedium( aNewUrl, pFilter, aOptions);
246
247 // aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here
248 ScDocShell* pSrcShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS);
249 SfxObjectShellLock aRef = pSrcShell;
250 pSrcShell->DoLoad(pMed);
251
252 ScDocument& rSrcDoc = pSrcShell->GetDocument();
253
254 // options could have been set
255 OUString aNewOpt = ScDocumentLoader::GetOptions(*pMed);
256 if (aNewOpt.isEmpty())
257 aNewOpt = aOptions;
258
259 // correct source range name list for web query import
260 OUString aTempArea;
261
262 if( rNewFilter == ScDocShell::GetWebQueryFilterName() )
263 aTempArea = ScFormatFilter::Get().GetHTMLRangeNameList( &rSrcDoc, rNewArea );
264 else
265 aTempArea = rNewArea;
266
267 // find total size of source area
268 SCCOL nWidth = 0;
269 SCROW nHeight = 0;
270 ScRangeList aSourceRanges;
271
272 if (rNewFilter == SC_TEXT_CSV_FILTER_NAME && aTempArea == "CSV_all")
273 {
274 // The dummy All range. All data, including top/left empty
275 // rows/columns.
276 aTempArea.clear();
277 SCCOL nEndCol = 0;
278 SCROW nEndRow = 0;
279 if (rSrcDoc.GetCellArea( 0, nEndCol, nEndRow))
280 {
281 aSourceRanges.push_back( ScRange( 0,0,0, nEndCol, nEndRow, 0));
282 nWidth = nEndCol + 1;
283 nHeight = nEndRow + 2;
284 }
285 }
286
287 if (!aTempArea.isEmpty())
288 {
289 sal_Int32 nIdx {0};
290 do
291 {
292 ScRange aTokenRange;
293 if( FindExtRange( aTokenRange, &rSrcDoc, aTempArea.getToken( 0, ';', nIdx ) ) )
294 {
295 aSourceRanges.push_back( aTokenRange);
296 // columns: find maximum
297 nWidth = std::max( nWidth, static_cast<SCCOL>(aTokenRange.aEnd.Col() - aTokenRange.aStart.Col() + 1) );
298 // rows: add row range + 1 empty row
299 nHeight += aTokenRange.aEnd.Row() - aTokenRange.aStart.Row() + 2;
300 }
301 }
302 while (nIdx>0);
303 }
304 // remove the last empty row
305 if( nHeight > 0 )
306 nHeight--;
307
308 // delete old data / copy new
309
310 ScAddress aDestPos = aDestArea.aStart;
311 SCTAB nDestTab = aDestPos.Tab();
312 ScRange aOldRange = aDestArea;
313 ScRange aNewRange = aDestArea; // old range, if file not found or similar
314 if (nWidth > 0 && nHeight > 0)
315 {
316 aNewRange.aEnd.SetCol( aNewRange.aStart.Col() + nWidth - 1 );
317 aNewRange.aEnd.SetRow( aNewRange.aStart.Row() + nHeight - 1 );
318 }
319
320 //! check CanFitBlock only if bDoInsert is set?
321 bool bCanDo = ValidColRow( aNewRange.aEnd.Col(), aNewRange.aEnd.Row() ) &&
322 rDoc.CanFitBlock( aOldRange, aNewRange );
323 if (bCanDo)
324 {
325 ScDocShellModificator aModificator( *m_pDocSh );
326
327 SCCOL nOldEndX = aOldRange.aEnd.Col();
328 SCROW nOldEndY = aOldRange.aEnd.Row();
329 SCCOL nNewEndX = aNewRange.aEnd.Col();
330 SCROW nNewEndY = aNewRange.aEnd.Row();
331 ScRange aMaxRange( aDestPos,
332 ScAddress(std::max(nOldEndX,nNewEndX), std::max(nOldEndY,nNewEndY), nDestTab) );
333
334 // initialise Undo
335
336 ScDocumentUniquePtr pUndoDoc;
337 if ( bAddUndo && bUndo )
338 {
339 pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
340 if ( bDoInsert )
341 {
342 if ( nNewEndX != nOldEndX || nNewEndY != nOldEndY ) // range changed?
343 {
344 pUndoDoc->InitUndo( &rDoc, 0, rDoc.GetTableCount()-1 );
345 rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB,
346 InsertDeleteFlags::FORMULA, false, *pUndoDoc); // all formulas
347 }
348 else
349 pUndoDoc->InitUndo( &rDoc, nDestTab, nDestTab ); // only destination table
350 rDoc.CopyToDocument(aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
351 }
352 else // without insertion
353 {
354 pUndoDoc->InitUndo( &rDoc, nDestTab, nDestTab ); // only destination table
355 rDoc.CopyToDocument(aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
356 }
357 }
358
359 // insert / delete cells
360 // DeleteAreaTab also deletes MERGE_FLAG attributes
361
362 if (bDoInsert)
363 rDoc.FitBlock( aOldRange, aNewRange ); // incl. deletion
364 else
365 rDoc.DeleteAreaTab( aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );
366
367 // copy data
368
369 if (nWidth > 0 && nHeight > 0)
370 {
371 ScDocument aClipDoc( SCDOCMODE_CLIP );
372 ScRange aNewTokenRange( aNewRange.aStart );
373 for (size_t nRange = 0; nRange < aSourceRanges.size(); ++nRange)
374 {
375 ScRange const & rTokenRange( aSourceRanges[nRange]);
376 SCTAB nSrcTab = rTokenRange.aStart.Tab();
377 ScMarkData aSourceMark(rSrcDoc.MaxRow(), rSrcDoc.MaxCol());
378 aSourceMark.SelectOneTable( nSrcTab ); // selecting for CopyToClip
379 aSourceMark.SetMarkArea( rTokenRange );
380
381 ScClipParam aClipParam(rTokenRange, false);
382 rSrcDoc.CopyToClip(aClipParam, &aClipDoc, &aSourceMark, false, false);
383
384 if ( aClipDoc.HasAttrib( 0,0,nSrcTab, rDoc.MaxCol(),rDoc.MaxRow(),nSrcTab,
385 HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
386 {
387 //! ResetAttrib at document !!!
388
389 ScPatternAttr aPattern( rSrcDoc.GetPool() );
390 aPattern.GetItemSet().Put( ScMergeAttr() ); // Defaults
391 aPattern.GetItemSet().Put( ScMergeFlagAttr() );
392 aClipDoc.ApplyPatternAreaTab( 0,0, rDoc.MaxCol(),rDoc.MaxRow(), nSrcTab, aPattern );
393 }
394
395 aNewTokenRange.aEnd.SetCol( aNewTokenRange.aStart.Col() + (rTokenRange.aEnd.Col() - rTokenRange.aStart.Col()) );
396 aNewTokenRange.aEnd.SetRow( aNewTokenRange.aStart.Row() + (rTokenRange.aEnd.Row() - rTokenRange.aStart.Row()) );
397 ScMarkData aDestMark(rDoc.MaxRow(), rDoc.MaxCol());
398 aDestMark.SelectOneTable( nDestTab );
399 aDestMark.SetMarkArea( aNewTokenRange );
400 rDoc.CopyFromClip( aNewTokenRange, aDestMark, InsertDeleteFlags::ALL, nullptr, &aClipDoc, false );
401 aNewTokenRange.aStart.SetRow( aNewTokenRange.aEnd.Row() + 2 );
402 }
403 }
404 else
405 {
406 OUString aErr = ScResId(STR_LINKERROR);
407 rDoc.SetString( aDestPos.Col(), aDestPos.Row(), aDestPos.Tab(), aErr );
408 }
409
410 // enter Undo
411
412 if ( bAddUndo && bUndo)
413 {
414 ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO ));
415 pRedoDoc->InitUndo( &rDoc, nDestTab, nDestTab );
416 rDoc.CopyToDocument(aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pRedoDoc);
417
418 m_pDocSh->GetUndoManager()->AddUndoAction(
419 std::make_unique<ScUndoUpdateAreaLink>( m_pDocSh,
420 aFileName, aFilterName, aOptions,
421 aSourceArea, aOldRange, GetRefreshDelay(),
422 aNewUrl, rNewFilter, aNewOpt,
423 rNewArea, aNewRange, nNewRefresh,
424 std::move(pUndoDoc), std::move(pRedoDoc), bDoInsert ) );
425 }
426
427 // remember new settings
428
429 if ( bNewUrlName )
430 aFileName = aNewUrl;
431 if ( rNewFilter != aFilterName )
432 aFilterName = rNewFilter;
433 if ( rNewArea != aSourceArea )
434 aSourceArea = rNewArea;
435 if ( aNewOpt != aOptions )
436 aOptions = aNewOpt;
437
438 if ( aNewRange != aDestArea )
439 aDestArea = aNewRange;
440
441 if ( nNewRefresh != GetRefreshDelay() )
442 SetRefreshDelay( nNewRefresh );
443
444 SCCOL nPaintEndX = std::max( aOldRange.aEnd.Col(), aNewRange.aEnd.Col() );
445 SCROW nPaintEndY = std::max( aOldRange.aEnd.Row(), aNewRange.aEnd.Row() );
446
447 if ( aOldRange.aEnd.Col() != aNewRange.aEnd.Col() )
448 nPaintEndX = rDoc.MaxCol();
449 if ( aOldRange.aEnd.Row() != aNewRange.aEnd.Row() )
450 nPaintEndY = rDoc.MaxRow();
451
452 if ( !m_pDocSh->AdjustRowHeight( aDestPos.Row(), nPaintEndY, nDestTab ) )
453 m_pDocSh->PostPaint(
454 ScRange(aDestPos.Col(), aDestPos.Row(), nDestTab, nPaintEndX, nPaintEndY, nDestTab),
455 PaintPartFlags::Grid);
456 aModificator.SetDocumentModified();
457 }
458 else
459 {
460 // CanFitBlock sal_False -> Problems with summarized cells or table boundary reached!
461 //! cell protection ???
462
463 //! Link dialog must set default parent
464 // "cannot insert rows"
465 vcl::Window* pWin = Application::GetDefDialogParent();
466 std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr,
467 VclMessageType::Info, VclButtonsType::Ok,
468 ScResId(STR_MSSG_DOSUBTOTALS_2)));
469 xInfoBox->run();
470 }
471
472 // clean up
473
474 aRef->DoClose();
475
476 rDoc.SetInLinkUpdate( false );
477
478 if (bCanDo)
479 {
480 // notify Uno objects (for XRefreshListener)
481 //! also notify Uno objects if file name was changed!
482 ScLinkRefreshedHint aHint;
483 aHint.SetAreaLink( aDestPos );
484 rDoc.BroadcastUno( aHint );
485 }
486
487 return bCanDo;
488 }
489
IMPL_LINK_NOARG(ScAreaLink,RefreshHdl,Timer *,void)490 IMPL_LINK_NOARG(ScAreaLink, RefreshHdl, Timer *, void)
491 {
492 Refresh( aFileName, aFilterName, aSourceArea, GetRefreshDelay() );
493 }
494
495 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
496