xref: /core/svx/source/svdraw/svdedxv.cxx (revision 226847e3)
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 <com/sun/star/i18n/WordType.hpp>
21 #include <editeng/editdata.hxx>
22 #include <editeng/editeng.hxx>
23 #include <editeng/editobj.hxx>
24 #include <editeng/editstat.hxx>
25 #include <editeng/outlobj.hxx>
26 #include <editeng/unotext.hxx>
27 #include <o3tl/deleter.hxx>
28 #include <svl/itemiter.hxx>
29 #include <svl/style.hxx>
30 #include <svl/whiter.hxx>
31 #include <svtools/accessibilityoptions.hxx>
32 #include <svx/sdtfchim.hxx>
33 #include <svx/selectioncontroller.hxx>
34 #include <svx/svdedxv.hxx>
35 #include <svx/svdetc.hxx>
36 #include <svx/svdotable.hxx>
37 #include <svx/svdotext.hxx>
38 #include <svx/svdoutl.hxx>
39 #include <svx/svdpage.hxx>
40 #include <svx/svdpagv.hxx>
41 #include <svx/svdundo.hxx>
42 #include <vcl/canvastools.hxx>
43 #include <vcl/commandevent.hxx>
44 #include <vcl/cursor.hxx>
45 #include <vcl/weld.hxx>
46 #include <vcl/window.hxx>
47 #include <comphelper/lok.hxx>
48 #include <drawinglayer/processor2d/baseprocessor2d.hxx>
49 #include <drawinglayer/processor2d/processor2dtools.hxx>
50 #include <editeng/outliner.hxx>
51 #include <sal/log.hxx>
52 #include <sdr/overlay/overlaytools.hxx>
53 #include <sfx2/viewsh.hxx>
54 #include <svx/dialmgr.hxx>
55 #include <svx/sdr/overlay/overlaymanager.hxx>
56 #include <svx/sdr/overlay/overlayselection.hxx>
57 #include <svx/sdr/table/tablecontroller.hxx>
58 #include <svx/sdrpagewindow.hxx>
59 #include <svx/sdrpaintwindow.hxx>
60 #include <svx/sdrundomanager.hxx>
61 #include <svx/strings.hrc>
62 #include <svx/svdviter.hxx>
63 #include <svtools/optionsdrawinglayer.hxx>
64 #include <textchain.hxx>
65 #include <textchaincursor.hxx>
66 #include <tools/debug.hxx>
67 #include <vcl/svapp.hxx>
68 
69 #include <memory>
70 
71 SdrObjEditView::SdrObjEditView(SdrModel& rSdrModel, OutputDevice* pOut)
72     : SdrGlueEditView(rSdrModel, pOut)
73     , mpTextEditPV(nullptr)
74     , mpTextEditOutlinerView(nullptr)
75     , mpTextEditWin(nullptr)
76     , pTextEditCursorBuffer(nullptr)
77     , pMacroObj(nullptr)
78     , pMacroPV(nullptr)
79     , pMacroWin(nullptr)
80     , nMacroTol(0)
81     , mbTextEditDontDelete(false)
82     , mbTextEditOnlyOneView(false)
83     , mbTextEditNewObj(false)
84     , mbQuickTextEditMode(true)
85     , mbMacroDown(false)
86     , mpOldTextEditUndoManager(nullptr)
87 {
88 }
89 
90 SdrObjEditView::~SdrObjEditView()
91 {
92     mpTextEditWin = nullptr; // so there's no ShowCursor in SdrEndTextEdit
93     assert(!IsTextEdit());
94     if (IsTextEdit())
95         suppress_fun_call_w_exception(SdrEndTextEdit());
96     mpTextEditOutliner.reset();
97     assert(nullptr == mpOldTextEditUndoManager); // should have been reset
98 }
99 
100 bool SdrObjEditView::IsAction() const { return IsMacroObj() || SdrGlueEditView::IsAction(); }
101 
102 void SdrObjEditView::MovAction(const Point& rPnt)
103 {
104     if (IsMacroObj())
105         MovMacroObj(rPnt);
106     SdrGlueEditView::MovAction(rPnt);
107 }
108 
109 void SdrObjEditView::EndAction()
110 {
111     if (IsMacroObj())
112         EndMacroObj();
113     SdrGlueEditView::EndAction();
114 }
115 
116 void SdrObjEditView::BckAction()
117 {
118     BrkMacroObj();
119     SdrGlueEditView::BckAction();
120 }
121 
122 void SdrObjEditView::BrkAction()
123 {
124     BrkMacroObj();
125     SdrGlueEditView::BrkAction();
126 }
127 
128 SdrPageView* SdrObjEditView::ShowSdrPage(SdrPage* pPage)
129 {
130     SdrPageView* pPageView = SdrGlueEditView::ShowSdrPage(pPage);
131 
132     if (comphelper::LibreOfficeKit::isActive() && pPageView)
133     {
134         // Check if other views have an active text edit on the same page as
135         // this one.
136         SdrViewIter aIter(pPageView->GetPage());
137         for (SdrView* pView = aIter.FirstView(); pView; pView = aIter.NextView())
138         {
139             if (pView == this || !pView->IsTextEdit())
140                 continue;
141 
142             OutputDevice* pOutDev = GetFirstOutputDevice();
143             if (!pOutDev || pOutDev->GetOutDevType() != OUTDEV_WINDOW)
144                 continue;
145 
146             // Found one, so create an outliner view, to get invalidations when
147             // the text edit changes.
148             // Call GetSfxViewShell() to make sure ImpMakeOutlinerView()
149             // registers the view shell of this draw view, and not the view
150             // shell of pView.
151             OutlinerView* pOutlinerView
152                 = pView->ImpMakeOutlinerView(pOutDev->GetOwnerWindow(), nullptr, GetSfxViewShell());
153             pOutlinerView->HideCursor();
154             pView->GetTextEditOutliner()->InsertView(pOutlinerView);
155         }
156     }
157 
158     return pPageView;
159 }
160 
161 namespace
162 {
163 /// Removes outliner views registered in other draw views that use pOutputDevice.
164 void lcl_RemoveTextEditOutlinerViews(SdrObjEditView const* pThis, SdrPageView const* pPageView,
165                                      OutputDevice const* pOutputDevice)
166 {
167     if (!comphelper::LibreOfficeKit::isActive())
168         return;
169 
170     if (!pPageView)
171         return;
172 
173     if (!pOutputDevice || pOutputDevice->GetOutDevType() != OUTDEV_WINDOW)
174         return;
175 
176     SdrViewIter aIter(pPageView->GetPage());
177     for (SdrView* pView = aIter.FirstView(); pView; pView = aIter.NextView())
178     {
179         if (pView == pThis || !pView->IsTextEdit())
180             continue;
181 
182         SdrOutliner* pOutliner = pView->GetTextEditOutliner();
183         for (size_t nView = 0; nView < pOutliner->GetViewCount(); ++nView)
184         {
185             OutlinerView* pOutlinerView = pOutliner->GetView(nView);
186             if (pOutlinerView->GetWindow()->GetOutDev() != pOutputDevice)
187                 continue;
188 
189             pOutliner->RemoveView(pOutlinerView);
190             delete pOutlinerView;
191         }
192     }
193 }
194 }
195 
196 void SdrObjEditView::HideSdrPage()
197 {
198     lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), GetFirstOutputDevice());
199 
200     if (mpTextEditPV == GetSdrPageView())
201     {
202         // HideSdrPage() will clear mpPageView, avoid a dangling pointer.
203         mpTextEditPV = nullptr;
204     }
205 
206     SdrGlueEditView::HideSdrPage();
207 }
208 
209 void SdrObjEditView::TakeActionRect(tools::Rectangle& rRect) const
210 {
211     if (IsMacroObj())
212     {
213         rRect = pMacroObj->GetCurrentBoundRect();
214     }
215     else
216     {
217         SdrGlueEditView::TakeActionRect(rRect);
218     }
219 }
220 
221 void SdrObjEditView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
222 {
223     SdrGlueEditView::Notify(rBC, rHint);
224     if (mpTextEditOutliner == nullptr)
225         return;
226 
227     // change of printer while editing
228     if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint)
229         return;
230 
231     const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
232     SdrHintKind eKind = pSdrHint->GetKind();
233     if (eKind == SdrHintKind::RefDeviceChange)
234     {
235         mpTextEditOutliner->SetRefDevice(mpModel->GetRefDevice());
236     }
237     if (eKind == SdrHintKind::DefaultTabChange)
238     {
239         mpTextEditOutliner->SetDefTab(mpModel->GetDefaultTabulator());
240     }
241 }
242 
243 void SdrObjEditView::ModelHasChanged()
244 {
245     SdrGlueEditView::ModelHasChanged();
246     if (mxWeakTextEditObj.is() && !mxWeakTextEditObj->IsInserted())
247         SdrEndTextEdit(); // object deleted
248     // TextEditObj changed?
249     if (!IsTextEdit())
250         return;
251 
252     SdrTextObj* pTextObj = mxWeakTextEditObj.get();
253     if (pTextObj != nullptr)
254     {
255         size_t nOutlViewCnt = mpTextEditOutliner->GetViewCount();
256         bool bAreaChg = false;
257         bool bAnchorChg = false;
258         bool bColorChg = false;
259         bool bContourFrame = pTextObj->IsContourTextFrame();
260         EEAnchorMode eNewAnchor(EEAnchorMode::VCenterHCenter);
261         tools::Rectangle aOldArea(aMinTextEditArea);
262         aOldArea.Union(aTextEditArea);
263         Color aNewColor;
264         { // check area
265             Size aPaperMin1;
266             Size aPaperMax1;
267             tools::Rectangle aEditArea1;
268             tools::Rectangle aMinArea1;
269             pTextObj->TakeTextEditArea(&aPaperMin1, &aPaperMax1, &aEditArea1, &aMinArea1);
270             Point aPvOfs(pTextObj->GetTextEditOffset());
271 
272             // add possible GridOffset to up-to-now view-independent EditAreas
273             basegfx::B2DVector aGridOffset(0.0, 0.0);
274             if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj, GetSdrPageView()))
275             {
276                 const Point aOffset(basegfx::fround(aGridOffset.getX()),
277                                     basegfx::fround(aGridOffset.getY()));
278 
279                 aEditArea1 += aOffset;
280                 aMinArea1 += aOffset;
281             }
282 
283             aEditArea1.Move(aPvOfs.X(), aPvOfs.Y());
284             aMinArea1.Move(aPvOfs.X(), aPvOfs.Y());
285             tools::Rectangle aNewArea(aMinArea1);
286             aNewArea.Union(aEditArea1);
287 
288             if (aNewArea != aOldArea || aEditArea1 != aTextEditArea || aMinArea1 != aMinTextEditArea
289                 || mpTextEditOutliner->GetMinAutoPaperSize() != aPaperMin1
290                 || mpTextEditOutliner->GetMaxAutoPaperSize() != aPaperMax1)
291             {
292                 aTextEditArea = aEditArea1;
293                 aMinTextEditArea = aMinArea1;
294 
295                 const bool bPrevUpdateLayout = mpTextEditOutliner->SetUpdateLayout(false);
296                 mpTextEditOutliner->SetMinAutoPaperSize(aPaperMin1);
297                 mpTextEditOutliner->SetMaxAutoPaperSize(aPaperMax1);
298                 mpTextEditOutliner->SetPaperSize(Size(0, 0)); // re-format Outliner
299 
300                 if (!bContourFrame)
301                 {
302                     mpTextEditOutliner->ClearPolygon();
303                     EEControlBits nStat = mpTextEditOutliner->GetControlWord();
304                     nStat |= EEControlBits::AUTOPAGESIZE;
305                     mpTextEditOutliner->SetControlWord(nStat);
306                 }
307                 else
308                 {
309                     EEControlBits nStat = mpTextEditOutliner->GetControlWord();
310                     nStat &= ~EEControlBits::AUTOPAGESIZE;
311                     mpTextEditOutliner->SetControlWord(nStat);
312                     tools::Rectangle aAnchorRect;
313                     pTextObj->TakeTextAnchorRect(aAnchorRect);
314                     pTextObj->ImpSetContourPolygon(*mpTextEditOutliner, aAnchorRect, true);
315                 }
316                 for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++)
317                 {
318                     OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV);
319                     EVControlBits nStat0 = pOLV->GetControlWord();
320                     EVControlBits nStat = nStat0;
321                     // AutoViewSize only if not ContourFrame.
322                     if (!bContourFrame)
323                         nStat |= EVControlBits::AUTOSIZE;
324                     else
325                         nStat &= ~EVControlBits::AUTOSIZE;
326                     if (nStat != nStat0)
327                         pOLV->SetControlWord(nStat);
328                 }
329 
330                 mpTextEditOutliner->SetUpdateLayout(bPrevUpdateLayout);
331                 bAreaChg = true;
332             }
333         }
334         if (mpTextEditOutlinerView != nullptr)
335         { // check fill and anchor
336             EEAnchorMode eOldAnchor = mpTextEditOutlinerView->GetAnchorMode();
337             eNewAnchor = pTextObj->GetOutlinerViewAnchorMode();
338             bAnchorChg = eOldAnchor != eNewAnchor;
339             Color aOldColor(mpTextEditOutlinerView->GetBackgroundColor());
340             aNewColor = GetTextEditBackgroundColor(*this);
341             bColorChg = aOldColor != aNewColor;
342         }
343         // refresh always when it's a contour frame. That
344         // refresh is necessary since it triggers the repaint
345         // which makes the Handles visible. Changes at TakeTextRect()
346         // seem to have resulted in a case where no refresh is executed.
347         // Before that, a refresh must have been always executed
348         // (else this error would have happened earlier), thus I
349         // even think here a refresh should be done always.
350         // Since follow-up problems cannot even be guessed I only
351         // add this one more case to the if below.
352         // BTW: It's VERY bad style that here, inside ModelHasChanged()
353         // the outliner is again massively changed for the text object
354         // in text edit mode. Normally, all necessary data should be
355         // set at SdrBeginTextEdit(). Some changes and value assigns in
356         // SdrBeginTextEdit() are completely useless since they are set here
357         // again on ModelHasChanged().
358         if (bContourFrame || bAreaChg || bAnchorChg || bColorChg)
359         {
360             for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++)
361             {
362                 OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV);
363                 { // invalidate old OutlinerView area
364                     vcl::Window* pWin = pOLV->GetWindow();
365                     tools::Rectangle aTmpRect(aOldArea);
366                     sal_uInt16 nPixSiz = pOLV->GetInvalidateMore() + 1;
367                     Size aMore(pWin->PixelToLogic(Size(nPixSiz, nPixSiz)));
368                     aTmpRect.AdjustLeft(-(aMore.Width()));
369                     aTmpRect.AdjustRight(aMore.Width());
370                     aTmpRect.AdjustTop(-(aMore.Height()));
371                     aTmpRect.AdjustBottom(aMore.Height());
372                     InvalidateOneWin(*pWin->GetOutDev(), aTmpRect);
373                 }
374                 if (bAnchorChg)
375                     pOLV->SetAnchorMode(eNewAnchor);
376                 if (bColorChg)
377                     pOLV->SetBackgroundColor(aNewColor);
378 
379                 pOLV->SetOutputArea(
380                     aTextEditArea); // because otherwise, we're not re-anchoring correctly
381                 ImpInvalidateOutlinerView(*pOLV);
382             }
383             mpTextEditOutlinerView->ShowCursor();
384         }
385     }
386     ImpMakeTextCursorAreaVisible();
387 }
388 
389 namespace
390 {
391 /**
392         Helper class to visualize the content of an active EditView as an
393         OverlayObject. These objects work with Primitives and are handled
394         from the OverlayManager(s) in place as needed.
395 
396         It allows complete visualization of the content of the active
397         EditView without the need of Invalidates triggered by the EditView
398         and thus avoiding potentially expensive repaints by using the
399         automatically buffered Overlay mechanism.
400 
401         It buffers as much as possible locally and *only* triggers a real
402         change (see call to objectChange()) when really needed.
403      */
404 class TextEditOverlayObject : public sdr::overlay::OverlayObject
405 {
406 protected:
407     /// local access to associated sdr::overlay::OverlaySelection
408     std::unique_ptr<sdr::overlay::OverlaySelection> mxOverlaySelection;
409 
410     /// local definition depends on active OutlinerView
411     OutlinerView& mrOutlinerView;
412 
413     /// geometry definitions with buffering
414     basegfx::B2DRange maLastRange;
415     basegfx::B2DRange maRange;
416 
417     /// text content definitions with buffering
418     drawinglayer::primitive2d::Primitive2DContainer maTextPrimitives;
419     drawinglayer::primitive2d::Primitive2DContainer maLastTextPrimitives;
420 
421     /// bitfield
422     bool mbVisualizeSurroundingFrame : 1;
423 
424     // geometry creation for OverlayObject, can use local *Last* values
425     virtual drawinglayer::primitive2d::Primitive2DContainer
426     createOverlayObjectPrimitive2DSequence() override;
427 
428 public:
429     TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView,
430                           bool bVisualizeSurroundingFrame);
431     virtual ~TextEditOverlayObject() override;
432 
433     // data read access
434     const sdr::overlay::OverlaySelection* getOverlaySelection() const
435     {
436         return mxOverlaySelection.get();
437     }
438     const OutlinerView& getOutlinerView() const { return mrOutlinerView; }
439 
440     /// override to check conditions for last createOverlayObjectPrimitive2DSequence
441     virtual drawinglayer::primitive2d::Primitive2DContainer
442     getOverlayObjectPrimitive2DSequence() const override;
443 
444     // data write access. In this OverlayObject we only have the
445     // callback that triggers detecting if something *has* changed
446     void checkDataChange(const basegfx::B2DRange& rMinTextEditArea);
447     void checkSelectionChange();
448 };
449 
450 drawinglayer::primitive2d::Primitive2DContainer
451 TextEditOverlayObject::createOverlayObjectPrimitive2DSequence()
452 {
453     drawinglayer::primitive2d::Primitive2DContainer aRetval;
454 
455     /// outer frame visualization
456     if (mbVisualizeSurroundingFrame)
457     {
458         const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01);
459         const sal_uInt16 nPixSiz(getOutlinerView().GetInvalidateMore() - 1);
460 
461         aRetval.push_back(new drawinglayer::primitive2d::OverlayRectanglePrimitive(
462             maRange, getBaseColor().getBColor(), fTransparence, std::max(6, nPixSiz - 2), // grow
463             0.0, // shrink
464             0.0));
465     }
466 
467     // add buffered TextPrimitives
468     aRetval.append(maTextPrimitives);
469 
470     return aRetval;
471 }
472 
473 TextEditOverlayObject::TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView,
474                                              bool bVisualizeSurroundingFrame)
475     : OverlayObject(rColor)
476     , mrOutlinerView(rOutlinerView)
477     , mbVisualizeSurroundingFrame(bVisualizeSurroundingFrame)
478 {
479     // no AA for TextEdit overlay
480     allowAntiAliase(false);
481 
482     // create local OverlaySelection - this is an integral part of EditText
483     // visualization
484     std::vector<basegfx::B2DRange> aEmptySelection{};
485     mxOverlaySelection.reset(new sdr::overlay::OverlaySelection(
486         sdr::overlay::OverlayType::Transparent, rColor, std::move(aEmptySelection), true));
487 }
488 
489 TextEditOverlayObject::~TextEditOverlayObject()
490 {
491     mxOverlaySelection.reset();
492 
493     if (getOverlayManager())
494     {
495         getOverlayManager()->remove(*this);
496     }
497 }
498 
499 drawinglayer::primitive2d::Primitive2DContainer
500 TextEditOverlayObject::getOverlayObjectPrimitive2DSequence() const
501 {
502     if (!getPrimitive2DSequence().empty())
503     {
504         if (!maRange.equal(maLastRange) || maLastTextPrimitives != maTextPrimitives)
505         {
506             // conditions of last local decomposition have changed, delete to force new evaluation
507             const_cast<TextEditOverlayObject*>(this)->resetPrimitive2DSequence();
508         }
509     }
510 
511     if (getPrimitive2DSequence().empty())
512     {
513         // remember new buffered values
514         const_cast<TextEditOverlayObject*>(this)->maLastRange = maRange;
515         const_cast<TextEditOverlayObject*>(this)->maLastTextPrimitives = maTextPrimitives;
516     }
517 
518     // call base implementation
519     return OverlayObject::getOverlayObjectPrimitive2DSequence();
520 }
521 
522 void TextEditOverlayObject::checkDataChange(const basegfx::B2DRange& rMinTextEditArea)
523 {
524     bool bObjectChange(false);
525 
526     // check current range
527     const tools::Rectangle aOutArea(mrOutlinerView.GetOutputArea());
528     basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aOutArea);
529     aNewRange.expand(rMinTextEditArea);
530 
531     if (aNewRange != maRange)
532     {
533         maRange = aNewRange;
534         bObjectChange = true;
535     }
536 
537     // check if text primitives did change
538     SdrOutliner* pSdrOutliner = dynamic_cast<SdrOutliner*>(getOutlinerView().GetOutliner());
539 
540     if (pSdrOutliner)
541     {
542         // get TextPrimitives directly from active Outliner
543         basegfx::B2DHomMatrix aNewTransformA;
544         basegfx::B2DHomMatrix aNewTransformB;
545         basegfx::B2DRange aClipRange;
546         drawinglayer::primitive2d::Primitive2DContainer aNewTextPrimitives;
547 
548         // active Outliner is always in unified oriented coordinate system (currently)
549         // so just translate to TopLeft of visible Range. Keep in mind that top-left
550         // depends on vertical text and top-to-bottom text attributes
551         const tools::Rectangle aVisArea(mrOutlinerView.GetVisArea());
552         const bool bVerticalWriting(pSdrOutliner->IsVertical());
553         const bool bTopToBottom(pSdrOutliner->IsTopToBottom());
554         const double fStartInX(bVerticalWriting && bTopToBottom
555                                    ? aOutArea.Right() - aVisArea.Left()
556                                    : aOutArea.Left() - aVisArea.Left());
557         const double fStartInY(bVerticalWriting && !bTopToBottom
558                                    ? aOutArea.Bottom() - aVisArea.Top()
559                                    : aOutArea.Top() - aVisArea.Top());
560 
561         aNewTransformB.translate(fStartInX, fStartInY);
562 
563         // get the current TextPrimitives. This is the most expensive part
564         // of this mechanism, it *may* be possible to buffer layouted
565         // primitives per ParaPortion with/in/dependent on the EditEngine
566         // content if needed. For now, get and compare
567         SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
568             aNewTextPrimitives, *pSdrOutliner, aNewTransformA, aNewTransformB, aClipRange);
569 
570         if (aNewTextPrimitives != maTextPrimitives)
571         {
572             maTextPrimitives = std::move(aNewTextPrimitives);
573             bObjectChange = true;
574         }
575     }
576 
577     if (bObjectChange)
578     {
579         // if there really *was* a change signal the OverlayManager to
580         // refresh this object's visualization
581         objectChange();
582 
583         // on data change, always do a SelectionChange, too
584         // since the selection is an integral part of text visualization
585         checkSelectionChange();
586     }
587 }
588 
589 void TextEditOverlayObject::checkSelectionChange()
590 {
591     if (!(getOverlaySelection() && getOverlayManager()))
592         return;
593 
594     std::vector<tools::Rectangle> aLogicRects;
595     std::vector<basegfx::B2DRange> aLogicRanges;
596     const Size aLogicPixel(getOverlayManager()->getOutputDevice().PixelToLogic(Size(1, 1)));
597 
598     // get logic selection
599     getOutlinerView().GetSelectionRectangles(aLogicRects);
600 
601     aLogicRanges.reserve(aLogicRects.size());
602     for (const auto& aRect : aLogicRects)
603     {
604         // convert from logic Rectangles to logic Ranges, do not forget to add
605         // one Unit (in this case logical units for one pixel, pre-calculated)
606         aLogicRanges.emplace_back(
607             aRect.Left() - aLogicPixel.Width(), aRect.Top() - aLogicPixel.Height(),
608             aRect.Right() + aLogicPixel.Width(), aRect.Bottom() + aLogicPixel.Height());
609     }
610 
611     mxOverlaySelection->setRanges(std::move(aLogicRanges));
612 }
613 } // end of anonymous namespace
614 
615 // TextEdit
616 
617 // callback from the active EditView, forward to evtl. existing instances of the
618 // TextEditOverlayObject(s). This will additionally update the selection which
619 // is an integral part of the text visualization
620 void SdrObjEditView::EditViewInvalidate(const tools::Rectangle&)
621 {
622     if (!IsTextEdit())
623         return;
624 
625     // MinTextRange may have changed. Forward it, too
626     const basegfx::B2DRange aMinTextRange
627         = vcl::unotools::b2DRectangleFromRectangle(aMinTextEditArea);
628 
629     for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++)
630     {
631         TextEditOverlayObject* pCandidate
632             = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a));
633 
634         if (pCandidate)
635         {
636             pCandidate->checkDataChange(aMinTextRange);
637         }
638     }
639 }
640 
641 // callback from the active EditView, forward to evtl. existing instances of the
642 // TextEditOverlayObject(s). This cvall *only* updates the selection visualization
643 // which is e.g. used when only the selection is changed, but not the text
644 void SdrObjEditView::EditViewSelectionChange()
645 {
646     if (!IsTextEdit())
647         return;
648 
649     for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++)
650     {
651         TextEditOverlayObject* pCandidate
652             = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a));
653 
654         if (pCandidate)
655         {
656             pCandidate->checkSelectionChange();
657         }
658     }
659 }
660 
661 OutputDevice& SdrObjEditView::EditViewOutputDevice() const { return *mpTextEditWin->GetOutDev(); }
662 
663 Point SdrObjEditView::EditViewPointerPosPixel() const
664 {
665     return mpTextEditWin->GetPointerPosPixel();
666 }
667 
668 css::uno::Reference<css::datatransfer::clipboard::XClipboard> SdrObjEditView::GetClipboard() const
669 {
670     if (!mpTextEditWin)
671         return nullptr;
672     return mpTextEditWin->GetClipboard();
673 }
674 
675 css::uno::Reference<css::datatransfer::dnd::XDropTarget> SdrObjEditView::GetDropTarget()
676 {
677     if (!mpTextEditWin)
678         return nullptr;
679     return mpTextEditWin->GetDropTarget();
680 }
681 
682 void SdrObjEditView::EditViewInputContext(const InputContext& rInputContext)
683 {
684     if (!mpTextEditWin)
685         return;
686     mpTextEditWin->SetInputContext(rInputContext);
687 }
688 
689 void SdrObjEditView::EditViewCursorRect(const tools::Rectangle& rRect, int nExtTextInputWidth)
690 {
691     if (!mpTextEditWin)
692         return;
693     mpTextEditWin->SetCursorRect(&rRect, nExtTextInputWidth);
694 }
695 
696 void SdrObjEditView::TextEditDrawing(SdrPaintWindow& rPaintWindow)
697 {
698     if (!comphelper::LibreOfficeKit::isActive())
699     {
700         // adapt all TextEditOverlayObject(s), so call EditViewInvalidate()
701         // to update accordingly (will update selection, too). Suppress new
702         // stuff when LibreOfficeKit is active
703         EditViewInvalidate(tools::Rectangle());
704     }
705     else
706     {
707         // draw old text edit stuff
708         if (IsTextEdit())
709         {
710             const SdrOutliner* pActiveOutliner = GetTextEditOutliner();
711 
712             if (pActiveOutliner)
713             {
714                 const sal_uInt32 nViewCount(pActiveOutliner->GetViewCount());
715 
716                 if (nViewCount)
717                 {
718                     const vcl::Region& rRedrawRegion = rPaintWindow.GetRedrawRegion();
719                     const tools::Rectangle aCheckRect(rRedrawRegion.GetBoundRect());
720 
721                     for (sal_uInt32 i(0); i < nViewCount; i++)
722                     {
723                         OutlinerView* pOLV = pActiveOutliner->GetView(i);
724 
725                         // If rPaintWindow knows that the output device is a render
726                         // context and is aware of the underlying vcl::Window,
727                         // compare against that; that's how double-buffering can
728                         // still find the matching OutlinerView.
729                         OutputDevice* pOutputDevice = rPaintWindow.GetWindow()
730                                                           ? rPaintWindow.GetWindow()->GetOutDev()
731                                                           : &rPaintWindow.GetOutputDevice();
732                         if (pOLV->GetWindow()->GetOutDev() == pOutputDevice
733                             || comphelper::LibreOfficeKit::isActive())
734                         {
735                             ImpPaintOutlinerView(*pOLV, aCheckRect,
736                                                  rPaintWindow.GetTargetOutputDevice());
737                             return;
738                         }
739                     }
740                 }
741             }
742         }
743     }
744 }
745 
746 void SdrObjEditView::ImpPaintOutlinerView(OutlinerView& rOutlView, const tools::Rectangle& rRect,
747                                           OutputDevice& rTargetDevice) const
748 {
749     const SdrTextObj* pText = GetTextEditObject();
750     bool bTextFrame(pText && pText->IsTextFrame());
751     bool bFitToSize(mpTextEditOutliner->GetControlWord() & EEControlBits::STRETCHING);
752     bool bModified(mpTextEditOutliner->IsModified());
753     tools::Rectangle aBlankRect(rOutlView.GetOutputArea());
754     aBlankRect.Union(aMinTextEditArea);
755     tools::Rectangle aPixRect(rTargetDevice.LogicToPixel(aBlankRect));
756 
757     // in the tiled rendering case, the setup is incomplete, and we very
758     // easily get an empty rRect on input - that will cause that everything is
759     // clipped; happens in case of editing text inside a shape in Calc.
760     // FIXME would be better to complete the setup so that we don't get an
761     // empty rRect here
762     if (!comphelper::LibreOfficeKit::isActive() || !rRect.IsEmpty())
763         aBlankRect.Intersection(rRect);
764 
765     rOutlView.GetOutliner()->SetUpdateLayout(true); // Bugfix #22596#
766     rOutlView.Paint(aBlankRect, &rTargetDevice);
767 
768     if (!bModified)
769     {
770         mpTextEditOutliner->ClearModifyFlag();
771     }
772 
773     if (bTextFrame && !bFitToSize)
774     {
775         // completely reworked to use primitives; this ensures same look and functionality
776         const drawinglayer::geometry::ViewInformation2D aViewInformation2D;
777         std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> xProcessor(
778             drawinglayer::processor2d::createProcessor2DFromOutputDevice(rTargetDevice,
779                                                                          aViewInformation2D));
780 
781         const bool bMapModeEnabled(rTargetDevice.IsMapModeEnabled());
782         const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aPixRect);
783         const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor());
784         const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01);
785         const sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1);
786         const drawinglayer::primitive2d::Primitive2DReference xReference(
787             new drawinglayer::primitive2d::OverlayRectanglePrimitive(
788                 aRange, aHilightColor.getBColor(), fTransparence, std::max(6, nPixSiz - 2), // grow
789                 0.0, // shrink
790                 0.0));
791         const drawinglayer::primitive2d::Primitive2DContainer aSequence{ xReference };
792 
793         rTargetDevice.EnableMapMode(false);
794         xProcessor->process(aSequence);
795         rTargetDevice.EnableMapMode(bMapModeEnabled);
796     }
797 
798     rOutlView.ShowCursor(/*bGotoCursor=*/true, /*bActivate=*/true);
799 }
800 
801 void SdrObjEditView::ImpInvalidateOutlinerView(OutlinerView const& rOutlView) const
802 {
803     vcl::Window* pWin = rOutlView.GetWindow();
804 
805     if (!pWin)
806         return;
807 
808     const SdrTextObj* pText = GetTextEditObject();
809     bool bTextFrame(pText && pText->IsTextFrame());
810     bool bFitToSize(pText && pText->IsFitToSize());
811 
812     if (!bTextFrame || bFitToSize)
813         return;
814 
815     tools::Rectangle aBlankRect(rOutlView.GetOutputArea());
816     aBlankRect.Union(aMinTextEditArea);
817     tools::Rectangle aPixRect(pWin->LogicToPixel(aBlankRect));
818     sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1);
819 
820     aPixRect.AdjustLeft(-1);
821     aPixRect.AdjustTop(-1);
822     aPixRect.AdjustRight(1);
823     aPixRect.AdjustBottom(1);
824 
825     {
826         // limit xPixRect because of driver problems when pixel coordinates are too far out
827         Size aMaxXY(pWin->GetOutputSizePixel());
828         tools::Long a(2 * nPixSiz);
829         tools::Long nMaxX(aMaxXY.Width() + a);
830         tools::Long nMaxY(aMaxXY.Height() + a);
831 
832         if (aPixRect.Left() < -a)
833             aPixRect.SetLeft(-a);
834         if (aPixRect.Top() < -a)
835             aPixRect.SetTop(-a);
836         if (aPixRect.Right() > nMaxX)
837             aPixRect.SetRight(nMaxX);
838         if (aPixRect.Bottom() > nMaxY)
839             aPixRect.SetBottom(nMaxY);
840     }
841 
842     tools::Rectangle aOuterPix(aPixRect);
843     aOuterPix.AdjustLeft(-nPixSiz);
844     aOuterPix.AdjustTop(-nPixSiz);
845     aOuterPix.AdjustRight(nPixSiz);
846     aOuterPix.AdjustBottom(nPixSiz);
847 
848     bool bMapModeEnabled(pWin->IsMapModeEnabled());
849     pWin->EnableMapMode(false);
850     pWin->Invalidate(aOuterPix);
851     pWin->EnableMapMode(bMapModeEnabled);
852 }
853 
854 OutlinerView* SdrObjEditView::ImpMakeOutlinerView(vcl::Window* pWin, OutlinerView* pGivenView,
855                                                   SfxViewShell* pViewShell) const
856 {
857     // background
858     Color aBackground(GetTextEditBackgroundColor(*this));
859     SdrTextObj* pText = mxWeakTextEditObj.get();
860     bool bTextFrame = pText != nullptr && pText->IsTextFrame();
861     bool bContourFrame = pText != nullptr && pText->IsContourTextFrame();
862     // create OutlinerView
863     OutlinerView* pOutlView = pGivenView;
864     mpTextEditOutliner->SetUpdateLayout(false);
865 
866     if (pOutlView == nullptr)
867     {
868         pOutlView = new OutlinerView(mpTextEditOutliner.get(), pWin);
869     }
870     else
871     {
872         pOutlView->SetWindow(pWin);
873     }
874 
875     if (mbNegativeX)
876         pOutlView->GetEditView().SetNegativeX(mbNegativeX);
877 
878     // disallow scrolling
879     EVControlBits nStat = pOutlView->GetControlWord();
880     nStat &= ~EVControlBits::AUTOSCROLL;
881     // AutoViewSize only if not ContourFrame.
882     if (!bContourFrame)
883         nStat |= EVControlBits::AUTOSIZE;
884     if (bTextFrame)
885     {
886         sal_uInt16 nPixSiz = maHdlList.GetHdlSize() * 2 + 1;
887         nStat |= EVControlBits::INVONEMORE;
888         pOutlView->SetInvalidateMore(nPixSiz);
889     }
890     pOutlView->SetControlWord(nStat);
891     pOutlView->SetBackgroundColor(aBackground);
892 
893     // In case we're in the process of constructing a new view shell,
894     // SfxViewShell::Current() may still point to the old one. So if possible,
895     // depend on the application owning this draw view to provide the view
896     // shell.
897     SfxViewShell* pSfxViewShell = pViewShell ? pViewShell : GetSfxViewShell();
898     pOutlView->RegisterViewShell(pSfxViewShell ? pSfxViewShell : SfxViewShell::Current());
899 
900     if (pText != nullptr)
901     {
902         pOutlView->SetAnchorMode(pText->GetOutlinerViewAnchorMode());
903         mpTextEditOutliner->SetFixedCellHeight(
904             pText->GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue());
905     }
906     // do update before setting output area so that aTextEditArea can be recalculated
907     mpTextEditOutliner->SetUpdateLayout(true);
908     pOutlView->SetOutputArea(aTextEditArea);
909     ImpInvalidateOutlinerView(*pOutlView);
910     return pOutlView;
911 }
912 
913 IMPL_LINK(SdrObjEditView, ImpOutlinerStatusEventHdl, EditStatus&, rEditStat, void)
914 {
915     if (mpTextEditOutliner)
916     {
917         SdrTextObj* pTextObj = mxWeakTextEditObj.get();
918         if (pTextObj)
919         {
920             pTextObj->onEditOutlinerStatusEvent(&rEditStat);
921         }
922     }
923 }
924 
925 void SdrObjEditView::ImpChainingEventHdl()
926 {
927     if (!mpTextEditOutliner)
928         return;
929 
930     SdrTextObj* pTextObj = mxWeakTextEditObj.get();
931     OutlinerView* pOLV = GetTextEditOutlinerView();
932     if (pTextObj && pOLV)
933     {
934         TextChain* pTextChain = pTextObj->GetTextChain();
935 
936         // XXX: IsChainable and GetNilChainingEvent are a bit mixed up atm
937         if (!pTextObj->IsChainable())
938         {
939             return;
940         }
941         // This is true during an underflow-caused overflow (with pEdtOutl->SetText())
942         if (pTextChain->GetNilChainingEvent(pTextObj))
943         {
944             return;
945         }
946 
947         // We prevent to trigger further handling of overflow/underflow for pTextObj
948         pTextChain->SetNilChainingEvent(pTextObj, true); // XXX
949 
950         // Save previous selection pos // NOTE: It must be done to have the right CursorEvent in KeyInput
951         pTextChain->SetPreChainingSel(pTextObj, pOLV->GetSelection());
952         //maPreChainingSel = new ESelection(pOLV->GetSelection());
953 
954         // Handling Undo
955         const int nText = 0; // XXX: hardcoded index (SdrTextObj::getText handles only 0)
956 
957         const bool bUndoEnabled = GetModel() && IsUndoEnabled();
958         std::unique_ptr<SdrUndoObjSetText> pTxtUndo;
959         if (bUndoEnabled)
960             pTxtUndo.reset(
961                 dynamic_cast<SdrUndoObjSetText*>(GetModel()
962                                                      ->GetSdrUndoFactory()
963                                                      .CreateUndoObjectSetText(*pTextObj, nText)
964                                                      .release()));
965 
966         // trigger actual chaining
967         pTextObj->onChainingEvent();
968 
969         if (pTxtUndo)
970         {
971             pTxtUndo->AfterSetText();
972             if (!pTxtUndo->IsDifferent())
973             {
974                 pTxtUndo.reset();
975             }
976         }
977 
978         if (pTxtUndo)
979             AddUndo(std::move(pTxtUndo));
980 
981         //maCursorEvent = new CursorChainingEvent(pTextChain->GetCursorEvent(pTextObj));
982         //SdrTextObj *pNextLink = pTextObj->GetNextLinkInChain();
983 
984         // NOTE: Must be called. Don't let the function return if you set it to true and not reset it
985         pTextChain->SetNilChainingEvent(pTextObj, false);
986     }
987     else
988     {
989         // XXX
990         SAL_INFO("svx.chaining", "[OnChaining] No Edit Outliner View");
991     }
992 }
993 
994 IMPL_LINK_NOARG(SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl, LinkParamNone*, void)
995 {
996     SdrTextObj* pTextObj = GetTextEditObject();
997     if (!pTextObj)
998         return;
999     ImpChainingEventHdl();
1000     TextChainCursorManager aCursorManager(this, pTextObj);
1001     ImpMoveCursorAfterChainingEvent(&aCursorManager);
1002 }
1003 
1004 void SdrObjEditView::ImpMoveCursorAfterChainingEvent(TextChainCursorManager* pCursorManager)
1005 {
1006     if (!mxWeakTextEditObj.is() || !pCursorManager)
1007         return;
1008 
1009     SdrTextObj* pTextObj = mxWeakTextEditObj.get();
1010 
1011     // Check if it has links to move it to
1012     if (!pTextObj || !pTextObj->IsChainable())
1013         return;
1014 
1015     TextChain* pTextChain = pTextObj->GetTextChain();
1016     ESelection aNewSel = pTextChain->GetPostChainingSel(pTextObj);
1017 
1018     pCursorManager->HandleCursorEventAfterChaining(pTextChain->GetCursorEvent(pTextObj), aNewSel);
1019 
1020     // Reset event
1021     pTextChain->SetCursorEvent(pTextObj, CursorChainingEvent::NULL_EVENT);
1022 }
1023 
1024 IMPL_LINK(SdrObjEditView, ImpOutlinerCalcFieldValueHdl, EditFieldInfo*, pFI, void)
1025 {
1026     bool bOk = false;
1027     OUString& rStr = pFI->GetRepresentation();
1028     rStr.clear();
1029     SdrTextObj* pTextObj = mxWeakTextEditObj.get();
1030     if (pTextObj != nullptr)
1031     {
1032         std::optional<Color> pTxtCol;
1033         std::optional<Color> pFldCol;
1034         bOk = pTextObj->CalcFieldValue(pFI->GetField(), pFI->GetPara(), pFI->GetPos(), true,
1035                                        pTxtCol, pFldCol, rStr);
1036         if (bOk)
1037         {
1038             if (pTxtCol)
1039             {
1040                 pFI->SetTextColor(*pTxtCol);
1041             }
1042             if (pFldCol)
1043             {
1044                 pFI->SetFieldColor(*pFldCol);
1045             }
1046             else
1047             {
1048                 pFI->SetFieldColor(COL_LIGHTGRAY); // TODO: remove this later on (357)
1049             }
1050         }
1051     }
1052     Outliner& rDrawOutl = mpModel->GetDrawOutliner(pTextObj);
1053     Link<EditFieldInfo*, void> aDrawOutlLink = rDrawOutl.GetCalcFieldValueHdl();
1054     if (!bOk && aDrawOutlLink.IsSet())
1055     {
1056         aDrawOutlLink.Call(pFI);
1057         bOk = !rStr.isEmpty();
1058     }
1059     if (!bOk)
1060     {
1061         aOldCalcFieldValueLink.Call(pFI);
1062     }
1063 }
1064 
1065 IMPL_LINK_NOARG(SdrObjEditView, EndTextEditHdl, SdrUndoManager*, void) { SdrEndTextEdit(); }
1066 
1067 // Default implementation - null UndoManager
1068 std::unique_ptr<SdrUndoManager> SdrObjEditView::createLocalTextUndoManager()
1069 {
1070     SAL_WARN("svx", "SdrObjEditView::createLocalTextUndoManager needs to be overridden");
1071     return std::unique_ptr<SdrUndoManager>();
1072 }
1073 
1074 bool SdrObjEditView::SdrBeginTextEdit(SdrObject* pObj_, SdrPageView* pPV, vcl::Window* pWin,
1075                                       bool bIsNewObj, SdrOutliner* pGivenOutliner,
1076                                       OutlinerView* pGivenOutlinerView, bool bDontDeleteOutliner,
1077                                       bool bOnlyOneView, bool bGrabFocus)
1078 {
1079     // FIXME cannot be an assert() yet, the code is not ready for that;
1080     // eg. press F7 in Impress when you are inside a text object with spelling
1081     // mistakes => boom; and it is unclear how to avoid that
1082     SAL_WARN_IF(IsTextEdit(), "svx", "SdrBeginTextEdit called when IsTextEdit() is already true.");
1083     // FIXME this encourages all sorts of bad habits and should be removed
1084     SdrEndTextEdit();
1085 
1086     SdrTextObj* pObj = dynamic_cast<SdrTextObj*>(pObj_);
1087     if (!pObj)
1088         return false; // currently only possible with text objects
1089 
1090     if (bGrabFocus && pWin)
1091     {
1092         // attention, this call may cause an EndTextEdit() call to this view
1093         pWin->GrabFocus(); // to force the cursor into the edit view
1094     }
1095 
1096     mbTextEditDontDelete = bDontDeleteOutliner && pGivenOutliner != nullptr;
1097     mbTextEditOnlyOneView = bOnlyOneView;
1098     mbTextEditNewObj = bIsNewObj;
1099     const sal_uInt32 nWinCount(PaintWindowCount());
1100 
1101     bool bBrk(false);
1102 
1103     if (!pWin)
1104     {
1105         for (sal_uInt32 i = 0; i < nWinCount && !pWin; i++)
1106         {
1107             SdrPaintWindow* pPaintWindow = GetPaintWindow(i);
1108 
1109             if (OUTDEV_WINDOW == pPaintWindow->GetOutputDevice().GetOutDevType())
1110             {
1111                 pWin = pPaintWindow->GetOutputDevice().GetOwnerWindow();
1112             }
1113         }
1114 
1115         // break, when no window exists
1116         if (!pWin)
1117         {
1118             bBrk = true;
1119         }
1120     }
1121 
1122     if (!bBrk && !pPV)
1123     {
1124         pPV = GetSdrPageView();
1125 
1126         // break, when no PageView for the object exists
1127         if (!pPV)
1128         {
1129             bBrk = true;
1130         }
1131     }
1132 
1133     // no TextEdit on objects in locked Layer
1134     if (pPV && pPV->GetLockedLayers().IsSet(pObj->GetLayer()))
1135     {
1136         bBrk = true;
1137     }
1138 
1139     if (mpTextEditOutliner)
1140     {
1141         OSL_FAIL("SdrObjEditView::SdrBeginTextEdit(): Old Outliner still exists.");
1142         mpTextEditOutliner.reset();
1143     }
1144 
1145     if (!bBrk)
1146     {
1147         mpTextEditWin = pWin;
1148         mpTextEditPV = pPV;
1149         mxWeakTextEditObj.reset(pObj);
1150         if (pGivenOutliner)
1151         {
1152             mpTextEditOutliner.reset(pGivenOutliner);
1153             pGivenOutliner = nullptr; // so we don't delete it on the error path
1154         }
1155         else
1156             mpTextEditOutliner = SdrMakeOutliner(OutlinerMode::TextObject,
1157                                                  mxWeakTextEditObj->getSdrModelFromSdrObject());
1158 
1159         {
1160             SvtAccessibilityOptions aOptions;
1161             mpTextEditOutliner->ForceAutoColor(aOptions.GetIsAutomaticFontColor());
1162         }
1163 
1164         aOldCalcFieldValueLink = mpTextEditOutliner->GetCalcFieldValueHdl();
1165         // FieldHdl has to be set by SdrBeginTextEdit, because this call an UpdateFields
1166         mpTextEditOutliner->SetCalcFieldValueHdl(
1167             LINK(this, SdrObjEditView, ImpOutlinerCalcFieldValueHdl));
1168         mpTextEditOutliner->SetBeginPasteOrDropHdl(LINK(this, SdrObjEditView, BeginPasteOrDropHdl));
1169         mpTextEditOutliner->SetEndPasteOrDropHdl(LINK(this, SdrObjEditView, EndPasteOrDropHdl));
1170 
1171         // It is just necessary to make the visualized page known. Set it.
1172         mpTextEditOutliner->setVisualizedPage(pPV->GetPage());
1173 
1174         mpTextEditOutliner->SetTextObjNoInit(mxWeakTextEditObj.get());
1175 
1176         if (mxWeakTextEditObj->BegTextEdit(*mpTextEditOutliner))
1177         {
1178             SdrTextObj* pTextObj = mxWeakTextEditObj.get();
1179             DBG_ASSERT(pTextObj, "svx::SdrObjEditView::BegTextEdit(), no text object?");
1180             if (!pTextObj)
1181                 return false;
1182 
1183             // switch off any running TextAnimations
1184             pTextObj->SetTextAnimationAllowed(false);
1185 
1186             // remember old cursor
1187             if (mpTextEditOutliner->GetViewCount() != 0)
1188             {
1189                 mpTextEditOutliner->RemoveView(static_cast<size_t>(0));
1190             }
1191 
1192             // Determine EditArea via TakeTextEditArea.
1193             // TODO: This could theoretically be left out, because TakeTextRect() calculates the aTextEditArea,
1194             // but aMinTextEditArea has to happen, too (therefore leaving this in right now)
1195             pTextObj->TakeTextEditArea(nullptr, nullptr, &aTextEditArea, &aMinTextEditArea);
1196 
1197             tools::Rectangle aTextRect;
1198             tools::Rectangle aAnchorRect;
1199             pTextObj->TakeTextRect(*mpTextEditOutliner, aTextRect, true,
1200                                    &aAnchorRect /* Give true here, not false */);
1201 
1202             if (!pTextObj->IsContourTextFrame())
1203             {
1204                 // FitToSize not together with ContourFrame, for now
1205                 if (pTextObj->IsFitToSize())
1206                     aTextRect = aAnchorRect;
1207             }
1208 
1209             aTextEditArea = aTextRect;
1210 
1211             // add possible GridOffset to up-to-now view-independent EditAreas
1212             basegfx::B2DVector aGridOffset(0.0, 0.0);
1213             if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj, pPV))
1214             {
1215                 const Point aOffset(basegfx::fround(aGridOffset.getX()),
1216                                     basegfx::fround(aGridOffset.getY()));
1217 
1218                 aTextEditArea += aOffset;
1219                 aMinTextEditArea += aOffset;
1220             }
1221 
1222             Point aPvOfs(pTextObj->GetTextEditOffset());
1223             aTextEditArea.Move(aPvOfs.X(), aPvOfs.Y());
1224             aMinTextEditArea.Move(aPvOfs.X(), aPvOfs.Y());
1225             pTextEditCursorBuffer = pWin->GetCursor();
1226 
1227             maHdlList.SetMoveOutside(true);
1228 
1229             // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary
1230             // to call AdjustMarkHdl() always.
1231             AdjustMarkHdl();
1232 
1233             mpTextEditOutlinerView = ImpMakeOutlinerView(pWin, pGivenOutlinerView);
1234 
1235             if (!comphelper::LibreOfficeKit::isActive() && mpTextEditOutlinerView)
1236             {
1237                 // activate visualization of EditView on Overlay, suppress when
1238                 // LibreOfficeKit is active
1239                 mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(this);
1240 
1241                 const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor());
1242                 const SdrTextObj* pText = GetTextEditObject();
1243                 const bool bTextFrame(pText && pText->IsTextFrame());
1244                 const bool bFitToSize(mpTextEditOutliner->GetControlWord()
1245                                       & EEControlBits::STRETCHING);
1246                 const bool bVisualizeSurroundingFrame(bTextFrame && !bFitToSize);
1247                 SdrPageView* pPageView = GetSdrPageView();
1248 
1249                 if (pPageView)
1250                 {
1251                     for (sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++)
1252                     {
1253                         const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
1254 
1255                         if (rPageWindow.GetPaintWindow().OutputToWindow())
1256                         {
1257                             const rtl::Reference<sdr::overlay::OverlayManager>& xManager
1258                                 = rPageWindow.GetOverlayManager();
1259                             if (xManager.is())
1260                             {
1261                                 std::unique_ptr<TextEditOverlayObject> pNewTextEditOverlayObject(
1262                                     new TextEditOverlayObject(aHilightColor,
1263                                                               *mpTextEditOutlinerView,
1264                                                               bVisualizeSurroundingFrame));
1265 
1266                                 xManager->add(*pNewTextEditOverlayObject);
1267                                 xManager->add(const_cast<sdr::overlay::OverlaySelection&>(
1268                                     *pNewTextEditOverlayObject->getOverlaySelection()));
1269 
1270                                 maTEOverlayGroup.append(std::move(pNewTextEditOverlayObject));
1271                             }
1272                         }
1273                     }
1274                 }
1275             }
1276 
1277             // check if this view is already inserted
1278             size_t i2, nCount = mpTextEditOutliner->GetViewCount();
1279             for (i2 = 0; i2 < nCount; i2++)
1280             {
1281                 if (mpTextEditOutliner->GetView(i2) == mpTextEditOutlinerView)
1282                     break;
1283             }
1284 
1285             if (i2 == nCount)
1286                 mpTextEditOutliner->InsertView(mpTextEditOutlinerView, 0);
1287 
1288             maHdlList.SetMoveOutside(false);
1289             maHdlList.SetMoveOutside(true);
1290 
1291             // register all windows as OutlinerViews with the Outliner
1292             if (!bOnlyOneView)
1293             {
1294                 for (sal_uInt32 i = 0; i < nWinCount; i++)
1295                 {
1296                     SdrPaintWindow* pPaintWindow = GetPaintWindow(i);
1297                     OutputDevice& rOutDev = pPaintWindow->GetOutputDevice();
1298 
1299                     if (&rOutDev != pWin->GetOutDev() && OUTDEV_WINDOW == rOutDev.GetOutDevType())
1300                     {
1301                         OutlinerView* pOutlView
1302                             = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr);
1303                         mpTextEditOutliner->InsertView(pOutlView, static_cast<sal_uInt16>(i));
1304                     }
1305                 }
1306 
1307                 if (comphelper::LibreOfficeKit::isActive())
1308                 {
1309                     // Register an outliner view for all other sdr views that
1310                     // show the same page, so that when the text edit changes,
1311                     // all interested windows get an invalidation.
1312                     SdrViewIter aIter(pObj->getSdrPageFromSdrObject());
1313                     for (SdrView* pView = aIter.FirstView(); pView; pView = aIter.NextView())
1314                     {
1315                         if (pView == this)
1316                             continue;
1317 
1318                         for (sal_uInt32 nViewPaintWindow = 0;
1319                              nViewPaintWindow < pView->PaintWindowCount(); ++nViewPaintWindow)
1320                         {
1321                             SdrPaintWindow* pPaintWindow = pView->GetPaintWindow(nViewPaintWindow);
1322                             OutputDevice& rOutDev = pPaintWindow->GetOutputDevice();
1323 
1324                             if (&rOutDev != pWin->GetOutDev()
1325                                 && OUTDEV_WINDOW == rOutDev.GetOutDevType())
1326                             {
1327                                 OutlinerView* pOutlView
1328                                     = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr);
1329                                 pOutlView->HideCursor();
1330                                 rOutDev.GetOwnerWindow()->SetCursor(nullptr);
1331                                 mpTextEditOutliner->InsertView(pOutlView);
1332                             }
1333                         }
1334                     }
1335                 }
1336             }
1337 
1338             mpTextEditOutlinerView->ShowCursor();
1339             mpTextEditOutliner->SetStatusEventHdl(
1340                 LINK(this, SdrObjEditView, ImpOutlinerStatusEventHdl));
1341             if (pTextObj->IsChainable())
1342             {
1343                 mpTextEditOutlinerView->SetEndCutPasteLinkHdl(
1344                     LINK(this, SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl));
1345             }
1346 
1347             mpTextEditOutliner->ClearModifyFlag();
1348 
1349             if (pTextObj->IsFitToSize())
1350             {
1351                 pWin->Invalidate(aTextEditArea);
1352             }
1353 
1354             if (GetModel())
1355             {
1356                 SdrHint aHint(SdrHintKind::BeginEdit, *pTextObj);
1357                 GetModel()->Broadcast(aHint);
1358             }
1359 
1360             mpTextEditOutliner->setVisualizedPage(nullptr);
1361 
1362             if (mxSelectionController.is())
1363                 mxSelectionController->onSelectionHasChanged();
1364 
1365             if (GetModel() && IsUndoEnabled()
1366                 && !GetModel()->GetDisableTextEditUsesCommonUndoManager())
1367             {
1368                 SdrUndoManager* pSdrUndoManager = nullptr;
1369                 mpLocalTextEditUndoManager = createLocalTextUndoManager();
1370 
1371                 if (mpLocalTextEditUndoManager)
1372                     pSdrUndoManager = mpLocalTextEditUndoManager.get();
1373 
1374                 if (pSdrUndoManager)
1375                 {
1376                     // we have an outliner, undo manager and it's an EditUndoManager, exchange
1377                     // the document undo manager and the default one from the outliner and tell
1378                     // it that text edit starts by setting a callback if it needs to end text edit mode.
1379                     assert(nullptr == mpOldTextEditUndoManager);
1380 
1381                     mpOldTextEditUndoManager = mpTextEditOutliner->SetUndoManager(pSdrUndoManager);
1382                     pSdrUndoManager->SetEndTextEditHdl(LINK(this, SdrObjEditView, EndTextEditHdl));
1383                 }
1384                 else
1385                 {
1386                     OSL_ENSURE(false,
1387                                "The document undo manager is not derived from SdrUndoManager (!)");
1388                 }
1389             }
1390 
1391             return true; // ran fine, let TextEdit run now
1392         }
1393         else
1394         {
1395             mpTextEditOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink);
1396             mpTextEditOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>());
1397             mpTextEditOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>());
1398         }
1399     }
1400     if (mpTextEditOutliner != nullptr)
1401     {
1402         mpTextEditOutliner->setVisualizedPage(nullptr);
1403     }
1404 
1405     // something went wrong...
1406     if (!bDontDeleteOutliner)
1407     {
1408         delete pGivenOutliner;
1409         if (pGivenOutlinerView != nullptr)
1410         {
1411             delete pGivenOutlinerView;
1412             pGivenOutlinerView = nullptr;
1413         }
1414     }
1415     mpTextEditOutliner.reset();
1416 
1417     mpTextEditOutlinerView = nullptr;
1418     mxWeakTextEditObj.reset(nullptr);
1419     mpTextEditPV = nullptr;
1420     mpTextEditWin = nullptr;
1421     maHdlList.SetMoveOutside(false);
1422 
1423     return false;
1424 }
1425 
1426 SdrEndTextEditKind SdrObjEditView::SdrEndTextEdit(bool bDontDeleteReally)
1427 {
1428     SdrEndTextEditKind eRet = SdrEndTextEditKind::Unchanged;
1429     SdrTextObj* pTEObj = mxWeakTextEditObj.get();
1430     vcl::Window* pTEWin = mpTextEditWin;
1431     OutlinerView* pTEOutlinerView = mpTextEditOutlinerView;
1432     vcl::Cursor* pTECursorBuffer = pTextEditCursorBuffer;
1433     SdrUndoManager* pUndoEditUndoManager = nullptr;
1434     bool bNeedToUndoSavedRedoTextEdit(false);
1435 
1436     if (GetModel() && IsUndoEnabled() && pTEObj && mpTextEditOutliner
1437         && !GetModel()->GetDisableTextEditUsesCommonUndoManager())
1438     {
1439         // change back the UndoManager to the remembered original one
1440         SfxUndoManager* pOriginal = mpTextEditOutliner->SetUndoManager(mpOldTextEditUndoManager);
1441         mpOldTextEditUndoManager = nullptr;
1442 
1443         if (pOriginal)
1444         {
1445             // check if we got back our document undo manager
1446             SdrUndoManager* pSdrUndoManager = mpLocalTextEditUndoManager.get();
1447 
1448             if (pSdrUndoManager && dynamic_cast<SdrUndoManager*>(pOriginal) == pSdrUndoManager)
1449             {
1450                 if (pSdrUndoManager->isEndTextEditTriggeredFromUndo())
1451                 {
1452                     // remember the UndoManager where missing Undos have to be triggered after end
1453                     // text edit. When the undo had triggered the end text edit, the original action
1454                     // which had to be undone originally is not yet undone.
1455                     pUndoEditUndoManager = pSdrUndoManager;
1456 
1457                     // We are ending text edit; if text edit was triggered from undo, execute all redos
1458                     // to create a complete text change undo action for the redo buffer. Also mark this
1459                     // state when at least one redo was executed; the created extra TextChange needs to
1460                     // be undone in addition to the first real undo outside the text edit changes
1461                     while (pSdrUndoManager->GetRedoActionCount()
1462                            > pSdrUndoManager->GetRedoActionCountBeforeTextEdit())
1463                     {
1464                         bNeedToUndoSavedRedoTextEdit = true;
1465                         pSdrUndoManager->Redo();
1466                     }
1467                 }
1468 
1469                 // reset the callback link and let the undo manager cleanup all text edit
1470                 // undo actions to get the stack back to the form before the text edit
1471                 pSdrUndoManager->SetEndTextEditHdl(Link<SdrUndoManager*, void>());
1472             }
1473             else
1474             {
1475                 OSL_ENSURE(false, "Got UndoManager back in SdrEndTextEdit which is NOT the "
1476                                   "expected document UndoManager (!)");
1477                 delete pOriginal;
1478             }
1479 
1480             // cid#1493241 - Wrapper object use after free
1481             if (pUndoEditUndoManager == mpLocalTextEditUndoManager.get())
1482                 pUndoEditUndoManager = nullptr;
1483             mpLocalTextEditUndoManager.reset();
1484         }
1485     }
1486     else
1487     {
1488         assert(nullptr == mpOldTextEditUndoManager); // cannot be restored!
1489     }
1490 
1491     if (GetModel() && mxWeakTextEditObj.is())
1492     {
1493         SdrHint aHint(SdrHintKind::EndEdit, *mxWeakTextEditObj);
1494         GetModel()->Broadcast(aHint);
1495     }
1496 
1497     // if new mechanism was used, clean it up. At cleanup no need to check
1498     // for LibreOfficeKit
1499     if (mpTextEditOutlinerView)
1500     {
1501         mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(nullptr);
1502         maTEOverlayGroup.clear();
1503     }
1504 
1505     mxWeakTextEditObj.reset(nullptr);
1506     mpTextEditPV = nullptr;
1507     mpTextEditWin = nullptr;
1508     SdrOutliner* pTEOutliner = mpTextEditOutliner.release();
1509     mpTextEditOutlinerView = nullptr;
1510     pTextEditCursorBuffer = nullptr;
1511     aTextEditArea = tools::Rectangle();
1512 
1513     if (pTEOutliner != nullptr)
1514     {
1515         bool bModified = pTEOutliner->IsModified();
1516         if (pTEOutlinerView != nullptr)
1517         {
1518             pTEOutlinerView->HideCursor();
1519         }
1520         if (pTEObj != nullptr)
1521         {
1522             pTEOutliner->CompleteOnlineSpelling();
1523 
1524             std::unique_ptr<SdrUndoObjSetText> pTxtUndo;
1525 
1526             if (bModified)
1527             {
1528                 sal_Int32 nText;
1529                 for (nText = 0; nText < pTEObj->getTextCount(); ++nText)
1530                     if (pTEObj->getText(nText) == pTEObj->getActiveText())
1531                         break;
1532 
1533                 pTxtUndo.reset(
1534                     dynamic_cast<SdrUndoObjSetText*>(GetModel()
1535                                                          ->GetSdrUndoFactory()
1536                                                          .CreateUndoObjectSetText(*pTEObj, nText)
1537                                                          .release()));
1538             }
1539             DBG_ASSERT(!bModified || pTxtUndo,
1540                        "svx::SdrObjEditView::EndTextEdit(), could not create undo action!");
1541             // Set old CalcFieldValue-Handler again, this
1542             // has to happen before Obj::EndTextEdit(), as this does UpdateFields().
1543             pTEOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink);
1544             pTEOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>());
1545             pTEOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>());
1546 
1547             const bool bUndo = IsUndoEnabled();
1548             if (bUndo)
1549             {
1550                 OUString aObjName(pTEObj->TakeObjNameSingul());
1551                 BegUndo(SvxResId(STR_UndoObjSetText), aObjName);
1552             }
1553 
1554             pTEObj->EndTextEdit(*pTEOutliner);
1555 
1556             if ((pTEObj->GetRotateAngle() != 0_deg100) || (pTEObj && pTEObj->IsFontwork()))
1557             {
1558                 pTEObj->ActionChanged();
1559             }
1560 
1561             if (pTxtUndo != nullptr)
1562             {
1563                 pTxtUndo->AfterSetText();
1564                 if (!pTxtUndo->IsDifferent())
1565                 {
1566                     pTxtUndo.reset();
1567                 }
1568             }
1569             // check deletion of entire TextObj
1570             std::unique_ptr<SdrUndoAction> pDelUndo;
1571             bool bDelObj = false;
1572             if (mbTextEditNewObj)
1573             {
1574                 bDelObj = pTEObj->IsTextFrame() && !pTEObj->HasText() && !pTEObj->IsEmptyPresObj()
1575                           && !pTEObj->HasFill() && !pTEObj->HasLine();
1576 
1577                 if (pTEObj->IsInserted() && bDelObj
1578                     && pTEObj->GetObjInventor() == SdrInventor::Default && !bDontDeleteReally)
1579                 {
1580                     SdrObjKind eIdent = pTEObj->GetObjIdentifier();
1581                     if (eIdent == SdrObjKind::Text)
1582                     {
1583                         pDelUndo = GetModel()->GetSdrUndoFactory().CreateUndoDeleteObject(*pTEObj);
1584                     }
1585                 }
1586             }
1587             if (pTxtUndo)
1588             {
1589                 if (bUndo)
1590                     AddUndo(std::move(pTxtUndo));
1591                 eRet = SdrEndTextEditKind::Changed;
1592             }
1593             if (pDelUndo != nullptr)
1594             {
1595                 if (bUndo)
1596                 {
1597                     AddUndo(std::move(pDelUndo));
1598                 }
1599                 eRet = SdrEndTextEditKind::Deleted;
1600                 DBG_ASSERT(pTEObj->getParentSdrObjListFromSdrObject() != nullptr,
1601                            "SdrObjEditView::SdrEndTextEdit(): Fatal: Object edited doesn't have an "
1602                            "ObjList!");
1603                 if (pTEObj->getParentSdrObjListFromSdrObject() != nullptr)
1604                 {
1605                     pTEObj->getParentSdrObjListFromSdrObject()->RemoveObject(pTEObj->GetOrdNum());
1606                     CheckMarked(); // remove selection immediately...
1607                 }
1608             }
1609             else if (bDelObj)
1610             { // for Writer: the app has to do the deletion itself.
1611                 eRet = SdrEndTextEditKind::ShouldBeDeleted;
1612             }
1613 
1614             if (bUndo)
1615                 EndUndo(); // EndUndo after Remove, in case UndoStack is deleted immediately
1616 
1617             // Switch on any TextAnimation again after TextEdit
1618             if (pTEObj)
1619             {
1620                 pTEObj->SetTextAnimationAllowed(true);
1621             }
1622 
1623             // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary
1624             // to call AdjustMarkHdl() always.
1625             AdjustMarkHdl();
1626         }
1627         // delete all OutlinerViews
1628         for (size_t i = pTEOutliner->GetViewCount(); i > 0;)
1629         {
1630             i--;
1631             OutlinerView* pOLV = pTEOutliner->GetView(i);
1632             sal_uInt16 nMorePix = pOLV->GetInvalidateMore() + 10;
1633             vcl::Window* pWin = pOLV->GetWindow();
1634             tools::Rectangle aRect(pOLV->GetOutputArea());
1635             pTEOutliner->RemoveView(i);
1636             if (!mbTextEditDontDelete || i != 0)
1637             {
1638                 // may not own the zeroth one
1639                 delete pOLV;
1640             }
1641             aRect.Union(aTextEditArea);
1642             aRect.Union(aMinTextEditArea);
1643             aRect = pWin->LogicToPixel(aRect);
1644             aRect.AdjustLeft(-nMorePix);
1645             aRect.AdjustTop(-nMorePix);
1646             aRect.AdjustRight(nMorePix);
1647             aRect.AdjustBottom(nMorePix);
1648             aRect = pWin->PixelToLogic(aRect);
1649             InvalidateOneWin(*pWin->GetOutDev(), aRect);
1650             pWin->GetOutDev()->SetFillColor();
1651             pWin->GetOutDev()->SetLineColor(COL_BLACK);
1652         }
1653         // and now the Outliner itself
1654         if (!mbTextEditDontDelete)
1655             delete pTEOutliner;
1656         else
1657             pTEOutliner->Clear();
1658         if (pTEWin != nullptr)
1659         {
1660             pTEWin->SetCursor(pTECursorBuffer);
1661         }
1662         maHdlList.SetMoveOutside(false);
1663         if (eRet != SdrEndTextEditKind::Unchanged)
1664         {
1665             GetMarkedObjectListWriteAccess().SetNameDirty();
1666         }
1667     }
1668 
1669     if (pTEObj && !pTEObj->getSdrModelFromSdrObject().isLocked() && pTEObj->GetBroadcaster())
1670     {
1671         SdrHint aHint(SdrHintKind::EndEdit, *pTEObj);
1672         const_cast<SfxBroadcaster*>(pTEObj->GetBroadcaster())->Broadcast(aHint);
1673     }
1674 
1675     if (pUndoEditUndoManager)
1676     {
1677         if (bNeedToUndoSavedRedoTextEdit)
1678         {
1679             // undo the text edit action since it was created as part of an EndTextEdit
1680             // callback from undo itself. This needs to be done after the call to
1681             // FmFormView::SdrEndTextEdit since it gets created there
1682             pUndoEditUndoManager->Undo();
1683         }
1684 
1685         // trigger the Undo which was not executed, but lead to this
1686         // end text edit
1687         pUndoEditUndoManager->Undo();
1688     }
1689 
1690     return eRet;
1691 }
1692 
1693 // info about TextEdit. Default is false.
1694 bool SdrObjEditView::IsTextEdit() const { return mxWeakTextEditObj.is(); }
1695 
1696 // info about TextEditPageView. Default is 0L.
1697 SdrPageView* SdrObjEditView::GetTextEditPageView() const { return mpTextEditPV; }
1698 
1699 OutlinerView* SdrObjEditView::ImpFindOutlinerView(vcl::Window const* pWin) const
1700 {
1701     if (pWin == nullptr)
1702         return nullptr;
1703     if (mpTextEditOutliner == nullptr)
1704         return nullptr;
1705     OutlinerView* pNewView = nullptr;
1706     size_t nWinCount = mpTextEditOutliner->GetViewCount();
1707     for (size_t i = 0; i < nWinCount && pNewView == nullptr; i++)
1708     {
1709         OutlinerView* pView = mpTextEditOutliner->GetView(i);
1710         if (pView->GetWindow() == pWin)
1711             pNewView = pView;
1712     }
1713     return pNewView;
1714 }
1715 
1716 void SdrObjEditView::SetTextEditWin(vcl::Window* pWin)
1717 {
1718     if (!(mxWeakTextEditObj.is() && pWin != nullptr && pWin != mpTextEditWin))
1719         return;
1720 
1721     OutlinerView* pNewView = ImpFindOutlinerView(pWin);
1722     if (pNewView != nullptr && pNewView != mpTextEditOutlinerView)
1723     {
1724         if (mpTextEditOutlinerView != nullptr)
1725         {
1726             mpTextEditOutlinerView->HideCursor();
1727         }
1728         mpTextEditOutlinerView = pNewView;
1729         mpTextEditWin = pWin;
1730         pWin->GrabFocus(); // Make the cursor blink here as well
1731         pNewView->ShowCursor();
1732         ImpMakeTextCursorAreaVisible();
1733     }
1734 }
1735 
1736 bool SdrObjEditView::IsTextEditHit(const Point& rHit) const
1737 {
1738     bool bOk = false;
1739     if (mxWeakTextEditObj.is())
1740     {
1741         tools::Rectangle aEditArea;
1742         if (OutlinerView* pOLV = mpTextEditOutliner->GetView(0))
1743             aEditArea.Union(pOLV->GetOutputArea());
1744 
1745         if (aEditArea.Contains(rHit))
1746         { // check if any characters were actually hit
1747             const Point aPnt(rHit - aEditArea.TopLeft());
1748             tools::Long nHitTol = 2000;
1749             if (OutputDevice* pRef = mpTextEditOutliner->GetRefDevice())
1750                 nHitTol = OutputDevice::LogicToLogic(nHitTol, MapUnit::Map100thMM,
1751                                                      pRef->GetMapMode().GetMapUnit());
1752 
1753             bOk = mpTextEditOutliner->IsTextPos(aPnt, static_cast<sal_uInt16>(nHitTol));
1754         }
1755     }
1756     return bOk;
1757 }
1758 
1759 bool SdrObjEditView::IsTextEditFrameHit(const Point& rHit) const
1760 {
1761     bool bOk = false;
1762     if (mxWeakTextEditObj.is())
1763     {
1764         SdrTextObj* pText = mxWeakTextEditObj.get();
1765         OutlinerView* pOLV = mpTextEditOutliner->GetView(0);
1766         if (pOLV)
1767         {
1768             vcl::Window* pWin = pOLV->GetWindow();
1769             if (pText != nullptr && pText->IsTextFrame() && pWin != nullptr)
1770             {
1771                 sal_uInt16 nPixSiz = pOLV->GetInvalidateMore();
1772                 tools::Rectangle aEditArea(aMinTextEditArea);
1773                 aEditArea.Union(pOLV->GetOutputArea());
1774                 if (!aEditArea.Contains(rHit))
1775                 {
1776                     Size aSiz(pWin->PixelToLogic(Size(nPixSiz, nPixSiz)));
1777                     aEditArea.AdjustLeft(-(aSiz.Width()));
1778                     aEditArea.AdjustTop(-(aSiz.Height()));
1779                     aEditArea.AdjustRight(aSiz.Width());
1780                     aEditArea.AdjustBottom(aSiz.Height());
1781                     bOk = aEditArea.Contains(rHit);
1782                 }
1783             }
1784         }
1785     }
1786     return bOk;
1787 }
1788 
1789 std::unique_ptr<TextChainCursorManager>
1790 SdrObjEditView::ImpHandleMotionThroughBoxesKeyInput(const KeyEvent& rKEvt, bool* bOutHandled)
1791 {
1792     *bOutHandled = false;
1793 
1794     SdrTextObj* pTextObj = mxWeakTextEditObj.get();
1795     if (!pTextObj)
1796         return nullptr;
1797 
1798     if (!pTextObj->GetNextLinkInChain() && !pTextObj->GetPrevLinkInChain())
1799         return nullptr;
1800 
1801     std::unique_ptr<TextChainCursorManager> pCursorManager(
1802         new TextChainCursorManager(this, pTextObj));
1803     if (pCursorManager->HandleKeyEvent(rKEvt))
1804     {
1805         // Possibly do other stuff here if necessary...
1806         // XXX: Careful with the checks below (in KeyInput) for pWin and co. You should do them here I guess.
1807         *bOutHandled = true;
1808     }
1809 
1810     return pCursorManager;
1811 }
1812 
1813 bool SdrObjEditView::KeyInput(const KeyEvent& rKEvt, vcl::Window* pWin)
1814 {
1815     if (mpTextEditOutlinerView)
1816     {
1817         /* Start special handling of keys within a chain */
1818         // We possibly move to another box before any handling
1819         bool bHandled = false;
1820         std::unique_ptr<TextChainCursorManager> xCursorManager(
1821             ImpHandleMotionThroughBoxesKeyInput(rKEvt, &bHandled));
1822         if (bHandled)
1823             return true;
1824         /* End special handling of keys within a chain */
1825 
1826         if (mpTextEditOutlinerView->PostKeyEvent(rKEvt, pWin))
1827         {
1828             if (mpModel)
1829             {
1830                 if (mpTextEditOutliner && mpTextEditOutliner->IsModified())
1831                     mpModel->SetChanged();
1832             }
1833 
1834             /* Start chaining processing */
1835             ImpChainingEventHdl();
1836             ImpMoveCursorAfterChainingEvent(xCursorManager.get());
1837             /* End chaining processing */
1838 
1839             if (pWin != nullptr && pWin != mpTextEditWin)
1840                 SetTextEditWin(pWin);
1841             ImpMakeTextCursorAreaVisible();
1842             return true;
1843         }
1844     }
1845     return SdrGlueEditView::KeyInput(rKEvt, pWin);
1846 }
1847 
1848 bool SdrObjEditView::MouseButtonDown(const MouseEvent& rMEvt, OutputDevice* pWin)
1849 {
1850     if (mpTextEditOutlinerView != nullptr)
1851     {
1852         bool bPostIt = mpTextEditOutliner->IsInSelectionMode();
1853         if (!bPostIt)
1854         {
1855             Point aPt(rMEvt.GetPosPixel());
1856             if (pWin != nullptr)
1857                 aPt = pWin->PixelToLogic(aPt);
1858             else if (mpTextEditWin != nullptr)
1859                 aPt = mpTextEditWin->PixelToLogic(aPt);
1860             bPostIt = IsTextEditHit(aPt);
1861         }
1862         if (bPostIt)
1863         {
1864             Point aPixPos(rMEvt.GetPosPixel());
1865             if (pWin)
1866             {
1867                 tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea()));
1868                 if (aPixPos.X() < aR.Left())
1869                     aPixPos.setX(aR.Left());
1870                 if (aPixPos.X() > aR.Right())
1871                     aPixPos.setX(aR.Right());
1872                 if (aPixPos.Y() < aR.Top())
1873                     aPixPos.setY(aR.Top());
1874                 if (aPixPos.Y() > aR.Bottom())
1875                     aPixPos.setY(aR.Bottom());
1876             }
1877             MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(),
1878                              rMEvt.GetModifier());
1879             if (mpTextEditOutlinerView->MouseButtonDown(aMEvt))
1880             {
1881                 if (pWin != nullptr && pWin != mpTextEditWin->GetOutDev()
1882                     && pWin->GetOutDevType() == OUTDEV_WINDOW)
1883                     SetTextEditWin(pWin->GetOwnerWindow());
1884                 ImpMakeTextCursorAreaVisible();
1885                 return true;
1886             }
1887         }
1888     }
1889     return SdrGlueEditView::MouseButtonDown(rMEvt, pWin);
1890 }
1891 
1892 bool SdrObjEditView::MouseButtonUp(const MouseEvent& rMEvt, OutputDevice* pWin)
1893 {
1894     if (mpTextEditOutlinerView != nullptr)
1895     {
1896         bool bPostIt = mpTextEditOutliner->IsInSelectionMode();
1897         if (!bPostIt)
1898         {
1899             Point aPt(rMEvt.GetPosPixel());
1900             if (pWin != nullptr)
1901                 aPt = pWin->PixelToLogic(aPt);
1902             else if (mpTextEditWin != nullptr)
1903                 aPt = mpTextEditWin->PixelToLogic(aPt);
1904             bPostIt = IsTextEditHit(aPt);
1905         }
1906         if (bPostIt && pWin)
1907         {
1908             Point aPixPos(rMEvt.GetPosPixel());
1909             tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea()));
1910             if (aPixPos.X() < aR.Left())
1911                 aPixPos.setX(aR.Left());
1912             if (aPixPos.X() > aR.Right())
1913                 aPixPos.setX(aR.Right());
1914             if (aPixPos.Y() < aR.Top())
1915                 aPixPos.setY(aR.Top());
1916             if (aPixPos.Y() > aR.Bottom())
1917                 aPixPos.setY(aR.Bottom());
1918             MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(),
1919                              rMEvt.GetModifier());
1920             if (mpTextEditOutlinerView->MouseButtonUp(aMEvt))
1921             {
1922                 ImpMakeTextCursorAreaVisible();
1923                 return true;
1924             }
1925         }
1926     }
1927     return SdrGlueEditView::MouseButtonUp(rMEvt, pWin);
1928 }
1929 
1930 bool SdrObjEditView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin)
1931 {
1932     if (mpTextEditOutlinerView != nullptr)
1933     {
1934         bool bSelMode = mpTextEditOutliner->IsInSelectionMode();
1935         bool bPostIt = bSelMode;
1936         if (!bPostIt)
1937         {
1938             Point aPt(rMEvt.GetPosPixel());
1939             if (pWin)
1940                 aPt = pWin->PixelToLogic(aPt);
1941             else if (mpTextEditWin)
1942                 aPt = mpTextEditWin->PixelToLogic(aPt);
1943             bPostIt = IsTextEditHit(aPt);
1944         }
1945         if (bPostIt)
1946         {
1947             Point aPixPos(rMEvt.GetPosPixel());
1948             tools::Rectangle aR(mpTextEditOutlinerView->GetOutputArea());
1949             if (pWin)
1950                 aR = pWin->LogicToPixel(aR);
1951             else if (mpTextEditWin)
1952                 aR = mpTextEditWin->LogicToPixel(aR);
1953             if (aPixPos.X() < aR.Left())
1954                 aPixPos.setX(aR.Left());
1955             if (aPixPos.X() > aR.Right())
1956                 aPixPos.setX(aR.Right());
1957             if (aPixPos.Y() < aR.Top())
1958                 aPixPos.setY(aR.Top());
1959             if (aPixPos.Y() > aR.Bottom())
1960                 aPixPos.setY(aR.Bottom());
1961             MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(),
1962                              rMEvt.GetModifier());
1963             if (mpTextEditOutlinerView->MouseMove(aMEvt) && bSelMode)
1964             {
1965                 ImpMakeTextCursorAreaVisible();
1966                 return true;
1967             }
1968         }
1969     }
1970     return SdrGlueEditView::MouseMove(rMEvt, pWin);
1971 }
1972 
1973 bool SdrObjEditView::Command(const CommandEvent& rCEvt, vcl::Window* pWin)
1974 {
1975     // as long as OutlinerView returns a sal_Bool, it only gets CommandEventId::StartDrag
1976     if (mpTextEditOutlinerView != nullptr)
1977     {
1978         if (rCEvt.GetCommand() == CommandEventId::StartDrag)
1979         {
1980             bool bPostIt = mpTextEditOutliner->IsInSelectionMode() || !rCEvt.IsMouseEvent();
1981             if (!bPostIt && rCEvt.IsMouseEvent())
1982             {
1983                 Point aPt(rCEvt.GetMousePosPixel());
1984                 if (pWin != nullptr)
1985                     aPt = pWin->PixelToLogic(aPt);
1986                 else if (mpTextEditWin != nullptr)
1987                     aPt = mpTextEditWin->PixelToLogic(aPt);
1988                 bPostIt = IsTextEditHit(aPt);
1989             }
1990             if (bPostIt)
1991             {
1992                 Point aPixPos(rCEvt.GetMousePosPixel());
1993                 if (rCEvt.IsMouseEvent() && pWin)
1994                 {
1995                     tools::Rectangle aR(
1996                         pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea()));
1997                     if (aPixPos.X() < aR.Left())
1998                         aPixPos.setX(aR.Left());
1999                     if (aPixPos.X() > aR.Right())
2000                         aPixPos.setX(aR.Right());
2001                     if (aPixPos.Y() < aR.Top())
2002                         aPixPos.setY(aR.Top());
2003                     if (aPixPos.Y() > aR.Bottom())
2004                         aPixPos.setY(aR.Bottom());
2005                 }
2006                 CommandEvent aCEvt(aPixPos, rCEvt.GetCommand(), rCEvt.IsMouseEvent());
2007                 // Command is void at the OutlinerView, sadly
2008                 mpTextEditOutlinerView->Command(aCEvt);
2009                 if (pWin != nullptr && pWin != mpTextEditWin)
2010                     SetTextEditWin(pWin);
2011                 ImpMakeTextCursorAreaVisible();
2012                 return true;
2013             }
2014         }
2015         else
2016         {
2017             mpTextEditOutlinerView->Command(rCEvt);
2018             if (mpModel && comphelper::LibreOfficeKit::isActive())
2019             {
2020                 // It could execute CommandEventId::ExtTextInput, while SdrObjEditView::KeyInput
2021                 // isn't called
2022                 if (mpTextEditOutliner && mpTextEditOutliner->IsModified())
2023                     mpModel->SetChanged();
2024             }
2025             return true;
2026         }
2027     }
2028     return SdrGlueEditView::Command(rCEvt, pWin);
2029 }
2030 
2031 bool SdrObjEditView::ImpIsTextEditAllSelected() const
2032 {
2033     bool bRet = false;
2034     if (mpTextEditOutliner != nullptr && mpTextEditOutlinerView != nullptr)
2035     {
2036         if (SdrTextObj::HasTextImpl(mpTextEditOutliner.get()))
2037         {
2038             const sal_Int32 nParaCnt = mpTextEditOutliner->GetParagraphCount();
2039             Paragraph* pLastPara
2040                 = mpTextEditOutliner->GetParagraph(nParaCnt > 1 ? nParaCnt - 1 : 0);
2041 
2042             ESelection aESel(mpTextEditOutlinerView->GetSelection());
2043             if (aESel.nStartPara == 0 && aESel.nStartPos == 0 && aESel.nEndPara == (nParaCnt - 1))
2044             {
2045                 if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nEndPos)
2046                     bRet = true;
2047             }
2048             // in case the selection was done backwards
2049             if (!bRet && aESel.nEndPara == 0 && aESel.nEndPos == 0
2050                 && aESel.nStartPara == (nParaCnt - 1))
2051             {
2052                 if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nStartPos)
2053                     bRet = true;
2054             }
2055         }
2056         else
2057         {
2058             bRet = true;
2059         }
2060     }
2061     return bRet;
2062 }
2063 
2064 void SdrObjEditView::ImpMakeTextCursorAreaVisible()
2065 {
2066     if (mpTextEditOutlinerView != nullptr && mpTextEditWin != nullptr)
2067     {
2068         vcl::Cursor* pCsr = mpTextEditWin->GetCursor();
2069         if (pCsr != nullptr)
2070         {
2071             Size aSiz(pCsr->GetSize());
2072             if (!aSiz.IsEmpty())
2073             {
2074                 MakeVisible(tools::Rectangle(pCsr->GetPos(), aSiz), *mpTextEditWin);
2075             }
2076         }
2077     }
2078 }
2079 
2080 SvtScriptType SdrObjEditView::GetScriptType() const
2081 {
2082     SvtScriptType nScriptType = SvtScriptType::NONE;
2083 
2084     if (IsTextEdit())
2085     {
2086         if (mxWeakTextEditObj->GetOutlinerParaObject())
2087             nScriptType
2088                 = mxWeakTextEditObj->GetOutlinerParaObject()->GetTextObject().GetScriptType();
2089 
2090         if (mpTextEditOutlinerView)
2091             nScriptType = mpTextEditOutlinerView->GetSelectedScriptType();
2092     }
2093     else
2094     {
2095         const size_t nMarkCount(GetMarkedObjectCount());
2096 
2097         for (size_t i = 0; i < nMarkCount; ++i)
2098         {
2099             OutlinerParaObject* pParaObj = GetMarkedObjectByIndex(i)->GetOutlinerParaObject();
2100 
2101             if (pParaObj)
2102             {
2103                 nScriptType |= pParaObj->GetTextObject().GetScriptType();
2104             }
2105         }
2106     }
2107 
2108     if (nScriptType == SvtScriptType::NONE)
2109         nScriptType = SvtScriptType::LATIN;
2110 
2111     return nScriptType;
2112 }
2113 
2114 void SdrObjEditView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const
2115 {
2116     if (mxSelectionController.is())
2117         if (mxSelectionController->GetAttributes(rTargetSet, bOnlyHardAttr))
2118             return;
2119 
2120     if (IsTextEdit())
2121     {
2122         DBG_ASSERT(mpTextEditOutlinerView != nullptr,
2123                    "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL");
2124         DBG_ASSERT(mpTextEditOutliner != nullptr,
2125                    "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL");
2126 
2127         // take care of bOnlyHardAttr(!)
2128         if (!bOnlyHardAttr && mxWeakTextEditObj->GetStyleSheet())
2129             rTargetSet.Put(mxWeakTextEditObj->GetStyleSheet()->GetItemSet());
2130 
2131         // add object attributes
2132         rTargetSet.Put(mxWeakTextEditObj->GetMergedItemSet());
2133 
2134         if (mpTextEditOutlinerView)
2135         {
2136             // FALSE= regard InvalidItems as "holes," not as Default
2137             rTargetSet.Put(mpTextEditOutlinerView->GetAttribs(), false);
2138         }
2139 
2140         if (GetMarkedObjectCount() == 1 && GetMarkedObjectByIndex(0) == mxWeakTextEditObj.get())
2141         {
2142             MergeNotPersistAttrFromMarked(rTargetSet);
2143         }
2144     }
2145     else
2146     {
2147         SdrGlueEditView::GetAttributes(rTargetSet, bOnlyHardAttr);
2148     }
2149 }
2150 
2151 bool SdrObjEditView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll)
2152 {
2153     bool bRet = false;
2154     bool bTextEdit = mpTextEditOutlinerView != nullptr && mxWeakTextEditObj.is();
2155     bool bAllTextSelected = ImpIsTextEditAllSelected();
2156     const SfxItemSet* pSet = &rSet;
2157 
2158     if (!bTextEdit)
2159     {
2160         // no TextEdit active -> all Items to drawing object
2161         if (mxSelectionController.is())
2162             bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll);
2163 
2164         if (!bRet)
2165         {
2166             SdrGlueEditView::SetAttributes(*pSet, bReplaceAll);
2167             bRet = true;
2168         }
2169     }
2170     else
2171     {
2172 #ifdef DBG_UTIL
2173         {
2174             bool bHasEEFeatureItems = false;
2175             SfxItemIter aIter(rSet);
2176             for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem;
2177                  pItem = aIter.NextItem())
2178             {
2179                 if (!IsInvalidItem(pItem))
2180                 {
2181                     sal_uInt16 nW = pItem->Which();
2182                     if (nW >= EE_FEATURE_START && nW <= EE_FEATURE_END)
2183                         bHasEEFeatureItems = true;
2184                 }
2185             }
2186 
2187             if (bHasEEFeatureItems)
2188             {
2189                 std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(
2190                     nullptr, VclMessageType::Info, VclButtonsType::Ok,
2191                     "SdrObjEditView::SetAttributes(): Setting EE_FEATURE items "
2192                     "at the SdrView does not make sense! It only leads to "
2193                     "overhead and unreadable documents."));
2194                 xInfoBox->run();
2195             }
2196         }
2197 #endif
2198 
2199         bool bOnlyEEItems;
2200         bool bNoEEItems = !SearchOutlinerItems(*pSet, bReplaceAll, &bOnlyEEItems);
2201         // everything selected? -> attributes to the border, too
2202         // if no EEItems, attributes to the border only
2203         if (bAllTextSelected || bNoEEItems)
2204         {
2205             if (mxSelectionController.is())
2206                 bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll);
2207 
2208             if (!bRet)
2209             {
2210                 const bool bUndo = IsUndoEnabled();
2211 
2212                 if (bUndo)
2213                 {
2214                     BegUndo(ImpGetDescriptionString(STR_EditSetAttributes));
2215                     AddUndo(
2216                         GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*mxWeakTextEditObj));
2217 
2218                     // If this is a text object also rescue the OutlinerParaObject since
2219                     // applying attributes to the object may change text layout when
2220                     // multiple portions exist with multiple formats. If an OutlinerParaObject
2221                     // really exists and needs to be rescued is evaluated in the undo
2222                     // implementation itself.
2223                     bool bRescueText = mxWeakTextEditObj;
2224 
2225                     AddUndo(GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(
2226                         *mxWeakTextEditObj, false, !bNoEEItems || bRescueText));
2227                     EndUndo();
2228                 }
2229 
2230                 mxWeakTextEditObj->SetMergedItemSetAndBroadcast(*pSet, bReplaceAll);
2231 
2232                 FlushComeBackTimer(); // to set ModeHasChanged immediately
2233             }
2234         }
2235         else if (!bOnlyEEItems)
2236         {
2237             // Otherwise split Set, if necessary.
2238             // Now we build an ItemSet aSet that doesn't contain EE_Items from
2239             // *pSet (otherwise it would be a copy).
2240             WhichRangesContainer pNewWhichTable
2241                 = RemoveWhichRange(pSet->GetRanges(), EE_ITEMS_START, EE_ITEMS_END);
2242             SfxItemSet aSet(mpModel->GetItemPool(), std::move(pNewWhichTable));
2243             SfxWhichIter aIter(aSet);
2244             sal_uInt16 nWhich = aIter.FirstWhich();
2245             while (nWhich != 0)
2246             {
2247                 const SfxPoolItem* pItem;
2248                 SfxItemState eState = pSet->GetItemState(nWhich, false, &pItem);
2249                 if (eState == SfxItemState::SET)
2250                     aSet.Put(*pItem);
2251                 nWhich = aIter.NextWhich();
2252             }
2253 
2254             if (mxSelectionController.is())
2255                 bRet = mxSelectionController->SetAttributes(aSet, bReplaceAll);
2256 
2257             if (!bRet)
2258             {
2259                 if (IsUndoEnabled())
2260                 {
2261                     BegUndo(ImpGetDescriptionString(STR_EditSetAttributes));
2262                     AddUndo(
2263                         GetModel()->GetSdrUndoFactory().CreateUndoGeoObject(*mxWeakTextEditObj));
2264                     AddUndo(
2265                         GetModel()->GetSdrUndoFactory().CreateUndoAttrObject(*mxWeakTextEditObj));
2266                     EndUndo();
2267                 }
2268 
2269                 mxWeakTextEditObj->SetMergedItemSetAndBroadcast(aSet, bReplaceAll);
2270 
2271                 if (GetMarkedObjectCount() == 1
2272                     && GetMarkedObjectByIndex(0) == mxWeakTextEditObj.get())
2273                 {
2274                     SetNotPersistAttrToMarked(aSet);
2275                 }
2276             }
2277             FlushComeBackTimer();
2278         }
2279         if (!bNoEEItems)
2280         {
2281             // and now the attributes to the EditEngine
2282             if (bReplaceAll)
2283             {
2284                 mpTextEditOutlinerView->RemoveAttribs(true);
2285             }
2286             mpTextEditOutlinerView->SetAttribs(rSet);
2287 
2288             Outliner* pTEOutliner = mpTextEditOutlinerView->GetOutliner();
2289             if (mpModel && pTEOutliner && pTEOutliner->IsModified())
2290                 mpModel->SetChanged();
2291 
2292             ImpMakeTextCursorAreaVisible();
2293         }
2294         bRet = true;
2295     }
2296     return bRet;
2297 }
2298 
2299 SfxStyleSheet* SdrObjEditView::GetStyleSheet() const
2300 {
2301     SfxStyleSheet* pSheet = nullptr;
2302 
2303     if (mxSelectionController.is())
2304     {
2305         if (mxSelectionController->GetStyleSheet(pSheet))
2306             return pSheet;
2307     }
2308 
2309     if (mpTextEditOutlinerView)
2310     {
2311         pSheet = mpTextEditOutlinerView->GetStyleSheet();
2312     }
2313     else
2314     {
2315         pSheet = SdrGlueEditView::GetStyleSheet();
2316     }
2317     return pSheet;
2318 }
2319 
2320 void SdrObjEditView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr)
2321 {
2322     if (mxSelectionController.is())
2323     {
2324         if (mxSelectionController->SetStyleSheet(pStyleSheet, bDontRemoveHardAttr))
2325             return;
2326     }
2327 
2328     // if we are currently in edit mode we must also set the stylesheet
2329     // on all paragraphs in the Outliner for the edit view
2330     if (nullptr != mpTextEditOutlinerView)
2331     {
2332         Outliner* pOutliner = mpTextEditOutlinerView->GetOutliner();
2333 
2334         const sal_Int32 nParaCount = pOutliner->GetParagraphCount();
2335         for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++)
2336         {
2337             pOutliner->SetStyleSheet(nPara, pStyleSheet);
2338         }
2339     }
2340 
2341     SdrGlueEditView::SetStyleSheet(pStyleSheet, bDontRemoveHardAttr);
2342 }
2343 
2344 void SdrObjEditView::AddWindowToPaintView(OutputDevice* pNewWin, vcl::Window* pWindow)
2345 {
2346     SdrGlueEditView::AddWindowToPaintView(pNewWin, pWindow);
2347 
2348     if (mxWeakTextEditObj.is() && !mbTextEditOnlyOneView
2349         && pNewWin->GetOutDevType() == OUTDEV_WINDOW)
2350     {
2351         OutlinerView* pOutlView = ImpMakeOutlinerView(pNewWin->GetOwnerWindow(), nullptr);
2352         mpTextEditOutliner->InsertView(pOutlView);
2353     }
2354 }
2355 
2356 void SdrObjEditView::DeleteWindowFromPaintView(OutputDevice* pOldWin)
2357 {
2358     SdrGlueEditView::DeleteWindowFromPaintView(pOldWin);
2359 
2360     if (mxWeakTextEditObj.is() && !mbTextEditOnlyOneView
2361         && pOldWin->GetOutDevType() == OUTDEV_WINDOW)
2362     {
2363         for (size_t i = mpTextEditOutliner->GetViewCount(); i > 0;)
2364         {
2365             i--;
2366             OutlinerView* pOLV = mpTextEditOutliner->GetView(i);
2367             if (pOLV && pOLV->GetWindow() == pOldWin->GetOwnerWindow())
2368             {
2369                 mpTextEditOutliner->RemoveView(i);
2370             }
2371         }
2372     }
2373 
2374     lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), pOldWin);
2375 }
2376 
2377 bool SdrObjEditView::IsTextEditInSelectionMode() const
2378 {
2379     return mpTextEditOutliner != nullptr && mpTextEditOutliner->IsInSelectionMode();
2380 }
2381 
2382 // MacroMode
2383 
2384 void SdrObjEditView::BegMacroObj(const Point& rPnt, short nTol, SdrObject* pObj, SdrPageView* pPV,
2385                                  vcl::Window* pWin)
2386 {
2387     BrkMacroObj();
2388     if (pObj != nullptr && pPV != nullptr && pWin != nullptr && pObj->HasMacro())
2389     {
2390         nTol = ImpGetHitTolLogic(nTol, nullptr);
2391         pMacroObj = pObj;
2392         pMacroPV = pPV;
2393         pMacroWin = pWin;
2394         mbMacroDown = false;
2395         nMacroTol = sal_uInt16(nTol);
2396         aMacroDownPos = rPnt;
2397         MovMacroObj(rPnt);
2398     }
2399 }
2400 
2401 void SdrObjEditView::ImpMacroUp(const Point& rUpPos)
2402 {
2403     if (pMacroObj != nullptr && mbMacroDown)
2404     {
2405         SdrObjMacroHitRec aHitRec;
2406         aHitRec.aPos = rUpPos;
2407         aHitRec.nTol = nMacroTol;
2408         aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers();
2409         aHitRec.pPageView = pMacroPV;
2410         pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec);
2411         mbMacroDown = false;
2412     }
2413 }
2414 
2415 void SdrObjEditView::ImpMacroDown(const Point& rDownPos)
2416 {
2417     if (pMacroObj != nullptr && !mbMacroDown)
2418     {
2419         SdrObjMacroHitRec aHitRec;
2420         aHitRec.aPos = rDownPos;
2421         aHitRec.nTol = nMacroTol;
2422         aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers();
2423         aHitRec.pPageView = pMacroPV;
2424         pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec);
2425         mbMacroDown = true;
2426     }
2427 }
2428 
2429 void SdrObjEditView::MovMacroObj(const Point& rPnt)
2430 {
2431     if (pMacroObj == nullptr)
2432         return;
2433 
2434     SdrObjMacroHitRec aHitRec;
2435     aHitRec.aPos = rPnt;
2436     aHitRec.nTol = nMacroTol;
2437     aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers();
2438     aHitRec.pPageView = pMacroPV;
2439     bool bDown = pMacroObj->IsMacroHit(aHitRec);
2440     if (bDown)
2441         ImpMacroDown(rPnt);
2442     else
2443         ImpMacroUp(rPnt);
2444 }
2445 
2446 void SdrObjEditView::BrkMacroObj()
2447 {
2448     if (pMacroObj != nullptr)
2449     {
2450         ImpMacroUp(aMacroDownPos);
2451         pMacroObj = nullptr;
2452         pMacroPV = nullptr;
2453         pMacroWin = nullptr;
2454     }
2455 }
2456 
2457 bool SdrObjEditView::EndMacroObj()
2458 {
2459     if (pMacroObj != nullptr && mbMacroDown)
2460     {
2461         ImpMacroUp(aMacroDownPos);
2462         SdrObjMacroHitRec aHitRec;
2463         aHitRec.aPos = aMacroDownPos;
2464         aHitRec.nTol = nMacroTol;
2465         aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers();
2466         aHitRec.pPageView = pMacroPV;
2467         bool bRet = pMacroObj->DoMacro(aHitRec);
2468         pMacroObj = nullptr;
2469         pMacroPV = nullptr;
2470         pMacroWin = nullptr;
2471         return bRet;
2472     }
2473     else
2474     {
2475         BrkMacroObj();
2476         return false;
2477     }
2478 }
2479 
2480 /** fills the given any with a XTextCursor for the current text selection.
2481     Leaves the any untouched if there currently is no text selected */
2482 void SdrObjEditView::getTextSelection(css::uno::Any& rSelection)
2483 {
2484     if (!IsTextEdit())
2485         return;
2486 
2487     OutlinerView* pOutlinerView = GetTextEditOutlinerView();
2488     if (!(pOutlinerView && pOutlinerView->HasSelection()))
2489         return;
2490 
2491     SdrObject* pObj = GetTextEditObject();
2492 
2493     if (!pObj)
2494         return;
2495 
2496     css::uno::Reference<css::text::XText> xText(pObj->getUnoShape(), css::uno::UNO_QUERY);
2497     if (xText.is())
2498     {
2499         SvxUnoTextBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextBase>(xText);
2500         if (pRange)
2501         {
2502             rSelection <<= pRange->createTextCursorBySelection(pOutlinerView->GetSelection());
2503         }
2504     }
2505 }
2506 
2507 /* check if we have a single selection and that single object likes
2508     to handle the mouse and keyboard events itself
2509 
2510     TODO: the selection controller should be queried from the
2511     object specific view contact. Currently this method only
2512     works for tables.
2513 */
2514 void SdrObjEditView::MarkListHasChanged()
2515 {
2516     SdrGlueEditView::MarkListHasChanged();
2517 
2518     if (mxSelectionController.is())
2519     {
2520         mxLastSelectionController = mxSelectionController;
2521         mxSelectionController->onSelectionHasChanged();
2522     }
2523 
2524     mxSelectionController.clear();
2525 
2526     const SdrMarkList& rMarkList = GetMarkedObjectList();
2527     if (rMarkList.GetMarkCount() != 1)
2528         return;
2529 
2530     const SdrObject* pObj(rMarkList.GetMark(0)->GetMarkedSdrObj());
2531     SdrView* pView(dynamic_cast<SdrView*>(this));
2532 
2533     // check for table
2534     if (pObj && pView && (pObj->GetObjInventor() == SdrInventor::Default)
2535         && (pObj->GetObjIdentifier() == SdrObjKind::Table))
2536     {
2537         mxSelectionController = sdr::table::CreateTableController(
2538             *pView, static_cast<const sdr::table::SdrTableObj&>(*pObj), mxLastSelectionController);
2539 
2540         if (mxSelectionController.is())
2541         {
2542             mxLastSelectionController.clear();
2543             mxSelectionController->onSelectionHasChanged();
2544         }
2545     }
2546 }
2547 
2548 IMPL_LINK(SdrObjEditView, EndPasteOrDropHdl, PasteOrDropInfos*, pInfo, void)
2549 {
2550     OnEndPasteOrDrop(pInfo);
2551 }
2552 
2553 IMPL_LINK(SdrObjEditView, BeginPasteOrDropHdl, PasteOrDropInfos*, pInfo, void)
2554 {
2555     OnBeginPasteOrDrop(pInfo);
2556 }
2557 
2558 void SdrObjEditView::OnBeginPasteOrDrop(PasteOrDropInfos*)
2559 {
2560     // applications can derive from these virtual methods to do something before a drop or paste operation
2561 }
2562 
2563 void SdrObjEditView::OnEndPasteOrDrop(PasteOrDropInfos*)
2564 {
2565     // applications can derive from these virtual methods to do something before a drop or paste operation
2566 }
2567 
2568 sal_uInt16 SdrObjEditView::GetSelectionLevel() const
2569 {
2570     sal_uInt16 nLevel = 0xFFFF;
2571     if (IsTextEdit())
2572     {
2573         DBG_ASSERT(mpTextEditOutlinerView != nullptr,
2574                    "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL");
2575         DBG_ASSERT(mpTextEditOutliner != nullptr,
2576                    "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL");
2577         if (mpTextEditOutlinerView)
2578         {
2579             //start and end position
2580             ESelection aSelect = mpTextEditOutlinerView->GetSelection();
2581             sal_uInt16 nStartPara = ::std::min(aSelect.nStartPara, aSelect.nEndPara);
2582             sal_uInt16 nEndPara = ::std::max(aSelect.nStartPara, aSelect.nEndPara);
2583             //get level from each paragraph
2584             nLevel = 0;
2585             for (sal_uInt16 nPara = nStartPara; nPara <= nEndPara; nPara++)
2586             {
2587                 sal_uInt16 nParaDepth
2588                     = 1 << static_cast<sal_uInt16>(mpTextEditOutliner->GetDepth(nPara));
2589                 if (!(nLevel & nParaDepth))
2590                     nLevel += nParaDepth;
2591             }
2592             //reduce one level for Outliner Object
2593             //if( nLevel > 0 && GetTextEditObject()->GetObjIdentifier() == OBJ_OUTLINETEXT )
2594             //  nLevel = nLevel >> 1;
2595             //no bullet paragraph selected
2596             if (nLevel == 0)
2597                 nLevel = 0xFFFF;
2598         }
2599     }
2600     return nLevel;
2601 }
2602 
2603 bool SdrObjEditView::SupportsFormatPaintbrush(SdrInventor nObjectInventor,
2604                                               SdrObjKind nObjectIdentifier)
2605 {
2606     if (nObjectInventor != SdrInventor::Default && nObjectInventor != SdrInventor::E3d)
2607         return false;
2608     switch (nObjectIdentifier)
2609     {
2610         case SdrObjKind::NONE:
2611         case SdrObjKind::Group:
2612             return false;
2613         case SdrObjKind::Line:
2614         case SdrObjKind::Rectangle:
2615         case SdrObjKind::CircleOrEllipse:
2616         case SdrObjKind::CircleSection:
2617         case SdrObjKind::CircleArc:
2618         case SdrObjKind::CircleCut:
2619         case SdrObjKind::Polygon:
2620         case SdrObjKind::PolyLine:
2621         case SdrObjKind::PathLine:
2622         case SdrObjKind::PathFill:
2623         case SdrObjKind::FreehandLine:
2624         case SdrObjKind::FreehandFill:
2625         case SdrObjKind::SplineLine:
2626         case SdrObjKind::SplineFill:
2627         case SdrObjKind::Text:
2628         case SdrObjKind::TitleText:
2629         case SdrObjKind::OutlineText:
2630         case SdrObjKind::Graphic:
2631         case SdrObjKind::OLE2:
2632         case SdrObjKind::Table:
2633             return true;
2634         case SdrObjKind::Edge:
2635         case SdrObjKind::Caption:
2636             return false;
2637         case SdrObjKind::PathPoly:
2638         case SdrObjKind::PathPolyLine:
2639             return true;
2640         case SdrObjKind::Page:
2641         case SdrObjKind::Measure:
2642         case SdrObjKind::OLEPluginFrame:
2643         case SdrObjKind::UNO:
2644             return false;
2645         case SdrObjKind::CustomShape:
2646             return true;
2647         default:
2648             return false;
2649     }
2650 }
2651 
2652 static const WhichRangesContainer& GetFormatRangeImpl(bool bTextOnly)
2653 {
2654     static const WhichRangesContainer gFull(
2655         svl::Items<XATTR_LINE_FIRST, XATTR_LINE_LAST, XATTR_FILL_FIRST, XATTRSET_FILL,
2656                    SDRATTR_SHADOW_FIRST, SDRATTR_SHADOW_LAST, SDRATTR_MISC_FIRST,
2657                    SDRATTR_MISC_LAST, // table cell formats
2658                    SDRATTR_GRAF_FIRST, SDRATTR_GRAF_LAST, SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST,
2659                    EE_PARA_START, EE_PARA_END, EE_CHAR_START, EE_CHAR_END>);
2660 
2661     static const WhichRangesContainer gTextOnly(
2662         svl::Items<SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, EE_PARA_START, EE_PARA_END, EE_CHAR_START,
2663                    EE_CHAR_END>);
2664 
2665     return bTextOnly ? gTextOnly : gFull;
2666 }
2667 
2668 void SdrObjEditView::TakeFormatPaintBrush(std::shared_ptr<SfxItemSet>& rFormatSet)
2669 {
2670     const SdrMarkList& rMarkList = GetMarkedObjectList();
2671     if (rMarkList.GetMarkCount() <= 0)
2672         return;
2673 
2674     OutlinerView* pOLV = GetTextEditOutlinerView();
2675 
2676     rFormatSet = std::make_shared<SfxItemSet>(GetModel()->GetItemPool(),
2677                                               GetFormatRangeImpl(pOLV != nullptr));
2678     if (pOLV)
2679     {
2680         rFormatSet->Put(pOLV->GetAttribs());
2681     }
2682     else
2683     {
2684         const bool bOnlyHardAttr = false;
2685         rFormatSet->Put(GetAttrFromMarked(bOnlyHardAttr));
2686     }
2687 
2688     // check for cloning from table cell, in which case we need to copy cell-specific formatting attributes
2689     const SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
2690     if (pObj && (pObj->GetObjInventor() == SdrInventor::Default)
2691         && (pObj->GetObjIdentifier() == SdrObjKind::Table))
2692     {
2693         auto pTable = static_cast<const sdr::table::SdrTableObj*>(pObj);
2694         if (mxSelectionController.is() && pTable->getActiveCell().is())
2695         {
2696             mxSelectionController->GetAttributes(*rFormatSet, false);
2697         }
2698     }
2699 }
2700 
2701 static SfxItemSet CreatePaintSet(const WhichRangesContainer& pRanges, SfxItemPool& rPool,
2702                                  const SfxItemSet& rSourceSet, const SfxItemSet& rTargetSet,
2703                                  bool bNoCharacterFormats, bool bNoParagraphFormats)
2704 {
2705     SfxItemSet aPaintSet(rPool, pRanges);
2706 
2707     for (const auto& pRange : pRanges)
2708     {
2709         sal_uInt16 nWhich = pRange.first;
2710         const sal_uInt16 nLastWhich = pRange.second;
2711 
2712         if (bNoCharacterFormats && (nWhich == EE_CHAR_START))
2713             continue;
2714 
2715         if (bNoParagraphFormats && (nWhich == EE_PARA_START))
2716             continue;
2717 
2718         for (; nWhich < nLastWhich; nWhich++)
2719         {
2720             const SfxPoolItem* pSourceItem = rSourceSet.GetItem(nWhich);
2721             const SfxPoolItem* pTargetItem = rTargetSet.GetItem(nWhich);
2722 
2723             if ((pSourceItem && !pTargetItem)
2724                 || (pSourceItem && pTargetItem && *pSourceItem != *pTargetItem))
2725             {
2726                 aPaintSet.Put(*pSourceItem);
2727             }
2728         }
2729     }
2730     return aPaintSet;
2731 }
2732 
2733 void SdrObjEditView::ApplyFormatPaintBrushToText(SfxItemSet const& rFormatSet, SdrTextObj& rTextObj,
2734                                                  SdrText* pText, bool bNoCharacterFormats,
2735                                                  bool bNoParagraphFormats)
2736 {
2737     OutlinerParaObject* pParaObj = pText ? pText->GetOutlinerParaObject() : nullptr;
2738     if (!pParaObj)
2739         return;
2740 
2741     SdrOutliner& rOutliner = rTextObj.ImpGetDrawOutliner();
2742     rOutliner.SetText(*pParaObj);
2743 
2744     sal_Int32 nParaCount(rOutliner.GetParagraphCount());
2745 
2746     if (!nParaCount)
2747         return;
2748 
2749     for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++)
2750     {
2751         if (!bNoCharacterFormats)
2752             rOutliner.RemoveCharAttribs(nPara);
2753 
2754         SfxItemSet aSet(rOutliner.GetParaAttribs(nPara));
2755         aSet.Put(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(), rFormatSet, aSet,
2756                                 bNoCharacterFormats, bNoParagraphFormats));
2757         rOutliner.SetParaAttribs(nPara, aSet);
2758     }
2759 
2760     std::optional<OutlinerParaObject> pTemp = rOutliner.CreateParaObject(0, nParaCount);
2761     rOutliner.Clear();
2762 
2763     rTextObj.NbcSetOutlinerParaObjectForText(std::move(pTemp), pText);
2764 }
2765 
2766 void SdrObjEditView::DisposeUndoManager()
2767 {
2768     if (mpTextEditOutliner)
2769     {
2770         if (typeid(mpTextEditOutliner->GetUndoManager()) != typeid(EditUndoManager))
2771         {
2772             // Non-owning pointer, clear it.
2773             mpTextEditOutliner->SetUndoManager(nullptr);
2774         }
2775     }
2776 
2777     mpOldTextEditUndoManager = nullptr;
2778 }
2779 
2780 void SdrObjEditView::ApplyFormatPaintBrush(SfxItemSet& rFormatSet, bool bNoCharacterFormats,
2781                                            bool bNoParagraphFormats)
2782 {
2783     if (mxSelectionController.is()
2784         && mxSelectionController->ApplyFormatPaintBrush(rFormatSet, bNoCharacterFormats,
2785                                                         bNoParagraphFormats))
2786     {
2787         return;
2788     }
2789 
2790     OutlinerView* pOLV = GetTextEditOutlinerView();
2791     const SdrMarkList& rMarkList = GetMarkedObjectList();
2792     if (!pOLV)
2793     {
2794         SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
2795         const SfxItemSet& rShapeSet = pObj->GetMergedItemSet();
2796 
2797         // if not in text edit mode (aka the user selected text or clicked on a word)
2798         // apply formatting attributes to selected shape
2799         // All formatting items (see ranges above) that are unequal in selected shape and
2800         // the format paintbrush are hard set on the selected shape.
2801 
2802         const WhichRangesContainer& pRanges = rFormatSet.GetRanges();
2803         bool bTextOnly = true;
2804 
2805         for (const auto& pRange : pRanges)
2806         {
2807             if ((pRange.first != EE_PARA_START) && (pRange.first != EE_CHAR_START))
2808             {
2809                 bTextOnly = false;
2810                 break;
2811             }
2812         }
2813 
2814         if (!bTextOnly)
2815         {
2816             SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(false), *rShapeSet.GetPool(),
2817                                                 rFormatSet, rShapeSet, bNoCharacterFormats,
2818                                                 bNoParagraphFormats));
2819             SetAttrToMarked(aPaintSet, false /*bReplaceAll*/);
2820         }
2821 
2822         // now apply character and paragraph formatting to text, if the shape has any
2823         SdrTextObj* pTextObj = dynamic_cast<SdrTextObj*>(pObj);
2824         if (pTextObj)
2825         {
2826             sal_Int32 nText = pTextObj->getTextCount();
2827 
2828             while (--nText >= 0)
2829             {
2830                 SdrText* pText = pTextObj->getText(nText);
2831                 ApplyFormatPaintBrushToText(rFormatSet, *pTextObj, pText, bNoCharacterFormats,
2832                                             bNoParagraphFormats);
2833             }
2834         }
2835     }
2836     else
2837     {
2838         ::Outliner* pOutliner = pOLV->GetOutliner();
2839         if (pOutliner)
2840         {
2841             const EditEngine& rEditEngine = pOutliner->GetEditEngine();
2842 
2843             ESelection aSel(pOLV->GetSelection());
2844             if (!aSel.HasRange())
2845                 pOLV->SetSelection(rEditEngine.GetWord(aSel, css::i18n::WordType::DICTIONARY_WORD));
2846 
2847             const bool bRemoveParaAttribs = !bNoParagraphFormats;
2848             pOLV->RemoveAttribsKeepLanguages(bRemoveParaAttribs);
2849             SfxItemSet aSet(pOLV->GetAttribs());
2850             SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(),
2851                                                 rFormatSet, aSet, bNoCharacterFormats,
2852                                                 bNoParagraphFormats));
2853             pOLV->SetAttribs(aPaintSet);
2854         }
2855     }
2856 
2857     // check for cloning to table cell, in which case we need to copy cell-specific formatting attributes
2858     SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();
2859     if (pObj && (pObj->GetObjInventor() == SdrInventor::Default)
2860         && (pObj->GetObjIdentifier() == SdrObjKind::Table))
2861     {
2862         auto pTable = static_cast<sdr::table::SdrTableObj*>(pObj);
2863         if (pTable->getActiveCell().is() && mxSelectionController.is())
2864         {
2865             mxSelectionController->SetAttributes(rFormatSet, false);
2866         }
2867     }
2868 }
2869 
2870 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2871