Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/sd/source/ui/view/Outliner.cxx
Line
Count
Source (jump to first uncovered line)
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 <Outliner.hxx>
21
#include <boost/property_tree/json_parser.hpp>
22
#include <vcl/settings.hxx>
23
#include <vcl/svapp.hxx>
24
25
#include <svl/srchitem.hxx>
26
#include <svl/intitem.hxx>
27
#include <editeng/editstat.hxx>
28
#include <vcl/canvastools.hxx>
29
#include <vcl/outdev.hxx>
30
#include <vcl/weld.hxx>
31
#include <sfx2/dispatch.hxx>
32
#include <svx/svdotext.hxx>
33
#include <svx/svdograf.hxx>
34
#include <editeng/unolingu.hxx>
35
#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
36
#include <svx/srchdlg.hxx>
37
#include <unotools/linguprops.hxx>
38
#include <unotools/lingucfg.hxx>
39
#include <editeng/editeng.hxx>
40
#include <sfx2/viewfrm.hxx>
41
#include <tools/debug.hxx>
42
#include <comphelper/diagnose_ex.hxx>
43
44
#include <strings.hrc>
45
#include <editeng/outliner.hxx>
46
#include <sdmod.hxx>
47
#include <Window.hxx>
48
#include <sdresid.hxx>
49
#include <DrawViewShell.hxx>
50
#include <OutlineView.hxx>
51
#include <OutlineViewShell.hxx>
52
#include <NotesPanelView.hxx>
53
#include <drawdoc.hxx>
54
#include <DrawDocShell.hxx>
55
#include <drawview.hxx>
56
#include <ViewShellBase.hxx>
57
#include <ViewShellManager.hxx>
58
#include <SpellDialogChildWindow.hxx>
59
#include <framework/FrameworkHelper.hxx>
60
#include <svx/svxids.hrc>
61
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
62
#include <comphelper/string.hxx>
63
#include <comphelper/lok.hxx>
64
#include <comphelper/scopeguard.hxx>
65
#include <VectorGraphicSearchContext.hxx>
66
#include <fusearch.hxx>
67
#include <sdpage.hxx>
68
#include <ResourceId.hxx>
69
70
using namespace ::com::sun::star;
71
using namespace ::com::sun::star::uno;
72
using namespace ::com::sun::star::lang;
73
using namespace ::com::sun::star::linguistic2;
74
75
class SfxStyleSheetPool;
76
77
class SdOutliner::Implementation
78
{
79
public:
80
    /** The original edit mode directly after switching to a different view
81
        mode.  Used for restoring the edit mode when leaving that view mode
82
        again.
83
    */
84
    EditMode meOriginalEditMode;
85
86
    Implementation();
87
    ~Implementation();
88
89
    /** Return the OutlinerView that was provided by the last call to
90
        ProvideOutlinerView() (or NULL when there was no such call.)
91
    */
92
0
    OutlinerView* GetOutlinerView() { return mpOutlineView;}
93
94
    /** Provide in the member mpOutlineView an instance of OutlinerView that
95
        is either taken from the ViewShell, when it is an OutlineViewShell,
96
        or is created.  When an OutlinerView already exists it is initialized.
97
    */
98
    void ProvideOutlinerView (
99
        Outliner& rOutliner,
100
        const std::shared_ptr<sd::ViewShell>& rpViewShell,
101
        vcl::Window* pWindow);
102
103
    /** This method is called when the OutlinerView is no longer used.
104
    */
105
    void ReleaseOutlinerView();
106
107
0
    sd::VectorGraphicSearchContext& getVectorGraphicSearchContext() { return maVectorGraphicSearchContext; }
108
109
private:
110
    /** Flag that specifies whether we own the outline view pointed to by
111
        <member>mpOutlineView</member> and thus have to
112
        delete it in <member>EndSpelling()</member>.
113
    */
114
    bool mbOwnOutlineView;
115
116
    /** The outline view used for searching and spelling.  If searching or
117
        spell checking an outline view this data member points to that view.
118
        For all other views an instance is created.  The
119
        <member>mbOwnOutlineView</member> distinguishes between both cases.
120
    */
121
    OutlinerView* mpOutlineView;
122
123
    sd::VectorGraphicSearchContext maVectorGraphicSearchContext;
124
};
125
126
namespace
127
{
128
129
sd::ViewShellBase* getViewShellBase()
130
0
{
131
0
    return dynamic_cast<sd::ViewShellBase*>(SfxViewShell::Current());
132
0
}
133
134
OutlinerView* lclGetNotesPaneOutliner(const std::shared_ptr<sd::ViewShell>& pViewShell)
135
0
{
136
0
    if (!pViewShell)
137
0
        return nullptr;
138
139
    // request the notes pane
140
0
    sd::ViewShellBase& rBase = pViewShell->GetViewShellBase();
141
142
0
    sd::framework::FrameworkHelper::Instance(rBase)->RequestView(
143
0
        sd::framework::FrameworkHelper::msNotesPanelViewURL,
144
0
        sd::framework::FrameworkHelper::msBottomImpressPaneURL);
145
146
0
    auto pInstance = sd::framework::FrameworkHelper::Instance(rBase);
147
0
    pInstance->RequestSynchronousUpdate();
148
149
0
    std::shared_ptr<sd::ViewShell> pNotesPaneShell(
150
0
        pInstance->GetViewShell(sd::framework::FrameworkHelper::msBottomImpressPaneURL));
151
152
0
    if (!pNotesPaneShell)
153
0
        return nullptr;
154
155
0
    return static_cast<sd::NotesPanelView*>(pNotesPaneShell->GetView())->GetOutlinerView();
156
0
}
157
158
} // end anonymous namespace
159
160
SdOutliner::SdOutliner( SdDrawDocument& rDoc, OutlinerMode nMode )
161
43.0k
    : SdrOutliner( &rDoc.GetItemPool(), nMode ),
162
43.0k
      mpImpl(new Implementation()),
163
43.0k
      meMode(SEARCH),
164
43.0k
      mpView(nullptr),
165
43.0k
      mpWindow(nullptr),
166
43.0k
      mrDrawDocument(rDoc),
167
43.0k
      mnConversionLanguage(LANGUAGE_NONE),
168
43.0k
      mnIgnoreCurrentPageChangesLevel(0),
169
43.0k
      mbStringFound(false),
170
43.0k
      mbMatchMayExist(false),
171
43.0k
      mnPageCount(0),
172
43.0k
      mbEndOfSearch(false),
173
43.0k
      mbFoundObject(false),
174
43.0k
      mbDirectionIsForward(true),
175
43.0k
      mbRestrictSearchToSelection(false),
176
43.0k
      mpObj(nullptr),
177
43.0k
      mpFirstObj(nullptr),
178
43.0k
      mpSearchSpellTextObj(nullptr),
179
43.0k
      mnText(0),
180
43.0k
      mpParaObj(nullptr),
181
43.0k
      meStartViewMode(PageKind::Standard),
182
43.0k
      meStartEditMode(EditMode::Page),
183
43.0k
      mnStartPageIndex(sal_uInt16(-1)),
184
43.0k
      mpStartEditedObject(nullptr),
185
43.0k
      mbPrepareSpellingPending(true)
186
43.0k
{
187
43.0k
    SetStyleSheetPool(static_cast<SfxStyleSheetPool*>( mrDrawDocument.GetStyleSheetPool() ));
188
43.0k
    SetEditTextObjectPool( &rDoc.GetItemPool() );
189
43.0k
    SetCalcFieldValueHdl(LINK(SdModule::get(), SdModule, CalcFieldValueHdl));
190
43.0k
    SetForbiddenCharsTable( rDoc.GetForbiddenCharsTable() );
191
192
43.0k
    EEControlBits nCntrl = GetControlWord();
193
43.0k
    nCntrl |= EEControlBits::ALLOWBIGOBJS;
194
43.0k
    nCntrl |= EEControlBits::MARKFIELDS;
195
43.0k
    nCntrl |= EEControlBits::AUTOCORRECT;
196
197
43.0k
    bool bOnlineSpell = false;
198
199
43.0k
    sd::DrawDocShell* pDocSh = mrDrawDocument.GetDocSh();
200
201
43.0k
    if (pDocSh)
202
43.0k
    {
203
43.0k
        bOnlineSpell = mrDrawDocument.GetOnlineSpell();
204
43.0k
    }
205
0
    else
206
0
    {
207
0
        bOnlineSpell = false;
208
209
0
        try
210
0
        {
211
0
            const SvtLinguConfig    aLinguConfig;
212
0
            Any aAny = aLinguConfig.GetProperty( UPN_IS_SPELL_AUTO );
213
0
            aAny >>= bOnlineSpell;
214
0
        }
215
0
        catch( ... )
216
0
        {
217
0
            OSL_FAIL( "Ill. type in linguistic property" );
218
0
        }
219
0
    }
220
221
43.0k
    if (bOnlineSpell)
222
0
        nCntrl |= EEControlBits::ONLINESPELLING;
223
43.0k
    else
224
43.0k
        nCntrl &= ~EEControlBits::ONLINESPELLING;
225
226
43.0k
    SetControlWord(nCntrl);
227
228
43.0k
    Reference< XSpellChecker1 > xSpellChecker( LinguMgr::GetSpellChecker() );
229
43.0k
    if ( xSpellChecker.is() )
230
43.0k
        SetSpeller( xSpellChecker );
231
232
43.0k
    Reference< XHyphenator > xHyphenator( LinguMgr::GetHyphenator() );
233
43.0k
    if( xHyphenator.is() )
234
43.0k
        SetHyphenator( xHyphenator );
235
236
43.0k
    SetDefaultLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
237
43.0k
}
238
239
/// Nothing spectacular in the destructor.
240
SdOutliner::~SdOutliner()
241
43.0k
{
242
43.0k
}
243
244
OutlinerView* SdOutliner::getOutlinerView()
245
0
{
246
0
    return mpImpl->GetOutlinerView();
247
0
}
248
249
/** Prepare find&replace or spellchecking.  This distinguishes between three
250
    cases:
251
    <ol>
252
    <li>The current shell is a <type>DrawViewShell</type>: Create a
253
    <type>OutlinerView</type> object and search all objects of (i) the
254
    current mark list, (ii) of the current view, or (iii) of all the view
255
    combinations:
256
    <ol>
257
    <li>Draw view, slide view</li>
258
    <li>Draw view, background view</li>
259
    <li>Notes view, slide view</li>
260
    <li>Notes view, background view</li>
261
    <li>Handout view, slide view</li>
262
    <li>Handout view, background view</li>
263
    </ol>
264
265
    <li>When the current shell is a <type>SdOutlineViewShell</type> then
266
    directly operate on it.  No switching into other views takes place.</li>
267
    </ol>
268
*/
269
void SdOutliner::PrepareSpelling()
270
0
{
271
0
    mbPrepareSpellingPending = false;
272
273
0
    sd::ViewShellBase* pBase = getViewShellBase();
274
0
    if (pBase != nullptr)
275
0
        SetViewShell (pBase->GetMainViewShell());
276
0
    SetRefDevice(SdModule::get()->GetVirtualRefDevice());
277
278
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
279
0
    if (pViewShell)
280
0
    {
281
0
        mbStringFound = false;
282
283
        // Supposed that we are not located at the very beginning/end of
284
        // the document then there may be a match in the document
285
        // prior/after the current position.
286
0
        mbMatchMayExist = true;
287
288
0
        maObjectIterator = sd::outliner::Iterator();
289
0
        maSearchStartPosition = sd::outliner::Iterator();
290
0
        RememberStartPosition();
291
292
0
        mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
293
294
0
        HandleChangedSelection ();
295
0
    }
296
0
    ClearModifyFlag();
297
0
}
298
299
void SdOutliner::StartSpelling()
300
0
{
301
0
    meMode = SPELL;
302
0
    mbDirectionIsForward = true;
303
0
    mpSearchItem.reset();
304
0
}
305
306
/** Free all resources acquired during the search/spell check.  After a
307
    spell check the start position is restored here.
308
*/
309
void SdOutliner::EndSpelling()
310
0
{
311
    // Keep old view shell alive until we release the outliner view.
312
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
313
0
    std::shared_ptr<sd::ViewShell> pOldViewShell (pViewShell);
314
315
0
    sd::ViewShellBase* pBase = getViewShellBase();
316
0
    if (pBase != nullptr)
317
0
        pViewShell = pBase->GetMainViewShell();
318
0
    else
319
0
        pViewShell.reset();
320
0
    mpWeakViewShell = pViewShell;
321
322
    // When in <member>PrepareSpelling()</member> a new outline view has
323
    // been created then delete it here.
324
0
    bool bViewIsDrawViewShell(dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ));
325
0
    if (bViewIsDrawViewShell)
326
0
    {
327
0
        SetStatusEventHdl(Link<EditStatus&,void>());
328
0
        mpView = pViewShell->GetView();
329
0
        mpView->UnmarkAllObj (mpView->GetSdrPageView());
330
0
        mpView->SdrEndTextEdit();
331
        // Make FuSelection the current function.
332
0
        pViewShell->GetDispatcher()->Execute(
333
0
            SID_OBJECT_SELECT,
334
0
            SfxCallMode::SYNCHRON | SfxCallMode::RECORD);
335
336
        // Remove and, if previously created by us, delete the outline
337
        // view.
338
0
        OutlinerView* pOutlinerView = getOutlinerView();
339
0
        if (pOutlinerView != nullptr)
340
0
        {
341
0
            RemoveView(pOutlinerView);
342
0
            mpImpl->ReleaseOutlinerView();
343
0
        }
344
345
0
        SetUpdateLayout(true);
346
0
    }
347
348
    // Before clearing the modify flag use it as a hint that
349
    // changes were done at SpellCheck
350
0
    if(IsModified())
351
0
    {
352
0
        if(auto pOutlineView = dynamic_cast<sd::OutlineView *>( mpView ))
353
0
            pOutlineView->PrepareClose();
354
0
        if(!mrDrawDocument.IsChanged())
355
0
            mrDrawDocument.SetChanged();
356
0
    }
357
358
    // Now clear the modify flag to have a specified state of
359
    // Outliner
360
0
    ClearModifyFlag();
361
362
    // When spell checking then restore the start position.
363
0
    if (meMode==SPELL || meMode==TEXT_CONVERSION)
364
0
        RestoreStartPosition ();
365
366
0
    mpWeakViewShell.reset();
367
0
    mpView = nullptr;
368
0
    mpWindow = nullptr;
369
0
    mnStartPageIndex = sal_uInt16(-1);
370
0
}
371
372
bool SdOutliner::SpellNextDocument()
373
0
{
374
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
375
0
    if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
376
0
    {
377
        // When doing a spell check in the outline view then there is
378
        // only one document.
379
0
        mbEndOfSearch = true;
380
0
        EndOfSearch ();
381
0
    }
382
0
    else
383
0
    {
384
0
        if( auto pOutlineView = dynamic_cast<sd::OutlineView *>( mpView ))
385
0
            pOutlineView->PrepareClose();
386
0
        mrDrawDocument.GetDocSh()->SetWaitCursor( true );
387
388
0
        Initialize (true);
389
390
0
        mpWindow = pViewShell->GetActiveWindow();
391
0
        OutlinerView* pOutlinerView = getOutlinerView();
392
0
        if (pOutlinerView != nullptr)
393
0
            pOutlinerView->SetWindow(mpWindow);
394
0
        ProvideNextTextObject ();
395
396
0
        mrDrawDocument.GetDocSh()->SetWaitCursor( false );
397
0
        ClearModifyFlag();
398
0
    }
399
400
0
    return !mbEndOfSearch;
401
0
}
402
403
/**
404
 * check next text object
405
 */
406
svx::SpellPortions SdOutliner::GetNextSpellSentence()
407
0
{
408
0
    svx::SpellPortions aResult;
409
410
0
    DetectChange();
411
    // Iterate over sentences and text shapes until a sentence with a
412
    // spelling error has been found.  If no such sentence can be
413
    // found the loop is left through a break.
414
    // It is the responsibility of the sd outliner object to correctly
415
    // iterate over all text shapes, i.e. switch between views, wrap
416
    // around at the end of the document, stop when all text shapes
417
    // have been examined exactly once.
418
0
    bool bFoundNextSentence = false;
419
0
    while ( ! bFoundNextSentence)
420
0
    {
421
0
        OutlinerView* pOutlinerView = GetView(0);
422
0
        if (pOutlinerView != nullptr)
423
0
        {
424
0
            ESelection aCurrentSelection (pOutlinerView->GetSelection());
425
0
            if ( ! mbMatchMayExist
426
0
                && maStartSelection < aCurrentSelection)
427
0
                EndOfSearch();
428
429
            // Advance to the next sentence.
430
0
            bFoundNextSentence = SpellSentence( pOutlinerView->GetEditView(), aResult);
431
0
        }
432
433
        // When no sentence with spelling errors has been found in the
434
        // currently selected text shape or there is no selected text
435
        // shape then advance to the next text shape.
436
0
        if ( ! bFoundNextSentence)
437
0
            if ( ! SpellNextDocument())
438
                // All text objects have been processed so exit the
439
                // loop and return an empty portions list.
440
0
                break;
441
0
    }
442
443
0
    return aResult;
444
0
}
445
446
/** Go to next match.
447
*/
448
bool SdOutliner::StartSearchAndReplace (const SvxSearchItem* pSearchItem)
449
0
{
450
0
    bool bEndOfSearch = true;
451
452
    // clear the search toolbar entry
453
0
    SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Empty);
454
455
0
    mrDrawDocument.GetDocSh()->SetWaitCursor( true );
456
457
    // Since REPLACE is really a replaceAndSearchNext instead of a searchAndReplace,
458
    // make sure that the search portion has not changed since the last FIND.
459
0
    if (!mbPrepareSpellingPending && mpSearchItem
460
0
        && pSearchItem->GetCommand() == SvxSearchCmd::REPLACE
461
0
        && !mpSearchItem->equalsIgnoring(*pSearchItem, /*bIgnoreReplace=*/true,
462
0
            /*bIgnoreCommand=*/true))
463
0
    {
464
0
        EndSpelling();
465
0
        mbPrepareSpellingPending = true;
466
0
    }
467
468
0
    if (mbPrepareSpellingPending)
469
0
        PrepareSpelling();
470
0
    sd::ViewShellBase* pBase = getViewShellBase();
471
    // Determine whether we have to abort the search.  This is necessary
472
    // when the main view shell does not support searching.
473
0
    bool bAbort = false;
474
0
    if (pBase != nullptr)
475
0
    {
476
0
        std::shared_ptr<sd::ViewShell> pShell (pBase->GetMainViewShell());
477
0
        SetViewShell(pShell);
478
0
        if (pShell == nullptr)
479
0
            bAbort = true;
480
0
        else
481
0
            switch (pShell->GetShellType())
482
0
            {
483
0
                case sd::ViewShell::ST_DRAW:
484
0
                case sd::ViewShell::ST_IMPRESS:
485
0
                case sd::ViewShell::ST_NOTES:
486
0
                case sd::ViewShell::ST_HANDOUT:
487
0
                case sd::ViewShell::ST_OUTLINE:
488
0
                    bAbort = false;
489
0
                    break;
490
0
                default:
491
0
                    bAbort = true;
492
0
                    break;
493
0
            }
494
0
    }
495
496
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
497
0
    if ( ! pViewShell)
498
0
    {
499
0
        OSL_ASSERT(pViewShell);
500
0
        return true;
501
0
    }
502
503
0
    if ( ! bAbort)
504
0
    {
505
0
        meMode = SEARCH;
506
0
        mpSearchItem.reset(pSearchItem->Clone());
507
508
0
        mbFoundObject = false;
509
510
0
        Initialize ( ! mpSearchItem->GetBackward());
511
512
0
        const SvxSearchCmd nCommand (mpSearchItem->GetCommand());
513
0
        if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
514
0
        {
515
0
            bEndOfSearch = SearchAndReplaceAll ();
516
0
        }
517
0
        else
518
0
        {
519
0
            RememberStartPosition ();
520
0
            bEndOfSearch = SearchAndReplaceOnce ();
521
            // restore start position if nothing was found
522
0
            if(!mbStringFound)
523
0
            {
524
0
                RestoreStartPosition ();
525
                // Nothing was changed, no need to restart the spellchecker.
526
0
                if (nCommand == SvxSearchCmd::FIND)
527
0
                    bEndOfSearch = false;
528
0
            }
529
0
            mnStartPageIndex = sal_uInt16(-1);
530
0
        }
531
0
    }
532
533
0
    mrDrawDocument.GetDocSh()->SetWaitCursor( false );
534
535
0
    return bEndOfSearch;
536
0
}
537
538
void SdOutliner::Initialize (bool bDirectionIsForward)
539
0
{
540
0
    const bool bIsAtEnd (maObjectIterator == sd::outliner::OutlinerContainer(this).end());
541
0
    const bool bOldDirectionIsForward = mbDirectionIsForward;
542
0
    mbDirectionIsForward = bDirectionIsForward;
543
544
0
    if (maObjectIterator == sd::outliner::Iterator())
545
0
    {
546
        // Initialize a new search.
547
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).current();
548
0
        maCurrentPosition = *maObjectIterator;
549
550
0
        std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
551
0
        if ( ! pViewShell)
552
0
        {
553
0
            OSL_ASSERT(pViewShell);
554
0
            return;
555
0
        }
556
557
        // In case we are searching in an outline view then first remove the
558
        // current selection and place cursor at its start or end.
559
0
        if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
560
0
        {
561
0
            ESelection aSelection = getOutlinerView()->GetSelection ();
562
0
            if (mbDirectionIsForward)
563
0
            {
564
0
                aSelection.CollapseToStart();
565
0
            }
566
0
            else
567
0
            {
568
0
                aSelection.CollapseToEnd();
569
0
            }
570
0
            getOutlinerView()->SetSelection (aSelection);
571
0
        }
572
573
        // When not beginning the search at the beginning of the search area
574
        // then there may be matches before the current position.
575
0
        mbMatchMayExist = (maObjectIterator!=sd::outliner::OutlinerContainer(this).begin());
576
0
    }
577
0
    else if (bOldDirectionIsForward != mbDirectionIsForward)
578
0
    {
579
        // Requested iteration direction has changed.  Turn around the iterator.
580
0
        maObjectIterator.Reverse();
581
0
        if (bIsAtEnd)
582
0
        {
583
            // The iterator has pointed to end(), which after the search
584
            // direction is reversed, becomes begin().
585
0
            maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
586
0
        }
587
0
        else
588
0
        {
589
            // The iterator has pointed to the object one ahead/before the current
590
            // one.  Now move it to the one before/ahead the current one.
591
0
            ++maObjectIterator;
592
0
            if (maObjectIterator != sd::outliner::OutlinerContainer(this).end())
593
0
            {
594
0
                ++maObjectIterator;
595
0
            }
596
0
        }
597
598
0
        mbMatchMayExist = true;
599
0
    }
600
601
    // Initialize the last valid position with where the search starts so
602
    // that it always points to a valid position.
603
0
    maLastValidPosition = *sd::outliner::OutlinerContainer(this).current();
604
0
}
605
606
bool SdOutliner::SearchAndReplaceAll()
607
0
{
608
0
    bool bRet = true;
609
610
    // Save the current position to be restored after having replaced all
611
    // matches.
612
0
    RememberStartPosition ();
613
614
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
615
0
    if ( ! pViewShell)
616
0
    {
617
0
        OSL_ASSERT(pViewShell);
618
0
        return true;
619
0
    }
620
621
0
    std::vector<sd::SearchSelection> aSelections;
622
0
    if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
623
0
    {
624
        // Put the cursor to the beginning/end of the outliner.
625
0
        getOutlinerView()->SetSelection (GetSearchStartPosition ());
626
627
        // The outliner does all the work for us when we are in this mode.
628
0
        SearchAndReplaceOnce();
629
0
    }
630
0
    else if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
631
0
    {
632
        // Disable selection change notifications during search all.
633
0
        SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
634
0
        rSfxViewShell.setTiledSearching(true);
635
0
        comphelper::ScopeGuard aGuard([&rSfxViewShell]()
636
0
        {
637
0
            rSfxViewShell.setTiledSearching(false);
638
0
        });
639
640
        // Go to beginning/end of document.
641
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
642
        // Switch to the first object which contains the search string.
643
0
        ProvideNextTextObject();
644
0
        if( !mbStringFound  )
645
0
        {
646
0
            RestoreStartPosition ();
647
0
            mnStartPageIndex = sal_uInt16(-1);
648
0
            return true;
649
0
        }
650
        // Reset the iterator back to the beginning
651
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
652
653
        // Search/replace until the end of the document is reached.
654
0
        bool bFoundMatch;
655
0
        do
656
0
        {
657
0
            bFoundMatch = ! SearchAndReplaceOnce(&aSelections);
658
0
            if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && bFoundMatch && aSelections.size() == 1)
659
0
            {
660
                // Without this, RememberStartPosition() will think it already has a remembered position.
661
0
                mnStartPageIndex = sal_uInt16(-1);
662
663
0
                RememberStartPosition();
664
665
                // So when RestoreStartPosition() restores the first match, then spellchecker doesn't kill the selection.
666
0
                bRet = false;
667
0
            }
668
0
        }
669
0
        while (bFoundMatch);
670
671
0
        if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && !aSelections.empty())
672
0
        {
673
0
            boost::property_tree::ptree aTree;
674
0
            aTree.put("searchString", mpSearchItem->GetSearchString().toUtf8().getStr());
675
0
            aTree.put("highlightAll", true);
676
677
0
            boost::property_tree::ptree aChildren;
678
0
            for (const sd::SearchSelection& rSelection : aSelections)
679
0
            {
680
0
                boost::property_tree::ptree aChild;
681
0
                aChild.put("part", OString::number(rSelection.m_nPage).getStr());
682
0
                aChild.put("rectangles", rSelection.m_aRectangles.getStr());
683
0
                aChildren.push_back(std::make_pair("", aChild));
684
0
            }
685
0
            aTree.add_child("searchResultSelection", aChildren);
686
687
0
            std::stringstream aStream;
688
0
            boost::property_tree::write_json(aStream, aTree);
689
0
            OString aPayload( aStream.str() );
690
0
            rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload);
691
0
        }
692
0
    }
693
694
0
    RestoreStartPosition ();
695
696
0
    if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && !bRet)
697
0
    {
698
        // Find-all, tiled rendering and we have at least one match.
699
0
        OString aPayload = OString::number(mnStartPageIndex);
700
0
        SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
701
0
        rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
702
703
        // Emit a selection callback here:
704
        // 1) The original one is no longer valid, as we there was a SET_PART in between
705
        // 2) The underlying editeng will only talk about the first match till
706
        // it doesn't support multi-selection.
707
0
        std::vector<OString> aRectangles;
708
0
        for (const sd::SearchSelection& rSelection : aSelections)
709
0
        {
710
0
            if (rSelection.m_nPage == mnStartPageIndex)
711
0
                aRectangles.push_back(rSelection.m_aRectangles);
712
0
        }
713
0
        OString sRectangles = comphelper::string::join("; ", aRectangles);
714
0
        rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangles);
715
0
    }
716
717
0
    mnStartPageIndex = sal_uInt16(-1);
718
719
0
    return bRet;
720
0
}
721
722
namespace
723
{
724
725
basegfx::B2DRectangle getPDFSelection(const std::unique_ptr<VectorGraphicSearch> & rVectorGraphicSearch,
726
                                       const SdrObject* pObject)
727
0
{
728
0
    basegfx::B2DRectangle aSelection;
729
730
0
    auto const aTextRectangles = rVectorGraphicSearch->getTextRectangles();
731
0
    if (aTextRectangles.empty())
732
0
        return aSelection;
733
734
0
    basegfx::B2DSize aPdfPageSizeHMM = rVectorGraphicSearch->pageSize();
735
736
0
    basegfx::B2DRectangle aObjectB2DRectHMM(vcl::unotools::b2DRectangleFromRectangle(pObject->GetLogicRect()));
737
738
    // Setup coordinate conversion matrix to convert the inner PDF
739
    // coordinates to the page relative coordinates
740
0
    basegfx::B2DHomMatrix aB2DMatrix;
741
742
0
    aB2DMatrix.scale(aObjectB2DRectHMM.getWidth() / aPdfPageSizeHMM.getWidth(),
743
0
                     aObjectB2DRectHMM.getHeight() / aPdfPageSizeHMM.getHeight());
744
745
0
    aB2DMatrix.translate(aObjectB2DRectHMM.getMinX(), aObjectB2DRectHMM.getMinY());
746
747
748
0
    for (auto const & rRectangle : rVectorGraphicSearch->getTextRectangles())
749
0
    {
750
0
        basegfx::B2DRectangle aRectangle(rRectangle);
751
0
        aRectangle *= aB2DMatrix;
752
753
0
        if (aSelection.isEmpty())
754
0
            aSelection = aRectangle;
755
0
        else
756
0
            aSelection.expand(aRectangle);
757
0
    }
758
759
0
    return aSelection;
760
0
}
761
762
} // end namespace
763
764
void SdOutliner::sendLOKSearchResultCallback(const std::shared_ptr<sd::ViewShell> & pViewShell,
765
                                             const OutlinerView* pOutlinerView,
766
                                             std::vector<sd::SearchSelection>* pSelections)
767
0
{
768
0
    std::vector<::tools::Rectangle> aLogicRects;
769
0
    auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
770
0
    if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
771
0
    {
772
0
        basegfx::B2DRectangle aSelectionHMM = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
773
774
0
        tools::Rectangle aSelection(Point(aSelectionHMM.getMinX(), aSelectionHMM.getMinY()),
775
0
                                    Size(aSelectionHMM.getWidth(), aSelectionHMM.getHeight()));
776
0
        aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip);
777
0
        aLogicRects.push_back(aSelection);
778
0
    }
779
0
    else
780
0
    {
781
0
        pOutlinerView->GetSelectionRectangles(aLogicRects);
782
783
        // convert to twips if in 100thmm (seems as if LibreOfficeKit is based on twips?). Do this
784
        // here where we have the only place needing this, *not* in ImpEditView::GetSelectionRectangles
785
        // which makes that method unusable for others
786
0
        if (pOutlinerView->GetWindow() && MapUnit::Map100thMM == pOutlinerView->GetWindow()->GetMapMode().GetMapUnit())
787
0
        {
788
0
            for (tools::Rectangle& rRectangle : aLogicRects)
789
0
            {
790
0
                rRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
791
0
            }
792
0
        }
793
0
    }
794
795
0
    std::vector<OString> aLogicRectStrings;
796
0
    std::transform(aLogicRects.begin(), aLogicRects.end(), std::back_inserter(aLogicRectStrings),
797
0
        [](const ::tools::Rectangle& rRectangle)
798
0
    {
799
0
        return rRectangle.toString();
800
0
    });
801
802
0
    OString sRectangles = comphelper::string::join("; ", aLogicRectStrings);
803
804
0
    if (!pSelections)
805
0
    {
806
        // notify LibreOfficeKit about changed page
807
0
        OString aPayload = OString::number(maCurrentPosition.mnPageIndex);
808
0
        SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
809
0
        rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
810
811
        // also about search result selections
812
0
        boost::property_tree::ptree aTree;
813
0
        aTree.put("searchString", mpSearchItem->GetSearchString().toUtf8().getStr());
814
0
        aTree.put("highlightAll", false);
815
816
0
        boost::property_tree::ptree aChildren;
817
0
        boost::property_tree::ptree aChild;
818
0
        aChild.put("part", OString::number(maCurrentPosition.mnPageIndex).getStr());
819
0
        aChild.put("rectangles", sRectangles.getStr());
820
0
        aChildren.push_back(std::make_pair("", aChild));
821
0
        aTree.add_child("searchResultSelection", aChildren);
822
823
0
        std::stringstream aStream;
824
0
        boost::property_tree::write_json(aStream, aTree);
825
0
        aPayload = OString(aStream.str());
826
0
        rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload);
827
828
0
        if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
829
0
        {
830
0
            rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangles);
831
0
        }
832
0
    }
833
0
    else
834
0
    {
835
0
        sd::SearchSelection aSelection(maCurrentPosition.mnPageIndex, sRectangles);
836
0
        bool bDuplicate = !pSelections->empty() && pSelections->back() == aSelection;
837
0
        if (!bDuplicate)
838
0
            pSelections->push_back(aSelection);
839
0
    }
840
0
}
841
842
bool SdOutliner::SearchAndReplaceOnce(std::vector<sd::SearchSelection>* pSelections)
843
0
{
844
0
    DetectChange ();
845
846
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
847
848
0
    if (!getOutlinerView() || !GetEditEngine().HasView(&getOutlinerView()->GetEditView()))
849
0
    {
850
0
        std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
851
0
            std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
852
853
        // Perhaps the user switched to a different page/slide between searches.
854
        // If so, reset the starting search position to the current slide like DetectChange does
855
0
        if (pDrawViewShell && pDrawViewShell->GetCurPagePos() != maCurrentPosition.mnPageIndex)
856
0
            maObjectIterator = sd::outliner::OutlinerContainer(this).current();
857
858
0
        mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
859
0
    }
860
861
0
    if (pViewShell)
862
0
    {
863
0
        mpView = pViewShell->GetView();
864
0
        mpWindow = pViewShell->GetActiveWindow();
865
0
        getOutlinerView()->SetWindow(mpWindow);
866
0
        auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
867
0
        if (nullptr != dynamic_cast<const sd::DrawViewShell*>(pViewShell.get()))
868
0
        {
869
0
            sal_uLong nMatchCount = 0;
870
871
0
            if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
872
0
            {
873
0
                OUString const & rString = mpSearchItem->GetSearchString();
874
0
                bool bBackwards = mpSearchItem->GetBackward();
875
876
0
                VectorGraphicSearchOptions aOptions;
877
0
                aOptions.meStartPosition = bBackwards ? SearchStartPosition::End : SearchStartPosition::Begin;
878
0
                aOptions.mbMatchCase = mpSearchItem->GetExact();
879
0
                aOptions.mbMatchWholeWord = mpSearchItem->GetWordOnly();
880
881
0
                bool bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->search(rString, aOptions);
882
883
0
                if (bResult)
884
0
                {
885
0
                    if (bBackwards)
886
0
                        bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->previous();
887
0
                    else
888
0
                        bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->next();
889
0
                }
890
891
0
                if (bResult)
892
0
                {
893
0
                    nMatchCount = 1;
894
895
0
                    SdrPageView* pPageView = mpView->GetSdrPageView();
896
0
                    mpView->UnmarkAllObj(pPageView);
897
898
0
                    std::vector<basegfx::B2DRectangle> aSubSelections;
899
0
                    basegfx::B2DRectangle aSubSelection = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
900
0
                    if (!aSubSelection.isEmpty())
901
0
                        aSubSelections.push_back(aSubSelection);
902
0
                    mpView->MarkObj(mpObj, pPageView, false, false, std::move(aSubSelections));
903
0
                }
904
0
                else
905
0
                {
906
0
                    rVectorGraphicSearchContext.reset();
907
0
                }
908
0
            }
909
0
            else
910
0
            {
911
                // When replacing we first check if there is a selection
912
                // indicating a match.  If there is then replace it.  The
913
                // following call to StartSearchAndReplace will then search for
914
                // the next match.
915
0
                if (meMode == SEARCH && mpSearchItem->GetCommand() == SvxSearchCmd::REPLACE)
916
0
                {
917
0
                    if (getOutlinerView()->GetSelection().HasRange())
918
0
                        getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
919
0
                }
920
921
                // Search for the next match.
922
0
                if (mpSearchItem->GetCommand() != SvxSearchCmd::REPLACE_ALL)
923
0
                {
924
0
                    nMatchCount = getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
925
0
                    if (nMatchCount && maCurrentPosition.meEditMode == EditMode::Page
926
0
                        && maCurrentPosition.mePageKind == PageKind::Notes)
927
0
                    {
928
0
                        if(auto pNotesPaneOutliner = lclGetNotesPaneOutliner(pViewShell))
929
0
                        {
930
0
                            pNotesPaneOutliner->SetSelection(getOutlinerView()->GetSelection());
931
0
                        }
932
0
                    }
933
0
                }
934
0
            }
935
936
            // Go to the next text object when there have been no matches in
937
            // the current object or the whole object has already been
938
            // processed.
939
0
            if (nMatchCount==0 || mpSearchItem->GetCommand()==SvxSearchCmd::REPLACE_ALL)
940
0
            {
941
0
                ProvideNextTextObject ();
942
943
0
                if (!mbEndOfSearch && !rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
944
0
                {
945
                    // Remember the current position as the last one with a
946
                    // text object.
947
0
                    maLastValidPosition = maCurrentPosition;
948
949
                    // Now that the mbEndOfSearch flag guards this block the
950
                    // following assertion and return should not be
951
                    // necessary anymore.
952
0
                    DBG_ASSERT(GetEditEngine().HasView(&getOutlinerView()->GetEditView() ),
953
0
                        "SearchAndReplace without valid view!" );
954
0
                    if ( ! GetEditEngine().HasView( &getOutlinerView()->GetEditView() )
955
0
                         && maCurrentPosition.mePageKind != PageKind::Notes )
956
0
                    {
957
0
                        mrDrawDocument.GetDocSh()->SetWaitCursor( false );
958
0
                        return true;
959
0
                    }
960
961
0
                    if (meMode == SEARCH)
962
0
                    {
963
0
                        auto nMatch = getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
964
0
                        if (nMatch && maCurrentPosition.meEditMode == EditMode::Page
965
0
                            && maCurrentPosition.mePageKind == PageKind::Notes)
966
0
                        {
967
0
                            if(auto pNotesPaneOutliner = lclGetNotesPaneOutliner(pViewShell))
968
0
                            {
969
0
                                pNotesPaneOutliner->SetSelection(getOutlinerView()->GetSelection());
970
0
                            }
971
0
                        }
972
0
                    }
973
0
                }
974
0
            }
975
0
        }
976
0
        else if (nullptr != dynamic_cast<const sd::OutlineViewShell*>(pViewShell.get()))
977
0
        {
978
0
            mrDrawDocument.GetDocSh()->SetWaitCursor(false);
979
            // The following loop is executed more than once only when a
980
            // wrap around search is done.
981
0
            while (true)
982
0
            {
983
0
                int nResult = getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
984
0
                if (nResult == 0)
985
0
                {
986
0
                    if (HandleFailedSearch ())
987
0
                    {
988
0
                        getOutlinerView()->SetSelection (GetSearchStartPosition ());
989
0
                        continue;
990
0
                    }
991
0
                }
992
0
                else
993
0
                    mbStringFound = true;
994
0
                break;
995
0
            }
996
0
        }
997
0
    }
998
999
0
    mrDrawDocument.GetDocSh()->SetWaitCursor( false );
1000
1001
0
    if (pViewShell && comphelper::LibreOfficeKit::isActive() && mbStringFound)
1002
0
    {
1003
0
        sendLOKSearchResultCallback(pViewShell, getOutlinerView(), pSelections);
1004
0
    }
1005
1006
0
    return mbEndOfSearch;
1007
0
}
1008
1009
/** Try to detect whether the document or the view (shell) has changed since
1010
    the last time <member>StartSearchAndReplace()</member> has been called.
1011
*/
1012
void SdOutliner::DetectChange()
1013
0
{
1014
0
    sd::outliner::IteratorPosition aPosition (maCurrentPosition);
1015
1016
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1017
0
    std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
1018
0
        std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1019
1020
0
    std::shared_ptr<sd::ViewShell> pOverridingViewShell{};
1021
0
    if(sd::ViewShellBase* pBase = getViewShellBase())
1022
0
    {
1023
0
        if (const std::shared_ptr<sd::ViewShellManager>& pViewShellManager = pBase->GetViewShellManager())
1024
0
            pOverridingViewShell = pViewShellManager->GetOverridingMainShell();
1025
0
    }
1026
1027
0
    bool bViewChanged = false;
1028
1029
0
    if( pDrawViewShell )
1030
0
    {
1031
0
        if( !pOverridingViewShell )
1032
0
            bViewChanged = (aPosition.meEditMode != pDrawViewShell->GetEditMode() || aPosition.mePageKind != pDrawViewShell->GetPageKind());
1033
0
        else
1034
0
        {
1035
0
            auto pPage = pOverridingViewShell->getCurrentPage();
1036
0
            auto ePageKind = pPage ? pPage->GetPageKind() : PageKind::Standard;
1037
0
            auto eEditMode = EditMode::Page;
1038
0
            bViewChanged = (aPosition.meEditMode != eEditMode || aPosition.mePageKind != ePageKind);
1039
0
        }
1040
0
    }
1041
1042
    // Detect whether the view has been switched from the outside.
1043
0
    if( bViewChanged )
1044
0
    {
1045
        // Either the edit mode or the page kind has changed.
1046
0
        SetStatusEventHdl(Link<EditStatus&,void>());
1047
1048
0
        SdrPageView* pPageView = mpView->GetSdrPageView();
1049
0
        if (pPageView != nullptr)
1050
0
            mpView->UnmarkAllObj (pPageView);
1051
0
        mpView->SdrEndTextEdit();
1052
0
        SetUpdateLayout(false);
1053
0
        OutlinerView* pOutlinerView = getOutlinerView();
1054
0
        if (pOutlinerView != nullptr)
1055
0
            pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1) ) );
1056
0
        if (meMode == SPELL)
1057
0
            SetPaperSize( Size(1, 1) );
1058
0
        SetText(OUString(), GetParagraph(0));
1059
1060
0
        RememberStartPosition ();
1061
1062
0
        mnPageCount = mrDrawDocument.GetSdPageCount(pDrawViewShell->GetPageKind());
1063
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1064
0
    }
1065
1066
    // Detect change of the set of selected objects.  If their number has
1067
    // changed start again with the first selected object.
1068
0
    else if (DetectSelectionChange())
1069
0
    {
1070
0
        HandleChangedSelection ();
1071
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1072
0
    }
1073
1074
    // Detect change of page count.  Restart search at first/last page in
1075
    // that case.
1076
0
    else if (aPosition.meEditMode == EditMode::Page
1077
0
        && mrDrawDocument.GetSdPageCount(aPosition.mePageKind) != mnPageCount)
1078
0
    {
1079
        // The number of pages has changed.
1080
0
        mnPageCount = mrDrawDocument.GetSdPageCount(aPosition.mePageKind);
1081
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1082
0
    }
1083
0
    else if (aPosition.meEditMode == EditMode::MasterPage
1084
0
        && mrDrawDocument.GetSdPageCount(aPosition.mePageKind) != mnPageCount)
1085
0
    {
1086
        // The number of master pages has changed.
1087
0
        mnPageCount = mrDrawDocument.GetSdPageCount(aPosition.mePageKind);
1088
0
        maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1089
0
    }
1090
0
}
1091
1092
bool SdOutliner::DetectSelectionChange()
1093
0
{
1094
0
    bool bSelectionHasChanged = false;
1095
1096
    // If mpObj is NULL then we have not yet found our first match.
1097
    // Detecting a change makes no sense.
1098
0
    if (mpObj != nullptr)
1099
0
    {
1100
0
        const size_t nMarkCount = mpView ? mpView->GetMarkedObjectList().GetMarkCount() : 0;
1101
0
        switch (nMarkCount)
1102
0
        {
1103
0
            case 0:
1104
                // The selection has changed when previously there have been
1105
                // selected objects.
1106
0
                bSelectionHasChanged = mbRestrictSearchToSelection;
1107
0
                break;
1108
0
            case 1:
1109
                // Check if the only selected object is not the one that we
1110
                // had selected.
1111
0
                if (mpView != nullptr)
1112
0
                {
1113
0
                    SdrMark* pMark = mpView->GetMarkedObjectList().GetMark(0);
1114
0
                    if (pMark != nullptr)
1115
0
                        bSelectionHasChanged = (mpObj != pMark->GetMarkedSdrObj ());
1116
0
                }
1117
0
                break;
1118
0
            default:
1119
                // We had selected exactly one object.
1120
0
                bSelectionHasChanged = true;
1121
0
                break;
1122
0
        }
1123
0
    }
1124
1125
0
    return bSelectionHasChanged;
1126
0
}
1127
1128
void SdOutliner::RememberStartPosition()
1129
0
{
1130
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1131
0
    if ( ! pViewShell)
1132
0
    {
1133
0
        OSL_ASSERT(pViewShell);
1134
0
        return;
1135
0
    }
1136
1137
0
    if ( mnStartPageIndex != sal_uInt16(-1) )
1138
0
        return;
1139
1140
0
    if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
1141
0
    {
1142
0
        std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
1143
0
            std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1144
0
        if (pDrawViewShell != nullptr)
1145
0
        {
1146
0
            meStartViewMode = pDrawViewShell->GetPageKind();
1147
0
            meStartEditMode = pDrawViewShell->GetEditMode();
1148
0
            mnStartPageIndex = pDrawViewShell->GetCurPagePos();
1149
0
        }
1150
1151
0
        if (mpView != nullptr)
1152
0
        {
1153
0
            mpStartEditedObject = mpView->GetTextEditObject();
1154
0
            if (mpStartEditedObject != nullptr)
1155
0
            {
1156
                // Try to retrieve current caret position only when there is an
1157
                // edited object.
1158
0
                ::Outliner* pOutliner =
1159
0
                    static_cast<sd::DrawView*>(mpView)->GetTextEditOutliner();
1160
0
                if (pOutliner!=nullptr && pOutliner->GetViewCount()>0)
1161
0
                {
1162
0
                    OutlinerView* pOutlinerView = pOutliner->GetView(0);
1163
0
                    maStartSelection = pOutlinerView->GetSelection();
1164
0
                }
1165
0
            }
1166
0
        }
1167
0
    }
1168
0
    else if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1169
0
    {
1170
        // Remember the current cursor position.
1171
0
        OutlinerView* pView = GetView(0);
1172
0
        if (pView != nullptr)
1173
0
            pView->GetSelection();
1174
0
    }
1175
0
}
1176
1177
void SdOutliner::RestoreStartPosition()
1178
0
{
1179
0
    bool bRestore = true;
1180
    // Take a negative start page index as indicator that restoring the
1181
    // start position is not requested.
1182
0
    if (mnStartPageIndex == sal_uInt16(-1) )
1183
0
        bRestore = false;
1184
    // Don't restore when the view shell is not valid.
1185
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1186
0
    if (pViewShell == nullptr)
1187
0
        bRestore = false;
1188
1189
0
    if (!bRestore)
1190
0
        return;
1191
1192
0
    if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
1193
0
    {
1194
0
        std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
1195
0
            std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1196
0
        SetViewMode (meStartViewMode);
1197
0
        if (pDrawViewShell != nullptr)
1198
0
        {
1199
0
            SetPage (meStartEditMode, mnStartPageIndex);
1200
0
            mpObj = mpStartEditedObject;
1201
0
            if (mpObj)
1202
0
            {
1203
0
                PutTextIntoOutliner();
1204
0
                EnterEditMode(false);
1205
0
                if (getOutlinerView())
1206
0
                    getOutlinerView()->SetSelection(maStartSelection);
1207
0
            }
1208
0
        }
1209
0
    }
1210
0
    else if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1211
0
    {
1212
        // Set cursor to its old position.
1213
0
        OutlinerView* pView = GetView(0);
1214
0
        if (pView != nullptr)
1215
0
            pView->SetSelection (maStartSelection);
1216
0
    }
1217
0
}
1218
1219
namespace
1220
{
1221
1222
bool lclIsValidTextObject(const sd::outliner::IteratorPosition& rPosition)
1223
0
{
1224
0
    auto* pObject = DynCastSdrTextObj( rPosition.mxObject.get().get() );
1225
0
    return (pObject != nullptr) && pObject->HasText() && ! pObject->IsEmptyPresObj();
1226
0
}
1227
1228
bool isValidVectorGraphicObject(const sd::outliner::IteratorPosition& rPosition)
1229
0
{
1230
0
    rtl::Reference<SdrGrafObj> pGraphicObject = dynamic_cast<SdrGrafObj*>(rPosition.mxObject.get().get());
1231
0
    if (pGraphicObject)
1232
0
    {
1233
0
        auto const& pVectorGraphicData = pGraphicObject->GetGraphic().getVectorGraphicData();
1234
0
        if (pVectorGraphicData && VectorGraphicDataType::Pdf == pVectorGraphicData->getType())
1235
0
        {
1236
0
            return true;
1237
0
        }
1238
0
    }
1239
0
    return false;
1240
0
}
1241
1242
} // end anonymous namespace
1243
1244
1245
/** The main purpose of this method is to iterate over all shape objects of
1246
    the search area (current selection, current view, or whole document)
1247
    until a text object has been found that contains at least one match or
1248
    until no such object can be found anymore.   These two conditions are
1249
    expressed by setting one of the flags <member>mbFoundObject</member> or
1250
    <member>mbEndOfSearch</member> to <TRUE/>.
1251
*/
1252
void SdOutliner::ProvideNextTextObject()
1253
0
{
1254
0
    mbEndOfSearch = false;
1255
0
    mbFoundObject = false;
1256
1257
    // reset the vector search
1258
0
    auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
1259
0
    rVectorGraphicSearchContext.reset();
1260
1261
0
    mpView->UnmarkAllObj (mpView->GetSdrPageView());
1262
0
    try
1263
0
    {
1264
0
        mpView->SdrEndTextEdit();
1265
0
    }
1266
0
    catch (const css::uno::Exception&)
1267
0
    {
1268
0
        DBG_UNHANDLED_EXCEPTION("sd.view");
1269
0
    }
1270
0
    SetUpdateLayout(false);
1271
0
    OutlinerView* pOutlinerView = getOutlinerView();
1272
0
    if (pOutlinerView != nullptr)
1273
0
        pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1) ) );
1274
0
    if (meMode == SPELL)
1275
0
        SetPaperSize( Size(1, 1) );
1276
0
    SetText(OUString(), GetParagraph(0));
1277
1278
0
    mpSearchSpellTextObj = nullptr;
1279
1280
    // Iterate until a valid text object has been found or the search ends.
1281
0
    do
1282
0
    {
1283
0
        mpObj = nullptr;
1284
0
        mpParaObj = nullptr;
1285
1286
0
        if (maObjectIterator != sd::outliner::OutlinerContainer(this).end())
1287
0
        {
1288
0
            maCurrentPosition = *maObjectIterator;
1289
1290
            // LOK: do not descent to notes or master pages when searching
1291
0
            bool bForbiddenPage = comphelper::LibreOfficeKit::isActive() && (maCurrentPosition.mePageKind != PageKind::Standard || maCurrentPosition.meEditMode != EditMode::Page);
1292
1293
0
            rVectorGraphicSearchContext.reset();
1294
1295
0
            if (!bForbiddenPage)
1296
0
            {
1297
                // Switch to the current object only if it is a valid text object.
1298
0
                if (lclIsValidTextObject(maCurrentPosition))
1299
0
                {
1300
                    // Don't set yet in case of searching: the text object may not match.
1301
0
                    if (meMode != SEARCH)
1302
0
                        mpObj = SetObject(maCurrentPosition);
1303
0
                    else
1304
0
                        mpObj = maCurrentPosition.mxObject.get().get();
1305
0
                }
1306
                // Or if the object is a valid graphic object which contains vector graphic
1307
0
                else if (meMode == SEARCH && isValidVectorGraphicObject(maCurrentPosition))
1308
0
                {
1309
0
                    mpObj = maCurrentPosition.mxObject.get().get();
1310
0
                    rVectorGraphicSearchContext.mbCurrentIsVectorGraphic = true;
1311
0
                }
1312
0
            }
1313
1314
            // Advance to the next object
1315
0
            ++maObjectIterator;
1316
1317
0
            if (mpObj)
1318
0
            {
1319
0
                if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
1320
0
                {
1321
                    // We know here the object is a SdrGrafObj and that it
1322
                    // contains a vector graphic
1323
0
                    auto* pGraphicObject = static_cast<SdrGrafObj*>(mpObj);
1324
0
                    OUString const & rString = mpSearchItem->GetSearchString();
1325
0
                    bool bBackwards = mpSearchItem->GetBackward();
1326
1327
0
                    VectorGraphicSearchOptions aOptions;
1328
0
                    aOptions.meStartPosition = bBackwards ? SearchStartPosition::End : SearchStartPosition::Begin;
1329
0
                    aOptions.mbMatchCase = mpSearchItem->GetExact();
1330
0
                    aOptions.mbMatchWholeWord = mpSearchItem->GetWordOnly();
1331
1332
0
                    rVectorGraphicSearchContext.mpVectorGraphicSearch = std::make_unique<VectorGraphicSearch>(pGraphicObject->GetGraphic());
1333
1334
0
                    bool bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->search(rString, aOptions);
1335
0
                    if (bResult)
1336
0
                    {
1337
0
                        if (bBackwards)
1338
0
                            bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->previous();
1339
0
                        else
1340
0
                            bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->next();
1341
0
                    }
1342
1343
0
                    if (bResult)
1344
0
                    {
1345
0
                        mpObj = SetObject(maCurrentPosition);
1346
1347
0
                        mbStringFound = true;
1348
0
                        mbMatchMayExist = true;
1349
0
                        mbFoundObject = true;
1350
1351
0
                        SdrPageView* pPageView = mpView->GetSdrPageView();
1352
0
                        mpView->UnmarkAllObj(pPageView);
1353
1354
0
                        std::vector<basegfx::B2DRectangle> aSubSelections;
1355
0
                        basegfx::B2DRectangle aSubSelection = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
1356
0
                        if (!aSubSelection.isEmpty())
1357
0
                            aSubSelections.push_back(aSubSelection);
1358
1359
0
                        mpView->MarkObj(mpObj, pPageView, false, false, std::move(aSubSelections));
1360
1361
0
                        mrDrawDocument.GetDocSh()->SetWaitCursor( false );
1362
0
                    }
1363
0
                    else
1364
0
                    {
1365
0
                        rVectorGraphicSearchContext.reset();
1366
0
                    }
1367
0
                }
1368
0
                else
1369
0
                {
1370
0
                    PutTextIntoOutliner();
1371
1372
0
                    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1373
0
                    if (pViewShell != nullptr)
1374
0
                    {
1375
0
                        switch (meMode)
1376
0
                        {
1377
0
                            case SEARCH:
1378
0
                                PrepareSearchAndReplace ();
1379
0
                                break;
1380
0
                            case SPELL:
1381
0
                                PrepareSpellCheck ();
1382
0
                                break;
1383
0
                            case TEXT_CONVERSION:
1384
0
                                PrepareConversion();
1385
0
                                break;
1386
0
                        }
1387
0
                    }
1388
0
                }
1389
0
            }
1390
0
        }
1391
0
        else
1392
0
        {
1393
0
            rVectorGraphicSearchContext.reset();
1394
1395
0
            if (meMode == SEARCH)
1396
                // Instead of doing a full-blown SetObject(), which would do the same -- but would also possibly switch pages.
1397
0
                mbStringFound = false;
1398
1399
0
            mbEndOfSearch = true;
1400
0
            EndOfSearch ();
1401
0
        }
1402
0
    }
1403
0
    while ( ! (mbFoundObject || mbEndOfSearch));
1404
0
}
1405
1406
void SdOutliner::EndOfSearch()
1407
0
{
1408
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1409
0
    if ( ! pViewShell)
1410
0
    {
1411
0
        OSL_ASSERT(pViewShell);
1412
0
        return;
1413
0
    }
1414
1415
    // Before we display a dialog we first jump to where the last valid text
1416
    // object was found.  All page and view mode switching since then was
1417
    // temporary and should not be visible to the user.
1418
0
    if(  nullptr == dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1419
0
        SetObject (maLastValidPosition);
1420
1421
0
    if (mbRestrictSearchToSelection)
1422
0
        ShowEndOfSearchDialog ();
1423
0
    else
1424
0
    {
1425
        // When no match has been found so far then terminate the search.
1426
0
        if ( ! mbMatchMayExist)
1427
0
        {
1428
0
            ShowEndOfSearchDialog ();
1429
0
            mbEndOfSearch = true;
1430
0
        }
1431
        // Ask the user whether to wrap around and continue the search or
1432
        // to terminate.
1433
0
        else if (meMode==TEXT_CONVERSION || ShowWrapAroundDialog ())
1434
0
        {
1435
0
            mbMatchMayExist = false;
1436
            // Everything back to beginning (or end?) of the document.
1437
0
            maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
1438
0
            if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1439
0
            {
1440
                // Set cursor to first character of the document.
1441
0
                OutlinerView* pOutlinerView = getOutlinerView();
1442
0
                if (pOutlinerView != nullptr)
1443
0
                    pOutlinerView->SetSelection (GetSearchStartPosition ());
1444
0
            }
1445
1446
0
            mbEndOfSearch = false;
1447
0
        }
1448
0
        else
1449
0
        {
1450
            // No wrap around.
1451
0
            mbEndOfSearch = true;
1452
0
        }
1453
0
    }
1454
0
}
1455
1456
void SdOutliner::ShowEndOfSearchDialog()
1457
0
{
1458
0
    if (meMode == SEARCH)
1459
0
    {
1460
0
        if (!mbStringFound)
1461
0
        {
1462
0
            SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound);
1463
0
            std::shared_ptr<sd::ViewShell> pViewShell(mpWeakViewShell.lock());
1464
0
            if (pViewShell)
1465
0
            {
1466
0
                SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
1467
0
                rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_NOT_FOUND, mpSearchItem->GetSearchString().toUtf8());
1468
0
            }
1469
0
        }
1470
1471
        // don't do anything else for search
1472
0
        return;
1473
0
    }
1474
1475
0
    OUString aString;
1476
0
    const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();
1477
0
    if (rMarkList.GetMarkCount() != 0)
1478
0
        aString = SdResId(STR_END_SPELLING_OBJ);
1479
0
    else
1480
0
        aString = SdResId(STR_END_SPELLING);
1481
1482
    // Show the message in an info box that is modal with respect to the whole application.
1483
0
    weld::Window* pParent = GetMessageBoxParent();
1484
0
    std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent,
1485
0
                                                  VclMessageType::Info, VclButtonsType::Ok, aString));
1486
0
    xInfoBox->run();
1487
0
}
1488
1489
bool SdOutliner::ShowWrapAroundDialog()
1490
0
{
1491
    // Determine whether to show the dialog.
1492
0
    if (mpSearchItem)
1493
0
    {
1494
        // When searching display the dialog only for single find&replace.
1495
0
        const SvxSearchCmd nCommand(mpSearchItem->GetCommand());
1496
0
        if (nCommand == SvxSearchCmd::REPLACE || nCommand == SvxSearchCmd::FIND)
1497
0
        {
1498
0
            if (mbDirectionIsForward)
1499
0
                SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::End);
1500
0
            else
1501
0
                SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Start);
1502
1503
0
            return true;
1504
0
        }
1505
0
        else
1506
0
            return false;
1507
0
    }
1508
1509
    // show dialog only for spelling
1510
0
    if (meMode != SPELL)
1511
0
        return false;
1512
1513
    // The question text depends on the search direction.
1514
0
    bool bImpress = mrDrawDocument.GetDocumentType() == DocumentType::Impress;
1515
1516
0
    TranslateId pStringId;
1517
0
    if (mbDirectionIsForward)
1518
0
        pStringId = bImpress ? STR_SAR_WRAP_FORWARD : STR_SAR_WRAP_FORWARD_DRAW;
1519
0
    else
1520
0
        pStringId = bImpress ? STR_SAR_WRAP_BACKWARD : STR_SAR_WRAP_BACKWARD_DRAW;
1521
1522
    // Pop up question box that asks the user whether to wrap around.
1523
    // The dialog is made modal with respect to the whole application.
1524
0
    weld::Window* pParent = GetMessageBoxParent();
1525
0
    std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pParent,
1526
0
                                                   VclMessageType::Question, VclButtonsType::YesNo, SdResId(pStringId)));
1527
0
    sal_uInt16 nBoxResult = xQueryBox->run();
1528
1529
0
    return (nBoxResult == RET_YES);
1530
0
}
1531
1532
void SdOutliner::PutTextIntoOutliner()
1533
0
{
1534
0
    mpSearchSpellTextObj = DynCastSdrTextObj( mpObj );
1535
0
    if ( mpSearchSpellTextObj && mpSearchSpellTextObj->HasText() && !mpSearchSpellTextObj->IsEmptyPresObj() )
1536
0
    {
1537
0
        SdrText* pText = mpSearchSpellTextObj->getText( maCurrentPosition.mnText );
1538
0
        mpParaObj = pText ? pText->GetOutlinerParaObject() : nullptr;
1539
1540
0
        if (mpParaObj != nullptr)
1541
0
        {
1542
0
            SetText(*mpParaObj);
1543
1544
0
            ClearModifyFlag();
1545
0
        }
1546
0
    }
1547
0
    else
1548
0
    {
1549
0
        mpSearchSpellTextObj = nullptr;
1550
0
    }
1551
0
}
1552
1553
void SdOutliner::PrepareSpellCheck()
1554
0
{
1555
0
    EESpellState eState = HasSpellErrors();
1556
0
    DBG_ASSERT(eState != EESpellState::NoSpeller, "No SpellChecker");
1557
1558
0
    if (eState == EESpellState::Ok)
1559
0
        return;
1560
1561
    // When spell checking we have to test whether we have processed the
1562
    // whole document and have reached the start page again.
1563
0
    if (meMode == SPELL)
1564
0
    {
1565
0
        if (maSearchStartPosition == sd::outliner::Iterator())
1566
            // Remember the position of the first text object so that we
1567
            // know when we have processed the whole document.
1568
0
            maSearchStartPosition = maObjectIterator;
1569
0
        else if (maSearchStartPosition == maObjectIterator)
1570
0
        {
1571
0
            mbEndOfSearch = true;
1572
0
        }
1573
0
    }
1574
1575
0
    EnterEditMode( false );
1576
0
}
1577
1578
void SdOutliner::PrepareSearchAndReplace()
1579
0
{
1580
0
    if (!HasText( *mpSearchItem ))
1581
0
        return;
1582
1583
    // Set the object now that we know it matches.
1584
0
    mpObj = SetObject(maCurrentPosition);
1585
1586
0
    mbStringFound = true;
1587
0
    mbMatchMayExist = true;
1588
1589
0
    EnterEditMode(false);
1590
1591
0
    mrDrawDocument.GetDocSh()->SetWaitCursor(false);
1592
1593
0
    OutlinerView* pOutlinerView = getOutlinerView();
1594
0
    if (pOutlinerView != nullptr)
1595
0
    {
1596
0
        pOutlinerView->SetSelection (GetSearchStartPosition ());
1597
0
        if (lclIsValidTextObject(maCurrentPosition) && maCurrentPosition.mePageKind == PageKind::Notes)
1598
0
        {
1599
0
            if (auto pNotesPaneOutliner = lclGetNotesPaneOutliner(mpWeakViewShell.lock()))
1600
0
            {
1601
0
                pNotesPaneOutliner->SetSelection(getOutlinerView()->GetSelection());
1602
0
            }
1603
0
        }
1604
0
    }
1605
0
}
1606
1607
void SdOutliner::SetViewMode (PageKind ePageKind)
1608
0
{
1609
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1610
0
    std::shared_ptr<sd::DrawViewShell> pDrawViewShell(
1611
0
        std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1612
0
    if (pDrawViewShell == nullptr || ePageKind == pDrawViewShell->GetPageKind())
1613
0
        return;
1614
1615
    // Restore old edit mode.
1616
0
    pDrawViewShell->ChangeEditMode(mpImpl->meOriginalEditMode, false);
1617
1618
0
    SetStatusEventHdl(Link<EditStatus&,void>());
1619
0
    OUString sViewURL;
1620
0
    switch (ePageKind)
1621
0
    {
1622
0
        case PageKind::Standard:
1623
0
        default:
1624
0
            sViewURL = sd::framework::FrameworkHelper::msImpressViewURL;
1625
0
            break;
1626
0
        case PageKind::Notes:
1627
0
            sViewURL = sd::framework::FrameworkHelper::msNotesViewURL;
1628
0
            break;
1629
0
        case PageKind::Handout:
1630
0
            sViewURL = sd::framework::FrameworkHelper::msHandoutViewURL;
1631
0
            break;
1632
0
    }
1633
    // The text object iterator is destroyed when the shells are
1634
    // switched but we need it so save it and restore it afterwards.
1635
0
    sd::outliner::Iterator aIterator (maObjectIterator);
1636
0
    bool bMatchMayExist = mbMatchMayExist;
1637
1638
0
    sd::ViewShellBase& rBase = pViewShell->GetViewShellBase();
1639
1640
0
    rtl::Reference<sd::FuSearch> xFuSearch;
1641
0
    if (pViewShell->GetView())
1642
0
        xFuSearch = pViewShell->GetView()->getSearchContext().getFunctionSearch();
1643
1644
0
    SetViewShell(std::shared_ptr<sd::ViewShell>());
1645
0
    sd::framework::FrameworkHelper::Instance(rBase)->RequestView(
1646
0
        sViewURL,
1647
0
        sd::framework::FrameworkHelper::msCenterPaneURL);
1648
1649
    // Force (well, request) a synchronous update of the configuration.
1650
    // In a better world we would handle the asynchronous view update
1651
    // instead.  But that would involve major restructuring of the
1652
    // Outliner code.
1653
0
    sd::framework::FrameworkHelper::Instance(rBase)->RequestSynchronousUpdate();
1654
1655
0
    auto pNewViewShell = rBase.GetMainViewShell();
1656
0
    SetViewShell(pNewViewShell);
1657
0
    if (xFuSearch.is() && pNewViewShell->GetView())
1658
0
        pNewViewShell->GetView()->getSearchContext().setSearchFunction(xFuSearch);
1659
1660
    // Switching to another view shell has intermediatly called
1661
    // EndSpelling().  A PrepareSpelling() is pending, so call that now.
1662
0
    PrepareSpelling();
1663
1664
    // Update the number of pages so that
1665
    // <member>DetectChange()</member> has the correct value to compare
1666
    // to.
1667
0
    mnPageCount = mrDrawDocument.GetSdPageCount(ePageKind);
1668
1669
0
    maObjectIterator = std::move(aIterator);
1670
0
    mbMatchMayExist = bMatchMayExist;
1671
1672
    // Save edit mode so that it can be restored when switching the view
1673
    // shell again.
1674
0
    pDrawViewShell = std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell);
1675
0
    OSL_ASSERT(pDrawViewShell != nullptr);
1676
0
    if (pDrawViewShell != nullptr)
1677
0
        mpImpl->meOriginalEditMode = pDrawViewShell->GetEditMode();
1678
0
}
1679
1680
void SdOutliner::SetPage (EditMode eEditMode, sal_uInt16 nPageIndex)
1681
0
{
1682
0
    if ( ! mbRestrictSearchToSelection)
1683
0
    {
1684
0
        std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1685
0
        std::shared_ptr<sd::DrawViewShell> pDrawViewShell(
1686
0
            std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1687
0
        OSL_ASSERT(pDrawViewShell != nullptr);
1688
0
        if (pDrawViewShell != nullptr)
1689
0
        {
1690
0
            pDrawViewShell->ChangeEditMode(eEditMode, false);
1691
0
            pDrawViewShell->SwitchPage(nPageIndex);
1692
0
        }
1693
0
    }
1694
0
}
1695
1696
void SdOutliner::EnterEditMode (bool bGrabFocus)
1697
0
{
1698
0
    OutlinerView* pOutlinerView = getOutlinerView();
1699
0
    if (!(pOutlinerView && mpSearchSpellTextObj))
1700
0
        return;
1701
1702
0
    pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1)));
1703
0
    SetPaperSize( mpSearchSpellTextObj->GetLogicRect().GetSize() );
1704
0
    SdrPageView* pPV = mpView->GetSdrPageView();
1705
1706
    // Make FuText the current function.
1707
0
    SfxUInt16Item aItem (SID_TEXTEDIT, 1);
1708
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1709
0
    if (!(pViewShell && pViewShell->GetDispatcher()))
1710
0
        return;
1711
1712
0
    pViewShell->GetDispatcher()->ExecuteList(
1713
0
        SID_TEXTEDIT, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, {&aItem});
1714
1715
0
    if (mpView->IsTextEdit())
1716
0
    {
1717
        // end text edition before starting it again
1718
0
        mpView->SdrEndTextEdit();
1719
0
    }
1720
1721
    // To be consistent with the usual behaviour in the Office the text
1722
    // object that is put into edit mode would have also to be selected.
1723
    // Starting the text edit mode is not enough so we do it here by
1724
    // hand.
1725
0
    mpView->UnmarkAllObj(pPV);
1726
0
    mpView->MarkObj(mpSearchSpellTextObj, pPV);
1727
1728
0
    mpSearchSpellTextObj->setActiveText(mnText);
1729
1730
    // Turn on the edit mode for the text object.
1731
0
    SetUpdateLayout(true);
1732
1733
0
    if(maCurrentPosition.mePageKind == PageKind::Notes
1734
0
       && maCurrentPosition.meEditMode == EditMode::Page)
1735
0
    {
1736
0
        sd::ViewShellBase& rBase = pViewShell->GetViewShellBase();
1737
1738
0
        sd::framework::FrameworkHelper::Instance(rBase)->RequestView(
1739
0
            sd::framework::FrameworkHelper::msNotesPanelViewURL,
1740
0
            sd::framework::FrameworkHelper::msBottomImpressPaneURL);
1741
1742
0
        auto pInstance = sd::framework::FrameworkHelper::Instance(rBase);
1743
0
        pInstance->RequestSynchronousUpdate();
1744
1745
0
        std::shared_ptr<sd::ViewShell> pNotesPaneShell(pInstance->GetViewShell(sd::framework::FrameworkHelper::msBottomImpressPaneURL));
1746
0
        if(pNotesPaneShell)
1747
0
        {
1748
0
            pNotesPaneShell->GetParentWindow()->GrabFocus();
1749
0
            pNotesPaneShell->GetContentWindow()->GrabFocus();
1750
0
        }
1751
0
    }
1752
0
    else
1753
0
    {
1754
0
        if (sd::ViewShellBase* pBase = getViewShellBase())
1755
0
        {
1756
0
            std::shared_ptr<sd::ViewShell> pOverridingViewShell{};
1757
0
            if (auto pViewShellManager = pBase->GetViewShellManager())
1758
0
                pOverridingViewShell = pViewShellManager->GetOverridingMainShell();
1759
1760
0
            if (pOverridingViewShell)
1761
0
            {
1762
0
                auto pMainViewShell = pBase->GetMainViewShell().get();
1763
0
                pMainViewShell->GetParentWindow()->GrabFocus();
1764
0
                pMainViewShell->GetContentWindow()->GrabFocus();
1765
0
                bGrabFocus = true;
1766
0
            }
1767
0
        }
1768
1769
0
        mpView->SdrBeginTextEdit(mpSearchSpellTextObj, pPV, mpWindow, true, this, pOutlinerView,
1770
0
                                 true, true, bGrabFocus);
1771
0
    }
1772
1773
0
    mbFoundObject = true;
1774
0
}
1775
1776
ESelection SdOutliner::GetSearchStartPosition() const
1777
0
{
1778
    // The default constructor uses the beginning of the text as default.
1779
0
    ESelection aPosition;
1780
0
    if (!mbDirectionIsForward)
1781
0
    {
1782
        // Retrieve the position after the last character in the last
1783
        // paragraph.
1784
0
        sal_Int32 nParagraphCount = GetParagraphCount();
1785
0
        if (nParagraphCount != 0)
1786
0
        {
1787
0
            sal_Int32 nLastParagraphLength = GetEditEngine().GetTextLen (
1788
0
                nParagraphCount-1);
1789
0
            aPosition = ESelection (nParagraphCount-1, nLastParagraphLength);
1790
0
        }
1791
0
    }
1792
1793
0
    return aPosition;
1794
0
}
1795
1796
bool SdOutliner::HasNoPreviousMatch()
1797
0
{
1798
0
    OutlinerView* pOutlinerView = getOutlinerView();
1799
1800
0
    assert(pOutlinerView && "outline view in SdOutliner::HasNoPreviousMatch is NULL");
1801
1802
    // Detect whether the cursor stands at the beginning
1803
    // resp. at the end of the text.
1804
0
    return pOutlinerView->GetSelection() == GetSearchStartPosition();
1805
0
}
1806
1807
bool SdOutliner::HandleFailedSearch()
1808
0
{
1809
0
    bool bContinueSearch = false;
1810
1811
0
    OutlinerView* pOutlinerView = getOutlinerView();
1812
0
    if (pOutlinerView && mpSearchItem)
1813
0
    {
1814
        // Detect whether there is/may be a prior match.  If there is then
1815
        // ask the user whether to wrap around.  Otherwise tell the user
1816
        // that there is no match.
1817
0
        if (HasNoPreviousMatch ())
1818
0
        {
1819
            // No match found in the whole presentation.
1820
0
            SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound);
1821
0
        }
1822
1823
0
        else
1824
0
        {
1825
            // No further matches found.  Ask the user whether to wrap
1826
            // around and start again.
1827
0
            bContinueSearch = ShowWrapAroundDialog();
1828
0
        }
1829
0
    }
1830
1831
0
    return bContinueSearch;
1832
0
}
1833
1834
SdrObject* SdOutliner::SetObject (
1835
    const sd::outliner::IteratorPosition& rPosition)
1836
0
{
1837
0
    if(rPosition.meEditMode == EditMode::Page && rPosition.mePageKind == PageKind::Notes)
1838
0
    {
1839
0
        std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1840
0
        if (std::shared_ptr<sd::DrawViewShell> pDrawViewShell =
1841
0
            std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell))
1842
0
        {
1843
0
            if (pDrawViewShell->GetEditMode() != EditMode::Page
1844
0
                || pDrawViewShell->GetCurPagePos() != rPosition.mnPageIndex)
1845
0
                SetPage(EditMode::Page, static_cast<sal_uInt16>(rPosition.mnPageIndex));
1846
0
        }
1847
0
        mnText = rPosition.mnText;
1848
0
        return rPosition.mxObject.get().get();
1849
0
    }
1850
0
    else
1851
0
    {
1852
0
        SetViewMode(rPosition.mePageKind);
1853
0
        SetPage(rPosition.meEditMode, static_cast<sal_uInt16>(rPosition.mnPageIndex));
1854
0
    }
1855
1856
0
    mnText = rPosition.mnText;
1857
0
    return rPosition.mxObject.get().get();
1858
0
}
1859
1860
void SdOutliner::SetViewShell (const std::shared_ptr<sd::ViewShell>& rpViewShell)
1861
0
{
1862
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1863
0
    if (pViewShell == rpViewShell)
1864
0
        return;
1865
1866
    // Set the new view shell.
1867
0
    mpWeakViewShell = rpViewShell;
1868
    // When the outline view is not owned by us then we have to clear
1869
    // that pointer so that the current one for the new view shell will
1870
    // be used (in ProvideOutlinerView).
1871
0
    if (rpViewShell)
1872
0
    {
1873
0
        mpView = rpViewShell->GetView();
1874
1875
0
        mpWindow = rpViewShell->GetActiveWindow();
1876
1877
0
        mpImpl->ProvideOutlinerView(*this, rpViewShell, mpWindow);
1878
0
        OutlinerView* pOutlinerView = getOutlinerView();
1879
0
        if (pOutlinerView != nullptr)
1880
0
            pOutlinerView->SetWindow(mpWindow);
1881
0
    }
1882
0
    else
1883
0
    {
1884
0
        mpView = nullptr;
1885
0
        mpWindow = nullptr;
1886
0
    }
1887
0
}
1888
1889
void SdOutliner::HandleChangedSelection()
1890
0
{
1891
0
    maMarkListCopy.clear();
1892
0
    const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();
1893
0
    mbRestrictSearchToSelection = rMarkList.GetMarkCount() != 0;
1894
0
    if (!mbRestrictSearchToSelection)
1895
0
        return;
1896
1897
    // Make a copy of the current mark list.
1898
0
    const size_t nCount = rMarkList.GetMarkCount();
1899
0
    if (nCount > 0)
1900
0
    {
1901
0
        maMarkListCopy.clear();
1902
0
        maMarkListCopy.reserve (nCount);
1903
0
        for (size_t i=0; i<nCount; ++i)
1904
0
            maMarkListCopy.emplace_back(rMarkList.GetMark(i)->GetMarkedSdrObj ());
1905
0
    }
1906
0
    else
1907
        // No marked object.  Is this case possible?
1908
0
        mbRestrictSearchToSelection = false;
1909
0
}
1910
1911
void SdOutliner::StartConversion( LanguageType nSourceLanguage,  LanguageType nTargetLanguage,
1912
        const vcl::Font *pTargetFont, sal_Int32 nOptions, bool bIsInteractive )
1913
0
{
1914
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1915
0
    bool bMultiDoc = nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() );
1916
1917
0
    meMode = TEXT_CONVERSION;
1918
0
    mbDirectionIsForward = true;
1919
0
    mpSearchItem.reset();
1920
0
    mnConversionLanguage = nSourceLanguage;
1921
1922
0
    BeginConversion();
1923
1924
0
    OutlinerView* pOutlinerView = getOutlinerView();
1925
0
    if (pOutlinerView != nullptr)
1926
0
    {
1927
0
        pOutlinerView->StartTextConversion(
1928
0
            GetMessageBoxParent(),
1929
0
            nSourceLanguage,
1930
0
            nTargetLanguage,
1931
0
            pTargetFont,
1932
0
            nOptions,
1933
0
            bIsInteractive,
1934
0
            bMultiDoc);
1935
0
    }
1936
1937
0
    EndConversion();
1938
0
}
1939
1940
/** Prepare to do a text conversion on the current text object. This
1941
    includes putting it into edit mode.
1942
*/
1943
void SdOutliner::PrepareConversion()
1944
0
{
1945
0
    SetUpdateLayout(true);
1946
0
    if( HasConvertibleTextPortion( mnConversionLanguage ) )
1947
0
    {
1948
0
        SetUpdateLayout(false);
1949
0
        mbStringFound = true;
1950
0
        mbMatchMayExist = true;
1951
1952
0
        EnterEditMode(true);
1953
1954
0
        mrDrawDocument.GetDocSh()->SetWaitCursor( false );
1955
        // Start search at the right end of the current object's text
1956
        // depending on the search direction.
1957
0
    }
1958
0
    else
1959
0
    {
1960
0
        SetUpdateLayout(false);
1961
0
    }
1962
0
}
1963
1964
void SdOutliner::BeginConversion()
1965
0
{
1966
0
    SetRefDevice(SdModule::get()->GetVirtualRefDevice());
1967
1968
0
    sd::ViewShellBase* pBase = getViewShellBase();
1969
0
    if (pBase != nullptr)
1970
0
        SetViewShell (pBase->GetMainViewShell());
1971
1972
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1973
0
    if (pViewShell)
1974
0
    {
1975
0
        mbStringFound = false;
1976
1977
        // Supposed that we are not located at the very beginning/end of the
1978
        // document then there may be a match in the document prior/after
1979
        // the current position.
1980
0
        mbMatchMayExist = true;
1981
1982
0
        maObjectIterator = sd::outliner::Iterator();
1983
0
        maSearchStartPosition = sd::outliner::Iterator();
1984
0
        RememberStartPosition();
1985
1986
0
        mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
1987
1988
0
        HandleChangedSelection ();
1989
0
    }
1990
0
    ClearModifyFlag();
1991
0
}
1992
1993
void SdOutliner::EndConversion()
1994
0
{
1995
0
    EndSpelling();
1996
0
}
1997
1998
bool SdOutliner::ConvertNextDocument()
1999
0
{
2000
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
2001
0
    if (dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ) )
2002
0
        return false;
2003
2004
0
    mrDrawDocument.GetDocSh()->SetWaitCursor( true );
2005
2006
0
    Initialize ( true );
2007
2008
0
    OutlinerView* pOutlinerView = getOutlinerView();
2009
0
    if (pOutlinerView != nullptr)
2010
0
    {
2011
0
        mpWindow = pViewShell->GetActiveWindow();
2012
0
        pOutlinerView->SetWindow(mpWindow);
2013
0
    }
2014
0
    ProvideNextTextObject ();
2015
2016
0
    mrDrawDocument.GetDocSh()->SetWaitCursor( false );
2017
0
    ClearModifyFlag();
2018
2019
    // for text conversion we automatically wrap around one
2020
    // time and stop at the start shape
2021
0
    if( mpFirstObj )
2022
0
    {
2023
0
        if( (mnText == 0) && (mpFirstObj == mpObj) )
2024
0
            return false;
2025
0
    }
2026
0
    else
2027
0
    {
2028
0
        mpFirstObj = mpObj;
2029
0
    }
2030
2031
0
    return !mbEndOfSearch;
2032
0
}
2033
2034
weld::Window* SdOutliner::GetMessageBoxParent()
2035
0
{
2036
    // We assume that the parent of the given message box is NULL, i.e. it is
2037
    // modal with respect to the top application window. However, this
2038
    // does not affect the search dialog. Therefore we have to lock it here
2039
    // while the message box is being shown. We also have to take into
2040
    // account that we are called during a spell check and the search dialog
2041
    // is not available.
2042
0
    weld::Window* pSearchDialog = nullptr;
2043
0
    SfxChildWindow* pChildWindow = nullptr;
2044
0
    switch (meMode)
2045
0
    {
2046
0
        case SEARCH:
2047
0
            if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
2048
0
                pChildWindow = pViewFrm->GetChildWindow(
2049
0
                    SvxSearchDialogWrapper::GetChildWindowId());
2050
0
            break;
2051
2052
0
        case SPELL:
2053
0
            if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
2054
0
                pChildWindow = pViewFrm->GetChildWindow(
2055
0
                    sd::SpellDialogChildWindow::GetChildWindowId());
2056
0
            break;
2057
2058
0
        case TEXT_CONVERSION:
2059
            // There should no messages boxes be displayed while doing the
2060
            // hangul hanja conversion.
2061
0
            break;
2062
0
    }
2063
2064
0
    if (pChildWindow != nullptr)
2065
0
    {
2066
0
        auto xController = pChildWindow->GetController();
2067
0
        pSearchDialog = xController ? xController->getDialog() : nullptr;
2068
0
    }
2069
2070
0
    if (pSearchDialog)
2071
0
        return pSearchDialog;
2072
2073
0
    std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
2074
0
    auto pWin = pViewShell->GetActiveWindow();
2075
0
    return pWin ? pWin->GetFrameWeld() : nullptr;
2076
0
}
2077
2078
//===== SdOutliner::Implementation ==============================================
2079
2080
SdOutliner::Implementation::Implementation()
2081
43.0k
    : meOriginalEditMode(EditMode::Page),
2082
43.0k
      mbOwnOutlineView(false),
2083
43.0k
      mpOutlineView(nullptr)
2084
43.0k
{
2085
43.0k
}
2086
2087
SdOutliner::Implementation::~Implementation()
2088
43.0k
{
2089
43.0k
    if (mbOwnOutlineView && mpOutlineView!=nullptr)
2090
0
    {
2091
0
        mpOutlineView->SetWindow(nullptr);
2092
0
        delete mpOutlineView;
2093
0
        mpOutlineView = nullptr;
2094
0
    }
2095
43.0k
}
2096
2097
/** We try to create a new OutlinerView only when there is none available,
2098
    either from an OutlinerViewShell or a previous call to
2099
    ProvideOutlinerView().  This is necessary to support the spell checker
2100
    which can not cope with exchanging the OutlinerView.
2101
*/
2102
void SdOutliner::Implementation::ProvideOutlinerView (
2103
    Outliner& rOutliner,
2104
    const std::shared_ptr<sd::ViewShell>& rpViewShell,
2105
    vcl::Window* pWindow)
2106
0
{
2107
0
    if (rpViewShell == nullptr)
2108
0
        return;
2109
2110
0
    switch (rpViewShell->GetShellType())
2111
0
    {
2112
0
        case sd::ViewShell::ST_DRAW:
2113
0
        case sd::ViewShell::ST_IMPRESS:
2114
0
        case sd::ViewShell::ST_NOTES:
2115
0
        case sd::ViewShell::ST_HANDOUT:
2116
0
        {
2117
            // Create a new outline view to do the search on.
2118
0
            bool bInsert = false;
2119
0
            if (mpOutlineView != nullptr && !mbOwnOutlineView)
2120
0
                mpOutlineView = nullptr;
2121
2122
0
            if (mpOutlineView == nullptr || !rOutliner.GetEditEngine().HasView(&mpOutlineView->GetEditView()))
2123
0
            {
2124
0
                delete mpOutlineView;
2125
0
                mpOutlineView = new OutlinerView(rOutliner, pWindow);
2126
0
                mbOwnOutlineView = true;
2127
0
                bInsert = true;
2128
0
            }
2129
0
            else
2130
0
                mpOutlineView->SetWindow(pWindow);
2131
2132
0
            EVControlBits nStat = mpOutlineView->GetControlWord();
2133
0
            nStat &= ~EVControlBits::AUTOSCROLL;
2134
0
            mpOutlineView->SetControlWord(nStat);
2135
2136
0
            if (bInsert)
2137
0
                rOutliner.InsertView( mpOutlineView );
2138
2139
0
            rOutliner.SetUpdateLayout(false);
2140
0
            mpOutlineView->SetOutputArea (::tools::Rectangle (Point(), Size(1, 1)));
2141
0
            rOutliner.SetPaperSize( Size(1, 1) );
2142
0
            rOutliner.SetText(OUString(), rOutliner.GetParagraph(0));
2143
2144
0
            meOriginalEditMode =
2145
0
                std::static_pointer_cast<sd::DrawViewShell>(rpViewShell)->GetEditMode();
2146
0
        }
2147
0
        break;
2148
2149
0
        case sd::ViewShell::ST_OUTLINE:
2150
0
        {
2151
0
            if (mpOutlineView!=nullptr && mbOwnOutlineView)
2152
0
                delete mpOutlineView;
2153
0
            mpOutlineView = rOutliner.GetView(0);
2154
0
            mbOwnOutlineView = false;
2155
0
        }
2156
0
        break;
2157
2158
0
        default:
2159
0
        case sd::ViewShell::ST_NONE:
2160
0
        case sd::ViewShell::ST_PRESENTATION:
2161
            // Ignored
2162
0
            break;
2163
0
    }
2164
0
}
2165
2166
void SdOutliner::Implementation::ReleaseOutlinerView()
2167
0
{
2168
0
    if (mbOwnOutlineView)
2169
0
    {
2170
0
        OutlinerView* pView = mpOutlineView;
2171
0
        mpOutlineView = nullptr;
2172
0
        mbOwnOutlineView = false;
2173
0
        if (pView != nullptr)
2174
0
        {
2175
0
            pView->SetWindow(nullptr);
2176
0
            delete pView;
2177
0
        }
2178
0
    }
2179
0
    else
2180
0
    {
2181
0
        mpOutlineView = nullptr;
2182
0
    }
2183
0
}
2184
2185
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */