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