Coverage Report

Created: 2025-11-16 09:57

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