summaryrefslogtreecommitdiff
path: root/sc/source/ui/docshell/arealink.cxx
blob: 808b5e688c579469a578ff6dbf38e1fd549fd4b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sfx2/app.hxx>
#include <sfx2/docfile.hxx>
#include <sfx2/fcontnr.hxx>
#include <sfx2/linkmgr.hxx>
#include <svl/stritem.hxx>
#include <vcl/weld.hxx>
#include <unotools/charclass.hxx>
#include <o3tl/make_unique.hxx>

#include <arealink.hxx>

#include <tablink.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <rangenam.hxx>
#include <dbdata.hxx>
#include <undoblk.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <markdata.hxx>
#include <hints.hxx>
#include <filter.hxx>

#include <attrib.hxx>
#include <patattr.hxx>
#include <docpool.hxx>

#include <sc.hrc>
#include <scabstdlg.hxx>
#include <clipparam.hxx>


ScAreaLink::ScAreaLink( SfxObjectShell* pShell, const OUString& rFile,
                        const OUString& rFilter, const OUString& rOpt,
                        const OUString& rArea, const ScRange& rDest,
                        sal_uLong nRefresh ) :
    ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE),
    ScRefreshTimer  ( nRefresh ),
    m_pDocSh(static_cast<ScDocShell*>(pShell)),
    aFileName       (rFile),
    aFilterName     (rFilter),
    aOptions        (rOpt),
    aSourceArea     (rArea),
    aDestArea       (rDest),
    bAddUndo        (true),
    bInCreate       (false),
    bDoInsert       (true)
{
    OSL_ENSURE(dynamic_cast< const ScDocShell *>( pShell ) !=  nullptr, "ScAreaLink with wrong ObjectShell");
    SetRefreshHandler( LINK( this, ScAreaLink, RefreshHdl ) );
    SetRefreshControl( &m_pDocSh->GetDocument().GetRefreshTimerControlAddress() );
}

ScAreaLink::~ScAreaLink()
{
    StopRefreshTimer();
}

void ScAreaLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /* rEndEditHdl */ )
{
    //  use own dialog instead of SvBaseLink::Edit...
    ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create();

    ScopedVclPtr<AbstractScLinkedAreaDlg> pDlg(pFact->CreateScLinkedAreaDlg(pParent));
    pDlg->InitFromOldLink( aFileName, aFilterName, aOptions, aSourceArea, GetRefreshDelay() );
    if ( pDlg->Execute() == RET_OK )
    {
        aOptions = pDlg->GetOptions();
        Refresh( pDlg->GetURL(), pDlg->GetFilter(),
                 pDlg->GetSource(), pDlg->GetRefresh() );

        //  copy source data from members (set in Refresh) into link name for dialog
        OUString aNewLinkName;
        sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName );
        SetName( aNewLinkName );
    }
}

::sfx2::SvBaseLink::UpdateResult ScAreaLink::DataChanged(
    const OUString&, const css::uno::Any& )
{
    //  Do not do anything at bInCreate so that update can be called to set
    //  the status in the LinkManager without changing the data in the document

    if (bInCreate)
        return SUCCESS;

    sfx2::LinkManager* pLinkManager=m_pDocSh->GetDocument().GetLinkManager();
    if (pLinkManager!=nullptr)
    {
        OUString aFile, aArea, aFilter;
        sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, &aArea, &aFilter);

        //  the file dialog returns the filter name with the application prefix
        //  -> remove prefix
        ScDocumentLoader::RemoveAppPrefix( aFilter );

        // dialog doesn't set area, so keep old one
        if (aArea.isEmpty())
        {
            aArea = aSourceArea;

            // adjust in dialog:
            OUString aNewLinkName;
            OUString aTmp = aFilter;
            sfx2::MakeLnkName(aNewLinkName, nullptr, aFile, aArea, &aTmp);
            aFilter = aTmp;
            SetName( aNewLinkName );
        }

        tools::SvRef<sfx2::SvBaseLink> const xThis(this); // keep yourself alive
        Refresh( aFile, aFilter, aArea, GetRefreshDelay() );
    }

    return SUCCESS;
}

void ScAreaLink::Closed()
{
    // delete link: Undo

    ScDocument& rDoc = m_pDocSh->GetDocument();
    bool bUndo (rDoc.IsUndoEnabled());
    if (bAddUndo && bUndo)
    {
        m_pDocSh->GetUndoManager()->AddUndoAction( o3tl::make_unique<ScUndoRemoveAreaLink>( m_pDocSh,
                                                        aFileName, aFilterName, aOptions,
                                                        aSourceArea, aDestArea, GetRefreshDelay() ) );

        bAddUndo = false;   // only once
    }

    SCTAB nDestTab = aDestArea.aStart.Tab();
    rDoc.SetStreamValid(nDestTab, false);

    SvBaseLink::Closed();
}

void ScAreaLink::SetDestArea(const ScRange& rNew)
{
    aDestArea = rNew;           // for Undo
}

void ScAreaLink::SetSource(const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
                                const OUString& rArea)
{
    aFileName   = rDoc;
    aFilterName = rFlt;
    aOptions    = rOpt;
    aSourceArea = rArea;

    //  also update link name for dialog
    OUString aNewLinkName;
    sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName );
    SetName( aNewLinkName );
}

bool ScAreaLink::IsEqual( const OUString& rFile, const OUString& rFilter, const OUString& rOpt,
                            const OUString& rSource, const ScRange& rDest ) const
{
    return aFileName == rFile && aFilterName == rFilter && aOptions == rOpt &&
            aSourceArea == rSource && aDestArea.aStart == rDest.aStart;
}

// find a range with name >rAreaName< in >pSrcDoc<, return it in >rRange<
bool ScAreaLink::FindExtRange( ScRange& rRange, const ScDocument* pSrcDoc, const OUString& rAreaName )
{
    bool bFound = false;
    OUString aUpperName = ScGlobal::pCharClass->uppercase(rAreaName);
    ScRangeName* pNames = pSrcDoc->GetRangeName();
    if (pNames)         // named ranges
    {
        const ScRangeData* p = pNames->findByUpperName(aUpperName);
        if (p && p->IsValidReference(rRange))
            bFound = true;
    }
    if (!bFound)        // database ranges
    {
        ScDBCollection* pDBColl = pSrcDoc->GetDBCollection();
        if (pDBColl)
        {
            const ScDBData* pDB = pDBColl->getNamedDBs().findByUpperName(aUpperName);
            if (pDB)
            {
                SCTAB nTab;
                SCCOL nCol1, nCol2;
                SCROW nRow1, nRow2;
                pDB->GetArea(nTab,nCol1,nRow1,nCol2,nRow2);
                rRange = ScRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab );
                bFound = true;
            }
        }
    }
    if (!bFound)        // direct reference (range or cell)
    {
        ScAddress::Details aDetails(pSrcDoc->GetAddressConvention(), 0, 0);
        if ( rRange.ParseAny( rAreaName, pSrcDoc, aDetails ) & ScRefFlags::VALID )
            bFound = true;
    }
    return bFound;
}

//  execute:

bool ScAreaLink::Refresh( const OUString& rNewFile, const OUString& rNewFilter,
                            const OUString& rNewArea, sal_uLong nNewRefresh )
{
    //  load document - like TabLink

    if (rNewFile.isEmpty() || rNewFilter.isEmpty())
        return false;

    OUString aNewUrl( ScGlobal::GetAbsDocName( rNewFile, m_pDocSh ) );
    bool bNewUrlName = (aNewUrl != aFileName);

    std::shared_ptr<const SfxFilter> pFilter = m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter);
    if (!pFilter)
        return false;

    ScDocument& rDoc = m_pDocSh->GetDocument();

    bool bUndo (rDoc.IsUndoEnabled());
    rDoc.SetInLinkUpdate( true );

    //  if new filter was selected, forget options
    if ( rNewFilter != aFilterName )
        aOptions.clear();

    SfxMedium* pMed = ScDocumentLoader::CreateMedium( aNewUrl, pFilter, aOptions);

    // aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here
    ScDocShell* pSrcShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS);
    SfxObjectShellLock aRef = pSrcShell;
    pSrcShell->DoLoad(pMed);

    ScDocument& rSrcDoc = pSrcShell->GetDocument();

    // options could have been set
    OUString aNewOpt = ScDocumentLoader::GetOptions(*pMed);
    if (aNewOpt.isEmpty())
        aNewOpt = aOptions;

    // correct source range name list for web query import
    OUString aTempArea;

    if( rNewFilter == ScDocShell::GetWebQueryFilterName() )
        aTempArea = ScFormatFilter::Get().GetHTMLRangeNameList( &rSrcDoc, rNewArea );
    else
        aTempArea = rNewArea;

    // find total size of source area
    SCCOL nWidth = 0;
    SCROW nHeight = 0;
    ScRangeList aSourceRanges;

    if (rNewFilter == SC_TEXT_CSV_FILTER_NAME && aTempArea == "CSV_all")
    {
        // The dummy All range. All data, including top/left empty
        // rows/columns.
        aTempArea.clear();
        SCCOL nEndCol = 0;
        SCROW nEndRow = 0;
        if (rSrcDoc.GetCellArea( 0, nEndCol, nEndRow))
        {
            aSourceRanges.push_back( ScRange( 0,0,0, nEndCol, nEndRow, 0));
            nWidth = nEndCol + 1;
            nHeight = nEndRow + 2;
        }
    }

    if (!aTempArea.isEmpty())
    {
        sal_Int32 nIdx {0};
        do
        {
            ScRange aTokenRange;
            if( FindExtRange( aTokenRange, &rSrcDoc, aTempArea.getToken( 0, ';', nIdx ) ) )
            {
                aSourceRanges.push_back( aTokenRange);
                // columns: find maximum
                nWidth = std::max( nWidth, static_cast<SCCOL>(aTokenRange.aEnd.Col() - aTokenRange.aStart.Col() + 1) );
                // rows: add row range + 1 empty row
                nHeight += aTokenRange.aEnd.Row() - aTokenRange.aStart.Row() + 2;
            }
        }
        while (nIdx>0);
    }
    // remove the last empty row
    if( nHeight > 0 )
        nHeight--;

    //  delete old data / copy new

    ScAddress aDestPos = aDestArea.aStart;
    SCTAB nDestTab = aDestPos.Tab();
    ScRange aOldRange = aDestArea;
    ScRange aNewRange = aDestArea;          // old range, if file not found or similar
    if (nWidth > 0 && nHeight > 0)
    {
        aNewRange.aEnd.SetCol( aNewRange.aStart.Col() + nWidth - 1 );
        aNewRange.aEnd.SetRow( aNewRange.aStart.Row() + nHeight - 1 );
    }

    //! check CanFitBlock only if bDoInsert is set?
    bool bCanDo = ValidColRow( aNewRange.aEnd.Col(), aNewRange.aEnd.Row() ) &&
                  rDoc.CanFitBlock( aOldRange, aNewRange );
    if (bCanDo)
    {
        ScDocShellModificator aModificator( *m_pDocSh );

        SCCOL nOldEndX = aOldRange.aEnd.Col();
        SCROW nOldEndY = aOldRange.aEnd.Row();
        SCCOL nNewEndX = aNewRange.aEnd.Col();
        SCROW nNewEndY = aNewRange.aEnd.Row();
        ScRange aMaxRange( aDestPos,
                    ScAddress(std::max(nOldEndX,nNewEndX), std::max(nOldEndY,nNewEndY), nDestTab) );

        //  initialise Undo

        ScDocumentUniquePtr pUndoDoc;
        if ( bAddUndo && bUndo )
        {
            pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
            if ( bDoInsert )
            {
                if ( nNewEndX != nOldEndX || nNewEndY != nOldEndY )             // range changed?
                {
                    pUndoDoc->InitUndo( &rDoc, 0, rDoc.GetTableCount()-1 );
                    rDoc.CopyToDocument(0, 0, 0, MAXCOL, MAXROW, MAXTAB,
                                        InsertDeleteFlags::FORMULA, false, *pUndoDoc);     // all formulas
                }
                else
                    pUndoDoc->InitUndo( &rDoc, nDestTab, nDestTab );             // only destination table
                rDoc.CopyToDocument(aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
            }
            else        // without insertion
            {
                pUndoDoc->InitUndo( &rDoc, nDestTab, nDestTab );             // only destination table
                rDoc.CopyToDocument(aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc);
            }
        }

        //  insert / delete cells
        //  DeleteAreaTab also deletes MERGE_FLAG attributes

        if (bDoInsert)
            rDoc.FitBlock( aOldRange, aNewRange );         // incl. deletion
        else
            rDoc.DeleteAreaTab( aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE );

        //  copy data

        if (nWidth > 0 && nHeight > 0)
        {
            ScDocument aClipDoc( SCDOCMODE_CLIP );
            ScRange aNewTokenRange( aNewRange.aStart );
            for (size_t nRange = 0; nRange < aSourceRanges.size(); ++nRange)
            {
                ScRange const & rTokenRange( aSourceRanges[nRange]);
                SCTAB nSrcTab = rTokenRange.aStart.Tab();
                ScMarkData aSourceMark;
                aSourceMark.SelectOneTable( nSrcTab );      // selecting for CopyToClip
                aSourceMark.SetMarkArea( rTokenRange );

                ScClipParam aClipParam(rTokenRange, false);
                rSrcDoc.CopyToClip(aClipParam, &aClipDoc, &aSourceMark, false, false);

                if ( aClipDoc.HasAttrib( 0,0,nSrcTab, MAXCOL,MAXROW,nSrcTab,
                            HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                {
                    //! ResetAttrib at document !!!

                    ScPatternAttr aPattern( rSrcDoc.GetPool() );
                    aPattern.GetItemSet().Put( ScMergeAttr() );             // Defaults
                    aPattern.GetItemSet().Put( ScMergeFlagAttr() );
                    aClipDoc.ApplyPatternAreaTab( 0,0, MAXCOL,MAXROW, nSrcTab, aPattern );
                }

                aNewTokenRange.aEnd.SetCol( aNewTokenRange.aStart.Col() + (rTokenRange.aEnd.Col() - rTokenRange.aStart.Col()) );
                aNewTokenRange.aEnd.SetRow( aNewTokenRange.aStart.Row() + (rTokenRange.aEnd.Row() - rTokenRange.aStart.Row()) );
                ScMarkData aDestMark;
                aDestMark.SelectOneTable( nDestTab );
                aDestMark.SetMarkArea( aNewTokenRange );
                rDoc.CopyFromClip( aNewTokenRange, aDestMark, InsertDeleteFlags::ALL, nullptr, &aClipDoc, false );
                aNewTokenRange.aStart.SetRow( aNewTokenRange.aEnd.Row() + 2 );
            }
        }
        else
        {
            OUString aErr = ScResId(STR_LINKERROR);
            rDoc.SetString( aDestPos.Col(), aDestPos.Row(), aDestPos.Tab(), aErr );
        }

        //  enter Undo

        if ( bAddUndo && bUndo)
        {
            ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO ));
            pRedoDoc->InitUndo( &rDoc, nDestTab, nDestTab );
            rDoc.CopyToDocument(aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pRedoDoc);

            m_pDocSh->GetUndoManager()->AddUndoAction(
                o3tl::make_unique<ScUndoUpdateAreaLink>( m_pDocSh,
                                            aFileName, aFilterName, aOptions,
                                            aSourceArea, aOldRange, GetRefreshDelay(),
                                            aNewUrl, rNewFilter, aNewOpt,
                                            rNewArea, aNewRange, nNewRefresh,
                                            std::move(pUndoDoc), std::move(pRedoDoc), bDoInsert ) );
        }

        //  remember new settings

        if ( bNewUrlName )
            aFileName = aNewUrl;
        if ( rNewFilter != aFilterName )
            aFilterName = rNewFilter;
        if ( rNewArea != aSourceArea )
            aSourceArea = rNewArea;
        if ( aNewOpt != aOptions )
            aOptions = aNewOpt;

        if ( aNewRange != aDestArea )
            aDestArea = aNewRange;

        if ( nNewRefresh != GetRefreshDelay() )
            SetRefreshDelay( nNewRefresh );

        SCCOL nPaintEndX = std::max( aOldRange.aEnd.Col(), aNewRange.aEnd.Col() );
        SCROW nPaintEndY = std::max( aOldRange.aEnd.Row(), aNewRange.aEnd.Row() );

        if ( aOldRange.aEnd.Col() != aNewRange.aEnd.Col() )
            nPaintEndX = MAXCOL;
        if ( aOldRange.aEnd.Row() != aNewRange.aEnd.Row() )
            nPaintEndY = MAXROW;

        if ( !m_pDocSh->AdjustRowHeight( aDestPos.Row(), nPaintEndY, nDestTab ) )
            m_pDocSh->PostPaint(
                ScRange(aDestPos.Col(), aDestPos.Row(), nDestTab, nPaintEndX, nPaintEndY, nDestTab),
                PaintPartFlags::Grid);
        aModificator.SetDocumentModified();
    }
    else
    {
        //  CanFitBlock sal_False -> Problems with summarized cells or table boundary reached!
        //! cell protection ???

        //! Link dialog must set default parent
        //  "cannot insert rows"
        vcl::Window* pWin = Application::GetDefDialogParent();
        std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr,
                                                      VclMessageType::Info, VclButtonsType::Ok,
                                                      ScResId(STR_MSSG_DOSUBTOTALS_2)));
        xInfoBox->run();
    }

    //  clean up

    aRef->DoClose();

    rDoc.SetInLinkUpdate( false );

    if (bCanDo)
    {
        //  notify Uno objects (for XRefreshListener)
        //! also notify Uno objects if file name was changed!
        ScLinkRefreshedHint aHint;
        aHint.SetAreaLink( aDestPos );
        rDoc.BroadcastUno( aHint );
    }

    return bCanDo;
}

IMPL_LINK_NOARG(ScAreaLink, RefreshHdl, Timer *, void)
{
    Refresh( aFileName, aFilterName, aSourceArea, GetRefreshDelay() );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */