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 <comphelper/string.hxx>
21 #include <sfx2/app.hxx>
22 #include <sfx2/docfile.hxx>
23 #include <sfx2/fcontnr.hxx>
24 #include <sfx2/linkmgr.hxx>
25 #include <svl/stritem.hxx>
26 #include <vcl/msgbox.hxx>
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 "markdata.hxx"
42 #include "patattr.hxx"
43 #include "docpool.hxx"
46 #include "scabstdlg.hxx"
47 #include "clipparam.hxx"
52 AbstractScLinkedAreaDlg
* m_pDialog
;
54 AreaLink_Impl() : m_pDocSh( nullptr ), m_pDialog( nullptr ) {}
58 ScAreaLink::ScAreaLink( SfxObjectShell
* pShell
, const OUString
& rFile
,
59 const OUString
& rFilter
, const OUString
& rOpt
,
60 const OUString
& rArea
, const ScRange
& rDest
,
61 sal_uLong nRefresh
) :
62 ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL
,SotClipboardFormatId::SIMPLE_FILE
),
63 ScRefreshTimer ( nRefresh
),
64 pImpl ( new AreaLink_Impl() ),
66 aFilterName (rFilter
),
74 OSL_ENSURE(dynamic_cast< const ScDocShell
*>( pShell
) != nullptr, "ScAreaLink mit falscher ObjectShell");
75 pImpl
->m_pDocSh
= static_cast< ScDocShell
* >( pShell
);
76 SetRefreshHandler( LINK( this, ScAreaLink
, RefreshHdl
) );
77 SetRefreshControl( &pImpl
->m_pDocSh
->GetDocument().GetRefreshTimerControlAddress() );
80 ScAreaLink::~ScAreaLink()
85 void ScAreaLink::Edit(vcl::Window
* pParent
, const Link
<SvBaseLink
&,void>& /* rEndEditHdl */ )
87 // use own dialog instead of SvBaseLink::Edit...
88 ScAbstractDialogFactory
* pFact
= ScAbstractDialogFactory::Create();
89 OSL_ENSURE(pFact
, "ScAbstractFactory create fail!");
91 AbstractScLinkedAreaDlg
* pDlg
= pFact
->CreateScLinkedAreaDlg(pParent
);
92 OSL_ENSURE(pDlg
, "Dialog create fail!");
93 pDlg
->InitFromOldLink( aFileName
, aFilterName
, aOptions
, aSourceArea
, GetRefreshDelay() );
94 pImpl
->m_pDialog
= pDlg
;
95 pDlg
->StartExecuteModal( LINK( this, ScAreaLink
, AreaEndEditHdl
) );
98 ::sfx2::SvBaseLink::UpdateResult
ScAreaLink::DataChanged(
99 const OUString
&, const css::uno::Any
& )
101 // bei bInCreate nichts tun, damit Update gerufen werden kann, um den Status im
102 // LinkManager zu setzen, ohne die Daten im Dokument zu aendern
107 sfx2::LinkManager
* pLinkManager
=pImpl
->m_pDocSh
->GetDocument().GetLinkManager();
108 if (pLinkManager
!=nullptr)
110 OUString aFile
, aArea
, aFilter
;
111 sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile
, &aArea
, &aFilter
);
113 // the file dialog returns the filter name with the application prefix
115 ScDocumentLoader::RemoveAppPrefix( aFilter
);
117 // dialog doesn't set area, so keep old one
123 OUString aNewLinkName
;
124 OUString aTmp
= aFilter
;
125 sfx2::MakeLnkName(aNewLinkName
, nullptr, aFile
, aArea
, &aTmp
);
127 SetName( aNewLinkName
);
130 tools::SvRef
<sfx2::SvBaseLink
> const xThis(this); // keep yourself alive
131 Refresh( aFile
, aFilter
, aArea
, GetRefreshDelay() );
137 void ScAreaLink::Closed()
139 // Verknuepfung loeschen: Undo
141 ScDocument
& rDoc
= pImpl
->m_pDocSh
->GetDocument();
142 bool bUndo (rDoc
.IsUndoEnabled());
143 if (bAddUndo
&& bUndo
)
145 pImpl
->m_pDocSh
->GetUndoManager()->AddUndoAction( new ScUndoRemoveAreaLink( pImpl
->m_pDocSh
,
146 aFileName
, aFilterName
, aOptions
,
147 aSourceArea
, aDestArea
, GetRefreshDelay() ) );
149 bAddUndo
= false; // nur einmal
152 SCTAB nDestTab
= aDestArea
.aStart
.Tab();
153 if (rDoc
.IsStreamValid(nDestTab
))
154 rDoc
.SetStreamValid(nDestTab
, false);
156 SvBaseLink::Closed();
159 void ScAreaLink::SetDestArea(const ScRange
& rNew
)
161 aDestArea
= rNew
; // fuer Undo
164 void ScAreaLink::SetSource(const OUString
& rDoc
, const OUString
& rFlt
, const OUString
& rOpt
,
165 const OUString
& rArea
)
172 // also update link name for dialog
173 OUString aNewLinkName
;
174 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
175 SetName( aNewLinkName
);
178 bool ScAreaLink::IsEqual( const OUString
& rFile
, const OUString
& rFilter
, const OUString
& rOpt
,
179 const OUString
& rSource
, const ScRange
& rDest
) const
181 return aFileName
== rFile
&& aFilterName
== rFilter
&& aOptions
== rOpt
&&
182 aSourceArea
== rSource
&& aDestArea
.aStart
== rDest
.aStart
;
185 // find a range with name >rAreaName< in >pSrcDoc<, return it in >rRange<
186 bool ScAreaLink::FindExtRange( ScRange
& rRange
, ScDocument
* pSrcDoc
, const OUString
& rAreaName
)
189 OUString aUpperName
= ScGlobal::pCharClass
->uppercase(rAreaName
);
190 ScRangeName
* pNames
= pSrcDoc
->GetRangeName();
191 if (pNames
) // benannte Bereiche
193 const ScRangeData
* p
= pNames
->findByUpperName(aUpperName
);
194 if (p
&& p
->IsValidReference(rRange
))
197 if (!bFound
) // Datenbankbereiche
199 ScDBCollection
* pDBColl
= pSrcDoc
->GetDBCollection();
202 const ScDBData
* pDB
= pDBColl
->getNamedDBs().findByUpperName(aUpperName
);
208 pDB
->GetArea(nTab
,nCol1
,nRow1
,nCol2
,nRow2
);
209 rRange
= ScRange( nCol1
,nRow1
,nTab
, nCol2
,nRow2
,nTab
);
214 if (!bFound
) // direct reference (range or cell)
216 ScAddress::Details
aDetails(pSrcDoc
->GetAddressConvention(), 0, 0);
217 if ( rRange
.ParseAny( rAreaName
, pSrcDoc
, aDetails
) & ScRefFlags::VALID
)
225 bool ScAreaLink::Refresh( const OUString
& rNewFile
, const OUString
& rNewFilter
,
226 const OUString
& rNewArea
, sal_uLong nNewRefresh
)
228 // Dokument laden - wie TabLink
230 if (rNewFile
.isEmpty() || rNewFilter
.isEmpty())
233 OUString
aNewUrl( ScGlobal::GetAbsDocName( rNewFile
, pImpl
->m_pDocSh
) );
234 bool bNewUrlName
= (aNewUrl
!= aFileName
);
236 std::shared_ptr
<const SfxFilter
> pFilter
= pImpl
->m_pDocSh
->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter
);
240 ScDocument
& rDoc
= pImpl
->m_pDocSh
->GetDocument();
242 bool bUndo (rDoc
.IsUndoEnabled());
243 rDoc
.SetInLinkUpdate( true );
245 // wenn neuer Filter ausgewaehlt wurde, Optionen vergessen
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 // Optionen koennten gesetzt worden sein
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 sal_Int32 nTokenCnt
= comphelper::string::getTokenCount(aTempArea
, ';');
275 sal_Int32 nStringIx
= 0;
278 for( nToken
= 0; nToken
< nTokenCnt
; nToken
++ )
280 OUString
aToken( aTempArea
.getToken( 0, ';', nStringIx
) );
282 if( FindExtRange( aTokenRange
, &rSrcDoc
, aToken
) )
284 // columns: find maximum
285 nWidth
= std::max( nWidth
, (SCCOL
)(aTokenRange
.aEnd
.Col() - aTokenRange
.aStart
.Col() + 1) );
286 // rows: add row range + 1 empty row
287 nHeight
+= aTokenRange
.aEnd
.Row() - aTokenRange
.aStart
.Row() + 2;
290 // remove the last empty row
294 // alte Daten loeschen / neue kopieren
296 ScAddress aDestPos
= aDestArea
.aStart
;
297 SCTAB nDestTab
= aDestPos
.Tab();
298 ScRange aOldRange
= aDestArea
;
299 ScRange aNewRange
= aDestArea
; // alter Bereich, wenn Datei nicht gefunden o.ae.
300 if (nWidth
> 0 && nHeight
> 0)
302 aNewRange
.aEnd
.SetCol( aNewRange
.aStart
.Col() + nWidth
- 1 );
303 aNewRange
.aEnd
.SetRow( aNewRange
.aStart
.Row() + nHeight
- 1 );
306 //! check CanFitBlock only if bDoInsert is set?
307 bool bCanDo
= ValidColRow( aNewRange
.aEnd
.Col(), aNewRange
.aEnd
.Row() ) &&
308 rDoc
.CanFitBlock( aOldRange
, aNewRange
);
311 ScDocShellModificator
aModificator( *pImpl
->m_pDocSh
);
313 SCCOL nOldEndX
= aOldRange
.aEnd
.Col();
314 SCROW nOldEndY
= aOldRange
.aEnd
.Row();
315 SCCOL nNewEndX
= aNewRange
.aEnd
.Col();
316 SCROW nNewEndY
= aNewRange
.aEnd
.Row();
317 ScRange
aMaxRange( aDestPos
,
318 ScAddress(std::max(nOldEndX
,nNewEndX
), std::max(nOldEndY
,nNewEndY
), nDestTab
) );
320 // Undo initialisieren
322 ScDocument
* pUndoDoc
= nullptr;
323 if ( bAddUndo
&& bUndo
)
325 pUndoDoc
= new ScDocument( SCDOCMODE_UNDO
);
328 if ( nNewEndX
!= nOldEndX
|| nNewEndY
!= nOldEndY
) // Bereich veraendert?
330 pUndoDoc
->InitUndo( &rDoc
, 0, rDoc
.GetTableCount()-1 );
331 rDoc
.CopyToDocument( 0,0,0,MAXCOL
,MAXROW
,MAXTAB
,
332 InsertDeleteFlags::FORMULA
, false, pUndoDoc
); // alle Formeln
335 pUndoDoc
->InitUndo( &rDoc
, nDestTab
, nDestTab
); // nur Zieltabelle
336 rDoc
.CopyToDocument( aOldRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, pUndoDoc
);
338 else // ohne Einfuegen
340 pUndoDoc
->InitUndo( &rDoc
, nDestTab
, nDestTab
); // nur Zieltabelle
341 rDoc
.CopyToDocument( aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, pUndoDoc
);
345 // Zellen einfuegen / loeschen
346 // DeleteAreaTab loescht auch MERGE_FLAG Attribute
349 rDoc
.FitBlock( aOldRange
, aNewRange
); // incl. loeschen
351 rDoc
.DeleteAreaTab( aMaxRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
);
355 if (nWidth
> 0 && nHeight
> 0)
357 ScDocument
aClipDoc( SCDOCMODE_CLIP
);
358 ScRange
aNewTokenRange( aNewRange
.aStart
);
360 for( nToken
= 0; nToken
< nTokenCnt
; nToken
++ )
362 OUString
aToken( aTempArea
.getToken( 0, ';', nStringIx
) );
364 if( FindExtRange( aTokenRange
, &rSrcDoc
, aToken
) )
366 SCTAB nSrcTab
= aTokenRange
.aStart
.Tab();
367 ScMarkData aSourceMark
;
368 aSourceMark
.SelectOneTable( nSrcTab
); // selektieren fuer CopyToClip
369 aSourceMark
.SetMarkArea( aTokenRange
);
371 ScClipParam
aClipParam(aTokenRange
, false);
372 rSrcDoc
.CopyToClip(aClipParam
, &aClipDoc
, &aSourceMark
, false, false);
374 if ( aClipDoc
.HasAttrib( 0,0,nSrcTab
, MAXCOL
,MAXROW
,nSrcTab
,
375 HASATTR_MERGED
| HASATTR_OVERLAPPED
) )
377 //! ResetAttrib am Dokument !!!
379 ScPatternAttr
aPattern( rSrcDoc
.GetPool() );
380 aPattern
.GetItemSet().Put( ScMergeAttr() ); // Defaults
381 aPattern
.GetItemSet().Put( ScMergeFlagAttr() );
382 aClipDoc
.ApplyPatternAreaTab( 0,0, MAXCOL
,MAXROW
, nSrcTab
, aPattern
);
385 aNewTokenRange
.aEnd
.SetCol( aNewTokenRange
.aStart
.Col() + (aTokenRange
.aEnd
.Col() - aTokenRange
.aStart
.Col()) );
386 aNewTokenRange
.aEnd
.SetRow( aNewTokenRange
.aStart
.Row() + (aTokenRange
.aEnd
.Row() - aTokenRange
.aStart
.Row()) );
387 ScMarkData aDestMark
;
388 aDestMark
.SelectOneTable( nDestTab
);
389 aDestMark
.SetMarkArea( aNewTokenRange
);
390 rDoc
.CopyFromClip( aNewTokenRange
, aDestMark
, InsertDeleteFlags::ALL
, nullptr, &aClipDoc
, false );
391 aNewTokenRange
.aStart
.SetRow( aNewTokenRange
.aEnd
.Row() + 2 );
397 OUString aErr
= ScGlobal::GetRscString(STR_LINKERROR
);
398 rDoc
.SetString( aDestPos
.Col(), aDestPos
.Row(), aDestPos
.Tab(), aErr
);
403 if ( bAddUndo
&& bUndo
)
405 ScDocument
* pRedoDoc
= new ScDocument( SCDOCMODE_UNDO
);
406 pRedoDoc
->InitUndo( &rDoc
, nDestTab
, nDestTab
);
407 rDoc
.CopyToDocument( aNewRange
, InsertDeleteFlags::ALL
& ~InsertDeleteFlags::NOTE
, false, pRedoDoc
);
409 pImpl
->m_pDocSh
->GetUndoManager()->AddUndoAction(
410 new ScUndoUpdateAreaLink( pImpl
->m_pDocSh
,
411 aFileName
, aFilterName
, aOptions
,
412 aSourceArea
, aOldRange
, GetRefreshDelay(),
413 aNewUrl
, rNewFilter
, aNewOpt
,
414 rNewArea
, aNewRange
, nNewRefresh
,
415 pUndoDoc
, pRedoDoc
, bDoInsert
) );
418 // neue Einstellungen merken
422 if ( rNewFilter
!= aFilterName
)
423 aFilterName
= rNewFilter
;
424 if ( rNewArea
!= aSourceArea
)
425 aSourceArea
= rNewArea
;
426 if ( aNewOpt
!= aOptions
)
429 if ( aNewRange
!= aDestArea
)
430 aDestArea
= aNewRange
;
432 if ( nNewRefresh
!= GetRefreshDelay() )
433 SetRefreshDelay( nNewRefresh
);
435 SCCOL nPaintEndX
= std::max( aOldRange
.aEnd
.Col(), aNewRange
.aEnd
.Col() );
436 SCROW nPaintEndY
= std::max( aOldRange
.aEnd
.Row(), aNewRange
.aEnd
.Row() );
438 if ( aOldRange
.aEnd
.Col() != aNewRange
.aEnd
.Col() )
440 if ( aOldRange
.aEnd
.Row() != aNewRange
.aEnd
.Row() )
443 if ( !pImpl
->m_pDocSh
->AdjustRowHeight( aDestPos
.Row(), nPaintEndY
, nDestTab
) )
444 pImpl
->m_pDocSh
->PostPaint(
445 ScRange(aDestPos
.Col(), aDestPos
.Row(), nDestTab
, nPaintEndX
, nPaintEndY
, nDestTab
),
447 aModificator
.SetDocumentModified();
451 // CanFitBlock sal_False -> Probleme mit zusammengefassten Zellen
452 // oder Tabellengrenze erreicht!
455 //! Link-Dialog muss Default-Parent setzen
456 // "kann keine Zeilen einfuegen"
457 ScopedVclPtrInstance
<InfoBox
> aBox( Application::GetDefDialogParent(),
458 ScGlobal::GetRscString( STR_MSSG_DOSUBTOTALS_2
) );
466 rDoc
.SetInLinkUpdate( false );
470 // notify Uno objects (for XRefreshListener)
471 //! also notify Uno objects if file name was changed!
472 ScLinkRefreshedHint aHint
;
473 aHint
.SetAreaLink( aDestPos
);
474 rDoc
.BroadcastUno( aHint
);
480 IMPL_LINK_NOARG_TYPED(ScAreaLink
, RefreshHdl
, Timer
*, void)
482 Refresh( aFileName
, aFilterName
, aSourceArea
, GetRefreshDelay() );
485 IMPL_LINK_NOARG_TYPED(ScAreaLink
, AreaEndEditHdl
, Dialog
&, void)
487 // #i76514# can't use link argument to access the dialog,
488 // because it's the ScLinkedAreaDlg, not AbstractScLinkedAreaDlg
490 if ( pImpl
->m_pDialog
&& pImpl
->m_pDialog
->GetResult() == RET_OK
)
492 aOptions
= pImpl
->m_pDialog
->GetOptions();
493 Refresh( pImpl
->m_pDialog
->GetURL(), pImpl
->m_pDialog
->GetFilter(),
494 pImpl
->m_pDialog
->GetSource(), pImpl
->m_pDialog
->GetRefresh() );
496 // copy source data from members (set in Refresh) into link name for dialog
497 OUString aNewLinkName
;
498 sfx2::MakeLnkName( aNewLinkName
, nullptr, aFileName
, aSourceArea
, &aFilterName
);
499 SetName( aNewLinkName
);
501 pImpl
->m_pDialog
= nullptr; // dialog is deleted with parent
504 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */