diff options
Diffstat (limited to 'svx/source/svdraw/svdedxv.cxx')
-rw-r--r-- | svx/source/svdraw/svdedxv.cxx | 3002 |
1 files changed, 3002 insertions, 0 deletions
diff --git a/svx/source/svdraw/svdedxv.cxx b/svx/source/svdraw/svdedxv.cxx new file mode 100644 index 0000000000..c2d35cc75b --- /dev/null +++ b/svx/source/svdraw/svdedxv.cxx @@ -0,0 +1,3002 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/i18n/WordType.hpp> +#include <editeng/editdata.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/unotext.hxx> +#include <o3tl/deleter.hxx> +#include <svl/itemiter.hxx> +#include <svl/style.hxx> +#include <svl/whiter.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svx/sdtfchim.hxx> +#include <svx/selectioncontroller.hxx> +#include <svx/svdedxv.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdotable.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdundo.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/cursor.hxx> +#include <vcl/weld.hxx> +#include <vcl/window.hxx> +#include <comphelper/lok.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <editeng/outliner.hxx> +#include <sal/log.hxx> +#include <sdr/overlay/overlaytools.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/dialmgr.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/sdr/overlay/overlayselection.hxx> +#include <svx/sdr/table/tablecontroller.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdrundomanager.hxx> +#include <svx/strings.hrc> +#include <svx/svdviter.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <textchain.hxx> +#include <textchaincursor.hxx> +#include <tools/debug.hxx> +#include <vcl/svapp.hxx> + +#include <memory> + +SdrObjEditView::SdrObjEditView(SdrModel& rSdrModel, OutputDevice* pOut) + : SdrGlueEditView(rSdrModel, pOut) + , mpTextEditPV(nullptr) + , mpTextEditOutlinerView(nullptr) + , mpTextEditWin(nullptr) + , pTextEditCursorBuffer(nullptr) + , pMacroObj(nullptr) + , pMacroPV(nullptr) + , pMacroWin(nullptr) + , nMacroTol(0) + , mbTextEditDontDelete(false) + , mbTextEditOnlyOneView(false) + , mbTextEditNewObj(false) + , mbQuickTextEditMode(true) + , mbMacroDown(false) + , mpOldTextEditUndoManager(nullptr) +{ +} + +SdrObjEditView::~SdrObjEditView() +{ + mpTextEditWin = nullptr; // so there's no ShowCursor in SdrEndTextEdit + assert(!IsTextEdit()); + if (IsTextEdit()) + suppress_fun_call_w_exception(SdrEndTextEdit()); + mpTextEditOutliner.reset(); + assert(nullptr == mpOldTextEditUndoManager); // should have been reset +} + +bool SdrObjEditView::IsAction() const { return IsMacroObj() || SdrGlueEditView::IsAction(); } + +void SdrObjEditView::MovAction(const Point& rPnt) +{ + if (IsMacroObj()) + MovMacroObj(rPnt); + SdrGlueEditView::MovAction(rPnt); +} + +void SdrObjEditView::EndAction() +{ + if (IsMacroObj()) + EndMacroObj(); + SdrGlueEditView::EndAction(); +} + +void SdrObjEditView::BckAction() +{ + BrkMacroObj(); + SdrGlueEditView::BckAction(); +} + +void SdrObjEditView::BrkAction() +{ + BrkMacroObj(); + SdrGlueEditView::BrkAction(); +} + +SdrPageView* SdrObjEditView::ShowSdrPage(SdrPage* pPage) +{ + SdrPageView* pPageView = SdrGlueEditView::ShowSdrPage(pPage); + + if (comphelper::LibreOfficeKit::isActive() && pPageView) + { + // Check if other views have an active text edit on the same page as + // this one. + SdrViewIter::ForAllViews(pPageView->GetPage(), [this](SdrView* pView) { + if (pView == this || !pView->IsTextEdit()) + return; + + OutputDevice* pOutDev = GetFirstOutputDevice(); + if (!pOutDev || pOutDev->GetOutDevType() != OUTDEV_WINDOW) + return; + + // Found one, so create an outliner view, to get invalidations when + // the text edit changes. + // Call GetSfxViewShell() to make sure ImpMakeOutlinerView() + // registers the view shell of this draw view, and not the view + // shell of pView. + OutlinerView* pOutlinerView + = pView->ImpMakeOutlinerView(pOutDev->GetOwnerWindow(), nullptr, GetSfxViewShell()); + pOutlinerView->HideCursor(); + pView->GetTextEditOutliner()->InsertView(pOutlinerView); + }); + } + + return pPageView; +} + +namespace +{ +/// Removes outliner views registered in other draw views that use pOutputDevice. +void lcl_RemoveTextEditOutlinerViews(SdrObjEditView const* pThis, SdrPageView const* pPageView, + OutputDevice const* pOutputDevice) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if (!pPageView) + return; + + if (!pOutputDevice || pOutputDevice->GetOutDevType() != OUTDEV_WINDOW) + return; + + SdrViewIter::ForAllViews(pPageView->GetPage(), [&pThis, &pOutputDevice](SdrView* pView) { + if (pView == pThis || !pView->IsTextEdit()) + return; + + SdrOutliner* pOutliner = pView->GetTextEditOutliner(); + for (size_t nView = 0; nView < pOutliner->GetViewCount(); ++nView) + { + OutlinerView* pOutlinerView = pOutliner->GetView(nView); + if (pOutlinerView->GetWindow()->GetOutDev() != pOutputDevice) + continue; + + pOutliner->RemoveView(pOutlinerView); + delete pOutlinerView; + } + }); +} +} + +void SdrObjEditView::HideSdrPage() +{ + lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), GetFirstOutputDevice()); + + if (mpTextEditPV == GetSdrPageView()) + { + // HideSdrPage() will clear mpPageView, avoid a dangling pointer. + mpTextEditPV = nullptr; + } + + SdrGlueEditView::HideSdrPage(); +} + +void SdrObjEditView::TakeActionRect(tools::Rectangle& rRect) const +{ + if (IsMacroObj()) + { + rRect = pMacroObj->GetCurrentBoundRect(); + } + else + { + SdrGlueEditView::TakeActionRect(rRect); + } +} + +void SdrObjEditView::Notify(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + SdrGlueEditView::Notify(rBC, rHint); + if (mpTextEditOutliner == nullptr) + return; + + // change of printer while editing + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint); + SdrHintKind eKind = pSdrHint->GetKind(); + if (eKind == SdrHintKind::RefDeviceChange) + { + mpTextEditOutliner->SetRefDevice(GetModel().GetRefDevice()); + } + if (eKind == SdrHintKind::DefaultTabChange) + { + mpTextEditOutliner->SetDefTab(GetModel().GetDefaultTabulator()); + } +} + +void SdrObjEditView::ModelHasChanged() +{ + SdrGlueEditView::ModelHasChanged(); + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj && !pTextObj->IsInserted()) + SdrEndTextEdit(); // object deleted + // TextEditObj changed? + if (!IsTextEdit()) + return; + + if (pTextObj != nullptr) + { + size_t nOutlViewCnt = mpTextEditOutliner->GetViewCount(); + bool bAreaChg = false; + bool bAnchorChg = false; + bool bColorChg = false; + bool bContourFrame = pTextObj->IsContourTextFrame(); + EEAnchorMode eNewAnchor(EEAnchorMode::VCenterHCenter); + tools::Rectangle aOldArea(aMinTextEditArea); + aOldArea.Union(aTextEditArea); + Color aNewColor; + { // check area + Size aPaperMin1; + Size aPaperMax1; + tools::Rectangle aEditArea1; + tools::Rectangle aMinArea1; + pTextObj->TakeTextEditArea(&aPaperMin1, &aPaperMax1, &aEditArea1, &aMinArea1); + Point aPvOfs(pTextObj->GetTextEditOffset()); + + // add possible GridOffset to up-to-now view-independent EditAreas + basegfx::B2DVector aGridOffset(0.0, 0.0); + if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj.get(), GetSdrPageView())) + { + const Point aOffset(basegfx::fround(aGridOffset.getX()), + basegfx::fround(aGridOffset.getY())); + + aEditArea1 += aOffset; + aMinArea1 += aOffset; + } + + aEditArea1.Move(aPvOfs.X(), aPvOfs.Y()); + aMinArea1.Move(aPvOfs.X(), aPvOfs.Y()); + tools::Rectangle aNewArea(aMinArea1); + aNewArea.Union(aEditArea1); + + if (aNewArea != aOldArea || aEditArea1 != aTextEditArea || aMinArea1 != aMinTextEditArea + || mpTextEditOutliner->GetMinAutoPaperSize() != aPaperMin1 + || mpTextEditOutliner->GetMaxAutoPaperSize() != aPaperMax1) + { + aTextEditArea = aEditArea1; + aMinTextEditArea = aMinArea1; + + const bool bPrevUpdateLayout = mpTextEditOutliner->SetUpdateLayout(false); + mpTextEditOutliner->SetMinAutoPaperSize(aPaperMin1); + mpTextEditOutliner->SetMaxAutoPaperSize(aPaperMax1); + mpTextEditOutliner->SetPaperSize(Size(0, 0)); // re-format Outliner + + if (!bContourFrame) + { + mpTextEditOutliner->ClearPolygon(); + EEControlBits nStat = mpTextEditOutliner->GetControlWord(); + nStat |= EEControlBits::AUTOPAGESIZE; + mpTextEditOutliner->SetControlWord(nStat); + } + else + { + EEControlBits nStat = mpTextEditOutliner->GetControlWord(); + nStat &= ~EEControlBits::AUTOPAGESIZE; + mpTextEditOutliner->SetControlWord(nStat); + tools::Rectangle aAnchorRect; + pTextObj->TakeTextAnchorRect(aAnchorRect); + pTextObj->ImpSetContourPolygon(*mpTextEditOutliner, aAnchorRect, true); + } + for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV); + EVControlBits nStat0 = pOLV->GetControlWord(); + EVControlBits nStat = nStat0; + // AutoViewSize only if not ContourFrame. + if (!bContourFrame) + nStat |= EVControlBits::AUTOSIZE; + else + nStat &= ~EVControlBits::AUTOSIZE; + if (nStat != nStat0) + pOLV->SetControlWord(nStat); + } + + mpTextEditOutliner->SetUpdateLayout(bPrevUpdateLayout); + bAreaChg = true; + } + } + if (mpTextEditOutlinerView != nullptr) + { // check fill and anchor + EEAnchorMode eOldAnchor = mpTextEditOutlinerView->GetAnchorMode(); + eNewAnchor = pTextObj->GetOutlinerViewAnchorMode(); + bAnchorChg = eOldAnchor != eNewAnchor; + Color aOldColor(mpTextEditOutlinerView->GetBackgroundColor()); + aNewColor = GetTextEditBackgroundColor(*this); + bColorChg = aOldColor != aNewColor; + } + // refresh always when it's a contour frame. That + // refresh is necessary since it triggers the repaint + // which makes the Handles visible. Changes at TakeTextRect() + // seem to have resulted in a case where no refresh is executed. + // Before that, a refresh must have been always executed + // (else this error would have happened earlier), thus I + // even think here a refresh should be done always. + // Since follow-up problems cannot even be guessed I only + // add this one more case to the if below. + // BTW: It's VERY bad style that here, inside ModelHasChanged() + // the outliner is again massively changed for the text object + // in text edit mode. Normally, all necessary data should be + // set at SdrBeginTextEdit(). Some changes and value assigns in + // SdrBeginTextEdit() are completely useless since they are set here + // again on ModelHasChanged(). + if (bContourFrame || bAreaChg || bAnchorChg || bColorChg) + { + for (size_t nOV = 0; nOV < nOutlViewCnt; nOV++) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(nOV); + { // invalidate old OutlinerView area + vcl::Window* pWin = pOLV->GetWindow(); + tools::Rectangle aTmpRect(aOldArea); + sal_uInt16 nPixSiz = pOLV->GetInvalidateMore() + 1; + Size aMore(pWin->PixelToLogic(Size(nPixSiz, nPixSiz))); + aTmpRect.AdjustLeft(-(aMore.Width())); + aTmpRect.AdjustRight(aMore.Width()); + aTmpRect.AdjustTop(-(aMore.Height())); + aTmpRect.AdjustBottom(aMore.Height()); + InvalidateOneWin(*pWin->GetOutDev(), aTmpRect); + } + if (bAnchorChg) + pOLV->SetAnchorMode(eNewAnchor); + if (bColorChg) + pOLV->SetBackgroundColor(aNewColor); + + pOLV->SetOutputArea( + aTextEditArea); // because otherwise, we're not re-anchoring correctly + ImpInvalidateOutlinerView(*pOLV); + } + mpTextEditOutlinerView->ShowCursor(); + } + } + ImpMakeTextCursorAreaVisible(); +} + +namespace +{ +class TextEditFrameOverlayObject; +class TextEditHighContrastOverlaySelection; + +/** + Helper class to visualize the content of an active EditView as an + OverlayObject. These objects work with Primitives and are handled + from the OverlayManager(s) in place as needed. + + It allows complete visualization of the content of the active + EditView without the need of Invalidates triggered by the EditView + and thus avoiding potentially expensive repaints by using the + automatically buffered Overlay mechanism. + + It buffers as much as possible locally and *only* triggers a real + change (see call to objectChange()) when really needed. + */ +class TextEditOverlayObject : public sdr::overlay::OverlayObject +{ +protected: + /// local access to associated sdr::overlay::OverlaySelection + std::unique_ptr<sdr::overlay::OverlaySelection> mxOverlayTransparentSelection; + std::unique_ptr<TextEditHighContrastOverlaySelection> mxOverlayHighContrastSelection; + std::unique_ptr<TextEditFrameOverlayObject> mxOverlayFrame; + + /// local definition depends on active OutlinerView + OutlinerView& mrOutlinerView; + + /// geometry definitions with buffering + basegfx::B2DRange maLastRange; + basegfx::B2DRange maRange; + + /// text content definitions with buffering + drawinglayer::primitive2d::Primitive2DContainer maTextPrimitives; + drawinglayer::primitive2d::Primitive2DContainer maLastTextPrimitives; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView); + virtual ~TextEditOverlayObject() override; + + sdr::overlay::OverlayObject* getOverlaySelection(); + sdr::overlay::OverlayObject* getOverlayFrame(); + + const OutlinerView& getOutlinerView() const { return mrOutlinerView; } + + /// override to check conditions for last createOverlayObjectPrimitive2DSequence + virtual drawinglayer::primitive2d::Primitive2DContainer + getOverlayObjectPrimitive2DSequence() const override; + + // data write access. In this OverlayObject we only have the + // callback that triggers detecting if something *has* changed + void checkDataChange(const basegfx::B2DRange& rMinTextEditArea); + void checkSelectionChange(); + + const basegfx::B2DRange& getRange() const { return maRange; } + const drawinglayer::primitive2d::Primitive2DContainer& getTextPrimitives() const + { + return maTextPrimitives; + } +}; + +class TextEditFrameOverlayObject : public sdr::overlay::OverlayObject +{ +private: + const TextEditOverlayObject& mrTextEditOverlayObject; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditFrameOverlayObject(const TextEditOverlayObject& rTextEditOverlayObject); + using sdr::overlay::OverlayObject::objectChange; + virtual ~TextEditFrameOverlayObject() override; +}; + +class TextEditHighContrastOverlaySelection : public sdr::overlay::OverlayObject +{ +private: + const TextEditOverlayObject& mrTextEditOverlayObject; + std::vector<basegfx::B2DRange> maRanges; + + // geometry creation for OverlayObject, can use local *Last* values + virtual drawinglayer::primitive2d::Primitive2DContainer + createOverlayObjectPrimitive2DSequence() override; + +public: + TextEditHighContrastOverlaySelection(const TextEditOverlayObject& rTextEditOverlayObject); + void setRanges(std::vector<basegfx::B2DRange>&& rNew); + virtual ~TextEditHighContrastOverlaySelection() override; +}; + +TextEditHighContrastOverlaySelection::TextEditHighContrastOverlaySelection( + const TextEditOverlayObject& rTextEditOverlayObject) + : OverlayObject(rTextEditOverlayObject.getBaseColor()) + , mrTextEditOverlayObject(rTextEditOverlayObject) +{ + allowAntiAliase(rTextEditOverlayObject.allowsAntiAliase()); + // use selection colors in HighContrast mode + mbHighContrastSelection = true; +} + +void TextEditHighContrastOverlaySelection::setRanges(std::vector<basegfx::B2DRange>&& rNew) +{ + if (rNew != maRanges) + { + maRanges = std::move(rNew); + objectChange(); + } +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditHighContrastOverlaySelection::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + size_t nCount = maRanges.size(); + + if (nCount) + { + basegfx::B2DPolyPolygon aClipPolyPolygon; + + basegfx::BColor aRGBColor(getBaseColor().getBColor()); + + for (size_t a = 0; a < nCount; ++a) + aClipPolyPolygon.append(basegfx::utils::createPolygonFromRect(maRanges[a])); + + // This is used in high contrast mode, we will render the selection + // with the bg forced to the selection Highlight color and the fg color + // forced to the HighlightText color + aRetval.append(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(aClipPolyPolygon.getB2DRange())), + aRGBColor))); + aRetval.append(mrTextEditOverlayObject.getTextPrimitives()); + aRetval.append(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::MaskPrimitive2D(aClipPolyPolygon, std::move(aRetval)))); + } + + return aRetval; +} + +TextEditHighContrastOverlaySelection::~TextEditHighContrastOverlaySelection() +{ + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +sdr::overlay::OverlayObject* TextEditOverlayObject::getOverlaySelection() +{ + if (mxOverlayTransparentSelection) + return mxOverlayTransparentSelection.get(); + return mxOverlayHighContrastSelection.get(); +} + +sdr::overlay::OverlayObject* TextEditOverlayObject::getOverlayFrame() +{ + if (!mxOverlayFrame) + mxOverlayFrame.reset(new TextEditFrameOverlayObject(*this)); + return mxOverlayFrame.get(); +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + // add buffered TextPrimitives + aRetval.append(maTextPrimitives); + + return aRetval; +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditFrameOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + /// outer frame visualization + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + const sal_uInt16 nPixSiz(mrTextEditOverlayObject.getOutlinerView().GetInvalidateMore() - 1); + + aRetval.push_back(new drawinglayer::primitive2d::OverlayRectanglePrimitive( + mrTextEditOverlayObject.getRange(), getBaseColor().getBColor(), fTransparence, + std::max(6, nPixSiz - 2), // grow + 0.0, // shrink + 0.0)); + + return aRetval; +} + +TextEditOverlayObject::TextEditOverlayObject(const Color& rColor, OutlinerView& rOutlinerView) + : OverlayObject(rColor) + , mrOutlinerView(rOutlinerView) +{ + // no AA for TextEdit overlay + allowAntiAliase(false); + + // create local OverlaySelection - this is an integral part of EditText + // visualization + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + mxOverlayHighContrastSelection.reset(new TextEditHighContrastOverlaySelection(*this)); + } + else + { + std::vector<basegfx::B2DRange> aEmptySelection{}; + mxOverlayTransparentSelection.reset(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, rColor, std::move(aEmptySelection), true)); + } +} + +TextEditOverlayObject::~TextEditOverlayObject() +{ + mxOverlayTransparentSelection.reset(); + mxOverlayHighContrastSelection.reset(); + + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +TextEditFrameOverlayObject::TextEditFrameOverlayObject( + const TextEditOverlayObject& rTextEditOverlayObject) + : OverlayObject(rTextEditOverlayObject.getBaseColor()) + , mrTextEditOverlayObject(rTextEditOverlayObject) +{ + allowAntiAliase(rTextEditOverlayObject.allowsAntiAliase()); + // use selection colors in HighContrast mode + mbHighContrastSelection = true; +} + +TextEditFrameOverlayObject::~TextEditFrameOverlayObject() +{ + if (getOverlayManager()) + { + getOverlayManager()->remove(*this); + } +} + +drawinglayer::primitive2d::Primitive2DContainer +TextEditOverlayObject::getOverlayObjectPrimitive2DSequence() const +{ + if (!getPrimitive2DSequence().empty()) + { + if (!maRange.equal(maLastRange) || maLastTextPrimitives != maTextPrimitives) + { + // conditions of last local decomposition have changed, delete to force new evaluation + const_cast<TextEditOverlayObject*>(this)->resetPrimitive2DSequence(); + } + } + + if (getPrimitive2DSequence().empty()) + { + // remember new buffered values + const_cast<TextEditOverlayObject*>(this)->maLastRange = maRange; + const_cast<TextEditOverlayObject*>(this)->maLastTextPrimitives = maTextPrimitives; + } + + // call base implementation + return OverlayObject::getOverlayObjectPrimitive2DSequence(); +} + +void TextEditOverlayObject::checkDataChange(const basegfx::B2DRange& rMinTextEditArea) +{ + bool bObjectChange(false); + + // check current range + const tools::Rectangle aOutArea(mrOutlinerView.GetOutputArea()); + basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aOutArea); + aNewRange.expand(rMinTextEditArea); + + if (aNewRange != maRange) + { + maRange = aNewRange; + bObjectChange = true; + } + + // check if text primitives did change + SdrOutliner* pSdrOutliner = dynamic_cast<SdrOutliner*>(getOutlinerView().GetOutliner()); + + if (pSdrOutliner) + { + // get TextPrimitives directly from active Outliner + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + basegfx::B2DRange aClipRange; + drawinglayer::primitive2d::Primitive2DContainer aNewTextPrimitives; + + // active Outliner is always in unified oriented coordinate system (currently) + // so just translate to TopLeft of visible Range. Keep in mind that top-left + // depends on vertical text and top-to-bottom text attributes + const tools::Rectangle aVisArea(mrOutlinerView.GetVisArea()); + const bool bVerticalWriting(pSdrOutliner->IsVertical()); + const bool bTopToBottom(pSdrOutliner->IsTopToBottom()); + const double fStartInX(bVerticalWriting && bTopToBottom + ? aOutArea.Right() - aVisArea.Left() + : aOutArea.Left() - aVisArea.Left()); + const double fStartInY(bVerticalWriting && !bTopToBottom + ? aOutArea.Bottom() - aVisArea.Top() + : aOutArea.Top() - aVisArea.Top()); + + aNewTransformB.translate(fStartInX, fStartInY); + + // get the current TextPrimitives. This is the most expensive part + // of this mechanism, it *may* be possible to buffer layouted + // primitives per ParaPortion with/in/dependent on the EditEngine + // content if needed. For now, get and compare + SdrTextObj::impDecomposeBlockTextPrimitiveDirect( + aNewTextPrimitives, *pSdrOutliner, aNewTransformA, aNewTransformB, aClipRange); + + if (aNewTextPrimitives != maTextPrimitives) + { + maTextPrimitives = std::move(aNewTextPrimitives); + bObjectChange = true; + } + } + + if (bObjectChange) + { + // if there really *was* a change signal the OverlayManager to + // refresh this object's visualization + objectChange(); + + if (mxOverlayFrame) + mxOverlayFrame->objectChange(); + + // on data change, always do a SelectionChange, too + // since the selection is an integral part of text visualization + checkSelectionChange(); + } +} + +void TextEditOverlayObject::checkSelectionChange() +{ + if (!(getOverlaySelection() && getOverlayManager())) + return; + + std::vector<tools::Rectangle> aLogicRects; + std::vector<basegfx::B2DRange> aLogicRanges; + const Size aLogicPixel(getOverlayManager()->getOutputDevice().PixelToLogic(Size(1, 1))); + + // get logic selection + getOutlinerView().GetSelectionRectangles(aLogicRects); + + aLogicRanges.reserve(aLogicRects.size()); + for (const auto& aRect : aLogicRects) + { + // convert from logic Rectangles to logic Ranges, do not forget to add + // one Unit (in this case logical units for one pixel, pre-calculated) + aLogicRanges.emplace_back( + aRect.Left() - aLogicPixel.Width(), aRect.Top() - aLogicPixel.Height(), + aRect.Right() + aLogicPixel.Width(), aRect.Bottom() + aLogicPixel.Height()); + } + + if (mxOverlayTransparentSelection) + mxOverlayTransparentSelection->setRanges(std::move(aLogicRanges)); + else + mxOverlayHighContrastSelection->setRanges(std::move(aLogicRanges)); +} +} // end of anonymous namespace + +// TextEdit + +// callback from the active EditView, forward to evtl. existing instances of the +// TextEditOverlayObject(s). This will additionally update the selection which +// is an integral part of the text visualization +void SdrObjEditView::EditViewInvalidate(const tools::Rectangle&) +{ + if (!IsTextEdit()) + return; + + // MinTextRange may have changed. Forward it, too + const basegfx::B2DRange aMinTextRange + = vcl::unotools::b2DRectangleFromRectangle(aMinTextEditArea); + + for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++) + { + TextEditOverlayObject* pCandidate + = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a)); + + if (pCandidate) + { + pCandidate->checkDataChange(aMinTextRange); + } + } +} + +// callback from the active EditView, forward to evtl. existing instances of the +// TextEditOverlayObject(s). This cvall *only* updates the selection visualization +// which is e.g. used when only the selection is changed, but not the text +void SdrObjEditView::EditViewSelectionChange() +{ + if (!IsTextEdit()) + return; + + for (sal_uInt32 a(0); a < maTEOverlayGroup.count(); a++) + { + TextEditOverlayObject* pCandidate + = dynamic_cast<TextEditOverlayObject*>(&maTEOverlayGroup.getOverlayObject(a)); + + if (pCandidate) + { + pCandidate->checkSelectionChange(); + } + } +} + +OutputDevice& SdrObjEditView::EditViewOutputDevice() const { return *mpTextEditWin->GetOutDev(); } + +Point SdrObjEditView::EditViewPointerPosPixel() const +{ + return mpTextEditWin->GetPointerPosPixel(); +} + +css::uno::Reference<css::datatransfer::clipboard::XClipboard> SdrObjEditView::GetClipboard() const +{ + if (!mpTextEditWin) + return nullptr; + return mpTextEditWin->GetClipboard(); +} + +css::uno::Reference<css::datatransfer::dnd::XDropTarget> SdrObjEditView::GetDropTarget() +{ + if (!mpTextEditWin) + return nullptr; + return mpTextEditWin->GetDropTarget(); +} + +void SdrObjEditView::EditViewInputContext(const InputContext& rInputContext) +{ + if (!mpTextEditWin) + return; + mpTextEditWin->SetInputContext(rInputContext); +} + +void SdrObjEditView::EditViewCursorRect(const tools::Rectangle& rRect, int nExtTextInputWidth) +{ + if (!mpTextEditWin) + return; + mpTextEditWin->SetCursorRect(&rRect, nExtTextInputWidth); +} + +void SdrObjEditView::TextEditDrawing(SdrPaintWindow& rPaintWindow) +{ + if (!comphelper::LibreOfficeKit::isActive()) + { + // adapt all TextEditOverlayObject(s), so call EditViewInvalidate() + // to update accordingly (will update selection, too). Suppress new + // stuff when LibreOfficeKit is active + EditViewInvalidate(tools::Rectangle()); + } + else + { + // draw old text edit stuff + if (IsTextEdit()) + { + const SdrOutliner* pActiveOutliner = GetTextEditOutliner(); + + if (pActiveOutliner) + { + const sal_uInt32 nViewCount(pActiveOutliner->GetViewCount()); + + if (nViewCount) + { + const vcl::Region& rRedrawRegion = rPaintWindow.GetRedrawRegion(); + const tools::Rectangle aCheckRect(rRedrawRegion.GetBoundRect()); + + for (sal_uInt32 i(0); i < nViewCount; i++) + { + OutlinerView* pOLV = pActiveOutliner->GetView(i); + + // If rPaintWindow knows that the output device is a render + // context and is aware of the underlying vcl::Window, + // compare against that; that's how double-buffering can + // still find the matching OutlinerView. + OutputDevice* pOutputDevice = rPaintWindow.GetWindow() + ? rPaintWindow.GetWindow()->GetOutDev() + : &rPaintWindow.GetOutputDevice(); + if (pOLV->GetWindow()->GetOutDev() == pOutputDevice + || comphelper::LibreOfficeKit::isActive()) + { + ImpPaintOutlinerView(*pOLV, aCheckRect, + rPaintWindow.GetTargetOutputDevice()); + return; + } + } + } + } + } + } +} + +void SdrObjEditView::ImpPaintOutlinerView(OutlinerView& rOutlView, const tools::Rectangle& rRect, + OutputDevice& rTargetDevice) const +{ + const SdrTextObj* pText = GetTextEditObject(); + bool bTextFrame(pText && pText->IsTextFrame()); + bool bFitToSize(mpTextEditOutliner->GetControlWord() & EEControlBits::STRETCHING); + bool bModified(mpTextEditOutliner->IsModified()); + tools::Rectangle aBlankRect(rOutlView.GetOutputArea()); + aBlankRect.Union(aMinTextEditArea); + tools::Rectangle aPixRect(rTargetDevice.LogicToPixel(aBlankRect)); + + // in the tiled rendering case, the setup is incomplete, and we very + // easily get an empty rRect on input - that will cause that everything is + // clipped; happens in case of editing text inside a shape in Calc. + // FIXME would be better to complete the setup so that we don't get an + // empty rRect here + if (!comphelper::LibreOfficeKit::isActive() || !rRect.IsEmpty()) + aBlankRect.Intersection(rRect); + + rOutlView.GetOutliner()->SetUpdateLayout(true); // Bugfix #22596# + rOutlView.Paint(aBlankRect, &rTargetDevice); + + if (!bModified) + { + mpTextEditOutliner->ClearModifyFlag(); + } + + if (bTextFrame && !bFitToSize) + { + // completely reworked to use primitives; this ensures same look and functionality + const drawinglayer::geometry::ViewInformation2D aViewInformation2D; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> xProcessor( + drawinglayer::processor2d::createProcessor2DFromOutputDevice(rTargetDevice, + aViewInformation2D)); + + const bool bMapModeEnabled(rTargetDevice.IsMapModeEnabled()); + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aPixRect); + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const double fTransparence(SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01); + const sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1); + const drawinglayer::primitive2d::Primitive2DReference xReference( + new drawinglayer::primitive2d::OverlayRectanglePrimitive( + aRange, aHilightColor.getBColor(), fTransparence, std::max(6, nPixSiz - 2), // grow + 0.0, // shrink + 0.0)); + const drawinglayer::primitive2d::Primitive2DContainer aSequence{ xReference }; + + rTargetDevice.EnableMapMode(false); + xProcessor->process(aSequence); + rTargetDevice.EnableMapMode(bMapModeEnabled); + } + + rOutlView.ShowCursor(/*bGotoCursor=*/true, /*bActivate=*/true); +} + +void SdrObjEditView::ImpInvalidateOutlinerView(OutlinerView const& rOutlView) const +{ + vcl::Window* pWin = rOutlView.GetWindow(); + + if (!pWin) + return; + + const SdrTextObj* pText = GetTextEditObject(); + bool bTextFrame(pText && pText->IsTextFrame()); + bool bFitToSize(pText && pText->IsFitToSize()); + + if (!bTextFrame || bFitToSize) + return; + + tools::Rectangle aBlankRect(rOutlView.GetOutputArea()); + aBlankRect.Union(aMinTextEditArea); + tools::Rectangle aPixRect(pWin->LogicToPixel(aBlankRect)); + sal_uInt16 nPixSiz(rOutlView.GetInvalidateMore() - 1); + + aPixRect.AdjustLeft(-1); + aPixRect.AdjustTop(-1); + aPixRect.AdjustRight(1); + aPixRect.AdjustBottom(1); + + { + // limit xPixRect because of driver problems when pixel coordinates are too far out + Size aMaxXY(pWin->GetOutputSizePixel()); + tools::Long a(2 * nPixSiz); + tools::Long nMaxX(aMaxXY.Width() + a); + tools::Long nMaxY(aMaxXY.Height() + a); + + if (aPixRect.Left() < -a) + aPixRect.SetLeft(-a); + if (aPixRect.Top() < -a) + aPixRect.SetTop(-a); + if (aPixRect.Right() > nMaxX) + aPixRect.SetRight(nMaxX); + if (aPixRect.Bottom() > nMaxY) + aPixRect.SetBottom(nMaxY); + } + + tools::Rectangle aOuterPix(aPixRect); + aOuterPix.AdjustLeft(-nPixSiz); + aOuterPix.AdjustTop(-nPixSiz); + aOuterPix.AdjustRight(nPixSiz); + aOuterPix.AdjustBottom(nPixSiz); + + bool bMapModeEnabled(pWin->IsMapModeEnabled()); + pWin->EnableMapMode(false); + pWin->Invalidate(aOuterPix); + pWin->EnableMapMode(bMapModeEnabled); +} + +OutlinerView* SdrObjEditView::ImpMakeOutlinerView(vcl::Window* pWin, OutlinerView* pGivenView, + SfxViewShell* pViewShell) const +{ + // background + Color aBackground(GetTextEditBackgroundColor(*this)); + rtl::Reference<SdrTextObj> pText = mxWeakTextEditObj.get(); + bool bTextFrame = pText != nullptr && pText->IsTextFrame(); + bool bContourFrame = pText != nullptr && pText->IsContourTextFrame(); + // create OutlinerView + OutlinerView* pOutlView = pGivenView; + mpTextEditOutliner->SetUpdateLayout(false); + + if (pOutlView == nullptr) + { + pOutlView = new OutlinerView(mpTextEditOutliner.get(), pWin); + } + else + { + pOutlView->SetWindow(pWin); + } + + if (mbNegativeX) + pOutlView->GetEditView().SetNegativeX(mbNegativeX); + + // disallow scrolling + EVControlBits nStat = pOutlView->GetControlWord(); + nStat &= ~EVControlBits::AUTOSCROLL; + // AutoViewSize only if not ContourFrame. + if (!bContourFrame) + nStat |= EVControlBits::AUTOSIZE; + if (bTextFrame) + { + sal_uInt16 nPixSiz = maHdlList.GetHdlSize() * 2 + 1; + nStat |= EVControlBits::INVONEMORE; + pOutlView->SetInvalidateMore(nPixSiz); + } + pOutlView->SetControlWord(nStat); + pOutlView->SetBackgroundColor(aBackground); + + // In case we're in the process of constructing a new view shell, + // SfxViewShell::Current() may still point to the old one. So if possible, + // depend on the application owning this draw view to provide the view + // shell. + SfxViewShell* pSfxViewShell = pViewShell ? pViewShell : GetSfxViewShell(); + pOutlView->RegisterViewShell(pSfxViewShell ? pSfxViewShell : SfxViewShell::Current()); + + if (pText != nullptr) + { + pOutlView->SetAnchorMode(pText->GetOutlinerViewAnchorMode()); + mpTextEditOutliner->SetFixedCellHeight( + pText->GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue()); + } + // do update before setting output area so that aTextEditArea can be recalculated + mpTextEditOutliner->SetUpdateLayout(true); + pOutlView->SetOutputArea(aTextEditArea); + ImpInvalidateOutlinerView(*pOutlView); + return pOutlView; +} + +IMPL_LINK(SdrObjEditView, ImpOutlinerStatusEventHdl, EditStatus&, rEditStat, void) +{ + if (mpTextEditOutliner) + { + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj) + { + pTextObj->onEditOutlinerStatusEvent(&rEditStat); + } + } +} + +void SdrObjEditView::ImpChainingEventHdl() +{ + if (!mpTextEditOutliner) + return; + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + OutlinerView* pOLV = GetTextEditOutlinerView(); + if (pTextObj && pOLV) + { + TextChain* pTextChain = pTextObj->GetTextChain(); + + // XXX: IsChainable and GetNilChainingEvent are a bit mixed up atm + if (!pTextObj->IsChainable()) + { + return; + } + // This is true during an underflow-caused overflow (with pEdtOutl->SetText()) + if (pTextChain->GetNilChainingEvent(pTextObj.get())) + { + return; + } + + // We prevent to trigger further handling of overflow/underflow for pTextObj + pTextChain->SetNilChainingEvent(pTextObj.get(), true); // XXX + + // Save previous selection pos // NOTE: It must be done to have the right CursorEvent in KeyInput + pTextChain->SetPreChainingSel(pTextObj.get(), pOLV->GetSelection()); + //maPreChainingSel = new ESelection(pOLV->GetSelection()); + + // Handling Undo + const int nText = 0; // XXX: hardcoded index (SdrTextObj::getText handles only 0) + + const bool bUndoEnabled = IsUndoEnabled(); + std::unique_ptr<SdrUndoObjSetText> pTxtUndo; + if (bUndoEnabled) + pTxtUndo.reset( + dynamic_cast<SdrUndoObjSetText*>(GetModel() + .GetSdrUndoFactory() + .CreateUndoObjectSetText(*pTextObj, nText) + .release())); + + // trigger actual chaining + pTextObj->onChainingEvent(); + + if (pTxtUndo) + { + pTxtUndo->AfterSetText(); + if (!pTxtUndo->IsDifferent()) + { + pTxtUndo.reset(); + } + } + + if (pTxtUndo) + AddUndo(std::move(pTxtUndo)); + + //maCursorEvent = new CursorChainingEvent(pTextChain->GetCursorEvent(pTextObj)); + //SdrTextObj *pNextLink = pTextObj->GetNextLinkInChain(); + + // NOTE: Must be called. Don't let the function return if you set it to true and not reset it + pTextChain->SetNilChainingEvent(pTextObj.get(), false); + } + else + { + // XXX + SAL_INFO("svx.chaining", "[OnChaining] No Edit Outliner View"); + } +} + +IMPL_LINK_NOARG(SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl, LinkParamNone*, void) +{ + SdrTextObj* pTextObj = GetTextEditObject(); + if (!pTextObj) + return; + ImpChainingEventHdl(); + TextChainCursorManager aCursorManager(this, pTextObj); + ImpMoveCursorAfterChainingEvent(&aCursorManager); +} + +void SdrObjEditView::ImpMoveCursorAfterChainingEvent(TextChainCursorManager* pCursorManager) +{ + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + + if (!pTextObj || !pCursorManager) + return; + + // Check if it has links to move it to + if (!pTextObj || !pTextObj->IsChainable()) + return; + + TextChain* pTextChain = pTextObj->GetTextChain(); + ESelection aNewSel = pTextChain->GetPostChainingSel(pTextObj.get()); + + pCursorManager->HandleCursorEventAfterChaining(pTextChain->GetCursorEvent(pTextObj.get()), + aNewSel); + + // Reset event + pTextChain->SetCursorEvent(pTextObj.get(), CursorChainingEvent::NULL_EVENT); +} + +IMPL_LINK(SdrObjEditView, ImpOutlinerCalcFieldValueHdl, EditFieldInfo*, pFI, void) +{ + bool bOk = false; + OUString& rStr = pFI->GetRepresentation(); + rStr.clear(); + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (pTextObj != nullptr) + { + std::optional<Color> pTxtCol; + std::optional<Color> pFldCol; + std::optional<FontLineStyle> pFldLineStyle; + bOk = pTextObj->CalcFieldValue(pFI->GetField(), pFI->GetPara(), pFI->GetPos(), true, + pTxtCol, pFldCol, pFldLineStyle, rStr); + if (bOk) + { + if (pTxtCol) + { + pFI->SetTextColor(*pTxtCol); + } + if (pFldLineStyle) + { + pFI->SetFontLineStyle(*pFldLineStyle); + } + if (pFldCol) + { + pFI->SetFieldColor(*pFldCol); + } + else + { + pFI->SetFieldColor(COL_LIGHTGRAY); // TODO: remove this later on (357) + } + } + } + Outliner& rDrawOutl = GetModel().GetDrawOutliner(pTextObj.get()); + Link<EditFieldInfo*, void> aDrawOutlLink = rDrawOutl.GetCalcFieldValueHdl(); + if (!bOk && aDrawOutlLink.IsSet()) + { + aDrawOutlLink.Call(pFI); + bOk = !rStr.isEmpty(); + } + if (!bOk) + { + aOldCalcFieldValueLink.Call(pFI); + } +} + +IMPL_LINK_NOARG(SdrObjEditView, EndTextEditHdl, SdrUndoManager*, void) { SdrEndTextEdit(); } + +// Default implementation - null UndoManager +std::unique_ptr<SdrUndoManager> SdrObjEditView::createLocalTextUndoManager() +{ + SAL_WARN("svx", "SdrObjEditView::createLocalTextUndoManager needs to be overridden"); + return std::unique_ptr<SdrUndoManager>(); +} + +bool SdrObjEditView::SdrBeginTextEdit(SdrObject* pObj_, SdrPageView* pPV, vcl::Window* pWin, + bool bIsNewObj, SdrOutliner* pGivenOutliner, + OutlinerView* pGivenOutlinerView, bool bDontDeleteOutliner, + bool bOnlyOneView, bool bGrabFocus) +{ + // FIXME cannot be an assert() yet, the code is not ready for that; + // eg. press F7 in Impress when you are inside a text object with spelling + // mistakes => boom; and it is unclear how to avoid that + SAL_WARN_IF(IsTextEdit(), "svx", "SdrBeginTextEdit called when IsTextEdit() is already true."); + // FIXME this encourages all sorts of bad habits and should be removed + SdrEndTextEdit(); + + SdrTextObj* pObj = DynCastSdrTextObj(pObj_); + if (!pObj) + return false; // currently only possible with text objects + + if (bGrabFocus && pWin) + { + // attention, this call may cause an EndTextEdit() call to this view + pWin->GrabFocus(); // to force the cursor into the edit view + } + + mbTextEditDontDelete = bDontDeleteOutliner && pGivenOutliner != nullptr; + mbTextEditOnlyOneView = bOnlyOneView; + mbTextEditNewObj = bIsNewObj; + const sal_uInt32 nWinCount(PaintWindowCount()); + + bool bBrk(false); + + if (!pWin) + { + for (sal_uInt32 i = 0; i < nWinCount && !pWin; i++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(i); + + if (OUTDEV_WINDOW == pPaintWindow->GetOutputDevice().GetOutDevType()) + { + pWin = pPaintWindow->GetOutputDevice().GetOwnerWindow(); + } + } + + // break, when no window exists + if (!pWin) + { + bBrk = true; + } + } + + if (!bBrk && !pPV) + { + pPV = GetSdrPageView(); + + // break, when no PageView for the object exists + if (!pPV) + { + bBrk = true; + } + } + + // no TextEdit on objects in locked Layer + if (pPV && pPV->GetLockedLayers().IsSet(pObj->GetLayer())) + { + bBrk = true; + } + + if (mpTextEditOutliner) + { + OSL_FAIL("SdrObjEditView::SdrBeginTextEdit(): Old Outliner still exists."); + mpTextEditOutliner.reset(); + } + + if (!bBrk) + { + mpTextEditWin = pWin; + mpTextEditPV = pPV; + mxWeakTextEditObj = pObj; + if (pGivenOutliner) + { + mpTextEditOutliner.reset(pGivenOutliner); + pGivenOutliner = nullptr; // so we don't delete it on the error path + } + else + mpTextEditOutliner + = SdrMakeOutliner(OutlinerMode::TextObject, pObj->getSdrModelFromSdrObject()); + + { + mpTextEditOutliner->ForceAutoColor(SvtAccessibilityOptions::GetIsAutomaticFontColor()); + } + + aOldCalcFieldValueLink = mpTextEditOutliner->GetCalcFieldValueHdl(); + // FieldHdl has to be set by SdrBeginTextEdit, because this call an UpdateFields + mpTextEditOutliner->SetCalcFieldValueHdl( + LINK(this, SdrObjEditView, ImpOutlinerCalcFieldValueHdl)); + mpTextEditOutliner->SetBeginPasteOrDropHdl(LINK(this, SdrObjEditView, BeginPasteOrDropHdl)); + mpTextEditOutliner->SetEndPasteOrDropHdl(LINK(this, SdrObjEditView, EndPasteOrDropHdl)); + + // It is just necessary to make the visualized page known. Set it. + mpTextEditOutliner->setVisualizedPage(pPV->GetPage()); + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + mpTextEditOutliner->SetTextObjNoInit(pTextObj.get()); + + if (pTextObj->BegTextEdit(*mpTextEditOutliner)) + { + // switch off any running TextAnimations + pTextObj->SetTextAnimationAllowed(false); + + // remember old cursor + if (mpTextEditOutliner->GetViewCount() != 0) + { + mpTextEditOutliner->RemoveView(static_cast<size_t>(0)); + } + + // Determine EditArea via TakeTextEditArea. + // TODO: This could theoretically be left out, because TakeTextRect() calculates the aTextEditArea, + // but aMinTextEditArea has to happen, too (therefore leaving this in right now) + pTextObj->TakeTextEditArea(nullptr, nullptr, &aTextEditArea, &aMinTextEditArea); + + tools::Rectangle aTextRect; + tools::Rectangle aAnchorRect; + pTextObj->TakeTextRect(*mpTextEditOutliner, aTextRect, true, + &aAnchorRect /* Give true here, not false */); + + if (!pTextObj->IsContourTextFrame()) + { + // FitToSize not together with ContourFrame, for now + if (pTextObj->IsFitToSize()) + aTextRect = aAnchorRect; + } + + aTextEditArea = aTextRect; + + // add possible GridOffset to up-to-now view-independent EditAreas + basegfx::B2DVector aGridOffset(0.0, 0.0); + if (getPossibleGridOffsetForSdrObject(aGridOffset, pTextObj.get(), pPV)) + { + const Point aOffset(basegfx::fround(aGridOffset.getX()), + basegfx::fround(aGridOffset.getY())); + + aTextEditArea += aOffset; + aMinTextEditArea += aOffset; + } + + Point aPvOfs(pTextObj->GetTextEditOffset()); + aTextEditArea.Move(aPvOfs.X(), aPvOfs.Y()); + aMinTextEditArea.Move(aPvOfs.X(), aPvOfs.Y()); + pTextEditCursorBuffer = pWin->GetCursor(); + + maHdlList.SetMoveOutside(true); + + // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary + // to call AdjustMarkHdl() always. + AdjustMarkHdl(); + + mpTextEditOutlinerView = ImpMakeOutlinerView(pWin, pGivenOutlinerView); + + if (!comphelper::LibreOfficeKit::isActive() && mpTextEditOutlinerView) + { + // activate visualization of EditView on Overlay, suppress when + // LibreOfficeKit is active + mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(this); + + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + const SdrTextObj* pText = GetTextEditObject(); + // show for cases like tdf#94223 but not for table cells like tdf#151311 + const bool bVisualizeSurroundingFrame( + pText && pText->GetObjIdentifier() != SdrObjKind::Table); + SdrPageView* pPageView = GetSdrPageView(); + + if (pPageView) + { + for (sal_uInt32 b(0); b < pPageView->PageWindowCount(); b++) + { + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b); + + if (rPageWindow.GetPaintWindow().OutputToWindow()) + { + const rtl::Reference<sdr::overlay::OverlayManager>& xManager + = rPageWindow.GetOverlayManager(); + if (xManager.is()) + { + std::unique_ptr<TextEditOverlayObject> pNewTextEditOverlayObject( + new TextEditOverlayObject(aHilightColor, + *mpTextEditOutlinerView)); + + xManager->add(*pNewTextEditOverlayObject); + if (bVisualizeSurroundingFrame) + xManager->add(*pNewTextEditOverlayObject->getOverlayFrame()); + xManager->add(*pNewTextEditOverlayObject->getOverlaySelection()); + + maTEOverlayGroup.append(std::move(pNewTextEditOverlayObject)); + } + } + } + } + } + + // check if this view is already inserted + size_t i2, nCount = mpTextEditOutliner->GetViewCount(); + for (i2 = 0; i2 < nCount; i2++) + { + if (mpTextEditOutliner->GetView(i2) == mpTextEditOutlinerView) + break; + } + + if (i2 == nCount) + mpTextEditOutliner->InsertView(mpTextEditOutlinerView, 0); + + maHdlList.SetMoveOutside(false); + maHdlList.SetMoveOutside(true); + + // register all windows as OutlinerViews with the Outliner + if (!bOnlyOneView) + { + for (sal_uInt32 i = 0; i < nWinCount; i++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(i); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if (&rOutDev != pWin->GetOutDev() && OUTDEV_WINDOW == rOutDev.GetOutDevType()) + { + OutlinerView* pOutlView + = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr); + mpTextEditOutliner->InsertView(pOutlView, static_cast<sal_uInt16>(i)); + } + } + + if (comphelper::LibreOfficeKit::isActive()) + { + // Register an outliner view for all other sdr views that + // show the same page, so that when the text edit changes, + // all interested windows get an invalidation. + SdrViewIter::ForAllViews(pObj->getSdrPageFromSdrObject(), [this, &pWin]( + SdrView* pView) { + if (pView == this) + return; + + for (sal_uInt32 nViewPaintWindow = 0; + nViewPaintWindow < pView->PaintWindowCount(); ++nViewPaintWindow) + { + SdrPaintWindow* pPaintWindow = pView->GetPaintWindow(nViewPaintWindow); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if (&rOutDev != pWin->GetOutDev() + && OUTDEV_WINDOW == rOutDev.GetOutDevType()) + { + OutlinerView* pOutlView + = ImpMakeOutlinerView(rOutDev.GetOwnerWindow(), nullptr); + pOutlView->HideCursor(); + rOutDev.GetOwnerWindow()->SetCursor(nullptr); + mpTextEditOutliner->InsertView(pOutlView); + } + } + }); + } + } + + mpTextEditOutlinerView->ShowCursor(); + mpTextEditOutliner->SetStatusEventHdl( + LINK(this, SdrObjEditView, ImpOutlinerStatusEventHdl)); + if (pTextObj->IsChainable()) + { + mpTextEditOutlinerView->SetEndCutPasteLinkHdl( + LINK(this, SdrObjEditView, ImpAfterCutOrPasteChainingEventHdl)); + } + + mpTextEditOutliner->ClearModifyFlag(); + + if (pTextObj->IsFitToSize()) + { + pWin->Invalidate(aTextEditArea); + } + + SdrHint aHint(SdrHintKind::BeginEdit, *pTextObj); + GetModel().Broadcast(aHint); + + mpTextEditOutliner->setVisualizedPage(nullptr); + + if (mxSelectionController.is()) + mxSelectionController->onSelectionHasChanged(); + + if (IsUndoEnabled() && !GetModel().GetDisableTextEditUsesCommonUndoManager()) + { + SdrUndoManager* pSdrUndoManager = nullptr; + mpLocalTextEditUndoManager = createLocalTextUndoManager(); + + if (mpLocalTextEditUndoManager) + pSdrUndoManager = mpLocalTextEditUndoManager.get(); + + if (pSdrUndoManager) + { + // we have an outliner, undo manager and it's an EditUndoManager, exchange + // the document undo manager and the default one from the outliner and tell + // it that text edit starts by setting a callback if it needs to end text edit mode. + assert(nullptr == mpOldTextEditUndoManager); + + mpOldTextEditUndoManager = mpTextEditOutliner->SetUndoManager(pSdrUndoManager); + pSdrUndoManager->SetEndTextEditHdl(LINK(this, SdrObjEditView, EndTextEditHdl)); + } + else + { + OSL_ENSURE(false, + "The document undo manager is not derived from SdrUndoManager (!)"); + } + } + + return true; // ran fine, let TextEdit run now + } + else + { + mpTextEditOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink); + mpTextEditOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + mpTextEditOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + } + } + if (mpTextEditOutliner != nullptr) + { + mpTextEditOutliner->setVisualizedPage(nullptr); + } + + // something went wrong... + if (!bDontDeleteOutliner) + { + delete pGivenOutliner; + if (pGivenOutlinerView != nullptr) + { + delete pGivenOutlinerView; + pGivenOutlinerView = nullptr; + } + } + mpTextEditOutliner.reset(); + + mpTextEditOutlinerView = nullptr; + mxWeakTextEditObj.clear(); + mpTextEditPV = nullptr; + mpTextEditWin = nullptr; + maHdlList.SetMoveOutside(false); + + return false; +} + +SdrEndTextEditKind SdrObjEditView::SdrEndTextEdit(bool bDontDeleteReally) +{ + SdrEndTextEditKind eRet = SdrEndTextEditKind::Unchanged; + rtl::Reference<SdrTextObj> pTEObj = mxWeakTextEditObj.get(); + vcl::Window* pTEWin = mpTextEditWin; + OutlinerView* pTEOutlinerView = mpTextEditOutlinerView; + vcl::Cursor* pTECursorBuffer = pTextEditCursorBuffer; + SdrUndoManager* pUndoEditUndoManager = nullptr; + bool bNeedToUndoSavedRedoTextEdit(false); + + if (IsUndoEnabled() && pTEObj && mpTextEditOutliner + && !GetModel().GetDisableTextEditUsesCommonUndoManager()) + { + // change back the UndoManager to the remembered original one + SfxUndoManager* pOriginal = mpTextEditOutliner->SetUndoManager(mpOldTextEditUndoManager); + mpOldTextEditUndoManager = nullptr; + + if (pOriginal) + { + // check if we got back our document undo manager + SdrUndoManager* pSdrUndoManager = mpLocalTextEditUndoManager.get(); + + if (pSdrUndoManager && dynamic_cast<SdrUndoManager*>(pOriginal) == pSdrUndoManager) + { + if (pSdrUndoManager->isEndTextEditTriggeredFromUndo()) + { + // remember the UndoManager where missing Undos have to be triggered after end + // text edit. When the undo had triggered the end text edit, the original action + // which had to be undone originally is not yet undone. + pUndoEditUndoManager = pSdrUndoManager; + + // We are ending text edit; if text edit was triggered from undo, execute all redos + // to create a complete text change undo action for the redo buffer. Also mark this + // state when at least one redo was executed; the created extra TextChange needs to + // be undone in addition to the first real undo outside the text edit changes + while (pSdrUndoManager->GetRedoActionCount() + > pSdrUndoManager->GetRedoActionCountBeforeTextEdit()) + { + bNeedToUndoSavedRedoTextEdit = true; + pSdrUndoManager->Redo(); + } + } + + // reset the callback link and let the undo manager cleanup all text edit + // undo actions to get the stack back to the form before the text edit + pSdrUndoManager->SetEndTextEditHdl(Link<SdrUndoManager*, void>()); + } + else + { + OSL_ENSURE(false, "Got UndoManager back in SdrEndTextEdit which is NOT the " + "expected document UndoManager (!)"); + delete pOriginal; + } + + // cid#1493241 - Wrapper object use after free + if (pUndoEditUndoManager == mpLocalTextEditUndoManager.get()) + pUndoEditUndoManager = nullptr; + mpLocalTextEditUndoManager.reset(); + } + } + else + { + assert(nullptr == mpOldTextEditUndoManager); // cannot be restored! + } + + if (auto pTextEditObj = mxWeakTextEditObj.get()) + { + SdrHint aHint(SdrHintKind::EndEdit, *pTextEditObj); + GetModel().Broadcast(aHint); + } + + // if new mechanism was used, clean it up. At cleanup no need to check + // for LibreOfficeKit + if (mpTextEditOutlinerView) + { + mpTextEditOutlinerView->GetEditView().setEditViewCallbacks(nullptr); + maTEOverlayGroup.clear(); + } + + mxWeakTextEditObj.clear(); + mpTextEditPV = nullptr; + mpTextEditWin = nullptr; + mpTextEditOutlinerView = nullptr; + pTextEditCursorBuffer = nullptr; + aTextEditArea = tools::Rectangle(); + + if (SdrOutliner* pTEOutliner = mpTextEditOutliner.release()) + { + bool bModified = pTEOutliner->IsModified(); + if (pTEOutlinerView != nullptr) + { + pTEOutlinerView->HideCursor(); + } + if (pTEObj != nullptr) + { + pTEOutliner->CompleteOnlineSpelling(); + + std::unique_ptr<SdrUndoObjSetText> pTxtUndo; + + if (bModified) + { + sal_Int32 nText; + for (nText = 0; nText < pTEObj->getTextCount(); ++nText) + if (pTEObj->getText(nText) == pTEObj->getActiveText()) + break; + + pTxtUndo.reset( + dynamic_cast<SdrUndoObjSetText*>(GetModel() + .GetSdrUndoFactory() + .CreateUndoObjectSetText(*pTEObj, nText) + .release())); + } + DBG_ASSERT(!bModified || pTxtUndo, + "svx::SdrObjEditView::EndTextEdit(), could not create undo action!"); + // Set old CalcFieldValue-Handler again, this + // has to happen before Obj::EndTextEdit(), as this does UpdateFields(). + pTEOutliner->SetCalcFieldValueHdl(aOldCalcFieldValueLink); + pTEOutliner->SetBeginPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + pTEOutliner->SetEndPasteOrDropHdl(Link<PasteOrDropInfos*, void>()); + + const bool bUndo = IsUndoEnabled(); + if (bUndo) + { + OUString aObjName(pTEObj->TakeObjNameSingul()); + BegUndo(SvxResId(STR_UndoObjSetText), aObjName); + } + + pTEObj->EndTextEdit(*pTEOutliner); + + if ((pTEObj->GetRotateAngle() != 0_deg100) || (pTEObj && pTEObj->IsFontwork())) + { + pTEObj->ActionChanged(); + } + + if (pTxtUndo != nullptr) + { + pTxtUndo->AfterSetText(); + if (!pTxtUndo->IsDifferent()) + { + pTxtUndo.reset(); + } + } + // check deletion of entire TextObj + std::unique_ptr<SdrUndoAction> pDelUndo; + bool bDelObj = false; + if (mbTextEditNewObj) + { + bDelObj = pTEObj->IsTextFrame() && !pTEObj->HasText() && !pTEObj->IsEmptyPresObj() + && !pTEObj->HasFill() && !pTEObj->HasLine(); + + if (pTEObj->IsInserted() && bDelObj + && pTEObj->GetObjInventor() == SdrInventor::Default && !bDontDeleteReally) + { + SdrObjKind eIdent = pTEObj->GetObjIdentifier(); + if (eIdent == SdrObjKind::Text) + { + pDelUndo = GetModel().GetSdrUndoFactory().CreateUndoDeleteObject(*pTEObj); + } + } + } + if (pTxtUndo) + { + if (bUndo) + AddUndo(std::move(pTxtUndo)); + eRet = SdrEndTextEditKind::Changed; + } + if (pDelUndo != nullptr) + { + if (bUndo) + { + AddUndo(std::move(pDelUndo)); + } + eRet = SdrEndTextEditKind::Deleted; + DBG_ASSERT(pTEObj->getParentSdrObjListFromSdrObject() != nullptr, + "SdrObjEditView::SdrEndTextEdit(): Fatal: Object edited doesn't have an " + "ObjList!"); + if (pTEObj->getParentSdrObjListFromSdrObject() != nullptr) + { + pTEObj->getParentSdrObjListFromSdrObject()->RemoveObject(pTEObj->GetOrdNum()); + CheckMarked(); // remove selection immediately... + } + } + else if (bDelObj) + { // for Writer: the app has to do the deletion itself. + eRet = SdrEndTextEditKind::ShouldBeDeleted; + } + + if (bUndo) + EndUndo(); // EndUndo after Remove, in case UndoStack is deleted immediately + + // Switch on any TextAnimation again after TextEdit + if (pTEObj) + { + pTEObj->SetTextAnimationAllowed(true); + } + + // Since IsMarkHdlWhenTextEdit() is ignored, it is necessary + // to call AdjustMarkHdl() always. + AdjustMarkHdl(); + } + // delete all OutlinerViews + for (size_t i = pTEOutliner->GetViewCount(); i > 0;) + { + i--; + OutlinerView* pOLV = pTEOutliner->GetView(i); + sal_uInt16 nMorePix = pOLV->GetInvalidateMore() + 10; + vcl::Window* pWin = pOLV->GetWindow(); + tools::Rectangle aRect(pOLV->GetOutputArea()); + pTEOutliner->RemoveView(i); + if (!mbTextEditDontDelete || i != 0) + { + // may not own the zeroth one + delete pOLV; + } + aRect.Union(aTextEditArea); + aRect.Union(aMinTextEditArea); + aRect = pWin->LogicToPixel(aRect); + aRect.AdjustLeft(-nMorePix); + aRect.AdjustTop(-nMorePix); + aRect.AdjustRight(nMorePix); + aRect.AdjustBottom(nMorePix); + aRect = pWin->PixelToLogic(aRect); + InvalidateOneWin(*pWin->GetOutDev(), aRect); + pWin->GetOutDev()->SetFillColor(); + pWin->GetOutDev()->SetLineColor(COL_BLACK); + } + // and now the Outliner itself + if (!mbTextEditDontDelete) + delete pTEOutliner; + else + pTEOutliner->Clear(); + if (pTEWin != nullptr) + { + pTEWin->SetCursor(pTECursorBuffer); + } + maHdlList.SetMoveOutside(false); + if (eRet != SdrEndTextEditKind::Unchanged) + { + GetMarkedObjectListWriteAccess().SetNameDirty(); + } + // coverity[leaked_storage] - if pTEOutliner wasn't deleted it didn't really belong to us + } + + if (pTEObj && !pTEObj->getSdrModelFromSdrObject().isLocked() && pTEObj->GetBroadcaster()) + { + SdrHint aHint(SdrHintKind::EndEdit, *pTEObj); + const_cast<SfxBroadcaster*>(pTEObj->GetBroadcaster())->Broadcast(aHint); + } + + if (pUndoEditUndoManager) + { + if (bNeedToUndoSavedRedoTextEdit) + { + // undo the text edit action since it was created as part of an EndTextEdit + // callback from undo itself. This needs to be done after the call to + // FmFormView::SdrEndTextEdit since it gets created there + pUndoEditUndoManager->Undo(); + } + + // trigger the Undo which was not executed, but lead to this + // end text edit + pUndoEditUndoManager->Undo(); + } + + return eRet; +} + +// info about TextEdit. Default is false. +bool SdrObjEditView::IsTextEdit() const { return mxWeakTextEditObj.get().is(); } + +// info about TextEditPageView. Default is 0L. +SdrPageView* SdrObjEditView::GetTextEditPageView() const { return mpTextEditPV; } + +OutlinerView* SdrObjEditView::ImpFindOutlinerView(vcl::Window const* pWin) const +{ + if (pWin == nullptr) + return nullptr; + if (mpTextEditOutliner == nullptr) + return nullptr; + OutlinerView* pNewView = nullptr; + size_t nWinCount = mpTextEditOutliner->GetViewCount(); + for (size_t i = 0; i < nWinCount && pNewView == nullptr; i++) + { + OutlinerView* pView = mpTextEditOutliner->GetView(i); + if (pView->GetWindow() == pWin) + pNewView = pView; + } + return pNewView; +} + +void SdrObjEditView::SetTextEditWin(vcl::Window* pWin) +{ + if (!(mxWeakTextEditObj.get() && pWin != nullptr && pWin != mpTextEditWin)) + return; + + OutlinerView* pNewView = ImpFindOutlinerView(pWin); + if (pNewView != nullptr && pNewView != mpTextEditOutlinerView) + { + if (mpTextEditOutlinerView != nullptr) + { + mpTextEditOutlinerView->HideCursor(); + } + mpTextEditOutlinerView = pNewView; + mpTextEditWin = pWin; + pWin->GrabFocus(); // Make the cursor blink here as well + pNewView->ShowCursor(); + ImpMakeTextCursorAreaVisible(); + } +} + +bool SdrObjEditView::IsTextEditHit(const Point& rHit) const +{ + bool bOk = false; + if (mxWeakTextEditObj.get()) + { + tools::Rectangle aEditArea; + if (OutlinerView* pOLV = mpTextEditOutliner->GetView(0)) + aEditArea.Union(pOLV->GetOutputArea()); + + if (aEditArea.Contains(rHit)) + { // check if any characters were actually hit + const Point aPnt(rHit - aEditArea.TopLeft()); + tools::Long nHitTol = 2000; + if (OutputDevice* pRef = mpTextEditOutliner->GetRefDevice()) + nHitTol = OutputDevice::LogicToLogic(nHitTol, MapUnit::Map100thMM, + pRef->GetMapMode().GetMapUnit()); + + bOk = mpTextEditOutliner->IsTextPos(aPnt, static_cast<sal_uInt16>(nHitTol)); + } + } + return bOk; +} + +bool SdrObjEditView::IsTextEditFrameHit(const Point& rHit) const +{ + bool bOk = false; + if (rtl::Reference<SdrTextObj> pText = mxWeakTextEditObj.get()) + { + OutlinerView* pOLV = mpTextEditOutliner->GetView(0); + if (pOLV) + { + vcl::Window* pWin = pOLV->GetWindow(); + if (pText != nullptr && pText->IsTextFrame() && pWin != nullptr) + { + sal_uInt16 nPixSiz = pOLV->GetInvalidateMore(); + tools::Rectangle aEditArea(aMinTextEditArea); + aEditArea.Union(pOLV->GetOutputArea()); + if (!aEditArea.Contains(rHit)) + { + Size aSiz(pWin->PixelToLogic(Size(nPixSiz, nPixSiz))); + aEditArea.AdjustLeft(-(aSiz.Width())); + aEditArea.AdjustTop(-(aSiz.Height())); + aEditArea.AdjustRight(aSiz.Width()); + aEditArea.AdjustBottom(aSiz.Height()); + bOk = aEditArea.Contains(rHit); + } + } + } + } + return bOk; +} + +std::unique_ptr<TextChainCursorManager> +SdrObjEditView::ImpHandleMotionThroughBoxesKeyInput(const KeyEvent& rKEvt, bool* bOutHandled) +{ + *bOutHandled = false; + + rtl::Reference<SdrTextObj> pTextObj = mxWeakTextEditObj.get(); + if (!pTextObj) + return nullptr; + + if (!pTextObj->GetNextLinkInChain() && !pTextObj->GetPrevLinkInChain()) + return nullptr; + + std::unique_ptr<TextChainCursorManager> pCursorManager( + new TextChainCursorManager(this, pTextObj.get())); + if (pCursorManager->HandleKeyEvent(rKEvt)) + { + // Possibly do other stuff here if necessary... + // XXX: Careful with the checks below (in KeyInput) for pWin and co. You should do them here I guess. + *bOutHandled = true; + } + + return pCursorManager; +} + +bool SdrObjEditView::KeyInput(const KeyEvent& rKEvt, vcl::Window* pWin) +{ + if (mpTextEditOutlinerView) + { + /* Start special handling of keys within a chain */ + // We possibly move to another box before any handling + bool bHandled = false; + std::unique_ptr<TextChainCursorManager> xCursorManager( + ImpHandleMotionThroughBoxesKeyInput(rKEvt, &bHandled)); + if (bHandled) + return true; + /* End special handling of keys within a chain */ + + if (mpTextEditOutlinerView->PostKeyEvent(rKEvt, pWin)) + { + if (mpTextEditOutliner && mpTextEditOutliner->IsModified()) + GetModel().SetChanged(); + + /* Start chaining processing */ + ImpChainingEventHdl(); + ImpMoveCursorAfterChainingEvent(xCursorManager.get()); + /* End chaining processing */ + + if (pWin != nullptr && pWin != mpTextEditWin) + SetTextEditWin(pWin); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + return SdrGlueEditView::KeyInput(rKEvt, pWin); +} + +bool SdrObjEditView::MouseButtonDown(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode(); + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rMEvt.GetPosPixel()); + if (pWin) + { + tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + } + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseButtonDown(aMEvt)) + { + if (pWin != nullptr && pWin != mpTextEditWin->GetOutDev() + && pWin->GetOutDevType() == OUTDEV_WINDOW) + SetTextEditWin(pWin->GetOwnerWindow()); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseButtonDown(rMEvt, pWin); +} + +bool SdrObjEditView::MouseButtonUp(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode(); + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt && pWin) + { + Point aPixPos(rMEvt.GetPosPixel()); + tools::Rectangle aR(pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseButtonUp(aMEvt)) + { + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseButtonUp(rMEvt, pWin); +} + +bool SdrObjEditView::MouseMove(const MouseEvent& rMEvt, OutputDevice* pWin) +{ + if (mpTextEditOutlinerView != nullptr) + { + bool bSelMode = mpTextEditOutliner->IsInSelectionMode(); + bool bPostIt = bSelMode; + if (!bPostIt) + { + Point aPt(rMEvt.GetPosPixel()); + if (pWin) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rMEvt.GetPosPixel()); + tools::Rectangle aR(mpTextEditOutlinerView->GetOutputArea()); + if (pWin) + aR = pWin->LogicToPixel(aR); + else if (mpTextEditWin) + aR = mpTextEditWin->LogicToPixel(aR); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + MouseEvent aMEvt(aPixPos, rMEvt.GetClicks(), rMEvt.GetMode(), rMEvt.GetButtons(), + rMEvt.GetModifier()); + if (mpTextEditOutlinerView->MouseMove(aMEvt) && bSelMode) + { + ImpMakeTextCursorAreaVisible(); + return true; + } + } + } + return SdrGlueEditView::MouseMove(rMEvt, pWin); +} + +bool SdrObjEditView::Command(const CommandEvent& rCEvt, vcl::Window* pWin) +{ + // as long as OutlinerView returns a sal_Bool, it only gets CommandEventId::StartDrag + if (mpTextEditOutlinerView != nullptr) + { + if (rCEvt.GetCommand() == CommandEventId::StartDrag) + { + bool bPostIt = mpTextEditOutliner->IsInSelectionMode() || !rCEvt.IsMouseEvent(); + if (!bPostIt && rCEvt.IsMouseEvent()) + { + Point aPt(rCEvt.GetMousePosPixel()); + if (pWin != nullptr) + aPt = pWin->PixelToLogic(aPt); + else if (mpTextEditWin != nullptr) + aPt = mpTextEditWin->PixelToLogic(aPt); + bPostIt = IsTextEditHit(aPt); + } + if (bPostIt) + { + Point aPixPos(rCEvt.GetMousePosPixel()); + if (rCEvt.IsMouseEvent() && pWin) + { + tools::Rectangle aR( + pWin->LogicToPixel(mpTextEditOutlinerView->GetOutputArea())); + if (aPixPos.X() < aR.Left()) + aPixPos.setX(aR.Left()); + if (aPixPos.X() > aR.Right()) + aPixPos.setX(aR.Right()); + if (aPixPos.Y() < aR.Top()) + aPixPos.setY(aR.Top()); + if (aPixPos.Y() > aR.Bottom()) + aPixPos.setY(aR.Bottom()); + } + CommandEvent aCEvt(aPixPos, rCEvt.GetCommand(), rCEvt.IsMouseEvent()); + // Command is void at the OutlinerView, sadly + mpTextEditOutlinerView->Command(aCEvt); + if (pWin != nullptr && pWin != mpTextEditWin) + SetTextEditWin(pWin); + ImpMakeTextCursorAreaVisible(); + return true; + } + } + else + { + mpTextEditOutlinerView->Command(rCEvt); + if (comphelper::LibreOfficeKit::isActive()) + { + // It could execute CommandEventId::ExtTextInput, while SdrObjEditView::KeyInput + // isn't called + if (mpTextEditOutliner && mpTextEditOutliner->IsModified()) + GetModel().SetChanged(); + } + return true; + } + } + return SdrGlueEditView::Command(rCEvt, pWin); +} + +bool SdrObjEditView::ImpIsTextEditAllSelected() const +{ + bool bRet = false; + if (mpTextEditOutliner != nullptr && mpTextEditOutlinerView != nullptr) + { + if (SdrTextObj::HasTextImpl(mpTextEditOutliner.get())) + { + const sal_Int32 nParaCnt = mpTextEditOutliner->GetParagraphCount(); + Paragraph* pLastPara + = mpTextEditOutliner->GetParagraph(nParaCnt > 1 ? nParaCnt - 1 : 0); + + ESelection aESel(mpTextEditOutlinerView->GetSelection()); + if (aESel.nStartPara == 0 && aESel.nStartPos == 0 && aESel.nEndPara == (nParaCnt - 1)) + { + if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nEndPos) + bRet = true; + } + // in case the selection was done backwards + if (!bRet && aESel.nEndPara == 0 && aESel.nEndPos == 0 + && aESel.nStartPara == (nParaCnt - 1)) + { + if (mpTextEditOutliner->GetText(pLastPara).getLength() == aESel.nStartPos) + bRet = true; + } + } + else + { + bRet = true; + } + } + return bRet; +} + +void SdrObjEditView::ImpMakeTextCursorAreaVisible() +{ + if (mpTextEditOutlinerView != nullptr && mpTextEditWin != nullptr) + { + vcl::Cursor* pCsr = mpTextEditWin->GetCursor(); + if (pCsr != nullptr) + { + Size aSiz(pCsr->GetSize()); + if (!aSiz.IsEmpty()) + { + MakeVisible(tools::Rectangle(pCsr->GetPos(), aSiz), *mpTextEditWin); + } + } + } +} + +SvtScriptType SdrObjEditView::GetScriptType() const +{ + SvtScriptType nScriptType = SvtScriptType::NONE; + + if (IsTextEdit()) + { + auto pText = mxWeakTextEditObj.get(); + if (pText->GetOutlinerParaObject()) + nScriptType = pText->GetOutlinerParaObject()->GetTextObject().GetScriptType(); + + if (mpTextEditOutlinerView) + nScriptType = mpTextEditOutlinerView->GetSelectedScriptType(); + } + else + { + const size_t nMarkCount(GetMarkedObjectCount()); + + for (size_t i = 0; i < nMarkCount; ++i) + { + OutlinerParaObject* pParaObj = GetMarkedObjectByIndex(i)->GetOutlinerParaObject(); + + if (pParaObj) + { + nScriptType |= pParaObj->GetTextObject().GetScriptType(); + } + } + } + + if (nScriptType == SvtScriptType::NONE) + nScriptType = SvtScriptType::LATIN; + + return nScriptType; +} + +void SdrObjEditView::GetAttributes(SfxItemSet& rTargetSet, bool bOnlyHardAttr) const +{ + if (mxSelectionController.is()) + if (mxSelectionController->GetAttributes(rTargetSet, bOnlyHardAttr)) + return; + + if (IsTextEdit()) + { + DBG_ASSERT(mpTextEditOutlinerView != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL"); + DBG_ASSERT(mpTextEditOutliner != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL"); + + auto pText = mxWeakTextEditObj.get(); + // take care of bOnlyHardAttr(!) + if (!bOnlyHardAttr && pText->GetStyleSheet()) + rTargetSet.Put(pText->GetStyleSheet()->GetItemSet()); + + // add object attributes + rTargetSet.Put(pText->GetMergedItemSet()); + + if (mpTextEditOutlinerView) + { + // FALSE= regard InvalidItems as "holes," not as Default + rTargetSet.Put(mpTextEditOutlinerView->GetAttribs(), false); + } + + if (GetMarkedObjectCount() == 1 && GetMarkedObjectByIndex(0) == pText.get()) + { + MergeNotPersistAttrFromMarked(rTargetSet); + } + } + else + { + SdrGlueEditView::GetAttributes(rTargetSet, bOnlyHardAttr); + } +} + +bool SdrObjEditView::SetAttributes(const SfxItemSet& rSet, bool bReplaceAll) +{ + bool bRet = false; + rtl::Reference<SdrTextObj> pTextEditObj = mxWeakTextEditObj.get(); + bool bTextEdit = mpTextEditOutlinerView != nullptr && pTextEditObj != nullptr; + bool bAllTextSelected = ImpIsTextEditAllSelected(); + const SfxItemSet* pSet = &rSet; + + if (!bTextEdit) + { + // no TextEdit active -> all Items to drawing object + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll); + + if (!bRet) + { + SdrGlueEditView::SetAttributes(*pSet, bReplaceAll); + bRet = true; + } + } + else + { +#ifdef DBG_UTIL + { + bool bHasEEFeatureItems = false; + SfxItemIter aIter(rSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); !bHasEEFeatureItems && pItem; + pItem = aIter.NextItem()) + { + if (!IsInvalidItem(pItem)) + { + sal_uInt16 nW = pItem->Which(); + if (nW >= EE_FEATURE_START && nW <= EE_FEATURE_END) + bHasEEFeatureItems = true; + } + } + + if (bHasEEFeatureItems) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog( + nullptr, VclMessageType::Info, VclButtonsType::Ok, + "SdrObjEditView::SetAttributes(): Setting EE_FEATURE items " + "at the SdrView does not make sense! It only leads to " + "overhead and unreadable documents.")); + xInfoBox->run(); + } + } +#endif + + bool bOnlyEEItems; + bool bNoEEItems = !SearchOutlinerItems(*pSet, bReplaceAll, &bOnlyEEItems); + // everything selected? -> attributes to the border, too + // if no EEItems, attributes to the border only + if (bAllTextSelected || bNoEEItems) + { + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(*pSet, bReplaceAll); + + if (!bRet) + { + const bool bUndo = IsUndoEnabled(); + + if (bUndo) + { + BegUndo(ImpGetDescriptionString(STR_EditSetAttributes)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pTextEditObj)); + + // If this is a text object also rescue the OutlinerParaObject since + // applying attributes to the object may change text layout when + // multiple portions exist with multiple formats. If an OutlinerParaObject + // really exists and needs to be rescued is evaluated in the undo + // implementation itself. + bool bRescueText(pTextEditObj); + + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject( + *pTextEditObj, false, !bNoEEItems || bRescueText)); + EndUndo(); + } + + pTextEditObj->SetMergedItemSetAndBroadcast(*pSet, bReplaceAll); + + FlushComeBackTimer(); // to set ModeHasChanged immediately + } + } + else if (!bOnlyEEItems) + { + // Otherwise split Set, if necessary. + // Now we build an ItemSet aSet that doesn't contain EE_Items from + // *pSet (otherwise it would be a copy). + WhichRangesContainer pNewWhichTable + = RemoveWhichRange(pSet->GetRanges(), EE_ITEMS_START, EE_ITEMS_END); + SfxItemSet aSet(GetModel().GetItemPool(), std::move(pNewWhichTable)); + SfxWhichIter aIter(aSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich != 0) + { + const SfxPoolItem* pItem; + SfxItemState eState = pSet->GetItemState(nWhich, false, &pItem); + if (eState == SfxItemState::SET) + aSet.Put(*pItem); + nWhich = aIter.NextWhich(); + } + + if (mxSelectionController.is()) + bRet = mxSelectionController->SetAttributes(aSet, bReplaceAll); + + if (!bRet) + { + if (IsUndoEnabled()) + { + BegUndo(ImpGetDescriptionString(STR_EditSetAttributes)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoGeoObject(*pTextEditObj)); + AddUndo(GetModel().GetSdrUndoFactory().CreateUndoAttrObject(*pTextEditObj)); + EndUndo(); + } + + pTextEditObj->SetMergedItemSetAndBroadcast(aSet, bReplaceAll); + + if (GetMarkedObjectCount() == 1 && GetMarkedObjectByIndex(0) == pTextEditObj.get()) + { + SetNotPersistAttrToMarked(aSet); + } + } + FlushComeBackTimer(); + } + if (!bNoEEItems) + { + // and now the attributes to the EditEngine + if (bReplaceAll) + { + mpTextEditOutlinerView->RemoveAttribs(true); + } + mpTextEditOutlinerView->SetAttribs(rSet); + + Outliner* pTEOutliner = mpTextEditOutlinerView->GetOutliner(); + if (pTEOutliner && pTEOutliner->IsModified()) + GetModel().SetChanged(); + + ImpMakeTextCursorAreaVisible(); + } + bRet = true; + } + return bRet; +} + +SfxStyleSheet* SdrObjEditView::GetStyleSheet() const +{ + SfxStyleSheet* pSheet = nullptr; + + if (mxSelectionController.is()) + { + if (mxSelectionController->GetStyleSheet(pSheet)) + return pSheet; + } + + if (mpTextEditOutlinerView) + { + pSheet = mpTextEditOutlinerView->GetStyleSheet(); + } + else + { + pSheet = SdrGlueEditView::GetStyleSheet(); + } + return pSheet; +} + +void SdrObjEditView::SetStyleSheet(SfxStyleSheet* pStyleSheet, bool bDontRemoveHardAttr) +{ + if (mxSelectionController.is()) + { + if (mxSelectionController->SetStyleSheet(pStyleSheet, bDontRemoveHardAttr)) + return; + } + + // if we are currently in edit mode we must also set the stylesheet + // on all paragraphs in the Outliner for the edit view + if (nullptr != mpTextEditOutlinerView) + { + Outliner* pOutliner = mpTextEditOutlinerView->GetOutliner(); + + const sal_Int32 nParaCount = pOutliner->GetParagraphCount(); + for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++) + { + pOutliner->SetStyleSheet(nPara, pStyleSheet); + } + } + + SdrGlueEditView::SetStyleSheet(pStyleSheet, bDontRemoveHardAttr); +} + +void SdrObjEditView::AddDeviceToPaintView(OutputDevice& rNewDev, vcl::Window* pWindow) +{ + SdrGlueEditView::AddDeviceToPaintView(rNewDev, pWindow); + + if (mxWeakTextEditObj.get() && !mbTextEditOnlyOneView + && rNewDev.GetOutDevType() == OUTDEV_WINDOW) + { + OutlinerView* pOutlView = ImpMakeOutlinerView(rNewDev.GetOwnerWindow(), nullptr); + mpTextEditOutliner->InsertView(pOutlView); + } +} + +void SdrObjEditView::DeleteDeviceFromPaintView(OutputDevice& rOldDev) +{ + SdrGlueEditView::DeleteDeviceFromPaintView(rOldDev); + + if (mxWeakTextEditObj.get() && !mbTextEditOnlyOneView + && rOldDev.GetOutDevType() == OUTDEV_WINDOW) + { + for (size_t i = mpTextEditOutliner->GetViewCount(); i > 0;) + { + i--; + OutlinerView* pOLV = mpTextEditOutliner->GetView(i); + if (pOLV && pOLV->GetWindow() == rOldDev.GetOwnerWindow()) + { + mpTextEditOutliner->RemoveView(i); + } + } + } + + lcl_RemoveTextEditOutlinerViews(this, GetSdrPageView(), &rOldDev); +} + +bool SdrObjEditView::IsTextEditInSelectionMode() const +{ + return mpTextEditOutliner != nullptr && mpTextEditOutliner->IsInSelectionMode(); +} + +// MacroMode + +void SdrObjEditView::BegMacroObj(const Point& rPnt, short nTol, SdrObject* pObj, SdrPageView* pPV, + vcl::Window* pWin) +{ + BrkMacroObj(); + if (pObj != nullptr && pPV != nullptr && pWin != nullptr && pObj->HasMacro()) + { + nTol = ImpGetHitTolLogic(nTol, nullptr); + pMacroObj = pObj; + pMacroPV = pPV; + pMacroWin = pWin; + mbMacroDown = false; + nMacroTol = sal_uInt16(nTol); + aMacroDownPos = rPnt; + MovMacroObj(rPnt); + } +} + +void SdrObjEditView::ImpMacroUp(const Point& rUpPos) +{ + if (pMacroObj != nullptr && mbMacroDown) + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rUpPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec); + mbMacroDown = false; + } +} + +void SdrObjEditView::ImpMacroDown(const Point& rDownPos) +{ + if (pMacroObj != nullptr && !mbMacroDown) + { + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rDownPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + pMacroObj->PaintMacro(*pMacroWin->GetOutDev(), tools::Rectangle(), aHitRec); + mbMacroDown = true; + } +} + +void SdrObjEditView::MovMacroObj(const Point& rPnt) +{ + if (pMacroObj == nullptr) + return; + + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = rPnt; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + bool bDown = pMacroObj->IsMacroHit(aHitRec); + if (bDown) + ImpMacroDown(rPnt); + else + ImpMacroUp(rPnt); +} + +void SdrObjEditView::BrkMacroObj() +{ + if (pMacroObj != nullptr) + { + ImpMacroUp(aMacroDownPos); + pMacroObj = nullptr; + pMacroPV = nullptr; + pMacroWin = nullptr; + } +} + +bool SdrObjEditView::EndMacroObj() +{ + if (pMacroObj != nullptr && mbMacroDown) + { + ImpMacroUp(aMacroDownPos); + SdrObjMacroHitRec aHitRec; + aHitRec.aPos = aMacroDownPos; + aHitRec.nTol = nMacroTol; + aHitRec.pVisiLayer = &pMacroPV->GetVisibleLayers(); + aHitRec.pPageView = pMacroPV; + bool bRet = pMacroObj->DoMacro(aHitRec); + pMacroObj = nullptr; + pMacroPV = nullptr; + pMacroWin = nullptr; + return bRet; + } + else + { + BrkMacroObj(); + return false; + } +} + +/** fills the given any with a XTextCursor for the current text selection. + Leaves the any untouched if there currently is no text selected */ +void SdrObjEditView::getTextSelection(css::uno::Any& rSelection) +{ + if (!IsTextEdit()) + return; + + OutlinerView* pOutlinerView = GetTextEditOutlinerView(); + if (!(pOutlinerView && pOutlinerView->HasSelection())) + return; + + SdrObject* pObj = GetTextEditObject(); + + if (!pObj) + return; + + css::uno::Reference<css::text::XText> xText(pObj->getUnoShape(), css::uno::UNO_QUERY); + if (xText.is()) + { + SvxUnoTextBase* pRange = comphelper::getFromUnoTunnel<SvxUnoTextBase>(xText); + if (pRange) + { + rSelection <<= pRange->createTextCursorBySelection(pOutlinerView->GetSelection()); + } + } +} + +/* check if we have a single selection and that single object likes + to handle the mouse and keyboard events itself + + TODO: the selection controller should be queried from the + object specific view contact. Currently this method only + works for tables. +*/ +void SdrObjEditView::MarkListHasChanged() +{ + SdrGlueEditView::MarkListHasChanged(); + + if (mxSelectionController.is()) + { + mxLastSelectionController = mxSelectionController; + mxSelectionController->onSelectionHasChanged(); + } + + mxSelectionController.clear(); + + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (rMarkList.GetMarkCount() != 1) + return; + + const SdrObject* pObj(rMarkList.GetMark(0)->GetMarkedSdrObj()); + SdrView* pView(dynamic_cast<SdrView*>(this)); + + // check for table + if (pObj && pView && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + mxSelectionController = sdr::table::CreateTableController( + *pView, static_cast<const sdr::table::SdrTableObj&>(*pObj), mxLastSelectionController); + + if (mxSelectionController.is()) + { + mxLastSelectionController.clear(); + mxSelectionController->onSelectionHasChanged(); + } + } +} + +IMPL_LINK(SdrObjEditView, EndPasteOrDropHdl, PasteOrDropInfos*, pInfo, void) +{ + OnEndPasteOrDrop(pInfo); +} + +IMPL_LINK(SdrObjEditView, BeginPasteOrDropHdl, PasteOrDropInfos*, pInfo, void) +{ + OnBeginPasteOrDrop(pInfo); +} + +void SdrObjEditView::OnBeginPasteOrDrop(PasteOrDropInfos*) +{ + // applications can derive from these virtual methods to do something before a drop or paste operation +} + +void SdrObjEditView::OnEndPasteOrDrop(PasteOrDropInfos*) +{ + // applications can derive from these virtual methods to do something before a drop or paste operation +} + +sal_uInt16 SdrObjEditView::GetSelectionLevel() const +{ + sal_uInt16 nLevel = 0xFFFF; + if (IsTextEdit()) + { + DBG_ASSERT(mpTextEditOutlinerView != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutlinerView=NULL"); + DBG_ASSERT(mpTextEditOutliner != nullptr, + "SdrObjEditView::GetAttributes(): mpTextEditOutliner=NULL"); + if (mpTextEditOutlinerView) + { + //start and end position + ESelection aSelect = mpTextEditOutlinerView->GetSelection(); + sal_uInt16 nStartPara = ::std::min(aSelect.nStartPara, aSelect.nEndPara); + sal_uInt16 nEndPara = ::std::max(aSelect.nStartPara, aSelect.nEndPara); + //get level from each paragraph + nLevel = 0; + for (sal_uInt16 nPara = nStartPara; nPara <= nEndPara; nPara++) + { + sal_uInt16 nParaDepth + = 1 << static_cast<sal_uInt16>(mpTextEditOutliner->GetDepth(nPara)); + if (!(nLevel & nParaDepth)) + nLevel += nParaDepth; + } + //reduce one level for Outliner Object + //if( nLevel > 0 && GetTextEditObject()->GetObjIdentifier() == OBJ_OUTLINETEXT ) + // nLevel = nLevel >> 1; + //no bullet paragraph selected + if (nLevel == 0) + nLevel = 0xFFFF; + } + } + return nLevel; +} + +bool SdrObjEditView::SupportsFormatPaintbrush(SdrInventor nObjectInventor, + SdrObjKind nObjectIdentifier) +{ + if (nObjectInventor != SdrInventor::Default && nObjectInventor != SdrInventor::E3d) + return false; + switch (nObjectIdentifier) + { + case SdrObjKind::NONE: + case SdrObjKind::Group: + return false; + case SdrObjKind::Line: + case SdrObjKind::Rectangle: + case SdrObjKind::CircleOrEllipse: + case SdrObjKind::CircleSection: + case SdrObjKind::CircleArc: + case SdrObjKind::CircleCut: + case SdrObjKind::Polygon: + case SdrObjKind::PolyLine: + case SdrObjKind::PathLine: + case SdrObjKind::PathFill: + case SdrObjKind::FreehandLine: + case SdrObjKind::FreehandFill: + case SdrObjKind::Text: + case SdrObjKind::TitleText: + case SdrObjKind::OutlineText: + case SdrObjKind::Graphic: + case SdrObjKind::OLE2: + case SdrObjKind::Table: + return true; + case SdrObjKind::Caption: + return false; + case SdrObjKind::Edge: + case SdrObjKind::PathPoly: + case SdrObjKind::PathPolyLine: + return true; + case SdrObjKind::Page: + case SdrObjKind::Measure: + case SdrObjKind::OLEPluginFrame: + case SdrObjKind::UNO: + return false; + case SdrObjKind::CustomShape: + return true; + default: + return false; + } +} + +static const WhichRangesContainer& GetFormatRangeImpl(bool bTextOnly) +{ + static const WhichRangesContainer gFull( + svl::Items<XATTR_LINE_FIRST, XATTR_LINE_LAST, XATTR_FILL_FIRST, XATTRSET_FILL, + SDRATTR_SHADOW_FIRST, SDRATTR_SHADOW_LAST, SDRATTR_MISC_FIRST, + SDRATTR_MISC_LAST, // table cell formats + SDRATTR_GRAF_FIRST, SDRATTR_GRAF_LAST, SDRATTR_TABLE_FIRST, SDRATTR_TABLE_LAST, + SDRATTR_GLOW_FIRST, SDRATTR_GLOW_LAST, SDRATTR_SOFTEDGE_FIRST, + SDRATTR_SOFTEDGE_LAST, EE_PARA_START, EE_PARA_END, EE_CHAR_START, EE_CHAR_END>); + + static const WhichRangesContainer gTextOnly( + svl::Items<SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, EE_PARA_START, EE_PARA_END, EE_CHAR_START, + EE_CHAR_END>); + + return bTextOnly ? gTextOnly : gFull; +} + +void SdrObjEditView::TakeFormatPaintBrush(std::shared_ptr<SfxItemSet>& rFormatSet) +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (rMarkList.GetMarkCount() <= 0) + return; + + OutlinerView* pOLV = GetTextEditOutlinerView(); + + rFormatSet = std::make_shared<SfxItemSet>(GetModel().GetItemPool(), + GetFormatRangeImpl(pOLV != nullptr)); + if (pOLV) + { + rFormatSet->Put(pOLV->GetAttribs()); + } + else + { + const bool bOnlyHardAttr = false; + rFormatSet->Put(GetAttrFromMarked(bOnlyHardAttr)); + } + + // check for cloning from table cell, in which case we need to copy cell-specific formatting attributes + const SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + auto pTable = static_cast<const sdr::table::SdrTableObj*>(pObj); + if (mxSelectionController.is() && pTable->getActiveCell().is()) + { + mxSelectionController->GetAttributes(*rFormatSet, false); + } + } +} + +static SfxItemSet CreatePaintSet(const WhichRangesContainer& pRanges, SfxItemPool& rPool, + const SfxItemSet& rSourceSet, const SfxItemSet& rTargetSet, + bool bNoCharacterFormats, bool bNoParagraphFormats) +{ + SfxItemSet aPaintSet(rPool, pRanges); + + for (const auto& pRange : pRanges) + { + sal_uInt16 nWhich = pRange.first; + const sal_uInt16 nLastWhich = pRange.second; + + if (bNoCharacterFormats && (nWhich == EE_CHAR_START)) + continue; + + if (bNoParagraphFormats && (nWhich == EE_PARA_START)) + continue; + + for (; nWhich <= nLastWhich; nWhich++) + { + const SfxPoolItem* pSourceItem = rSourceSet.GetItem(nWhich); + const SfxPoolItem* pTargetItem = rTargetSet.GetItem(nWhich); + + if ((pSourceItem && !pTargetItem) + || (pSourceItem && pTargetItem && *pSourceItem != *pTargetItem)) + { + aPaintSet.Put(*pSourceItem); + } + } + } + return aPaintSet; +} + +void SdrObjEditView::ApplyFormatPaintBrushToText(SfxItemSet const& rFormatSet, SdrTextObj& rTextObj, + SdrText* pText, bool bNoCharacterFormats, + bool bNoParagraphFormats) +{ + OutlinerParaObject* pParaObj = pText ? pText->GetOutlinerParaObject() : nullptr; + if (!pParaObj) + return; + + SdrOutliner& rOutliner = rTextObj.ImpGetDrawOutliner(); + rOutliner.SetText(*pParaObj); + + sal_Int32 nParaCount(rOutliner.GetParagraphCount()); + + if (!nParaCount) + return; + + for (sal_Int32 nPara = 0; nPara < nParaCount; nPara++) + { + if (!bNoCharacterFormats) + rOutliner.RemoveCharAttribs(nPara); + + SfxItemSet aSet(rOutliner.GetParaAttribs(nPara)); + aSet.Put(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(), rFormatSet, aSet, + bNoCharacterFormats, bNoParagraphFormats)); + rOutliner.SetParaAttribs(nPara, aSet); + } + + std::optional<OutlinerParaObject> pTemp = rOutliner.CreateParaObject(0, nParaCount); + rOutliner.Clear(); + + rTextObj.NbcSetOutlinerParaObjectForText(std::move(pTemp), pText); +} + +void SdrObjEditView::DisposeUndoManager() +{ + if (mpTextEditOutliner) + { + if (typeid(mpTextEditOutliner->GetUndoManager()) != typeid(EditUndoManager)) + { + // Non-owning pointer, clear it. + mpTextEditOutliner->SetUndoManager(nullptr); + } + } + + mpOldTextEditUndoManager = nullptr; +} + +void SdrObjEditView::ApplyFormatPaintBrush(SfxItemSet& rFormatSet, bool bNoCharacterFormats, + bool bNoParagraphFormats) +{ + if (mxSelectionController.is() + && mxSelectionController->ApplyFormatPaintBrush(rFormatSet, bNoCharacterFormats, + bNoParagraphFormats)) + { + return; + } + + OutlinerView* pOLV = GetTextEditOutlinerView(); + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if (!pOLV) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + const SfxItemSet& rShapeSet = pObj->GetMergedItemSet(); + + // if not in text edit mode (aka the user selected text or clicked on a word) + // apply formatting attributes to selected shape + // All formatting items (see ranges above) that are unequal in selected shape and + // the format paintbrush are hard set on the selected shape. + + const WhichRangesContainer& pRanges = rFormatSet.GetRanges(); + bool bTextOnly = true; + + for (const auto& pRange : pRanges) + { + if ((pRange.first != EE_PARA_START) && (pRange.first != EE_CHAR_START)) + { + bTextOnly = false; + break; + } + } + + if (!bTextOnly) + { + SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(false), *rShapeSet.GetPool(), + rFormatSet, rShapeSet, bNoCharacterFormats, + bNoParagraphFormats)); + SetAttrToMarked(aPaintSet, false /*bReplaceAll*/); + } + + // now apply character and paragraph formatting to text, if the shape has any + SdrTextObj* pTextObj = DynCastSdrTextObj(pObj); + if (pTextObj) + { + sal_Int32 nText = pTextObj->getTextCount(); + + while (--nText >= 0) + { + SdrText* pText = pTextObj->getText(nText); + ApplyFormatPaintBrushToText(rFormatSet, *pTextObj, pText, bNoCharacterFormats, + bNoParagraphFormats); + } + } + } + else + { + ::Outliner* pOutliner = pOLV->GetOutliner(); + if (pOutliner) + { + const EditEngine& rEditEngine = pOutliner->GetEditEngine(); + + ESelection aSel(pOLV->GetSelection()); + if (!aSel.HasRange()) + pOLV->SetSelection(rEditEngine.GetWord(aSel, css::i18n::WordType::DICTIONARY_WORD)); + + const bool bRemoveParaAttribs = !bNoParagraphFormats; + pOLV->RemoveAttribsKeepLanguages(bRemoveParaAttribs); + SfxItemSet aSet(pOLV->GetAttribs()); + SfxItemSet aPaintSet(CreatePaintSet(GetFormatRangeImpl(true), *aSet.GetPool(), + rFormatSet, aSet, bNoCharacterFormats, + bNoParagraphFormats)); + pOLV->SetAttribs(aPaintSet); + } + } + + // check for cloning to table cell, in which case we need to copy cell-specific formatting attributes + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj && (pObj->GetObjInventor() == SdrInventor::Default) + && (pObj->GetObjIdentifier() == SdrObjKind::Table)) + { + auto pTable = static_cast<sdr::table::SdrTableObj*>(pObj); + if (pTable->getActiveCell().is() && mxSelectionController.is()) + { + mxSelectionController->SetAttributes(rFormatSet, false); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |