Coverage Report

Created: 2026-08-14 10:22

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