Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/navipi/content.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 <sal/config.h>
21
22
#include <string_view>
23
24
#include <svx/svditer.hxx>
25
#include <svx/svdobj.hxx>
26
#include <svx/svdview.hxx>
27
#include <sfx2/linkmgr.hxx>
28
#include <sfx2/docfile.hxx>
29
#include <sfx2/viewfrm.hxx>
30
#include <vcl/commandevent.hxx>
31
#include <vcl/svapp.hxx>
32
#include <osl/diagnose.h>
33
#include <tools/urlobj.hxx>
34
#include <sal/log.hxx>
35
#include <unotools/charclass.hxx>
36
37
#include <content.hxx>
38
#include <navipi.hxx>
39
#include <global.hxx>
40
#include <docsh.hxx>
41
#include <docfunc.hxx>
42
#include <scmod.hxx>
43
#include <rangenam.hxx>
44
#include <dbdata.hxx>
45
#include <drwlayer.hxx>
46
#include <transobj.hxx>
47
#include <drwtrans.hxx>
48
#include <lnktrans.hxx>
49
#include <strings.hrc>
50
#include <scresid.hxx>
51
#include <bitmaps.hlst>
52
#include <arealink.hxx>
53
#include <navicfg.hxx>
54
#include <navsett.hxx>
55
#include <postit.hxx>
56
#include <tabvwsh.hxx>
57
#include <drawview.hxx>
58
#include <clipparam.hxx>
59
#include <markdata.hxx>
60
61
using namespace com::sun::star;
62
63
//  order of the categories in navigator -------------------------------------
64
65
const ScContentId pTypeList[int(ScContentId::LAST) + 1] =
66
{
67
    ScContentId::ROOT,            // ROOT (0) has to be at the front
68
    ScContentId::TABLE,
69
    ScContentId::RANGENAME,
70
    ScContentId::DBAREA,
71
    ScContentId::AREALINK,
72
    ScContentId::GRAPHIC,
73
    ScContentId::OLEOBJECT,
74
    ScContentId::NOTE,
75
    ScContentId::DRAWING
76
};
77
78
constexpr OUString aContentBmps[]=
79
{
80
    RID_BMP_CONTENT_TABLE,
81
    RID_BMP_CONTENT_RANGENAME,
82
    RID_BMP_CONTENT_DBAREA,
83
    RID_BMP_CONTENT_GRAPHIC,
84
    RID_BMP_CONTENT_OLEOBJECT,
85
    RID_BMP_CONTENT_NOTE,
86
    RID_BMP_CONTENT_AREALINK,
87
    RID_BMP_CONTENT_DRAWING
88
};
89
90
ScDocShell* ScContentTree::GetManualOrCurrent()
91
0
{
92
0
    ScDocShell* pSh = nullptr;
93
0
    if ( !aManualDoc.isEmpty() )
94
0
    {
95
0
        SfxObjectShell* pObjSh = SfxObjectShell::GetFirst( checkSfxObjectShell<ScDocShell> );
96
0
        while ( pObjSh && !pSh )
97
0
        {
98
0
            if ( pObjSh->GetTitle() == aManualDoc )
99
0
                pSh = dynamic_cast<ScDocShell*>( pObjSh  );
100
0
            pObjSh = SfxObjectShell::GetNext( *pObjSh, checkSfxObjectShell<ScDocShell> );
101
0
        }
102
0
    }
103
0
    else
104
0
    {
105
        //  only current when manual isn't set
106
        //  (so it's detected when the documents don't exists any longer)
107
108
0
        SfxViewShell* pViewSh = SfxViewShell::Current();
109
0
        if ( pViewSh )
110
0
        {
111
0
            SfxObjectShell* pObjSh = pViewSh->GetViewFrame().GetObjectShell();
112
0
            pSh = dynamic_cast<ScDocShell*>( pObjSh  );
113
0
        }
114
0
    }
115
116
0
    return pSh;
117
0
}
118
119
//          ScContentTree
120
121
ScContentTree::ScContentTree(std::unique_ptr<weld::TreeView> xTreeView, ScNavigatorDlg* pNavigatorDlg)
122
0
    : m_xTreeView(std::move(xTreeView))
123
0
    , m_xScratchIter(m_xTreeView->make_iterator())
124
0
    , m_xTransferObj(new ScLinkTransferObj)
125
0
    , pParentWindow(pNavigatorDlg)
126
0
    , nRootType(ScContentId::ROOT)
127
0
    , bIsInNavigatorDlg(false)
128
0
    , m_bFreeze(false)
129
0
    , m_nAsyncMouseReleaseId(nullptr)
130
0
{
131
0
    for (sal_uInt16 i = 0; i <= int(ScContentId::LAST); ++i)
132
0
        pPosList[pTypeList[i]] = i;         // inverse for searching
133
134
0
    m_aRootNodes[ScContentId::ROOT] = nullptr;
135
0
    for (sal_uInt16 i = 1; i < int(ScContentId::LAST); ++i)
136
0
        InitRoot(static_cast<ScContentId>(i));
137
138
0
    m_xTreeView->connect_row_activated(LINK(this, ScContentTree, ContentDoubleClickHdl));
139
0
    m_xTreeView->connect_mouse_release(LINK(this, ScContentTree, MouseReleaseHdl));
140
0
    m_xTreeView->connect_key_press(LINK(this, ScContentTree, KeyInputHdl));
141
0
    m_xTreeView->connect_popup_menu(LINK(this, ScContentTree, CommandHdl));
142
0
    m_xTreeView->connect_query_tooltip(LINK(this, ScContentTree, QueryTooltipHdl));
143
144
0
    rtl::Reference<TransferDataContainer> xHelper(m_xTransferObj);
145
0
    m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);
146
147
0
    m_xTreeView->connect_drag_begin(LINK(this, ScContentTree, DragBeginHdl));
148
149
0
    m_xTreeView->set_selection_mode( SelectionMode::Single );
150
151
0
    m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 30,
152
0
                                  m_xTreeView->get_text_height() * 13);
153
0
}
154
155
ScContentTree::~ScContentTree()
156
0
{
157
0
    if (m_nAsyncMouseReleaseId)
158
0
    {
159
0
        Application::RemoveUserEvent(m_nAsyncMouseReleaseId);
160
0
        m_nAsyncMouseReleaseId = nullptr;
161
0
    }
162
0
}
163
164
const TranslateId SCSTR_CONTENT_ARY[] =
165
{
166
    SCSTR_CONTENT_ROOT,
167
    SCSTR_CONTENT_TABLE,
168
    SCSTR_CONTENT_RANGENAME,
169
    SCSTR_CONTENT_DBAREA,
170
    SCSTR_CONTENT_GRAPHIC,
171
    SCSTR_CONTENT_OLEOBJECT,
172
    SCSTR_CONTENT_NOTE,
173
    SCSTR_CONTENT_AREALINK,
174
    SCSTR_CONTENT_DRAWING
175
};
176
177
void ScContentTree::InitRoot( ScContentId nType )
178
0
{
179
0
    if ( nType == ScContentId::ROOT )
180
0
        return;
181
182
0
    if ( nRootType != ScContentId::ROOT && nRootType != nType )              // hidden ?
183
0
    {
184
0
        m_aRootNodes[nType] = nullptr;
185
0
        return;
186
0
    }
187
188
0
    auto const & aImage = aContentBmps[static_cast<int>(nType) - 1];
189
190
0
    OUString aName;
191
0
    if(comphelper::LibreOfficeKit::isActive())
192
0
    {
193
        //In case of LOK we may have many different ScContentTrees in different languages.
194
        //At creation time, we store what language we use, and then use it later too.
195
        //It does not work in the constructor, that is why it is here.
196
0
        if (!m_pResLocaleForLOK)
197
0
        {
198
0
            m_pResLocaleForLOK = std::make_unique<std::locale>(ScModule::get()->GetResLocale());
199
0
        }
200
0
        aName = Translate::get(SCSTR_CONTENT_ARY[static_cast<int>(nType)], *m_pResLocaleForLOK);
201
0
    }
202
0
    else
203
0
    {
204
0
        aName = ScResId(SCSTR_CONTENT_ARY[static_cast<int>(nType)]);
205
0
    }
206
    // back to the correct position:
207
0
    sal_uInt16 nPos = nRootType != ScContentId::ROOT ? 0 : pPosList[nType]-1;
208
0
    m_aRootNodes[nType] = m_xTreeView->make_iterator();
209
0
    m_xTreeView->insert(nullptr, nPos, &aName, nullptr, nullptr, nullptr, false, m_aRootNodes[nType].get());
210
0
    m_xTreeView->set_image(*m_aRootNodes[nType], aImage);
211
0
}
212
213
void ScContentTree::ClearAll()
214
0
{
215
    //There are one method in Control::SetUpdateMode(), and one override method SvTreeListBox::SetUpdateMode(). Here although
216
    //SvTreeListBox::SetUpdateMode() is called in refresh method, it only call SvTreeListBox::SetUpdateMode(), not Control::SetUpdateMode().
217
    //In m_xTreeView->clear(), Broadcast( LISTACTION_CLEARED ) will be called and finally, it will be trapped into the event yield() loop. And
218
    //the InitRoot() method won't be called. Then if a user click or press key to update the navigator tree, crash happens.
219
    //So the solution is to disable the UpdateMode of Control, then call Clear(), then recover the update mode
220
0
    bool bWasFrozen = m_bFreeze;
221
0
    if (!bWasFrozen)
222
0
        freeze();
223
0
    m_xTreeView->clear();
224
0
    if (!bWasFrozen)
225
0
        thaw();
226
0
    for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++)
227
0
        InitRoot(static_cast<ScContentId>(i));
228
0
}
229
230
void ScContentTree::ClearType(ScContentId nType)
231
0
{
232
0
    if (nType == ScContentId::ROOT)
233
0
        ClearAll();
234
0
    else
235
0
    {
236
0
        weld::TreeIter* pParent = m_aRootNodes[nType].get();
237
0
        if (!pParent || m_xTreeView->iter_has_child(*pParent)) // not if no children existing
238
0
        {
239
0
            if (pParent)
240
0
                m_xTreeView->remove(*pParent);          // with all children
241
0
            InitRoot( nType );                          // if needed insert anew
242
0
        }
243
0
    }
244
0
}
245
246
void ScContentTree::InsertContent( ScContentId nType, const OUString& rValue )
247
0
{
248
0
    weld::TreeIter* pParent = m_aRootNodes[nType].get();
249
0
    if (pParent)
250
0
    {
251
0
        m_xTreeView->insert(pParent, -1, &rValue, nullptr, nullptr, nullptr, false, m_xScratchIter.get());
252
0
        m_xTreeView->set_sensitive(*m_xScratchIter, true);
253
0
    }
254
0
    else
255
0
    {
256
0
        OSL_FAIL("InsertContent without parent");
257
0
    }
258
0
}
259
260
void ScContentTree::GetEntryIndexes(ScContentId& rnRootIndex, sal_uLong& rnChildIndex, const weld::TreeIter* pEntry) const
261
0
{
262
0
    rnRootIndex = ScContentId::ROOT;
263
0
    rnChildIndex = SC_CONTENT_NOCHILD;
264
265
0
    if( !pEntry ) {
266
0
        SAL_WARN("sc", "got a null TreeIter");
267
0
        return;
268
0
    }
269
270
0
    std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(pEntry));
271
0
    if (!m_xTreeView->iter_parent(*xParent))
272
0
        xParent.reset();
273
0
    bool bFound = false;
274
0
    for( int i = 1; !bFound && (i <= int(ScContentId::LAST)); ++i )
275
0
    {
276
0
        ScContentId nRoot = static_cast<ScContentId>(i);
277
0
        if (!m_aRootNodes[nRoot])
278
0
            continue;
279
0
        if (m_xTreeView->iter_compare(*pEntry, *m_aRootNodes[nRoot]) == 0)
280
0
        {
281
0
            rnRootIndex = nRoot;
282
0
            rnChildIndex = ~0UL;
283
0
            bFound = true;
284
0
        }
285
0
        else if (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[nRoot]) == 0)
286
0
        {
287
0
            rnRootIndex = nRoot;
288
289
            // search the entry in all child entries of the parent
290
0
            sal_uLong nEntry = 0;
291
0
            std::unique_ptr<weld::TreeIter> xIterEntry(m_xTreeView->make_iterator(xParent.get()));
292
0
            bool bIterEntry = m_xTreeView->iter_children(*xIterEntry);
293
0
            while (!bFound && bIterEntry)
294
0
            {
295
0
                if (m_xTreeView->iter_compare(*pEntry, *xIterEntry) == 0)
296
0
                {
297
0
                    rnChildIndex = nEntry;
298
0
                    bFound = true;  // exit the while loop
299
0
                }
300
0
                bIterEntry = m_xTreeView->iter_next_sibling(*xIterEntry);
301
0
                ++nEntry;
302
0
            }
303
304
0
            bFound = true;  // exit the for loop
305
0
        }
306
0
    }
307
0
}
308
309
sal_uLong ScContentTree::GetChildIndex(const weld::TreeIter* pEntry) const
310
0
{
311
0
    ScContentId nRoot;
312
0
    sal_uLong nChild;
313
0
    GetEntryIndexes(nRoot, nChild, pEntry);
314
0
    return nChild;
315
0
}
316
317
static OUString lcl_GetDBAreaRange( const ScDocument* pDoc, const OUString& rDBName )
318
0
{
319
0
    OUString aRet;
320
0
    if (pDoc)
321
0
    {
322
0
        ScDBCollection* pDbNames = pDoc->GetDBCollection();
323
0
        const ScDBData* pData = pDbNames->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName));
324
0
        if (pData)
325
0
        {
326
0
            ScRange aRange;
327
0
            pData->GetArea(aRange);
328
0
            aRet = aRange.Format(*pDoc, ScRefFlags::RANGE_ABS_3D);
329
0
        }
330
0
    }
331
0
    return aRet;
332
0
}
333
334
IMPL_LINK_NOARG(ScContentTree, ContentDoubleClickHdl, weld::TreeView&, bool)
335
0
{
336
0
    ScContentId nType;
337
0
    sal_uLong nChild;
338
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
339
0
    if (!m_xTreeView->get_cursor(xEntry.get()))
340
0
        xEntry.reset();
341
0
    GetEntryIndexes(nType, nChild, xEntry.get());
342
343
0
    if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD))
344
0
    {
345
0
        OUString aText(m_xTreeView->get_text(*xEntry));
346
347
0
        if ( !aManualDoc.isEmpty() )
348
0
            pParentWindow->SetCurrentDoc( aManualDoc );
349
350
0
        switch( nType )
351
0
        {
352
0
            case ScContentId::TABLE:
353
0
            {
354
                // tdf#133159 store current config before changing sheet
355
                // plausible that this should be done for all cases, but this
356
                // is the known case that needs it
357
0
                StoreNavigatorSettings();
358
0
                pParentWindow->SetCurrentTableStr( aText );
359
0
            }
360
0
            break;
361
362
0
            case ScContentId::RANGENAME:
363
0
                pParentWindow->SetCurrentCellStr( aText );
364
0
            break;
365
366
0
            case ScContentId::DBAREA:
367
0
            {
368
                //  If the same names of area and DB exists, then
369
                //  SID_CURRENTCELL takes the area name.
370
                //  Therefore for DB areas access them directly via address.
371
372
0
                OUString aRangeStr = lcl_GetDBAreaRange( GetSourceDocument(), aText );
373
0
                if (!aRangeStr.isEmpty())
374
0
                    pParentWindow->SetCurrentCellStr( aRangeStr );
375
0
            }
376
0
            break;
377
378
0
            case ScContentId::OLEOBJECT:
379
0
            case ScContentId::GRAPHIC:
380
0
            case ScContentId::DRAWING:
381
0
                pParentWindow->SetCurrentObject( aText );
382
0
            break;
383
384
0
            case ScContentId::NOTE:
385
0
            {
386
0
                ScAddress aPos = GetNotePos( nChild );
387
0
                pParentWindow->SetCurrentTable( aPos.Tab() );
388
0
                pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() );
389
                // Check whether the comment is currently visible and toggle its visibility
390
0
                ScDocument* pSrcDoc = GetSourceDocument();
391
0
                if (ScPostIt* pNote = pSrcDoc ? pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab()) : nullptr)
392
0
                {
393
0
                    bool bVisible = pNote->IsCaptionShown();
394
                    // Effectivelly set the visibility of the comment
395
0
                    GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, !bVisible);
396
                    // Put the note in edit mode
397
0
                    ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
398
0
                    pScTabViewShell->EditNote();
399
0
                }
400
0
            }
401
0
            break;
402
403
0
            case ScContentId::AREALINK:
404
0
            {
405
0
                const ScAreaLink* pLink = GetLink(nChild);
406
0
                ScDocument* pSrcDoc = GetSourceDocument();
407
0
                if (pLink && pSrcDoc)
408
0
                {
409
0
                    const ScRange& aRange = pLink->GetDestArea();
410
0
                    OUString aRangeStr(aRange.Format(*pSrcDoc, ScRefFlags::RANGE_ABS_3D, pSrcDoc->GetAddressConvention()));
411
0
                    pParentWindow->SetCurrentCellStr( aRangeStr );
412
0
                }
413
0
            }
414
0
            break;
415
0
            default: break;
416
0
        }
417
418
0
        ScNavigatorDlg::ReleaseFocus();     // set focus into document
419
0
    }
420
421
0
    return false;
422
0
}
423
424
void ScContentTree::LaunchAsyncStoreNavigatorSettings()
425
0
{
426
0
    if (!m_nAsyncMouseReleaseId)
427
0
        m_nAsyncMouseReleaseId = Application::PostUserEvent(LINK(this, ScContentTree, AsyncStoreNavigatorSettings));
428
0
}
429
430
IMPL_LINK_NOARG(ScContentTree, MouseReleaseHdl, const MouseEvent&, bool)
431
0
{
432
0
    LaunchAsyncStoreNavigatorSettings();
433
0
    return false;
434
0
}
435
436
IMPL_LINK_NOARG(ScContentTree, AsyncStoreNavigatorSettings, void*, void)
437
0
{
438
0
    m_nAsyncMouseReleaseId = nullptr;
439
0
    StoreNavigatorSettings();
440
0
}
441
442
IMPL_LINK(ScContentTree, KeyInputHdl, const KeyEvent&, rKEvt, bool)
443
0
{
444
0
    bool bUsed = false;
445
446
0
    const vcl::KeyCode aCode = rKEvt.GetKeyCode();
447
0
    if (aCode.GetCode() == KEY_RETURN)
448
0
    {
449
0
        switch (aCode.GetModifier())
450
0
        {
451
0
            case KEY_MOD1:
452
0
                ToggleRoot();       // toggle root mode (as in Writer)
453
0
                bUsed = true;
454
0
                break;
455
0
            case 0:
456
0
            {
457
0
                std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
458
0
                if (!m_xTreeView->get_cursor(xEntry.get()))
459
0
                    xEntry.reset();
460
0
                if (xEntry)
461
0
                {
462
0
                    ScContentId nType;
463
0
                    sal_uLong nChild;
464
0
                    GetEntryIndexes(nType, nChild, xEntry.get());
465
466
0
                    if (nType != ScContentId::ROOT && nChild == SC_CONTENT_NOCHILD)
467
0
                    {
468
0
                        if (m_xTreeView->get_row_expanded(*xEntry))
469
0
                            m_xTreeView->collapse_row(*xEntry);
470
0
                        else
471
0
                            m_xTreeView->expand_row(*xEntry);
472
0
                    }
473
0
                    else
474
0
                        ContentDoubleClickHdl(*m_xTreeView);      // select content as if double clicked
475
0
                }
476
477
0
                bUsed = true;
478
0
            }
479
0
            break;
480
0
        }
481
0
    }
482
    //Make KEY_SPACE has same function as DoubleClick, and realize
483
    //multi-selection.
484
0
    if ( bIsInNavigatorDlg )
485
0
    {
486
0
        if(aCode.GetCode() == KEY_SPACE )
487
0
        {
488
0
            bUsed = true;
489
0
            ScContentId nType;
490
0
            sal_uLong nChild;
491
0
            std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
492
0
            if (!m_xTreeView->get_cursor(xEntry.get()))
493
0
                xEntry.reset();
494
0
            GetEntryIndexes(nType, nChild, xEntry.get());
495
496
0
            if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD))
497
0
            {
498
0
                OUString aText(m_xTreeView->get_text(*xEntry));
499
0
                if (!aManualDoc.isEmpty())
500
0
                    pParentWindow->SetCurrentDoc( aManualDoc );
501
0
                switch (nType)
502
0
                {
503
0
                    case ScContentId::OLEOBJECT:
504
0
                    case ScContentId::GRAPHIC:
505
0
                    case ScContentId::DRAWING:
506
0
                    {
507
0
                        ScDrawView* pScDrawView = nullptr;
508
0
                        ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
509
0
                        if (pScTabViewShell)
510
0
                            pScDrawView = pScTabViewShell->GetViewData().GetScDrawView();
511
0
                        if (pScDrawView)
512
0
                        {
513
0
                            pScDrawView->SelectCurrentViewObject(aText);
514
0
                            bool bHasMakredObject = false;
515
0
                            weld::TreeIter* pParent = m_aRootNodes[nType].get();
516
0
                            std::unique_ptr<weld::TreeIter> xBeginEntry(m_xTreeView->make_iterator(pParent));
517
0
                            bool bBeginEntry = false;
518
0
                            if (pParent)
519
0
                                bBeginEntry = m_xTreeView->iter_children(*xBeginEntry);
520
0
                            while (bBeginEntry)
521
0
                            {
522
0
                                OUString aTempText(m_xTreeView->get_text(*xBeginEntry));
523
0
                                if( pScDrawView->GetObjectIsMarked( pScDrawView->GetObjectByName( aTempText ) ) )
524
0
                                {
525
0
                                    bHasMakredObject = true;
526
0
                                    break;
527
0
                                }
528
0
                                bBeginEntry = m_xTreeView->iter_next(*xBeginEntry);
529
0
                            }
530
0
                            if (!bHasMakredObject && pScTabViewShell)
531
0
                                pScTabViewShell->SetDrawShell(false);
532
0
                        }
533
0
                        break;
534
0
                    }
535
0
                    default:
536
0
                        break;
537
0
                }
538
0
            }
539
0
        }
540
0
    }
541
542
0
    if (!bUsed)
543
0
    {
544
0
        if (aCode.GetCode() == KEY_F5)
545
0
            StoreNavigatorSettings();
546
0
        else
547
0
            LaunchAsyncStoreNavigatorSettings();
548
0
    }
549
550
0
    return bUsed;
551
0
}
552
553
IMPL_LINK(ScContentTree, CommandHdl, const CommandEvent&, rCEvt, bool)
554
0
{
555
0
    bool bDone = false;
556
557
0
    ScContentId nType;
558
0
    sal_uLong nChild;
559
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
560
0
    if (!m_xTreeView->get_cursor(xEntry.get()))
561
0
        xEntry.reset();
562
0
    GetEntryIndexes(nType, nChild, xEntry.get());
563
564
0
    switch ( rCEvt.GetCommand() )
565
0
    {
566
0
        case CommandEventId::ContextMenu:
567
0
            {
568
0
                if (comphelper::LibreOfficeKit::isActive())
569
0
                    break;
570
571
                //  drag-and-drop mode
572
0
                std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"modules/scalc/ui/dropmenu.ui"_ustr));
573
0
                std::unique_ptr<weld::Menu> xPop(xBuilder->weld_menu(u"contextmenu"_ustr));
574
0
                std::unique_ptr<weld::Menu> xDropMenu(xBuilder->weld_menu(u"dragmodesubmenu"_ustr));
575
576
0
                switch (pParentWindow->GetDropMode())
577
0
                {
578
0
                    case 0:
579
0
                        xDropMenu->set_active(u"hyperlink"_ustr, true);
580
0
                        break;
581
0
                    case 1:
582
0
                        xDropMenu->set_active(u"link"_ustr, true);
583
0
                        break;
584
0
                    case 2:
585
0
                        xDropMenu->set_active(u"copy"_ustr, true);
586
0
                        break;
587
0
                }
588
589
                //  displayed document
590
0
                std::unique_ptr<weld::Menu> xDocMenu(xBuilder->weld_menu(u"displaymenu"_ustr));
591
0
                sal_uInt16 i=0;
592
0
                OUString sActive;
593
0
                OUString sId;
594
                //  loaded documents
595
0
                ScDocShell* pCurrentSh = dynamic_cast<ScDocShell*>( SfxObjectShell::Current()  );
596
0
                SfxObjectShell* pSh = SfxObjectShell::GetFirst();
597
0
                while ( pSh )
598
0
                {
599
0
                    if ( dynamic_cast<const ScDocShell*>( pSh) !=  nullptr )
600
0
                    {
601
0
                        OUString aName = pSh->GetTitle();
602
0
                        OUString aEntry = aName;
603
0
                        if ( pSh == pCurrentSh )
604
0
                            aEntry += pParentWindow->aStrActive;
605
0
                        else
606
0
                            aEntry += pParentWindow->aStrNotActive;
607
0
                        ++i;
608
0
                        sId = "document" + OUString::number(i);
609
0
                        xDocMenu->append_radio(sId, aEntry);
610
0
                        if (aName == aManualDoc)
611
0
                            sActive = sId;
612
0
                    }
613
0
                    pSh = SfxObjectShell::GetNext( *pSh );
614
0
                }
615
                //  "active window"
616
0
                ++i;
617
0
                sId = "document" + OUString::number(i);
618
0
                xDocMenu->append_radio(sId, pParentWindow->aStrActiveWin);
619
0
                if (aManualDoc.isEmpty())
620
0
                    sActive = sId;
621
0
                xDocMenu->set_active(sActive, true);
622
623
                // Edit/Delete Comments are only visible for comments
624
0
                if (nType != ScContentId::NOTE)
625
0
                {
626
0
                    xPop->set_visible(u"edit"_ustr, false);
627
0
                    xPop->set_visible(u"delete"_ustr, false);
628
0
                }
629
630
0
                OUString sIdent = xPop->popup_at_rect(m_xTreeView.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1)));
631
0
                if (sIdent == "hyperlink")
632
0
                    pParentWindow->SetDropMode(0);
633
0
                else if (sIdent == "link")
634
0
                    pParentWindow->SetDropMode(1);
635
0
                else if (sIdent == "copy")
636
0
                    pParentWindow->SetDropMode(2);
637
0
                else if (sIdent.startsWith("document"))
638
0
                {
639
0
                    OUString aName = xDocMenu->get_label(sIdent);
640
0
                    SelectDoc(aName);
641
0
                }
642
0
                else if (sIdent == "edit")
643
0
                {
644
0
                    ScAddress aPos = GetNotePos( nChild );
645
0
                    pParentWindow->SetCurrentTable( aPos.Tab() );
646
0
                    pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() );
647
0
                    ScDocument* pSrcDoc = GetSourceDocument();
648
0
                    if (pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab()))
649
0
                    {
650
                        // Make the note visible and put it in edit mode
651
0
                        GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, true);
652
0
                        ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
653
0
                        pScTabViewShell->EditNote();
654
0
                        bDone = true;
655
0
                    }
656
0
                }
657
0
                else if (sIdent == "delete")
658
0
                {
659
0
                    ScAddress aPos = GetNotePos(nChild);
660
0
                    pParentWindow->SetCurrentTable(aPos.Tab());
661
0
                    pParentWindow->SetCurrentCell(aPos.Col(), aPos.Row());
662
0
                    ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell();
663
0
                    pScTabViewShell->DeleteContents(InsertDeleteFlags::NOTE);
664
0
                }
665
0
            }
666
0
            break;
667
0
            default: break;
668
0
    }
669
670
0
    return bDone;
671
0
}
672
673
IMPL_LINK(ScContentTree, QueryTooltipHdl, const weld::TreeIter&, rEntry, OUString)
674
0
{
675
0
    OUString aHelpText;
676
677
0
    std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(&rEntry));
678
0
    if (!m_xTreeView->iter_parent(*xParent))
679
0
        xParent.reset();
680
681
0
    if (!xParent)                                 // Top-Level ?
682
0
    {
683
0
        aHelpText = OUString::number(m_xTreeView->iter_n_children(rEntry)) +
684
0
                    " " + m_xTreeView->get_text(rEntry);
685
0
    }
686
0
    else if (m_aRootNodes[ScContentId::NOTE] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::NOTE]) == 0)
687
0
    {
688
0
        aHelpText = m_xTreeView->get_text(rEntry);     // notes as help text
689
0
    }
690
0
    else if (m_aRootNodes[ScContentId::AREALINK] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::AREALINK]) == 0)
691
0
    {
692
0
        auto nIndex = GetChildIndex(&rEntry);
693
0
        if (nIndex != SC_CONTENT_NOCHILD)
694
0
        {
695
0
            const ScAreaLink* pLink = GetLink(nIndex);
696
0
            if (pLink)
697
0
            {
698
0
                aHelpText = pLink->GetFile();           // source file as help text
699
0
            }
700
0
        }
701
0
    }
702
703
0
    return aHelpText;
704
0
}
705
706
ScDocument* ScContentTree::GetSourceDocument()
707
0
{
708
0
    ScDocShell* pSh = GetManualOrCurrent();
709
0
    if (pSh)
710
0
        return &pSh->GetDocument();
711
0
    return nullptr;
712
0
}
713
714
void ScContentTree::Refresh( ScContentId nType )
715
0
{
716
    //  if nothing has changed the cancel right away (against flicker)
717
0
    if ( nType == ScContentId::NOTE )
718
0
        if (!NoteStringsChanged())
719
0
            return;
720
0
    if ( nType == ScContentId::GRAPHIC )
721
0
        if (!DrawNamesChanged(ScContentId::GRAPHIC))
722
0
            return;
723
0
    if ( nType == ScContentId::OLEOBJECT )
724
0
        if (!DrawNamesChanged(ScContentId::OLEOBJECT))
725
0
            return;
726
0
    if ( nType == ScContentId::DRAWING )
727
0
        if (!DrawNamesChanged(ScContentId::DRAWING))
728
0
            return;
729
730
0
    freeze();
731
732
0
    ClearType( nType );
733
734
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::TABLE )
735
0
        GetTableNames();
736
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::RANGENAME )
737
0
        GetAreaNames();
738
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::DBAREA )
739
0
        GetDbNames();
740
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::GRAPHIC )
741
0
        GetGraphicNames();
742
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::OLEOBJECT )
743
0
        GetOleNames();
744
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::DRAWING )
745
0
        GetDrawingNames();
746
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::NOTE )
747
0
        GetNoteStrings();
748
0
    if ( nType == ScContentId::ROOT || nType == ScContentId::AREALINK )
749
0
        GetLinkNames();
750
751
0
    thaw();
752
753
0
    ApplyNavigatorSettings();
754
0
}
755
756
void ScContentTree::GetTableNames()
757
0
{
758
0
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::TABLE )       // hidden ?
759
0
        return;
760
761
0
    ScDocument* pDoc = GetSourceDocument();
762
0
    if (!pDoc)
763
0
        return;
764
765
0
    OUString aName;
766
0
    SCTAB nCount = pDoc->GetTableCount();
767
0
    for ( SCTAB i=0; i<nCount; i++ )
768
0
    {
769
0
        pDoc->GetName( i, aName );
770
0
        InsertContent( ScContentId::TABLE, aName );
771
0
    }
772
0
}
773
774
namespace {
775
776
OUString createLocalRangeName(std::u16string_view rName, std::u16string_view rTableName)
777
0
{
778
0
    return OUString::Concat(rName) + " (" + rTableName + ")";
779
0
}
780
}
781
782
void ScContentTree::GetAreaNames()
783
0
{
784
0
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::RANGENAME )       // hidden ?
785
0
        return;
786
787
0
    ScDocument* pDoc = GetSourceDocument();
788
0
    if (!pDoc)
789
0
        return;
790
791
0
    ScRange aDummy;
792
0
    std::set<OUString> aSet;
793
0
    ScRangeName* pRangeNames = pDoc->GetRangeName();
794
0
    for (const auto& rEntry : *pRangeNames)
795
0
    {
796
0
        if (rEntry.second->IsValidReference(aDummy))
797
0
            aSet.insert(rEntry.second->GetName());
798
0
    }
799
0
    for (SCTAB i = 0; i < pDoc->GetTableCount(); ++i)
800
0
    {
801
0
        ScRangeName* pLocalRangeName = pDoc->GetRangeName(i);
802
0
        if (pLocalRangeName && !pLocalRangeName->empty())
803
0
        {
804
0
            OUString aTableName;
805
0
            pDoc->GetName(i, aTableName);
806
0
            for (const auto& rEntry : *pLocalRangeName)
807
0
            {
808
0
                if (rEntry.second->IsValidReference(aDummy))
809
0
                    aSet.insert(createLocalRangeName(rEntry.second->GetName(), aTableName));
810
0
            }
811
0
        }
812
0
    }
813
814
0
    for (const auto& rItem : aSet)
815
0
    {
816
0
        InsertContent(ScContentId::RANGENAME, rItem);
817
0
    }
818
0
}
819
820
void ScContentTree::GetDbNames()
821
0
{
822
0
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::DBAREA )      // hidden ?
823
0
        return;
824
825
0
    ScDocument* pDoc = GetSourceDocument();
826
0
    if (!pDoc)
827
0
        return;
828
829
0
    ScDBCollection* pDbNames = pDoc->GetDBCollection();
830
0
    const ScDBCollection::NamedDBs& rDBs = pDbNames->getNamedDBs();
831
0
    for (const auto& rxDB : rDBs)
832
0
    {
833
0
        const OUString& aStrName = rxDB->GetName();
834
0
        InsertContent(ScContentId::DBAREA, aStrName);
835
0
    }
836
0
}
837
838
bool ScContentTree::IsPartOfType( ScContentId nContentType, SdrObjKind nObjIdentifier )
839
0
{
840
0
    bool bRet = false;
841
0
    switch ( nContentType )
842
0
    {
843
0
        case ScContentId::GRAPHIC:
844
0
            bRet = ( nObjIdentifier == SdrObjKind::Graphic );
845
0
            break;
846
0
        case ScContentId::OLEOBJECT:
847
0
            bRet = ( nObjIdentifier == SdrObjKind::OLE2 );
848
0
            break;
849
0
        case ScContentId::DRAWING:
850
0
            bRet = ( nObjIdentifier != SdrObjKind::Graphic && nObjIdentifier != SdrObjKind::OLE2 );    // everything else
851
0
            break;
852
0
        default:
853
0
            OSL_FAIL("unknown content type");
854
0
    }
855
0
    return bRet;
856
0
}
857
858
constexpr int MAX_TREE_NODES = 1000;
859
860
void ScContentTree::GetDrawNames( ScContentId nType )
861
0
{
862
0
    if (!bIsInNavigatorDlg)
863
0
        return;
864
865
0
    if ( nRootType != ScContentId::ROOT && nRootType != nType )              // hidden ?
866
0
        return;
867
868
0
    ScDocument* pDoc = GetSourceDocument();
869
0
    if (!pDoc)
870
0
        return;
871
872
0
    ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer();
873
0
    if (!pDrawLayer)
874
0
        return;
875
876
0
    ScDocShell* pShell = pDoc->GetDocumentShell();
877
0
    if (!pShell)
878
0
        return;
879
880
    // iterate in flat mode for groups
881
0
    SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups;
882
883
0
    std::vector<OUString> aNames;
884
0
    SCTAB nTabCount = pDoc->GetTableCount();
885
0
    for (SCTAB nTab=0; nTab<nTabCount; nTab++)
886
0
    {
887
0
        SdrPage* pPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
888
0
        OSL_ENSURE(pPage,"Page ?");
889
0
        if (!pPage)
890
0
            continue;
891
0
        SdrObjListIter aIter(pPage, eIter);
892
0
        SdrObject* pObject = aIter.Next();
893
0
        while (pObject)
894
0
        {
895
0
            if (IsPartOfType(nType, pObject->GetObjIdentifier()))
896
0
            {
897
0
                OUString aName = ScDrawLayer::GetVisibleName(pObject);
898
0
                if (!aName.isEmpty())
899
0
                    aNames.push_back(aName);
900
0
                if (aNames.size() > MAX_TREE_NODES)
901
0
                {
902
0
                    SAL_WARN("sc", "too many tree nodes, ignoring the rest");
903
0
                    break;
904
0
                }
905
0
            }
906
0
            pObject = aIter.Next();
907
0
        }
908
0
    }
909
910
0
    weld::TreeIter* pParent = m_aRootNodes[nType].get();
911
0
    assert(pParent && "InsertContent without parent");
912
    // insert all of these in one go under pParent
913
0
    m_xTreeView->bulk_insert_for_each(aNames.size(), [this, &aNames](weld::TreeIter& rIter, int nIndex) {
914
0
        m_xTreeView->set_text(rIter, aNames[nIndex], 0);
915
0
        m_xTreeView->set_sensitive(rIter, true);
916
0
    }, pParent);
917
0
}
918
919
void ScContentTree::GetGraphicNames()
920
0
{
921
0
    GetDrawNames( ScContentId::GRAPHIC );
922
0
}
923
924
void ScContentTree::GetOleNames()
925
0
{
926
0
    GetDrawNames( ScContentId::OLEOBJECT );
927
0
}
928
929
void ScContentTree::GetDrawingNames()
930
0
{
931
0
    GetDrawNames( ScContentId::DRAWING );
932
0
}
933
934
void ScContentTree::GetLinkNames()
935
0
{
936
0
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::AREALINK )                // hidden ?
937
0
        return;
938
939
0
    ScDocument* pDoc = GetSourceDocument();
940
0
    if (!pDoc)
941
0
        return;
942
943
0
    sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager();
944
0
    assert(pLinkManager && "no LinkManager on document?");
945
0
    const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
946
0
    sal_uInt16 nCount = rLinks.size();
947
0
    for (sal_uInt16 i=0; i<nCount; i++)
948
0
    {
949
0
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
950
0
        if (auto pScAreaLink = dynamic_cast<const ScAreaLink*>( pBase))
951
0
            InsertContent( ScContentId::AREALINK, pScAreaLink->GetSource() );
952
953
            //  insert in list the names of source areas
954
0
    }
955
0
}
956
957
const ScAreaLink* ScContentTree::GetLink( sal_uLong nIndex )
958
0
{
959
0
    ScDocument* pDoc = GetSourceDocument();
960
0
    if (!pDoc)
961
0
        return nullptr;
962
963
0
    sal_uLong nFound = 0;
964
0
    sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager();
965
0
    assert(pLinkManager && "no LinkManager on document?");
966
0
    const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
967
0
    sal_uInt16 nCount = rLinks.size();
968
0
    for (sal_uInt16 i=0; i<nCount; i++)
969
0
    {
970
0
        ::sfx2::SvBaseLink* pBase = rLinks[i].get();
971
0
        if (auto pAreaLink = dynamic_cast<const ScAreaLink*>( pBase))
972
0
        {
973
0
            if (nFound == nIndex)
974
0
                return pAreaLink;
975
0
            ++nFound;
976
0
        }
977
0
    }
978
979
0
    OSL_FAIL("link not found");
980
0
    return nullptr;
981
0
}
982
983
static OUString lcl_NoteString( const ScPostIt& rNote )
984
0
{
985
0
    return rNote.GetText().replace('\n', ' ');
986
0
}
987
988
void ScContentTree::GetNoteStrings()
989
0
{
990
0
    if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::NOTE )        // hidden ?
991
0
        return;
992
993
0
    ScDocument* pDoc = GetSourceDocument();
994
0
    if (!pDoc)
995
0
        return;
996
997
    // loop over cell notes
998
0
    std::vector<sc::NoteEntry> aEntries;
999
0
    pDoc->GetAllNoteEntries(aEntries);
1000
0
    weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get();
1001
0
    for (const auto& rEntry : aEntries)
1002
0
    {
1003
0
        OUString aValue = lcl_NoteString(*rEntry.mpNote);
1004
0
        OUString aId = OUString::number(rEntry.mpNote->GetId());
1005
0
        m_xTreeView->insert(pParent, -1, &aValue, &aId, nullptr, nullptr, false, m_xScratchIter.get());
1006
0
        m_xTreeView->set_sensitive(*m_xScratchIter, true);
1007
0
    }
1008
0
}
1009
1010
ScAddress ScContentTree::GetNotePos( sal_uLong nIndex )
1011
0
{
1012
0
    ScDocument* pDoc = GetSourceDocument();
1013
0
    if (!pDoc)
1014
0
        return ScAddress();
1015
1016
0
    return pDoc->GetNotePosition(nIndex);
1017
0
}
1018
1019
bool ScContentTree::NoteStringsChanged()
1020
0
{
1021
0
    ScDocument* pDoc = GetSourceDocument();
1022
0
    if (!pDoc)
1023
0
        return false;
1024
1025
0
    weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get();
1026
0
    if (!pParent)
1027
0
        return false;
1028
1029
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
1030
0
    bool bEntry = m_xTreeView->iter_children(*xEntry);
1031
1032
0
    std::vector<sc::NoteEntry> aEntries;
1033
0
    pDoc->GetAllNoteEntries(aEntries);
1034
0
    for (const auto& rEntry : aEntries)
1035
0
    {
1036
0
        const ScPostIt* pNote = rEntry.mpNote;
1037
0
        if (!bEntry)
1038
0
            return true;
1039
1040
0
        if (lcl_NoteString(*pNote) != m_xTreeView->get_text(*xEntry))
1041
0
            return true;
1042
1043
0
        bEntry = m_xTreeView->iter_next_sibling(*xEntry);
1044
0
    }
1045
1046
0
    return bEntry;
1047
0
}
1048
1049
bool ScContentTree::DrawNamesChanged( ScContentId nType )
1050
0
{
1051
0
    ScDocument* pDoc = GetSourceDocument();
1052
0
    if (!pDoc)
1053
0
        return false;
1054
1055
0
    weld::TreeIter* pParent = m_aRootNodes[nType].get();
1056
0
    if (!pParent)
1057
0
        return false;
1058
1059
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
1060
0
    bool bEntry = m_xTreeView->iter_children(*xEntry);
1061
1062
    // iterate in flat mode for groups
1063
0
    SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups;
1064
1065
0
    bool bEqual = true;
1066
0
    ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer();
1067
0
    ScDocShell* pShell = pDoc->GetDocumentShell();
1068
0
    if (pDrawLayer && pShell)
1069
0
    {
1070
0
        SCTAB nTabCount = pDoc->GetTableCount();
1071
0
        for (SCTAB nTab=0; nTab<nTabCount && bEqual; nTab++)
1072
0
        {
1073
0
            SdrPage* pPage = pDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
1074
0
            OSL_ENSURE(pPage,"Page ?");
1075
0
            if (pPage)
1076
0
            {
1077
0
                SdrObjListIter aIter( pPage, eIter );
1078
0
                SdrObject* pObject = aIter.Next();
1079
0
                while (pObject && bEqual)
1080
0
                {
1081
0
                    if ( IsPartOfType( nType, pObject->GetObjIdentifier() ) )
1082
0
                    {
1083
0
                        if ( !bEntry )
1084
0
                            bEqual = false;
1085
0
                        else
1086
0
                        {
1087
0
                            if (ScDrawLayer::GetVisibleName(pObject) != m_xTreeView->get_text(*xEntry))
1088
0
                                bEqual = false;
1089
1090
0
                            bEntry = m_xTreeView->iter_next_sibling(*xEntry);
1091
0
                        }
1092
0
                    }
1093
0
                    pObject = aIter.Next();
1094
0
                }
1095
0
            }
1096
0
        }
1097
0
    }
1098
1099
0
    if ( bEntry )
1100
0
        bEqual = false;             // anything else
1101
1102
0
    return !bEqual;
1103
0
}
1104
1105
static bool lcl_GetRange( const ScDocument& rDoc, ScContentId nType, const OUString& rName, ScRange& rRange )
1106
0
{
1107
0
    bool bFound = false;
1108
1109
0
    if ( nType == ScContentId::RANGENAME )
1110
0
    {
1111
0
        ScRangeName* pList = rDoc.GetRangeName();
1112
0
        if (pList)
1113
0
        {
1114
0
            const ScRangeData* p = pList->findByUpperName(ScGlobal::getCharClass().uppercase(rName));
1115
0
            if (p && p->IsValidReference(rRange))
1116
0
                bFound = true;
1117
0
        }
1118
0
    }
1119
0
    else if ( nType == ScContentId::DBAREA )
1120
0
    {
1121
0
        ScDBCollection* pList = rDoc.GetDBCollection();
1122
0
        if (pList)
1123
0
        {
1124
0
            const ScDBData* p = pList->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rName));
1125
0
            if (p)
1126
0
            {
1127
0
                SCTAB nTab;
1128
0
                SCCOL nCol1, nCol2;
1129
0
                SCROW nRow1, nRow2;
1130
0
                p->GetArea(nTab, nCol1, nRow1, nCol2, nRow2);
1131
0
                rRange = ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab);
1132
0
                bFound = true;
1133
0
            }
1134
0
        }
1135
0
    }
1136
1137
0
    return bFound;
1138
0
}
1139
1140
static bool lcl_DoDragObject( ScDocShell& rSrcShell, std::u16string_view rName, ScContentId nType, weld::TreeView& rTreeView )
1141
0
{
1142
0
    bool bDisallow = true;
1143
1144
0
    ScDocument& rSrcDoc = rSrcShell.GetDocument();
1145
0
    ScDrawLayer* pModel = rSrcDoc.GetDrawLayer();
1146
0
    if (pModel)
1147
0
    {
1148
0
        bool bOle = ( nType == ScContentId::OLEOBJECT );
1149
0
        bool bGraf = ( nType == ScContentId::GRAPHIC );
1150
0
        SdrObjKind nDrawId = bOle ? SdrObjKind::OLE2 : ( bGraf ? SdrObjKind::Graphic : SdrObjKind::Group );
1151
0
        SCTAB nTab = 0;
1152
0
        SdrObject* pObject = pModel->GetNamedObject( rName, nDrawId, nTab );
1153
0
        if (pObject)
1154
0
        {
1155
0
            SdrView aEditView(*pModel);
1156
0
            aEditView.ShowSdrPage(aEditView.GetModel().GetPage(nTab));
1157
0
            SdrPageView* pPV = aEditView.GetSdrPageView();
1158
0
            aEditView.MarkObj(pObject, pPV);
1159
1160
            // tdf125520 this is a D&D-start potentially with an OLE object. If
1161
            // so, we need to do similar as e.g. in ScDrawView::BeginDrag so that
1162
            // the temporary SdrModel for transfer does have a GetPersist() so
1163
            // that the EmbeddedObjectContainer gets copied. We need no CheckOle
1164
            // here, test is simpler.
1165
0
            ScDocShellRef aDragShellRef;
1166
0
            if(SdrObjKind::OLE2 == pObject->GetObjIdentifier())
1167
0
            {
1168
0
                aDragShellRef = new ScDocShell;     // DocShell needs a Ref immediately
1169
0
                aDragShellRef->DoInitNew();
1170
0
            }
1171
1172
0
            ScDrawLayer::SetGlobalDrawPersist(aDragShellRef.get());
1173
0
            std::unique_ptr<SdrModel> pDragModel(aEditView.CreateMarkedObjModel());
1174
0
            ScDrawLayer::SetGlobalDrawPersist(nullptr);
1175
1176
0
            TransferableObjectDescriptor aObjDesc;
1177
0
            rSrcShell.FillTransferableObjectDescriptor( aObjDesc );
1178
0
            aObjDesc.maDisplayName = rSrcShell.GetMedium()->GetURLObject().GetURLNoPass();
1179
            // maSize is set in ScDrawTransferObj ctor
1180
1181
0
            rtl::Reference<ScDrawTransferObj> pTransferObj = new ScDrawTransferObj( std::move(pDragModel), rSrcShell, std::move(aObjDesc) );
1182
1183
0
            pTransferObj->SetDragSourceObj( *pObject, nTab );
1184
0
            pTransferObj->SetDragSourceFlags(ScDragSrc::Navigator);
1185
1186
0
            ScModule::get()->SetDragObject(nullptr, pTransferObj.get());
1187
1188
0
            rtl::Reference<TransferDataContainer> xHelper(pTransferObj);
1189
0
            rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);
1190
1191
0
            bDisallow = false;
1192
0
        }
1193
0
    }
1194
1195
0
    return bDisallow;
1196
0
}
1197
1198
static bool lcl_DoDragCells( ScDocShell& rSrcShell, const ScRange& rRange, ScDragSrc nFlags, weld::TreeView& rTreeView )
1199
0
{
1200
0
    bool bDisallow = true;
1201
1202
0
    ScDocument& rSrcDoc = rSrcShell.GetDocument();
1203
0
    ScMarkData aMark(rSrcDoc.GetSheetLimits());
1204
0
    aMark.SelectTable( rRange.aStart.Tab(), true );
1205
0
    aMark.SetMarkArea( rRange );
1206
1207
0
    if ( !rSrcDoc.HasSelectedBlockMatrixFragment( rRange.aStart.Col(), rRange.aStart.Row(),
1208
0
                                                   rRange.aEnd.Col(),   rRange.aEnd.Row(),
1209
0
                                                   aMark ) )
1210
0
    {
1211
0
        ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP ));
1212
0
        ScClipParam aClipParam(rRange, false);
1213
0
        rSrcDoc.CopyToClip(aClipParam, pClipDoc.get(), &aMark, false, false);
1214
        // pClipDoc->ExtendMerge( rRange, sal_True );
1215
1216
0
        TransferableObjectDescriptor aObjDesc;
1217
0
        rSrcShell.FillTransferableObjectDescriptor( aObjDesc );
1218
0
        aObjDesc.maDisplayName = rSrcShell.GetMedium()->GetURLObject().GetURLNoPass();
1219
        // maSize is set in ScTransferObj ctor
1220
1221
0
        rtl::Reference<ScTransferObj> pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) );
1222
1223
0
        pTransferObj->SetDragSource( &rSrcShell, aMark );
1224
0
        pTransferObj->SetDragSourceFlags( nFlags );
1225
1226
0
        ScModule::get()->SetDragObject(pTransferObj.get(), nullptr); // for internal D&D
1227
1228
0
        rtl::Reference<TransferDataContainer> xHelper(pTransferObj);
1229
0
        rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);
1230
1231
0
        bDisallow = false;
1232
0
    }
1233
1234
0
    return bDisallow;
1235
0
}
1236
1237
IMPL_LINK(ScContentTree, DragBeginHdl, bool&, rUnsetDragIcon, bool)
1238
0
{
1239
0
    rUnsetDragIcon = true;
1240
1241
0
    StoreNavigatorSettings();
1242
1243
0
    bool bDisallow = true;
1244
1245
0
    ScModule* pScMod = ScModule::get();
1246
1247
0
    ScContentId nType;
1248
0
    sal_uLong nChild;
1249
1250
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
1251
0
    if (!m_xTreeView->get_cursor(xEntry.get()))
1252
0
        xEntry.reset();
1253
1254
0
    GetEntryIndexes(nType, nChild, xEntry.get());
1255
1256
0
    if( xEntry &&
1257
0
        (nChild != SC_CONTENT_NOCHILD) &&
1258
0
        (nType != ScContentId::ROOT) &&
1259
0
        (nType != ScContentId::NOTE) &&
1260
0
        (nType != ScContentId::AREALINK) )
1261
0
    {
1262
0
        OUString aText(m_xTreeView->get_text(*xEntry));
1263
1264
0
        ScDocument* pLocalDoc = nullptr;                   // for URL drop
1265
0
        OUString aDocName;
1266
0
        ScDocShell* pDocSh = GetManualOrCurrent();
1267
0
        if (pDocSh)
1268
0
        {
1269
0
            if (pDocSh->HasName())
1270
0
                aDocName = pDocSh->GetMedium()->GetName();
1271
0
            else
1272
0
                pLocalDoc = &pDocSh->GetDocument();      // drop only in this document
1273
0
        }
1274
1275
0
        bool bDoLinkTrans = false;      // use ScLinkTransferObj
1276
0
        OUString aLinkURL;                // for ScLinkTransferObj
1277
0
        OUString aLinkText;
1278
1279
0
        sal_uInt16 nDropMode = pParentWindow->GetDropMode();
1280
0
        switch ( nDropMode )
1281
0
        {
1282
0
            case SC_DROPMODE_URL:
1283
0
                {
1284
0
                    OUString aUrl = aDocName + "#" + aText;
1285
1286
0
                    pScMod->SetDragJump( pLocalDoc, aUrl, aText );
1287
1288
0
                    if (!aDocName.isEmpty())
1289
0
                    {
1290
                        //  provide URL to outside only if the document has a name
1291
                        //  (without name, only internal D&D via SetDragJump)
1292
1293
0
                        aLinkURL = aUrl;
1294
0
                        aLinkText = aText;
1295
0
                    }
1296
0
                    bDoLinkTrans = true;
1297
0
                }
1298
0
                break;
1299
0
            case SC_DROPMODE_LINK:
1300
0
                {
1301
0
                    if ( !aDocName.isEmpty() )           // link only to named documents
1302
0
                    {
1303
                        // for internal D&D, set flag to insert a link
1304
1305
0
                        switch ( nType )
1306
0
                        {
1307
0
                            case ScContentId::TABLE:
1308
0
                                pScMod->SetDragLink( aDocName, aText, OUString() );
1309
0
                                bDoLinkTrans = true;
1310
0
                                break;
1311
0
                            case ScContentId::RANGENAME:
1312
0
                            case ScContentId::DBAREA:
1313
0
                                pScMod->SetDragLink( aDocName, OUString(), aText );
1314
0
                                bDoLinkTrans = true;
1315
0
                                break;
1316
1317
                            // other types cannot be linked
1318
0
                            default: break;
1319
0
                        }
1320
0
                    }
1321
0
                }
1322
0
                break;
1323
0
            case SC_DROPMODE_COPY:
1324
0
                {
1325
0
                    ScDocShell* pSrcShell = GetManualOrCurrent();
1326
0
                    if ( pSrcShell )
1327
0
                    {
1328
0
                        ScDocument& rSrcDoc = pSrcShell->GetDocument();
1329
0
                        if ( nType == ScContentId::RANGENAME || nType == ScContentId::DBAREA )
1330
0
                        {
1331
0
                            ScRange aRange;
1332
0
                            if ( lcl_GetRange( rSrcDoc, nType, aText, aRange ) )
1333
0
                            {
1334
0
                                bDisallow = lcl_DoDragCells( *pSrcShell, aRange, ScDragSrc::Navigator, *m_xTreeView );
1335
0
                            }
1336
0
                        }
1337
0
                        else if ( nType == ScContentId::TABLE )
1338
0
                        {
1339
0
                            SCTAB nTab;
1340
0
                            if ( rSrcDoc.GetTable( aText, nTab ) )
1341
0
                            {
1342
0
                                ScRange aRange(0, 0, nTab, rSrcDoc.MaxCol(), rSrcDoc.MaxRow(), nTab);
1343
0
                                bDisallow = lcl_DoDragCells( *pSrcShell, aRange, (ScDragSrc::Navigator | ScDragSrc::Table), *m_xTreeView );
1344
0
                            }
1345
0
                        }
1346
0
                        else if ( nType == ScContentId::GRAPHIC || nType == ScContentId::OLEOBJECT ||
1347
0
                                    nType == ScContentId::DRAWING )
1348
0
                        {
1349
0
                            bDisallow = lcl_DoDragObject( *pSrcShell, aText, nType, *m_xTreeView );
1350
1351
                            //  during ExecuteDrag the navigator can be deleted
1352
                            //  -> don't access member anymore !!!
1353
0
                        }
1354
0
                    }
1355
0
                }
1356
0
                break;
1357
0
        }
1358
1359
0
        if (bDoLinkTrans)
1360
0
        {
1361
0
            if (!aLinkURL.isEmpty())
1362
0
                m_xTransferObj->SetLinkURL(aLinkURL, aLinkText);
1363
1364
0
            rtl::Reference<TransferDataContainer> xHelper(m_xTransferObj);
1365
0
            m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK);
1366
1367
0
            bDisallow = false;
1368
0
        }
1369
0
    }
1370
1371
0
    return bDisallow;
1372
0
}
1373
1374
void ScContentTree::SetRootType( ScContentId nNew )
1375
0
{
1376
0
    if ( nNew != nRootType )
1377
0
    {
1378
0
        nRootType = nNew;
1379
0
        Refresh();
1380
1381
0
        ScNavipiCfg& rCfg = ScModule::get()->GetNavipiCfg();
1382
0
        rCfg.SetRootType( nRootType );
1383
0
    }
1384
0
}
1385
1386
void ScContentTree::ToggleRoot()        // after selection
1387
0
{
1388
0
    ScContentId nNew = ScContentId::ROOT;
1389
0
    if ( nRootType == ScContentId::ROOT )
1390
0
    {
1391
0
        std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator());
1392
0
        if (m_xTreeView->get_cursor(xEntry.get()))
1393
0
        {
1394
0
            std::unique_ptr<weld::TreeIter> xParent(m_xTreeView->make_iterator(xEntry.get()));
1395
0
            if (!m_xTreeView->iter_parent(*xParent))
1396
0
                xParent.reset();
1397
1398
0
            for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++)
1399
0
            {
1400
0
                if (!m_aRootNodes[static_cast<ScContentId>(i)])
1401
0
                    continue;
1402
0
                if ((m_xTreeView->iter_compare(*xEntry, *m_aRootNodes[static_cast<ScContentId>(i)]) == 0) ||
1403
0
                    (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[static_cast<ScContentId>(i)]) == 0))
1404
0
                {
1405
0
                    nNew = static_cast<ScContentId>(i);
1406
0
                }
1407
0
            }
1408
0
        }
1409
0
    }
1410
1411
0
    SetRootType( nNew );
1412
0
}
1413
1414
void ScContentTree::ResetManualDoc()
1415
0
{
1416
0
    aManualDoc.clear();
1417
1418
0
    ActiveDocChanged();
1419
0
}
1420
1421
bool ScContentTree::ActiveDocChanged()
1422
0
{
1423
0
    bool bRefreshed = false;
1424
1425
0
    if (aManualDoc.isEmpty())
1426
0
    {
1427
0
        Refresh();                                  // content only if automatic
1428
0
        bRefreshed = true;
1429
0
    }
1430
1431
        //  if flag active Listbox must be updated
1432
1433
0
    OUString aCurrent;
1434
1435
0
    ScDocShell* pSh = GetManualOrCurrent();
1436
0
    if (pSh)
1437
0
        aCurrent = pSh->GetTitle();
1438
0
    else
1439
0
    {
1440
        //  document is no longer available
1441
1442
0
        aManualDoc.clear();             // again automatically
1443
0
        Refresh();
1444
0
        bRefreshed = true;
1445
0
        pSh = GetManualOrCurrent();     // should be active now
1446
0
        if (pSh)
1447
0
            aCurrent = pSh->GetTitle();
1448
0
    }
1449
1450
0
    pParentWindow->GetDocNames( &aCurrent );        // select
1451
1452
0
    return bRefreshed;
1453
0
}
1454
1455
void ScContentTree::SetManualDoc(const OUString& rName)
1456
0
{
1457
0
    aManualDoc = rName;
1458
0
    Refresh();
1459
0
    pParentWindow->GetDocNames( &aManualDoc );      // select
1460
0
}
1461
1462
void ScContentTree::SelectDoc(const OUString& rName)      // rName like shown in Menu/Listbox
1463
0
{
1464
0
    if ( rName == pParentWindow->aStrActiveWin )
1465
0
    {
1466
0
        ResetManualDoc();
1467
0
        return;
1468
0
    }
1469
1470
    //  omit "active" or "inactive"
1471
1472
0
    OUString aRealName = rName;
1473
0
    sal_Int32 nLen = rName.getLength();
1474
0
    sal_Int32 nActiveStart = nLen - pParentWindow->aStrActive.getLength();
1475
0
    if ( rName.subView( nActiveStart ) == pParentWindow->aStrActive )
1476
0
        aRealName = rName.copy( 0, nActiveStart );
1477
0
    sal_Int32 nNotActiveStart = nLen - pParentWindow->aStrNotActive.getLength();
1478
0
    if ( rName.subView( nNotActiveStart ) == pParentWindow->aStrNotActive )
1479
0
        aRealName = rName.copy( 0, nNotActiveStart );
1480
1481
0
    bool bLoaded = false;
1482
1483
    // Is it a normally loaded document?
1484
1485
0
    SfxObjectShell* pSh = SfxObjectShell::GetFirst();
1486
0
    while ( pSh && !bLoaded )
1487
0
    {
1488
0
        if ( dynamic_cast<const ScDocShell*>( pSh) !=  nullptr )
1489
0
            if ( pSh->GetTitle() == aRealName )
1490
0
                bLoaded = true;
1491
0
        pSh = SfxObjectShell::GetNext( *pSh );
1492
0
    }
1493
1494
0
    if (bLoaded)
1495
0
    {
1496
0
        SetManualDoc(aRealName);
1497
0
    }
1498
0
    else
1499
0
    {
1500
0
        OSL_FAIL("SelectDoc: not found");
1501
0
    }
1502
0
}
1503
1504
void ScContentTree::SelectEntryByName(const ScContentId nRoot, std::u16string_view rName)
1505
0
{
1506
0
    weld::TreeIter* pParent = m_aRootNodes[nRoot].get();
1507
1508
0
    if (!pParent || !m_xTreeView->iter_has_child(*pParent))
1509
0
        return;
1510
1511
0
    std::unique_ptr<weld::TreeIter> xEntry(m_xTreeView->make_iterator(pParent));
1512
0
    bool bEntry = m_xTreeView->iter_children(*xEntry);
1513
1514
0
    while (bEntry)
1515
0
    {
1516
0
        if (m_xTreeView->get_text(*xEntry) == rName)
1517
0
        {
1518
0
            m_xTreeView->select(*xEntry);
1519
0
            m_xTreeView->set_cursor(*xEntry);
1520
1521
            // Scroll to the selected item
1522
0
            m_xTreeView->scroll_to_row(*xEntry);
1523
1524
0
            StoreNavigatorSettings();
1525
1526
0
            return;
1527
0
        }
1528
0
        bEntry = m_xTreeView->iter_next(*xEntry);
1529
0
    }
1530
0
}
1531
1532
void ScContentTree::BringCommentToAttention(sal_uInt16 nCommentId)
1533
0
{
1534
0
    std::unique_ptr<weld::TreeIter> xIter(m_xTreeView->make_iterator());
1535
0
    if (!m_xTreeView->get_iter_first(*xIter))
1536
0
        return;
1537
1538
0
    do
1539
0
    {
1540
0
        ScContentId nType;
1541
0
        sal_uLong nChild;
1542
0
        GetEntryIndexes(nType, nChild, xIter.get());
1543
1544
0
        if (nType == ScContentId::NOTE)
1545
0
        {
1546
0
            m_xTreeView->set_cursor(*xIter);
1547
0
            m_xTreeView->select(*xIter);
1548
0
            m_xTreeView->expand_row(*xIter);
1549
1550
0
            OUString aCommentId(OUString::number(nCommentId));
1551
0
            for (bool bChild = m_xTreeView->iter_children(*xIter); bChild;
1552
0
                 bChild = m_xTreeView->iter_next_sibling(*xIter))
1553
0
            {
1554
0
                if (m_xTreeView->get_id(*xIter) == aCommentId)
1555
0
                {
1556
0
                    m_xTreeView->select(*xIter);
1557
0
                    break;
1558
0
                }
1559
0
            }
1560
0
            break;
1561
0
        }
1562
0
        else
1563
0
            m_xTreeView->collapse_row(*xIter);
1564
1565
0
    } while (m_xTreeView->iter_next_sibling(*xIter));}
1566
1567
void ScContentTree::ApplyNavigatorSettings()
1568
0
{
1569
0
    const ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings();
1570
0
    if( !pSettings )
1571
0
        return;
1572
1573
0
    ScContentId nRootSel = pSettings->GetRootSelected();
1574
0
    auto nChildSel = pSettings->GetChildSelected();
1575
1576
    // tdf#133079 ensure Sheet root is selected if nothing
1577
    // else would be
1578
0
    if (nRootSel == ScContentId::ROOT)
1579
0
    {
1580
0
        nRootSel = ScContentId::TABLE;
1581
0
        nChildSel = SC_CONTENT_NOCHILD;
1582
0
    }
1583
1584
0
    for( int i = 1; i <= int(ScContentId::LAST); ++i )
1585
0
    {
1586
0
        ScContentId nEntry = static_cast<ScContentId>(i);
1587
0
        if( m_aRootNodes[ nEntry ] )
1588
0
        {
1589
            // gray or ungray
1590
0
            if (!m_xTreeView->iter_has_child(*m_aRootNodes[nEntry]))
1591
0
                m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], false);
1592
0
            else
1593
0
                m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], true);
1594
1595
            // expand
1596
0
            bool bExp = pSettings->IsExpanded( nEntry );
1597
0
            if (bExp != m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry]))
1598
0
            {
1599
0
                if( bExp )
1600
0
                    m_xTreeView->expand_row(*m_aRootNodes[nEntry]);
1601
0
                else
1602
0
                    m_xTreeView->collapse_row(*m_aRootNodes[nEntry]);
1603
0
            }
1604
1605
            // select
1606
0
            if( nRootSel == nEntry )
1607
0
            {
1608
0
                std::unique_ptr<weld::TreeIter> xEntry;
1609
0
                if (bExp && (nChildSel != SC_CONTENT_NOCHILD))
1610
0
                {
1611
0
                    xEntry = m_xTreeView->make_iterator(m_aRootNodes[nEntry].get());
1612
0
                    if (!m_xTreeView->iter_children(*xEntry) || !m_xTreeView->iter_nth_sibling(*xEntry, nChildSel))
1613
0
                        xEntry.reset();
1614
0
                }
1615
0
                m_xTreeView->select(xEntry ? *xEntry : *m_aRootNodes[nEntry]);
1616
0
                m_xTreeView->set_cursor(xEntry ? *xEntry : *m_aRootNodes[nEntry]);
1617
0
            }
1618
0
        }
1619
0
    }
1620
0
}
1621
1622
void ScContentTree::StoreNavigatorSettings()
1623
0
{
1624
0
    if (m_nAsyncMouseReleaseId)
1625
0
    {
1626
0
        Application::RemoveUserEvent(m_nAsyncMouseReleaseId);
1627
0
        m_nAsyncMouseReleaseId = nullptr;
1628
0
    }
1629
1630
0
    ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings();
1631
0
    if( !pSettings )
1632
0
        return;
1633
1634
0
    for( int i = 1; i <= int(ScContentId::LAST); ++i )
1635
0
    {
1636
0
        ScContentId nEntry = static_cast<ScContentId>(i);
1637
0
        bool bExp = m_aRootNodes[nEntry] && m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry]);
1638
0
        pSettings->SetExpanded( nEntry, bExp );
1639
0
    }
1640
1641
0
    std::unique_ptr<weld::TreeIter> xCurEntry(m_xTreeView->make_iterator());
1642
0
    if (!m_xTreeView->get_cursor(xCurEntry.get()))
1643
0
        xCurEntry.reset();
1644
1645
0
    ScContentId nRoot;
1646
0
    sal_uLong nChild;
1647
0
    GetEntryIndexes(nRoot, nChild, xCurEntry.get());
1648
1649
0
    pSettings->SetRootSelected( nRoot );
1650
0
    pSettings->SetChildSelected( nChild );
1651
0
}
1652
1653
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */