Coverage Report

Created: 2026-07-10 11:04

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