android: Update app-specific/MIME type icons
[LibreOffice.git] / sd / source / ui / view / Outliner.cxx
blobaaf4cc6a8160b17a3f0412c5a3244c1244b76eaf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <Outliner.hxx>
21 #include <boost/property_tree/json_parser.hpp>
22 #include <vcl/settings.hxx>
23 #include <vcl/svapp.hxx>
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>
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 <drawdoc.hxx>
53 #include <DrawDocShell.hxx>
54 #include <drawview.hxx>
55 #include <ViewShellBase.hxx>
56 #include <SpellDialogChildWindow.hxx>
57 #include <framework/FrameworkHelper.hxx>
58 #include <svx/svxids.hrc>
59 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
60 #include <comphelper/string.hxx>
61 #include <comphelper/lok.hxx>
62 #include <comphelper/scopeguard.hxx>
63 #include <VectorGraphicSearchContext.hxx>
64 #include <fusearch.hxx>
66 using namespace ::com::sun::star;
67 using namespace ::com::sun::star::uno;
68 using namespace ::com::sun::star::lang;
69 using namespace ::com::sun::star::linguistic2;
71 class SfxStyleSheetPool;
73 class SdOutliner::Implementation
75 public:
76 /** The original edit mode directly after switching to a different view
77 mode. Used for restoring the edit mode when leaving that view mode
78 again.
80 EditMode meOriginalEditMode;
82 Implementation();
83 ~Implementation();
85 /** Return the OutlinerView that was provided by the last call to
86 ProvideOutlinerView() (or NULL when there was no such call.)
88 OutlinerView* GetOutlinerView() { return mpOutlineView;}
90 /** Provide in the member mpOutlineView an instance of OutlinerView that
91 is either taken from the ViewShell, when it is an OutlineViewShell,
92 or is created. When an OutlinerView already exists it is initialized.
94 void ProvideOutlinerView (
95 Outliner& rOutliner,
96 const std::shared_ptr<sd::ViewShell>& rpViewShell,
97 vcl::Window* pWindow);
99 /** This method is called when the OutlinerView is no longer used.
101 void ReleaseOutlinerView();
103 sd::VectorGraphicSearchContext& getVectorGraphicSearchContext() { return maVectorGraphicSearchContext; }
105 private:
106 /** Flag that specifies whether we own the outline view pointed to by
107 <member>mpOutlineView</member> and thus have to
108 delete it in <member>EndSpelling()</member>.
110 bool mbOwnOutlineView;
112 /** The outline view used for searching and spelling. If searching or
113 spell checking an outline view this data member points to that view.
114 For all other views an instance is created. The
115 <member>mbOwnOutlineView</member> distinguishes between both cases.
117 OutlinerView* mpOutlineView;
119 sd::VectorGraphicSearchContext maVectorGraphicSearchContext;
122 namespace
125 sd::ViewShellBase* getViewShellBase()
127 return dynamic_cast<sd::ViewShellBase*>(SfxViewShell::Current());
130 } // end anonymous namespace
132 SdOutliner::SdOutliner( SdDrawDocument* pDoc, OutlinerMode nMode )
133 : SdrOutliner( &pDoc->GetItemPool(), nMode ),
134 mpImpl(new Implementation()),
135 meMode(SEARCH),
136 mpView(nullptr),
137 mpWindow(nullptr),
138 mpDrawDocument(pDoc),
139 mnConversionLanguage(LANGUAGE_NONE),
140 mnIgnoreCurrentPageChangesLevel(0),
141 mbStringFound(false),
142 mbMatchMayExist(false),
143 mnPageCount(0),
144 mbEndOfSearch(false),
145 mbFoundObject(false),
146 mbDirectionIsForward(true),
147 mbRestrictSearchToSelection(false),
148 mpObj(nullptr),
149 mpFirstObj(nullptr),
150 mpSearchSpellTextObj(nullptr),
151 mnText(0),
152 mpParaObj(nullptr),
153 meStartViewMode(PageKind::Standard),
154 meStartEditMode(EditMode::Page),
155 mnStartPageIndex(sal_uInt16(-1)),
156 mpStartEditedObject(nullptr),
157 mbPrepareSpellingPending(true)
159 SetStyleSheetPool(static_cast<SfxStyleSheetPool*>( mpDrawDocument->GetStyleSheetPool() ));
160 SetEditTextObjectPool( &pDoc->GetItemPool() );
161 SetCalcFieldValueHdl(LINK(SD_MOD(), SdModule, CalcFieldValueHdl));
162 SetForbiddenCharsTable( pDoc->GetForbiddenCharsTable() );
164 EEControlBits nCntrl = GetControlWord();
165 nCntrl |= EEControlBits::ALLOWBIGOBJS;
166 nCntrl |= EEControlBits::MARKFIELDS;
167 nCntrl |= EEControlBits::AUTOCORRECT;
169 bool bOnlineSpell = false;
171 sd::DrawDocShell* pDocSh = mpDrawDocument->GetDocSh();
173 if (pDocSh)
175 bOnlineSpell = mpDrawDocument->GetOnlineSpell();
177 else
179 bOnlineSpell = false;
183 const SvtLinguConfig aLinguConfig;
184 Any aAny = aLinguConfig.GetProperty( UPN_IS_SPELL_AUTO );
185 aAny >>= bOnlineSpell;
187 catch( ... )
189 OSL_FAIL( "Ill. type in linguistic property" );
193 if (bOnlineSpell)
194 nCntrl |= EEControlBits::ONLINESPELLING;
195 else
196 nCntrl &= ~EEControlBits::ONLINESPELLING;
198 SetControlWord(nCntrl);
200 Reference< XSpellChecker1 > xSpellChecker( LinguMgr::GetSpellChecker() );
201 if ( xSpellChecker.is() )
202 SetSpeller( xSpellChecker );
204 Reference< XHyphenator > xHyphenator( LinguMgr::GetHyphenator() );
205 if( xHyphenator.is() )
206 SetHyphenator( xHyphenator );
208 SetDefaultLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
211 /// Nothing spectacular in the destructor.
212 SdOutliner::~SdOutliner()
216 OutlinerView* SdOutliner::getOutlinerView()
218 return mpImpl->GetOutlinerView();
221 /** Prepare find&replace or spellchecking. This distinguishes between three
222 cases:
223 <ol>
224 <li>The current shell is a <type>DrawViewShell</type>: Create a
225 <type>OutlinerView</type> object and search all objects of (i) the
226 current mark list, (ii) of the current view, or (iii) of all the view
227 combinations:
228 <ol>
229 <li>Draw view, slide view</li>
230 <li>Draw view, background view</li>
231 <li>Notes view, slide view</li>
232 <li>Notes view, background view</li>
233 <li>Handout view, slide view</li>
234 <li>Handout view, background view</li>
235 </ol>
237 <li>When the current shell is a <type>SdOutlineViewShell</type> then
238 directly operate on it. No switching into other views takes place.</li>
239 </ol>
241 void SdOutliner::PrepareSpelling()
243 mbPrepareSpellingPending = false;
245 sd::ViewShellBase* pBase = getViewShellBase();
246 if (pBase != nullptr)
247 SetViewShell (pBase->GetMainViewShell());
248 SetRefDevice( SD_MOD()->GetVirtualRefDevice() );
250 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
251 if (pViewShell)
253 mbStringFound = false;
255 // Supposed that we are not located at the very beginning/end of
256 // the document then there may be a match in the document
257 // prior/after the current position.
258 mbMatchMayExist = true;
260 maObjectIterator = sd::outliner::Iterator();
261 maSearchStartPosition = sd::outliner::Iterator();
262 RememberStartPosition();
264 mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
266 HandleChangedSelection ();
268 ClearModifyFlag();
271 void SdOutliner::StartSpelling()
273 meMode = SPELL;
274 mbDirectionIsForward = true;
275 mpSearchItem.reset();
278 /** Free all resources acquired during the search/spell check. After a
279 spell check the start position is restored here.
281 void SdOutliner::EndSpelling()
283 // Keep old view shell alive until we release the outliner view.
284 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
285 std::shared_ptr<sd::ViewShell> pOldViewShell (pViewShell);
287 sd::ViewShellBase* pBase = getViewShellBase();
288 if (pBase != nullptr)
289 pViewShell = pBase->GetMainViewShell();
290 else
291 pViewShell.reset();
292 mpWeakViewShell = pViewShell;
294 // When in <member>PrepareSpelling()</member> a new outline view has
295 // been created then delete it here.
296 bool bViewIsDrawViewShell(dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ));
297 if (bViewIsDrawViewShell)
299 SetStatusEventHdl(Link<EditStatus&,void>());
300 mpView = pViewShell->GetView();
301 mpView->UnmarkAllObj (mpView->GetSdrPageView());
302 mpView->SdrEndTextEdit();
303 // Make FuSelection the current function.
304 pViewShell->GetDispatcher()->Execute(
305 SID_OBJECT_SELECT,
306 SfxCallMode::SYNCHRON | SfxCallMode::RECORD);
308 // Remove and, if previously created by us, delete the outline
309 // view.
310 OutlinerView* pOutlinerView = getOutlinerView();
311 if (pOutlinerView != nullptr)
313 RemoveView(pOutlinerView);
314 mpImpl->ReleaseOutlinerView();
317 SetUpdateLayout(true);
320 // Before clearing the modify flag use it as a hint that
321 // changes were done at SpellCheck
322 if(IsModified())
324 if(auto pOutlineView = dynamic_cast<sd::OutlineView *>( mpView ))
325 pOutlineView->PrepareClose();
326 if(mpDrawDocument && !mpDrawDocument->IsChanged())
327 mpDrawDocument->SetChanged();
330 // Now clear the modify flag to have a specified state of
331 // Outliner
332 ClearModifyFlag();
334 // When spell checking then restore the start position.
335 if (meMode==SPELL || meMode==TEXT_CONVERSION)
336 RestoreStartPosition ();
338 mpWeakViewShell.reset();
339 mpView = nullptr;
340 mpWindow = nullptr;
341 mnStartPageIndex = sal_uInt16(-1);
344 bool SdOutliner::SpellNextDocument()
346 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
347 if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
349 // When doing a spell check in the outline view then there is
350 // only one document.
351 mbEndOfSearch = true;
352 EndOfSearch ();
354 else
356 if( auto pOutlineView = dynamic_cast<sd::OutlineView *>( mpView ))
357 pOutlineView->PrepareClose();
358 mpDrawDocument->GetDocSh()->SetWaitCursor( true );
360 Initialize (true);
362 mpWindow = pViewShell->GetActiveWindow();
363 OutlinerView* pOutlinerView = getOutlinerView();
364 if (pOutlinerView != nullptr)
365 pOutlinerView->SetWindow(mpWindow);
366 ProvideNextTextObject ();
368 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
369 ClearModifyFlag();
372 return !mbEndOfSearch;
376 * check next text object
378 svx::SpellPortions SdOutliner::GetNextSpellSentence()
380 svx::SpellPortions aResult;
382 DetectChange();
383 // Iterate over sentences and text shapes until a sentence with a
384 // spelling error has been found. If no such sentence can be
385 // found the loop is left through a break.
386 // It is the responsibility of the sd outliner object to correctly
387 // iterate over all text shapes, i.e. switch between views, wrap
388 // around at the end of the document, stop when all text shapes
389 // have been examined exactly once.
390 bool bFoundNextSentence = false;
391 while ( ! bFoundNextSentence)
393 OutlinerView* pOutlinerView = GetView(0);
394 if (pOutlinerView != nullptr)
396 ESelection aCurrentSelection (pOutlinerView->GetSelection());
397 if ( ! mbMatchMayExist
398 && maStartSelection < aCurrentSelection)
399 EndOfSearch();
401 // Advance to the next sentence.
402 bFoundNextSentence = SpellSentence( pOutlinerView->GetEditView(), aResult);
405 // When no sentence with spelling errors has been found in the
406 // currently selected text shape or there is no selected text
407 // shape then advance to the next text shape.
408 if ( ! bFoundNextSentence)
409 if ( ! SpellNextDocument())
410 // All text objects have been processed so exit the
411 // loop and return an empty portions list.
412 break;
415 return aResult;
418 /** Go to next match.
420 bool SdOutliner::StartSearchAndReplace (const SvxSearchItem* pSearchItem)
422 bool bEndOfSearch = true;
424 // clear the search toolbar entry
425 SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Empty);
427 mpDrawDocument->GetDocSh()->SetWaitCursor( true );
429 // Since REPLACE is really a replaceAndSearchNext instead of a searchAndReplace,
430 // make sure that the search portion has not changed since the last FIND.
431 if (!mbPrepareSpellingPending && mpSearchItem
432 && pSearchItem->GetCommand() == SvxSearchCmd::REPLACE
433 && !mpSearchItem->equalsIgnoring(*pSearchItem, /*bIgnoreReplace=*/true,
434 /*bIgnoreCommand=*/true))
436 EndSpelling();
437 mbPrepareSpellingPending = true;
440 if (mbPrepareSpellingPending)
441 PrepareSpelling();
442 sd::ViewShellBase* pBase = getViewShellBase();
443 // Determine whether we have to abort the search. This is necessary
444 // when the main view shell does not support searching.
445 bool bAbort = false;
446 if (pBase != nullptr)
448 std::shared_ptr<sd::ViewShell> pShell (pBase->GetMainViewShell());
449 SetViewShell(pShell);
450 if (pShell == nullptr)
451 bAbort = true;
452 else
453 switch (pShell->GetShellType())
455 case sd::ViewShell::ST_DRAW:
456 case sd::ViewShell::ST_IMPRESS:
457 case sd::ViewShell::ST_NOTES:
458 case sd::ViewShell::ST_HANDOUT:
459 case sd::ViewShell::ST_OUTLINE:
460 bAbort = false;
461 break;
462 default:
463 bAbort = true;
464 break;
468 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
469 if ( ! pViewShell)
471 OSL_ASSERT(pViewShell);
472 return true;
475 if ( ! bAbort)
477 meMode = SEARCH;
478 mpSearchItem.reset(pSearchItem->Clone());
480 mbFoundObject = false;
482 Initialize ( ! mpSearchItem->GetBackward());
484 const SvxSearchCmd nCommand (mpSearchItem->GetCommand());
485 if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
487 bEndOfSearch = SearchAndReplaceAll ();
489 else
491 RememberStartPosition ();
492 bEndOfSearch = SearchAndReplaceOnce ();
493 // restore start position if nothing was found
494 if(!mbStringFound)
496 RestoreStartPosition ();
497 // Nothing was changed, no need to restart the spellchecker.
498 if (nCommand == SvxSearchCmd::FIND)
499 bEndOfSearch = false;
501 mnStartPageIndex = sal_uInt16(-1);
505 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
507 return bEndOfSearch;
510 void SdOutliner::Initialize (bool bDirectionIsForward)
512 const bool bIsAtEnd (maObjectIterator == sd::outliner::OutlinerContainer(this).end());
513 const bool bOldDirectionIsForward = mbDirectionIsForward;
514 mbDirectionIsForward = bDirectionIsForward;
516 if (maObjectIterator == sd::outliner::Iterator())
518 // Initialize a new search.
519 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
520 maCurrentPosition = *maObjectIterator;
522 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
523 if ( ! pViewShell)
525 OSL_ASSERT(pViewShell);
526 return;
529 // In case we are searching in an outline view then first remove the
530 // current selection and place cursor at its start or end.
531 if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
533 ESelection aSelection = getOutlinerView()->GetSelection ();
534 if (mbDirectionIsForward)
536 aSelection.nEndPara = aSelection.nStartPara;
537 aSelection.nEndPos = aSelection.nStartPos;
539 else
541 aSelection.nStartPara = aSelection.nEndPara;
542 aSelection.nStartPos = aSelection.nEndPos;
544 getOutlinerView()->SetSelection (aSelection);
547 // When not beginning the search at the beginning of the search area
548 // then there may be matches before the current position.
549 mbMatchMayExist = (maObjectIterator!=sd::outliner::OutlinerContainer(this).begin());
551 else if (bOldDirectionIsForward != mbDirectionIsForward)
553 // Requested iteration direction has changed. Turn around the iterator.
554 maObjectIterator.Reverse();
555 if (bIsAtEnd)
557 // The iterator has pointed to end(), which after the search
558 // direction is reversed, becomes begin().
559 maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
561 else
563 // The iterator has pointed to the object one ahead/before the current
564 // one. Now move it to the one before/ahead the current one.
565 ++maObjectIterator;
566 if (maObjectIterator != sd::outliner::OutlinerContainer(this).end())
568 ++maObjectIterator;
572 mbMatchMayExist = true;
575 // Initialize the last valid position with where the search starts so
576 // that it always points to a valid position.
577 maLastValidPosition = *sd::outliner::OutlinerContainer(this).current();
580 bool SdOutliner::SearchAndReplaceAll()
582 bool bRet = true;
584 // Save the current position to be restored after having replaced all
585 // matches.
586 RememberStartPosition ();
588 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
589 if ( ! pViewShell)
591 OSL_ASSERT(pViewShell);
592 return true;
595 std::vector<sd::SearchSelection> aSelections;
596 if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
598 // Put the cursor to the beginning/end of the outliner.
599 getOutlinerView()->SetSelection (GetSearchStartPosition ());
601 // The outliner does all the work for us when we are in this mode.
602 SearchAndReplaceOnce();
604 else if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
606 // Disable selection change notifications during search all.
607 SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
608 rSfxViewShell.setTiledSearching(true);
609 comphelper::ScopeGuard aGuard([&rSfxViewShell]()
611 rSfxViewShell.setTiledSearching(false);
614 // Go to beginning/end of document.
615 maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
616 // Switch to the first object which contains the search string.
617 ProvideNextTextObject();
618 if( !mbStringFound )
620 RestoreStartPosition ();
621 mnStartPageIndex = sal_uInt16(-1);
622 return true;
624 // Reset the iterator back to the beginning
625 maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
627 // Search/replace until the end of the document is reached.
628 bool bFoundMatch;
631 bFoundMatch = ! SearchAndReplaceOnce(&aSelections);
632 if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && bFoundMatch && aSelections.size() == 1)
634 // Without this, RememberStartPosition() will think it already has a remembered position.
635 mnStartPageIndex = sal_uInt16(-1);
637 RememberStartPosition();
639 // So when RestoreStartPosition() restores the first match, then spellchecker doesn't kill the selection.
640 bRet = false;
643 while (bFoundMatch);
645 if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && !aSelections.empty())
647 boost::property_tree::ptree aTree;
648 aTree.put("searchString", mpSearchItem->GetSearchString().toUtf8().getStr());
649 aTree.put("highlightAll", true);
651 boost::property_tree::ptree aChildren;
652 for (const sd::SearchSelection& rSelection : aSelections)
654 boost::property_tree::ptree aChild;
655 aChild.put("part", OString::number(rSelection.m_nPage).getStr());
656 aChild.put("rectangles", rSelection.m_aRectangles.getStr());
657 aChildren.push_back(std::make_pair("", aChild));
659 aTree.add_child("searchResultSelection", aChildren);
661 std::stringstream aStream;
662 boost::property_tree::write_json(aStream, aTree);
663 OString aPayload( aStream.str() );
664 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload);
668 RestoreStartPosition ();
670 if (mpSearchItem->GetCommand() == SvxSearchCmd::FIND_ALL && comphelper::LibreOfficeKit::isActive() && !bRet)
672 // Find-all, tiled rendering and we have at least one match.
673 OString aPayload = OString::number(mnStartPageIndex);
674 SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
675 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
677 // Emit a selection callback here:
678 // 1) The original one is no longer valid, as we there was a SET_PART in between
679 // 2) The underlying editeng will only talk about the first match till
680 // it doesn't support multi-selection.
681 std::vector<OString> aRectangles;
682 for (const sd::SearchSelection& rSelection : aSelections)
684 if (rSelection.m_nPage == mnStartPageIndex)
685 aRectangles.push_back(rSelection.m_aRectangles);
687 OString sRectangles = comphelper::string::join("; ", aRectangles);
688 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangles);
691 mnStartPageIndex = sal_uInt16(-1);
693 return bRet;
696 namespace
699 basegfx::B2DRectangle getPDFSelection(const std::unique_ptr<VectorGraphicSearch> & rVectorGraphicSearch,
700 const SdrObject* pObject)
702 basegfx::B2DRectangle aSelection;
704 auto const & rTextRectangles = rVectorGraphicSearch->getTextRectangles();
705 if (rTextRectangles.empty())
706 return aSelection;
708 basegfx::B2DSize aPdfPageSizeHMM = rVectorGraphicSearch->pageSize();
710 basegfx::B2DRectangle aObjectB2DRectHMM(vcl::unotools::b2DRectangleFromRectangle(pObject->GetLogicRect()));
712 // Setup coordinate conversion matrix to convert the inner PDF
713 // coordinates to the page relative coordinates
714 basegfx::B2DHomMatrix aB2DMatrix;
716 aB2DMatrix.scale(aObjectB2DRectHMM.getWidth() / aPdfPageSizeHMM.getWidth(),
717 aObjectB2DRectHMM.getHeight() / aPdfPageSizeHMM.getHeight());
719 aB2DMatrix.translate(aObjectB2DRectHMM.getMinX(), aObjectB2DRectHMM.getMinY());
722 for (auto const & rRectangle : rVectorGraphicSearch->getTextRectangles())
724 basegfx::B2DRectangle aRectangle(rRectangle);
725 aRectangle *= aB2DMatrix;
727 if (aSelection.isEmpty())
728 aSelection = aRectangle;
729 else
730 aSelection.expand(aRectangle);
733 return aSelection;
736 } // end namespace
738 void SdOutliner::sendLOKSearchResultCallback(const std::shared_ptr<sd::ViewShell> & pViewShell,
739 const OutlinerView* pOutlinerView,
740 std::vector<sd::SearchSelection>* pSelections)
742 std::vector<::tools::Rectangle> aLogicRects;
743 auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
744 if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
746 basegfx::B2DRectangle aSelectionHMM = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
748 tools::Rectangle aSelection(Point(aSelectionHMM.getMinX(), aSelectionHMM.getMinY()),
749 Size(aSelectionHMM.getWidth(), aSelectionHMM.getHeight()));
750 aSelection = o3tl::convert(aSelection, o3tl::Length::mm100, o3tl::Length::twip);
751 aLogicRects.push_back(aSelection);
753 else
755 pOutlinerView->GetSelectionRectangles(aLogicRects);
757 // convert to twips if in 100thmm (seems as if LibreOfficeKit is based on twips?). Do this
758 // here where we have the only place needing this, *not* in ImpEditView::GetSelectionRectangles
759 // which makes that method unusable for others
760 if (pOutlinerView->GetWindow() && MapUnit::Map100thMM == pOutlinerView->GetWindow()->GetMapMode().GetMapUnit())
762 for (tools::Rectangle& rRectangle : aLogicRects)
764 rRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
769 std::vector<OString> aLogicRectStrings;
770 std::transform(aLogicRects.begin(), aLogicRects.end(), std::back_inserter(aLogicRectStrings),
771 [](const ::tools::Rectangle& rRectangle)
773 return rRectangle.toString();
776 OString sRectangles = comphelper::string::join("; ", aLogicRectStrings);
778 if (!pSelections)
780 // notify LibreOfficeKit about changed page
781 OString aPayload = OString::number(maCurrentPosition.mnPageIndex);
782 SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
783 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload);
785 // also about search result selections
786 boost::property_tree::ptree aTree;
787 aTree.put("searchString", mpSearchItem->GetSearchString().toUtf8().getStr());
788 aTree.put("highlightAll", false);
790 boost::property_tree::ptree aChildren;
791 boost::property_tree::ptree aChild;
792 aChild.put("part", OString::number(maCurrentPosition.mnPageIndex).getStr());
793 aChild.put("rectangles", sRectangles.getStr());
794 aChildren.push_back(std::make_pair("", aChild));
795 aTree.add_child("searchResultSelection", aChildren);
797 std::stringstream aStream;
798 boost::property_tree::write_json(aStream, aTree);
799 aPayload = OString(aStream.str());
800 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload);
802 if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
804 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangles);
807 else
809 sd::SearchSelection aSelection(maCurrentPosition.mnPageIndex, sRectangles);
810 bool bDuplicate = !pSelections->empty() && pSelections->back() == aSelection;
811 if (!bDuplicate)
812 pSelections->push_back(aSelection);
816 bool SdOutliner::SearchAndReplaceOnce(std::vector<sd::SearchSelection>* pSelections)
818 DetectChange ();
820 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
822 if (!getOutlinerView() || !GetEditEngine().HasView(&getOutlinerView()->GetEditView()))
824 std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
825 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
827 // Perhaps the user switched to a different page/slide between searches.
828 // If so, reset the starting search position to the current slide like DetectChange does
829 if (pDrawViewShell && pDrawViewShell->GetCurPagePos() != maCurrentPosition.mnPageIndex)
830 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
832 mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
835 if (pViewShell)
837 mpView = pViewShell->GetView();
838 mpWindow = pViewShell->GetActiveWindow();
839 getOutlinerView()->SetWindow(mpWindow);
840 auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
841 if (nullptr != dynamic_cast<const sd::DrawViewShell*>(pViewShell.get()))
843 sal_uLong nMatchCount = 0;
845 if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
847 OUString const & rString = mpSearchItem->GetSearchString();
848 bool bBackwards = mpSearchItem->GetBackward();
850 VectorGraphicSearchOptions aOptions;
851 aOptions.meStartPosition = bBackwards ? SearchStartPosition::End : SearchStartPosition::Begin;
852 aOptions.mbMatchCase = mpSearchItem->GetExact();
853 aOptions.mbMatchWholeWord = mpSearchItem->GetWordOnly();
855 bool bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->search(rString, aOptions);
857 if (bResult)
859 if (bBackwards)
860 bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->previous();
861 else
862 bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->next();
865 if (bResult)
867 nMatchCount = 1;
869 SdrPageView* pPageView = mpView->GetSdrPageView();
870 mpView->UnmarkAllObj(pPageView);
872 std::vector<basegfx::B2DRectangle> aSubSelections;
873 basegfx::B2DRectangle aSubSelection = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
874 if (!aSubSelection.isEmpty())
875 aSubSelections.push_back(aSubSelection);
876 mpView->MarkObj(mpObj, pPageView, false, false, std::move(aSubSelections));
878 else
880 rVectorGraphicSearchContext.reset();
883 else
885 // When replacing we first check if there is a selection
886 // indicating a match. If there is then replace it. The
887 // following call to StartSearchAndReplace will then search for
888 // the next match.
889 if (meMode == SEARCH && mpSearchItem->GetCommand() == SvxSearchCmd::REPLACE)
891 if (getOutlinerView()->GetSelection().HasRange())
892 getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
895 // Search for the next match.
896 if (mpSearchItem->GetCommand() != SvxSearchCmd::REPLACE_ALL)
898 nMatchCount = getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
902 // Go to the next text object when there have been no matches in
903 // the current object or the whole object has already been
904 // processed.
905 if (nMatchCount==0 || mpSearchItem->GetCommand()==SvxSearchCmd::REPLACE_ALL)
907 ProvideNextTextObject ();
909 if (!mbEndOfSearch && !rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
911 // Remember the current position as the last one with a
912 // text object.
913 maLastValidPosition = maCurrentPosition;
915 // Now that the mbEndOfSearch flag guards this block the
916 // following assertion and return should not be
917 // necessary anymore.
918 DBG_ASSERT(GetEditEngine().HasView(&getOutlinerView()->GetEditView() ),
919 "SearchAndReplace without valid view!" );
920 if ( ! GetEditEngine().HasView( &getOutlinerView()->GetEditView() ) )
922 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
923 return true;
926 if (meMode == SEARCH)
927 getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
931 else if (nullptr != dynamic_cast<const sd::OutlineViewShell*>(pViewShell.get()))
933 mpDrawDocument->GetDocSh()->SetWaitCursor(false);
934 // The following loop is executed more than once only when a
935 // wrap around search is done.
936 while (true)
938 int nResult = getOutlinerView()->StartSearchAndReplace(*mpSearchItem);
939 if (nResult == 0)
941 if (HandleFailedSearch ())
943 getOutlinerView()->SetSelection (GetSearchStartPosition ());
944 continue;
947 else
948 mbStringFound = true;
949 break;
954 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
956 if (pViewShell && comphelper::LibreOfficeKit::isActive() && mbStringFound)
958 sendLOKSearchResultCallback(pViewShell, getOutlinerView(), pSelections);
961 return mbEndOfSearch;
964 /** Try to detect whether the document or the view (shell) has changed since
965 the last time <member>StartSearchAndReplace()</member> has been called.
967 void SdOutliner::DetectChange()
969 sd::outliner::IteratorPosition aPosition (maCurrentPosition);
971 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
972 std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
973 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
975 // Detect whether the view has been switched from the outside.
976 if (pDrawViewShell != nullptr
977 && (aPosition.meEditMode != pDrawViewShell->GetEditMode()
978 || aPosition.mePageKind != pDrawViewShell->GetPageKind()))
980 // Either the edit mode or the page kind has changed.
981 SetStatusEventHdl(Link<EditStatus&,void>());
983 SdrPageView* pPageView = mpView->GetSdrPageView();
984 if (pPageView != nullptr)
985 mpView->UnmarkAllObj (pPageView);
986 mpView->SdrEndTextEdit();
987 SetUpdateLayout(false);
988 OutlinerView* pOutlinerView = getOutlinerView();
989 if (pOutlinerView != nullptr)
990 pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1) ) );
991 if (meMode == SPELL)
992 SetPaperSize( Size(1, 1) );
993 SetText(OUString(), GetParagraph(0));
995 RememberStartPosition ();
997 mnPageCount = mpDrawDocument->GetSdPageCount(pDrawViewShell->GetPageKind());
998 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1001 // Detect change of the set of selected objects. If their number has
1002 // changed start again with the first selected object.
1003 else if (DetectSelectionChange())
1005 HandleChangedSelection ();
1006 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1009 // Detect change of page count. Restart search at first/last page in
1010 // that case.
1011 else if (aPosition.meEditMode == EditMode::Page
1012 && mpDrawDocument->GetSdPageCount(aPosition.mePageKind) != mnPageCount)
1014 // The number of pages has changed.
1015 mnPageCount = mpDrawDocument->GetSdPageCount(aPosition.mePageKind);
1016 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1018 else if (aPosition.meEditMode == EditMode::MasterPage
1019 && mpDrawDocument->GetSdPageCount(aPosition.mePageKind) != mnPageCount)
1021 // The number of master pages has changed.
1022 mnPageCount = mpDrawDocument->GetSdPageCount(aPosition.mePageKind);
1023 maObjectIterator = sd::outliner::OutlinerContainer(this).current();
1027 bool SdOutliner::DetectSelectionChange()
1029 bool bSelectionHasChanged = false;
1031 // If mpObj is NULL then we have not yet found our first match.
1032 // Detecting a change makes no sense.
1033 if (mpObj != nullptr)
1035 const size_t nMarkCount = mpView ? mpView->GetMarkedObjectList().GetMarkCount() : 0;
1036 switch (nMarkCount)
1038 case 0:
1039 // The selection has changed when previously there have been
1040 // selected objects.
1041 bSelectionHasChanged = mbRestrictSearchToSelection;
1042 break;
1043 case 1:
1044 // Check if the only selected object is not the one that we
1045 // had selected.
1046 if (mpView != nullptr)
1048 SdrMark* pMark = mpView->GetMarkedObjectList().GetMark(0);
1049 if (pMark != nullptr)
1050 bSelectionHasChanged = (mpObj != pMark->GetMarkedSdrObj ());
1052 break;
1053 default:
1054 // We had selected exactly one object.
1055 bSelectionHasChanged = true;
1056 break;
1060 return bSelectionHasChanged;
1063 void SdOutliner::RememberStartPosition()
1065 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1066 if ( ! pViewShell)
1068 OSL_ASSERT(pViewShell);
1069 return;
1072 if ( mnStartPageIndex != sal_uInt16(-1) )
1073 return;
1075 if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
1077 std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
1078 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1079 if (pDrawViewShell != nullptr)
1081 meStartViewMode = pDrawViewShell->GetPageKind();
1082 meStartEditMode = pDrawViewShell->GetEditMode();
1083 mnStartPageIndex = pDrawViewShell->GetCurPagePos();
1086 if (mpView != nullptr)
1088 mpStartEditedObject = mpView->GetTextEditObject();
1089 if (mpStartEditedObject != nullptr)
1091 // Try to retrieve current caret position only when there is an
1092 // edited object.
1093 ::Outliner* pOutliner =
1094 static_cast<sd::DrawView*>(mpView)->GetTextEditOutliner();
1095 if (pOutliner!=nullptr && pOutliner->GetViewCount()>0)
1097 OutlinerView* pOutlinerView = pOutliner->GetView(0);
1098 maStartSelection = pOutlinerView->GetSelection();
1103 else if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1105 // Remember the current cursor position.
1106 OutlinerView* pView = GetView(0);
1107 if (pView != nullptr)
1108 pView->GetSelection();
1110 else
1112 mnStartPageIndex = sal_uInt16(-1);
1116 void SdOutliner::RestoreStartPosition()
1118 bool bRestore = true;
1119 // Take a negative start page index as indicator that restoring the
1120 // start position is not requested.
1121 if (mnStartPageIndex == sal_uInt16(-1) )
1122 bRestore = false;
1123 // Don't restore when the view shell is not valid.
1124 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1125 if (pViewShell == nullptr)
1126 bRestore = false;
1128 if (!bRestore)
1129 return;
1131 if( nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() ))
1133 std::shared_ptr<sd::DrawViewShell> pDrawViewShell (
1134 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1135 SetViewMode (meStartViewMode);
1136 if (pDrawViewShell != nullptr)
1138 SetPage (meStartEditMode, mnStartPageIndex);
1139 mpObj = mpStartEditedObject;
1140 if (mpObj)
1142 PutTextIntoOutliner();
1143 EnterEditMode(false);
1144 if (getOutlinerView())
1145 getOutlinerView()->SetSelection(maStartSelection);
1149 else if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1151 // Set cursor to its old position.
1152 OutlinerView* pView = GetView(0);
1153 if (pView != nullptr)
1154 pView->SetSelection (maStartSelection);
1158 namespace
1161 bool lclIsValidTextObject(const sd::outliner::IteratorPosition& rPosition)
1163 auto* pObject = DynCastSdrTextObj( rPosition.mxObject.get().get() );
1164 return (pObject != nullptr) && pObject->HasText() && ! pObject->IsEmptyPresObj();
1167 bool isValidVectorGraphicObject(const sd::outliner::IteratorPosition& rPosition)
1169 rtl::Reference<SdrGrafObj> pGraphicObject = dynamic_cast<SdrGrafObj*>(rPosition.mxObject.get().get());
1170 if (pGraphicObject)
1172 auto const& pVectorGraphicData = pGraphicObject->GetGraphic().getVectorGraphicData();
1173 if (pVectorGraphicData && VectorGraphicDataType::Pdf == pVectorGraphicData->getType())
1175 return true;
1178 return false;
1181 } // end anonymous namespace
1184 /** The main purpose of this method is to iterate over all shape objects of
1185 the search area (current selection, current view, or whole document)
1186 until a text object has been found that contains at least one match or
1187 until no such object can be found anymore. These two conditions are
1188 expressed by setting one of the flags <member>mbFoundObject</member> or
1189 <member>mbEndOfSearch</member> to <TRUE/>.
1191 void SdOutliner::ProvideNextTextObject()
1193 mbEndOfSearch = false;
1194 mbFoundObject = false;
1196 // reset the vector search
1197 auto& rVectorGraphicSearchContext = mpImpl->getVectorGraphicSearchContext();
1198 rVectorGraphicSearchContext.reset();
1200 mpView->UnmarkAllObj (mpView->GetSdrPageView());
1203 mpView->SdrEndTextEdit();
1205 catch (const css::uno::Exception&)
1207 DBG_UNHANDLED_EXCEPTION("sd.view");
1209 SetUpdateLayout(false);
1210 OutlinerView* pOutlinerView = getOutlinerView();
1211 if (pOutlinerView != nullptr)
1212 pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1) ) );
1213 if (meMode == SPELL)
1214 SetPaperSize( Size(1, 1) );
1215 SetText(OUString(), GetParagraph(0));
1217 mpSearchSpellTextObj = nullptr;
1219 // Iterate until a valid text object has been found or the search ends.
1222 mpObj = nullptr;
1223 mpParaObj = nullptr;
1225 if (maObjectIterator != sd::outliner::OutlinerContainer(this).end())
1227 maCurrentPosition = *maObjectIterator;
1229 // LOK: do not descent to notes or master pages when searching
1230 bool bForbiddenPage = comphelper::LibreOfficeKit::isActive() && (maCurrentPosition.mePageKind != PageKind::Standard || maCurrentPosition.meEditMode != EditMode::Page);
1232 rVectorGraphicSearchContext.reset();
1234 if (!bForbiddenPage)
1236 // Switch to the current object only if it is a valid text object.
1237 if (lclIsValidTextObject(maCurrentPosition))
1239 // Don't set yet in case of searching: the text object may not match.
1240 if (meMode != SEARCH)
1241 mpObj = SetObject(maCurrentPosition);
1242 else
1243 mpObj = maCurrentPosition.mxObject.get().get();
1245 // Or if the object is a valid graphic object which contains vector graphic
1246 else if (meMode == SEARCH && isValidVectorGraphicObject(maCurrentPosition))
1248 mpObj = maCurrentPosition.mxObject.get().get();
1249 rVectorGraphicSearchContext.mbCurrentIsVectorGraphic = true;
1253 // Advance to the next object
1254 ++maObjectIterator;
1256 if (mpObj)
1258 if (rVectorGraphicSearchContext.mbCurrentIsVectorGraphic)
1260 // We know here the object is a SdrGrafObj and that it
1261 // contains a vector graphic
1262 auto* pGraphicObject = static_cast<SdrGrafObj*>(mpObj);
1263 OUString const & rString = mpSearchItem->GetSearchString();
1264 bool bBackwards = mpSearchItem->GetBackward();
1266 VectorGraphicSearchOptions aOptions;
1267 aOptions.meStartPosition = bBackwards ? SearchStartPosition::End : SearchStartPosition::Begin;
1268 aOptions.mbMatchCase = mpSearchItem->GetExact();
1269 aOptions.mbMatchWholeWord = mpSearchItem->GetWordOnly();
1271 rVectorGraphicSearchContext.mpVectorGraphicSearch = std::make_unique<VectorGraphicSearch>(pGraphicObject->GetGraphic());
1273 bool bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->search(rString, aOptions);
1274 if (bResult)
1276 if (bBackwards)
1277 bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->previous();
1278 else
1279 bResult = rVectorGraphicSearchContext.mpVectorGraphicSearch->next();
1282 if (bResult)
1284 mpObj = SetObject(maCurrentPosition);
1286 mbStringFound = true;
1287 mbMatchMayExist = true;
1288 mbFoundObject = true;
1290 SdrPageView* pPageView = mpView->GetSdrPageView();
1291 mpView->UnmarkAllObj(pPageView);
1293 std::vector<basegfx::B2DRectangle> aSubSelections;
1294 basegfx::B2DRectangle aSubSelection = getPDFSelection(rVectorGraphicSearchContext.mpVectorGraphicSearch, mpObj);
1295 if (!aSubSelection.isEmpty())
1296 aSubSelections.push_back(aSubSelection);
1298 mpView->MarkObj(mpObj, pPageView, false, false, std::move(aSubSelections));
1300 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
1302 else
1304 rVectorGraphicSearchContext.reset();
1307 else
1309 PutTextIntoOutliner();
1311 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1312 if (pViewShell != nullptr)
1314 switch (meMode)
1316 case SEARCH:
1317 PrepareSearchAndReplace ();
1318 break;
1319 case SPELL:
1320 PrepareSpellCheck ();
1321 break;
1322 case TEXT_CONVERSION:
1323 PrepareConversion();
1324 break;
1330 else
1332 rVectorGraphicSearchContext.reset();
1334 if (meMode == SEARCH)
1335 // Instead of doing a full-blown SetObject(), which would do the same -- but would also possibly switch pages.
1336 mbStringFound = false;
1338 mbEndOfSearch = true;
1339 EndOfSearch ();
1342 while ( ! (mbFoundObject || mbEndOfSearch));
1345 void SdOutliner::EndOfSearch()
1347 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1348 if ( ! pViewShell)
1350 OSL_ASSERT(pViewShell);
1351 return;
1354 // Before we display a dialog we first jump to where the last valid text
1355 // object was found. All page and view mode switching since then was
1356 // temporary and should not be visible to the user.
1357 if( nullptr == dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1358 SetObject (maLastValidPosition);
1360 if (mbRestrictSearchToSelection)
1361 ShowEndOfSearchDialog ();
1362 else
1364 // When no match has been found so far then terminate the search.
1365 if ( ! mbMatchMayExist)
1367 ShowEndOfSearchDialog ();
1368 mbEndOfSearch = true;
1370 // Ask the user whether to wrap around and continue the search or
1371 // to terminate.
1372 else if (meMode==TEXT_CONVERSION || ShowWrapAroundDialog ())
1374 mbMatchMayExist = false;
1375 // Everything back to beginning (or end?) of the document.
1376 maObjectIterator = sd::outliner::OutlinerContainer(this).begin();
1377 if( nullptr != dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ))
1379 // Set cursor to first character of the document.
1380 OutlinerView* pOutlinerView = getOutlinerView();
1381 if (pOutlinerView != nullptr)
1382 pOutlinerView->SetSelection (GetSearchStartPosition ());
1385 mbEndOfSearch = false;
1387 else
1389 // No wrap around.
1390 mbEndOfSearch = true;
1395 void SdOutliner::ShowEndOfSearchDialog()
1397 if (meMode == SEARCH)
1399 if (!mbStringFound)
1401 SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound);
1402 std::shared_ptr<sd::ViewShell> pViewShell(mpWeakViewShell.lock());
1403 if (pViewShell)
1405 SfxViewShell& rSfxViewShell = pViewShell->GetViewShellBase();
1406 rSfxViewShell.libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_NOT_FOUND, mpSearchItem->GetSearchString().toUtf8());
1410 // don't do anything else for search
1411 return;
1414 OUString aString;
1415 if (mpView->AreObjectsMarked())
1416 aString = SdResId(STR_END_SPELLING_OBJ);
1417 else
1418 aString = SdResId(STR_END_SPELLING);
1420 // Show the message in an info box that is modal with respect to the whole application.
1421 weld::Window* pParent = GetMessageBoxParent();
1422 std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent,
1423 VclMessageType::Info, VclButtonsType::Ok, aString));
1424 xInfoBox->run();
1427 bool SdOutliner::ShowWrapAroundDialog()
1429 // Determine whether to show the dialog.
1430 if (mpSearchItem)
1432 // When searching display the dialog only for single find&replace.
1433 const SvxSearchCmd nCommand(mpSearchItem->GetCommand());
1434 if (nCommand == SvxSearchCmd::REPLACE || nCommand == SvxSearchCmd::FIND)
1436 if (mbDirectionIsForward)
1437 SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::End);
1438 else
1439 SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Start);
1441 return true;
1443 else
1444 return false;
1447 // show dialog only for spelling
1448 if (meMode != SPELL)
1449 return false;
1451 // The question text depends on the search direction.
1452 bool bImpress = mpDrawDocument && mpDrawDocument->GetDocumentType() == DocumentType::Impress;
1454 TranslateId pStringId;
1455 if (mbDirectionIsForward)
1456 pStringId = bImpress ? STR_SAR_WRAP_FORWARD : STR_SAR_WRAP_FORWARD_DRAW;
1457 else
1458 pStringId = bImpress ? STR_SAR_WRAP_BACKWARD : STR_SAR_WRAP_BACKWARD_DRAW;
1460 // Pop up question box that asks the user whether to wrap around.
1461 // The dialog is made modal with respect to the whole application.
1462 weld::Window* pParent = GetMessageBoxParent();
1463 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pParent,
1464 VclMessageType::Question, VclButtonsType::YesNo, SdResId(pStringId)));
1465 sal_uInt16 nBoxResult = xQueryBox->run();
1467 return (nBoxResult == RET_YES);
1470 void SdOutliner::PutTextIntoOutliner()
1472 mpSearchSpellTextObj = DynCastSdrTextObj( mpObj );
1473 if ( mpSearchSpellTextObj && mpSearchSpellTextObj->HasText() && !mpSearchSpellTextObj->IsEmptyPresObj() )
1475 SdrText* pText = mpSearchSpellTextObj->getText( maCurrentPosition.mnText );
1476 mpParaObj = pText ? pText->GetOutlinerParaObject() : nullptr;
1478 if (mpParaObj != nullptr)
1480 SetText(*mpParaObj);
1482 ClearModifyFlag();
1485 else
1487 mpSearchSpellTextObj = nullptr;
1491 void SdOutliner::PrepareSpellCheck()
1493 EESpellState eState = HasSpellErrors();
1494 DBG_ASSERT(eState != EESpellState::NoSpeller, "No SpellChecker");
1496 if (eState == EESpellState::Ok)
1497 return;
1499 // When spell checking we have to test whether we have processed the
1500 // whole document and have reached the start page again.
1501 if (meMode == SPELL)
1503 if (maSearchStartPosition == sd::outliner::Iterator())
1504 // Remember the position of the first text object so that we
1505 // know when we have processed the whole document.
1506 maSearchStartPosition = maObjectIterator;
1507 else if (maSearchStartPosition == maObjectIterator)
1509 mbEndOfSearch = true;
1513 EnterEditMode( false );
1516 void SdOutliner::PrepareSearchAndReplace()
1518 if (!HasText( *mpSearchItem ))
1519 return;
1521 // Set the object now that we know it matches.
1522 mpObj = SetObject(maCurrentPosition);
1524 mbStringFound = true;
1525 mbMatchMayExist = true;
1527 EnterEditMode(false);
1529 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
1530 // Start search at the right end of the current object's text
1531 // depending on the search direction.
1532 OutlinerView* pOutlinerView = getOutlinerView();
1533 if (pOutlinerView != nullptr)
1534 pOutlinerView->SetSelection (GetSearchStartPosition ());
1537 void SdOutliner::SetViewMode (PageKind ePageKind)
1539 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1540 std::shared_ptr<sd::DrawViewShell> pDrawViewShell(
1541 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1542 if (pDrawViewShell == nullptr || ePageKind == pDrawViewShell->GetPageKind())
1543 return;
1545 // Restore old edit mode.
1546 pDrawViewShell->ChangeEditMode(mpImpl->meOriginalEditMode, false);
1548 SetStatusEventHdl(Link<EditStatus&,void>());
1549 OUString sViewURL;
1550 switch (ePageKind)
1552 case PageKind::Standard:
1553 default:
1554 sViewURL = sd::framework::FrameworkHelper::msImpressViewURL;
1555 break;
1556 case PageKind::Notes:
1557 sViewURL = sd::framework::FrameworkHelper::msNotesViewURL;
1558 break;
1559 case PageKind::Handout:
1560 sViewURL = sd::framework::FrameworkHelper::msHandoutViewURL;
1561 break;
1563 // The text object iterator is destroyed when the shells are
1564 // switched but we need it so save it and restore it afterwards.
1565 sd::outliner::Iterator aIterator (maObjectIterator);
1566 bool bMatchMayExist = mbMatchMayExist;
1568 sd::ViewShellBase& rBase = pViewShell->GetViewShellBase();
1570 rtl::Reference<sd::FuSearch> xFuSearch;
1571 if (pViewShell->GetView())
1572 xFuSearch = pViewShell->GetView()->getSearchContext().getFunctionSearch();
1574 SetViewShell(std::shared_ptr<sd::ViewShell>());
1575 sd::framework::FrameworkHelper::Instance(rBase)->RequestView(
1576 sViewURL,
1577 sd::framework::FrameworkHelper::msCenterPaneURL);
1579 // Force (well, request) a synchronous update of the configuration.
1580 // In a better world we would handle the asynchronous view update
1581 // instead. But that would involve major restructuring of the
1582 // Outliner code.
1583 sd::framework::FrameworkHelper::Instance(rBase)->RequestSynchronousUpdate();
1585 auto pNewViewShell = rBase.GetMainViewShell();
1586 SetViewShell(pNewViewShell);
1587 if (xFuSearch.is() && pNewViewShell->GetView())
1588 pNewViewShell->GetView()->getSearchContext().setSearchFunction(xFuSearch);
1590 // Switching to another view shell has intermediatly called
1591 // EndSpelling(). A PrepareSpelling() is pending, so call that now.
1592 PrepareSpelling();
1594 // Update the number of pages so that
1595 // <member>DetectChange()</member> has the correct value to compare
1596 // to.
1597 mnPageCount = mpDrawDocument->GetSdPageCount(ePageKind);
1599 maObjectIterator = aIterator;
1600 mbMatchMayExist = bMatchMayExist;
1602 // Save edit mode so that it can be restored when switching the view
1603 // shell again.
1604 pDrawViewShell = std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell);
1605 OSL_ASSERT(pDrawViewShell != nullptr);
1606 if (pDrawViewShell != nullptr)
1607 mpImpl->meOriginalEditMode = pDrawViewShell->GetEditMode();
1610 void SdOutliner::SetPage (EditMode eEditMode, sal_uInt16 nPageIndex)
1612 if ( ! mbRestrictSearchToSelection)
1614 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1615 std::shared_ptr<sd::DrawViewShell> pDrawViewShell(
1616 std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell));
1617 OSL_ASSERT(pDrawViewShell != nullptr);
1618 if (pDrawViewShell != nullptr)
1620 pDrawViewShell->ChangeEditMode(eEditMode, false);
1621 pDrawViewShell->SwitchPage(nPageIndex);
1626 void SdOutliner::EnterEditMode (bool bGrabFocus)
1628 OutlinerView* pOutlinerView = getOutlinerView();
1629 if (!(pOutlinerView && mpSearchSpellTextObj))
1630 return;
1632 pOutlinerView->SetOutputArea( ::tools::Rectangle( Point(), Size(1, 1)));
1633 SetPaperSize( mpSearchSpellTextObj->GetLogicRect().GetSize() );
1634 SdrPageView* pPV = mpView->GetSdrPageView();
1636 // Make FuText the current function.
1637 SfxUInt16Item aItem (SID_TEXTEDIT, 1);
1638 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1639 if (!(pViewShell && pViewShell->GetDispatcher()))
1640 return;
1642 pViewShell->GetDispatcher()->ExecuteList(
1643 SID_TEXTEDIT, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, {&aItem});
1645 if (mpView->IsTextEdit())
1647 // end text edition before starting it again
1648 mpView->SdrEndTextEdit();
1651 // To be consistent with the usual behaviour in the Office the text
1652 // object that is put into edit mode would have also to be selected.
1653 // Starting the text edit mode is not enough so we do it here by
1654 // hand.
1655 mpView->UnmarkAllObj(pPV);
1656 mpView->MarkObj(mpSearchSpellTextObj, pPV);
1658 mpSearchSpellTextObj->setActiveText(mnText);
1660 // Turn on the edit mode for the text object.
1661 SetUpdateLayout(true);
1662 mpView->SdrBeginTextEdit(mpSearchSpellTextObj, pPV, mpWindow, true, this,
1663 pOutlinerView, true, true, bGrabFocus);
1665 mbFoundObject = true;
1668 ESelection SdOutliner::GetSearchStartPosition() const
1670 ESelection aPosition;
1671 if (mbDirectionIsForward)
1673 // The default constructor uses the beginning of the text as default.
1674 aPosition = ESelection ();
1676 else
1678 // Retrieve the position after the last character in the last
1679 // paragraph.
1680 sal_Int32 nParagraphCount = GetParagraphCount();
1681 if (nParagraphCount == 0)
1682 aPosition = ESelection();
1683 else
1685 sal_Int32 nLastParagraphLength = GetEditEngine().GetTextLen (
1686 nParagraphCount-1);
1687 aPosition = ESelection (nParagraphCount-1, nLastParagraphLength);
1691 return aPosition;
1694 bool SdOutliner::HasNoPreviousMatch()
1696 OutlinerView* pOutlinerView = getOutlinerView();
1698 DBG_ASSERT (pOutlinerView!=nullptr, "outline view in SdOutliner::HasNoPreviousMatch is NULL");
1700 // Detect whether the cursor stands at the beginning
1701 // resp. at the end of the text.
1702 return pOutlinerView->GetSelection() == GetSearchStartPosition();
1705 bool SdOutliner::HandleFailedSearch()
1707 bool bContinueSearch = false;
1709 OutlinerView* pOutlinerView = getOutlinerView();
1710 if (pOutlinerView && mpSearchItem)
1712 // Detect whether there is/may be a prior match. If there is then
1713 // ask the user whether to wrap around. Otherwise tell the user
1714 // that there is no match.
1715 if (HasNoPreviousMatch ())
1717 // No match found in the whole presentation.
1718 SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound);
1721 else
1723 // No further matches found. Ask the user whether to wrap
1724 // around and start again.
1725 bContinueSearch = ShowWrapAroundDialog();
1729 return bContinueSearch;
1732 SdrObject* SdOutliner::SetObject (
1733 const sd::outliner::IteratorPosition& rPosition)
1735 SetViewMode (rPosition.mePageKind);
1736 SetPage (rPosition.meEditMode, static_cast<sal_uInt16>(rPosition.mnPageIndex));
1737 mnText = rPosition.mnText;
1738 return rPosition.mxObject.get().get();
1741 void SdOutliner::SetViewShell (const std::shared_ptr<sd::ViewShell>& rpViewShell)
1743 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1744 if (pViewShell == rpViewShell)
1745 return;
1747 // Set the new view shell.
1748 mpWeakViewShell = rpViewShell;
1749 // When the outline view is not owned by us then we have to clear
1750 // that pointer so that the current one for the new view shell will
1751 // be used (in ProvideOutlinerView).
1752 if (rpViewShell)
1754 mpView = rpViewShell->GetView();
1756 mpWindow = rpViewShell->GetActiveWindow();
1758 mpImpl->ProvideOutlinerView(*this, rpViewShell, mpWindow);
1759 OutlinerView* pOutlinerView = getOutlinerView();
1760 if (pOutlinerView != nullptr)
1761 pOutlinerView->SetWindow(mpWindow);
1763 else
1765 mpView = nullptr;
1766 mpWindow = nullptr;
1770 void SdOutliner::HandleChangedSelection()
1772 maMarkListCopy.clear();
1773 mbRestrictSearchToSelection = mpView->AreObjectsMarked();
1774 if (!mbRestrictSearchToSelection)
1775 return;
1777 // Make a copy of the current mark list.
1778 const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();
1779 const size_t nCount = rMarkList.GetMarkCount();
1780 if (nCount > 0)
1782 maMarkListCopy.clear();
1783 maMarkListCopy.reserve (nCount);
1784 for (size_t i=0; i<nCount; ++i)
1785 maMarkListCopy.emplace_back(rMarkList.GetMark(i)->GetMarkedSdrObj ());
1787 else
1788 // No marked object. Is this case possible?
1789 mbRestrictSearchToSelection = false;
1792 void SdOutliner::StartConversion( LanguageType nSourceLanguage, LanguageType nTargetLanguage,
1793 const vcl::Font *pTargetFont, sal_Int32 nOptions, bool bIsInteractive )
1795 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1796 bool bMultiDoc = nullptr != dynamic_cast< const sd::DrawViewShell *>( pViewShell.get() );
1798 meMode = TEXT_CONVERSION;
1799 mbDirectionIsForward = true;
1800 mpSearchItem.reset();
1801 mnConversionLanguage = nSourceLanguage;
1803 BeginConversion();
1805 OutlinerView* pOutlinerView = getOutlinerView();
1806 if (pOutlinerView != nullptr)
1808 pOutlinerView->StartTextConversion(
1809 GetMessageBoxParent(),
1810 nSourceLanguage,
1811 nTargetLanguage,
1812 pTargetFont,
1813 nOptions,
1814 bIsInteractive,
1815 bMultiDoc);
1818 EndConversion();
1821 /** Prepare to do a text conversion on the current text object. This
1822 includes putting it into edit mode.
1824 void SdOutliner::PrepareConversion()
1826 SetUpdateLayout(true);
1827 if( HasConvertibleTextPortion( mnConversionLanguage ) )
1829 SetUpdateLayout(false);
1830 mbStringFound = true;
1831 mbMatchMayExist = true;
1833 EnterEditMode(true);
1835 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
1836 // Start search at the right end of the current object's text
1837 // depending on the search direction.
1839 else
1841 SetUpdateLayout(false);
1845 void SdOutliner::BeginConversion()
1847 SetRefDevice( SD_MOD()->GetVirtualRefDevice() );
1849 sd::ViewShellBase* pBase = getViewShellBase();
1850 if (pBase != nullptr)
1851 SetViewShell (pBase->GetMainViewShell());
1853 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1854 if (pViewShell)
1856 mbStringFound = false;
1858 // Supposed that we are not located at the very beginning/end of the
1859 // document then there may be a match in the document prior/after
1860 // the current position.
1861 mbMatchMayExist = true;
1863 maObjectIterator = sd::outliner::Iterator();
1864 maSearchStartPosition = sd::outliner::Iterator();
1865 RememberStartPosition();
1867 mpImpl->ProvideOutlinerView(*this, pViewShell, mpWindow);
1869 HandleChangedSelection ();
1871 ClearModifyFlag();
1874 void SdOutliner::EndConversion()
1876 EndSpelling();
1879 bool SdOutliner::ConvertNextDocument()
1881 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1882 if (dynamic_cast< const sd::OutlineViewShell *>( pViewShell.get() ) )
1883 return false;
1885 mpDrawDocument->GetDocSh()->SetWaitCursor( true );
1887 Initialize ( true );
1889 OutlinerView* pOutlinerView = getOutlinerView();
1890 if (pOutlinerView != nullptr)
1892 mpWindow = pViewShell->GetActiveWindow();
1893 pOutlinerView->SetWindow(mpWindow);
1895 ProvideNextTextObject ();
1897 mpDrawDocument->GetDocSh()->SetWaitCursor( false );
1898 ClearModifyFlag();
1900 // for text conversion we automatically wrap around one
1901 // time and stop at the start shape
1902 if( mpFirstObj )
1904 if( (mnText == 0) && (mpFirstObj == mpObj) )
1905 return false;
1907 else
1909 mpFirstObj = mpObj;
1912 return !mbEndOfSearch;
1915 weld::Window* SdOutliner::GetMessageBoxParent()
1917 // We assume that the parent of the given message box is NULL, i.e. it is
1918 // modal with respect to the top application window. However, this
1919 // does not affect the search dialog. Therefore we have to lock it here
1920 // while the message box is being shown. We also have to take into
1921 // account that we are called during a spell check and the search dialog
1922 // is not available.
1923 weld::Window* pSearchDialog = nullptr;
1924 SfxChildWindow* pChildWindow = nullptr;
1925 switch (meMode)
1927 case SEARCH:
1928 if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
1929 pChildWindow = pViewFrm->GetChildWindow(
1930 SvxSearchDialogWrapper::GetChildWindowId());
1931 break;
1933 case SPELL:
1934 if (SfxViewFrame* pViewFrm = SfxViewFrame::Current())
1935 pChildWindow = pViewFrm->GetChildWindow(
1936 sd::SpellDialogChildWindow::GetChildWindowId());
1937 break;
1939 case TEXT_CONVERSION:
1940 // There should no messages boxes be displayed while doing the
1941 // hangul hanja conversion.
1942 break;
1945 if (pChildWindow != nullptr)
1947 auto xController = pChildWindow->GetController();
1948 pSearchDialog = xController ? xController->getDialog() : nullptr;
1951 if (pSearchDialog)
1952 return pSearchDialog;
1954 std::shared_ptr<sd::ViewShell> pViewShell (mpWeakViewShell.lock());
1955 auto pWin = pViewShell->GetActiveWindow();
1956 return pWin ? pWin->GetFrameWeld() : nullptr;
1959 //===== SdOutliner::Implementation ==============================================
1961 SdOutliner::Implementation::Implementation()
1962 : meOriginalEditMode(EditMode::Page),
1963 mbOwnOutlineView(false),
1964 mpOutlineView(nullptr)
1968 SdOutliner::Implementation::~Implementation()
1970 if (mbOwnOutlineView && mpOutlineView!=nullptr)
1972 mpOutlineView->SetWindow(nullptr);
1973 delete mpOutlineView;
1974 mpOutlineView = nullptr;
1978 /** We try to create a new OutlinerView only when there is none available,
1979 either from an OutlinerViewShell or a previous call to
1980 ProvideOutlinerView(). This is necessary to support the spell checker
1981 which can not cope with exchanging the OutlinerView.
1983 void SdOutliner::Implementation::ProvideOutlinerView (
1984 Outliner& rOutliner,
1985 const std::shared_ptr<sd::ViewShell>& rpViewShell,
1986 vcl::Window* pWindow)
1988 if (rpViewShell == nullptr)
1989 return;
1991 switch (rpViewShell->GetShellType())
1993 case sd::ViewShell::ST_DRAW:
1994 case sd::ViewShell::ST_IMPRESS:
1995 case sd::ViewShell::ST_NOTES:
1996 case sd::ViewShell::ST_HANDOUT:
1998 // Create a new outline view to do the search on.
1999 bool bInsert = false;
2000 if (mpOutlineView != nullptr && !mbOwnOutlineView)
2001 mpOutlineView = nullptr;
2003 if (mpOutlineView == nullptr || !rOutliner.GetEditEngine().HasView(&mpOutlineView->GetEditView()))
2005 delete mpOutlineView;
2006 mpOutlineView = new OutlinerView(&rOutliner, pWindow);
2007 mbOwnOutlineView = true;
2008 bInsert = true;
2010 else
2011 mpOutlineView->SetWindow(pWindow);
2013 EVControlBits nStat = mpOutlineView->GetControlWord();
2014 nStat &= ~EVControlBits::AUTOSCROLL;
2015 mpOutlineView->SetControlWord(nStat);
2017 if (bInsert)
2018 rOutliner.InsertView( mpOutlineView );
2020 rOutliner.SetUpdateLayout(false);
2021 mpOutlineView->SetOutputArea (::tools::Rectangle (Point(), Size(1, 1)));
2022 rOutliner.SetPaperSize( Size(1, 1) );
2023 rOutliner.SetText(OUString(), rOutliner.GetParagraph(0));
2025 meOriginalEditMode =
2026 std::static_pointer_cast<sd::DrawViewShell>(rpViewShell)->GetEditMode();
2028 break;
2030 case sd::ViewShell::ST_OUTLINE:
2032 if (mpOutlineView!=nullptr && mbOwnOutlineView)
2033 delete mpOutlineView;
2034 mpOutlineView = rOutliner.GetView(0);
2035 mbOwnOutlineView = false;
2037 break;
2039 default:
2040 case sd::ViewShell::ST_NONE:
2041 case sd::ViewShell::ST_PRESENTATION:
2042 // Ignored
2043 break;
2047 void SdOutliner::Implementation::ReleaseOutlinerView()
2049 if (mbOwnOutlineView)
2051 OutlinerView* pView = mpOutlineView;
2052 mpOutlineView = nullptr;
2053 mbOwnOutlineView = false;
2054 if (pView != nullptr)
2056 pView->SetWindow(nullptr);
2057 delete pView;
2060 else
2062 mpOutlineView = nullptr;
2066 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */