diff options
Diffstat (limited to 'sw/source/uibase/docvw')
29 files changed, 18032 insertions, 0 deletions
diff --git a/sw/source/uibase/docvw/AnchorOverlayObject.cxx b/sw/source/uibase/docvw/AnchorOverlayObject.cxx new file mode 100644 index 000000000..482bc6b83 --- /dev/null +++ b/sw/source/uibase/docvw/AnchorOverlayObject.cxx @@ -0,0 +1,384 @@ +/* -*- 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 "AnchorOverlayObject.hxx" +#include <SidebarWindowsConsts.hxx> + +#include <swrect.hxx> +#include <view.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svdview.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <tools/long.hxx> + +#include <sw_primitivetypes2d.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/primitive2d/primitivetools2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> + +namespace sw::sidebarwindows { + +namespace { + +// helper class: Primitive for discrete visualisation +class AnchorPrimitive : public drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D +{ +private: + basegfx::B2DPolygon maTriangle; + basegfx::B2DPolygon maLine; + basegfx::B2DPolygon maLineTop; + const AnchorState maAnchorState; + basegfx::BColor maColor; + + // discrete line width + double mfDiscreteLineWidth; + + bool mbLineSolid : 1; + +protected: + virtual void create2DDecomposition( + drawinglayer::primitive2d::Primitive2DContainer& rContainer, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) const override; + +public: + AnchorPrimitive( const basegfx::B2DPolygon& rTriangle, + const basegfx::B2DPolygon& rLine, + const basegfx::B2DPolygon& rLineTop, + AnchorState aAnchorState, + const basegfx::BColor& rColor, + double fDiscreteLineWidth, + bool bLineSolid ) + : maTriangle(rTriangle), + maLine(rLine), + maLineTop(rLineTop), + maAnchorState(aAnchorState), + maColor(rColor), + mfDiscreteLineWidth(fDiscreteLineWidth), + mbLineSolid(bLineSolid) + {} + + // data access + const basegfx::B2DPolygon& getLine() const { return maLine; } + const basegfx::BColor& getColor() const { return maColor; } + bool getLineSolid() const { return mbLineSolid; } + + virtual bool operator==( const drawinglayer::primitive2d::BasePrimitive2D& rPrimitive ) const override; + + virtual sal_uInt32 getPrimitive2DID() const override; +}; + +} + +void AnchorPrimitive::create2DDecomposition( + drawinglayer::primitive2d::Primitive2DContainer& rContainer, + const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if ( AnchorState::Tri == maAnchorState || + AnchorState::All == maAnchorState ) + { + // create triangle + const drawinglayer::primitive2d::Primitive2DReference aTriangle( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(maTriangle), + getColor())); + + rContainer.push_back(aTriangle); + } + + // prepare view-independent LineWidth and color + const drawinglayer::attribute::LineAttribute aLineAttribute( + getColor(), + mfDiscreteLineWidth * getDiscreteUnit()); + + if ( AnchorState::All == maAnchorState ) + { + // create line start + if(getLineSolid()) + { + const drawinglayer::primitive2d::Primitive2DReference aSolidLine( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + getLine(), + aLineAttribute)); + + rContainer.push_back(aSolidLine); + } + else + { + std::vector< double > aDotDashArray; + const double fDistance(3.0 * 15.0); + const double fDashLen(5.0 * 15.0); + + aDotDashArray.push_back(fDashLen); + aDotDashArray.push_back(fDistance); + + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute( + std::move(aDotDashArray), + fDistance + fDashLen); + + const drawinglayer::primitive2d::Primitive2DReference aStrokedLine( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + getLine(), + aLineAttribute, + aStrokeAttribute)); + + rContainer.push_back(aStrokedLine); + } + } + + if ( AnchorState::All == maAnchorState || + AnchorState::End == maAnchorState ) + { + // LineTop has to be created, too, but uses no shadow, so add after + // the other parts are created + const drawinglayer::primitive2d::Primitive2DReference aLineTop( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + maLineTop, + aLineAttribute)); + + rContainer.push_back(aLineTop); + } +} + +bool AnchorPrimitive::operator==( const drawinglayer::primitive2d::BasePrimitive2D& rPrimitive ) const +{ + if(drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const AnchorPrimitive& rCompare = static_cast< const AnchorPrimitive& >(rPrimitive); + + return (maTriangle == rCompare.maTriangle + && getLine() == rCompare.getLine() + && maLineTop == rCompare.maLineTop + && maAnchorState == rCompare.maAnchorState + && getColor() == rCompare.getColor() + && mfDiscreteLineWidth == rCompare.mfDiscreteLineWidth + && getLineSolid() == rCompare.getLineSolid()); + } + + return false; +} + +sal_uInt32 AnchorPrimitive::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_SWSIDEBARANCHORPRIMITIVE; +} + +/*static*/ std::unique_ptr<AnchorOverlayObject> AnchorOverlayObject::CreateAnchorOverlayObject( + SwView const & rDocView, + const SwRect& aAnchorRect, + tools::Long aPageBorder, + const Point& aLineStart, + const Point& aLineEnd, + const Color& aColorAnchor ) +{ + std::unique_ptr<AnchorOverlayObject> pAnchorOverlayObject; + if ( rDocView.GetDrawView() ) + { + SdrPaintWindow* pPaintWindow = rDocView.GetDrawView()->GetPaintWindow(0); + if( pPaintWindow ) + { + const rtl::Reference< sdr::overlay::OverlayManager >& xOverlayManager = pPaintWindow->GetOverlayManager(); + + if ( xOverlayManager.is() ) + { + pAnchorOverlayObject.reset(new AnchorOverlayObject( + basegfx::B2DPoint( aAnchorRect.Left() , aAnchorRect.Bottom()-5*15), + basegfx::B2DPoint( aAnchorRect.Left()-5*15 , aAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( aAnchorRect.Left()+5*15 , aAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( aAnchorRect.Left(), aAnchorRect.Bottom()+2*15), + basegfx::B2DPoint( aPageBorder ,aAnchorRect.Bottom()+2*15), + basegfx::B2DPoint( aLineStart.X(),aLineStart.Y()), + basegfx::B2DPoint( aLineEnd.X(),aLineEnd.Y()) , + aColorAnchor)); + xOverlayManager->add(*pAnchorOverlayObject); + } + } + } + + return pAnchorOverlayObject; +} + +AnchorOverlayObject::AnchorOverlayObject( const basegfx::B2DPoint& rBasePos, + const basegfx::B2DPoint& rSecondPos, + const basegfx::B2DPoint& rThirdPos, + const basegfx::B2DPoint& rFourthPos, + const basegfx::B2DPoint& rFifthPos, + const basegfx::B2DPoint& rSixthPos, + const basegfx::B2DPoint& rSeventhPos, + const Color& rBaseColor) + : OverlayObjectWithBasePosition(rBasePos, rBaseColor) + , maSecondPosition(rSecondPos) + , maThirdPosition(rThirdPos) + , maFourthPosition(rFourthPos) + , maFifthPosition(rFifthPos) + , maSixthPosition(rSixthPos) + , maSeventhPosition(rSeventhPos) + , mAnchorState(AnchorState::All) + , mbLineSolid(false) +{ +} + +AnchorOverlayObject::~AnchorOverlayObject() +{ + if ( getOverlayManager() ) + { + // remove this object from the chain + getOverlayManager()->remove(*this); + } +} + +void AnchorOverlayObject::implEnsureGeometry() +{ + if(!maTriangle.count()) + { + maTriangle.append(getBasePosition()); + maTriangle.append(GetSecondPosition()); + maTriangle.append(GetThirdPosition()); + maTriangle.setClosed(true); + } + + if(!maLine.count()) + { + maLine.append(GetFourthPosition()); + maLine.append(GetFifthPosition()); + maLine.append(GetSixthPosition()); + } + + if(!maLineTop.count()) + { + maLineTop.append(GetSixthPosition()); + maLineTop.append(GetSeventhPosition()); + } +} + +void AnchorOverlayObject::implResetGeometry() +{ + maTriangle.clear(); + maLine.clear(); + maLineTop.clear(); +} + +drawinglayer::primitive2d::Primitive2DContainer AnchorOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + implEnsureGeometry(); + + static const double aDiscreteLineWidth(1.6); + const drawinglayer::primitive2d::Primitive2DReference aReference( + new AnchorPrimitive( maTriangle, + maLine, + maLineTop, + GetAnchorState(), + getBaseColor().getBColor(), + ANCHORLINE_WIDTH * aDiscreteLineWidth, + getLineSolid()) ); + + return drawinglayer::primitive2d::Primitive2DContainer { aReference }; +} + +void AnchorOverlayObject::SetAllPosition( const basegfx::B2DPoint& rPoint1, + const basegfx::B2DPoint& rPoint2, + const basegfx::B2DPoint& rPoint3, + const basegfx::B2DPoint& rPoint4, + const basegfx::B2DPoint& rPoint5, + const basegfx::B2DPoint& rPoint6, + const basegfx::B2DPoint& rPoint7) +{ + if ( !(rPoint1 != getBasePosition() || + rPoint2 != GetSecondPosition() || + rPoint3 != GetThirdPosition() || + rPoint4 != GetFourthPosition() || + rPoint5 != GetFifthPosition() || + rPoint6 != GetSixthPosition() || + rPoint7 != GetSeventhPosition()) ) + return; + + maBasePosition = rPoint1; + maSecondPosition = rPoint2; + maThirdPosition = rPoint3; + maFourthPosition = rPoint4; + maFifthPosition = rPoint5; + maSixthPosition = rPoint6; + maSeventhPosition = rPoint7; + + implResetGeometry(); + objectChange(); +} + +void AnchorOverlayObject::SetSixthPosition(const basegfx::B2DPoint& rNew) +{ + if(rNew != maSixthPosition) + { + maSixthPosition = rNew; + implResetGeometry(); + objectChange(); + } +} + +void AnchorOverlayObject::SetSeventhPosition(const basegfx::B2DPoint& rNew) +{ + if(rNew != maSeventhPosition) + { + maSeventhPosition = rNew; + implResetGeometry(); + objectChange(); + } +} + +void AnchorOverlayObject::SetTriPosition(const basegfx::B2DPoint& rPoint1,const basegfx::B2DPoint& rPoint2,const basegfx::B2DPoint& rPoint3, + const basegfx::B2DPoint& rPoint4,const basegfx::B2DPoint& rPoint5) +{ + if(rPoint1 != getBasePosition() + || rPoint2 != GetSecondPosition() + || rPoint3 != GetThirdPosition() + || rPoint4 != GetFourthPosition() + || rPoint5 != GetFifthPosition()) + { + maBasePosition = rPoint1; + maSecondPosition = rPoint2; + maThirdPosition = rPoint3; + maFourthPosition = rPoint4; + maFifthPosition = rPoint5; + + implResetGeometry(); + objectChange(); + } +} + +void AnchorOverlayObject::setLineSolid( const bool bNew ) +{ + if ( bNew != getLineSolid() ) + { + mbLineSolid = bNew; + objectChange(); + } +} + +void AnchorOverlayObject::SetAnchorState( const AnchorState aState) +{ + if ( mAnchorState != aState) + { + mAnchorState = aState; + objectChange(); + } +} + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/AnchorOverlayObject.hxx b/sw/source/uibase/docvw/AnchorOverlayObject.hxx new file mode 100644 index 000000000..744b2f062 --- /dev/null +++ b/sw/source/uibase/docvw/AnchorOverlayObject.hxx @@ -0,0 +1,120 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svx/sdr/overlay/overlayobject.hxx> +#include <tools/long.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> + +class SwView; +class SwRect; +class Point; + +namespace sw::sidebarwindows { + +enum class AnchorState +{ + All, + End, + Tri +}; + +class AnchorOverlayObject final : public sdr::overlay::OverlayObjectWithBasePosition +{ + public: + static std::unique_ptr<AnchorOverlayObject> CreateAnchorOverlayObject( SwView const & rDocView, + const SwRect& aAnchorRect, + tools::Long aPageBorder, + const Point& aLineStart, + const Point& aLineEnd, + const Color& aColorAnchor ); + + const basegfx::B2DPoint& GetSecondPosition() const { return maSecondPosition; } + const basegfx::B2DPoint& GetThirdPosition() const { return maThirdPosition; } + const basegfx::B2DPoint& GetFourthPosition() const { return maFourthPosition; } + const basegfx::B2DPoint& GetFifthPosition() const { return maFifthPosition; } + const basegfx::B2DPoint& GetSixthPosition() const { return maSixthPosition; } + const basegfx::B2DPoint& GetSeventhPosition() const { return maSeventhPosition; } + + void SetAllPosition( const basegfx::B2DPoint& rPoint1, + const basegfx::B2DPoint& rPoint2, + const basegfx::B2DPoint& rPoint3, + const basegfx::B2DPoint& rPoint4, + const basegfx::B2DPoint& rPoint5, + const basegfx::B2DPoint& rPoint6, + const basegfx::B2DPoint& rPoint7 ); + void SetTriPosition( const basegfx::B2DPoint& rPoint1, + const basegfx::B2DPoint& rPoint2, + const basegfx::B2DPoint& rPoint3, + const basegfx::B2DPoint& rPoint4, + const basegfx::B2DPoint& rPoint5 ); + void SetSixthPosition( const basegfx::B2DPoint& rNew ); + void SetSeventhPosition( const basegfx::B2DPoint& rNew ); + + void setLineSolid( const bool bNew ); + bool getLineSolid() const { return mbLineSolid; } + + void SetAnchorState( const AnchorState aState ); + AnchorState GetAnchorState() const { return mAnchorState; } + + private: + /* 6------------7 + 1 / + /4\ ---------------5 + 2 - 3 + */ + + basegfx::B2DPoint maSecondPosition; + basegfx::B2DPoint maThirdPosition; + basegfx::B2DPoint maFourthPosition; + basegfx::B2DPoint maFifthPosition; + basegfx::B2DPoint maSixthPosition; + basegfx::B2DPoint maSeventhPosition; + + // helpers to fill and reset geometry + void implEnsureGeometry(); + void implResetGeometry(); + + // geometry creation for OverlayObject + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + + // object's geometry + basegfx::B2DPolygon maTriangle; + basegfx::B2DPolygon maLine; + basegfx::B2DPolygon maLineTop; + AnchorState mAnchorState; + + bool mbLineSolid : 1; + + AnchorOverlayObject( const basegfx::B2DPoint& rBasePos, + const basegfx::B2DPoint& rSecondPos, + const basegfx::B2DPoint& rThirdPos, + const basegfx::B2DPoint& rFourthPos, + const basegfx::B2DPoint& rFifthPos, + const basegfx::B2DPoint& rSixthPos, + const basegfx::B2DPoint& rSeventhPos, + const Color& rBaseColor ); + public: + virtual ~AnchorOverlayObject() override; +}; + +} // end of namespace sw::annotation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/AnnotationMenuButton.cxx b/sw/source/uibase/docvw/AnnotationMenuButton.cxx new file mode 100644 index 000000000..1696f7e6d --- /dev/null +++ b/sw/source/uibase/docvw/AnnotationMenuButton.cxx @@ -0,0 +1,133 @@ +/* -*- 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 <AnnotationWin.hxx> +#include <strings.hrc> + +#include <unotools/useroptions.hxx> + +#include <vcl/event.hxx> + +#include <cmdid.h> + +#include <swtypes.hxx> + +namespace sw::annotation { + +IMPL_LINK(SwAnnotationWin, SelectHdl, const OString&, rIdent, void) +{ + if (rIdent.isEmpty()) + return; + + // tdf#136682 ensure this is the currently active sidebar win so the command + // operates in an active sidebar context + bool bSwitchedFocus = SetActiveSidebarWin(); + + if (rIdent == "reply") + ExecuteCommand(FN_REPLY); + if (rIdent == "resolve" || rIdent == "unresolve") + ExecuteCommand(FN_RESOLVE_NOTE); + else if (rIdent == "resolvethread" || rIdent == "unresolvethread") + ExecuteCommand(FN_RESOLVE_NOTE_THREAD); + else if (rIdent == "delete") + ExecuteCommand(FN_DELETE_COMMENT); + else if (rIdent == "deletethread") + ExecuteCommand(FN_DELETE_COMMENT_THREAD); + else if (rIdent == "deleteby") + ExecuteCommand(FN_DELETE_NOTE_AUTHOR); + else if (rIdent == "deleteall") + ExecuteCommand(FN_DELETE_ALL_NOTES); + else if (rIdent == "formatall") + ExecuteCommand(FN_FORMAT_ALL_NOTES); + + if (bSwitchedFocus) + UnsetActiveSidebarWin(); + GrabFocusToDocument(); +} + +IMPL_LINK_NOARG(SwAnnotationWin, ToggleHdl, weld::Toggleable&, void) +{ + if (!mxMenuButton->get_active()) + return; + + bool bReplyVis = true; + + bool bReadOnly = IsReadOnly(); + if (bReadOnly) + { + mxMenuButton->set_item_visible("reply", false); + bReplyVis = false; + mxMenuButton->set_item_visible("resolve", false); + mxMenuButton->set_item_visible("unresolve", false); + mxMenuButton->set_item_visible("resolvethread", false); + mxMenuButton->set_item_visible("unresolvethread", false); + mxMenuButton->set_item_visible("delete", false ); + } + else + { + mxMenuButton->set_item_visible("resolve", !IsResolved()); + mxMenuButton->set_item_visible("unresolve", IsResolved()); + mxMenuButton->set_item_visible("resolvethread", !IsThreadResolved()); + mxMenuButton->set_item_visible("unresolvethread", IsThreadResolved()); + mxMenuButton->set_item_visible("delete", !IsProtected()); + } + + mxMenuButton->set_item_visible("deletethread", !bReadOnly); + mxMenuButton->set_item_visible("deleteby", !bReadOnly); + mxMenuButton->set_item_visible("deleteall", !bReadOnly); + mxMenuButton->set_item_visible("formatall", !bReadOnly); + + if (IsProtected()) + { + mxMenuButton->set_item_visible("reply", false); + bReplyVis = false; + } + else + { + SvtUserOptions aUserOpt; + OUString sAuthor; + if ((sAuthor = aUserOpt.GetFullName()).isEmpty()) + { + if ((sAuthor = aUserOpt.GetID()).isEmpty()) + { + sAuthor = SwResId(STR_REDLINE_UNKNOWN_AUTHOR); + } + } + // do not allow to reply to ourself and no answer possible if this note is in a protected section + bReplyVis = sAuthor != GetAuthor(); + mxMenuButton->set_item_visible("reply", bReplyVis); + } + mxMenuButton->set_item_visible("sep1", bReplyVis); +} + +IMPL_LINK(SwAnnotationWin, KeyInputHdl, const KeyEvent&, rKeyEvt, bool) +{ + const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode(); + if (rKeyCode.GetCode() == KEY_TAB) + { + ActivatePostIt(); + GrabFocus(); + return true; + } + return false; +} + +} // end of namespace sw::annotation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/AnnotationWin.cxx b/sw/source/uibase/docvw/AnnotationWin.cxx new file mode 100644 index 000000000..b7858a973 --- /dev/null +++ b/sw/source/uibase/docvw/AnnotationWin.cxx @@ -0,0 +1,514 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <AnnotationWin.hxx> + +#include <PostItMgr.hxx> + +#include <strings.hrc> + +#include <uiobject.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +#include <svl/undo.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <svl/languageoptions.hxx> +#include <osl/diagnose.h> + +#include <editeng/eeitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/langitem.hxx> + +#include <editeng/editview.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editobj.hxx> +#include <editeng/outlobj.hxx> + +#include <comphelper/lok.hxx> +#include <docufld.hxx> +#include <txtfld.hxx> +#include <ndtxt.hxx> +#include <view.hxx> +#include <viewopt.hxx> +#include <wrtsh.hxx> +#include <docsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <SwUndoField.hxx> +#include <edtwin.hxx> +#include "ShadowOverlayObject.hxx" +#include "AnchorOverlayObject.hxx" +#include "OverlayRanges.hxx" +#include "SidebarTxtControl.hxx" +#include "SidebarWinAcc.hxx" + +#include <memory> + +namespace{ + +void collectUIInformation( const OUString& aevent , const OUString& aID ) +{ + EventDescription aDescription; + aDescription.aID = aID; + aDescription.aParameters = {{"" , ""}}; + aDescription.aAction = aevent; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "SwEditWinUIObject"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +namespace sw::annotation { + +// see AnnotationContents in sd for something similar +SwAnnotationWin::SwAnnotationWin( SwEditWin& rEditWin, + SwPostItMgr& aMgr, + SwSidebarItem& rSidebarItem, + SwFormatField* aField ) + : InterimItemWindow(&rEditWin, "modules/swriter/ui/annotation.ui", "Annotation") + , mrMgr(aMgr) + , mrView(rEditWin.GetView()) + , mnDeleteEventId(nullptr) + , meSidebarPosition(sw::sidebarwindows::SidebarPosition::NONE) + , mPageBorder(0) + , mbAnchorRectChanged(false) + , mbResolvedStateUpdated(false) + , mbMouseOver(false) + , mLayoutStatus(SwPostItHelper::INVISIBLE) + , mbReadonly(false) + , mbIsFollow(false) + , mrSidebarItem(rSidebarItem) + , mpAnchorFrame(rSidebarItem.maLayoutInfo.mpAnchorFrame) + , mpFormatField(aField) + , mpField( static_cast<SwPostItField*>(aField->GetField())) +{ + set_id("Comment"+OUString::number(mpField->GetPostItId())); + + m_xContainer->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl)); + + mpShadow = sidebarwindows::ShadowOverlayObject::CreateShadowOverlayObject( mrView ); + if ( mpShadow ) + { + mpShadow->setVisible(false); + } + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + mrMgr.ConnectSidebarWinToFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame), + mrSidebarItem.GetFormatField(), + *this ); +#endif + + if (SupportsDoubleBuffering()) + // When double-buffering, allow parents to paint on our area. That's + // necessary when parents paint the complete buffer. + SetParentClipMode(ParentClipMode::NoClip); +} + +SwAnnotationWin::~SwAnnotationWin() +{ + disposeOnce(); +} + +void SwAnnotationWin::dispose() +{ +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + mrMgr.DisconnectSidebarWinFromFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame), + *this ); +#endif + Disable(); + + mxSidebarTextControlWin.reset(); + mxSidebarTextControl.reset(); + + mxMetadataAuthor.reset(); + mxMetadataResolved.reset(); + mxMetadataDate.reset(); + mxVScrollbar.reset(); + + mpAnchor.reset(); + mpShadow.reset(); + + mpTextRangeOverlay.reset(); + + mxMenuButton.reset(); + + if (mnDeleteEventId) + Application::RemoveUserEvent(mnDeleteEventId); + + mpOutliner.reset(); + mpOutlinerView.reset(); + + InterimItemWindow::dispose(); +} + +void SwAnnotationWin::SetPostItText() +{ + //If the cursor was visible, then make it visible again after + //changing text, e.g. fdo#33599 + vcl::Cursor *pCursor = GetOutlinerView()->GetEditView().GetCursor(); + bool bCursorVisible = pCursor && pCursor->IsVisible(); + + //If the new text is the same as the old text, keep the same insertion + //point .e.g. fdo#33599 + mpField = static_cast<SwPostItField*>(mpFormatField->GetField()); + OUString sNewText = mpField->GetPar2(); + bool bTextUnchanged = sNewText == mpOutliner->GetEditEngine().GetText(); + ESelection aOrigSelection(GetOutlinerView()->GetEditView().GetSelection()); + + // get text from SwPostItField and insert into our textview + mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() ); + mpOutliner->EnableUndo( false ); + if( mpField->GetTextObject() ) + mpOutliner->SetText( *mpField->GetTextObject() ); + else + { + mpOutliner->Clear(); + GetOutlinerView()->SetAttribs(DefaultItem()); + GetOutlinerView()->InsertText(sNewText); + } + + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); + mpOutliner->EnableUndo( true ); + mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) ); + if (bTextUnchanged) + GetOutlinerView()->GetEditView().SetSelection(aOrigSelection); + if (bCursorVisible) + GetOutlinerView()->ShowCursor(); + Invalidate(); +} + +void SwAnnotationWin::SetResolved(bool resolved) +{ + bool oldState = IsResolved(); + static_cast<SwPostItField*>(mpFormatField->GetField())->SetResolved(resolved); + const SwViewOption* pVOpt = mrView.GetWrtShellPtr()->GetViewOptions(); + mrSidebarItem.mbShow = !IsResolved() || (pVOpt->IsResolvedPostIts()); + + mpTextRangeOverlay.reset(); + + if(IsResolved()) + mxMetadataResolved->show(); + else + mxMetadataResolved->hide(); + + if(IsResolved() != oldState) + mbResolvedStateUpdated = true; + UpdateData(); + Invalidate(); + collectUIInformation("SETRESOLVED",get_id()); +} + +void SwAnnotationWin::ToggleResolved() +{ + SetResolved(!IsResolved()); +} + +void SwAnnotationWin::ToggleResolvedForThread() +{ + GetTopReplyNote()->ToggleResolved(); + mrMgr.UpdateResolvedStatus(GetTopReplyNote()); + mrMgr.LayoutPostIts(); +} + +void SwAnnotationWin::DeleteThread() +{ + // Go to the top and delete each comment one by one + SwAnnotationWin *current, *topNote; + current = topNote = GetTopReplyNote(); + SwAnnotationWin* next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current); + + while(next && next->GetTopReplyNote() == topNote) + { + current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true ); + current = next; + next = mrMgr.GetNextPostIt(KEY_PAGEDOWN, current); + } + current->mnDeleteEventId = Application::PostUserEvent( LINK( current, SwAnnotationWin, DeleteHdl), nullptr, true ); +} + +bool SwAnnotationWin::IsResolved() const +{ + return static_cast<SwPostItField*>(mpFormatField->GetField())->GetResolved(); +} + +bool SwAnnotationWin::IsThreadResolved() +{ + /// First Get the top note + // then iterate downwards checking resolved status + SwAnnotationWin *pTopNote, *TopNote; + pTopNote = TopNote = GetTopReplyNote(); + if (!pTopNote->IsResolved()) + return false; + + SwAnnotationWin* pSidebarWin = mrMgr.GetNextPostIt(KEY_PAGEDOWN, pTopNote); + + while (pSidebarWin && pSidebarWin->GetTopReplyNote() == TopNote) + { + pTopNote = pSidebarWin; + if (!pTopNote->IsResolved()) + return false; + pSidebarWin = mrMgr.GetNextPostIt(KEY_PAGEDOWN, pSidebarWin); + } + return true; +} + +void SwAnnotationWin::UpdateData() +{ + if ( mpOutliner->IsModified() || mbResolvedStateUpdated ) + { + IDocumentUndoRedo & rUndoRedo( + mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo()); + std::unique_ptr<SwField> pOldField; + if (rUndoRedo.DoesUndo()) + { + pOldField = mpField->Copy(); + } + mpField->SetPar2(mpOutliner->GetEditEngine().GetText()); + mpField->SetTextObject(mpOutliner->CreateParaObject()); + if (rUndoRedo.DoesUndo()) + { + SwTextField *const pTextField = mpFormatField->GetTextField(); + SwPosition aPosition( pTextField->GetTextNode() ); + aPosition.nContent = pTextField->GetStart(); + rUndoRedo.AppendUndo( + std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, nullptr, true)); + } + // so we get a new layout of notes (anchor position is still the same and we would otherwise not get one) + mrMgr.SetLayout(); + // #i98686# if we have several views, all notes should update their text + if(mbResolvedStateUpdated) + mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::RESOLVED)); + else + mpFormatField->Broadcast(SwFormatFieldHint( nullptr, SwFormatFieldHintWhich::CHANGED)); + mrView.GetDocShell()->SetModified(); + } + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); + mbResolvedStateUpdated = false; +} + +void SwAnnotationWin::Delete() +{ + collectUIInformation("DELETE",get_id()); + if (!mrView.GetWrtShellPtr()->GotoField(*mpFormatField)) + return; + + if ( mrMgr.GetActiveSidebarWin() == this) + { + mrMgr.SetActiveSidebarWin(nullptr); + // if the note is empty, the previous line will send a delete event, but we are already there + if (mnDeleteEventId) + { + Application::RemoveUserEvent(mnDeleteEventId); + mnDeleteEventId = nullptr; + } + } + // we delete the field directly, the Mgr cleans up the PostIt by listening + GrabFocusToDocument(); + mrView.GetWrtShellPtr()->ClearMark(); + mrView.GetWrtShellPtr()->DelRight(); +} + +void SwAnnotationWin::GotoPos() +{ + mrView.GetDocShell()->GetWrtShell()->GotoField(*mpFormatField); +} + +sal_uInt32 SwAnnotationWin::MoveCaret() +{ + // if this is an answer, do not skip over all following ones, but insert directly behind the current one + // but when just leaving a note, skip all following ones as well to continue typing + return mrMgr.IsAnswer() + ? 1 + : 1 + CountFollowing(); +} + +// returns a non-zero postit parent id, if exists, otherwise 0 for root comments +sal_uInt32 SwAnnotationWin::CalcParent() +{ + SwTextField* pTextField = mpFormatField->GetTextField(); + SwPosition aPosition( pTextField->GetTextNode() ); + aPosition.nContent = pTextField->GetStart(); + SwTextAttr * const pTextAttr = + pTextField->GetTextNode().GetTextAttrForCharAt( + aPosition.nContent.GetIndex() - 1, + RES_TXTATR_ANNOTATION ); + const SwField* pField = pTextAttr ? pTextAttr->GetFormatField().GetField() : nullptr; + sal_uInt32 nParentId = 0; + if (pField && pField->Which() == SwFieldIds::Postit) + { + const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField); + nParentId = pPostItField->GetPostItId(); + } + return nParentId; +} + +// counts how many SwPostItField we have right after the current one +sal_uInt32 SwAnnotationWin::CountFollowing() +{ + sal_uInt32 aCount = 1; // we start with 1, so we have to subtract one at the end again + SwTextField* pTextField = mpFormatField->GetTextField(); + SwPosition aPosition( pTextField->GetTextNode() ); + aPosition.nContent = pTextField->GetStart(); + + SwTextAttr * pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt( + aPosition.nContent.GetIndex() + 1, + RES_TXTATR_ANNOTATION ); + SwField* pField = pTextAttr + ? const_cast<SwField*>(pTextAttr->GetFormatField().GetField()) + : nullptr; + while ( pField && ( pField->Which()== SwFieldIds::Postit ) ) + { + aCount++; + pTextAttr = pTextField->GetTextNode().GetTextAttrForCharAt( + aPosition.nContent.GetIndex() + aCount, + RES_TXTATR_ANNOTATION ); + pField = pTextAttr + ? const_cast<SwField*>(pTextAttr->GetFormatField().GetField()) + : nullptr; + } + return aCount - 1; +} + +void SwAnnotationWin::InitAnswer(OutlinerParaObject const & rText) +{ + // If tiled annotations is off in lok case, skip adding additional reply text. + if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) + return; + + //collect our old meta data + SwAnnotationWin* pWin = mrMgr.GetNextPostIt(KEY_PAGEUP, this); + if (!pWin) + return; + + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData(); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, pWin->GetAuthor()); + const OUString aText = aRewriter.Apply(SwResId(STR_REPLY)) + + " (" + rLocalData.getDate( pWin->GetDate()) + + ", " + rLocalData.getTime( pWin->GetTime(), false) + + "): \""; + GetOutlinerView()->InsertText(aText); + + // insert old, selected text or "..." + // TODO: iterate over all paragraphs, not only first one to find out if it is empty + if (!rText.GetTextObject().GetText(0).isEmpty()) + GetOutlinerView()->GetEditView().InsertText(rText.GetTextObject()); + else + GetOutlinerView()->InsertText("..."); + GetOutlinerView()->InsertText("\"\n"); + + GetOutlinerView()->SetSelection(ESelection(0,0,EE_PARA_ALL,EE_TEXTPOS_ALL)); + SfxItemSet aAnswerSet( mrView.GetDocShell()->GetPool() ); + aAnswerSet.Put(SvxFontHeightItem(200,80,EE_CHAR_FONTHEIGHT)); + aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC)); + GetOutlinerView()->SetAttribs(aAnswerSet); + GetOutlinerView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT)); + + //remove all attributes and reset our standard ones + GetOutlinerView()->GetEditView().RemoveAttribsKeepLanguages(true); + GetOutlinerView()->SetAttribs(DefaultItem()); + // lets insert an undo step so the initial text can be easily deleted + // but do not use UpdateData() directly, would set modified state again and reentrance into Mgr + mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() ); + IDocumentUndoRedo & rUndoRedo( + mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo()); + std::unique_ptr<SwField> pOldField; + if (rUndoRedo.DoesUndo()) + { + pOldField = mpField->Copy(); + } + mpField->SetPar2(mpOutliner->GetEditEngine().GetText()); + mpField->SetTextObject(mpOutliner->CreateParaObject()); + if (rUndoRedo.DoesUndo()) + { + SwTextField *const pTextField = mpFormatField->GetTextField(); + SwPosition aPosition( pTextField->GetTextNode() ); + aPosition.nContent = pTextField->GetStart(); + rUndoRedo.AppendUndo( + std::make_unique<SwUndoFieldFromDoc>(aPosition, *pOldField, *mpField, nullptr, true)); + } + mpOutliner->SetModifyHdl( LINK( this, SwAnnotationWin, ModifyHdl ) ); + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); +} + +void SwAnnotationWin::UpdateText(const OUString& aText) +{ + mpOutliner->Clear(); + GetOutlinerView()->InsertText(aText); + UpdateData(); +} + +SvxLanguageItem SwAnnotationWin::GetLanguage() const +{ + // set initial language for outliner + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( mpField->GetLanguage() ); + sal_uInt16 nLangWhichId = 0; + switch (nScriptType) + { + case SvtScriptType::LATIN : nLangWhichId = EE_CHAR_LANGUAGE ; break; + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: OSL_FAIL("GetLanguage: wrong script type"); + } + return SvxLanguageItem(mpField->GetLanguage(),nLangWhichId); +} + +bool SwAnnotationWin::IsProtected() const +{ + return mbReadonly || + GetLayoutStatus() == SwPostItHelper::DELETED || + ( mpFormatField && mpFormatField->IsProtect() ); +} + +OUString SwAnnotationWin::GetAuthor() const +{ + return mpField->GetPar1(); +} + +Date SwAnnotationWin::GetDate() const +{ + return mpField->GetDate(); +} + +tools::Time SwAnnotationWin::GetTime() const +{ + return mpField->GetTime(); +} + +FactoryFunction SwAnnotationWin::GetUITestFactory() const +{ + return CommentUIObject::create; +} + +} // end of namespace sw::annotation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/AnnotationWin2.cxx b/sw/source/uibase/docvw/AnnotationWin2.cxx new file mode 100644 index 000000000..fb414a4f1 --- /dev/null +++ b/sw/source/uibase/docvw/AnnotationWin2.cxx @@ -0,0 +1,1450 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <sal/config.h> + +#include <cstddef> + +#include "SidebarWinAcc.hxx" +#include <PostItMgr.hxx> +#include <AnnotationWin.hxx> +#include <IDocumentUndoRedo.hxx> +#include <basegfx/range/b2drange.hxx> +#include "SidebarTxtControl.hxx" +#include "AnchorOverlayObject.hxx" +#include "ShadowOverlayObject.hxx" +#include "OverlayRanges.hxx" + +#include <strings.hrc> + +#include <viewopt.hxx> +#include <cmdid.h> + +#include <editeng/fhgtitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/editview.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editeng.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/outlobj.hxx> + +#include <svl/undo.hxx> +#include <svl/stritem.hxx> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> + +#include <vcl/decoview.hxx> +#include <vcl/event.hxx> +#include <vcl/gradient.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +#include <edtwin.hxx> +#include <view.hxx> +#include <docsh.hxx> +#include <wrtsh.hxx> +#include <doc.hxx> +#include <swmodule.hxx> + +#include <SwRewriter.hxx> +#include <txtannotationfld.hxx> +#include <ndtxt.hxx> + +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <osl/diagnose.h> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <memory> +#include <comphelper/lok.hxx> + +using namespace sw::sidebarwindows; + +namespace +{ + +void collectUIInformation( const OUString& aevent , const OUString& aID ) +{ + EventDescription aDescription; + aDescription.aID = aID; + aDescription.aParameters = {{"" , ""}}; + aDescription.aAction = aevent; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "SwEditWinUIObject"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +namespace sw::annotation { + +#define METABUTTON_WIDTH 16 +#define METABUTTON_HEIGHT 18 +#define POSTIT_MINIMUMSIZE_WITHOUT_META 50 + +void SwAnnotationWin::PaintTile(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + bool bMenuButtonVisible = mxMenuButton->get_visible(); + // No point in showing this button till click on it are not handled. + if (bMenuButtonVisible) + mxMenuButton->hide(); + + // draw left over space + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + rRenderContext.SetFillColor(COL_BLACK); + else + rRenderContext.SetFillColor(mColorDark); + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(rRect); + + m_xContainer->draw(rRenderContext, rRect.TopLeft(), GetSizePixel()); + + const drawinglayer::geometry::ViewInformation2D aViewInformation; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext, aViewInformation)); + + // drawinglayer sets the map mode to pixels, not needed here. + rRenderContext.Pop(); + // Work in document-global twips. + rRenderContext.Pop(); + if (mpAnchor) + pProcessor->process(mpAnchor->getOverlayObjectPrimitive2DSequence()); + if (mpTextRangeOverlay) + pProcessor->process(mpTextRangeOverlay->getOverlayObjectPrimitive2DSequence()); + + rRenderContext.Push(vcl::PushFlags::NONE); + pProcessor.reset(); + rRenderContext.Push(vcl::PushFlags::NONE); + + if (bMenuButtonVisible) + mxMenuButton->show(); +} + +bool SwAnnotationWin::IsHitWindow(const Point& rPointLogic) +{ + tools::Rectangle aRectangleLogic(EditWin().PixelToLogic(GetPosPixel()), EditWin().PixelToLogic(GetSizePixel())); + return aRectangleLogic.Contains(rPointLogic); +} + +void SwAnnotationWin::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark) +{ + mxSidebarTextControl->SetCursorLogicPosition(rPosition, bPoint, bClearMark); +} + +void SwAnnotationWin::DrawForPage(OutputDevice* pDev, const Point& rPt) +{ + // tdf#143511 unclip SysObj so get_extents_relative_to of children + // of the SysObj can provide meaningful results + UnclipVisibleSysObj(); + + pDev->Push(); + + pDev->SetFillColor(mColorDark); + pDev->SetLineColor(); + + pDev->SetTextColor(mColorAnchor); + vcl::Font aFont = maLabelFont; + aFont.SetFontHeight(aFont.GetFontHeight() * 20); + pDev->SetFont(aFont); + + Size aSz = PixelToLogic(GetSizePixel()); + + pDev->DrawRect(tools::Rectangle(rPt, aSz)); + + if (mxMetadataAuthor->get_visible()) + { + int x, y, width, height; + mxMetadataAuthor->get_extents_relative_to(*m_xContainer, x, y, width, height); + Point aPos(rPt + PixelToLogic(Point(x, y))); + Size aSize(PixelToLogic(Size(width, height))); + + pDev->Push(vcl::PushFlags::CLIPREGION); + pDev->IntersectClipRegion(tools::Rectangle(aPos, aSize)); + pDev->DrawText(aPos, mxMetadataAuthor->get_label()); + pDev->Pop(); + } + + if (mxMetadataDate->get_visible()) + { + int x, y, width, height; + mxMetadataDate->get_extents_relative_to(*m_xContainer, x, y, width, height); + Point aPos(rPt + PixelToLogic(Point(x, y))); + Size aSize(PixelToLogic(Size(width, height))); + + pDev->Push(vcl::PushFlags::CLIPREGION); + pDev->IntersectClipRegion(tools::Rectangle(aPos, aSize)); + pDev->DrawText(aPos, mxMetadataDate->get_label()); + pDev->Pop(); + } + + if (mxMetadataResolved->get_visible()) + { + int x, y, width, height; + mxMetadataResolved->get_extents_relative_to(*m_xContainer, x, y, width, height); + Point aPos(rPt + PixelToLogic(Point(x, y))); + Size aSize(PixelToLogic(Size(width, height))); + + pDev->Push(vcl::PushFlags::CLIPREGION); + pDev->IntersectClipRegion(tools::Rectangle(aPos, aSize)); + pDev->DrawText(aPos, mxMetadataResolved->get_label()); + pDev->Pop(); + } + + mxSidebarTextControl->DrawForPage(pDev, rPt); + + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice( + *pDev, aNewViewInfos )); + + if (mpAnchor) + pProcessor->process(mpAnchor->getOverlayObjectPrimitive2DSequence()); + if (mpTextRangeOverlay) + pProcessor->process(mpTextRangeOverlay->getOverlayObjectPrimitive2DSequence()); + pProcessor.reset(); + + if (mxVScrollbar->get_vpolicy() != VclPolicyType::NEVER) + { + // if there is a scrollbar shown, draw "..." to indicate the comment isn't + // completely shown + int x, y, width, height; + mxMenuButton->get_extents_relative_to(*m_xContainer, x, y, width, height); + Point aPos(rPt + PixelToLogic(Point(x, y))); + pDev->DrawText(aPos, "..."); + } + + pDev->Pop(); +} + +void SwAnnotationWin::SetPosSizePixelRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, + const SwRect& aAnchorRect, const tools::Long aPageBorder) +{ + mPosSize = tools::Rectangle(Point(nX,nY),Size(nWidth,nHeight)); + if (!mAnchorRect.IsEmpty() && mAnchorRect != aAnchorRect) + mbAnchorRectChanged = true; + mAnchorRect = aAnchorRect; + mPageBorder = aPageBorder; +} + +void SwAnnotationWin::SetSize( const Size& rNewSize ) +{ + mPosSize.SetSize(rNewSize); +} + +void SwAnnotationWin::SetVirtualPosSize( const Point& aPoint, const Size& aSize) +{ + mPosSize = tools::Rectangle(aPoint,aSize); +} + +void SwAnnotationWin::TranslateTopPosition(const tools::Long aAmount) +{ + mPosSize.Move(0,aAmount); +} + +void SwAnnotationWin::ShowAnchorOnly(const Point &aPoint) +{ + HideNote(); + SetPosAndSize(); + if (mpAnchor) + { + mpAnchor->SetSixthPosition(basegfx::B2DPoint(aPoint.X(),aPoint.Y())); + mpAnchor->SetSeventhPosition(basegfx::B2DPoint(aPoint.X(),aPoint.Y())); + mpAnchor->SetAnchorState(AnchorState::All); + mpAnchor->setVisible(true); + } + if (mpShadow) + mpShadow->setVisible(false); +} + +SfxItemSet SwAnnotationWin::DefaultItem() +{ + SfxItemSet aItem( mrView.GetDocShell()->GetPool() ); + aItem.Put(SvxFontHeightItem(200,100,EE_CHAR_FONTHEIGHT)); + return aItem; +} + +void SwAnnotationWin::InitControls() +{ + // window controls for author and date + mxMetadataAuthor = m_xBuilder->weld_label("author"); + mxMetadataAuthor->set_accessible_name( SwResId( STR_ACCESS_ANNOTATION_AUTHOR_NAME ) ); + mxMetadataAuthor->set_direction(AllSettings::GetLayoutRTL()); + + maLabelFont = Application::GetSettings().GetStyleSettings().GetLabelFont(); + maLabelFont.SetFontHeight(8); + + // we should leave this setting alone, but for this we need a better layout algo + // with variable meta size height + mxMetadataAuthor->set_font(maLabelFont); + + mxMetadataDate = m_xBuilder->weld_label("date"); + mxMetadataDate->set_accessible_name( SwResId( STR_ACCESS_ANNOTATION_DATE_NAME ) ); + mxMetadataDate->set_direction(AllSettings::GetLayoutRTL()); + mxMetadataDate->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl)); + + // we should leave this setting alone, but for this we need a better layout algo + // with variable meta size height + mxMetadataDate->set_font(maLabelFont); + + mxMetadataResolved = m_xBuilder->weld_label("resolved"); + mxMetadataResolved->set_accessible_name( SwResId( STR_ACCESS_ANNOTATION_RESOLVED_NAME ) ); + mxMetadataResolved->set_direction(AllSettings::GetLayoutRTL()); + mxMetadataResolved->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl)); + + // we should leave this setting alone, but for this we need a better layout algo + // with variable meta size height + mxMetadataResolved->set_font(maLabelFont); + mxMetadataResolved->set_label(SwResId(STR_ACCESS_ANNOTATION_RESOLVED_NAME)); + + SwDocShell* aShell = mrView.GetDocShell(); + mpOutliner.reset(new Outliner(&aShell->GetPool(),OutlinerMode::TextObject)); + aShell->GetDoc()->SetCalcFieldValueHdl( mpOutliner.get() ); + mpOutliner->SetUpdateLayout( true ); + + mpOutlinerView.reset(new OutlinerView(mpOutliner.get(), nullptr)); + mpOutliner->InsertView(mpOutlinerView.get()); + + //create Scrollbars + mxVScrollbar = m_xBuilder->weld_scrolled_window("scrolledwindow", true); + + mxMenuButton = m_xBuilder->weld_menu_button("menubutton"); + mxMenuButton->set_size_request(METABUTTON_WIDTH, METABUTTON_HEIGHT); + + // actual window which holds the user text + mxSidebarTextControl.reset(new SidebarTextControl(*this, mrView, mrMgr)); + mxSidebarTextControlWin.reset(new weld::CustomWeld(*m_xBuilder, "editview", *mxSidebarTextControl)); + mxSidebarTextControl->SetPointer(PointerStyle::Text); + + Rescale(); + + mpOutlinerView->SetBackgroundColor(COL_TRANSPARENT); + mpOutlinerView->SetOutputArea( PixelToLogic( tools::Rectangle(0,0,1,1) ) ); + + mpOutlinerView->SetAttribs(DefaultItem()); + + mxVScrollbar->set_direction(false); + mxVScrollbar->connect_vadjustment_changed(LINK(this, SwAnnotationWin, ScrollHdl)); + mxVScrollbar->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl)); + + const SwViewOption* pVOpt = mrView.GetWrtShellPtr()->GetViewOptions(); + EEControlBits nCntrl = mpOutliner->GetControlWord(); + // TODO: crash when AUTOCOMPLETE enabled + nCntrl |= EEControlBits::MARKFIELDS | EEControlBits::PASTESPECIAL | EEControlBits::AUTOCORRECT | EEControlBits::USECHARATTRIBS; // | EEControlBits::AUTOCOMPLETE; + if (SwViewOption::IsFieldShadings()) + nCntrl |= EEControlBits::MARKFIELDS; + else + nCntrl &= ~EEControlBits::MARKFIELDS; + if (pVOpt->IsOnlineSpell()) + nCntrl |= EEControlBits::ONLINESPELLING; + else + nCntrl &= ~EEControlBits::ONLINESPELLING; + mpOutliner->SetControlWord(nCntrl); + + std::size_t aIndex = SW_MOD()->InsertRedlineAuthor(GetAuthor()); + SetColor( SwPostItMgr::GetColorDark(aIndex), + SwPostItMgr::GetColorLight(aIndex), + SwPostItMgr::GetColorAnchor(aIndex)); + + CheckMetaText(); + + // expand %1 "Author" + OUString aText = mxMenuButton->get_item_label("deleteby"); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, GetAuthor()); + aText = aRewriter.Apply(aText); + mxMenuButton->set_item_label("deleteby", aText); + + mxMenuButton->set_accessible_name(SwResId(STR_ACCESS_ANNOTATION_BUTTON_NAME)); + mxMenuButton->set_accessible_description(SwResId(STR_ACCESS_ANNOTATION_BUTTON_DESC)); + mxMenuButton->set_tooltip_text(SwResId(STR_ACCESS_ANNOTATION_BUTTON_DESC)); + + mxMenuButton->connect_toggled(LINK(this, SwAnnotationWin, ToggleHdl)); + mxMenuButton->connect_selected(LINK(this, SwAnnotationWin, SelectHdl)); + mxMenuButton->connect_key_press(LINK(this, SwAnnotationWin, KeyInputHdl)); + mxMenuButton->connect_mouse_move(LINK(this, SwAnnotationWin, MouseMoveHdl)); + + SetLanguage(GetLanguage()); + GetOutlinerView()->StartSpeller(mxSidebarTextControl->GetDrawingArea()); + SetPostItText(); + mpOutliner->CompleteOnlineSpelling(); + + mxSidebarTextControl->Show(); + mxMetadataAuthor->show(); + mxMetadataDate->show(); + mxMetadataResolved->set_visible(IsResolved()); + mxVScrollbar->set_vpolicy(VclPolicyType::ALWAYS); +} + +void SwAnnotationWin::CheckMetaText() +{ + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData(); + OUString sMeta = GetAuthor(); + if (sMeta.isEmpty()) + { + sMeta = SwResId(STR_NOAUTHOR); + } + else if (sMeta.getLength() > 23) + { + sMeta = OUString::Concat(sMeta.subView(0, 20)) + "..."; + } + if ( mxMetadataAuthor->get_label() != sMeta ) + { + mxMetadataAuthor->set_label(sMeta); + } + + Date aDate = GetDate(); + if (aDate.IsValidAndGregorian() ) + { + sMeta = rLocalData.getDate(aDate); + } + else + { + sMeta = SwResId(STR_NODATE); + } + if (GetTime().GetTime()!=0) + { + sMeta += " " + rLocalData.getTime( GetTime(),false ); + } + if ( mxMetadataDate->get_label() != sMeta ) + { + mxMetadataDate->set_label(sMeta); + } + + std::size_t aIndex = SW_MOD()->InsertRedlineAuthor(GetAuthor()); + SetColor( SwPostItMgr::GetColorDark(aIndex), + SwPostItMgr::GetColorLight(aIndex), + SwPostItMgr::GetColorAnchor(aIndex)); +} + +static Color ColorFromAlphaColor(const sal_uInt8 aTransparency, const Color& aFront, const Color& aBack) +{ + return Color(sal_uInt8(aFront.GetRed() * aTransparency / 255.0 + aBack.GetRed() * (1 - aTransparency / 255.0)), + sal_uInt8(aFront.GetGreen() * aTransparency / 255.0 + aBack.GetGreen() * (1 - aTransparency / 255.0)), + sal_uInt8(aFront.GetBlue() * aTransparency / 255.0 + aBack.GetBlue() * (1 - aTransparency / 255.0))); +} + +void SwAnnotationWin::SetMenuButtonColors() +{ + if (!mxMenuButton) + return; + + mxMenuButton->set_background(mColorDark); + + const Fraction& rFraction = mrView.GetWrtShellPtr()->GetOut()->GetMapMode().GetScaleY(); + + ScopedVclPtrInstance<VirtualDevice> xVirDev; + Size aSize(tools::Long(METABUTTON_WIDTH * rFraction), + tools::Long(METABUTTON_HEIGHT * rFraction)); + tools::Rectangle aRect(Point(0, 0), aSize); + xVirDev->SetOutputSizePixel(aSize); + + Gradient aGradient(GradientStyle::Linear, + ColorFromAlphaColor(15, mColorAnchor, mColorDark), + ColorFromAlphaColor(80, mColorAnchor, mColorDark)); + xVirDev->DrawGradient(aRect, aGradient); + + //draw rect around button + xVirDev->SetFillColor(); + xVirDev->SetLineColor(ColorFromAlphaColor(90, mColorAnchor, mColorDark)); + xVirDev->DrawRect(aRect); + + tools::Rectangle aSymbolRect(aRect); + // 25% distance to the left and right button border + const tools::Long nBorderDistanceLeftAndRight = ((aSymbolRect.GetWidth() * 250) + 500) / 1000; + aSymbolRect.AdjustLeft(nBorderDistanceLeftAndRight ); + aSymbolRect.AdjustRight( -nBorderDistanceLeftAndRight ); + // 40% distance to the top button border + const tools::Long nBorderDistanceTop = ((aSymbolRect.GetHeight() * 400) + 500) / 1000; + aSymbolRect.AdjustTop(nBorderDistanceTop ); + // 15% distance to the bottom button border + const tools::Long nBorderDistanceBottom = ((aSymbolRect.GetHeight() * 150) + 500) / 1000; + aSymbolRect.AdjustBottom( -nBorderDistanceBottom ); + DecorationView aDecoView(xVirDev.get()); + aDecoView.DrawSymbol(aSymbolRect, SymbolType::SPIN_DOWN, GetTextColor(), + DrawSymbolFlags::NONE); + mxMenuButton->set_image(xVirDev); + mxMenuButton->set_size_request(aSize.Width() + 4, aSize.Height() + 4); +} + +void SwAnnotationWin::Rescale() +{ + // On Android, this method leads to invoke ImpEditEngine::UpdateViews + // which hides the text cursor. Moreover it causes sudden document scroll + // when modifying a commented text. Not clear the root cause, + // anyway skipping this method fixes the problem, and there should be + // no side effect, since the client has disabled annotations rendering. + if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) + return; + + MapMode aMode = GetParent()->GetMapMode(); + aMode.SetOrigin( Point() ); + SetMapMode( aMode ); + mxSidebarTextControl->SetMapMode( aMode ); + const Fraction& rFraction = mrView.GetWrtShellPtr()->GetOut()->GetMapMode().GetScaleY(); + + vcl::Font aFont = maLabelFont; + sal_Int32 nHeight = tools::Long(aFont.GetFontHeight() * rFraction); + aFont.SetFontHeight( nHeight ); + + if (mxMetadataAuthor) + mxMetadataAuthor->set_font(aFont); + if (mxMetadataDate) + mxMetadataDate->set_font(aFont); + if (mxMetadataResolved) + mxMetadataResolved->set_font(aFont); + SetMenuButtonColors(); + if (mxVScrollbar) + mxVScrollbar->set_scroll_thickness(GetPrefScrollbarWidth()); +} + +void SwAnnotationWin::SetPosAndSize() +{ + bool bChange = false; + + if (GetSizePixel() != mPosSize.GetSize()) + { + bChange = true; + SetSizePixel(mPosSize.GetSize()); + + DoResize(); + } + + if (GetPosPixel().X() != mPosSize.Left() || (std::abs(GetPosPixel().Y() - mPosSize.Top()) > 5) ) + { + bChange = true; + SetPosPixel(mPosSize.TopLeft()); + + Point aLineStart; + Point aLineEnd ; + switch ( meSidebarPosition ) + { + case sw::sidebarwindows::SidebarPosition::LEFT: + { + aLineStart = EditWin().PixelToLogic( Point(GetPosPixel().X()+GetSizePixel().Width(),GetPosPixel().Y()-1) ); + aLineEnd = EditWin().PixelToLogic( Point(GetPosPixel().X(),GetPosPixel().Y()-1) ); + } + break; + case sw::sidebarwindows::SidebarPosition::RIGHT: + { + aLineStart = EditWin().PixelToLogic( Point(GetPosPixel().X(),GetPosPixel().Y()-1) ); + aLineEnd = EditWin().PixelToLogic( Point(GetPosPixel().X()+GetSizePixel().Width(),GetPosPixel().Y()-1) ); + } + break; + default: + OSL_FAIL( "<SwAnnotationWin::SetPosAndSize()> - unexpected position of sidebar" ); + break; + } + + // LOK has map mode disabled, and we still want to perform pixel -> + // twips conversion for the size of the line above the note. + if (comphelper::LibreOfficeKit::isActive() && !EditWin().IsMapModeEnabled()) + { + EditWin().EnableMapMode(); + Size aSize(aLineEnd.getX() - aLineStart.getX(), aLineEnd.getY() - aLineStart.getY()); + aSize = EditWin().PixelToLogic(aSize); + aLineEnd = aLineStart; + aLineEnd.Move(aSize.getWidth(), aSize.getHeight()); + EditWin().EnableMapMode(false); + } + + if (mpAnchor) + { + mpAnchor->SetAllPosition( basegfx::B2DPoint( mAnchorRect.Left() , mAnchorRect.Bottom() - 5* 15), + basegfx::B2DPoint( mAnchorRect.Left()-5*15 , mAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( mAnchorRect.Left()+5*15 , mAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( mAnchorRect.Left(), mAnchorRect.Bottom()+2*15), + basegfx::B2DPoint( mPageBorder ,mAnchorRect.Bottom()+2*15), + basegfx::B2DPoint( aLineStart.X(),aLineStart.Y()), + basegfx::B2DPoint( aLineEnd.X(),aLineEnd.Y())); + } + else + { + mpAnchor = AnchorOverlayObject::CreateAnchorOverlayObject( mrView, + mAnchorRect, + mPageBorder, + aLineStart, + aLineEnd, + mColorAnchor ); + if ( mpAnchor ) + { + mpAnchor->setVisible(true); + mpAnchor->SetAnchorState(AnchorState::Tri); + if (HasChildPathFocus()) + { + mpAnchor->setLineSolid(true); + } + } + } + } + else + { + if ( mpAnchor && + ( mpAnchor->getBasePosition() != basegfx::B2DPoint( mAnchorRect.Left() , mAnchorRect.Bottom()-5*15) ) ) + { + mpAnchor->SetTriPosition( basegfx::B2DPoint( mAnchorRect.Left() , mAnchorRect.Bottom() - 5* 15), + basegfx::B2DPoint( mAnchorRect.Left()-5*15 , mAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( mAnchorRect.Left()+5*15 , mAnchorRect.Bottom()+5*15), + basegfx::B2DPoint( mAnchorRect.Left(), mAnchorRect.Bottom()+2*15), + basegfx::B2DPoint( mPageBorder , mAnchorRect.Bottom()+2*15)); + } + } + + if (mpShadow && bChange) + { + Point aStart = EditWin().PixelToLogic(GetPosPixel()+Point(0,GetSizePixel().Height())); + Point aEnd = EditWin().PixelToLogic(GetPosPixel()+Point(GetSizePixel().Width()-1,GetSizePixel().Height())); + mpShadow->SetPosition(basegfx::B2DPoint(aStart.X(),aStart.Y()), basegfx::B2DPoint(aEnd.X(),aEnd.Y())); + } + + if (mrMgr.ShowNotes()) + { + if (IsFollow() && !HasChildPathFocus()) + { + // #i111964# + if ( mpAnchor ) + { + mpAnchor->SetAnchorState(AnchorState::End); + } + } + else + { + // #i111964# + if ( mpAnchor ) + { + mpAnchor->SetAnchorState(AnchorState::All); + } + SwAnnotationWin* pWin = GetTopReplyNote(); + // #i111964# + if ( pWin != this && pWin->Anchor() ) + { + pWin->Anchor()->SetAnchorState(AnchorState::End); + } + } + } + + + // text range overlay + maAnnotationTextRanges.clear(); + if ( mrSidebarItem.maLayoutInfo.mnStartNodeIdx != SwNodeOffset(0) + && mrSidebarItem.maLayoutInfo.mnStartContent != -1 ) + { + const SwTextAnnotationField* pTextAnnotationField = + dynamic_cast< const SwTextAnnotationField* >( mrSidebarItem.GetFormatField().GetTextField() ); + SwTextNode* pTextNode = pTextAnnotationField ? pTextAnnotationField->GetpTextNode() : nullptr; + SwContentNode* pContentNd = nullptr; + if (pTextNode) + { + SwNodes& rNds = pTextNode->GetDoc().GetNodes(); + pContentNd = rNds[mrSidebarItem.maLayoutInfo.mnStartNodeIdx]->GetContentNode(); + } + if (pContentNd) + { + SwPosition aStartPos( *pContentNd, mrSidebarItem.maLayoutInfo.mnStartContent ); + SwShellCursor* pTmpCursor = nullptr; + const bool bTableCursorNeeded = pTextNode->FindTableBoxStartNode() != pContentNd->FindTableBoxStartNode(); + if ( bTableCursorNeeded ) + { + SwShellTableCursor* pTableCursor = new SwShellTableCursor( mrView.GetWrtShell(), aStartPos ); + pTableCursor->SetMark(); + pTableCursor->GetMark()->nNode = *pTextNode; + pTableCursor->GetMark()->nContent.Assign( pTextNode, pTextAnnotationField->GetStart()+1 ); + pTableCursor->NewTableSelection(); + pTmpCursor = pTableCursor; + } + else + { + SwShellCursor* pCursor = new SwShellCursor( mrView.GetWrtShell(), aStartPos ); + pCursor->SetMark(); + pCursor->GetMark()->nNode = *pTextNode; + pCursor->GetMark()->nContent.Assign( pTextNode, pTextAnnotationField->GetStart()+1 ); + pTmpCursor = pCursor; + } + std::unique_ptr<SwShellCursor> pTmpCursorForAnnotationTextRange( pTmpCursor ); + + // For annotation text range rectangles to be calculated correctly, + // we need the map mode disabled + bool bDisableMapMode = comphelper::LibreOfficeKit::isActive() && EditWin().IsMapModeEnabled(); + if (bDisableMapMode) + EditWin().EnableMapMode(false); + + if (mrSidebarItem.maLayoutInfo.mPositionFromCommentAnchor) + pTmpCursorForAnnotationTextRange->FillRects(); + + if (bDisableMapMode) + EditWin().EnableMapMode(); + + SwRects* pRects(pTmpCursorForAnnotationTextRange.get()); + for(const SwRect & rNextRect : *pRects) + { + const tools::Rectangle aPntRect(rNextRect.SVRect()); + maAnnotationTextRanges.emplace_back( + aPntRect.Left(), aPntRect.Top(), + aPntRect.Right() + 1, aPntRect.Bottom() + 1); + } + } + } + + if (mrMgr.ShowNotes() && !maAnnotationTextRanges.empty()) + { + if ( mpTextRangeOverlay != nullptr ) + { + mpTextRangeOverlay->setRanges( std::vector(maAnnotationTextRanges) ); + if ( mpAnchor != nullptr && mpAnchor->getLineSolid() ) + { + mpTextRangeOverlay->ShowSolidBorder(); + } + else + { + mpTextRangeOverlay->HideSolidBorder(); + } + } + else if (!IsFollow()) + { + // This window is not a reply, then draw its range overlay. + mpTextRangeOverlay = + sw::overlay::OverlayRanges::CreateOverlayRange( + mrView, + mColorAnchor, + std::vector(maAnnotationTextRanges), + mpAnchor && mpAnchor->getLineSolid() ); + } + } + else + { + mpTextRangeOverlay.reset(); + } +} + +void SwAnnotationWin::DoResize() +{ + tools::Long aHeight = GetSizePixel().Height(); + tools::ULong aWidth = GetSizePixel().Width(); + + aHeight -= GetMetaHeight(); + + mpOutliner->SetPaperSize( PixelToLogic( Size(aWidth, aHeight) ) ) ; + tools::Long aTextHeight = LogicToPixel( mpOutliner->CalcTextSize()).Height(); + + mxMetadataAuthor->show(); + if(IsResolved()) { mxMetadataResolved->show(); } + mxMetadataDate->show(); + + if (aTextHeight > aHeight) + { + const int nThickness = mxVScrollbar->get_scroll_thickness(); + if (nThickness) + { + // we need vertical scrollbars and have to reduce the width + aWidth -= nThickness; + mpOutliner->SetPaperSize(PixelToLogic(Size(aWidth, aHeight))); + } + mxVScrollbar->set_vpolicy(VclPolicyType::ALWAYS); + } + else + { + mxVScrollbar->set_vpolicy(VclPolicyType::NEVER); + } + + tools::Rectangle aOutputArea = PixelToLogic(tools::Rectangle(0, 0, aWidth, aHeight)); + if (mxVScrollbar->get_vpolicy() == VclPolicyType::NEVER) + { + // if we do not have a scrollbar anymore, we want to see the complete text + mpOutlinerView->SetVisArea(aOutputArea); + } + mpOutlinerView->SetOutputArea(aOutputArea); + mpOutlinerView->ShowCursor(true, true); + + // Don't leave an empty area at the bottom if we can move the text down. + tools::Long nMaxVisAreaTop = mpOutliner->GetTextHeight() - aOutputArea.GetHeight(); + if (mpOutlinerView->GetVisArea().Top() > nMaxVisAreaTop) + { + GetOutlinerView()->Scroll(0, mpOutlinerView->GetVisArea().Top() - nMaxVisAreaTop); + } + + int nUpper = mpOutliner->GetTextHeight(); + int nCurrentDocPos = mpOutlinerView->GetVisArea().Top(); + int nStepIncrement = mpOutliner->GetTextHeight() / 10; + int nPageIncrement = PixelToLogic(Size(0,aHeight)).Height() * 8 / 10; + int nPageSize = PixelToLogic(Size(0,aHeight)).Height(); + + /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has + effectively... + + lower = gtk_adjustment_get_lower + upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size + + and requires that upper > lower or the deceleration animation never ends + */ + nPageSize = std::min(nPageSize, nUpper); + + mxVScrollbar->vadjustment_configure(nCurrentDocPos, 0, nUpper, + nStepIncrement, nPageIncrement, nPageSize); +} + +void SwAnnotationWin::SetSizePixel( const Size& rNewSize ) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + + InterimItemWindow::SetSizePixel(rNewSize); + + if (mpShadow) + { + Point aStart = EditWin().PixelToLogic(GetPosPixel()+Point(0,GetSizePixel().Height())); + Point aEnd = EditWin().PixelToLogic(GetPosPixel()+Point(GetSizePixel().Width()-1,GetSizePixel().Height())); + mpShadow->SetPosition(basegfx::B2DPoint(aStart.X(),aStart.Y()), basegfx::B2DPoint(aEnd.X(),aEnd.Y())); + } +} + +void SwAnnotationWin::SetScrollbar() +{ + mxVScrollbar->vadjustment_set_value(mpOutlinerView->GetVisArea().Top()); +} + +void SwAnnotationWin::ResizeIfNecessary(tools::Long aOldHeight, tools::Long aNewHeight) +{ + if (aOldHeight != aNewHeight) + { + //check for lower border or next note + tools::Long aBorder = mrMgr.GetNextBorder(); + if (aBorder != -1) + { + if (aNewHeight > GetMinimumSizeWithoutMeta()) + { + tools::Long aNewLowerValue = GetPosPixel().Y() + aNewHeight + GetMetaHeight(); + if (aNewLowerValue < aBorder) + SetSizePixel(Size(GetSizePixel().Width(),aNewHeight+GetMetaHeight())); + else + SetSizePixel(Size(GetSizePixel().Width(),aBorder - GetPosPixel().Y())); + DoResize(); + Invalidate(); + } + else + { + if (GetSizePixel().Height() != GetMinimumSizeWithoutMeta() + GetMetaHeight()) + SetSizePixel(Size(GetSizePixel().Width(),GetMinimumSizeWithoutMeta() + GetMetaHeight())); + DoResize(); + Invalidate(); + } + } + else + { + DoResize(); + Invalidate(); + } + } + else + { + SetScrollbar(); + } +} + +void SwAnnotationWin::SetColor(Color aColorDark,Color aColorLight, Color aColorAnchor) +{ + mColorDark = aColorDark; + mColorLight = aColorLight; + mColorAnchor = aColorAnchor; + + if ( Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + return; + + m_xContainer->set_background(mColorDark); + SetMenuButtonColors(); + + mxMetadataAuthor->set_font_color(aColorAnchor); + + mxMetadataDate->set_font_color(aColorAnchor); + + mxMetadataResolved->set_font_color(aColorAnchor); + + mxVScrollbar->customize_scrollbars(mColorLight, + mColorAnchor, + mColorDark); +} + +void SwAnnotationWin::SetSidebarPosition(sw::sidebarwindows::SidebarPosition eSidebarPosition) +{ + meSidebarPosition = eSidebarPosition; +} + +void SwAnnotationWin::SetReadonly(bool bSet) +{ + mbReadonly = bSet; + GetOutlinerView()->SetReadOnly(bSet); +} + +void SwAnnotationWin::SetLanguage(const SvxLanguageItem& rNewItem) +{ + IDocumentUndoRedo& rUndoRedo( + mrView.GetDocShell()->GetDoc()->GetIDocumentUndoRedo()); + const bool bDocUndoEnabled = rUndoRedo.DoesUndo(); + const bool bOutlinerUndoEnabled = mpOutliner->IsUndoEnabled(); + const bool bOutlinerModified = mpOutliner->IsModified(); + const bool bDisableAndRestoreUndoMode = !bDocUndoEnabled && bOutlinerUndoEnabled; + + if (bDisableAndRestoreUndoMode) + { + // doc undo is disabled, but outliner was enabled, turn outliner undo off + // for the duration of this function + mpOutliner->EnableUndo(false); + } + + Link<LinkParamNone*,void> aLink = mpOutliner->GetModifyHdl(); + mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() ); + ESelection aOld = GetOutlinerView()->GetSelection(); + + ESelection aNewSelection( 0, 0, mpOutliner->GetParagraphCount()-1, EE_TEXTPOS_ALL ); + GetOutlinerView()->SetSelection( aNewSelection ); + SfxItemSet aEditAttr(GetOutlinerView()->GetAttribs()); + aEditAttr.Put(rNewItem); + GetOutlinerView()->SetAttribs( aEditAttr ); + + if (!mpOutliner->IsUndoEnabled() && !bOutlinerModified) + { + // if undo was disabled (e.g. this is a redo action) and we were + // originally 'unmodified' keep it that way + mpOutliner->ClearModifyFlag(); + } + + GetOutlinerView()->SetSelection(aOld); + mpOutliner->SetModifyHdl( aLink ); + + const SwViewOption* pVOpt = mrView.GetWrtShellPtr()->GetViewOptions(); + EEControlBits nCntrl = mpOutliner->GetControlWord(); + // turn off + nCntrl &= ~EEControlBits::ONLINESPELLING; + mpOutliner->SetControlWord(nCntrl); + + //turn back on + if (pVOpt->IsOnlineSpell()) + nCntrl |= EEControlBits::ONLINESPELLING; + else + nCntrl &= ~EEControlBits::ONLINESPELLING; + mpOutliner->SetControlWord(nCntrl); + + mpOutliner->CompleteOnlineSpelling(); + + // restore original mode + if (bDisableAndRestoreUndoMode) + mpOutliner->EnableUndo(true); + + Invalidate(); +} + +void SwAnnotationWin::GetFocus() +{ + if (mxSidebarTextControl) + mxSidebarTextControl->GrabFocus(); +} + +void SwAnnotationWin::LoseFocus() +{ +} + +void SwAnnotationWin::ShowNote() +{ + SetPosAndSize(); + if (!IsVisible()) + Window::Show(); + if (mpShadow && !mpShadow->isVisible()) + mpShadow->setVisible(true); + if (mpAnchor && !mpAnchor->isVisible()) + mpAnchor->setVisible(true); + if (mpTextRangeOverlay && !mpTextRangeOverlay->isVisible()) + mpTextRangeOverlay->setVisible(true); + + collectUIInformation("SHOW",get_id()); +} + +void SwAnnotationWin::HideNote() +{ + if (IsVisible()) + Window::Hide(); + if (mpAnchor) + { + if (mrMgr.IsShowAnchor()) + mpAnchor->SetAnchorState(AnchorState::Tri); + else + mpAnchor->setVisible(false); + } + if (mpShadow && mpShadow->isVisible()) + mpShadow->setVisible(false); + if (mpTextRangeOverlay && mpTextRangeOverlay->isVisible()) + mpTextRangeOverlay->setVisible(false); + collectUIInformation("HIDE",get_id()); +} + +void SwAnnotationWin::ActivatePostIt() +{ + mrMgr.AssureStdModeAtShell(); + + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); + + CheckMetaText(); + SetViewState(ViewState::EDIT); + + // prevent autoscroll to the old cursor location + // when cursor out of visible area + GetOutlinerView()->ShowCursor(false); + + mpOutlinerView->GetEditView().SetInsertMode(mrView.GetWrtShellPtr()->IsInsMode()); + + if ( !Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + GetOutlinerView()->SetBackgroundColor(mColorDark); + + //tdf#119130 only have the active postit as a dialog control in which pressing + //ctrl+tab cycles between text and button so we don't waste time searching + //thousands of SwAnnotationWins + SetStyle(GetStyle() | WB_DIALOGCONTROL); +} + +void SwAnnotationWin::DeactivatePostIt() +{ + //tdf#119130 only have the active postit as a dialog control in which pressing + //ctrl+tab cycles between text and button so we don't waste time searching + //thousands of SwAnnotationWins + SetStyle(GetStyle() & ~WB_DIALOGCONTROL); + + // remove selection, #i87073# + if (GetOutlinerView()->GetEditView().HasSelection()) + { + ESelection aSelection = GetOutlinerView()->GetEditView().GetSelection(); + aSelection.nEndPara = aSelection.nStartPara; + aSelection.nEndPos = aSelection.nStartPos; + GetOutlinerView()->GetEditView().SetSelection(aSelection); + } + + mpOutliner->CompleteOnlineSpelling(); + + SetViewState(ViewState::NORMAL); + // Make sure this view doesn't emit LOK callbacks during the update, as the + // sidebar window's SidebarTextControl doesn't have a valid twip offset + // (map mode origin) during that operation. + bool bTiledPainting = comphelper::LibreOfficeKit::isTiledPainting(); + comphelper::LibreOfficeKit::setTiledPainting(true); + // write the visible text back into the SwField + UpdateData(); + comphelper::LibreOfficeKit::setTiledPainting(bTiledPainting); + + if ( !Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + GetOutlinerView()->SetBackgroundColor(COL_TRANSPARENT); + + if (!mnDeleteEventId && !IsProtected() && mpOutliner->GetEditEngine().GetText().isEmpty()) + { + mnDeleteEventId = Application::PostUserEvent( LINK( this, SwAnnotationWin, DeleteHdl), nullptr, true ); + } +} + +void SwAnnotationWin::ToggleInsMode() +{ + if (!mrView.GetWrtShell().IsRedlineOn()) + { + //change outliner + mpOutlinerView->GetEditView().SetInsertMode(!mpOutlinerView->GetEditView().IsInsertMode()); + //change document + mrView.GetWrtShell().ToggleInsMode(); + //update statusbar + SfxBindings &rBnd = mrView.GetViewFrame()->GetBindings(); + rBnd.Invalidate(SID_ATTR_INSERT); + rBnd.Update(SID_ATTR_INSERT); + } +} + +void SwAnnotationWin::ExecuteCommand(sal_uInt16 nSlot) +{ + mrMgr.AssureStdModeAtShell(); + + switch (nSlot) + { + case FN_POSTIT: + case FN_REPLY: + { + // if this note is empty, it will be deleted once losing the focus, so no reply, but only a new note + // will be created + if (!mpOutliner->GetEditEngine().GetText().isEmpty()) + { + OutlinerParaObject aPara(GetOutlinerView()->GetEditView().CreateTextObject()); + mrMgr.RegisterAnswer(&aPara); + } + if (mrMgr.HasActiveSidebarWin()) + mrMgr.SetActiveSidebarWin(nullptr); + SwitchToFieldPos(); + mrView.GetViewFrame()->GetDispatcher()->Execute(FN_POSTIT); + break; + } + case FN_DELETE_COMMENT: + //Delete(); // do not kill the parent of our open popup menu + mnDeleteEventId = Application::PostUserEvent( LINK( this, SwAnnotationWin, DeleteHdl), nullptr, true ); + break; + case FN_DELETE_COMMENT_THREAD: + DeleteThread(); + break; + case FN_RESOLVE_NOTE: + ToggleResolved(); + DoResize(); + Invalidate(); + mrMgr.LayoutPostIts(); + break; + case FN_RESOLVE_NOTE_THREAD: + GetTopReplyNote()->SetResolved(!IsThreadResolved()); + mrMgr.UpdateResolvedStatus(GetTopReplyNote()); + DoResize(); + Invalidate(); + mrMgr.LayoutPostIts(); + break; + case FN_FORMAT_ALL_NOTES: + case FN_DELETE_ALL_NOTES: + case FN_HIDE_ALL_NOTES: + // not possible as slot as this would require that "this" is the active postit + mrView.GetViewFrame()->GetBindings().Execute( nSlot, nullptr, SfxCallMode::ASYNCHRON ); + break; + case FN_DELETE_NOTE_AUTHOR: + case FN_HIDE_NOTE_AUTHOR: + { + // not possible as slot as this would require that "this" is the active postit + SfxStringItem aItem( nSlot, GetAuthor() ); + const SfxPoolItem* aItems[2]; + aItems[0] = &aItem; + aItems[1] = nullptr; + mrView.GetViewFrame()->GetBindings().Execute( nSlot, aItems, SfxCallMode::ASYNCHRON ); + } + break; + default: + mrView.GetViewFrame()->GetBindings().Execute( nSlot ); + break; + } +} + +SwEditWin& SwAnnotationWin::EditWin() +{ + return mrView.GetEditWin(); +} + +tools::Long SwAnnotationWin::GetPostItTextHeight() +{ + return mpOutliner ? LogicToPixel(mpOutliner->CalcTextSize()).Height() : 0; +} + +void SwAnnotationWin::SwitchToPostIt(sal_uInt16 aDirection) +{ + SwAnnotationWin* pPostIt = mrMgr.GetNextPostIt(aDirection, this); + if (pPostIt) + pPostIt->GrabFocus(); +} + +IMPL_LINK(SwAnnotationWin, MouseMoveHdl, const MouseEvent&, rMEvt, bool) +{ + if (rMEvt.IsEnterWindow()) + { + mbMouseOver = true; + if ( !HasFocus() ) + { + SetViewState(ViewState::VIEW); + Invalidate(); + } + } + else if (rMEvt.IsLeaveWindow()) + { + mbMouseOver = false; + if ( !HasFocus() ) + { + SetViewState(ViewState::NORMAL); + Invalidate(); + } + } + return false; +} + +bool SwAnnotationWin::SetActiveSidebarWin() +{ + if (mrMgr.GetActiveSidebarWin() == this) + return false; + mrView.GetWrtShell().LockView( true ); + mrMgr.SetActiveSidebarWin(this); + mrView.GetWrtShell().LockView( true ); + + return true; +} + +void SwAnnotationWin::UnsetActiveSidebarWin() +{ + if (mrMgr.GetActiveSidebarWin() != this) + return; + mrView.GetWrtShell().LockView( true ); + mrMgr.SetActiveSidebarWin(nullptr); + mrView.GetWrtShell().LockView( false ); +} + +void SwAnnotationWin::LockView(bool bLock) +{ + mrView.GetWrtShell().LockView( bLock ); +} + +IMPL_LINK(SwAnnotationWin, ScrollHdl, weld::ScrolledWindow&, rScrolledWindow, void) +{ + tools::Long nDiff = GetOutlinerView()->GetEditView().GetVisArea().Top() - rScrolledWindow.vadjustment_get_value(); + GetOutlinerView()->Scroll( 0, nDiff ); +} + +IMPL_LINK_NOARG(SwAnnotationWin, ModifyHdl, LinkParamNone*, void) +{ + mrView.GetDocShell()->SetModified(); +} + +IMPL_LINK_NOARG(SwAnnotationWin, DeleteHdl, void*, void) +{ + mnDeleteEventId = nullptr; + Delete(); +} + +void SwAnnotationWin::ResetAttributes() +{ + mpOutlinerView->RemoveAttribsKeepLanguages(true); + mpOutliner->RemoveFields(); + mpOutlinerView->SetAttribs(DefaultItem()); +} + +int SwAnnotationWin::GetPrefScrollbarWidth() const +{ + const Fraction& f(mrView.GetWrtShellPtr()->GetOut()->GetMapMode().GetScaleY()); + return tools::Long(Application::GetSettings().GetStyleSettings().GetScrollBarSize() * f); +} + +sal_Int32 SwAnnotationWin::GetMetaHeight() const +{ + const int fields = GetNumFields(); + + sal_Int32 nRequiredHeight = 0; + weld::Label* aLabels[3] = { mxMetadataAuthor.get(), mxMetadataDate.get(), mxMetadataResolved.get() }; + for (int i = 0; i < fields; ++i) + nRequiredHeight += aLabels[i]->get_preferred_size().Height(); + + return nRequiredHeight; +} + +sal_Int32 SwAnnotationWin::GetNumFields() const +{ + return IsResolved() ? 3 : 2; +} + +sal_Int32 SwAnnotationWin::GetMinimumSizeWithMeta() const +{ + return mrMgr.GetMinimumSizeWithMeta(); +} + +sal_Int32 SwAnnotationWin::GetMinimumSizeWithoutMeta() const +{ + const Fraction& f(mrView.GetWrtShellPtr()->GetOut()->GetMapMode().GetScaleY()); + return tools::Long(POSTIT_MINIMUMSIZE_WITHOUT_META * f); +} + +void SwAnnotationWin::SetSpellChecking() +{ + const SwViewOption* pVOpt = mrView.GetWrtShellPtr()->GetViewOptions(); + EEControlBits nCntrl = mpOutliner->GetControlWord(); + if (pVOpt->IsOnlineSpell()) + nCntrl |= EEControlBits::ONLINESPELLING; + else + nCntrl &= ~EEControlBits::ONLINESPELLING; + mpOutliner->SetControlWord(nCntrl); + + mpOutliner->CompleteOnlineSpelling(); + Invalidate(); +} + +void SwAnnotationWin::SetViewState(ViewState bViewState) +{ + switch (bViewState) + { + case ViewState::EDIT: + { + if (mpAnchor) + { + mpAnchor->SetAnchorState(AnchorState::All); + SwAnnotationWin* pWin = GetTopReplyNote(); + // #i111964# + if ( pWin != this && pWin->Anchor() ) + { + pWin->Anchor()->SetAnchorState(AnchorState::End); + } + mpAnchor->setLineSolid(true); + if ( mpTextRangeOverlay != nullptr ) + { + mpTextRangeOverlay->ShowSolidBorder(); + } + } + if (mpShadow) + mpShadow->SetShadowState(SS_EDIT); + break; + } + case ViewState::VIEW: + { + if (mpAnchor) + { + mpAnchor->setLineSolid(true); + if ( mpTextRangeOverlay != nullptr ) + { + mpTextRangeOverlay->ShowSolidBorder(); + } + } + if (mpShadow) + mpShadow->SetShadowState(SS_VIEW); + break; + } + case ViewState::NORMAL: + { + if (mpAnchor) + { + if (IsFollow()) + { + // if there is no visible parent note, we want to see the complete anchor ?? + //if (IsAnyStackParentVisible()) + mpAnchor->SetAnchorState(AnchorState::End); + SwAnnotationWin* pTopWinSelf = GetTopReplyNote(); + SwAnnotationWin* pTopWinActive = mrMgr.HasActiveSidebarWin() + ? mrMgr.GetActiveSidebarWin()->GetTopReplyNote() + : nullptr; + // #i111964# + if ( ( pTopWinSelf != this ) && + ( pTopWinSelf != pTopWinActive ) && + pTopWinSelf->Anchor() ) + { + if ( pTopWinSelf != mrMgr.GetActiveSidebarWin() ) + { + pTopWinSelf->Anchor()->setLineSolid(false); + if ( pTopWinSelf->TextRange() != nullptr ) + { + pTopWinSelf->TextRange()->HideSolidBorder(); + } + } + pTopWinSelf->Anchor()->SetAnchorState(AnchorState::All); + } + } + mpAnchor->setLineSolid(false); + if ( mpTextRangeOverlay != nullptr ) + { + mpTextRangeOverlay->HideSolidBorder(); + } + } + if ( mpShadow ) + { + mpShadow->SetShadowState(SS_NORMAL); + } + break; + } + } +} + +SwAnnotationWin* SwAnnotationWin::GetTopReplyNote() +{ + SwAnnotationWin* pTopNote = this; + SwAnnotationWin* pSidebarWin = IsFollow() ? mrMgr.GetNextPostIt(KEY_PAGEUP, this) : nullptr; + while (pSidebarWin) + { + pTopNote = pSidebarWin; + pSidebarWin = pSidebarWin->IsFollow() ? mrMgr.GetNextPostIt(KEY_PAGEUP, pSidebarWin) : nullptr; + } + return pTopNote; +} + +void SwAnnotationWin::SwitchToFieldPos() +{ + if ( mrMgr.GetActiveSidebarWin() == this ) + mrMgr.SetActiveSidebarWin(nullptr); + GotoPos(); + sal_uInt32 aCount = MoveCaret(); + if (aCount) + mrView.GetDocShell()->GetWrtShell()->SwCursorShell::Right(aCount, 0); + GrabFocusToDocument(); + collectUIInformation("LEAVE",get_id()); +} + +void SwAnnotationWin::SetChangeTracking( const SwPostItHelper::SwLayoutStatus aLayoutStatus, + const Color& aChangeColor ) +{ + if ( (mLayoutStatus != aLayoutStatus) || + (mChangeColor != aChangeColor) ) + { + mLayoutStatus = aLayoutStatus; + mChangeColor = aChangeColor; + Invalidate(); + } +} + +bool SwAnnotationWin::HasScrollbar() const +{ + return static_cast<bool>(mxVScrollbar); +} + +bool SwAnnotationWin::IsScrollbarVisible() const +{ + return HasScrollbar() && mxVScrollbar->get_vpolicy() == VclPolicyType::ALWAYS; +} + +void SwAnnotationWin::ChangeSidebarItem( SwSidebarItem const & rSidebarItem ) +{ +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + const bool bAnchorChanged = mpAnchorFrame != rSidebarItem.maLayoutInfo.mpAnchorFrame; + if ( bAnchorChanged ) + { + mrMgr.DisconnectSidebarWinFromFrame( *mpAnchorFrame, *this ); + } +#endif + + mrSidebarItem = rSidebarItem; + mpAnchorFrame = mrSidebarItem.maLayoutInfo.mpAnchorFrame; + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (mxSidebarWinAccessible) + mxSidebarWinAccessible->ChangeSidebarItem( mrSidebarItem ); + + if ( bAnchorChanged ) + { + mrMgr.ConnectSidebarWinToFrame( *(mrSidebarItem.maLayoutInfo.mpAnchorFrame), + mrSidebarItem.GetFormatField(), + *this ); + } +#endif +} + +css::uno::Reference< css::accessibility::XAccessible > SwAnnotationWin::CreateAccessible() +{ +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + // This is rather dodgy code. Normally in CreateAccessible, if we want a custom + // object, we return a custom object, but we do no override the default toolkit + // window peer. + if (!mxSidebarWinAccessible) + mxSidebarWinAccessible = new SidebarWinAccessible( *this, + mrView.GetWrtShell(), + mrSidebarItem ); +#endif + return mxSidebarWinAccessible; +} + +} // eof of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/DashedLine.cxx b/sw/source/uibase/docvw/DashedLine.cxx new file mode 100644 index 000000000..5727cf280 --- /dev/null +++ b/sw/source/uibase/docvw/DashedLine.cxx @@ -0,0 +1,91 @@ +/* -*- 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/. + */ + +#include <DashedLine.hxx> + +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <memory> + +SwDashedLine::SwDashedLine( vcl::Window* pParent, Color& ( *pColorFn )() ) + : Control( pParent, WB_DIALOGCONTROL | WB_HORZ ) + , m_pColorFn( pColorFn ) +{ +} + +SwDashedLine::~SwDashedLine( ) +{ +} + +void SwDashedLine::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos)); + + // Compute the start and end points + const tools::Rectangle aRect(tools::Rectangle(Point(0, 0), rRenderContext.PixelToLogic(GetSizePixel()))); + double nHalfWidth = double(aRect.Top() + aRect.Bottom()) / 2.0; + + basegfx::B2DPoint aStart(double(aRect.Left()), nHalfWidth); + basegfx::B2DPoint aEnd(double(aRect.Right()), nHalfWidth); + + basegfx::B2DPolygon aPolygon; + aPolygon.append(aStart); + aPolygon.append(aEnd); + + drawinglayer::primitive2d::Primitive2DContainer aSeq(1); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + std::vector<double> aStrokePattern; + basegfx::BColor aColor = m_pColorFn().getBColor(); + if (rSettings.GetHighContrastMode()) + { + // Only a solid line in high contrast mode + aColor = rSettings.GetDialogTextColor().getBColor(); + } + else + { + // Get a color for the contrast + basegfx::BColor aHslLine = basegfx::utils::rgb2hsl(aColor); + double nLuminance = aHslLine.getZ(); + nLuminance += (1.0 - nLuminance) * 0.75; + if (aHslLine.getZ() > 0.7) + nLuminance = aHslLine.getZ() * 0.7; + aHslLine.setZ(nLuminance); + const basegfx::BColor aOtherColor = basegfx::utils::hsl2rgb(aHslLine); + + // Compute the plain line + aSeq[0] = new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aOtherColor); + + // Dashed line in twips + aStrokePattern.push_back(3); + aStrokePattern.push_back(3); + + aSeq.resize(2); + } + + // Compute the dashed line primitive + aSeq[aSeq.size() - 1] = + new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + drawinglayer::attribute::LineAttribute(m_pColorFn().getBColor()), + drawinglayer::attribute::StrokeAttribute(std::move(aStrokePattern))); + + pProcessor->process(aSeq); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/FrameControlsManager.cxx b/sw/source/uibase/docvw/FrameControlsManager.cxx new file mode 100644 index 000000000..03252faa1 --- /dev/null +++ b/sw/source/uibase/docvw/FrameControlsManager.cxx @@ -0,0 +1,269 @@ +/* -*- 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/. + */ + +#include <edtwin.hxx> +#include <cntfrm.hxx> +#include <FrameControlsManager.hxx> +#include <HeaderFooterWin.hxx> +#include <PageBreakWin.hxx> +#include <UnfloatTableButton.hxx> +#include <pagefrm.hxx> +#include <flyfrm.hxx> +#include <viewopt.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <OutlineContentVisibilityWin.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> + +SwFrameControlsManager::SwFrameControlsManager( SwEditWin* pEditWin ) : + m_pEditWin( pEditWin ) +{ +} + +SwFrameControlsManager::~SwFrameControlsManager() +{ +} + +void SwFrameControlsManager::dispose() +{ + m_aControls.clear(); +} + +SwFrameControlPtr SwFrameControlsManager::GetControl( FrameControlType eType, const SwFrame* pFrame ) +{ + SwFrameControlPtrMap& rControls = m_aControls[eType]; + + SwFrameControlPtrMap::iterator aIt = rControls.find(pFrame); + + if (aIt != rControls.end()) + return aIt->second; + + return SwFrameControlPtr(); +} + +void SwFrameControlsManager::RemoveControls( const SwFrame* pFrame ) +{ + for ( auto& rEntry : m_aControls ) + { + SwFrameControlPtrMap& rMap = rEntry.second; + rMap.erase(pFrame); + } +} + +void SwFrameControlsManager::RemoveControlsByType( FrameControlType eType, const SwFrame* pFrame ) +{ + SwFrameControlPtrMap& rMap = m_aControls[eType]; + rMap.erase(pFrame); +} + +void SwFrameControlsManager::HideControls( FrameControlType eType ) +{ + for ( const auto& rCtrl : m_aControls[eType] ) + rCtrl.second->ShowAll( false ); +} + +void SwFrameControlsManager::SetReadonlyControls( bool bReadonly ) +{ + for ( auto& rEntry : m_aControls ) + for ( auto& rCtrl : rEntry.second ) + rCtrl.second->SetReadonly( bReadonly ); +} + +void SwFrameControlsManager::SetHeaderFooterControl( const SwPageFrame* pPageFrame, FrameControlType eType, Point aOffset ) +{ + assert( eType == FrameControlType::Header || eType == FrameControlType::Footer ); + + // Check if we already have the control + SwFrameControlPtr pControl; + const bool bHeader = ( eType == FrameControlType::Header ); + + SwFrameControlPtrMap& rControls = m_aControls[eType]; + + SwFrameControlPtrMap::iterator lb = rControls.lower_bound(pPageFrame); + if (lb != rControls.end() && !(rControls.key_comp()(pPageFrame, lb->first))) + pControl = lb->second; + else + { + SwFrameControlPtr pNewControl = + std::make_shared<SwFrameControl>( VclPtr<SwHeaderFooterDashedLine>::Create( + m_pEditWin, pPageFrame, bHeader ).get() ); + const SwViewOption* pViewOpt = m_pEditWin->GetView().GetWrtShell().GetViewOptions(); + pNewControl->SetReadonly( pViewOpt->IsReadonly() ); + rControls.insert(lb, make_pair(pPageFrame, pNewControl)); + pControl.swap( pNewControl ); + } + + tools::Rectangle aPageRect = m_pEditWin->LogicToPixel( pPageFrame->getFrameArea().SVRect() ); + + SwHeaderFooterDashedLine* pWin = dynamic_cast<SwHeaderFooterDashedLine*>(pControl->GetWindow()); + assert( pWin != nullptr) ; + assert( pWin->IsHeader() == bHeader ); + pWin->SetOffset( aOffset, aPageRect.Left(), aPageRect.Right() ); + + if (!pWin->IsVisible()) + pControl->ShowAll( true ); +} + +void SwFrameControlsManager::SetPageBreakControl( const SwPageFrame* pPageFrame ) +{ + // Check if we already have the control + SwFrameControlPtr pControl; + + SwFrameControlPtrMap& rControls = m_aControls[FrameControlType::PageBreak]; + + SwFrameControlPtrMap::iterator lb = rControls.lower_bound(pPageFrame); + if (lb != rControls.end() && !(rControls.key_comp()(pPageFrame, lb->first))) + pControl = lb->second; + else + { + SwFrameControlPtr pNewControl = std::make_shared<SwFrameControl>( + VclPtr<SwBreakDashedLine>::Create( m_pEditWin, pPageFrame ).get() ); + const SwViewOption* pViewOpt = m_pEditWin->GetView().GetWrtShell().GetViewOptions(); + pNewControl->SetReadonly( pViewOpt->IsReadonly() ); + + rControls.insert(lb, make_pair(pPageFrame, pNewControl)); + + pControl.swap( pNewControl ); + } + + SwBreakDashedLine* pWin = static_cast<SwBreakDashedLine*>(pControl->GetWindow()); + assert (pWin != nullptr); + pWin->UpdatePosition(); + if (!pWin->IsVisible()) + pControl->ShowAll( true ); +} + +void SwFrameControlsManager::SetUnfloatTableButton( const SwFlyFrame* pFlyFrame, bool bShow, Point aTopRightPixel ) +{ + if(pFlyFrame == nullptr) + return; + + // Check if we already have the control + SwFrameControlPtr pControl; + + SwFrameControlPtrMap& rControls = m_aControls[FrameControlType::FloatingTable]; + + SwFrameControlPtrMap::iterator lb = rControls.lower_bound(pFlyFrame); + if (lb != rControls.end() && !(rControls.key_comp()(pFlyFrame, lb->first))) + pControl = lb->second; + else if (!bShow) // Do not create the control when it's not shown + return; + else + { + SwFrameControlPtr pNewControl = std::make_shared<SwFrameControl>( + VclPtr<UnfloatTableButton>::Create( m_pEditWin, pFlyFrame ).get() ); + const SwViewOption* pViewOpt = m_pEditWin->GetView().GetWrtShell().GetViewOptions(); + pNewControl->SetReadonly( pViewOpt->IsReadonly() ); + + rControls.insert(lb, make_pair(pFlyFrame, pNewControl)); + + pControl.swap( pNewControl ); + } + + UnfloatTableButton* pButton = dynamic_cast<UnfloatTableButton*>(pControl->GetWindow()); + assert(pButton != nullptr); + pButton->SetOffset(aTopRightPixel); + pControl->ShowAll( bShow ); +} + +SwFrameMenuButtonBase::SwFrameMenuButtonBase(SwEditWin* pEditWin, const SwFrame* pFrame, + const OUString& rUIXMLDescription, const OString& rID) + : InterimItemWindow(pEditWin, rUIXMLDescription, rID) + , m_pEditWin(pEditWin) + , m_pFrame(pFrame) +{ +} + +void SwFrameControlsManager::SetOutlineContentVisibilityButton(const SwContentFrame* pContentFrame) +{ + // Check if we already have the control + SwFrameControlPtr pControl; + + SwFrameControlPtrMap& rControls = m_aControls[FrameControlType::Outline]; + + SwFrameControlPtrMap::iterator lb = rControls.lower_bound(pContentFrame); + if (lb != rControls.end() && !(rControls.key_comp()(pContentFrame, lb->first))) + { + pControl = lb->second; + } + else + { + SwFrameControlPtr pNewControl = + std::make_shared<SwFrameControl>(VclPtr<SwOutlineContentVisibilityWin>::Create( + m_pEditWin, pContentFrame).get()); + rControls.insert(lb, make_pair(pContentFrame, pNewControl)); + pControl.swap(pNewControl); + } + + SwOutlineContentVisibilityWin* pWin = dynamic_cast<SwOutlineContentVisibilityWin *>(pControl->GetWindow()); + assert(pWin != nullptr); + pWin->Set(); + + if (pWin->GetSymbol() == ButtonSymbol::SHOW) + pWin->Show(); // show the SHOW button immediately + else if (!pWin->IsVisible() && pWin->GetSymbol() == ButtonSymbol::HIDE) + pWin->ShowAll(true); +} + +const SwPageFrame* SwFrameMenuButtonBase::GetPageFrame(const SwFrame* pFrame) +{ + if (pFrame->IsPageFrame()) + return static_cast<const SwPageFrame*>(pFrame); + + if (pFrame->IsFlyFrame()) + return static_cast<const SwFlyFrame*>(pFrame)->GetAnchorFrame()->FindPageFrame(); + + return pFrame->FindPageFrame(); +} + +const SwPageFrame* SwFrameMenuButtonBase::GetPageFrame() const +{ + return SwFrameMenuButtonBase::GetPageFrame(m_pFrame); +} + +void SwFrameMenuButtonBase::dispose() +{ + m_pEditWin.clear(); + m_pFrame = nullptr; + m_xVirDev.disposeAndClear(); + InterimItemWindow::dispose(); +} + +void SwFrameMenuButtonBase::SetVirDevFont(OutputDevice& rVirDev) +{ + // Get the font and configure it + vcl::Font aFont = Application::GetSettings().GetStyleSettings().GetToolFont(); + weld::SetPointFont(rVirDev, aFont); +} + +void SwFrameMenuButtonBase::SetVirDevFont() +{ + SetVirDevFont(*m_xVirDev); +} + +SwFrameControl::SwFrameControl( const VclPtr<vcl::Window> &pWindow ) +{ + assert(static_cast<bool>(pWindow)); + mxWindow.reset( pWindow ); + mpIFace = dynamic_cast<ISwFrameControl *>( pWindow.get() ); +} + +SwFrameControl::~SwFrameControl() +{ + mpIFace = nullptr; + mxWindow.disposeAndClear(); +} + +ISwFrameControl::~ISwFrameControl() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/HeaderFooterWin.cxx b/sw/source/uibase/docvw/HeaderFooterWin.cxx new file mode 100644 index 000000000..6c184c6fe --- /dev/null +++ b/sw/source/uibase/docvw/HeaderFooterWin.cxx @@ -0,0 +1,591 @@ +/* -*- 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/. + */ + +#include <strings.hrc> + +#include <doc.hxx> +#include <drawdoc.hxx> +#include <cmdid.h> +#include <DashedLine.hxx> +#include <docsh.hxx> +#include <edtwin.hxx> +#include <fmthdft.hxx> +#include <HeaderFooterWin.hxx> +#include <pagedesc.hxx> +#include <pagefrm.hxx> +#include <view.hxx> +#include <viewopt.hxx> +#include <wrtsh.hxx> +#include <IDocumentDrawModelAccess.hxx> + +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <drawinglayer/attribute/fillgradientattribute.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <editeng/boxitem.hxx> +#include <svx/hdft.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/metric.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <memory> + +#define TEXT_PADDING 5 +#define BOX_DISTANCE 10 +#define BUTTON_WIDTH 18 + +using namespace basegfx; +using namespace basegfx::utils; +using namespace drawinglayer::attribute; + +namespace +{ + basegfx::BColor lcl_GetFillColor(const basegfx::BColor& rLineColor) + { + basegfx::BColor aHslLine = basegfx::utils::rgb2hsl(rLineColor); + double nLuminance = aHslLine.getZ() * 2.5; + if ( nLuminance == 0 ) + nLuminance = 0.5; + else if ( nLuminance >= 1.0 ) + nLuminance = aHslLine.getZ() * 0.4; + aHslLine.setZ( nLuminance ); + return basegfx::utils::hsl2rgb( aHslLine ); + } + + basegfx::BColor lcl_GetLighterGradientColor(const basegfx::BColor& rDarkColor) + { + basegfx::BColor aHslDark = basegfx::utils::rgb2hsl(rDarkColor); + double nLuminance = aHslDark.getZ() * 255 + 20; + aHslDark.setZ( nLuminance / 255.0 ); + return basegfx::utils::hsl2rgb( aHslDark ); + } + + B2DPolygon lcl_GetPolygon( const ::tools::Rectangle& rRect, bool bOnTop ) + { + const double nRadius = 3; + const double nKappa((M_SQRT2 - 1.0) * 4.0 / 3.0); + + B2DPolygon aPolygon; + aPolygon.append( B2DPoint( rRect.Left(), rRect.Top() ) ); + + { + B2DPoint aCorner( rRect.Left(), rRect.Bottom() ); + B2DPoint aStart( rRect.Left(), rRect.Bottom() - nRadius ); + B2DPoint aEnd( rRect.Left() + nRadius, rRect.Bottom() ); + aPolygon.append( aStart ); + aPolygon.appendBezierSegment( + interpolate( aStart, aCorner, nKappa ), + interpolate( aEnd, aCorner, nKappa ), + aEnd ); + } + + { + B2DPoint aCorner( rRect.Right(), rRect.Bottom() ); + B2DPoint aStart( rRect.Right() - nRadius, rRect.Bottom() ); + B2DPoint aEnd( rRect.Right(), rRect.Bottom() - nRadius ); + aPolygon.append( aStart ); + aPolygon.appendBezierSegment( + interpolate( aStart, aCorner, nKappa ), + interpolate( aEnd, aCorner, nKappa ), + aEnd ); + } + + aPolygon.append( B2DPoint( rRect.Right(), rRect.Top() ) ); + + if ( !bOnTop ) + { + B2DRectangle aBRect = vcl::unotools::b2DRectangleFromRectangle(rRect); + B2DHomMatrix aRotation = createRotateAroundPoint( + aBRect.getCenterX(), aBRect.getCenterY(), M_PI ); + aPolygon.transform( aRotation ); + } + + return aPolygon; + } +} + +void SwFrameButtonPainter::PaintButton(drawinglayer::primitive2d::Primitive2DContainer& rSeq, + const tools::Rectangle& rRect, bool bOnTop) +{ + rSeq.clear(); + B2DPolygon aPolygon = lcl_GetPolygon(rRect, bOnTop); + + // Colors + basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor(); + basegfx::BColor aFillColor = lcl_GetFillColor(aLineColor); + basegfx::BColor aLighterColor = lcl_GetLighterGradientColor(aFillColor); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if (rSettings.GetHighContrastMode()) + { + aFillColor = rSettings.GetDialogColor().getBColor(); + aLineColor = rSettings.GetDialogTextColor().getBColor(); + + rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(B2DPolyPolygon(aPolygon), aFillColor))); + } + else + { + B2DRectangle aGradientRect = vcl::unotools::b2DRectangleFromRectangle(rRect); + double nAngle = M_PI; + if (bOnTop) + nAngle = 0; + FillGradientAttribute aFillAttrs(drawinglayer::attribute::GradientStyle::Linear, 0.0, 0.0, 0.0, nAngle, aLighterColor, aFillColor); + rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::FillGradientPrimitive2D(aGradientRect, aFillAttrs))); + } + + // Create the border lines primitive + rSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aLineColor))); +} + +SwHeaderFooterDashedLine::SwHeaderFooterDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader) + : SwDashedLine(pEditWin, &SwViewOption::GetHeaderFooterMarkColor) + , m_pEditWin(pEditWin) + , m_pFrame(pFrame) + , m_bIsHeader(bHeader) +{ +} + +bool SwHeaderFooterDashedLine::IsOnScreen() +{ + tools::Rectangle aBounds(GetPosPixel(), GetSizePixel()); + tools::Rectangle aVisArea = GetEditWin()->LogicToPixel(GetEditWin()->GetView().GetVisArea()); + return aBounds.Overlaps(aVisArea); +} + +void SwHeaderFooterDashedLine::EnsureWin() +{ + if (!m_pWin) + { + m_pWin = VclPtr<SwHeaderFooterWin>::Create(m_pEditWin, m_pFrame, m_bIsHeader); + m_pWin->SetZOrder(this, ZOrderFlags::Before); + } +} + +void SwHeaderFooterDashedLine::ShowAll(bool bShow) +{ + Show(bShow); + if (!m_pWin && IsOnScreen()) + EnsureWin(); + if (m_pWin) + m_pWin->ShowAll(bShow); +} + +void SwHeaderFooterDashedLine::SetReadonly(bool bReadonly) +{ + ShowAll(!bReadonly); +} + +bool SwHeaderFooterDashedLine::Contains(const Point &rDocPt) const +{ + if (m_pWin && m_pWin->Contains(rDocPt)) + return true; + + ::tools::Rectangle aLineRect(GetPosPixel(), GetSizePixel()); + return aLineRect.Contains(rDocPt); +} + +void SwHeaderFooterDashedLine::SetOffset(Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd) +{ + Point aLinePos(nXLineStart, aOffset.Y()); + Size aLineSize(nXLineEnd - nXLineStart, 1); + SetPosSizePixel(aLinePos, aLineSize); + + bool bOnScreen = IsOnScreen(); + if (!m_pWin && bOnScreen) + { + EnsureWin(); + m_pWin->ShowAll(true); + } + else if (m_pWin && !bOnScreen) + m_pWin.disposeAndClear(); + + if (m_pWin) + m_pWin->SetOffset(aOffset); +} + +SwHeaderFooterWin::SwHeaderFooterWin(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) : + InterimItemWindow(pEditWin, "modules/swriter/ui/hfmenubutton.ui", "HFMenuButton"), + m_xMenuButton(m_xBuilder->weld_menu_button("menubutton")), + m_xPushButton(m_xBuilder->weld_button("button")), + m_pEditWin(pEditWin), + m_pFrame(pFrame), + m_bIsHeader( bHeader ), + m_bIsAppearing( false ), + m_nFadeRate( 100 ), + m_aFadeTimer("SwHeaderFooterWin m_aFadeTimer") +{ + m_xVirDev = m_xMenuButton->create_virtual_device(); + SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev); + + m_xPushButton->connect_clicked(LINK(this, SwHeaderFooterWin, ClickHdl)); + m_xMenuButton->connect_selected(LINK(this, SwHeaderFooterWin, SelectHdl)); + + // set the PopupMenu + // Rewrite the menu entries' text + if (m_bIsHeader) + { + m_xMenuButton->set_item_label("edit", SwResId(STR_FORMAT_HEADER)); + m_xMenuButton->set_item_label("delete", SwResId(STR_DELETE_HEADER)); + } + else + { + m_xMenuButton->set_item_label("edit", SwResId(STR_FORMAT_FOOTER)); + m_xMenuButton->set_item_label("delete", SwResId(STR_DELETE_FOOTER)); + } + + m_aFadeTimer.SetTimeout(50); + m_aFadeTimer.SetInvokeHandler(LINK(this, SwHeaderFooterWin, FadeHandler)); +} + +SwHeaderFooterWin::~SwHeaderFooterWin( ) +{ + disposeOnce(); +} + +void SwHeaderFooterWin::dispose() +{ + m_xPushButton.reset(); + m_xMenuButton.reset(); + m_pEditWin.clear(); + m_xVirDev.disposeAndClear(); + InterimItemWindow::dispose(); +} + +void SwHeaderFooterWin::SetOffset(Point aOffset) +{ + // Compute the text to show + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); + bool bIsFirst = !pDesc->IsFirstShared() && pPageFrame->OnFirstPage(); + bool bIsLeft = !pDesc->IsHeaderShared() && !pPageFrame->OnRightPage(); + bool bIsRight = !pDesc->IsHeaderShared() && pPageFrame->OnRightPage(); + m_sLabel = SwResId(STR_HEADER_TITLE); + if (!m_bIsHeader) + m_sLabel = bIsFirst ? SwResId(STR_FIRST_FOOTER_TITLE) + : bIsLeft ? SwResId(STR_LEFT_FOOTER_TITLE) + : bIsRight ? SwResId(STR_RIGHT_FOOTER_TITLE) + : SwResId(STR_FOOTER_TITLE ); + else + m_sLabel = bIsFirst ? SwResId(STR_FIRST_HEADER_TITLE) + : bIsLeft ? SwResId(STR_LEFT_HEADER_TITLE) + : bIsRight ? SwResId(STR_RIGHT_HEADER_TITLE) + : SwResId(STR_HEADER_TITLE); + + sal_Int32 nPos = m_sLabel.lastIndexOf("%1"); + m_sLabel = m_sLabel.replaceAt(nPos, 2, pDesc->GetName()); + m_xMenuButton->set_accessible_name(m_sLabel); + + // Compute the text size and get the box position & size from it + ::tools::Rectangle aTextRect; + m_xVirDev->GetTextBoundRect(aTextRect, m_sLabel); + ::tools::Rectangle aTextPxRect = m_xVirDev->LogicToPixel(aTextRect); + FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->GetFont()); + Size aBoxSize (aTextPxRect.GetWidth() + BUTTON_WIDTH + TEXT_PADDING * 2, + aFontMetric.GetLineHeight() + TEXT_PADDING * 2 ); + + tools::Long nYFooterOff = 0; + if (!m_bIsHeader) + nYFooterOff = aBoxSize.Height(); + + Point aBoxPos(aOffset.X() - aBoxSize.Width() - BOX_DISTANCE, + aOffset.Y() - nYFooterOff); + + if (AllSettings::GetLayoutRTL()) + { + aBoxPos.setX( aOffset.X() + BOX_DISTANCE ); + } + + // Set the position & Size of the window + SetPosSizePixel(aBoxPos, aBoxSize); + + m_xVirDev->SetOutputSizePixel(aBoxSize); + PaintButton(); +} + +void SwHeaderFooterWin::ShowAll(bool bShow) +{ + bool bIsEmptyHeaderFooter = IsEmptyHeaderFooter(); + m_xMenuButton->set_visible(!bIsEmptyHeaderFooter); + m_xPushButton->set_visible(bIsEmptyHeaderFooter); + + m_bIsAppearing = bShow; + + if (m_aFadeTimer.IsActive()) + m_aFadeTimer.Stop(); + m_aFadeTimer.Start(); +} + +bool SwHeaderFooterWin::Contains( const Point &rDocPt ) const +{ + ::tools::Rectangle aRect(GetPosPixel(), GetSizePixel()); + return aRect.Contains(rDocPt); +} + +void SwHeaderFooterWin::PaintButton() +{ + if (!m_xVirDev) + return; + + // Use pixels for the rest of the drawing + SetMapMode(MapMode(MapUnit::MapPixel)); + drawinglayer::primitive2d::Primitive2DContainer aSeq; + const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), m_xVirDev->PixelToLogic(GetSizePixel()))); + + SwFrameButtonPainter::PaintButton(aSeq, aRect, m_bIsHeader); + + // Create the text primitive + basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor(); + B2DVector aFontSize; + FontAttribute aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, m_xVirDev->GetFont(), false, false); + + FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->GetFont()); + double nTextOffsetY = aFontMetric.GetAscent() + TEXT_PADDING; + Point aTextPos(TEXT_PADDING, nTextOffsetY); + + basegfx::B2DHomMatrix aTextMatrix(createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), + double(aTextPos.X()), double(aTextPos.Y()))); + + aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), + std::vector<double>(), aFontAttr, css::lang::Locale(), aLineColor))); + + // Create the 'plus' or 'arrow' primitive + B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0), + B2DSize(aRect.Right(), aRect.getHeight())); + + B2DPolygon aSign; + bool bIsEmptyHeaderFooter = IsEmptyHeaderFooter(); + if (bIsEmptyHeaderFooter) + { + // Create the + polygon + double nLeft = aSignArea.getMinX() + TEXT_PADDING; + double nRight = aSignArea.getMaxX() - TEXT_PADDING; + double nHalfW = ( nRight - nLeft ) / 2.0; + + double nTop = aSignArea.getCenterY() - nHalfW; + double nBottom = aSignArea.getCenterY() + nHalfW; + + aSign.append(B2DPoint(nLeft, aSignArea.getCenterY() - 1.0)); + aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, aSignArea.getCenterY() - 1.0)); + aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, nTop)); + aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, nTop)); + aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, aSignArea.getCenterY() - 1.0)); + aSign.append(B2DPoint(nRight, aSignArea.getCenterY() - 1.0)); + aSign.append(B2DPoint(nRight, aSignArea.getCenterY() + 1.0)); + aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, aSignArea.getCenterY() + 1.0)); + aSign.append(B2DPoint(aSignArea.getCenterX() + 1.0, nBottom)); + aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, nBottom)); + aSign.append(B2DPoint(aSignArea.getCenterX() - 1.0, aSignArea.getCenterY() + 1.0)); + aSign.append(B2DPoint(nLeft, aSignArea.getCenterY() + 1.0)); + aSign.setClosed(true); + } + else + { + // Create the v polygon + B2DPoint aLeft(aSignArea.getMinX() + TEXT_PADDING, aSignArea.getCenterY()); + B2DPoint aRight(aSignArea.getMaxX() - TEXT_PADDING, aSignArea.getCenterY()); + B2DPoint aBottom((aLeft.getX() + aRight.getX()) / 2.0, aLeft.getY() + 4.0); + aSign.append(aLeft); + aSign.append(aRight); + aSign.append(aBottom); + aSign.setClosed(true); + } + + BColor aSignColor = COL_BLACK.getBColor(); + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + aSignColor = COL_WHITE.getBColor(); + + aSeq.push_back( drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aSign), aSignColor)) ); + + // Create the processor and process the primitives + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(*m_xVirDev, aNewViewInfos)); + + // TODO Ghost it all if needed + drawinglayer::primitive2d::Primitive2DContainer aGhostedSeq(1); + double nFadeRate = double(m_nFadeRate) / 100.0; + + const basegfx::BColorModifierSharedPtr aBColorModifier = + std::make_shared<basegfx::BColorModifier_interpolate>(COL_WHITE.getBColor(), + 1.0 - nFadeRate); + + aGhostedSeq[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::ModifiedColorPrimitive2D(std::move(aSeq), aBColorModifier)); + + pProcessor->process(aGhostedSeq); + + if (bIsEmptyHeaderFooter) + m_xPushButton->set_custom_button(m_xVirDev.get()); + else + m_xMenuButton->set_custom_button(m_xVirDev.get()); +} + +bool SwHeaderFooterWin::IsEmptyHeaderFooter( ) const +{ + bool bResult = true; + + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + if (!pPageFrame) + { + return bResult; + } + + // Actually check it + const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); + + bool const bFirst(pPageFrame->OnFirstPage()); + const SwFrameFormat *const pFormat = (pPageFrame->OnRightPage()) + ? pDesc->GetRightFormat(bFirst) + : pDesc->GetLeftFormat(bFirst); + + if ( pFormat ) + { + if ( m_bIsHeader ) + bResult = !pFormat->GetHeader().IsActive(); + else + bResult = !pFormat->GetFooter().IsActive(); + } + + return bResult; +} + +void SwHeaderFooterWin::ExecuteCommand(std::string_view rIdent) +{ + SwView& rView = m_pEditWin->GetView(); + SwWrtShell& rSh = rView.GetWrtShell(); + + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName(); + if (rIdent == "edit") + { + OString sPageId = m_bIsHeader ? OString("header") : OString("footer"); + rView.GetDocShell()->FormatPage(rView.GetFrameWeld(), rStyleName, sPageId, rSh); + } + else if (rIdent == "borderback") + { + const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); + const SwFrameFormat& rMaster = pDesc->GetMaster(); + SwFrameFormat* pHFFormat = const_cast< SwFrameFormat* >( rMaster.GetFooter().GetFooterFormat() ); + if ( m_bIsHeader ) + pHFFormat = const_cast< SwFrameFormat* >( rMaster.GetHeader().GetHeaderFormat() ); + SfxItemSet aSet( pHFFormat->GetAttrSet() ); + + // Items to hand over XPropertyList things like XColorList, + // XHatchList, XGradientList, and XBitmapList to the Area TabPage: + aSet.MergeRange( SID_COLOR_TABLE, SID_PATTERN_LIST ); + // create needed items for XPropertyList entries from the DrawModel so that + // the Area TabPage can access them + rSh.GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->PutAreaListItems( aSet ); + + aSet.MergeRange(SID_ATTR_BORDER_INNER, SID_ATTR_BORDER_INNER); + // Create a box info item... needed by the dialog + std::shared_ptr<SvxBoxInfoItem> aBoxInfo(std::make_shared<SvxBoxInfoItem>(SID_ATTR_BORDER_INNER)); + if (const SvxBoxInfoItem *pBoxInfo = pHFFormat->GetAttrSet().GetItemIfSet(SID_ATTR_BORDER_INNER)) + aBoxInfo.reset(pBoxInfo->Clone()); + + aBoxInfo->SetTable(false); + aBoxInfo->SetDist(true); + aBoxInfo->SetMinDist(false); + aBoxInfo->SetDefDist(MIN_BORDER_DIST); + aBoxInfo->SetValid(SvxBoxInfoItemValidFlags::DISABLE); + aSet.Put(*aBoxInfo); + + if (svx::ShowBorderBackgroundDlg( GetFrameWeld(), &aSet ) ) + { + pHFFormat->SetFormatAttr( aSet ); + rView.GetDocShell()->SetModified(); + } + } + else if (rIdent == "delete") + { + rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, false, true ); + // warning: "this" may be disposed now + rSh.GetWin()->GrabFocusToDocument(); + } + else if (rIdent == "insert_pagenumber") + { + SfxViewFrame* pVFrame = rSh.GetView().GetViewFrame(); + pVFrame->GetBindings().Execute(FN_INSERT_FLD_PGNUMBER); + } + else if (rIdent == "insert_pagecount") + { + SfxViewFrame* pVFrame = rSh.GetView().GetViewFrame(); + pVFrame->GetBindings().Execute(FN_INSERT_FLD_PGCOUNT); + } +} + +IMPL_LINK_NOARG(SwHeaderFooterWin, ClickHdl, weld::Button&, void) +{ + SwView& rView = m_pEditWin->GetView(); + SwWrtShell& rSh = rView.GetWrtShell(); + + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName(); + { + VclPtr<SwHeaderFooterWin> xThis(this); + rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, true, false ); + //tdf#153059 after ChangeHeaderOrFooter is it possible that "this" is disposed + if (xThis->isDisposed()) + return; + } + m_xPushButton->hide(); + m_xMenuButton->show(); + PaintButton(); +} + +IMPL_LINK(SwHeaderFooterWin, SelectHdl, const OString&, rIdent, void) +{ + ExecuteCommand(rIdent); +} + +IMPL_LINK_NOARG(SwHeaderFooterWin, FadeHandler, Timer *, void) +{ + if (m_bIsAppearing && m_nFadeRate > 0) + m_nFadeRate -= 25; + else if (!m_bIsAppearing && m_nFadeRate < 100) + m_nFadeRate += 25; + + if (m_nFadeRate != 100 && !IsVisible()) + { + Show(); + } + else if (m_nFadeRate == 100 && IsVisible()) + { + Show(false); + } + else + PaintButton(); + + if (IsVisible() && m_nFadeRate > 0 && m_nFadeRate < 100) + m_aFadeTimer.Start(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/OutlineContentVisibilityWin.cxx b/sw/source/uibase/docvw/OutlineContentVisibilityWin.cxx new file mode 100644 index 000000000..33d0b72ff --- /dev/null +++ b/sw/source/uibase/docvw/OutlineContentVisibilityWin.cxx @@ -0,0 +1,246 @@ +/* -*- 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/. + */ + +#include <edtwin.hxx> +#include <OutlineContentVisibilityWin.hxx> +#include <view.hxx> +#include <wrtsh.hxx> + +#include <IDocumentOutlineNodes.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <vcl/InterimItemWindow.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <strings.hrc> + +#include <viewopt.hxx> + +#include <FrameControlsManager.hxx> + +SwOutlineContentVisibilityWin::SwOutlineContentVisibilityWin(SwEditWin* pEditWin, + const SwFrame* pFrame) + : InterimItemWindow(pEditWin, "modules/swriter/ui/outlinebutton.ui", "OutlineButton") + , m_xShowBtn(m_xBuilder->weld_button("show")) + , m_xHideBtn(m_xBuilder->weld_button("hide")) + , m_pEditWin(pEditWin) + , m_pFrame(pFrame) + , m_nDelayAppearing(0) + , m_aDelayTimer("SwOutlineContentVisibilityWin m_aDelayTimer") + , m_bDestroyed(false) + , m_nOutlinePos(SwOutlineNodes::npos) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + SetPaintTransparent(false); + SetBackground(rStyleSettings.GetFaceColor()); + + Size aBtnsSize(m_xShowBtn->get_preferred_size()); + auto nDim = std::max(aBtnsSize.Width(), aBtnsSize.Height()); + m_xShowBtn->set_size_request(nDim, nDim); + m_xHideBtn->set_size_request(nDim, nDim); + + SetSizePixel(get_preferred_size()); + SetSymbol(ButtonSymbol::NONE); + + m_xShowBtn->connect_mouse_press(LINK(this, SwOutlineContentVisibilityWin, MousePressHdl)); + m_xHideBtn->connect_mouse_press(LINK(this, SwOutlineContentVisibilityWin, MousePressHdl)); + + m_aDelayTimer.SetTimeout(25); + m_aDelayTimer.SetInvokeHandler(LINK(this, SwOutlineContentVisibilityWin, DelayAppearHandler)); +} + +void SwOutlineContentVisibilityWin::dispose() +{ + m_bDestroyed = true; + m_aDelayTimer.Stop(); + + m_pEditWin.clear(); + m_pFrame = nullptr; + + m_xHideBtn.reset(); + m_xShowBtn.reset(); + + InterimItemWindow::dispose(); +} + +ButtonSymbol SwOutlineContentVisibilityWin::GetSymbol() const +{ + if (m_xShowBtn->get_visible()) + return ButtonSymbol::SHOW; + if (m_xHideBtn->get_visible()) + return ButtonSymbol::HIDE; + return ButtonSymbol::NONE; +} + +void SwOutlineContentVisibilityWin::SetSymbol(ButtonSymbol eStyle) +{ + if (GetSymbol() == eStyle) + return; + + bool bShow = eStyle == ButtonSymbol::SHOW; + bool bHide = eStyle == ButtonSymbol::HIDE; + + // disable mouse move for the hidden button so we don't get mouse + // leave events we don't care about when we swap buttons + m_xShowBtn->connect_mouse_move(Link<const MouseEvent&, bool>()); + m_xHideBtn->connect_mouse_move(Link<const MouseEvent&, bool>()); + + m_xShowBtn->set_visible(bShow); + m_xHideBtn->set_visible(bHide); + + weld::Button* pButton = nullptr; + if (bShow) + pButton = m_xShowBtn.get(); + else if (bHide) + pButton = m_xHideBtn.get(); + InitControlBase(pButton); + if (pButton) + pButton->connect_mouse_move(LINK(this, SwOutlineContentVisibilityWin, MouseMoveHdl)); +} + +void SwOutlineContentVisibilityWin::Set() +{ + const SwTextFrame* pTextFrame = static_cast<const SwTextFrame*>(GetFrame()); + const SwTextNode* pTextNode = pTextFrame->GetTextNodeFirst(); + SwWrtShell& rSh = GetEditWin()->GetView().GetWrtShell(); + const SwOutlineNodes& rOutlineNodes = rSh.GetNodes().GetOutLineNds(); + + (void)rOutlineNodes.Seek_Entry(static_cast<SwNode*>(const_cast<SwTextNode*>(pTextNode)), + &m_nOutlinePos); + + // set symbol displayed on button + bool bVisible = true; + const_cast<SwTextNode*>(pTextNode)->GetAttrOutlineContentVisible(bVisible); + SetSymbol(bVisible ? ButtonSymbol::HIDE : ButtonSymbol::SHOW); + + // set quick help + SwOutlineNodes::size_type nOutlineNodesCount + = rSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); + int nLevel = rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(m_nOutlinePos); + OUString sQuickHelp(SwResId(STR_OUTLINE_CONTENT_TOGGLE_VISIBILITY)); + if (!rSh.GetViewOptions()->IsTreatSubOutlineLevelsAsContent() + && m_nOutlinePos + 1 < nOutlineNodesCount + && rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(m_nOutlinePos + 1) > nLevel) + sQuickHelp += " (" + SwResId(STR_OUTLINE_CONTENT_TOGGLE_VISIBILITY_EXT) + ")"; + SetQuickHelpText(sQuickHelp); + + // Set the position of the window + SwRect aFrameAreaRect = GetFrame()->getFrameArea(); + aFrameAreaRect.AddTop(GetFrame()->GetTopMargin()); + SwRect aCharRect; + GetFrame()->GetCharRect(aCharRect, SwPosition(*pTextNode)); + Point aPxPt(GetEditWin()->GetOutDev()->LogicToPixel( + Point(aCharRect.Left(), aFrameAreaRect.Center().getY()))); + if (GetFrame()->IsRightToLeft()) + aPxPt.AdjustX(2); + else + aPxPt.AdjustX(-(GetSizePixel().getWidth() + 2)); + aPxPt.AdjustY(-GetSizePixel().getHeight() / 2); + SetPosPixel(aPxPt); +} + +void SwOutlineContentVisibilityWin::ShowAll(bool bShow) +{ + if (bShow) + { + m_nDelayAppearing = 0; + if (!m_bDestroyed && m_aDelayTimer.IsActive()) + m_aDelayTimer.Stop(); + if (!m_bDestroyed) + m_aDelayTimer.Start(); + } + else + Hide(); +} + +bool SwOutlineContentVisibilityWin::Contains(const Point& rDocPt) const +{ + ::tools::Rectangle aRect(GetPosPixel(), GetSizePixel()); + if (aRect.Contains(rDocPt)) + return true; + return false; +} + +IMPL_LINK(SwOutlineContentVisibilityWin, MouseMoveHdl, const MouseEvent&, rMEvt, bool) +{ + if (rMEvt.IsLeaveWindow()) + { + if (GetSymbol() == ButtonSymbol::HIDE) + { + // MouseMove event may not be seen by the edit window for example when move is to + // a show button or when move is outside of the edit window. + // Only hide when mouse leave results in leaving the frame. + tools::Rectangle aFrameAreaPxRect + = GetEditWin()->LogicToPixel(GetFrame()->getFrameArea().SVRect()); + auto nY = GetPosPixel().getY() + rMEvt.GetPosPixel().getY(); + if (nY <= 0 || nY <= aFrameAreaPxRect.Top() || nY >= aFrameAreaPxRect.Bottom() + || nY >= GetEditWin()->GetSizePixel().Height()) + { + GetEditWin()->SetSavedOutlineFrame(nullptr); + GetEditWin()->GetFrameControlsManager().RemoveControlsByType( + FrameControlType::Outline, GetFrame()); + // warning: "this" is disposed now + } + } + } + else if (rMEvt.IsEnterWindow()) + { + // Leave window event might not have resulted in removing hide button from saved frame + // and the edit win might not receive mouse event between leaving saved frame button and + // entering this button. + if (GetFrame() != GetEditWin()->GetSavedOutlineFrame()) + { + SwFrameControlPtr pFrameControl = GetEditWin()->GetFrameControlsManager().GetControl( + FrameControlType::Outline, GetEditWin()->GetSavedOutlineFrame()); + if (pFrameControl) + { + SwOutlineContentVisibilityWin* pControl + = dynamic_cast<SwOutlineContentVisibilityWin*>(pFrameControl->GetIFacePtr()); + if (pControl && pControl->GetSymbol() == ButtonSymbol::HIDE) + { + GetEditWin()->GetFrameControlsManager().RemoveControlsByType( + FrameControlType::Outline, GetEditWin()->GetSavedOutlineFrame()); + // The outline content visibility window frame control (hide button) + // for saved outline frame is now disposed. + } + } + GetEditWin()->SetSavedOutlineFrame( + static_cast<SwTextFrame*>(const_cast<SwFrame*>(GetFrame()))); + } + if (!m_bDestroyed && m_aDelayTimer.IsActive()) + m_aDelayTimer.Stop(); + // bring button to top + SetZOrder(this, ZOrderFlags::First); + } + return false; +} + +// Toggle the outline content visibility on mouse press +IMPL_LINK(SwOutlineContentVisibilityWin, MousePressHdl, const MouseEvent&, rMEvt, bool) +{ + Hide(); + GetEditWin()->ToggleOutlineContentVisibility(m_nOutlinePos, rMEvt.IsRight()); + return false; +} + +IMPL_LINK_NOARG(SwOutlineContentVisibilityWin, DelayAppearHandler, Timer*, void) +{ + const int TICKS_BEFORE_WE_APPEAR = 3; + if (m_nDelayAppearing < TICKS_BEFORE_WE_APPEAR) + { + ++m_nDelayAppearing; + m_aDelayTimer.Start(); + return; + } + if (GetEditWin()->GetSavedOutlineFrame() == GetFrame()) + Show(); + m_aDelayTimer.Stop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/OverlayRanges.cxx b/sw/source/uibase/docvw/OverlayRanges.cxx new file mode 100644 index 000000000..ccbcc0041 --- /dev/null +++ b/sw/source/uibase/docvw/OverlayRanges.cxx @@ -0,0 +1,176 @@ +/* -*- 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 "OverlayRanges.hxx" +#include <view.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svdview.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svtools/optionsdrawinglayer.hxx> + +namespace +{ + // combine ranges geometrically to a single, ORed polygon + basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges) + { + const sal_uInt32 nCount(rRanges.size()); + basegfx::B2DPolyPolygon aRetval; + + for(sal_uInt32 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a])); + + if(0 == a) + { + aRetval.append(aDiscretePolygon); + } + else + { + aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon)); + } + } + + return aRetval; + } +} + +namespace sw::overlay +{ + drawinglayer::primitive2d::Primitive2DContainer OverlayRanges::createOverlayObjectPrimitive2DSequence() + { + const sal_uInt32 nCount(getRanges().size()); + drawinglayer::primitive2d::Primitive2DContainer aRetval; + aRetval.resize(nCount); + for ( sal_uInt32 a = 0; a < nCount; ++a ) + { + const basegfx::BColor aRGBColor(getBaseColor().getBColor()); + const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(maRanges[a])); + aRetval[a] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + aRGBColor)); + } + // embed all rectangles in transparent paint + const sal_uInt16 nTransparence( SvtOptionsDrawinglayer::GetTransparentSelectionPercent() ); + const double fTransparence( nTransparence / 100.0 ); + const drawinglayer::primitive2d::Primitive2DReference aUnifiedTransparence( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(aRetval), + fTransparence)); + + if ( mbShowSolidBorder ) + { + const basegfx::BColor aRGBColor(getBaseColor().getBColor()); + const basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges())); + const drawinglayer::primitive2d::Primitive2DReference aOutline( + new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D( + aPolyPolygon, + aRGBColor)); + + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aUnifiedTransparence, aOutline }; + } + else + { + aRetval = drawinglayer::primitive2d::Primitive2DContainer { aUnifiedTransparence }; + } + + return aRetval; + } + + /*static*/ std::unique_ptr<OverlayRanges> OverlayRanges::CreateOverlayRange( + SwView const & rDocView, + const Color& rColor, + std::vector< basegfx::B2DRange >&& rRanges, + const bool bShowSolidBorder ) + { + std::unique_ptr<OverlayRanges> pOverlayRanges; + + SdrView* pView = rDocView.GetDrawView(); + if ( pView != nullptr ) + { + SdrPaintWindow* pCandidate = pView->GetPaintWindow(0); + const rtl::Reference<sdr::overlay::OverlayManager>& xTargetOverlay = pCandidate->GetOverlayManager(); + + if ( xTargetOverlay.is() ) + { + pOverlayRanges.reset(new sw::overlay::OverlayRanges( rColor, std::move(rRanges), bShowSolidBorder )); + xTargetOverlay->add( *pOverlayRanges ); + } + } + + return pOverlayRanges; + } + + OverlayRanges::OverlayRanges( + const Color& rColor, + std::vector< basegfx::B2DRange >&& rRanges, + const bool bShowSolidBorder ) + : sdr::overlay::OverlayObject( rColor ) + , maRanges( std::move(rRanges) ) + , mbShowSolidBorder( bShowSolidBorder ) + { + // no AA for highlight overlays + allowAntiAliase(false); + } + + OverlayRanges::~OverlayRanges() + { + if( getOverlayManager() ) + { + getOverlayManager()->remove(*this); + } + } + + void OverlayRanges::setRanges(std::vector< basegfx::B2DRange >&& rNew) + { + if(rNew != maRanges) + { + maRanges = std::move(rNew); + objectChange(); + } + } + + void OverlayRanges::ShowSolidBorder() + { + if ( !mbShowSolidBorder ) + { + mbShowSolidBorder = true; + objectChange(); + } + } + + void OverlayRanges::HideSolidBorder() + { + if ( mbShowSolidBorder ) + { + mbShowSolidBorder = false; + objectChange(); + } + } + +} // end of namespace sw::overlay + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/OverlayRanges.hxx b/sw/source/uibase/docvw/OverlayRanges.hxx new file mode 100644 index 000000000..d698e2cde --- /dev/null +++ b/sw/source/uibase/docvw/OverlayRanges.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svx/sdr/overlay/overlayobject.hxx> +#include <basegfx/range/b2drange.hxx> + +#include <memory> +#include <vector> + +class SwView; + +namespace sw::overlay + { + class OverlayRanges final : public sdr::overlay::OverlayObject + { + public: + static std::unique_ptr<OverlayRanges> CreateOverlayRange( + SwView const & rDocView, + const Color& rColor, + std::vector< basegfx::B2DRange >&& rRanges, + const bool bShowSolidBorder ); + + virtual ~OverlayRanges() override; + + // data read access + const std::vector< basegfx::B2DRange >& getRanges() const + { + return maRanges; + } + + // data write access + void setRanges(std::vector< basegfx::B2DRange >&& rNew); + + void ShowSolidBorder(); + void HideSolidBorder(); + + private: + OverlayRanges( + const Color& rColor, + std::vector< basegfx::B2DRange >&& rRanges, + const bool bShowSolidBorder ); + + // geometry creation for OverlayObject + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + + // geometry of overlay + std::vector< basegfx::B2DRange > maRanges; + bool mbShowSolidBorder; + }; +} // end of namespace sw::overlay + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/PageBreakWin.cxx b/sw/source/uibase/docvw/PageBreakWin.cxx new file mode 100644 index 000000000..600d23a44 --- /dev/null +++ b/sw/source/uibase/docvw/PageBreakWin.cxx @@ -0,0 +1,506 @@ +/* -*- 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/. + */ + +#include <bitmaps.hlst> + +#include <cmdid.h> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <notxtfrm.hxx> +#include <ndtxt.hxx> +#include <DashedLine.hxx> +#include <doc.hxx> +#include <edtwin.hxx> +#include <fmtpdsc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <PageBreakWin.hxx> +#include <pagefrm.hxx> +#include <PostItMgr.hxx> +#include <FrameControlsManager.hxx> +#include <strings.hrc> +#include <tabfrm.hxx> +#include <uiitems.hxx> +#include <uiobject.hxx> +#include <view.hxx> +#include <viewopt.hxx> +#include <wrtsh.hxx> + +#include <basegfx/color/bcolortools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <editeng/formatbreakitem.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/stritem.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <memory> + +#define BUTTON_WIDTH 30 +#define BUTTON_HEIGHT 19 +#define ARROW_WIDTH 9 + +using namespace basegfx; +using namespace basegfx::utils; + +SwBreakDashedLine::SwBreakDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame) + : SwDashedLine(pEditWin, &SwViewOption::GetPageBreakColor) + , m_pEditWin(pEditWin) + , m_pFrame(pFrame) +{ + set_id("PageBreak"); // for uitest +} + +SwPageBreakWin& SwBreakDashedLine::GetOrCreateWin() +{ + if (!m_pWin) + { + m_pWin = VclPtr<SwPageBreakWin>::Create(this, m_pEditWin, m_pFrame); + m_pWin->SetPosSizePixel(m_aBtnRect.TopLeft(), m_aBtnRect.GetSize()); + m_pWin->SetZOrder(this, ZOrderFlags::Before); + } + return *m_pWin; +} + +void SwBreakDashedLine::DestroyWin() +{ + m_pWin.disposeAndClear(); +} + +void SwBreakDashedLine::MouseMove( const MouseEvent& rMEvt ) +{ + if ( rMEvt.IsLeaveWindow() ) + { + // don't fade if we just move to the 'button' + Point aEventPos( GetPosPixel() + rMEvt.GetPosPixel() ); + if (m_pWin && (!Contains(aEventPos) || !m_pWin->IsVisible())) + m_pWin->Fade(false); + } + else if (!m_pWin || !m_pWin->IsVisible()) + { + GetOrCreateWin().Fade(true); + } + + if (!rMEvt.IsSynthetic() && (!m_pWin || !m_pWin->IsVisible())) + { + UpdatePosition(rMEvt.GetPosPixel()); + } +} + +void SwBreakDashedLine::ShowAll(bool bShow) +{ + Show(bShow); +} + +void SwBreakDashedLine::SetReadonly(bool bReadonly) +{ + ShowAll(!bReadonly); +} + +bool SwBreakDashedLine::Contains(const Point &rDocPt) const +{ + if (m_aBtnRect.Contains(rDocPt)) + return true; + + ::tools::Rectangle aLineRect(GetPosPixel(), GetSizePixel()); + return aLineRect.Contains(rDocPt); +} + +SwPageBreakWin::SwPageBreakWin(SwBreakDashedLine* pLine, SwEditWin* pEditWin, const SwFrame *pFrame) : + InterimItemWindow(pEditWin, "modules/swriter/ui/pbmenubutton.ui", "PBMenuButton"), + m_xMenuButton(m_xBuilder->weld_menu_button("menubutton")), + m_pLine(pLine), + m_pEditWin(pEditWin), + m_pFrame(pFrame), + m_bIsAppearing( false ), + m_nFadeRate( 100 ), + m_nDelayAppearing( 0 ), + m_aFadeTimer("SwPageBreakWin m_aFadeTimer"), + m_bDestroyed( false ) +{ + m_xMenuButton->connect_toggled(LINK(this, SwPageBreakWin, ToggleHdl)); + m_xMenuButton->connect_selected(LINK(this, SwPageBreakWin, SelectHdl)); + m_xMenuButton->set_accessible_name(SwResId(STR_PAGE_BREAK_BUTTON)); + + m_xVirDev = m_xMenuButton->create_virtual_device(); + SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev); + + // Use pixels for the rest of the drawing + m_xVirDev->SetMapMode( MapMode ( MapUnit::MapPixel ) ); + + m_aFadeTimer.SetTimeout( 50 ); + m_aFadeTimer.SetInvokeHandler( LINK( this, SwPageBreakWin, FadeHandler ) ); +} + +SwPageBreakWin::~SwPageBreakWin( ) +{ + disposeOnce(); +} + +void SwPageBreakWin::dispose() +{ + m_bDestroyed = true; + m_aFadeTimer.Stop(); + m_xVirDev.disposeAndClear(); + + m_pLine.clear(); + m_pEditWin.clear(); + + m_xMenuButton.reset(); + InterimItemWindow::dispose(); +} + +void SwPageBreakWin::PaintButton() +{ + if (!m_xVirDev) + return; + + const ::tools::Rectangle aRect(::tools::Rectangle(Point(0, 0), m_xVirDev->PixelToLogic(GetSizePixel()))); + + // Properly paint the control + BColor aColor = SwViewOption::GetPageBreakColor().getBColor(); + + BColor aHslLine = rgb2hsl(aColor); + double nLuminance = aHslLine.getZ(); + nLuminance += (1.0 - nLuminance) * 0.75; + if ( aHslLine.getZ() > 0.7 ) + nLuminance = aHslLine.getZ() * 0.7; + aHslLine.setZ(nLuminance); + BColor aOtherColor = hsl2rgb(aHslLine); + + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + if (rSettings.GetHighContrastMode()) + { + aColor = rSettings.GetDialogTextColor().getBColor(); + aOtherColor = rSettings.GetDialogColor().getBColor(); + } + + bool bRtl = AllSettings::GetLayoutRTL(); + + drawinglayer::primitive2d::Primitive2DContainer aSeq(3); + B2DRectangle aBRect = vcl::unotools::b2DRectangleFromRectangle(aRect); + B2DPolygon aPolygon = createPolygonFromRect(aBRect, 3.0 / BUTTON_WIDTH, 3.0 / BUTTON_HEIGHT); + + // Create the polygon primitives + aSeq[0].set(new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aPolygon), aOtherColor)); + aSeq[1].set(new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aPolygon, aColor)); + + // Create the primitive for the image + BitmapEx aBmpEx(RID_BMP_PAGE_BREAK); + double nImgOfstX = 3.0; + if (bRtl) + nImgOfstX = aRect.Right() - aBmpEx.GetSizePixel().Width() - 3.0; + aSeq[2].set(new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D( + aBmpEx, B2DPoint(nImgOfstX, 1.0))); + + double nTop = double(aRect.getHeight()) / 2.0; + double nBottom = nTop + 4.0; + double nLeft = aRect.getWidth() - ARROW_WIDTH - 6.0; + if (bRtl) + nLeft = ARROW_WIDTH - 2.0; + double nRight = nLeft + 8.0; + + B2DPolygon aTriangle; + aTriangle.append(B2DPoint(nLeft, nTop)); + aTriangle.append(B2DPoint(nRight, nTop)); + aTriangle.append(B2DPoint((nLeft + nRight) / 2.0, nBottom)); + aTriangle.setClosed(true); + + BColor aTriangleColor = COL_BLACK.getBColor(); + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + aTriangleColor = COL_WHITE.getBColor(); + + aSeq.emplace_back(); + aSeq.back().set( new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + B2DPolyPolygon(aTriangle), aTriangleColor)); + + drawinglayer::primitive2d::Primitive2DContainer aGhostedSeq(1); + double nFadeRate = double(m_nFadeRate) / 100.0; + const basegfx::BColorModifierSharedPtr aBColorModifier = + std::make_shared<basegfx::BColorModifier_interpolate>(COL_WHITE.getBColor(), + 1.0 - nFadeRate); + aGhostedSeq[0].set( new drawinglayer::primitive2d::ModifiedColorPrimitive2D( + std::move(aSeq), aBColorModifier)); + + // Create the processor and process the primitives + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(*m_xVirDev, aNewViewInfos)); + + pProcessor->process(aGhostedSeq); + + m_xMenuButton->set_custom_button(m_xVirDev.get()); +} + +static SvxBreak lcl_GetBreakItem(const SwContentFrame* pCnt) +{ + SvxBreak eBreak = SvxBreak::NONE; + if ( pCnt ) + { + if ( pCnt->IsInTab() ) + eBreak = pCnt->FindTabFrame()->GetBreakItem().GetBreak(); + else + eBreak = pCnt->GetBreakItem().GetBreak(); + } + return eBreak; +} + +IMPL_LINK(SwPageBreakWin, SelectHdl, const OString&, rIdent, void) +{ + SwFrameControlPtr pFrameControl = m_pEditWin->GetFrameControlsManager().GetControl(FrameControlType::PageBreak, m_pFrame); + + m_pLine->execute(rIdent); + + // Only fade if there is more than this temporary shared pointer: + // The main reference has been deleted due to a page break removal + if (pFrameControl.use_count() > 1) + Fade( false ); +} + +void SwBreakDashedLine::execute(std::string_view rIdent) +{ + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + // Is there a PageBefore break on this page? + SwContentFrame *pCnt = const_cast<SwContentFrame*>(pPageFrame->FindFirstBodyContent()); + SvxBreak eBreak = lcl_GetBreakItem( pCnt ); + + // Also check the previous page - to see if there is a PageAfter break + SwContentFrame *pPrevCnt = nullptr; + SvxBreak ePrevBreak = SvxBreak::NONE; + const SwPageFrame* pPrevPage = static_cast<const SwPageFrame*>(pPageFrame->GetPrev()); + if ( pPrevPage ) + { + pPrevCnt = const_cast<SwContentFrame*>(pPrevPage->FindLastBodyContent()); + ePrevBreak = lcl_GetBreakItem( pPrevCnt ); + } + + if (pCnt && rIdent == "edit") + { + SwWrtShell& rSh = m_pEditWin->GetView().GetWrtShell(); + bool bOldLock = rSh.IsViewLocked(); + rSh.LockView( true ); + + // Order of edit detection: first RES_BREAK PageAfter, then RES_BREAK PageBefore/RES_PAGEDESC + if ( ePrevBreak == SvxBreak::PageAfter ) + pCnt = pPrevCnt; + + SwContentNode& rNd = pCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + + if ( pCnt->IsInTab() ) + { + rSh.Push( ); + rSh.ClearMark(); + + rSh.SetSelection( rNd ); + + SfxStringItem aItem(m_pEditWin->GetView().GetPool().GetWhich(FN_FORMAT_TABLE_DLG), "textflow"); + m_pEditWin->GetView().GetViewFrame()->GetDispatcher()->ExecuteList( + FN_FORMAT_TABLE_DLG, + SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + { &aItem }); + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + else + { + SwPaM aPaM( rNd ); + SwPaMItem aPaMItem( m_pEditWin->GetView().GetPool( ).GetWhich( FN_PARAM_PAM ), &aPaM ); + SfxStringItem aItem( SID_PARA_DLG, "textflow" ); + m_pEditWin->GetView().GetViewFrame()->GetDispatcher()->ExecuteList( + SID_PARA_DLG, + SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + { &aItem, &aPaMItem }); + } + rSh.LockView( bOldLock ); + m_pEditWin->GrabFocus( ); + } + else if (pCnt && rIdent == "delete") + { + SwContentNode& rNd = pCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pCnt)->GetNode(); + + rNd.GetDoc().GetIDocumentUndoRedo( ).StartUndo( SwUndoId::UI_DELETE_PAGE_BREAK, nullptr ); + + SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> aSet( + m_pEditWin->GetView().GetWrtShell().GetAttrPool()); + + aSet.Put( SwFormatPageDesc( nullptr ) ); + // This break could be from the current paragraph, if it has a PageBefore break. + if ( eBreak == SvxBreak::PageBefore ) + aSet.Put( SvxFormatBreakItem( SvxBreak::NONE, RES_BREAK ) ); + + rNd.GetDoc().getIDocumentContentOperations().InsertItemSet( + SwPaM(rNd), aSet, SetAttrMode::DEFAULT, pPageFrame->getRootFrame()); + + // This break could be from the previous paragraph, if it has a PageAfter break. + if ( ePrevBreak == SvxBreak::PageAfter ) + { + SwContentNode& rPrevNd = pPrevCnt->IsTextFrame() + ? *static_cast<SwTextFrame*>(pPrevCnt)->GetTextNodeFirst() + : *static_cast<SwNoTextFrame*>(pPrevCnt)->GetNode(); + aSet.ClearItem(); + aSet.Put( SvxFormatBreakItem( SvxBreak::NONE, RES_BREAK ) ); + rPrevNd.GetDoc().getIDocumentContentOperations().InsertItemSet( + SwPaM(rPrevNd), aSet, SetAttrMode::DEFAULT, pPrevCnt->getRootFrame()); + } + + rNd.GetDoc().GetIDocumentUndoRedo( ).EndUndo( SwUndoId::UI_DELETE_PAGE_BREAK, nullptr ); + } +} + +void SwBreakDashedLine::UpdatePosition(const std::optional<Point>& xEvtPt) +{ + if ( xEvtPt ) + { + if ( xEvtPt == m_xMousePt ) + return; + m_xMousePt = xEvtPt; + } + + const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame); + const SwFrame* pPrevPage = pPageFrame; + do + { + pPrevPage = pPrevPage->GetPrev(); + } + while ( pPrevPage && ( ( pPrevPage->getFrameArea().Top( ) == pPageFrame->getFrameArea().Top( ) ) + || static_cast< const SwPageFrame* >( pPrevPage )->IsEmptyPage( ) ) ); + + ::tools::Rectangle aBoundRect = GetEditWin()->LogicToPixel( pPageFrame->GetBoundRect(GetEditWin()->GetOutDev()).SVRect() ); + ::tools::Rectangle aFrameRect = GetEditWin()->LogicToPixel( pPageFrame->getFrameArea().SVRect() ); + + tools::Long nYLineOffset = ( aBoundRect.Top() + aFrameRect.Top() ) / 2; + if ( pPrevPage ) + { + ::tools::Rectangle aPrevFrameRect = GetEditWin()->LogicToPixel( pPrevPage->getFrameArea().SVRect() ); + nYLineOffset = ( aPrevFrameRect.Bottom() + aFrameRect.Top() ) / 2; + } + + // Get the page + sidebar coords + tools::Long nPgLeft = aFrameRect.Left(); + tools::Long nPgRight = aFrameRect.Right(); + + tools::ULong nSidebarWidth = 0; + const SwPostItMgr* pPostItMngr = GetEditWin()->GetView().GetWrtShell().GetPostItMgr(); + if ( pPostItMngr && pPostItMngr->HasNotes() && pPostItMngr->ShowNotes() ) + nSidebarWidth = pPostItMngr->GetSidebarBorderWidth( true ) + pPostItMngr->GetSidebarWidth( true ); + + if ( pPageFrame->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::LEFT ) + nPgLeft -= nSidebarWidth; + else if ( pPageFrame->SidebarPosition( ) == sw::sidebarwindows::SidebarPosition::RIGHT ) + nPgRight += nSidebarWidth; + + Size aBtnSize( BUTTON_WIDTH + ARROW_WIDTH, BUTTON_HEIGHT ); + + // Place the button on the left or right? + ::tools::Rectangle aVisArea = GetEditWin()->LogicToPixel( GetEditWin()->GetView().GetVisArea() ); + + tools::Long nLineLeft = std::max( nPgLeft, aVisArea.Left() ); + tools::Long nLineRight = std::min( nPgRight, aVisArea.Right() ); + tools::Long nBtnLeft = nLineLeft; + + if ( m_xMousePt ) + { + nBtnLeft = nLineLeft + m_xMousePt->X() - aBtnSize.getWidth() / 2; + + if ( nBtnLeft < nLineLeft ) + nBtnLeft = nLineLeft; + else if ( ( nBtnLeft + aBtnSize.getWidth() ) > nLineRight ) + nBtnLeft = nLineRight - aBtnSize.getWidth(); + } + + // Set the button position + m_aBtnRect = ::tools::Rectangle(Point(nBtnLeft, nYLineOffset - BUTTON_HEIGHT / 2), aBtnSize); + if (m_pWin) + m_pWin->SetRectanglePixel(m_aBtnRect); + + // Set the line position + Point aLinePos( nLineLeft, nYLineOffset - 5 ); + Size aLineSize( nLineRight - nLineLeft, 10 ); + SetPosSizePixel(aLinePos, aLineSize); +} + +void SwPageBreakWin::SetRectanglePixel(const ::tools::Rectangle& rRect) +{ + SetPosSizePixel(rRect.TopLeft(), rRect.GetSize()); + m_xVirDev->SetOutputSizePixel(rRect.GetSize()); +} + +void SwPageBreakWin::Fade( bool bFadeIn ) +{ + m_bIsAppearing = bFadeIn; + if ( bFadeIn ) + m_nDelayAppearing = 0; + + if ( !m_bDestroyed && m_aFadeTimer.IsActive( ) ) + m_aFadeTimer.Stop(); + if ( !m_bDestroyed ) + m_aFadeTimer.Start( ); +} + +IMPL_LINK(SwPageBreakWin, ToggleHdl, weld::Toggleable&, rMenuButton, void) +{ + // hide on dropdown, draw fully unfaded if dropdown before fully faded in + Fade(rMenuButton.get_active()); +} + +IMPL_LINK_NOARG(SwPageBreakWin, FadeHandler, Timer *, void) +{ + const int TICKS_BEFORE_WE_APPEAR = 10; + if ( m_bIsAppearing && m_nDelayAppearing < TICKS_BEFORE_WE_APPEAR ) + { + ++m_nDelayAppearing; + m_aFadeTimer.Start(); + return; + } + + if ( m_bIsAppearing && m_nFadeRate > 0 ) + m_nFadeRate -= 25; + else if ( !m_bIsAppearing && m_nFadeRate < 100 ) + m_nFadeRate += 25; + + if ( m_nFadeRate != 100 && !IsVisible() ) + Show(); + else if ( m_nFadeRate == 100 && IsVisible( ) ) + { + Hide(); + m_pLine->DestroyWin(); + return; + } + else + { + m_pLine->UpdatePosition(); + PaintButton(); + } + + if (IsVisible( ) && m_nFadeRate > 0 && m_nFadeRate < 100) + m_aFadeTimer.Start(); +} + +FactoryFunction SwBreakDashedLine::GetUITestFactory() const +{ + return PageBreakUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/PostItMgr.cxx b/sw/source/uibase/docvw/PostItMgr.cxx new file mode 100644 index 000000000..56daa35f7 --- /dev/null +++ b/sw/source/uibase/docvw/PostItMgr.cxx @@ -0,0 +1,2518 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <boost/property_tree/json_parser.hpp> + +#include <PostItMgr.hxx> +#include <postithelper.hxx> + +#include <AnnotationWin.hxx> +#include "frmsidebarwincontainer.hxx" +#include <accmap.hxx> + +#include <SidebarWindowsConsts.hxx> +#include "AnchorOverlayObject.hxx" +#include "ShadowOverlayObject.hxx" + +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> + +#include <chrdlgmodes.hxx> +#include <viewopt.hxx> +#include <view.hxx> +#include <docsh.hxx> +#include <wrtsh.hxx> +#include <doc.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <fldbas.hxx> +#include <fmtfld.hxx> +#include <docufld.hxx> +#include <edtwin.hxx> +#include <txtfld.hxx> +#include <txtannotationfld.hxx> +#include <rootfrm.hxx> +#include <SwRewriter.hxx> +#include <tools/color.hxx> +#include <unotools/datetime.hxx> + +#include <swmodule.hxx> +#include <strings.hrc> +#include <cmdid.h> + +#include <sfx2/request.hxx> +#include <sfx2/event.hxx> +#include <svl/srchitem.hxx> + +#include <svl/languageoptions.hxx> +#include <svl/hint.hxx> + +#include <svx/svdview.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/outliner.hxx> +#include <editeng/outlobj.hxx> + +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <annotsh.hxx> +#include <swabstdlg.hxx> +#include <memory> + +// distance between Anchor Y and initial note position +#define POSTIT_INITIAL_ANCHOR_DISTANCE 20 +//distance between two postits +#define POSTIT_SPACE_BETWEEN 8 +#define POSTIT_MINIMUMSIZE_WITH_META 60 +#define POSTIT_SCROLL_SIDEBAR_HEIGHT 20 + +// if we layout more often we stop, this should never happen +#define MAX_LOOP_COUNT 50 + +using namespace sw::sidebarwindows; +using namespace sw::annotation; + +namespace { + + enum class CommentNotificationType { Add, Remove, Modify, Resolve }; + + bool comp_pos(const std::unique_ptr<SwSidebarItem>& a, const std::unique_ptr<SwSidebarItem>& b) + { + // sort by anchor position + SwPosition aPosAnchorA = a->GetAnchorPosition(); + SwPosition aPosAnchorB = b->GetAnchorPosition(); + + bool aAnchorAInFooter = false; + bool aAnchorBInFooter = false; + + // is the anchor placed in Footnote or the Footer? + if( aPosAnchorA.nNode.GetNode().FindFootnoteStartNode() || aPosAnchorA.nNode.GetNode().FindFooterStartNode() ) + aAnchorAInFooter = true; + if( aPosAnchorB.nNode.GetNode().FindFootnoteStartNode() || aPosAnchorB.nNode.GetNode().FindFooterStartNode() ) + aAnchorBInFooter = true; + + // fdo#34800 + // if AnchorA is in footnote, and AnchorB isn't + // we do not want to change over the position + if( aAnchorAInFooter && !aAnchorBInFooter ) + return false; + // if aAnchorA is not placed in a footnote, and aAnchorB is + // force a change over + else if( !aAnchorAInFooter && aAnchorBInFooter ) + return true; + // If neither or both are in the footer, compare the positions. + // Since footnotes are in Inserts section of nodes array and footers + // in Autotext section, all footnotes precede any footers so no need + // to check that. + else + return aPosAnchorA < aPosAnchorB; + } + + /// Emits LOK notification about one addition/removal/change of a comment + void lcl_CommentNotification(const SwView* pView, const CommentNotificationType nType, const SwSidebarItem* pItem, const sal_uInt32 nPostItId) + { + if (!comphelper::LibreOfficeKit::isActive()) + return; + + boost::property_tree::ptree aAnnotation; + aAnnotation.put("action", (nType == CommentNotificationType::Add ? "Add" : + (nType == CommentNotificationType::Remove ? "Remove" : + (nType == CommentNotificationType::Modify ? "Modify" : + (nType == CommentNotificationType::Resolve ? "Resolve" : "???"))))); + aAnnotation.put("id", nPostItId); + if (nType != CommentNotificationType::Remove && pItem != nullptr) + { + sw::annotation::SwAnnotationWin* pWin = pItem->mpPostIt.get(); + + const SwPostItField* pField = pWin->GetPostItField(); + const SwRect& aRect = pWin->GetAnchorRect(); + tools::Rectangle aSVRect(aRect.Pos().getX(), + aRect.Pos().getY(), + aRect.Pos().getX() + aRect.SSize().Width(), + aRect.Pos().getY() + aRect.SSize().Height()); + + if (!pItem->maLayoutInfo.mPositionFromCommentAnchor) + { + // Comments on frames: anchor position is the corner position, not the whole frame. + aSVRect.SetSize(Size(0, 0)); + } + + std::vector<OString> aRects; + for (const basegfx::B2DRange& aRange : pWin->GetAnnotationTextRanges()) + { + const SwRect rect(aRange.getMinX(), aRange.getMinY(), aRange.getWidth(), aRange.getHeight()); + aRects.push_back(rect.SVRect().toString()); + } + const OString sRects = comphelper::string::join("; ", aRects); + + aAnnotation.put("id", pField->GetPostItId()); + aAnnotation.put("parent", pWin->CalcParent()); + aAnnotation.put("author", pField->GetPar1().toUtf8().getStr()); + aAnnotation.put("text", pField->GetPar2().toUtf8().getStr()); + aAnnotation.put("resolved", pField->GetResolved() ? "true" : "false"); + aAnnotation.put("dateTime", utl::toISO8601(pField->GetDateTime().GetUNODateTime())); + aAnnotation.put("anchorPos", aSVRect.toString()); + aAnnotation.put("textRange", sRects.getStr()); + } + + boost::property_tree::ptree aTree; + aTree.add_child("comment", aAnnotation); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aPayload = aStream.str(); + + if (pView) + { + pView->libreOfficeKitViewCallback(LOK_CALLBACK_COMMENT, aPayload.c_str()); + } + } + +} // anonymous namespace + +SwPostItMgr::SwPostItMgr(SwView* pView) + : mpView(pView) + , mpWrtShell(mpView->GetDocShell()->GetWrtShell()) + , mpEditWin(&mpView->GetEditWin()) + , mnEventId(nullptr) + , mbWaitingForCalcRects(false) + , mpActivePostIt(nullptr) + , mbLayout(false) + , mbLayoutHeight(0) + , mbLayouting(false) + , mbReadOnly(mpView->GetDocShell()->IsReadOnly()) + , mbDeleteNote(true) + , mbIsShowAnchor( false ) +{ + if(!mpView->GetDrawView() ) + mpView->GetWrtShell().MakeDrawView(); + + SwNoteProps aProps; + mbIsShowAnchor = aProps.IsShowAnchor(); + + //make sure we get the colour yellow always, even if not the first one of comments or redlining + SW_MOD()->GetRedlineAuthor(); + + // collect all PostIts and redline comments that exist after loading the document + // don't check for existence for any of them, don't focus them + AddPostIts(false,false); + /* this code can be used once we want redline comments in the Sidebar + AddRedlineComments(false,false); + */ + // we want to receive stuff like SfxHintId::DocChanged + StartListening(*mpView->GetDocShell()); + if (!mvPostItFields.empty()) + { + mbWaitingForCalcRects = true; + mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) ); + } +} + +SwPostItMgr::~SwPostItMgr() +{ + if ( mnEventId ) + Application::RemoveUserEvent( mnEventId ); + // forget about all our Sidebar windows + RemoveSidebarWin(); + EndListening( *mpView->GetDocShell() ); + + mPages.clear(); +} + +bool SwPostItMgr::CheckForRemovedPostIts() +{ + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + bool bRemoved = false; + auto it = mvPostItFields.begin(); + while(it != mvPostItFields.end()) + { + if (!(*it)->UseElement(*mpWrtShell->GetLayout(), rIDRA)) + { + EndListening(const_cast<SfxBroadcaster&>(*(*it)->GetBroadcaster())); + std::unique_ptr<SwSidebarItem> p = std::move(*it); + it = mvPostItFields.erase(it); + if (GetActiveSidebarWin() == p->mpPostIt) + SetActiveSidebarWin(nullptr); + p->mpPostIt.disposeAndClear(); + bRemoved = true; + } + else + ++it; + } + + if ( !bRemoved ) + return false; + + // make sure that no deleted items remain in page lists + // todo: only remove deleted ones?! + if ( mvPostItFields.empty() ) + { + PreparePageContainer(); + PrepareView(); + } + else + { + // if postits are there make sure that page lists are not empty + // otherwise sudden paints can cause pain (in BorderOverPageBorder) + CalcRects(); + } + + return true; +} + +SwSidebarItem* SwPostItMgr::InsertItem(SfxBroadcaster* pItem, bool bCheckExistence, bool bFocus) +{ + if (bCheckExistence) + { + for (auto const& postItField : mvPostItFields) + { + if ( postItField->GetBroadcaster() == pItem ) + return nullptr; + } + } + mbLayout = bFocus; + + SwSidebarItem* pAnnotationItem = nullptr; + if (auto pSwFormatField = dynamic_cast< SwFormatField *>( pItem )) + { + mvPostItFields.push_back(std::make_unique<SwAnnotationItem>(*pSwFormatField, bFocus)); + pAnnotationItem = mvPostItFields.back().get(); + } + assert(dynamic_cast< const SwFormatField *>( pItem ) && "Mgr::InsertItem: seems like new stuff was added"); + StartListening(*pItem); + return pAnnotationItem; +} + +void SwPostItMgr::RemoveItem( SfxBroadcaster* pBroadcast ) +{ + EndListening(*pBroadcast); + auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(), + [&pBroadcast](const std::unique_ptr<SwSidebarItem>& pField) { return pField->GetBroadcaster() == pBroadcast; }); + if (i != mvPostItFields.end()) + { + std::unique_ptr<SwSidebarItem> p = std::move(*i); + // tdf#120487 remove from list before dispose, so comment window + // won't be recreated due to the entry still in the list if focus + // transferring from the pPostIt triggers relayout of postits + // tdf#133348 remove from list before calling SetActiveSidebarWin + // so GetNextPostIt won't deal with mvPostItFields containing empty unique_ptr + mvPostItFields.erase(i); + if (GetActiveSidebarWin() == p->mpPostIt) + SetActiveSidebarWin(nullptr); + p->mpPostIt.disposeAndClear(); + } + mbLayout = true; + PrepareView(); +} + +void SwPostItMgr::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if ( const SfxEventHint* pSfxEventHint = dynamic_cast<const SfxEventHint*>(&rHint) ) + { + if ( pSfxEventHint->GetEventId() == SfxEventHintId::SwEventLayoutFinished ) + { + if ( !mbWaitingForCalcRects && !mvPostItFields.empty()) + { + mbWaitingForCalcRects = true; + mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) ); + } + } + } + else if ( const SwFormatFieldHint * pFormatHint = dynamic_cast<const SwFormatFieldHint*>(&rHint) ) + { + SwFormatField* pField = const_cast <SwFormatField*>( pFormatHint->GetField() ); + switch ( pFormatHint->Which() ) + { + case SwFormatFieldHintWhich::INSERTED : + { + if (!pField) + { + AddPostIts(); + break; + } + // get field to be inserted from hint + if ( pField->IsFieldInDoc() ) + { + bool bEmpty = !HasNotes(); + SwSidebarItem* pItem = InsertItem( pField, true, false ); + + if (bEmpty && !mvPostItFields.empty()) + PrepareView(true); + + // True until the layout of this post it finishes + if (pItem) + pItem->mbPendingLayout = true; + } + else + { + OSL_FAIL("Inserted field not in document!" ); + } + break; + } + case SwFormatFieldHintWhich::REMOVED: + { + if (mbDeleteNote) + { + if (!pField) + { + const bool bWasRemoved = CheckForRemovedPostIts(); + // tdf#143643 ensure relayout on undo of insert comment + if (bWasRemoved) + mbLayout = true; + break; + } + RemoveItem(pField); + + // If LOK has disabled tiled annotations, emit annotation callbacks + if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) + { + SwPostItField* pPostItField = static_cast<SwPostItField*>(pField->GetField()); + lcl_CommentNotification(mpView, CommentNotificationType::Remove, nullptr, pPostItField->GetPostItId()); + } + } + break; + } + case SwFormatFieldHintWhich::FOCUS: + { + if (pFormatHint->GetView()== mpView) + Focus(rBC); + break; + } + case SwFormatFieldHintWhich::CHANGED: + case SwFormatFieldHintWhich::RESOLVED: + { + SwFormatField* pFormatField = dynamic_cast<SwFormatField*>(&rBC); + for (auto const& postItField : mvPostItFields) + { + if ( pFormatField == postItField->GetBroadcaster() ) + { + if (postItField->mpPostIt) + { + postItField->mpPostIt->SetPostItText(); + mbLayout = true; + } + + // If LOK has disabled tiled annotations, emit annotation callbacks + if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) + { + if(SwFormatFieldHintWhich::CHANGED == pFormatHint->Which()) + lcl_CommentNotification(mpView, CommentNotificationType::Modify, postItField.get(), 0); + else + lcl_CommentNotification(mpView, CommentNotificationType::Resolve, postItField.get(), 0); + } + break; + } + } + break; + } + + case SwFormatFieldHintWhich::LANGUAGE: + { + SwFormatField* pFormatField = dynamic_cast<SwFormatField*>(&rBC); + for (auto const& postItField : mvPostItFields) + { + if ( pFormatField == postItField->GetBroadcaster() ) + { + if (postItField->mpPostIt) + { + const SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( postItField->GetFormatField().GetField()->GetLanguage() ); + sal_uInt16 nLangWhichId = 0; + switch (nScriptType) + { + case SvtScriptType::LATIN : nLangWhichId = EE_CHAR_LANGUAGE ; break; + case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break; + case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break; + default: break; + } + postItField->mpPostIt->SetLanguage( + SvxLanguageItem( + postItField->GetFormatField().GetField()->GetLanguage(), + nLangWhichId) ); + } + break; + } + } + break; + } + } + } + else + { + SfxHintId nId = rHint.GetId(); + switch ( nId ) + { + case SfxHintId::ModeChanged: + { + if ( mbReadOnly != mpView->GetDocShell()->IsReadOnly() ) + { + mbReadOnly = !mbReadOnly; + SetReadOnlyState(); + mbLayout = true; + } + break; + } + case SfxHintId::DocChanged: + { + if ( mpView->GetDocShell() == &rBC ) + { + if ( !mbWaitingForCalcRects && !mvPostItFields.empty()) + { + mbWaitingForCalcRects = true; + mnEventId = Application::PostUserEvent( LINK( this, SwPostItMgr, CalcHdl) ); + } + } + break; + } + case SfxHintId::SwSplitNodeOperation: + { + // if we are in a SplitNode/Cut operation, do not delete note and then add again, as this will flicker + mbDeleteNote = !mbDeleteNote; + break; + } + case SfxHintId::Dying: + { + if ( mpView->GetDocShell() != &rBC ) + { + // field to be removed is the broadcaster + OSL_FAIL("Notification for removed SwFormatField was not sent!"); + RemoveItem(&rBC); + } + break; + } + default: break; + } + } +} + +void SwPostItMgr::Focus(const SfxBroadcaster& rBC) +{ + if (!mpWrtShell->GetViewOptions()->IsPostIts()) + { + SfxRequest aRequest(mpView->GetViewFrame(), SID_TOGGLE_NOTES); + mpView->ExecViewOptions(aRequest); + } + + for (auto const& postItField : mvPostItFields) + { + // field to get the focus is the broadcaster + if ( &rBC == postItField->GetBroadcaster() ) + { + if (postItField->mpPostIt) + { + if (postItField->mpPostIt->IsResolved() && + !mpWrtShell->GetViewOptions()->IsResolvedPostIts()) + { + SfxRequest aRequest(mpView->GetViewFrame(), SID_TOGGLE_RESOLVED_NOTES); + mpView->ExecViewOptions(aRequest); + } + postItField->mpPostIt->GrabFocus(); + MakeVisible(postItField->mpPostIt); + } + else + { + // when the layout algorithm starts, this postit is created and receives focus + postItField->mbFocus = true; + } + } + } +} + +bool SwPostItMgr::CalcRects() +{ + if ( mnEventId ) + { + // if CalcRects() was forced and an event is still pending: remove it + // it is superfluous and also may cause reentrance problems if triggered while layouting + Application::RemoveUserEvent( mnEventId ); + mnEventId = nullptr; + } + + bool bChange = false; + bool bRepair = false; + PreparePageContainer(); + if ( !mvPostItFields.empty() ) + { + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + for (auto const& pItem : mvPostItFields) + { + if (!pItem->UseElement(*mpWrtShell->GetLayout(), rIDRA)) + { + OSL_FAIL("PostIt is not in doc or other wrong use"); + bRepair = true; + continue; + } + const SwRect aOldAnchorRect( pItem->maLayoutInfo.mPosition ); + const SwPostItHelper::SwLayoutStatus eOldLayoutStatus = pItem->mLayoutStatus; + const SwNodeOffset nOldStartNodeIdx( pItem->maLayoutInfo.mnStartNodeIdx ); + const sal_Int32 nOldStartContent( pItem->maLayoutInfo.mnStartContent ); + { + // update layout information + const SwTextAnnotationField* pTextAnnotationField = + dynamic_cast< const SwTextAnnotationField* >( pItem->GetFormatField().GetTextField() ); + const ::sw::mark::IMark* pAnnotationMark = + pTextAnnotationField != nullptr ? pTextAnnotationField->GetAnnotationMark() : nullptr; + if ( pAnnotationMark != nullptr ) + { + pItem->mLayoutStatus = + SwPostItHelper::getLayoutInfos( + pItem->maLayoutInfo, + pItem->GetAnchorPosition(), + pAnnotationMark ); + } + else + { + pItem->mLayoutStatus = + SwPostItHelper::getLayoutInfos( pItem->maLayoutInfo, pItem->GetAnchorPosition() ); + } + } + bChange = bChange + || pItem->maLayoutInfo.mPosition != aOldAnchorRect + || pItem->mLayoutStatus != eOldLayoutStatus + || pItem->maLayoutInfo.mnStartNodeIdx != nOldStartNodeIdx + || pItem->maLayoutInfo.mnStartContent != nOldStartContent; + } + + // show notes in right order in navigator + //prevent Anchors during layout to overlap, e.g. when moving a frame + if (mvPostItFields.size()>1 ) + std::stable_sort(mvPostItFields.begin(), mvPostItFields.end(), comp_pos); + + // sort the items into the right page vector, so layout can be done by page + for (auto const& pItem : mvPostItFields) + { + if( SwPostItHelper::INVISIBLE == pItem->mLayoutStatus ) + { + if (pItem->mpPostIt) + pItem->mpPostIt->HideNote(); + continue; + } + + if( SwPostItHelper::HIDDEN == pItem->mLayoutStatus ) + { + if (!mpWrtShell->GetViewOptions()->IsShowHiddenChar()) + { + if (pItem->mpPostIt) + pItem->mpPostIt->HideNote(); + continue; + } + } + + const tools::ULong aPageNum = pItem->maLayoutInfo.mnPageNumber; + if (aPageNum > mPages.size()) + { + const tools::ULong nNumberOfPages = mPages.size(); + mPages.reserve(aPageNum); + for (tools::ULong j=0; j<aPageNum - nNumberOfPages; ++j) + mPages.emplace_back( new SwPostItPageItem()); + } + mPages[aPageNum-1]->mvSidebarItems.push_back(pItem.get()); + mPages[aPageNum-1]->mPageRect = pItem->maLayoutInfo.mPageFrame; + mPages[aPageNum-1]->eSidebarPosition = pItem->maLayoutInfo.meSidebarPosition; + } + + if (!bChange && mpWrtShell->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE)) + { + tools::Long nLayoutHeight = SwPostItHelper::getLayoutHeight( mpWrtShell->GetLayout() ); + if( nLayoutHeight > mbLayoutHeight ) + { + if (mPages[0]->bScrollbar || HasScrollbars()) + bChange = true; + } + else if( nLayoutHeight < mbLayoutHeight ) + { + if (mPages[0]->bScrollbar || !BorderOverPageBorder(1)) + bChange = true; + } + } + } + + if ( bRepair ) + CheckForRemovedPostIts(); + + mbLayoutHeight = SwPostItHelper::getLayoutHeight( mpWrtShell->GetLayout() ); + mbWaitingForCalcRects = false; + return bChange; +} + +bool SwPostItMgr::HasScrollbars() const +{ + for (auto const& postItField : mvPostItFields) + { + if (postItField->mbShow && postItField->mpPostIt && postItField->mpPostIt->HasScrollbar()) + return true; + } + return false; +} + +void SwPostItMgr::PreparePageContainer() +{ + // we do not just delete the SwPostItPageItem, so offset/scrollbar is not lost + tools::Long lPageSize = mpWrtShell->GetNumPages(); + tools::Long lContainerSize = mPages.size(); + + if (lContainerSize < lPageSize) + { + mPages.reserve(lPageSize); + for (tools::Long i=0; i<lPageSize - lContainerSize;i++) + mPages.emplace_back( new SwPostItPageItem()); + } + else if (lContainerSize > lPageSize) + { + for (int i=mPages.size()-1; i >= lPageSize;--i) + { + mPages.pop_back(); + } + } + // only clear the list, DO NOT delete the objects itself + for (auto const& page : mPages) + { + page->mvSidebarItems.clear(); + if (mvPostItFields.empty()) + page->bScrollbar = false; + } +} + +void SwPostItMgr::LayoutPostIts() +{ + bool bEnableMapMode = comphelper::LibreOfficeKit::isActive() && !mpEditWin->IsMapModeEnabled(); + if (bEnableMapMode) + mpEditWin->EnableMapMode(); + + if ( !mvPostItFields.empty() && !mbWaitingForCalcRects ) + { + mbLayouting = true; + + //loop over all pages and do the layout + // - create SwPostIt if necessary + // - place SwPostIts on their initial position + // - calculate necessary height for all PostIts together + bool bUpdate = false; + for (std::unique_ptr<SwPostItPageItem>& pPage : mPages) + { + // only layout if there are notes on this page + if (!pPage->mvSidebarItems.empty()) + { + std::vector<SwAnnotationWin*> aVisiblePostItList; + tools::ULong lNeededHeight = 0; + tools::Long mlPageBorder = 0; + tools::Long mlPageEnd = 0; + + for (auto const& pItem : pPage->mvSidebarItems) + { + VclPtr<SwAnnotationWin> pPostIt = pItem->mpPostIt; + + if (pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT ) + { + // x value for notes positioning + mlPageBorder = mpEditWin->LogicToPixel( Point( pPage->mPageRect.Left(), 0)).X() - GetSidebarWidth(true);// - GetSidebarBorderWidth(true); + //bending point + mlPageEnd = + mpWrtShell->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) + ? pItem->maLayoutInfo.mPagePrtArea.Left() + : pPage->mPageRect.Left() + 350; + } + else if (pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT ) + { + // x value for notes positioning + mlPageBorder = mpEditWin->LogicToPixel( Point(pPage->mPageRect.Right(), 0)).X() + GetSidebarBorderWidth(true); + //bending point + mlPageEnd = + mpWrtShell->getIDocumentSettingAccess().get(DocumentSettingId::BROWSE_MODE) + ? pItem->maLayoutInfo.mPagePrtArea.Right() : + pPage->mPageRect.Right() - 350; + } + + if (pItem->mbShow) + { + tools::Long Y = mpEditWin->LogicToPixel( Point(0,pItem->maLayoutInfo.mPosition.Bottom())).Y(); + tools::Long aPostItHeight = 0; + if (!pPostIt) + { + pPostIt = pItem->GetSidebarWindow( mpView->GetEditWin(), + *this ); + pPostIt->InitControls(); + pPostIt->SetReadonly(mbReadOnly); + pItem->mpPostIt = pPostIt; + if (mpAnswer) + { + if (static_cast<bool>(pPostIt->CalcParent())) //do we really have another note in front of this one + pPostIt->InitAnswer(*mpAnswer); + mpAnswer.reset(); + } + } + + pPostIt->SetChangeTracking( + pItem->mLayoutStatus, + GetColorAnchor(pItem->maLayoutInfo.mRedlineAuthor)); + pPostIt->SetSidebarPosition(pPage->eSidebarPosition); + pPostIt->SetFollow(static_cast<bool>(pPostIt->CalcParent())); + aPostItHeight = ( pPostIt->GetPostItTextHeight() < pPostIt->GetMinimumSizeWithoutMeta() + ? pPostIt->GetMinimumSizeWithoutMeta() + : pPostIt->GetPostItTextHeight() ) + + pPostIt->GetMetaHeight(); + pPostIt->SetPosSizePixelRect( mlPageBorder , + Y - GetInitialAnchorDistance(), + GetSidebarWidth(true), + aPostItHeight, + pItem->maLayoutInfo.mPosition, + mlPageEnd ); + pPostIt->ChangeSidebarItem( *pItem ); + + if (pItem->mbFocus) + { + mbLayout = true; + pPostIt->GrabFocus(); + pItem->mbFocus = false; + } + // only the visible postits are used for the final layout + aVisiblePostItList.push_back(pPostIt); + lNeededHeight += pPostIt->IsFollow() ? aPostItHeight : aPostItHeight+GetSpaceBetween(); + } + else // we don't want to see it + { + if (pPostIt) + pPostIt->HideNote(); + } + } + + if ((!aVisiblePostItList.empty()) && ShowNotes()) + { + bool bOldScrollbar = pPage->bScrollbar; + if (ShowNotes()) + pPage->bScrollbar = LayoutByPage(aVisiblePostItList, pPage->mPageRect.SVRect(), lNeededHeight); + else + pPage->bScrollbar = false; + if (!pPage->bScrollbar) + { + pPage->lOffset = 0; + } + else if (sal_Int32 nScrollSize = GetScrollSize()) + { + //when we changed our zoom level, the offset value can be too big, so lets check for the largest possible zoom value + tools::Long aAvailableHeight = mpEditWin->LogicToPixel(Size(0,pPage->mPageRect.Height())).Height() - 2 * GetSidebarScrollerHeight(); + tools::Long lOffset = -1 * nScrollSize * (aVisiblePostItList.size() - aAvailableHeight / nScrollSize); + if (pPage->lOffset < lOffset) + pPage->lOffset = lOffset; + } + bUpdate = (bOldScrollbar != pPage->bScrollbar) || bUpdate; + const tools::Long aSidebarheight = pPage->bScrollbar ? mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height() : 0; + /* + TODO + - enlarge all notes till GetNextBorder(), as we resized to average value before + */ + //lets hide the ones which overlap the page + for (auto const& visiblePostIt : aVisiblePostItList) + { + if (pPage->lOffset != 0) + visiblePostIt->TranslateTopPosition(pPage->lOffset); + + bool bBottom = mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y()+visiblePostIt->VirtualSize().Height())).Y() <= (pPage->mPageRect.Bottom()-aSidebarheight); + bool bTop = mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y())).Y() >= (pPage->mPageRect.Top()+aSidebarheight); + if ( bBottom && bTop ) + { + // When tiled rendering, make sure that only the + // view that has the comment focus emits callbacks, + // so the editing view jumps to the comment, but + // not the others. + bool bTiledPainting = comphelper::LibreOfficeKit::isTiledPainting(); + if (!bTiledPainting) + // No focus -> disable callbacks. + comphelper::LibreOfficeKit::setTiledPainting(!visiblePostIt->HasChildPathFocus()); + visiblePostIt->ShowNote(); + if (!bTiledPainting) + comphelper::LibreOfficeKit::setTiledPainting(bTiledPainting); + } + else + { + if (mpEditWin->PixelToLogic(Point(0,visiblePostIt->VirtualPos().Y())).Y() < (pPage->mPageRect.Top()+aSidebarheight)) + { + if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT ) + visiblePostIt->ShowAnchorOnly(Point( pPage->mPageRect.Left(), + pPage->mPageRect.Top())); + else if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT ) + visiblePostIt->ShowAnchorOnly(Point( pPage->mPageRect.Right(), + pPage->mPageRect.Top())); + } + else + { + if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT ) + visiblePostIt->ShowAnchorOnly(Point(pPage->mPageRect.Left(), + pPage->mPageRect.Bottom())); + else if ( pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT ) + visiblePostIt->ShowAnchorOnly(Point(pPage->mPageRect.Right(), + pPage->mPageRect.Bottom())); + } + OSL_ENSURE(pPage->bScrollbar,"SwPostItMgr::LayoutByPage(): note overlaps, but bScrollbar is not true"); + } + } + } + else + { + for (auto const& visiblePostIt : aVisiblePostItList) + { + visiblePostIt->SetPosAndSize(); + } + + bool bOldScrollbar = pPage->bScrollbar; + pPage->bScrollbar = false; + bUpdate = (bOldScrollbar != pPage->bScrollbar) || bUpdate; + } + + for (auto const& visiblePostIt : aVisiblePostItList) + { + if (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) + { + if (visiblePostIt->GetSidebarItem().mbPendingLayout) + lcl_CommentNotification(mpView, CommentNotificationType::Add, &visiblePostIt->GetSidebarItem(), 0); + else if (visiblePostIt->IsAnchorRectChanged()) + { + lcl_CommentNotification(mpView, CommentNotificationType::Modify, &visiblePostIt->GetSidebarItem(), 0); + visiblePostIt->ResetAnchorRectChanged(); + } + } + + // Layout for this post it finished now + visiblePostIt->GetSidebarItem().mbPendingLayout = false; + } + } + else + { + if (pPage->bScrollbar) + bUpdate = true; + pPage->bScrollbar = false; + } + } + + if (!ShowNotes()) + { // we do not want to see the notes anymore -> Options-Writer-View-Notes + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + bool bRepair = false; + for (auto const& postItField : mvPostItFields) + { + if (!postItField->UseElement(*mpWrtShell->GetLayout(), rIDRA)) + { + OSL_FAIL("PostIt is not in doc!"); + bRepair = true; + continue; + } + + if (postItField->mpPostIt) + { + postItField->mpPostIt->HideNote(); + if (postItField->mpPostIt->HasChildPathFocus()) + { + SetActiveSidebarWin(nullptr); + postItField->mpPostIt->GrabFocusToDocument(); + } + } + } + + if ( bRepair ) + CheckForRemovedPostIts(); + } + + // notes scrollbar is otherwise not drawn correctly for some cases + // scrollbar area is enough + if (bUpdate) + mpEditWin->Invalidate(); /*This is a super expensive relayout and render of the entire page*/ + + mbLayouting = false; + } + + if (bEnableMapMode) + mpEditWin->EnableMapMode(false); +} + +bool SwPostItMgr::BorderOverPageBorder(tools::ULong aPage) const +{ + if ( mPages[aPage-1]->mvSidebarItems.empty() ) + { + OSL_FAIL("Notes SidePane painted but no rects and page lists calculated!"); + return false; + } + + auto aItem = mPages[aPage-1]->mvSidebarItems.end(); + --aItem; + OSL_ENSURE ((*aItem)->mpPostIt,"BorderOverPageBorder: NULL postIt, should never happen"); + if ((*aItem)->mpPostIt) + { + const tools::Long aSidebarheight = mPages[aPage-1]->bScrollbar ? mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height() : 0; + const tools::Long aEndValue = mpEditWin->PixelToLogic(Point(0,(*aItem)->mpPostIt->GetPosPixel().Y()+(*aItem)->mpPostIt->GetSizePixel().Height())).Y(); + return aEndValue <= mPages[aPage-1]->mPageRect.Bottom()-aSidebarheight; + } + else + return false; +} + +void SwPostItMgr::DrawNotesForPage(OutputDevice *pOutDev, sal_uInt32 nPage) +{ + assert(nPage < mPages.size()); + if (nPage >= mPages.size()) + return; + for (auto const& pItem : mPages[nPage]->mvSidebarItems) + { + SwAnnotationWin* pPostIt = pItem->mpPostIt; + if (!pPostIt) + continue; + Point aPoint(mpEditWin->PixelToLogic(pPostIt->GetPosPixel())); + pPostIt->DrawForPage(pOutDev, aPoint); + } +} + +void SwPostItMgr::PaintTile(OutputDevice& rRenderContext) +{ + for (const std::unique_ptr<SwSidebarItem>& pItem : mvPostItFields) + { + SwAnnotationWin* pPostIt = pItem->mpPostIt; + if (!pPostIt) + continue; + + bool bEnableMapMode = !mpEditWin->IsMapModeEnabled(); + mpEditWin->EnableMapMode(); + rRenderContext.Push(vcl::PushFlags::MAPMODE); + Point aOffset(mpEditWin->PixelToLogic(pPostIt->GetPosPixel())); + MapMode aMapMode(rRenderContext.GetMapMode()); + aMapMode.SetOrigin(aMapMode.GetOrigin() + aOffset); + rRenderContext.SetMapMode(aMapMode); + Size aSize(rRenderContext.PixelToLogic(pPostIt->GetSizePixel())); + tools::Rectangle aRectangle(Point(0, 0), aSize); + + pPostIt->PaintTile(rRenderContext, aRectangle); + + rRenderContext.Pop(); + if (bEnableMapMode) + mpEditWin->EnableMapMode(false); + } +} + +void SwPostItMgr::Scroll(const tools::Long lScroll,const tools::ULong aPage) +{ + OSL_ENSURE((lScroll % GetScrollSize() )==0,"SwPostItMgr::Scroll: scrolling by wrong value"); + // do not scroll more than necessary up or down + if ( ((mPages[aPage-1]->lOffset == 0) && (lScroll>0)) || ( BorderOverPageBorder(aPage) && (lScroll<0)) ) + return; + + const bool bOldUp = ArrowEnabled(KEY_PAGEUP,aPage); + const bool bOldDown = ArrowEnabled(KEY_PAGEDOWN,aPage); + const tools::Long aSidebarheight = mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height(); + for (auto const& item : mPages[aPage-1]->mvSidebarItems) + { + SwAnnotationWin* pPostIt = item->mpPostIt; + // if this is an answer, we should take the normal position and not the real, slightly moved position + pPostIt->SetVirtualPosSize(pPostIt->GetPosPixel(),pPostIt->GetSizePixel()); + pPostIt->TranslateTopPosition(lScroll); + + if (item->mbShow) + { + bool bBottom = mpEditWin->PixelToLogic(Point(0,pPostIt->VirtualPos().Y()+pPostIt->VirtualSize().Height())).Y() <= (mPages[aPage-1]->mPageRect.Bottom()-aSidebarheight); + bool bTop = mpEditWin->PixelToLogic(Point(0,pPostIt->VirtualPos().Y())).Y() >= (mPages[aPage-1]->mPageRect.Top()+aSidebarheight); + if ( bBottom && bTop) + { + pPostIt->ShowNote(); + } + else + { + if ( mpEditWin->PixelToLogic(Point(0,pPostIt->VirtualPos().Y())).Y() < (mPages[aPage-1]->mPageRect.Top()+aSidebarheight)) + { + if (mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT) + pPostIt->ShowAnchorOnly(Point(mPages[aPage-1]->mPageRect.Left(),mPages[aPage-1]->mPageRect.Top())); + else if (mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT) + pPostIt->ShowAnchorOnly(Point(mPages[aPage-1]->mPageRect.Right(),mPages[aPage-1]->mPageRect.Top())); + } + else + { + if (mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT) + pPostIt->ShowAnchorOnly(Point(mPages[aPage-1]->mPageRect.Left(),mPages[aPage-1]->mPageRect.Bottom())); + else if (mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::RIGHT) + pPostIt->ShowAnchorOnly(Point(mPages[aPage-1]->mPageRect.Right(),mPages[aPage-1]->mPageRect.Bottom())); + } + } + } + } + mPages[aPage-1]->lOffset += lScroll; + if ( (bOldUp != ArrowEnabled(KEY_PAGEUP,aPage)) ||(bOldDown != ArrowEnabled(KEY_PAGEDOWN,aPage)) ) + { + mpEditWin->Invalidate(GetBottomScrollRect(aPage)); + mpEditWin->Invalidate(GetTopScrollRect(aPage)); + } +} + +void SwPostItMgr::AutoScroll(const SwAnnotationWin* pPostIt,const tools::ULong aPage ) +{ + // otherwise all notes are visible + if (!mPages[aPage-1]->bScrollbar) + return; + + const tools::Long aSidebarheight = mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height(); + const bool bBottom = mpEditWin->PixelToLogic(Point(0,pPostIt->GetPosPixel().Y()+pPostIt->GetSizePixel().Height())).Y() <= (mPages[aPage-1]->mPageRect.Bottom()-aSidebarheight); + const bool bTop = mpEditWin->PixelToLogic(Point(0,pPostIt->GetPosPixel().Y())).Y() >= (mPages[aPage-1]->mPageRect.Top()+aSidebarheight); + if ( !(bBottom && bTop)) + { + const tools::Long aDiff = bBottom ? mpEditWin->LogicToPixel(Point(0,mPages[aPage-1]->mPageRect.Top() + aSidebarheight)).Y() - pPostIt->GetPosPixel().Y() : + mpEditWin->LogicToPixel(Point(0,mPages[aPage-1]->mPageRect.Bottom() - aSidebarheight)).Y() - (pPostIt->GetPosPixel().Y()+pPostIt->GetSizePixel().Height()); + // this just adds the missing value to get the next a* GetScrollSize() after aDiff + // e.g aDiff= 61 POSTIT_SCROLL=50 --> lScroll = 100 + const auto nScrollSize = GetScrollSize(); + assert(nScrollSize); + const tools::Long lScroll = bBottom ? (aDiff + ( nScrollSize - (aDiff % nScrollSize))) : (aDiff - (nScrollSize + (aDiff % nScrollSize))); + Scroll(lScroll, aPage); + } +} + +void SwPostItMgr::MakeVisible(const SwAnnotationWin* pPostIt ) +{ + tools::Long aPage = -1; + // we don't know the page yet, lets find it ourselves + std::vector<SwPostItPageItem*>::size_type n=0; + for (auto const& page : mPages) + { + for (auto const& item : page->mvSidebarItems) + { + if (item->mpPostIt==pPostIt) + { + aPage = n+1; + break; + } + } + ++n; + } + if (aPage!=-1) + AutoScroll(pPostIt,aPage); + tools::Rectangle aNoteRect (Point(pPostIt->GetPosPixel().X(),pPostIt->GetPosPixel().Y()-5),pPostIt->GetSizePixel()); + if (!aNoteRect.IsEmpty()) + mpWrtShell->MakeVisible(SwRect(mpEditWin->PixelToLogic(aNoteRect))); +} + +bool SwPostItMgr::ArrowEnabled(sal_uInt16 aDirection,tools::ULong aPage) const +{ + switch (aDirection) + { + case KEY_PAGEUP: + { + return (mPages[aPage-1]->lOffset != 0); + } + case KEY_PAGEDOWN: + { + return (!BorderOverPageBorder(aPage)); + } + default: return false; + } +} + +Color SwPostItMgr::GetArrowColor(sal_uInt16 aDirection,tools::ULong aPage) const +{ + if (ArrowEnabled(aDirection,aPage)) + { + if (Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + return COL_WHITE; + else + return COL_NOTES_SIDEPANE_ARROW_ENABLED; + } + else + { + return COL_NOTES_SIDEPANE_ARROW_DISABLED; + } +} + +bool SwPostItMgr::LayoutByPage(std::vector<SwAnnotationWin*> &aVisiblePostItList, const tools::Rectangle& rBorder, tools::Long lNeededHeight) +{ + /*** General layout idea:***/ + // - if we have space left, we always move the current one up, + // otherwise the next one down + // - first all notes are resized + // - then the real layout starts + + //rBorder is the page rect + const tools::Rectangle aBorder = mpEditWin->LogicToPixel(rBorder); + tools::Long lTopBorder = aBorder.Top() + 5; + tools::Long lBottomBorder = aBorder.Bottom() - 5; + const tools::Long lVisibleHeight = lBottomBorder - lTopBorder; //aBorder.GetHeight() ; + const size_t nPostItListSize = aVisiblePostItList.size(); + tools::Long lTranslatePos = 0; + bool bScrollbars = false; + + // do all necessary resizings + if (nPostItListSize > 0 && lVisibleHeight < lNeededHeight) + { + // ok, now we have to really resize and adding scrollbars + const tools::Long lAverageHeight = (lVisibleHeight - nPostItListSize*GetSpaceBetween()) / nPostItListSize; + if (lAverageHeight<GetMinimumSizeWithMeta()) + { + bScrollbars = true; + lTopBorder += GetSidebarScrollerHeight() + 10; + lBottomBorder -= (GetSidebarScrollerHeight() + 10); + for (auto const& visiblePostIt : aVisiblePostItList) + visiblePostIt->SetSize(Size(visiblePostIt->VirtualSize().getWidth(),visiblePostIt->GetMinimumSizeWithMeta())); + } + else + { + for (auto const& visiblePostIt : aVisiblePostItList) + { + if ( visiblePostIt->VirtualSize().getHeight() > lAverageHeight) + visiblePostIt->SetSize(Size(visiblePostIt->VirtualSize().getWidth(),lAverageHeight)); + } + } + } + + //start the real layout so nothing overlaps anymore + if (aVisiblePostItList.size()>1) + { + int loop = 0; + bool bDone = false; + // if no window is moved anymore we are finished + while (!bDone) + { + loop++; + bDone = true; + tools::Long lSpaceUsed = lTopBorder + GetSpaceBetween(); + for(auto i = aVisiblePostItList.begin(); i != aVisiblePostItList.end() ; ++i) + { + auto aNextPostIt = i; + ++aNextPostIt; + + if (aNextPostIt != aVisiblePostItList.end()) + { + lTranslatePos = ( (*i)->VirtualPos().Y() + (*i)->VirtualSize().Height()) - (*aNextPostIt)->VirtualPos().Y(); + if (lTranslatePos > 0) // note windows overlaps the next one + { + // we are not done yet, loop at least once more + bDone = false; + // if there is space left, move the current note up + // it could also happen that there is no space left for the first note due to a scrollbar + // then we also jump into, so we move the current one up and the next one down + if ( (lSpaceUsed <= (*i)->VirtualPos().Y()) || (i==aVisiblePostItList.begin())) + { + // we have space left, so let's move the current one up + if ( ((*i)->VirtualPos().Y()- lTranslatePos - GetSpaceBetween()) > lTopBorder) + { + if ((*aNextPostIt)->IsFollow()) + (*i)->TranslateTopPosition(-1*(lTranslatePos+ANCHORLINE_WIDTH)); + else + (*i)->TranslateTopPosition(-1*(lTranslatePos+GetSpaceBetween())); + } + else + { + tools::Long lMoveUp = (*i)->VirtualPos().Y() - lTopBorder; + (*i)->TranslateTopPosition(-1* lMoveUp); + if ((*aNextPostIt)->IsFollow()) + (*aNextPostIt)->TranslateTopPosition( (lTranslatePos+ANCHORLINE_WIDTH) - lMoveUp); + else + (*aNextPostIt)->TranslateTopPosition( (lTranslatePos+GetSpaceBetween()) - lMoveUp); + } + } + else + { + // no space left, left move the next one down + if ((*aNextPostIt)->IsFollow()) + (*aNextPostIt)->TranslateTopPosition(lTranslatePos+ANCHORLINE_WIDTH); + else + (*aNextPostIt)->TranslateTopPosition(lTranslatePos+GetSpaceBetween()); + } + } + else + { + // the first one could overlap the topborder instead of a second note + if (i==aVisiblePostItList.begin()) + { + tools::Long lMoveDown = lTopBorder - (*i)->VirtualPos().Y(); + if (lMoveDown>0) + { + bDone = false; + (*i)->TranslateTopPosition( lMoveDown); + } + } + } + if ( (*aNextPostIt)->IsFollow() ) + lSpaceUsed += (*i)->VirtualSize().Height() + ANCHORLINE_WIDTH; + else + lSpaceUsed += (*i)->VirtualSize().Height() + GetSpaceBetween(); + } + else + { + //(*i) is the last visible item + auto aPrevPostIt = i; + --aPrevPostIt; + lTranslatePos = ( (*aPrevPostIt)->VirtualPos().Y() + (*aPrevPostIt)->VirtualSize().Height() ) - (*i)->VirtualPos().Y(); + if (lTranslatePos > 0) + { + bDone = false; + if ( ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()+lTranslatePos) < lBottomBorder) + { + if ( (*i)->IsFollow() ) + (*i)->TranslateTopPosition(lTranslatePos+ANCHORLINE_WIDTH); + else + (*i)->TranslateTopPosition(lTranslatePos+GetSpaceBetween()); + } + else + { + (*i)->TranslateTopPosition(lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()) ); + } + } + else + { + // note does not overlap, but we might be over the lower border + // only do this if there are no scrollbars, otherwise notes are supposed to overlap the border + if (!bScrollbars && ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height() > lBottomBorder) ) + { + bDone = false; + (*i)->TranslateTopPosition(lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height())); + } + } + } + } + // security check so we don't loop forever + if (loop>MAX_LOOP_COUNT) + { + OSL_FAIL("PostItMgr::Layout(): We are looping forever"); + break; + } + } + } + else + { + // only one left, make sure it is not hidden at the top or bottom + auto i = aVisiblePostItList.begin(); + lTranslatePos = lTopBorder - (*i)->VirtualPos().Y(); + if (lTranslatePos>0) + { + (*i)->TranslateTopPosition(lTranslatePos+GetSpaceBetween()); + } + lTranslatePos = lBottomBorder - ((*i)->VirtualPos().Y()+ (*i)->VirtualSize().Height()); + if (lTranslatePos<0) + { + (*i)->TranslateTopPosition(lTranslatePos); + } + } + return bScrollbars; + } + +void SwPostItMgr::AddPostIts(const bool bCheckExistence, const bool bFocus) +{ + const bool bEmpty = mvPostItFields.empty(); + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + SwFieldType* pType = mpView->GetDocShell()->GetDoc()->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Postit, OUString(),false); + std::vector<SwFormatField*> vFormatFields; + pType->CollectPostIts(vFormatFields, rIDRA, mpWrtShell->GetLayout()->IsHideRedlines()); + for(auto pFormatField : vFormatFields) + InsertItem(pFormatField, bCheckExistence, bFocus); + // if we just added the first one we have to update the view for centering + if (bEmpty && !mvPostItFields.empty()) + PrepareView(true); +} + +void SwPostItMgr::RemoveSidebarWin() +{ + for (auto& postItField : mvPostItFields) + { + EndListening( *const_cast<SfxBroadcaster*>(postItField->GetBroadcaster()) ); + postItField->mpPostIt.disposeAndClear(); + postItField.reset(); + } + mvPostItFields.clear(); + + // all postits removed, no items should be left in pages + PreparePageContainer(); +} + +namespace { + +class FilterFunctor +{ +public: + virtual bool operator()(const SwFormatField* pField) const = 0; + virtual ~FilterFunctor() {} +}; + +class IsPostitField : public FilterFunctor +{ +public: + bool operator()(const SwFormatField* pField) const override + { + return pField->GetField()->GetTyp()->Which() == SwFieldIds::Postit; + } +}; + +class IsPostitFieldWithAuthorOf : public FilterFunctor +{ + OUString m_sAuthor; +public: + explicit IsPostitFieldWithAuthorOf(const OUString &rAuthor) + : m_sAuthor(rAuthor) + { + } + bool operator()(const SwFormatField* pField) const override + { + if (pField->GetField()->GetTyp()->Which() != SwFieldIds::Postit) + return false; + return static_cast<const SwPostItField*>(pField->GetField())->GetPar1() == m_sAuthor; + } +}; + +class IsPostitFieldWithPostitId : public FilterFunctor +{ + sal_uInt32 m_nPostItId; +public: + explicit IsPostitFieldWithPostitId(sal_uInt32 nPostItId) + : m_nPostItId(nPostItId) + {} + + bool operator()(const SwFormatField* pField) const override + { + if (pField->GetField()->GetTyp()->Which() != SwFieldIds::Postit) + return false; + return static_cast<const SwPostItField*>(pField->GetField())->GetPostItId() == m_nPostItId; + } +}; + +class IsFieldNotDeleted : public FilterFunctor +{ +private: + IDocumentRedlineAccess const& m_rIDRA; + FilterFunctor const& m_rNext; + +public: + IsFieldNotDeleted(IDocumentRedlineAccess const& rIDRA, + const FilterFunctor & rNext) + : m_rIDRA(rIDRA) + , m_rNext(rNext) + { + } + bool operator()(const SwFormatField* pField) const override + { + if (!m_rNext(pField)) + return false; + if (!pField->GetTextField()) + return false; + return !sw::IsFieldDeletedInModel(m_rIDRA, *pField->GetTextField()); + } +}; + +//Manages the passed in vector by automatically removing entries if they are deleted +//and automatically adding entries if they appear in the document and match the +//functor. +// +//This will completely refill in the case of a "anonymous" NULL pField stating +//rather unhelpfully that "something changed" so you may process the same +//Fields more than once. +class FieldDocWatchingStack : public SfxListener +{ + std::vector<std::unique_ptr<SwSidebarItem>>& m_aSidebarItems; + std::vector<const SwFormatField*> m_aFormatFields; + SwDocShell& m_rDocShell; + FilterFunctor& m_rFilter; + + virtual void Notify(SfxBroadcaster&, const SfxHint& rHint) override + { + const SwFormatFieldHint* pHint = dynamic_cast<const SwFormatFieldHint*>(&rHint); + if (!pHint) + return; + + bool bAllInvalidated = false; + if (pHint->Which() == SwFormatFieldHintWhich::REMOVED) + { + const SwFormatField* pField = pHint->GetField(); + bAllInvalidated = pField == nullptr; + if (!bAllInvalidated && m_rFilter(pField)) + { + EndListening(const_cast<SwFormatField&>(*pField)); + m_aFormatFields.erase(std::remove(m_aFormatFields.begin(), m_aFormatFields.end(), pField), m_aFormatFields.end()); + } + } + else if (pHint->Which() == SwFormatFieldHintWhich::INSERTED) + { + const SwFormatField* pField = pHint->GetField(); + bAllInvalidated = pField == nullptr; + if (!bAllInvalidated && m_rFilter(pField)) + { + StartListening(const_cast<SwFormatField&>(*pField)); + m_aFormatFields.push_back(pField); + } + } + + if (bAllInvalidated) + FillVector(); + + return; + } + +public: + FieldDocWatchingStack(std::vector<std::unique_ptr<SwSidebarItem>>& in, SwDocShell &rDocShell, FilterFunctor& rFilter) + : m_aSidebarItems(in) + , m_rDocShell(rDocShell) + , m_rFilter(rFilter) + { + FillVector(); + StartListening(m_rDocShell); + } + void FillVector() + { + EndListeningToAllFields(); + m_aFormatFields.clear(); + m_aFormatFields.reserve(m_aSidebarItems.size()); + for (auto const& p : m_aSidebarItems) + { + const SwFormatField& rField = p->GetFormatField(); + if (!m_rFilter(&rField)) + continue; + StartListening(const_cast<SwFormatField&>(rField)); + m_aFormatFields.push_back(&rField); + } + } + void EndListeningToAllFields() + { + for (auto const& pField : m_aFormatFields) + { + EndListening(const_cast<SwFormatField&>(*pField)); + } + } + virtual ~FieldDocWatchingStack() override + { + EndListeningToAllFields(); + EndListening(m_rDocShell); + } + const SwFormatField* pop() + { + if (m_aFormatFields.empty()) + return nullptr; + const SwFormatField* p = m_aFormatFields.back(); + EndListening(const_cast<SwFormatField&>(*p)); + m_aFormatFields.pop_back(); + return p; + } +}; + +} + +// copy to new vector, otherwise RemoveItem would operate and delete stuff on mvPostItFields as well +// RemoveItem will clean up the core field and visible postit if necessary +// we cannot just delete everything as before, as postits could move into change tracking +void SwPostItMgr::Delete(const OUString& rAuthor) +{ + mpWrtShell->StartAllAction(); + if (HasActiveSidebarWin() && (GetActiveSidebarWin()->GetAuthor() == rAuthor)) + { + SetActiveSidebarWin(nullptr); + } + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_DELETE_AUTHOR_NOTES) + rAuthor); + mpWrtShell->StartUndo( SwUndoId::DELETE, &aRewriter ); + + IsPostitFieldWithAuthorOf aFilter(rAuthor); + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + IsFieldNotDeleted aFilter2(rIDRA, aFilter); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), aFilter2); + while (const SwFormatField* pField = aStack.pop()) + { + if (mpWrtShell->GotoField(*pField)) + mpWrtShell->DelRight(); + } + mpWrtShell->EndUndo(); + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::Delete(sal_uInt32 nPostItId) +{ + mpWrtShell->StartAllAction(); + if (HasActiveSidebarWin() && + mpActivePostIt->GetPostItField()->GetPostItId() == nPostItId) + { + SetActiveSidebarWin(nullptr); + } + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_CONTENT_TYPE_SINGLE_POSTIT)); + mpWrtShell->StartUndo( SwUndoId::DELETE, &aRewriter ); + + IsPostitFieldWithPostitId aFilter(nPostItId); + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + IsFieldNotDeleted aFilter2(rIDRA, aFilter); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), aFilter2); + const SwFormatField* pField = aStack.pop(); + if (pField && mpWrtShell->GotoField(*pField)) + mpWrtShell->DelRight(); + mpWrtShell->EndUndo(); + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::DeleteCommentThread(sal_uInt32 nPostItId) +{ + mpWrtShell->StartAllAction(); + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_CONTENT_TYPE_SINGLE_POSTIT)); + + // We have no undo ID at the moment. + + IsPostitFieldWithPostitId aFilter(nPostItId); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), aFilter); + const SwFormatField* pField = aStack.pop(); + // pField now contains our AnnotationWin object + if (pField) { + SwAnnotationWin* pWin = GetSidebarWin(pField); + pWin->DeleteThread(); + } + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::ToggleResolved(sal_uInt32 nPostItId) +{ + mpWrtShell->StartAllAction(); + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_CONTENT_TYPE_SINGLE_POSTIT)); + + // We have no undo ID at the moment. + + IsPostitFieldWithPostitId aFilter(nPostItId); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), aFilter); + const SwFormatField* pField = aStack.pop(); + // pField now contains our AnnotationWin object + if (pField) { + SwAnnotationWin* pWin = GetSidebarWin(pField); + pWin->ToggleResolved(); + } + + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::ToggleResolvedForThread(sal_uInt32 nPostItId) +{ + mpWrtShell->StartAllAction(); + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_CONTENT_TYPE_SINGLE_POSTIT)); + + // We have no undo ID at the moment. + + IsPostitFieldWithPostitId aFilter(nPostItId); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), aFilter); + const SwFormatField* pField = aStack.pop(); + // pField now contains our AnnotationWin object + if (pField) { + SwAnnotationWin* pWin = GetSidebarWin(pField); + pWin->ToggleResolvedForThread(); + } + + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + + +void SwPostItMgr::Delete() +{ + mpWrtShell->StartAllAction(); + SetActiveSidebarWin(nullptr); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_DELETE_ALL_NOTES) ); + mpWrtShell->StartUndo( SwUndoId::DELETE, &aRewriter ); + + IsPostitField aFilter; + IDocumentRedlineAccess const& rIDRA(mpWrtShell->getIDocumentRedlineAccess()); + IsFieldNotDeleted aFilter2(rIDRA, aFilter); + FieldDocWatchingStack aStack(mvPostItFields, *mpView->GetDocShell(), + aFilter2); + while (const SwFormatField* pField = aStack.pop()) + { + if (mpWrtShell->GotoField(*pField)) + mpWrtShell->DelRight(); + } + + mpWrtShell->EndUndo(); + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::ExecuteFormatAllDialog(SwView& rView) +{ + if (mvPostItFields.empty()) + return; + sw::annotation::SwAnnotationWin *pOrigActiveWin = GetActiveSidebarWin(); + sw::annotation::SwAnnotationWin *pWin = pOrigActiveWin; + if (!pWin) + { + for (auto const& postItField : mvPostItFields) + { + pWin = postItField->mpPostIt; + if (pWin) + break; + } + } + if (!pWin) + return; + SetActiveSidebarWin(pWin); + OutlinerView* pOLV = pWin->GetOutlinerView(); + SfxItemSet aEditAttr(pOLV->GetAttribs()); + SfxItemPool* pPool(SwAnnotationShell::GetAnnotationPool(rView)); + SfxItemSetFixed<XATTR_FILLSTYLE, XATTR_FILLCOLOR, EE_ITEMS_START, EE_ITEMS_END> aDlgAttr(*pPool); + aDlgAttr.Put(aEditAttr); + SwAbstractDialogFactory* pFact = SwAbstractDialogFactory::Create(); + ScopedVclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateSwCharDlg(rView.GetFrameWeld(), rView, aDlgAttr, SwCharDlgMode::Ann)); + sal_uInt16 nRet = pDlg->Execute(); + if (RET_OK == nRet) + { + aDlgAttr.Put(*pDlg->GetOutputItemSet()); + FormatAll(aDlgAttr); + } + pDlg.disposeAndClear(); + SetActiveSidebarWin(pOrigActiveWin); +} + +void SwPostItMgr::FormatAll(const SfxItemSet &rNewAttr) +{ + mpWrtShell->StartAllAction(); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_FORMAT_ALL_NOTES) ); + mpWrtShell->StartUndo( SwUndoId::INSATTR, &aRewriter ); + + for (auto const& postItField : mvPostItFields) + { + if (!postItField->mpPostIt) + continue; + OutlinerView* pOLV = postItField->mpPostIt->GetOutlinerView(); + //save old selection + ESelection aOrigSel(pOLV->GetSelection()); + //select all + Outliner *pOutliner = pOLV->GetOutliner(); + if (pOutliner) + { + sal_Int32 nParaCount = pOutliner->GetParagraphCount(); + if (nParaCount > 0) + pOLV->SelectRange(0, nParaCount); + } + //set new char properties + pOLV->SetAttribs(rNewAttr); + //restore old selection + pOLV->SetSelection(aOrigSel); + // tdf#91596 store updated formatting in SwField + postItField->mpPostIt->UpdateData(); + } + + mpWrtShell->EndUndo(); + PrepareView(); + mpWrtShell->EndAllAction(); + mbLayout = true; + CalcRects(); + LayoutPostIts(); +} + +void SwPostItMgr::Hide( std::u16string_view rAuthor ) +{ + for (auto const& postItField : mvPostItFields) + { + if ( postItField->mpPostIt && (postItField->mpPostIt->GetAuthor() == rAuthor) ) + { + postItField->mbShow = false; + postItField->mpPostIt->HideNote(); + } + } + + LayoutPostIts(); +} + +void SwPostItMgr::Hide() +{ + for (auto const& postItField : mvPostItFields) + { + postItField->mbShow = false; + if (postItField->mpPostIt) + postItField->mpPostIt->HideNote(); + } +} + +void SwPostItMgr::Show() +{ + for (auto const& postItField : mvPostItFields) + { + postItField->mbShow = true; + } + LayoutPostIts(); +} + +SwAnnotationWin* SwPostItMgr::GetSidebarWin( const SfxBroadcaster* pBroadcaster) const +{ + for (auto const& postItField : mvPostItFields) + { + if ( postItField->GetBroadcaster() == pBroadcaster) + return postItField->mpPostIt; + } + return nullptr; +} + +sw::annotation::SwAnnotationWin* SwPostItMgr::GetAnnotationWin(const SwPostItField* pField) const +{ + for (auto const& postItField : mvPostItFields) + { + if ( postItField->GetFormatField().GetField() == pField ) + return postItField->mpPostIt.get(); + } + return nullptr; +} + +sw::annotation::SwAnnotationWin* SwPostItMgr::GetAnnotationWin(const sal_uInt32 nPostItId) const +{ + for (auto const& postItField : mvPostItFields) + { + if ( static_cast<const SwPostItField*>(postItField->GetFormatField().GetField())->GetPostItId() == nPostItId ) + return postItField->mpPostIt.get(); + } + return nullptr; +} + +SwAnnotationWin* SwPostItMgr::GetNextPostIt( sal_uInt16 aDirection, + SwAnnotationWin* aPostIt ) +{ + if (mvPostItFields.size()>1) + { + auto i = std::find_if(mvPostItFields.begin(), mvPostItFields.end(), + [&aPostIt](const std::unique_ptr<SwSidebarItem>& pField) { return pField->mpPostIt == aPostIt; }); + if (i == mvPostItFields.end()) + return nullptr; + + auto iNextPostIt = i; + if (aDirection == KEY_PAGEUP) + { + if ( iNextPostIt == mvPostItFields.begin() ) + { + return nullptr; + } + --iNextPostIt; + } + else + { + ++iNextPostIt; + if ( iNextPostIt == mvPostItFields.end() ) + { + return nullptr; + } + } + // lets quit, we are back at the beginning + if ( (*iNextPostIt)->mpPostIt == aPostIt) + return nullptr; + return (*iNextPostIt)->mpPostIt; + } + else + return nullptr; +} + +tools::Long SwPostItMgr::GetNextBorder() +{ + for (auto const& pPage : mPages) + { + for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b) + { + if ((*b)->mpPostIt == mpActivePostIt) + { + auto aNext = b; + ++aNext; + bool bFollow = (aNext != pPage->mvSidebarItems.end()) && (*aNext)->mpPostIt->IsFollow(); + if ( pPage->bScrollbar || bFollow ) + { + return -1; + } + else + { + //if this is the last item, return the bottom border otherwise the next item + if (aNext == pPage->mvSidebarItems.end()) + return mpEditWin->LogicToPixel(Point(0,pPage->mPageRect.Bottom())).Y() - GetSpaceBetween(); + else + return (*aNext)->mpPostIt->GetPosPixel().Y() - GetSpaceBetween(); + } + } + } + } + + OSL_FAIL("SwPostItMgr::GetNextBorder(): We have to find a next border here"); + return -1; +} + +void SwPostItMgr::SetShadowState(const SwPostItField* pField,bool bCursor) +{ + if (pField) + { + if (pField !=mShadowState.mpShadowField) + { + if (mShadowState.mpShadowField) + { + // reset old one if still alive + // TODO: does not work properly if mouse and cursor was set + sw::annotation::SwAnnotationWin* pOldPostIt = + GetAnnotationWin(mShadowState.mpShadowField); + if (pOldPostIt && pOldPostIt->Shadow() && (pOldPostIt->Shadow()->GetShadowState() != SS_EDIT)) + pOldPostIt->SetViewState(ViewState::NORMAL); + } + //set new one, if it is not currently edited + sw::annotation::SwAnnotationWin* pNewPostIt = GetAnnotationWin(pField); + if (pNewPostIt && pNewPostIt->Shadow() && (pNewPostIt->Shadow()->GetShadowState() != SS_EDIT)) + { + pNewPostIt->SetViewState(ViewState::VIEW); + //remember our new field + mShadowState.mpShadowField = pField; + mShadowState.bCursor = false; + mShadowState.bMouse = false; + } + } + if (bCursor) + mShadowState.bCursor = true; + else + mShadowState.bMouse = true; + } + else + { + if (mShadowState.mpShadowField) + { + if (bCursor) + mShadowState.bCursor = false; + else + mShadowState.bMouse = false; + if (!mShadowState.bCursor && !mShadowState.bMouse) + { + // reset old one if still alive + sw::annotation::SwAnnotationWin* pOldPostIt = GetAnnotationWin(mShadowState.mpShadowField); + if (pOldPostIt && pOldPostIt->Shadow() && (pOldPostIt->Shadow()->GetShadowState() != SS_EDIT)) + { + pOldPostIt->SetViewState(ViewState::NORMAL); + mShadowState.mpShadowField = nullptr; + } + } + } + } +} + +void SwPostItMgr::PrepareView(bool bIgnoreCount) +{ + if (!HasNotes() || bIgnoreCount) + { + mpWrtShell->StartAllAction(); + SwRootFrame* pLayout = mpWrtShell->GetLayout(); + if ( pLayout ) + SwPostItHelper::setSidebarChanged( pLayout, + mpWrtShell->getIDocumentSettingAccess().get( DocumentSettingId::BROWSE_MODE ) ); + mpWrtShell->EndAllAction(); + } +} + +bool SwPostItMgr::ShowScrollbar(const tools::ULong aPage) const +{ + if (mPages.size() > aPage-1) + return (mPages[aPage-1]->bScrollbar && !mbWaitingForCalcRects); + else + return false; +} + +bool SwPostItMgr::IsHit(const Point &aPointPixel) +{ + if (HasNotes() && ShowNotes()) + { + const Point aPoint = mpEditWin->PixelToLogic(aPointPixel); + const SwRootFrame* pLayout = mpWrtShell->GetLayout(); + SwRect aPageFrame; + const tools::ULong nPageNum = SwPostItHelper::getPageInfo( aPageFrame, pLayout, aPoint ); + if( nPageNum ) + { + tools::Rectangle aRect; + OSL_ENSURE(mPages.size()>nPageNum-1,"SwPostitMgr:: page container size wrong"); + aRect = mPages[nPageNum-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? tools::Rectangle(Point(aPageFrame.Left()-GetSidebarWidth()-GetSidebarBorderWidth(),aPageFrame.Top()),Size(GetSidebarWidth(),aPageFrame.Height())) + : tools::Rectangle( Point(aPageFrame.Right()+GetSidebarBorderWidth(),aPageFrame.Top()) , Size(GetSidebarWidth(),aPageFrame.Height())); + if (aRect.Contains(aPoint)) + { + // we hit the note's sidebar + // lets now test for the arrow area + if (mPages[nPageNum-1]->bScrollbar) + return ScrollbarHit(nPageNum,aPoint); + else + return false; + } + } + } + return false; +} + +vcl::Window* SwPostItMgr::IsHitSidebarWindow(const Point& rPointLogic) +{ + vcl::Window* pRet = nullptr; + + if (HasNotes() && ShowNotes()) + { + bool bEnableMapMode = !mpEditWin->IsMapModeEnabled(); + if (bEnableMapMode) + mpEditWin->EnableMapMode(); + + for (const std::unique_ptr<SwSidebarItem>& pItem : mvPostItFields) + { + SwAnnotationWin* pPostIt = pItem->mpPostIt; + if (!pPostIt) + continue; + + if (pPostIt->IsHitWindow(rPointLogic)) + { + pRet = pPostIt; + break; + } + } + + if (bEnableMapMode) + mpEditWin->EnableMapMode(false); + } + + return pRet; +} + +tools::Rectangle SwPostItMgr::GetBottomScrollRect(const tools::ULong aPage) const +{ + SwRect aPageRect = mPages[aPage-1]->mPageRect; + Point aPointBottom = mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? Point(aPageRect.Left() - GetSidebarWidth() - GetSidebarBorderWidth() + mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- mpEditWin->PixelToLogic(Size(0,2+GetSidebarScrollerHeight())).Height()) + : Point(aPageRect.Right() + GetSidebarBorderWidth() + mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- mpEditWin->PixelToLogic(Size(0,2+GetSidebarScrollerHeight())).Height()); + Size aSize(GetSidebarWidth() - mpEditWin->PixelToLogic(Size(4,0)).Width(), mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height()) ; + return tools::Rectangle(aPointBottom,aSize); +} + +tools::Rectangle SwPostItMgr::GetTopScrollRect(const tools::ULong aPage) const +{ + SwRect aPageRect = mPages[aPage-1]->mPageRect; + Point aPointTop = mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? Point(aPageRect.Left() - GetSidebarWidth() -GetSidebarBorderWidth()+ mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + mpEditWin->PixelToLogic(Size(0,2)).Height()) + : Point(aPageRect.Right() + GetSidebarBorderWidth() + mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + mpEditWin->PixelToLogic(Size(0,2)).Height()); + Size aSize(GetSidebarWidth() - mpEditWin->PixelToLogic(Size(4,0)).Width(), mpEditWin->PixelToLogic(Size(0,GetSidebarScrollerHeight())).Height()) ; + return tools::Rectangle(aPointTop,aSize); +} + +//IMPORTANT: if you change the rects here, also change SwPageFrame::PaintNotesSidebar() +bool SwPostItMgr::ScrollbarHit(const tools::ULong aPage,const Point &aPoint) +{ + SwRect aPageRect = mPages[aPage-1]->mPageRect; + Point aPointBottom = mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? Point(aPageRect.Left() - GetSidebarWidth()-GetSidebarBorderWidth() + mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- mpEditWin->PixelToLogic(Size(0,2+GetSidebarScrollerHeight())).Height()) + : Point(aPageRect.Right() + GetSidebarBorderWidth()+ mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Bottom()- mpEditWin->PixelToLogic(Size(0,2+GetSidebarScrollerHeight())).Height()); + + Point aPointTop = mPages[aPage-1]->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? Point(aPageRect.Left() - GetSidebarWidth()-GetSidebarBorderWidth()+ mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + mpEditWin->PixelToLogic(Size(0,2)).Height()) + : Point(aPageRect.Right()+GetSidebarBorderWidth()+ mpEditWin->PixelToLogic(Size(2,0)).Width(),aPageRect.Top() + mpEditWin->PixelToLogic(Size(0,2)).Height()); + + tools::Rectangle aRectBottom(GetBottomScrollRect(aPage)); + tools::Rectangle aRectTop(GetTopScrollRect(aPage)); + + if (aRectBottom.Contains(aPoint)) + { + if (aPoint.X() < tools::Long((aPointBottom.X() + GetSidebarWidth()/3))) + Scroll( GetScrollSize(),aPage); + else + Scroll( -1*GetScrollSize(), aPage); + return true; + } + else if (aRectTop.Contains(aPoint)) + { + if (aPoint.X() < tools::Long((aPointTop.X() + GetSidebarWidth()/3*2))) + Scroll(GetScrollSize(), aPage); + else + Scroll(-1*GetScrollSize(), aPage); + return true; + } + return false; +} + +void SwPostItMgr::CorrectPositions() +{ + if ( mbWaitingForCalcRects || mbLayouting || mvPostItFields.empty() ) + return; + + // find first valid note + SwAnnotationWin *pFirstPostIt = nullptr; + for (auto const& postItField : mvPostItFields) + { + pFirstPostIt = postItField->mpPostIt; + if (pFirstPostIt) + break; + } + + //if we have not found a valid note, forget about it and leave + if (!pFirstPostIt) + return; + + // yeah, I know, if this is a left page it could be wrong, but finding the page and the note is probably not even faster than just doing it + // check, if anchor overlay object exists. + const tools::Long aAnchorX = pFirstPostIt->Anchor() + ? mpEditWin->LogicToPixel( Point(static_cast<tools::Long>(pFirstPostIt->Anchor()->GetSixthPosition().getX()),0)).X() + : 0; + const tools::Long aAnchorY = pFirstPostIt->Anchor() + ? mpEditWin->LogicToPixel( Point(0,static_cast<tools::Long>(pFirstPostIt->Anchor()->GetSixthPosition().getY()))).Y() + 1 + : 0; + if (Point(aAnchorX,aAnchorY) == pFirstPostIt->GetPosPixel()) + return; + + tools::Long aAnchorPosX = 0; + tools::Long aAnchorPosY = 0; + for (const std::unique_ptr<SwPostItPageItem>& pPage : mPages) + { + for (auto const& item : pPage->mvSidebarItems) + { + // check, if anchor overlay object exists. + if ( item->mbShow && item->mpPostIt && item->mpPostIt->Anchor() ) + { + aAnchorPosX = pPage->eSidebarPosition == sw::sidebarwindows::SidebarPosition::LEFT + ? mpEditWin->LogicToPixel( Point(static_cast<tools::Long>(item->mpPostIt->Anchor()->GetSeventhPosition().getX()),0)).X() + : mpEditWin->LogicToPixel( Point(static_cast<tools::Long>(item->mpPostIt->Anchor()->GetSixthPosition().getX()),0)).X(); + aAnchorPosY = mpEditWin->LogicToPixel( Point(0,static_cast<tools::Long>(item->mpPostIt->Anchor()->GetSixthPosition().getY()))).Y() + 1; + item->mpPostIt->SetPosPixel(Point(aAnchorPosX,aAnchorPosY)); + } + } + } +} + +bool SwPostItMgr::ShowNotes() const +{ + // we only want to see notes if Options - Writer - View - Notes is ticked + return mpWrtShell->GetViewOptions()->IsPostIts(); +} + +bool SwPostItMgr::HasNotes() const +{ + return !mvPostItFields.empty(); +} + +tools::ULong SwPostItMgr::GetSidebarWidth(bool bPx) const +{ + bool bEnableMapMode = !mpWrtShell->GetOut()->IsMapModeEnabled(); + sal_uInt16 nZoom = mpWrtShell->GetViewOptions()->GetZoom(); + if (comphelper::LibreOfficeKit::isActive() && !bEnableMapMode) + { + // The output device is the tile and contains the real wanted scale factor. + double fScaleX = double(mpWrtShell->GetOut()->GetMapMode().GetScaleX()); + nZoom = fScaleX * 100; + } + tools::ULong aWidth = static_cast<tools::ULong>(nZoom * 1.8); + + if (bPx) + return aWidth; + else + { + if (bEnableMapMode) + // The output device is the window. + mpWrtShell->GetOut()->EnableMapMode(); + tools::Long nRet = mpWrtShell->GetOut()->PixelToLogic(Size(aWidth, 0)).Width(); + if (bEnableMapMode) + mpWrtShell->GetOut()->EnableMapMode(false); + return nRet; + } +} + +tools::ULong SwPostItMgr::GetSidebarBorderWidth(bool bPx) const +{ + if (bPx) + return 2; + else + return mpWrtShell->GetOut()->PixelToLogic(Size(2,0)).Width(); +} + +Color SwPostItMgr::GetColorDark(std::size_t aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayNormal[] = { + COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL, + COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL, + COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL }; + + return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal )]; + } + else + return COL_WHITE; +} + +Color SwPostItMgr::GetColorLight(std::size_t aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayLight[] = { + COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT, + COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT, + COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT }; + + return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight )]; + } + else + return COL_WHITE; +} + +Color SwPostItMgr::GetColorAnchor(std::size_t aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayAnchor[] = { + COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK, + COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK, + COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK }; + + return aArrayAnchor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnchor )]; + } + else + return COL_WHITE; +} + +void SwPostItMgr::SetActiveSidebarWin( SwAnnotationWin* p) +{ + if ( p == mpActivePostIt ) + return; + + // we need the temp variable so we can set mpActivePostIt before we call DeactivatePostIt + // therefore we get a new layout in DOCCHANGED when switching from postit to document, + // otherwise, GetActivePostIt() would still hold our old postit + SwAnnotationWin* pActive = mpActivePostIt; + mpActivePostIt = p; + if (pActive) + { + pActive->DeactivatePostIt(); + mShadowState.mpShadowField = nullptr; + } + if (mpActivePostIt) + { + mpActivePostIt->GotoPos(); + mpView->AttrChangedNotify(nullptr); + mpActivePostIt->ActivatePostIt(); + } +} + +IMPL_LINK_NOARG( SwPostItMgr, CalcHdl, void*, void ) +{ + mnEventId = nullptr; + if ( mbLayouting ) + { + OSL_FAIL("Reentrance problem in Layout Manager!"); + mbWaitingForCalcRects = false; + return; + } + + // do not change order, even if it would seem so in the first place, we need the calcrects always + if (CalcRects() || mbLayout) + { + mbLayout = false; + LayoutPostIts(); + } +} + +void SwPostItMgr::Rescale() +{ + for (auto const& postItField : mvPostItFields) + if ( postItField->mpPostIt ) + postItField->mpPostIt->Rescale(); +} + +sal_Int32 SwPostItMgr::GetInitialAnchorDistance() const +{ + const Fraction& f( mpEditWin->GetMapMode().GetScaleY() ); + return sal_Int32(POSTIT_INITIAL_ANCHOR_DISTANCE * f); +} + +sal_Int32 SwPostItMgr::GetSpaceBetween() const +{ + const Fraction& f( mpEditWin->GetMapMode().GetScaleY() ); + return sal_Int32(POSTIT_SPACE_BETWEEN * f); +} + +sal_Int32 SwPostItMgr::GetScrollSize() const +{ + const Fraction& f( mpEditWin->GetMapMode().GetScaleY() ); + return sal_Int32((POSTIT_SPACE_BETWEEN + POSTIT_MINIMUMSIZE_WITH_META) * f); +} + +sal_Int32 SwPostItMgr::GetMinimumSizeWithMeta() const +{ + const Fraction& f( mpEditWin->GetMapMode().GetScaleY() ); + return sal_Int32(POSTIT_MINIMUMSIZE_WITH_META * f); +} + +sal_Int32 SwPostItMgr::GetSidebarScrollerHeight() const +{ + const Fraction& f( mpEditWin->GetMapMode().GetScaleY() ); + return sal_Int32(POSTIT_SCROLL_SIDEBAR_HEIGHT * f); +} + +void SwPostItMgr::SetSpellChecking() +{ + for (auto const& postItField : mvPostItFields) + if ( postItField->mpPostIt ) + postItField->mpPostIt->SetSpellChecking(); +} + +void SwPostItMgr::SetReadOnlyState() +{ + for (auto const& postItField : mvPostItFields) + if ( postItField->mpPostIt ) + postItField->mpPostIt->SetReadonly( mbReadOnly ); +} + +void SwPostItMgr::CheckMetaText() +{ + for (auto const& postItField : mvPostItFields) + if ( postItField->mpPostIt ) + postItField->mpPostIt->CheckMetaText(); + +} + +sal_uInt16 SwPostItMgr::Replace(SvxSearchItem const * pItem) +{ + SwAnnotationWin* pWin = GetActiveSidebarWin(); + sal_uInt16 aResult = pWin->GetOutlinerView()->StartSearchAndReplace( *pItem ); + if (!aResult) + SetActiveSidebarWin(nullptr); + return aResult; +} + +sal_uInt16 SwPostItMgr::FinishSearchReplace(const i18nutil::SearchOptions2& rSearchOptions, bool bSrchForward) +{ + SwAnnotationWin* pWin = GetActiveSidebarWin(); + SvxSearchItem aItem(SID_SEARCH_ITEM ); + aItem.SetSearchOptions(rSearchOptions); + aItem.SetBackward(!bSrchForward); + sal_uInt16 aResult = pWin->GetOutlinerView()->StartSearchAndReplace( aItem ); + if (!aResult) + SetActiveSidebarWin(nullptr); + return aResult; +} + +sal_uInt16 SwPostItMgr::SearchReplace(const SwFormatField &pField, const i18nutil::SearchOptions2& rSearchOptions, bool bSrchForward) +{ + sal_uInt16 aResult = 0; + SwAnnotationWin* pWin = GetSidebarWin(&pField); + if (pWin) + { + ESelection aOldSelection = pWin->GetOutlinerView()->GetSelection(); + if (bSrchForward) + pWin->GetOutlinerView()->SetSelection(ESelection(0,0,0,0)); + else + pWin->GetOutlinerView()->SetSelection( + ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT)); + SvxSearchItem aItem(SID_SEARCH_ITEM ); + aItem.SetSearchOptions(rSearchOptions); + aItem.SetBackward(!bSrchForward); + aResult = pWin->GetOutlinerView()->StartSearchAndReplace( aItem ); + if (!aResult) + pWin->GetOutlinerView()->SetSelection(aOldSelection); + else + { + SetActiveSidebarWin(pWin); + MakeVisible(pWin); + } + } + return aResult; +} + +void SwPostItMgr::AssureStdModeAtShell() +{ + // deselect any drawing or frame and leave editing mode + SdrView* pSdrView = mpWrtShell->GetDrawView(); + if ( pSdrView && pSdrView->IsTextEdit() ) + { + bool bLockView = mpWrtShell->IsViewLocked(); + mpWrtShell->LockView( true ); + mpWrtShell->EndTextEdit(); + mpWrtShell->LockView( bLockView ); + } + + if( mpWrtShell->IsSelFrameMode() || mpWrtShell->IsObjSelected()) + { + mpWrtShell->UnSelectFrame(); + mpWrtShell->LeaveSelFrameMode(); + mpWrtShell->GetView().LeaveDrawCreate(); + mpWrtShell->EnterStdMode(); + + mpWrtShell->DrawSelChanged(); + mpView->StopShellTimer(); + } +} + +bool SwPostItMgr::HasActiveSidebarWin() const +{ + return mpActivePostIt != nullptr; +} + +bool SwPostItMgr::HasActiveAnnotationWin() const +{ + return HasActiveSidebarWin() && + mpActivePostIt != nullptr; +} + +void SwPostItMgr::GrabFocusOnActiveSidebarWin() +{ + if ( HasActiveSidebarWin() ) + { + mpActivePostIt->GrabFocus(); + } +} + +void SwPostItMgr::UpdateDataOnActiveSidebarWin() +{ + if ( HasActiveSidebarWin() ) + { + mpActivePostIt->UpdateData(); + } +} + +void SwPostItMgr::DeleteActiveSidebarWin() +{ + if ( HasActiveSidebarWin() ) + { + mpActivePostIt->Delete(); + } +} + +void SwPostItMgr::HideActiveSidebarWin() +{ + if ( HasActiveSidebarWin() ) + { + mpActivePostIt->Hide(); + } +} + +void SwPostItMgr::ToggleInsModeOnActiveSidebarWin() +{ + if ( HasActiveSidebarWin() ) + { + mpActivePostIt->ToggleInsMode(); + } +} + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY +void SwPostItMgr::ConnectSidebarWinToFrame( const SwFrame& rFrame, + const SwFormatField& rFormatField, + SwAnnotationWin& rSidebarWin ) +{ + if ( mpFrameSidebarWinContainer == nullptr ) + { + mpFrameSidebarWinContainer.reset(new SwFrameSidebarWinContainer()); + } + + const bool bInserted = mpFrameSidebarWinContainer->insert( rFrame, rFormatField, rSidebarWin ); + if ( bInserted && + mpWrtShell->GetAccessibleMap() ) + { + mpWrtShell->GetAccessibleMap()->InvalidatePosOrSize( nullptr, nullptr, &rSidebarWin, SwRect() ); + } +} + +void SwPostItMgr::DisconnectSidebarWinFromFrame( const SwFrame& rFrame, + SwAnnotationWin& rSidebarWin ) +{ + if ( mpFrameSidebarWinContainer != nullptr ) + { + const bool bRemoved = mpFrameSidebarWinContainer->remove( rFrame, rSidebarWin ); + if ( bRemoved && + mpWrtShell->GetAccessibleMap() ) + { + mpWrtShell->GetAccessibleMap()->A11yDispose( nullptr, nullptr, &rSidebarWin ); + } + } +} +#endif // ENABLE_WASM_STRIP_ACCESSIBILITY + +bool SwPostItMgr::HasFrameConnectedSidebarWins( const SwFrame& rFrame ) +{ + bool bRet( false ); + + if ( mpFrameSidebarWinContainer != nullptr ) + { + bRet = !mpFrameSidebarWinContainer->empty( rFrame ); + } + + return bRet; +} + +vcl::Window* SwPostItMgr::GetSidebarWinForFrameByIndex( const SwFrame& rFrame, + const sal_Int32 nIndex ) +{ + vcl::Window* pSidebarWin( nullptr ); + + if ( mpFrameSidebarWinContainer != nullptr ) + { + pSidebarWin = mpFrameSidebarWinContainer->get( rFrame, nIndex ); + } + + return pSidebarWin; +} + +void SwPostItMgr::GetAllSidebarWinForFrame( const SwFrame& rFrame, + std::vector< vcl::Window* >* pChildren ) +{ + if ( mpFrameSidebarWinContainer != nullptr ) + { + mpFrameSidebarWinContainer->getAll( rFrame, pChildren ); + } +} + +void SwPostItMgr::ShowHideResolvedNotes(bool visible) { + for (auto const& pPage : mPages) + { + for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b) + { + if ((*b)->mpPostIt->IsResolved()) + { + (*b)->mpPostIt->SetResolved(true); + (*b)->mpPostIt->GetSidebarItem().mbShow = visible; + } + } + } + LayoutPostIts(); +} + +void SwPostItMgr::UpdateResolvedStatus(const sw::annotation::SwAnnotationWin* topNote) { + // Given the topmost note as an argument, scans over all notes and sets the + // 'resolved' state of each descendant of the top notes to the resolved state + // of the top note. + bool resolved = topNote->IsResolved(); + for (auto const& pPage : mPages) + { + for(auto b = pPage->mvSidebarItems.begin(); b!= pPage->mvSidebarItems.end(); ++b) + { + if((*b)->mpPostIt->GetTopReplyNote() == topNote) { + (*b)->mpPostIt->SetResolved(resolved); + } + } + } +} + +void SwNoteProps::ImplCommit() {} +void SwNoteProps::Notify( const css::uno::Sequence< OUString >& ) {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/ShadowOverlayObject.cxx b/sw/source/uibase/docvw/ShadowOverlayObject.cxx new file mode 100644 index 000000000..7607cb335 --- /dev/null +++ b/sw/source/uibase/docvw/ShadowOverlayObject.cxx @@ -0,0 +1,236 @@ +/* -*- 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 "ShadowOverlayObject.hxx" + +#include <view.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svdview.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> + +#include <sw_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/primitivetools2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> + +namespace sw::sidebarwindows { + +// helper SwPostItShadowPrimitive + +namespace { + +// Used to allow view-dependent primitive definition. For that purpose, the +// initially created primitive (this one) always has to be view-independent, +// but the decomposition is made view-dependent. Very simple primitive which +// just remembers the discrete data and applies it at decomposition time. +class ShadowPrimitive : public drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D +{ +private: + basegfx::B2DPoint maBasePosition; + basegfx::B2DPoint maSecondPosition; + ShadowState maShadowState; + +protected: + virtual void create2DDecomposition( + drawinglayer::primitive2d::Primitive2DContainer& rContainer, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) const override; + +public: + ShadowPrimitive( + const basegfx::B2DPoint& rBasePosition, + const basegfx::B2DPoint& rSecondPosition, + ShadowState aShadowState) + : maBasePosition(rBasePosition), + maSecondPosition(rSecondPosition), + maShadowState(aShadowState) + {} + + // data access + const basegfx::B2DPoint& getSecondPosition() const { return maSecondPosition; } + + virtual bool operator==( const drawinglayer::primitive2d::BasePrimitive2D& rPrimitive ) const override; + + virtual sal_uInt32 getPrimitive2DID() const override; +}; + +} + +void ShadowPrimitive::create2DDecomposition( + drawinglayer::primitive2d::Primitive2DContainer& rContainer, + const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // get logic sizes in object coordinate system + basegfx::B2DRange aRange(maBasePosition); + + switch(maShadowState) + { + case SS_NORMAL: + { + aRange.expand(basegfx::B2DTuple(getSecondPosition().getX(), getSecondPosition().getY() + (2.0 * getDiscreteUnit()))); + const ::drawinglayer::attribute::FillGradientAttribute aFillGradientAttribute( + drawinglayer::attribute::GradientStyle::Linear, + 0.0, + 0.5, + 0.5, + M_PI, + basegfx::BColor(230.0/255.0,230.0/255.0,230.0/255.0), + basegfx::BColor(180.0/255.0,180.0/255.0,180.0/255.0)); + + rContainer.push_back( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aFillGradientAttribute)); + break; + } + case SS_VIEW: + { + aRange.expand(basegfx::B2DTuple(getSecondPosition().getX(), getSecondPosition().getY() + (4.0 * getDiscreteUnit()))); + const drawinglayer::attribute::FillGradientAttribute aFillGradientAttribute( + drawinglayer::attribute::GradientStyle::Linear, + 0.0, + 0.5, + 0.5, + M_PI, + basegfx::BColor(230.0/255.0,230.0/255.0,230.0/255.0), + basegfx::BColor(180.0/255.0,180.0/255.0,180.0/255.0)); + + rContainer.push_back( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aFillGradientAttribute)); + break; + } + case SS_EDIT: + { + aRange.expand(basegfx::B2DTuple(getSecondPosition().getX(), getSecondPosition().getY() + (4.0 * getDiscreteUnit()))); + const drawinglayer::attribute::FillGradientAttribute aFillGradientAttribute( + drawinglayer::attribute::GradientStyle::Linear, + 0.0, + 0.5, + 0.5, + M_PI, + basegfx::BColor(230.0/255.0,230.0/255.0,230.0/255.0), + basegfx::BColor(83.0/255.0,83.0/255.0,83.0/255.0)); + + rContainer.push_back( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aFillGradientAttribute)); + break; + } + default: + { + break; + } + } +} + +bool ShadowPrimitive::operator==( const drawinglayer::primitive2d::BasePrimitive2D& rPrimitive ) const +{ + if(drawinglayer::primitive2d::DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const ShadowPrimitive& rCompare = static_cast< const ShadowPrimitive& >(rPrimitive); + + return (maBasePosition == rCompare.maBasePosition + && getSecondPosition() == rCompare.getSecondPosition() + && maShadowState == rCompare.maShadowState); + } + + return false; +} + +sal_uInt32 ShadowPrimitive::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_SWSIDEBARSHADOWPRIMITIVE; +} + +/* static */ std::unique_ptr<ShadowOverlayObject> ShadowOverlayObject::CreateShadowOverlayObject( SwView const & rDocView ) +{ + std::unique_ptr<ShadowOverlayObject> pShadowOverlayObject; + + if ( rDocView.GetDrawView() ) + { + SdrPaintWindow* pPaintWindow = rDocView.GetDrawView()->GetPaintWindow(0); + if( pPaintWindow ) + { + const rtl::Reference< sdr::overlay::OverlayManager >& xOverlayManager = pPaintWindow->GetOverlayManager(); + + if ( xOverlayManager.is() ) + { + pShadowOverlayObject.reset( new ShadowOverlayObject( basegfx::B2DPoint(0,0), + basegfx::B2DPoint(0,0), + Color(0,0,0) ) ); + xOverlayManager->add(*pShadowOverlayObject); + } + } + } + + return pShadowOverlayObject; +} + +ShadowOverlayObject::ShadowOverlayObject( const basegfx::B2DPoint& rBasePos, + const basegfx::B2DPoint& rSecondPosition, + Color aBaseColor ) + : OverlayObjectWithBasePosition(rBasePos, aBaseColor) + , maSecondPosition(rSecondPosition) + , mShadowState(SS_NORMAL) +{ +} + +ShadowOverlayObject::~ShadowOverlayObject() +{ + if ( getOverlayManager() ) + { + getOverlayManager()->remove(*this); + } +} + +drawinglayer::primitive2d::Primitive2DContainer ShadowOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + const drawinglayer::primitive2d::Primitive2DReference aReference( + new ShadowPrimitive( getBasePosition(), + maSecondPosition, + GetShadowState() ) ); + return drawinglayer::primitive2d::Primitive2DContainer { aReference }; +} + +void ShadowOverlayObject::SetShadowState(ShadowState aState) +{ + if (mShadowState != aState) + { + mShadowState = aState; + + objectChange(); + } +} + +void ShadowOverlayObject::SetPosition( const basegfx::B2DPoint& rPoint1, + const basegfx::B2DPoint& rPoint2) +{ + if(!rPoint1.equal(getBasePosition()) || !rPoint2.equal(maSecondPosition)) + { + maBasePosition = rPoint1; + maSecondPosition = rPoint2; + + objectChange(); + } +} + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/ShadowOverlayObject.hxx b/sw/source/uibase/docvw/ShadowOverlayObject.hxx new file mode 100644 index 000000000..cd612f7ce --- /dev/null +++ b/sw/source/uibase/docvw/ShadowOverlayObject.hxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <memory> + +#include <svx/sdr/overlay/overlayobject.hxx> + +class SwView; + +namespace sw::sidebarwindows { + +enum ShadowState +{ + SS_NORMAL, + SS_VIEW, + SS_EDIT +}; + +class ShadowOverlayObject: public sdr::overlay::OverlayObjectWithBasePosition +{ +protected: + // geometry creation for OverlayObject + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + +private: + basegfx::B2DPoint maSecondPosition; + ShadowState mShadowState; + + ShadowOverlayObject( const basegfx::B2DPoint& rBasePos, + const basegfx::B2DPoint& rSecondPosition, + Color aBaseColor ); + +public: + virtual ~ShadowOverlayObject() override; + + void SetShadowState(ShadowState aState); + ShadowState GetShadowState() const {return mShadowState;} + + void SetPosition( const basegfx::B2DPoint& rPoint1, + const basegfx::B2DPoint& rPoint2 ); + + static std::unique_ptr<ShadowOverlayObject> CreateShadowOverlayObject( SwView const & rDocView ); +}; + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/SidebarTxtControl.cxx b/sw/source/uibase/docvw/SidebarTxtControl.cxx new file mode 100644 index 000000000..7b7002b7f --- /dev/null +++ b/sw/source/uibase/docvw/SidebarTxtControl.cxx @@ -0,0 +1,470 @@ +/* -*- 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 <config_wasm_strip.h> + +#include "SidebarTxtControl.hxx" + +#include <docsh.hxx> +#include <doc.hxx> + +#include <PostItMgr.hxx> + +#include <cmdid.h> +#include <strings.hrc> + +#include <unotools/securityoptions.hxx> +#include <officecfg/Office/Common.hxx> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxhelp.hxx> + +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/gradient.hxx> +#include <vcl/settings.hxx> + +#include <editeng/outliner.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> + +#include <uitool.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <AnnotationWin.hxx> +#include <IDocumentDeviceAccess.hxx> +#include <redline.hxx> +#include <memory> + +namespace sw::sidebarwindows { + +SidebarTextControl::SidebarTextControl(sw::annotation::SwAnnotationWin& rSidebarWin, + SwView& rDocView, + SwPostItMgr& rPostItMgr) + : mrSidebarWin(rSidebarWin) + , mrDocView(rDocView) + , mrPostItMgr(rPostItMgr) + , mbMouseDownGainingFocus(false) +{ +} + +EditView* SidebarTextControl::GetEditView() const +{ + OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView(); + if (!pOutlinerView) + return nullptr; + return &pOutlinerView->GetEditView(); +} + +EditEngine* SidebarTextControl::GetEditEngine() const +{ + OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView(); + if (!pOutlinerView) + return nullptr; + return pOutlinerView->GetEditView().GetEditEngine(); +} + +void SidebarTextControl::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(0, 0); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + + SetOutputSizePixel(aSize); + + weld::CustomWidgetController::SetDrawingArea(pDrawingArea); + + EnableRTL(false); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + Color aBgColor = rStyleSettings.GetWindowColor(); + + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + rDevice.SetMapMode(MapMode(MapUnit::MapTwip)); + rDevice.SetBackground(aBgColor); + + Size aOutputSize(rDevice.PixelToLogic(aSize)); + + EditView* pEditView = GetEditView(); + pEditView->setEditViewCallbacks(this); + + EditEngine* pEditEngine = GetEditEngine(); + // For tdf#143443 note we want an 'infinite' height initially (which is the + // editengines default). For tdf#144686 it is helpful if the initial width + // is the "SidebarWidth" so the calculated text height is always meaningful + // for layout in the sidebar. + Size aPaperSize(mrPostItMgr.GetSidebarWidth(), pEditEngine->GetPaperSize().Height()); + pEditEngine->SetPaperSize(aPaperSize); + pEditEngine->SetRefDevice(mrDocView.GetWrtShell().getIDocumentDeviceAccess().getReferenceDevice(false)); + + pEditView->SetOutputArea(tools::Rectangle(Point(0, 0), aOutputSize)); + pEditView->SetBackgroundColor(aBgColor); + + pDrawingArea->set_cursor(PointerStyle::Text); + +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + InitAccessible(); +#endif +} + +void SidebarTextControl::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark) +{ + Point aMousePos = EditViewOutputDevice().PixelToLogic(rPosition); + m_xEditView->SetCursorLogicPosition(aMousePos, bPoint, bClearMark); +} + +void SidebarTextControl::GetFocus() +{ + WeldEditView::GetFocus(); + if ( !mrSidebarWin.IsMouseOver() ) + Invalidate(); + mrSidebarWin.SetActiveSidebarWin(); +} + +void SidebarTextControl::LoseFocus() +{ + // write the visible text back into the SwField + mrSidebarWin.UpdateData(); + + WeldEditView::LoseFocus(); + if ( !mrSidebarWin.IsMouseOver() ) + { + Invalidate(); + } + // set false for autoscroll to typing location + mrSidebarWin.LockView(false); +} + +OUString SidebarTextControl::RequestHelp(tools::Rectangle& rHelpRect) +{ + if (EditView* pEditView = GetEditView()) + { + Point aPos = rHelpRect.TopLeft(); + + const OutputDevice& rOutDev = pEditView->GetOutputDevice(); + Point aLogicClick = rOutDev.PixelToLogic(aPos); + const SvxFieldItem* pItem = pEditView->GetField(aLogicClick); + if (pItem) + { + const SvxFieldData* pField = pItem->GetField(); + const SvxURLField* pURL = dynamic_cast<const SvxURLField*>( pField ); + if (pURL) + { + rHelpRect = tools::Rectangle(aPos, Size(50, 10)); + return SfxHelp::GetURLHelpText(pURL->GetURL()); + } + } + } + + TranslateId pResId; + switch( mrSidebarWin.GetLayoutStatus() ) + { + case SwPostItHelper::INSERTED: pResId = STR_REDLINE_INSERT; break; + case SwPostItHelper::DELETED: pResId = STR_REDLINE_DELETE; break; + default: break; + } + + SwContentAtPos aContentAtPos( IsAttrAtPos::Redline ); + if ( pResId && + mrDocView.GetWrtShell().GetContentAtPos( mrSidebarWin.GetAnchorPos(), aContentAtPos ) ) + { + OUString sText = SwResId(pResId) + ": " + + aContentAtPos.aFnd.pRedl->GetAuthorString() + " - " + + GetAppLangDateTimeString( aContentAtPos.aFnd.pRedl->GetTimeStamp() ); + return sText; + } + + return OUString(); +} + +void SidebarTextControl::EditViewScrollStateChange() +{ + mrSidebarWin.SetScrollbar(); +} + +void SidebarTextControl::DrawForPage(OutputDevice* pDev, const Point& rPt) +{ + //Take the control's height, but overwrite the scrollbar area if there was one + OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); + Size aSize(rDevice.PixelToLogic(GetOutputSizePixel())); + + if (OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView()) + { + pOutlinerView->GetOutliner()->Draw(*pDev, tools::Rectangle(rPt, aSize)); + } + + if ( mrSidebarWin.GetLayoutStatus()!=SwPostItHelper::DELETED ) + return; + + pDev->Push(vcl::PushFlags::LINECOLOR); + + pDev->SetLineColor(mrSidebarWin.GetChangeColor()); + Point aBottomRight(rPt); + aBottomRight.Move(aSize); + pDev->DrawLine(rPt, aBottomRight); + + Point aTopRight(rPt); + aTopRight.Move(Size(aSize.Width(), 0)); + + Point aBottomLeft(rPt); + aBottomLeft.Move(Size(0, aSize.Height())); + + pDev->DrawLine(aTopRight, aBottomLeft); + + pDev->Pop(); +} + +void SidebarTextControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + Size aSize = GetOutputSizePixel(); + Point aPos; + + if (!rRenderContext.GetSettings().GetStyleSettings().GetHighContrastMode()) + { + if (mrSidebarWin.IsMouseOverSidebarWin() || HasFocus()) + { + rRenderContext.DrawGradient(tools::Rectangle(aPos, rRenderContext.PixelToLogic(aSize)), + Gradient(GradientStyle::Linear, mrSidebarWin.ColorDark(), mrSidebarWin.ColorDark())); + } + else + { + rRenderContext.DrawGradient(tools::Rectangle(aPos, rRenderContext.PixelToLogic(aSize)), + Gradient(GradientStyle::Linear, mrSidebarWin.ColorLight(), mrSidebarWin.ColorDark())); + } + } + + DoPaint(rRenderContext, rRect); + + if (mrSidebarWin.GetLayoutStatus() != SwPostItHelper::DELETED) + return; + + const AntialiasingFlags nFormerAntialiasing( rRenderContext.GetAntialiasing() ); + const bool bIsAntiAliasing = officecfg::Office::Common::Drawinglayer::AntiAliasing::get(); + if ( bIsAntiAliasing ) + rRenderContext.SetAntialiasing(AntialiasingFlags::Enable); + rRenderContext.SetLineColor(mrSidebarWin.GetChangeColor()); + rRenderContext.DrawLine(rRenderContext.PixelToLogic(aPos), + rRenderContext.PixelToLogic(aPos + Point(aSize.Width(), + aSize.Height() * 0.95))); + rRenderContext.DrawLine(rRenderContext.PixelToLogic(aPos + Point(aSize.Width(), + 0)), + rRenderContext.PixelToLogic(aPos + Point(0, + aSize.Height() * 0.95))); + if ( bIsAntiAliasing ) + rRenderContext.SetAntialiasing(nFormerAntialiasing); +} + +void SidebarTextControl::MakeVisible() +{ + //let's make sure we see our note + mrPostItMgr.MakeVisible(&mrSidebarWin); +} + +bool SidebarTextControl::KeyInput( const KeyEvent& rKeyEvt ) +{ + if (getenv("SW_DEBUG") && rKeyEvt.GetKeyCode().GetCode() == KEY_F12) + { + if (rKeyEvt.GetKeyCode().IsShift()) + { + mrDocView.GetDocShell()->GetDoc()->dumpAsXml(); + return true; + } + } + + bool bDone = false; + + const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode(); + sal_uInt16 nKey = rKeyCode.GetCode(); + if ( ( rKeyCode.IsMod1() && rKeyCode.IsMod2() ) && + ( (nKey == KEY_PAGEUP) || (nKey == KEY_PAGEDOWN) ) ) + { + mrSidebarWin.SwitchToPostIt(nKey); + bDone = true; + } + else if ( nKey == KEY_ESCAPE || + ( rKeyCode.IsMod1() && + ( nKey == KEY_PAGEUP || + nKey == KEY_PAGEDOWN ) ) ) + { + mrSidebarWin.SwitchToFieldPos(); + bDone = true; + } + else if ( rKeyCode.GetFullCode() == KEY_INSERT ) + { + mrSidebarWin.ToggleInsMode(); + bDone = true; + } + else + { + MakeVisible(); + + tools::Long aOldHeight = mrSidebarWin.GetPostItTextHeight(); + + /// HACK: need to switch off processing of Undo/Redo in Outliner + if ( !( (nKey == KEY_Z || nKey == KEY_Y) && rKeyCode.IsMod1()) ) + { + bool bIsProtected = mrSidebarWin.IsProtected(); + if ( !bIsProtected || !EditEngine::DoesKeyChangeText(rKeyEvt) ) + { + EditView* pEditView = GetEditView(); + bDone = pEditView && pEditView->PostKeyEvent(rKeyEvt); + } + else + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetDrawingArea(), "modules/swriter/ui/inforeadonlydialog.ui")); + std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog("InfoReadonlyDialog")); + xQuery->run(); + } + } + if (bDone) + mrSidebarWin.ResizeIfNecessary( aOldHeight, mrSidebarWin.GetPostItTextHeight() ); + else + { + // write back data first when showing navigator + if ( nKey==KEY_F5 ) + mrSidebarWin.UpdateData(); + bDone = mrDocView.KeyInput(rKeyEvt); + } + } + + mrDocView.GetViewFrame()->GetBindings().InvalidateAll(false); + + return bDone; +} + +bool SidebarTextControl::MouseButtonDown(const MouseEvent& rMEvt) +{ + if (EditView* pEditView = GetEditView()) + { + bool bExecuteMod = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink); + + if ( !bExecuteMod || (rMEvt.GetModifier() == KEY_MOD1)) + { + const OutputDevice& rOutDev = pEditView->GetOutputDevice(); + Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel()); + if (const SvxFieldItem* pItem = pEditView->GetField(aLogicClick)) + { + const SvxFieldData* pField = pItem->GetField(); + const SvxURLField* pURL = dynamic_cast<const SvxURLField*>( pField ); + if ( pURL ) + { + pEditView->MouseButtonDown( rMEvt ); + SwWrtShell &rSh = mrDocView.GetWrtShell(); + const OUString& sURL( pURL->GetURL() ); + const OUString& sTarget( pURL->GetTargetFrame() ); + ::LoadURL(rSh, sURL, LoadUrlFlags::NONE, sTarget); + return true; + } + } + } + } + + mbMouseDownGainingFocus = !HasFocus(); + GrabFocus(); + + bool bRet = WeldEditView::MouseButtonDown(rMEvt); + + mrDocView.GetViewFrame()->GetBindings().InvalidateAll(false); + + return bRet; +} + +bool SidebarTextControl::MouseButtonUp(const MouseEvent& rMEvt) +{ + bool bRet = WeldEditView::MouseButtonUp(rMEvt); + + if (mbMouseDownGainingFocus) + { + MakeVisible(); + mbMouseDownGainingFocus = false; + } + + return bRet; +} + +IMPL_LINK( SidebarTextControl, OnlineSpellCallback, SpellCallbackInfo&, rInfo, void ) +{ + if ( rInfo.nCommand == SpellCallbackCommand::STARTSPELLDLG ) + { + mrDocView.GetViewFrame()->GetDispatcher()->Execute( FN_SPELL_GRAMMAR_DIALOG, SfxCallMode::ASYNCHRON); + } +} + +bool SidebarTextControl::Command( const CommandEvent& rCEvt ) +{ + EditView* pEditView = GetEditView(); + + if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) + { + if (IsMouseCaptured()) + ReleaseMouse(); + if ( !mrSidebarWin.IsProtected() && + pEditView && + pEditView->IsWrongSpelledWordAtPos( rCEvt.GetMousePosPixel(), true )) + { + Link<SpellCallbackInfo&,void> aLink = LINK(this, SidebarTextControl, OnlineSpellCallback); + pEditView->ExecuteSpellPopup(rCEvt.GetMousePosPixel(), aLink); + } + else + { + Point aPos; + if (rCEvt.IsMouseEvent()) + aPos = rCEvt.GetMousePosPixel(); + else + { + const Size aSize = GetOutputSizePixel(); + aPos = Point( aSize.getWidth()/2, aSize.getHeight()/2 ); + } + SfxDispatcher::ExecutePopup(&mrSidebarWin, &aPos); + } + return true; + } + else if (rCEvt.GetCommand() == CommandEventId::Wheel) + { + // if no scrollbar, or extra keys held scroll the document and consume + // this event, otherwise don't consume and let the event get to the + // surrounding scrolled window + if (!mrSidebarWin.IsScrollbarVisible()) + { + mrDocView.HandleWheelCommands(rCEvt); + return true; + } + else + { + const CommandWheelData* pData = rCEvt.GetWheelData(); + if (pData->IsShift() || pData->IsMod1() || pData->IsMod2()) + { + mrDocView.HandleWheelCommands(rCEvt); + return true; + } + } + } + + return WeldEditView::Command(rCEvt); +} + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/SidebarTxtControl.hxx b/sw/source/uibase/docvw/SidebarTxtControl.hxx new file mode 100644 index 000000000..e0652aa5b --- /dev/null +++ b/sw/source/uibase/docvw/SidebarTxtControl.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#pragma once + +#include <svx/weldeditview.hxx> + +class OutlinerView; +class SwView; +class SwPostItMgr; +struct SpellCallbackInfo; +namespace sw::annotation { class SwAnnotationWin; } + +namespace sw::sidebarwindows { + +class SidebarTextControl : public WeldEditView +{ + private: + sw::annotation::SwAnnotationWin& mrSidebarWin; + SwView& mrDocView; + SwPostItMgr& mrPostItMgr; + bool mbMouseDownGainingFocus; + + void MakeVisible(); + + protected: + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; + + virtual bool Command(const CommandEvent& rCEvt) override; + virtual void GetFocus() override; + virtual void LoseFocus() override; + + virtual OUString RequestHelp(tools::Rectangle& rRect) override; + + public: + SidebarTextControl(sw::annotation::SwAnnotationWin& rSidebarWin, + SwView& rDocView, + SwPostItMgr& rPostItMgr); + + virtual EditView* GetEditView() const override; + + virtual EditEngine* GetEditEngine() const override; + + virtual void EditViewScrollStateChange() override; + + void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + + void SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark); + + virtual bool KeyInput(const KeyEvent& rKeyEvt) override; + virtual bool MouseButtonDown(const MouseEvent& rMEvt) override; + virtual bool MouseButtonUp(const MouseEvent& rMEvt) override; + + void SetMapMode(const MapMode& rNewMapMode) + { + OutputDevice& rDevice = GetDrawingArea()->get_ref_device(); + rDevice.SetMapMode(rNewMapMode); + } + + DECL_LINK( OnlineSpellCallback, SpellCallbackInfo&, void ); + + void DrawForPage(OutputDevice* pDev, const Point& rPos); +}; + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/SidebarWinAcc.cxx b/sw/source/uibase/docvw/SidebarWinAcc.cxx new file mode 100644 index 000000000..54c30f518 --- /dev/null +++ b/sw/source/uibase/docvw/SidebarWinAcc.cxx @@ -0,0 +1,142 @@ +/* -*- 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 "SidebarWinAcc.hxx" +#include <AnnotationWin.hxx> + +#include <viewsh.hxx> +#include <accmap.hxx> +#include <toolkit/awt/vclxaccessiblecomponent.hxx> + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <mutex> + +namespace sw::sidebarwindows { + +namespace { + +// declaration and implementation of accessible context for <SidebarWinAccessible> instance +class SidebarWinAccessibleContext : public VCLXAccessibleComponent +{ + public: + explicit SidebarWinAccessibleContext( sw::annotation::SwAnnotationWin& rSidebarWin, + SwViewShell& rViewShell, + const SwFrame* pAnchorFrame ) + : VCLXAccessibleComponent( dynamic_cast<VCLXWindow*>(rSidebarWin.CreateAccessible().get()) ) + , mrViewShell( rViewShell ) + , mpAnchorFrame( pAnchorFrame ) + { + rSidebarWin.SetAccessibleRole( css::accessibility::AccessibleRole::COMMENT ); + } + + void ChangeAnchor( const SwFrame* pAnchorFrame ) + { + std::scoped_lock aGuard(maMutex); + + mpAnchorFrame = pAnchorFrame; + } + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL + getAccessibleParent() override + { + std::scoped_lock aGuard(maMutex); + + css::uno::Reference< css::accessibility::XAccessible > xAccParent; + + if ( mpAnchorFrame && + mrViewShell.GetAccessibleMap() ) + { + xAccParent = mrViewShell.GetAccessibleMap()->GetContext( mpAnchorFrame, false ); + } + + return xAccParent; + } + + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent() override + { + std::scoped_lock aGuard(maMutex); + + sal_Int32 nIndex( -1 ); + + if ( mpAnchorFrame && GetWindow() && + mrViewShell.GetAccessibleMap() ) + { + nIndex = mrViewShell.GetAccessibleMap()->GetChildIndex( *mpAnchorFrame, + *GetWindow() ); + } + + return nIndex; + } + + private: + SwViewShell& mrViewShell; + const SwFrame* mpAnchorFrame; + + std::mutex maMutex; +}; + +} + +// implementation of accessible for <SwAnnotationWin> instance +SidebarWinAccessible::SidebarWinAccessible( sw::annotation::SwAnnotationWin& rSidebarWin, + SwViewShell& rViewShell, + const SwSidebarItem& rSidebarItem ) + : mrSidebarWin( rSidebarWin ) + , mrViewShell( rViewShell ) + , mpAnchorFrame( rSidebarItem.maLayoutInfo.mpAnchorFrame ) + , m_bAccContextCreated( false ) +{ + SetWindow( &mrSidebarWin ); +} + +SidebarWinAccessible::~SidebarWinAccessible() +{ +} + +void SidebarWinAccessible::ChangeSidebarItem( const SwSidebarItem& rSidebarItem ) +{ + if ( !m_bAccContextCreated ) + return; + + css::uno::Reference< css::accessibility::XAccessibleContext > xAcc + = getAccessibleContext(); + if ( xAcc.is() ) + { + SidebarWinAccessibleContext* pAccContext = + dynamic_cast<SidebarWinAccessibleContext*>(xAcc.get()); + if ( pAccContext ) + { + pAccContext->ChangeAnchor( rSidebarItem.maLayoutInfo.mpAnchorFrame ); + } + } +} + +css::uno::Reference< css::accessibility::XAccessibleContext > SidebarWinAccessible::CreateAccessibleContext() +{ + rtl::Reference<SidebarWinAccessibleContext> pAccContext = + new SidebarWinAccessibleContext( mrSidebarWin, + mrViewShell, + mpAnchorFrame ); + m_bAccContextCreated = true; + return pAccContext; +} + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/SidebarWinAcc.hxx b/sw/source/uibase/docvw/SidebarWinAcc.hxx new file mode 100644 index 000000000..5453a42c9 --- /dev/null +++ b/sw/source/uibase/docvw/SidebarWinAcc.hxx @@ -0,0 +1,53 @@ +/* -*- 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 . + */ + +#pragma once + +#include <toolkit/awt/vclxwindow.hxx> + +class SwViewShell; +class SwSidebarItem; +class SwFrame; +namespace sw::annotation { class SwAnnotationWin; } + +namespace sw::sidebarwindows { + +class SidebarWinAccessible : public VCLXWindow +{ + public: + explicit SidebarWinAccessible( sw::annotation::SwAnnotationWin& rSidebarWin, + SwViewShell& rViewShell, + const SwSidebarItem& rSidebarItem ); + virtual ~SidebarWinAccessible() override; + + virtual css::uno::Reference< css::accessibility::XAccessibleContext > + CreateAccessibleContext() override; + + void ChangeSidebarItem( const SwSidebarItem& rSidebarItem ); + + private: + sw::annotation::SwAnnotationWin& mrSidebarWin; + SwViewShell& mrViewShell; + const SwFrame* mpAnchorFrame; + bool m_bAccContextCreated; +}; + +} // end of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/UnfloatTableButton.cxx b/sw/source/uibase/docvw/UnfloatTableButton.cxx new file mode 100644 index 000000000..962140567 --- /dev/null +++ b/sw/source/uibase/docvw/UnfloatTableButton.cxx @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <UnfloatTableButton.hxx> +#include <HeaderFooterWin.hxx> + +#include <edtwin.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <strings.hrc> +#include <fmtpdsc.hxx> +#include <vcl/metric.hxx> +#include <vcl/settings.hxx> +#include <viewopt.hxx> +#include <frame.hxx> +#include <flyfrm.hxx> +#include <tabfrm.hxx> +#include <txtfrm.hxx> +#include <pagefrm.hxx> +#include <ndindex.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <unoprnms.hxx> +#include <unotbl.hxx> +#include <IDocumentState.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/processor2d/processorfromoutputdevice.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <svl/grabbagitem.hxx> +#include <doc.hxx> + +#define TEXT_PADDING 3 +#define BOX_DISTANCE 3 +#define BUTTON_WIDTH 12 + +UnfloatTableButton::UnfloatTableButton(SwEditWin* pEditWin, const SwFrame* pFrame) + : SwFrameMenuButtonBase(pEditWin, pFrame, "modules/swriter/ui/unfloatbutton.ui", + "UnfloatButton") + , m_xPushButton(m_xBuilder->weld_button("button")) + , m_sLabel(SwResId(STR_UNFLOAT_TABLE)) +{ + m_xPushButton->set_accessible_name(m_sLabel); + m_xVirDev = m_xPushButton->create_virtual_device(); + SetVirDevFont(); +} + +UnfloatTableButton::~UnfloatTableButton() { disposeOnce(); } + +void UnfloatTableButton::dispose() +{ + m_xPushButton.reset(); + m_xVirDev.disposeAndClear(); + SwFrameMenuButtonBase::dispose(); +} + +void UnfloatTableButton::SetOffset(Point aTopRightPixel) +{ + // Compute the text size and get the box position & size from it + tools::Rectangle aTextRect; + m_xVirDev->GetTextBoundRect(aTextRect, m_sLabel); + tools::Rectangle aTextPxRect = m_xVirDev->LogicToPixel(aTextRect); + FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->GetFont()); + Size aBoxSize(aTextPxRect.GetWidth() + BUTTON_WIDTH + TEXT_PADDING * 2, + aFontMetric.GetLineHeight() + TEXT_PADDING * 2); + + Point aBoxPos(aTopRightPixel.X() - aBoxSize.Width() - BOX_DISTANCE, aTopRightPixel.Y()); + + if (AllSettings::GetLayoutRTL()) + { + aBoxPos.setX(aTopRightPixel.X() + BOX_DISTANCE); + } + + // Set the position & Size of the window + SetPosSizePixel(aBoxPos, aBoxSize); + m_xVirDev->SetOutputSizePixel(aBoxSize); + + PaintButton(); +} + +void UnfloatTableButton::MouseButtonDown(const MouseEvent& /*rMEvt*/) +{ + assert(GetFrame()->IsFlyFrame()); + // const_cast is needed because of bad design of ISwFrameControl and derived classes + SwFlyFrame* pFlyFrame = const_cast<SwFlyFrame*>(static_cast<const SwFlyFrame*>(GetFrame())); + + // Find the table inside the text frame + SwTabFrame* pTableFrame = nullptr; + SwFrame* pLower = pFlyFrame->GetLower(); + while (pLower) + { + if (pLower->IsTabFrame()) + { + pTableFrame = static_cast<SwTabFrame*>(pLower); + break; + } + pLower = pLower->GetNext(); + } + + if (pTableFrame == nullptr) + return; + + // Insert the table at the position of the text node which has the frame anchored to + SwFrame* pAnchoreFrame = pFlyFrame->AnchorFrame(); + if (pAnchoreFrame == nullptr || !pAnchoreFrame->IsTextFrame()) + return; + + SwTextFrame* pTextFrame = static_cast<SwTextFrame*>(pAnchoreFrame); + if (pTextFrame->GetTextNodeFirst() == nullptr) + return; + + SwNodeIndex aInsertPos((*pTextFrame->GetTextNodeFirst())); + + SwTableNode* pTableNode = pTableFrame->GetTable()->GetTableNode(); + if (pTableNode == nullptr) + return; + + SwDoc& rDoc = pTextFrame->GetDoc(); + + // tdf#129176: clear "TablePosition" grab bag, since we explicitly change the position here + // See DomainMapperTableHandler::endTableGetTableStyle, where the grab bag is filled, and + // DocxAttributeOutput::TableDefinition that uses it on export + SwFrameFormat* pTableFormat = pTableFrame->GetTable()->GetFrameFormat(); + assert(pTableFormat); + if (const SfxGrabBagItem* pGrabBagItem = pTableFormat->GetAttrSet().GetItem(RES_FRMATR_GRABBAG)) + { + SfxGrabBagItem aGrabBagItem(*pGrabBagItem); // Editable copy + if (aGrabBagItem.GetGrabBag().erase("TablePosition")) + { + css::uno::Any aVal; + aGrabBagItem.QueryValue(aVal); + const auto xTable = SwXTextTable::CreateXTextTable(pTableFormat); + const css::uno::Reference<css::beans::XPropertySet> xSet(xTable, css::uno::UNO_QUERY); + assert(xSet); + xSet->setPropertyValue(UNO_NAME_TABLE_INTEROP_GRAB_BAG, aVal); + } + } + + // When we move the table before the first text node, we need to clear RES_PAGEDESC attribute + // of the text node otherwise LO will create a page break after the table + if (pTextFrame->GetTextNodeFirst()) + { + const SwPageDesc* pPageDesc + = pTextFrame->GetPageDescItem().GetPageDesc(); // First text node of the page has this + if (pPageDesc) + { + // First set the existing page desc for the table node + SfxItemSetFixed<RES_PAGEDESC, RES_PAGEDESC> aSet( + GetEditWin()->GetView().GetWrtShell().GetAttrPool()); + aSet.Put(SwFormatPageDesc(pPageDesc)); + SwPaM aPaMTable(*pTableNode); + rDoc.getIDocumentContentOperations().InsertItemSet( + aPaMTable, aSet, SetAttrMode::DEFAULT, GetPageFrame()->getRootFrame()); + + // Then remove pagedesc from the attributes of the text node + aSet.Put(SwFormatPageDesc(nullptr)); + SwPaM aPaMTextNode(*pTextFrame->GetTextNodeFirst()); + rDoc.getIDocumentContentOperations().InsertItemSet( + aPaMTextNode, aSet, SetAttrMode::DEFAULT, GetPageFrame()->getRootFrame()); + } + } + + // Move the table outside of the text frame + SwNodeRange aRange(*pTableNode, SwNodeOffset(0), *pTableNode->EndOfSectionNode(), + SwNodeOffset(1)); + rDoc.getIDocumentContentOperations().MoveNodeRange(aRange, aInsertPos, SwMoveFlags::DEFAULT); + + // Remove the floating table's frame + SwFlyFrameFormat* pFrameFormat = pFlyFrame->GetFormat(); + if (pFrameFormat) + { + rDoc.getIDocumentLayoutAccess().DelLayoutFormat(pFrameFormat); + } + + rDoc.getIDocumentState().SetModified(); + + // Undoing MoveNodeRange() is not working correctly in case of tables, it crashes sometimes + // So don't allow to undo after unfloating (similar to MakeFlyAndMove() method) + if (rDoc.GetIDocumentUndoRedo().DoesUndo()) + { + rDoc.GetIDocumentUndoRedo().DelAllUndoObj(); + } +} + +void UnfloatTableButton::PaintButton() +{ + if (!m_xVirDev) + return; + + m_xVirDev->SetMapMode(MapMode(MapUnit::MapPixel)); + drawinglayer::primitive2d::Primitive2DContainer aSeq; + const ::tools::Rectangle aRect( + ::tools::Rectangle(Point(0, 0), m_xVirDev->PixelToLogic(GetSizePixel()))); + + // Create button + SwFrameButtonPainter::PaintButton(aSeq, aRect, true); + + // Create the text primitive + basegfx::BColor aLineColor = SwViewOption::GetHeaderFooterMarkColor().getBColor(); + basegfx::B2DVector aFontSize; + drawinglayer::attribute::FontAttribute aFontAttr + = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, m_xVirDev->GetFont(), + false, false); + + FontMetric aFontMetric = m_xVirDev->GetFontMetric(m_xVirDev->GetFont()); + double nTextOffsetY = aFontMetric.GetAscent() + TEXT_PADDING; + double nTextOffsetX = std::abs(aRect.GetWidth() - m_xVirDev->GetTextWidth(m_sLabel)) / 2.0; + Point aTextPos(nTextOffsetX, nTextOffsetY); + + basegfx::B2DHomMatrix aTextMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), static_cast<double>(aTextPos.X()), + static_cast<double>(aTextPos.Y()))); + + aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), std::vector<double>(), aFontAttr, + css::lang::Locale(), aLineColor))); + + // Create the processor and process the primitives + const drawinglayer::geometry::ViewInformation2D aNewViewInfos; + std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor( + drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(*m_xVirDev, + aNewViewInfos)); + + pProcessor->process(aSeq); + + m_xPushButton->set_custom_button(m_xVirDev.get()); +} + +void UnfloatTableButton::ShowAll(bool bShow) { Show(bShow); } + +bool UnfloatTableButton::Contains(const Point& rDocPt) const +{ + ::tools::Rectangle aRect(GetPosPixel(), GetSizePixel()); + if (aRect.Contains(rDocPt)) + return true; + + return false; +} + +void UnfloatTableButton::SetReadonly(bool bReadonly) { ShowAll(!bReadonly); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/uibase/docvw/edtdd.cxx b/sw/source/uibase/docvw/edtdd.cxx new file mode 100644 index 000000000..8f86aa1d3 --- /dev/null +++ b/sw/source/uibase/docvw/edtdd.cxx @@ -0,0 +1,497 @@ +/* -*- 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 <svx/svdview.hxx> +#include <editeng/outliner.hxx> +#include <svx/svdobj.hxx> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <sfx2/bindings.hxx> +#include <vcl/commandevent.hxx> +#include <osl/diagnose.h> + +#include <sfx2/viewfrm.hxx> +#include <fmturl.hxx> +#include <frmfmt.hxx> +#include <wrtsh.hxx> +#include <edtdd.hxx> +#include <edtwin.hxx> +#include <view.hxx> +#include <viewopt.hxx> +#include <swdtflvr.hxx> +#include <swmodule.hxx> +#include <docsh.hxx> +#include <wdocsh.hxx> + +using namespace ::com::sun::star; + +// no include "dbgoutsw.hxx" here!!!!!! + +bool g_bExecuteDrag = false; + +void SwEditWin::StartDDTimer() +{ + m_aTimer.SetInvokeHandler(LINK(this, SwEditWin, DDHandler)); + m_aTimer.SetTimeout(480); + m_aTimer.Start(); + g_bDDTimerStarted = true; +} + +void SwEditWin::StopDDTimer(SwWrtShell *pSh, const Point &rPt) +{ + m_aTimer.Stop(); + g_bDDTimerStarted = false; + if(!pSh->IsSelFrameMode()) + pSh->CallSetCursor(&rPt, false); + m_aTimer.SetInvokeHandler(LINK(this,SwEditWin, TimerHandler)); +} + +void SwEditWin::StartDrag( sal_Int8 /*nAction*/, const Point& rPosPixel ) +{ + if (m_rView.GetObjectShell()->isContentExtractionLocked()) + return; + + SwWrtShell &rSh = m_rView.GetWrtShell(); + if( rSh.GetDrawView() ) + { + CommandEvent aDragEvent( rPosPixel, CommandEventId::StartDrag, true ); + if( rSh.GetDrawView()->Command( aDragEvent, this ) ) + { + m_rView.GetViewFrame()->GetBindings().InvalidateAll(false); + return; // Event evaluated by SdrView + } + } + + if ( m_pApplyTempl || rSh.IsDrawCreate() || IsDrawAction()) + return; + + bool bStart = false, bDelSelect = false; + SdrObject *pObj = nullptr; + Point aDocPos( PixelToLogic( rPosPixel ) ); + const bool bInSelect = rSh.IsInSelect(); + if (!bInSelect && rSh.TestCurrPam(aDocPos, true)) + //We are not selecting and aren't at a selection + bStart = true; + else if ( !g_bFrameDrag && rSh.IsSelFrameMode() && + rSh.IsInsideSelectedObj( aDocPos ) && + nullptr == m_pAnchorMarker) + { + //We are not dragging internally and are not at an + //object (frame, draw object) + + // #i106131# *and* AnchorDrag is *not* active: When active, + // entering global drag mode will destroy the AnchorHdl but + // keep the now invalid ptr in place, next access will crash. + // It is indeed wrong to enter drag mode when AnchorDrag is + // already active + bStart = true; + } + else if( !g_bFrameDrag && m_rView.GetDocShell()->IsReadOnly() && + OBJCNT_NONE != rSh.GetObjCntType( aDocPos, pObj )) + { + rSh.LockPaint(); + if( rSh.SelectObj( aDocPos, 0, pObj )) + bStart = bDelSelect = true; + else + rSh.UnlockPaint(); + } + else if (!bInSelect)// tdf#116384 only drag hyperlink if user's not currently setting the selection + { + SwContentAtPos aSwContentAtPos( IsAttrAtPos::InetAttr ); + bStart = rSh.GetContentAtPos( aDocPos, + aSwContentAtPos ); + } + + if ( !bStart || m_bIsInDrag ) + return; + + m_bMBPressed = false; + ReleaseMouse(); + g_bFrameDrag = false; + g_bExecuteDrag = true; + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + m_aMovePos = aDocPos; + StartExecuteDrag(); + if( bDelSelect ) + { + rSh.UnSelectFrame(); + rSh.UnlockPaint(); + } +} + +void SwEditWin::StartExecuteDrag() +{ + if( !g_bExecuteDrag || m_bIsInDrag ) + return; + + m_bIsInDrag = true; + + rtl::Reference<SwTransferable> pTransfer = new SwTransferable( m_rView.GetWrtShell() ); + + pTransfer->StartDrag( this, m_aMovePos ); +} + +void SwEditWin::DragFinished() +{ + DropCleanup(); + m_aTimer.SetInvokeHandler( LINK(this,SwEditWin, TimerHandler) ); + m_bIsInDrag = false; +} + +void SwEditWin::DropCleanup() +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + + // reset statuses + g_bNoInterrupt = false; + if ( m_bOldIdleSet ) + { + rSh.GetViewOptions()->SetIdle( m_bOldIdle ); + m_bOldIdleSet = false; + } + if ( m_pUserMarker ) + CleanupDropUserMarker(); + else + rSh.UnSetVisibleCursor(); + +} + +void SwEditWin::CleanupDropUserMarker() +{ + if ( m_pUserMarker ) + { + m_pUserMarker.reset(); + m_pUserMarkerObj = nullptr; + } +} + +//exhibition hack (MA,MBA) +void SwView::SelectShellForDrop() +{ + if ( !GetCurShell() ) + SelectShell(); +} + +sal_Int8 SwEditWin::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + GetView().SelectShellForDrop(); + DropCleanup(); + sal_Int8 nRet = DND_ACTION_NONE; + + //A Drop to an open OutlinerView doesn't concern us (also see QueryDrop) + SwWrtShell &rSh = m_rView.GetWrtShell(); + const Point aDocPt( PixelToLogic( rEvt.maPosPixel )); + SdrObject *pObj = nullptr; + OutlinerView* pOLV; + rSh.GetObjCntType( aDocPt, pObj ); + + if( pObj && nullptr != ( pOLV = rSh.GetDrawView()->GetTextEditOutlinerView() )) + { + tools::Rectangle aRect( pOLV->GetOutputArea() ); + aRect.Union( pObj->GetLogicRect() ); + const Point aPos = pOLV->GetWindow()->PixelToLogic(rEvt.maPosPixel); + if ( aRect.Contains(aPos) ) + { + rSh.StartAllAction(); + rSh.EndAllAction(); + return nRet; + } + } + + // There's a special treatment for file lists with a single + // element, that depends on the actual content of the + // Transferable to be accessible. Since the transferable + // may only be accessed after the drop has been accepted + // (according to KA due to Java D&D), we'll have to + // reevaluate the drop action once more _with_ the + // Transferable. + sal_uInt8 nEventAction; + sal_Int8 nUserOpt = rEvt.mbDefault ? EXCHG_IN_ACTION_DEFAULT + : rEvt.mnAction; + SotExchangeActionFlags nActionFlags; + m_nDropAction = SotExchange::GetExchangeAction( + GetDataFlavorExVector(), + m_nDropDestination, + rEvt.mnAction, + nUserOpt, m_nDropFormat, nEventAction, SotClipboardFormatId::NONE, + &rEvt.maDropEvent.Transferable, + &nActionFlags ); + + TransferableDataHelper aData( rEvt.maDropEvent.Transferable ); + nRet = rEvt.mnAction; + if( !SwTransferable::PasteData( aData, rSh, m_nDropAction, nActionFlags, m_nDropFormat, + m_nDropDestination, false, rEvt.mbDefault, &aDocPt, nRet)) + nRet = DND_ACTION_NONE; + else if ( SW_MOD()->m_pDragDrop ) + //Don't clean up anymore at internal D&D! + SW_MOD()->m_pDragDrop->SetCleanUp( false ); + + return nRet; +} + +SotExchangeDest SwEditWin::GetDropDestination( const Point& rPixPnt, SdrObject ** ppObj ) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + const Point aDocPt( PixelToLogic( rPixPnt ) ); + if( rSh.TestCurrPam( aDocPt ) + || rSh.IsOverReadOnlyPos( aDocPt ) + || rSh.DocPtInsideInputField( aDocPt ) ) + return SotExchangeDest::NONE; + + SdrObject *pObj = nullptr; + const ObjCntType eType = rSh.GetObjCntType( aDocPt, pObj ); + + //Drop to OutlinerView (TextEdit in Drawing) should decide it on its own! + if( pObj ) + { + OutlinerView* pOLV = rSh.GetDrawView()->GetTextEditOutlinerView(); + if ( pOLV ) + { + tools::Rectangle aRect( pOLV->GetOutputArea() ); + aRect.Union( pObj->GetLogicRect() ); + const Point aPos = pOLV->GetWindow()->PixelToLogic( rPixPnt ); + if( aRect.Contains( aPos ) ) + return SotExchangeDest::NONE; + } + } + + //What do we want to drop on now? + SotExchangeDest nDropDestination = SotExchangeDest::NONE; + + //Did anything else arrive from the DrawingEngine? + if( OBJCNT_NONE != eType ) + { + switch ( eType ) + { + case OBJCNT_GRF: + { + bool bLink, + bIMap = nullptr != rSh.GetFormatFromObj( aDocPt )->GetURL().GetMap(); + OUString aDummy; + rSh.GetGrfAtPos( aDocPt, aDummy, bLink ); + if ( bLink && bIMap ) + nDropDestination = SotExchangeDest::DOC_LNKD_GRAPH_W_IMAP; + else if ( bLink ) + nDropDestination = SotExchangeDest::DOC_LNKD_GRAPHOBJ; + else if ( bIMap ) + nDropDestination = SotExchangeDest::DOC_GRAPH_W_IMAP; + else + nDropDestination = SotExchangeDest::DOC_GRAPHOBJ; + } + break; + case OBJCNT_FLY: + if( dynamic_cast< const SwWebDocShell *>( rSh.GetView().GetDocShell() ) != nullptr ) + nDropDestination = SotExchangeDest::DOC_TEXTFRAME_WEB; + else + nDropDestination = SotExchangeDest::DOC_TEXTFRAME; + break; + case OBJCNT_OLE: nDropDestination = SotExchangeDest::DOC_OLEOBJ; break; + case OBJCNT_CONTROL: /* no Action avail */ + case OBJCNT_SIMPLE: nDropDestination = SotExchangeDest::DOC_DRAWOBJ; break; + case OBJCNT_URLBUTTON: nDropDestination = SotExchangeDest::DOC_URLBUTTON; break; + case OBJCNT_GROUPOBJ: nDropDestination = SotExchangeDest::DOC_GROUPOBJ; break; + + default: OSL_ENSURE( false, "new ObjectType?" ); + } + } + if ( nDropDestination == SotExchangeDest::NONE ) + { + if( dynamic_cast< const SwWebDocShell *>( rSh.GetView().GetDocShell() ) != nullptr ) + nDropDestination = SotExchangeDest::SWDOC_FREE_AREA_WEB; + else + nDropDestination = SotExchangeDest::SWDOC_FREE_AREA; + } + if( ppObj ) + *ppObj = pObj; + return nDropDestination; +} + +sal_Int8 SwEditWin::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + if( rEvt.mbLeaving ) + { + DropCleanup(); + return rEvt.mnAction; + } + + if( m_rView.GetDocShell()->IsReadOnly() ) + return DND_ACTION_NONE; + + SwWrtShell &rSh = m_rView.GetWrtShell(); + + Point aPixPt( rEvt.maPosPixel ); + + // If the cursor is near the inner boundary + // we attempt to scroll towards the desired direction. + tools::Rectangle aWin(Point(), GetOutputSizePixel()); + const int nMargin = 10; + aWin.AdjustLeft(nMargin ); + aWin.AdjustTop(nMargin ); + aWin.AdjustRight( -nMargin ); + aWin.AdjustBottom( -nMargin ); + if(!aWin.Contains(aPixPt)) { + static sal_uInt64 last_tick = 0; + sal_uInt64 current_tick = tools::Time::GetSystemTicks(); + if((current_tick-last_tick) > 500) { + last_tick = current_tick; + if(!m_bOldIdleSet) { + m_bOldIdle = rSh.GetViewOptions()->IsIdle(); + rSh.GetViewOptions()->SetIdle(false); + m_bOldIdleSet = true; + } + CleanupDropUserMarker(); + if(aPixPt.X() > aWin.Right()) aPixPt.AdjustX(nMargin ); + if(aPixPt.X() < aWin.Left()) aPixPt.AdjustX( -nMargin ); + if(aPixPt.Y() > aWin.Bottom()) aPixPt.AdjustY(nMargin ); + if(aPixPt.Y() < aWin.Top()) aPixPt.AdjustY( -nMargin ); + Point aDocPt(PixelToLogic(aPixPt)); + SwRect rect(aDocPt,Size(1,1)); + rSh.MakeVisible(rect); + } + } + + if(m_bOldIdleSet) { + rSh.GetViewOptions()->SetIdle( m_bOldIdle ); + m_bOldIdleSet = false; + } + + SdrObject *pObj = nullptr; + m_nDropDestination = GetDropDestination( aPixPt, &pObj ); + if( m_nDropDestination == SotExchangeDest::NONE ) + return DND_ACTION_NONE; + + sal_uInt8 nEventAction; + sal_Int8 nUserOpt = rEvt.mbDefault ? EXCHG_IN_ACTION_DEFAULT + : rEvt.mnAction; + + m_nDropAction = SotExchange::GetExchangeAction( + GetDataFlavorExVector(), + m_nDropDestination, + rEvt.mnAction, + nUserOpt, m_nDropFormat, nEventAction ); + + if( EXCHG_INOUT_ACTION_NONE != m_nDropAction ) + { + const Point aDocPt( PixelToLogic( aPixPt ) ); + + //With the default action we still want to have a say. + SwModule *pMod = SW_MOD(); + if( pMod->m_pDragDrop ) + { + bool bCleanup = false; + //Drawing objects in Headers/Footers are not allowed + + SwWrtShell *pSrcSh = pMod->m_pDragDrop->GetShell(); + if( (pSrcSh->GetSelFrameType() == FrameTypeFlags::DRAWOBJ) && + pSrcSh->IsSelContainsControl() && + (rSh.GetFrameType( &aDocPt, false ) & (FrameTypeFlags::HEADER|FrameTypeFlags::FOOTER)) ) + { + bCleanup = true; + } + // don't more position protected objects! + else if( DND_ACTION_MOVE == rEvt.mnAction && + pSrcSh->IsSelObjProtected( FlyProtectFlags::Pos ) != FlyProtectFlags::NONE ) + { + bCleanup = true; + } + else if( rEvt.mbDefault ) + { + // internal Drag&Drop: within same Doc a Move + // otherwise a Copy - Task 54974 + nEventAction = pSrcSh->GetDoc() == rSh.GetDoc() + ? DND_ACTION_MOVE + : DND_ACTION_COPY; + } + if ( bCleanup ) + { + CleanupDropUserMarker(); + rSh.UnSetVisibleCursor(); + return DND_ACTION_NONE; + } + } + else + { + //D&D from outside of SW should be a Copy per default. + if( EXCHG_IN_ACTION_DEFAULT == nEventAction && + DND_ACTION_MOVE == rEvt.mnAction ) + nEventAction = DND_ACTION_COPY; + + if( (SotClipboardFormatId::SBA_FIELDDATAEXCHANGE == m_nDropFormat && + EXCHG_IN_ACTION_LINK == m_nDropAction) || + SotClipboardFormatId::SBA_CTRLDATAEXCHANGE == m_nDropFormat ) + { + SdrMarkView* pMView = rSh.GetDrawView(); + if( pMView && !pMView->IsDesignMode() ) + return DND_ACTION_NONE; + } + } + + if ( EXCHG_IN_ACTION_DEFAULT != nEventAction ) + nUserOpt = static_cast<sal_Int8>(nEventAction); + + // show DropCursor or UserMarker ? + if( SotExchangeDest::SWDOC_FREE_AREA_WEB == m_nDropDestination || + SotExchangeDest::SWDOC_FREE_AREA == m_nDropDestination ) + { + CleanupDropUserMarker(); + SwContentAtPos aCont( IsAttrAtPos::ContentCheck ); + if(rSh.GetContentAtPos(aDocPt, aCont)) + rSh.SwCursorShell::SetVisibleCursor( aDocPt ); + } + else + { + rSh.UnSetVisibleCursor(); + + if ( m_pUserMarkerObj != pObj ) + { + CleanupDropUserMarker(); + m_pUserMarkerObj = pObj; + + if(m_pUserMarkerObj) + { + m_pUserMarker.reset(new SdrDropMarkerOverlay( *rSh.GetDrawView(), *m_pUserMarkerObj )); + } + } + } + return nUserOpt; + } + + CleanupDropUserMarker(); + rSh.UnSetVisibleCursor(); + return DND_ACTION_NONE; +} + +IMPL_LINK_NOARG(SwEditWin, DDHandler, Timer *, void) +{ + g_bDDTimerStarted = false; + m_aTimer.Stop(); + m_aTimer.SetTimeout(240); + m_bMBPressed = false; + ReleaseMouse(); + g_bFrameDrag = false; + + if ( m_rView.GetViewFrame() ) + { + g_bExecuteDrag = true; + StartExecuteDrag(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx new file mode 100644 index 000000000..ef7070fd0 --- /dev/null +++ b/sw/source/uibase/docvw/edtwin.cxx @@ -0,0 +1,6845 @@ +/* -*- 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 <config_wasm_strip.h> + +#include <swtypes.hxx> +#include <hintids.hxx> + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/awt/PopupMenuDirection.hpp> +#include <com/sun/star/awt/XPopupMenu.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/InputSequenceCheckMode.hpp> +#include <com/sun/star/i18n/UnicodeScript.hpp> +#include <com/sun/star/i18n/XExtendedInputSequenceChecker.hpp> +#include <com/sun/star/ui/ContextMenuExecuteEvent.hpp> + +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> + +#include <vcl/dialoghelper.hxx> +#include <vcl/inputctx.hxx> +#include <vcl/help.hxx> +#include <vcl/weld.hxx> +#include <vcl/ptrstyle.hxx> +#include <svl/macitem.hxx> +#include <unotools/securityoptions.hxx> +#include <basic/sbxvar.hxx> +#include <svl/ctloptions.hxx> +#include <basic/sbx.hxx> +#include <svl/eitem.hxx> +#include <svl/stritem.hxx> +#include <sfx2/ipclient.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/request.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <svl/ptitem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/langitem.hxx> +#include <svx/svdview.hxx> +#include <svx/svdhdl.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/svxacorr.hxx> +#include <editeng/flditem.hxx> +#include <editeng/colritem.hxx> +#include <unotools/charclass.hxx> +#include <unotools/datetime.hxx> + +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> + +#include <editeng/acorrcfg.hxx> +#include <SwSmartTagMgr.hxx> +#include <edtdd.hxx> +#include <edtwin.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentUndoRedo.hxx> +#include <textboxhelper.hxx> +#include <dcontact.hxx> +#include <fldbas.hxx> +#include <swmodule.hxx> +#include <docsh.hxx> +#include <viewopt.hxx> +#include <drawbase.hxx> +#include <dselect.hxx> +#include <textsh.hxx> +#include <shdwcrsr.hxx> +#include <txatbase.hxx> +#include <fmtanchr.hxx> +#include <fmtornt.hxx> +#include <fmthdft.hxx> +#include <frmfmt.hxx> +#include <modcfg.hxx> +#include <fmtcol.hxx> +#include <wview.hxx> +#include <gloslst.hxx> +#include <inputwin.hxx> +#include <gloshdl.hxx> +#include <swundo.hxx> +#include <drwtxtsh.hxx> +#include <fchrfmt.hxx> +#include "romenu.hxx" +#include <initui.hxx> +#include <frmatr.hxx> +#include <extinput.hxx> +#include <acmplwrd.hxx> +#include <swcalwrp.hxx> +#include <swdtflvr.hxx> +#include <breakit.hxx> +#include <checkit.hxx> +#include <pagefrm.hxx> + +#include <helpids.h> +#include <cmdid.h> +#include <uitool.hxx> +#include <fmtfollowtextflow.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <charfmt.hxx> +#include <numrule.hxx> +#include <pagedesc.hxx> +#include <svtools/ruler.hxx> +#include <formatclipboard.hxx> +#include <vcl/svapp.hxx> +#include <wordcountdialog.hxx> +#include <fmtfld.hxx> + +#include <IMark.hxx> +#include <doc.hxx> +#include <xmloff/odffields.hxx> + +#include <PostItMgr.hxx> +#include <FrameControlsManager.hxx> +#include <AnnotationWin.hxx> + +#include <algorithm> +#include <vector> + +#include <rootfrm.hxx> + +#include <unotools/syslocaleoptions.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <salhelper/singletonref.hxx> +#include <sfx2/event.hxx> +#include <memory> + +#include "../../core/crsr/callnk.hxx" +#include <IDocumentOutlineNodes.hxx> +#include <ndtxt.hxx> +#include <cntfrm.hxx> +#include <txtfrm.hxx> +#include <strings.hrc> +#include <textcontentcontrol.hxx> +#include <contentcontrolbutton.hxx> + +using namespace sw::mark; +using namespace ::com::sun::star; + +/** + * Globals + */ +static bool g_bInputLanguageSwitched = false; + +// Usually in MouseButtonUp a selection is revoked when the selection is +// not currently being pulled open. Unfortunately in MouseButtonDown there +// is being selected at double/triple click. That selection is completely +// finished in the Handler and thus can't be distinguished in the Up. +// To resolve this g_bHoldSelection is set in Down and evaluated in Up. +static bool g_bHoldSelection = false; + +bool g_bFrameDrag = false; +static bool g_bValidCursorPos = false; +static bool g_bModePushed = false; +bool g_bDDTimerStarted = false; +bool g_bDDINetAttr = false; +static SdrHdlKind g_eSdrMoveHdl = SdrHdlKind::User; + +QuickHelpData* SwEditWin::s_pQuickHlpData = nullptr; + +tools::Long SwEditWin::s_nDDStartPosY = 0; +tools::Long SwEditWin::s_nDDStartPosX = 0; + +static SfxShell* lcl_GetTextShellFromDispatcher( SwView const & rView ); + +/// Check if the selected shape has a TextBox: if so, go into that instead. +static bool lcl_goIntoTextBox(SwEditWin& rEditWin, SwWrtShell& rSh) +{ + SdrMark* pMark = rSh.GetDrawView()->GetMarkedObjectList().GetMark(0); + if (!pMark) + return false; + + SdrObject* pSdrObject = pMark->GetMarkedSdrObj(); + SwFrameFormat* pObjectFormat = ::FindFrameFormat(pSdrObject); + if (SwFrameFormat* pTextBoxFormat = SwTextBoxHelper::getOtherTextBoxFormat(pObjectFormat, RES_DRAWFRMFMT)) + { + SdrObject* pTextBox = pTextBoxFormat->FindRealSdrObject(); + SdrView* pSdrView = rSh.GetDrawView(); + // Unmark the shape. + pSdrView->UnmarkAllObj(); + // Mark the textbox. + rSh.SelectObj(Point(), SW_ALLOW_TEXTBOX, pTextBox); + // Clear the DrawFuncPtr. + rEditWin.StopInsFrame(); + return true; + } + return false; +} + +class SwAnchorMarker +{ + SdrHdl* m_pHdl; + Point m_aHdlPos; + Point m_aLastPos; + bool m_bTopRightHandle; +public: + explicit SwAnchorMarker( SdrHdl* pH ) + : m_pHdl( pH ) + , m_aHdlPos( pH->GetPos() ) + , m_aLastPos( pH->GetPos() ) + , m_bTopRightHandle( pH->GetKind() == SdrHdlKind::Anchor_TR ) + {} + const Point& GetLastPos() const { return m_aLastPos; } + void SetLastPos( const Point& rNew ) { m_aLastPos = rNew; } + void SetPos( const Point& rNew ) { m_pHdl->SetPos( rNew ); } + const Point& GetHdlPos() const { return m_aHdlPos; } + SdrHdl* GetHdl() const { return m_pHdl; } + void ChgHdl( SdrHdl* pNew ) + { + m_pHdl = pNew; + if ( m_pHdl ) + { + m_bTopRightHandle = (m_pHdl->GetKind() == SdrHdlKind::Anchor_TR); + } + } + Point GetPosForHitTest( const OutputDevice& rOut ) + { + Point aHitTestPos( m_pHdl->GetPos() ); + aHitTestPos = rOut.LogicToPixel( aHitTestPos ); + if ( m_bTopRightHandle ) + { + aHitTestPos += Point( -1, 1 ); + } + else + { + aHitTestPos += Point( 1, 1 ); + } + aHitTestPos = rOut.PixelToLogic( aHitTestPos ); + + return aHitTestPos; + } +}; + +/// Assists with auto-completion of AutoComplete words and AutoText names. +struct QuickHelpData +{ + /// Strings that at least partially match an input word, and match length. + std::vector<std::pair<OUString, sal_uInt16>> m_aHelpStrings; + /// Index of the current help string. + sal_uInt16 nCurArrPos; + static constexpr sal_uInt16 nNoPos = std::numeric_limits<sal_uInt16>::max(); + + /// Help data stores AutoText names rather than AutoComplete words. + bool m_bIsAutoText; + /// Display help string as a tip rather than inline. + bool m_bIsTip; + /// Tip ID when a help string is displayed as a tip. + void* nTipId; + /// Append a space character to the displayed help string (if appropriate). + bool m_bAppendSpace; + + /// Help string is currently displayed. + bool m_bIsDisplayed; + + QuickHelpData() { ClearContent(); } + + void Move( QuickHelpData& rCpy ); + void ClearContent(); + void Start(SwWrtShell& rSh, bool bRestart); + void Stop( SwWrtShell& rSh ); + + bool HasContent() const { return !m_aHelpStrings.empty() && nCurArrPos != nNoPos; } + const OUString& CurStr() const { return m_aHelpStrings[nCurArrPos].first; } + sal_uInt16 CurLen() const { return m_aHelpStrings[nCurArrPos].second; } + + /// Next help string. + void Next( bool bEndLess ) + { + if( ++nCurArrPos >= m_aHelpStrings.size() ) + nCurArrPos = (bEndLess && !m_bIsAutoText ) ? 0 : nCurArrPos-1; + } + /// Previous help string. + void Previous( bool bEndLess ) + { + if( 0 == nCurArrPos-- ) + nCurArrPos = (bEndLess && !m_bIsAutoText ) ? m_aHelpStrings.size()-1 : 0; + } + + // Fills internal structures with hopefully helpful information. + void FillStrArr( SwWrtShell const & rSh, const OUString& rWord ); + void SortAndFilter(const OUString &rOrigWord); +}; + +/** + * Avoid minimal movement shiver + */ +#define HIT_PIX 2 /* hit tolerance in pixel */ +#define MIN_MOVE 4 + +static bool IsMinMove(const Point &rStartPos, const Point &rLPt) +{ + return std::abs(rStartPos.X() - rLPt.X()) > MIN_MOVE || + std::abs(rStartPos.Y() - rLPt.Y()) > MIN_MOVE; +} + +/** + * For MouseButtonDown - determine whether a DrawObject + * a NO SwgFrame was hit! Shift/Ctrl should only result + * in selecting, with DrawObjects; at SwgFlys to trigger + * hyperlinks if applicable (Download/NewWindow!) + */ +static bool IsDrawObjSelectable( const SwWrtShell& rSh, const Point& rPt ) +{ + bool bRet = true; + SdrObject* pObj; + switch( rSh.GetObjCntType( rPt, pObj )) + { + case OBJCNT_NONE: + case OBJCNT_FLY: + case OBJCNT_GRF: + case OBJCNT_OLE: + bRet = false; + break; + default:; //prevent warning + } + return bRet; +} + +/* + * Switch pointer + */ +void SwEditWin::UpdatePointer(const Point &rLPt, sal_uInt16 nModifier ) +{ + SetQuickHelpText(OUString()); + SwWrtShell &rSh = m_rView.GetWrtShell(); + if( m_pApplyTempl ) + { + PointerStyle eStyle = PointerStyle::Fill; + if ( rSh.IsOverReadOnlyPos( rLPt ) ) + { + m_pUserMarker.reset(); + + eStyle = PointerStyle::NotAllowed; + } + else + { + SwRect aRect; + SwRect* pRect = &aRect; + const SwFrameFormat* pFormat = nullptr; + + bool bFrameIsValidTarget = false; + if( m_pApplyTempl->m_pFormatClipboard ) + bFrameIsValidTarget = m_pApplyTempl->m_pFormatClipboard->HasContentForThisType( SelectionType::Frame ); + else if( !m_pApplyTempl->nColor ) + bFrameIsValidTarget = ( m_pApplyTempl->eType == SfxStyleFamily::Frame ); + + if( bFrameIsValidTarget && + nullptr !=(pFormat = rSh.GetFormatFromObj( rLPt, &pRect )) && + dynamic_cast<const SwFlyFrameFormat*>( pFormat) ) + { + //turn on highlight for frame + tools::Rectangle aTmp( pRect->SVRect() ); + + if ( !m_pUserMarker ) + { + m_pUserMarker.reset(new SdrDropMarkerOverlay( *rSh.GetDrawView(), aTmp )); + } + } + else + { + m_pUserMarker.reset(); + } + + rSh.SwCursorShell::SetVisibleCursor( rLPt ); + } + SetPointer( eStyle ); + return; + } + + if( !rSh.VisArea().Width() ) + return; + + CurrShell aCurr(&rSh); + + if ( IsChainMode() ) + { + SwRect aRect; + SwChainRet nChainable = rSh.Chainable( aRect, *rSh.GetFlyFrameFormat(), rLPt ); + PointerStyle eStyle = nChainable != SwChainRet::OK + ? PointerStyle::ChainNotAllowed : PointerStyle::Chain; + if ( nChainable == SwChainRet::OK ) + { + tools::Rectangle aTmp( aRect.SVRect() ); + + if ( !m_pUserMarker ) + { + m_pUserMarker.reset(new SdrDropMarkerOverlay( *rSh.GetDrawView(), aTmp )); + } + } + else + { + m_pUserMarker.reset(); + } + + SetPointer( eStyle ); + return; + } + + bool bExecHyperlinks = m_rView.GetDocShell()->IsReadOnly(); + if ( !bExecHyperlinks ) + { + const bool bSecureOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink ); + if ( ( bSecureOption && nModifier == KEY_MOD1 ) || + ( !bSecureOption && nModifier != KEY_MOD1 ) ) + bExecHyperlinks = true; + } + + const bool bExecSmarttags = nModifier == KEY_MOD1; + + SdrView *pSdrView = rSh.GetDrawView(); + bool bPrefSdrPointer = false; + bool bHitHandle = false; + bool bCntAtPos = false; + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() && + rSh.IsCursorReadonly(); + m_aActHitType = SdrHitKind::NONE; + PointerStyle eStyle = PointerStyle::Text; + if ( !pSdrView ) + bCntAtPos = true; + else if ( (bHitHandle = (pSdrView->PickHandle(rLPt) != nullptr)) ) + { + m_aActHitType = SdrHitKind::Object; + bPrefSdrPointer = true; + } + else + { + const bool bNotInSelObj = !rSh.IsInsideSelectedObj( rLPt ); + if ( m_rView.GetDrawFuncPtr() && !m_bInsDraw && bNotInSelObj ) + { + m_aActHitType = SdrHitKind::Object; + if (IsObjectSelect()) + eStyle = PointerStyle::Arrow; + else + bPrefSdrPointer = true; + } + else + { + SdrPageView* pPV = nullptr; + pSdrView->SetHitTolerancePixel( HIT_PIX ); + SdrObject* pObj = (bNotInSelObj && bExecHyperlinks) ? + pSdrView->PickObj(rLPt, pSdrView->getHitTolLog(), pPV, SdrSearchOptions::PICKMACRO) : + nullptr; + if (pObj) + { + SdrObjMacroHitRec aTmp; + aTmp.aPos = rLPt; + aTmp.pPageView = pPV; + SetPointer( pObj->GetMacroPointer( aTmp ) ); + return; + } + else + { + // dvo: IsObjSelectable() eventually calls SdrView::PickObj, so + // apparently this is used to determine whether this is a + // drawling layer object or not. + if ( rSh.IsObjSelectable( rLPt ) ) + { + if (pSdrView->IsTextEdit()) + { + m_aActHitType = SdrHitKind::NONE; + bPrefSdrPointer = true; + } + else + { + SdrViewEvent aVEvt; + SdrHitKind eHit = pSdrView->PickAnything(rLPt, aVEvt); + + if (eHit == SdrHitKind::UrlField && bExecHyperlinks) + { + m_aActHitType = SdrHitKind::Object; + bPrefSdrPointer = true; + } + else + { + // if we're over a selected object, we show an + // ARROW by default. We only show a MOVE if 1) the + // object is selected, and 2) it may be moved + // (i.e., position is not protected). + bool bMovable = + (!bNotInSelObj) && + (rSh.IsObjSelected() || rSh.IsFrameSelected()) && + (rSh.IsSelObjProtected(FlyProtectFlags::Pos) == FlyProtectFlags::NONE); + + SdrObject* pSelectableObj = rSh.GetObjAt(rLPt); + // Don't update pointer if this is a background image only. + if (pSelectableObj->GetLayer() != rSh.GetDoc()->getIDocumentDrawModelAccess().GetHellId()) + eStyle = bMovable ? PointerStyle::Move : PointerStyle::Arrow; + m_aActHitType = SdrHitKind::Object; + } + } + } + else + { + if ( rSh.IsFrameSelected() && !bNotInSelObj ) + { + // dvo: this branch appears to be dead and should be + // removed in a future version. Reason: The condition + // !bNotInSelObj means that this branch will only be + // executed in the cursor points inside a selected + // object. However, if this is the case, the previous + // if( rSh.IsObjSelectable(rLPt) ) must always be true: + // rLPt is inside a selected object, then obviously + // rLPt is over a selectable object. + if (rSh.IsSelObjProtected(FlyProtectFlags::Size) != FlyProtectFlags::NONE) + eStyle = PointerStyle::NotAllowed; + else + eStyle = PointerStyle::Move; + m_aActHitType = SdrHitKind::Object; + } + else + { + if ( m_rView.GetDrawFuncPtr() ) + bPrefSdrPointer = true; + else + bCntAtPos = true; + } + } + } + } + } + if ( bPrefSdrPointer ) + { + if (bIsDocReadOnly || (rSh.IsObjSelected() && rSh.IsSelObjProtected(FlyProtectFlags::Content) != FlyProtectFlags::NONE)) + SetPointer( PointerStyle::NotAllowed ); + else + { + if (m_rView.GetDrawFuncPtr() && m_rView.GetDrawFuncPtr()->IsInsertForm() && !bHitHandle) + SetPointer( PointerStyle::DrawRect ); + else + SetPointer( pSdrView->GetPreferredPointer( rLPt, rSh.GetOut() ) ); + } + } + else + { + if( !rSh.IsPageAtPos( rLPt ) || m_pAnchorMarker ) + eStyle = PointerStyle::Arrow; + else + { + // Even if we already have something, prefer URLs if possible. + SwContentAtPos aUrlPos(IsAttrAtPos::InetAttr); + if (bCntAtPos || rSh.GetContentAtPos(rLPt, aUrlPos)) + { + SwContentAtPos aSwContentAtPos( + IsAttrAtPos::Field | + IsAttrAtPos::ClickField | + IsAttrAtPos::InetAttr | + IsAttrAtPos::Ftn | + IsAttrAtPos::SmartTag); + if( rSh.GetContentAtPos( rLPt, aSwContentAtPos) ) + { + // Is edit inline input field + if (IsAttrAtPos::Field == aSwContentAtPos.eContentAtPos + && aSwContentAtPos.pFndTextAttr != nullptr + && aSwContentAtPos.pFndTextAttr->Which() == RES_TXTATR_INPUTFIELD) + { + const SwField *pCursorField = rSh.CursorInsideInputField() ? rSh.GetCurField( true ) : nullptr; + if (!(pCursorField && pCursorField == aSwContentAtPos.pFndTextAttr->GetFormatField().GetField())) + eStyle = PointerStyle::RefHand; + } + else + { + const bool bClickToFollow = IsAttrAtPos::InetAttr == aSwContentAtPos.eContentAtPos || + IsAttrAtPos::SmartTag == aSwContentAtPos.eContentAtPos; + if( !bClickToFollow || + (IsAttrAtPos::InetAttr == aSwContentAtPos.eContentAtPos && bExecHyperlinks) || + (IsAttrAtPos::SmartTag == aSwContentAtPos.eContentAtPos && bExecSmarttags) ) + eStyle = PointerStyle::RefHand; + } + } + else if (GetView().GetWrtShell().GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + aSwContentAtPos.eContentAtPos = IsAttrAtPos::Outline; + if (rSh.GetContentAtPos(rLPt, aSwContentAtPos)) + { + if (IsAttrAtPos::Outline == aSwContentAtPos.eContentAtPos) + { + if (nModifier == KEY_MOD1) + { + eStyle = PointerStyle::RefHand; + // set quick help + if(aSwContentAtPos.aFnd.pNode && aSwContentAtPos.aFnd.pNode->IsTextNode()) + { + const SwNodes& rNds = GetView().GetWrtShell().GetDoc()->GetNodes(); + SwOutlineNodes::size_type nPos; + rNds.GetOutLineNds().Seek_Entry(aSwContentAtPos.aFnd.pNode->GetTextNode(), &nPos); + SwOutlineNodes::size_type nOutlineNodesCount + = rSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); + int nLevel = rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(nPos); + OUString sQuickHelp(SwResId(STR_CLICK_OUTLINE_CONTENT_TOGGLE_VISIBILITY)); + if (!rSh.GetViewOptions()->IsTreatSubOutlineLevelsAsContent() + && nPos + 1 < nOutlineNodesCount + && rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(nPos + 1) > nLevel) + sQuickHelp += " (" + SwResId(STR_CLICK_OUTLINE_CONTENT_TOGGLE_VISIBILITY_EXT) + ")"; + SetQuickHelpText(sQuickHelp); + } + } + } + } + } + } + } + + // which kind of text pointer have we to show - horz / vert - ? + if( PointerStyle::Text == eStyle && rSh.IsInVerticalText( &rLPt )) + eStyle = PointerStyle::TextVertical; + else if (rSh.GetViewOptions()->CanHideWhitespace() && + rSh.GetLayout()->IsBetweenPages(rLPt)) + { + if (rSh.GetViewOptions()->IsHideWhitespaceMode()) + eStyle = PointerStyle::ShowWhitespace; + else + eStyle = PointerStyle::HideWhitespace; + } + + SetPointer( eStyle ); + } +} + +/** + * Increase timer for selection + */ +IMPL_LINK_NOARG(SwEditWin, TimerHandler, Timer *, void) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + Point aModPt( m_aMovePos ); + const SwRect aOldVis( rSh.VisArea() ); + bool bDone = false; + + if ( !rSh.VisArea().Contains( aModPt ) ) + { + if ( m_bInsDraw ) + { + const int nMaxScroll = 40; + m_rView.Scroll( tools::Rectangle(aModPt,Size(1,1)), nMaxScroll, nMaxScroll); + bDone = true; + } + else if ( g_bFrameDrag ) + { + rSh.Drag(&aModPt, false); + bDone = true; + } + if ( !bDone ) + aModPt = rSh.GetContentPos( aModPt,aModPt.Y() > rSh.VisArea().Bottom() ); + } + if ( !bDone && !(g_bFrameDrag || m_bInsDraw) ) + { + if ( m_xRowColumnSelectionStart ) + { + Point aPos( aModPt ); + rSh.SelectTableRowCol( *m_xRowColumnSelectionStart, &aPos, m_bIsRowDrag ); + } + else + rSh.CallSetCursor( &aModPt, false ); + + // It can be that a "jump" over a table cannot be accomplished like + // that. So we jump over the table by Up/Down here. + const SwRect& rVisArea = rSh.VisArea(); + if( aOldVis == rVisArea && !rSh.IsStartOfDoc() && !rSh.IsEndOfDoc() ) + { + // take the center point of VisArea to + // decide in which direction the user want. + if( aModPt.Y() < ( rVisArea.Top() + rVisArea.Height() / 2 ) ) + rSh.Up( true ); + else + rSh.Down( true ); + } + } + + m_aMovePos += rSh.VisArea().Pos() - aOldVis.Pos(); + JustifyAreaTimer(); +} + +void SwEditWin::JustifyAreaTimer() +{ + const tools::Rectangle &rVisArea = GetView().GetVisArea(); +#ifdef UNX + const tools::Long coMinLen = 100; +#else + const tools::Long coMinLen = 50; +#endif + tools::Long const nTimeout = 800, + nDiff = std::max( + std::max( m_aMovePos.Y() - rVisArea.Bottom(), rVisArea.Top() - m_aMovePos.Y() ), + std::max( m_aMovePos.X() - rVisArea.Right(), rVisArea.Left() - m_aMovePos.X())); + m_aTimer.SetTimeout( std::max( coMinLen, nTimeout - nDiff*2L) ); +} + +void SwEditWin::LeaveArea(const Point &rPos) +{ + m_aMovePos = rPos; + JustifyAreaTimer(); + if( !m_aTimer.IsActive() ) + m_aTimer.Start(); + m_pShadCursor.reset(); +} + +inline void SwEditWin::EnterArea() +{ + m_aTimer.Stop(); +} + +/** + * Insert mode for frames + */ +void SwEditWin::InsFrame(sal_uInt16 nCols) +{ + StdDrawMode( SdrObjKind::NONE, false ); + m_bInsFrame = true; + m_nInsFrameColCount = nCols; +} + +void SwEditWin::StdDrawMode( SdrObjKind eSdrObjectKind, bool bObjSelect ) +{ + SetSdrDrawMode( eSdrObjectKind ); + + if (bObjSelect) + m_rView.SetDrawFuncPtr(std::make_unique<DrawSelection>( &m_rView.GetWrtShell(), this, &m_rView )); + else + m_rView.SetDrawFuncPtr(std::make_unique<SwDrawBase>( &m_rView.GetWrtShell(), this, &m_rView )); + + m_rView.SetSelDrawSlot(); + SetSdrDrawMode( eSdrObjectKind ); + if (bObjSelect) + m_rView.GetDrawFuncPtr()->Activate( SID_OBJECT_SELECT ); + else + m_rView.GetDrawFuncPtr()->Activate( sal::static_int_cast< sal_uInt16 >(eSdrObjectKind) ); + m_bInsFrame = false; + m_nInsFrameColCount = 1; +} + +void SwEditWin::StopInsFrame() +{ + if (m_rView.GetDrawFuncPtr()) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + } + m_rView.LeaveDrawCreate(); // leave construction mode + m_bInsFrame = false; + m_nInsFrameColCount = 1; +} + +bool SwEditWin::IsInputSequenceCheckingRequired( const OUString &rText, const SwPaM& rCursor ) +{ + const SvtCTLOptions& rCTLOptions = SW_MOD()->GetCTLOptions(); + if ( !rCTLOptions.IsCTLFontEnabled() || + !rCTLOptions.IsCTLSequenceChecking() ) + return false; + + if ( 0 == rCursor.Start()->nContent.GetIndex() ) /* first char needs not to be checked */ + return false; + + SwBreakIt *pBreakIter = SwBreakIt::Get(); + uno::Reference < i18n::XBreakIterator > xBI = pBreakIter->GetBreakIter(); + assert(xBI.is()); + tools::Long nCTLScriptPos = -1; + + if (xBI->getScriptType( rText, 0 ) == i18n::ScriptType::COMPLEX) + nCTLScriptPos = 0; + else + nCTLScriptPos = xBI->nextScript( rText, 0, i18n::ScriptType::COMPLEX ); + + return (0 <= nCTLScriptPos && nCTLScriptPos <= rText.getLength()); +} + +//return INVALID_HINT if language should not be explicitly overridden, the correct +//HintId to use for the eBufferLanguage otherwise +static sal_uInt16 lcl_isNonDefaultLanguage(LanguageType eBufferLanguage, SwView const & rView, + const OUString &rInBuffer) +{ + sal_uInt16 nWhich = INVALID_HINT; + + //If the option to IgnoreLanguageChange is set, short-circuit this method + //which results in the document/paragraph language remaining the same + //despite a change to the keyboard/input language + SvtSysLocaleOptions aSysLocaleOptions; + if(aSysLocaleOptions.IsIgnoreLanguageChange()) + { + return INVALID_HINT; + } + + bool bLang = true; + if(eBufferLanguage != LANGUAGE_DONTKNOW) + { + switch( SvtLanguageOptions::GetI18NScriptTypeOfLanguage( eBufferLanguage )) + { + case i18n::ScriptType::ASIAN: nWhich = RES_CHRATR_CJK_LANGUAGE; break; + case i18n::ScriptType::COMPLEX: nWhich = RES_CHRATR_CTL_LANGUAGE; break; + case i18n::ScriptType::LATIN: nWhich = RES_CHRATR_LANGUAGE; break; + default: bLang = false; + } + if(bLang) + { + SfxItemSet aLangSet(rView.GetPool(), nWhich, nWhich); + SwWrtShell& rSh = rView.GetWrtShell(); + rSh.GetCurAttr(aLangSet); + if(SfxItemState::DEFAULT <= aLangSet.GetItemState(nWhich)) + { + LanguageType eLang = static_cast<const SvxLanguageItem&>(aLangSet.Get(nWhich)).GetLanguage(); + if ( eLang == eBufferLanguage ) + { + // current language attribute equal to language reported from system + bLang = false; + } + else if ( !g_bInputLanguageSwitched && RES_CHRATR_LANGUAGE == nWhich ) + { + // special case: switching between two "LATIN" languages + // In case the current keyboard setting might be suitable + // for both languages we can't safely assume that the user + // wants to use the language reported from the system, + // except if we knew that it was explicitly switched (thus + // the check for "bInputLangeSwitched"). + + // The language reported by the system could be just the + // system default language that the user is not even aware + // of, because no language selection tool is installed at + // all. In this case the OOo language should get preference + // as it might have been selected by the user explicitly. + + // Usually this case happens if the OOo language is + // different to the system language but the system keyboard + // is still suitable for the OOo language (e.g. writing + // English texts with a German keyboard). + + // For non-latin keyboards overwriting the attribute is + // still valid. We do this for cyrillic and greek ATM. In + // future versions of OOo this should be replaced by a + // configuration switch that allows to give the preference + // to the OOo setting or the system setting explicitly + // and/or a better handling of the script type. + i18n::UnicodeScript eType = !rInBuffer.isEmpty() ? + GetAppCharClass().getScript( rInBuffer, 0 ) : + i18n::UnicodeScript_kScriptCount; + + bool bSystemIsNonLatin = false; + switch ( eType ) + { + case i18n::UnicodeScript_kGreek: + case i18n::UnicodeScript_kCyrillic: + // in case other UnicodeScripts require special + // keyboards they can be added here + bSystemIsNonLatin = true; + break; + default: + break; + } + + bool bOOoLangIsNonLatin = MsLangId::isNonLatinWestern( eLang); + + bLang = (bSystemIsNonLatin != bOOoLangIsNonLatin); + } + } + } + } + return bLang ? nWhich : INVALID_HINT; +} + +/** + * Character buffer is inserted into the document + */ +void SwEditWin::FlushInBuffer() +{ + if ( m_aKeyInputFlushTimer.IsActive()) + m_aKeyInputFlushTimer.Stop(); + + if ( m_aInBuffer.isEmpty() ) + return; + + SwWrtShell& rSh = m_rView.GetWrtShell(); + + // generate new sequence input checker if not already done + if ( !pCheckIt ) + pCheckIt = new SwCheckIt; + + uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = pCheckIt->xCheck; + if ( xISC.is() && IsInputSequenceCheckingRequired( m_aInBuffer, *rSh.GetCursor() ) ) + { + + // apply (Thai) input sequence checking/correction + + rSh.Push(); // push current cursor to stack + + // get text from the beginning (i.e left side) of current selection + // to the start of the paragraph + rSh.NormalizePam(); // make point be the first (left) one + if (!rSh.GetCursor()->HasMark()) + rSh.GetCursor()->SetMark(); + rSh.GetCursor()->GetMark()->nContent = 0; + + const OUString aOldText( rSh.GetCursor()->GetText() ); + const sal_Int32 nOldLen = aOldText.getLength(); + + SvtCTLOptions& rCTLOptions = SW_MOD()->GetCTLOptions(); + + sal_Int32 nExpandSelection = 0; + if (nOldLen > 0) + { + sal_Int32 nTmpPos = nOldLen; + sal_Int16 nCheckMode = rCTLOptions.IsCTLSequenceCheckingRestricted() ? + i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC; + + OUString aNewText( aOldText ); + if (rCTLOptions.IsCTLSequenceCheckingTypeAndReplace()) + { + for( sal_Int32 k = 0; k < m_aInBuffer.getLength(); ++k) + { + const sal_Unicode cChar = m_aInBuffer[k]; + const sal_Int32 nPrevPos =xISC->correctInputSequence( aNewText, nTmpPos - 1, cChar, nCheckMode ); + + // valid sequence or sequence could be corrected: + if (nPrevPos != aNewText.getLength()) + nTmpPos = nPrevPos + 1; + } + + // find position of first character that has changed + sal_Int32 nNewLen = aNewText.getLength(); + const sal_Unicode *pOldText = aOldText.getStr(); + const sal_Unicode *pNewText = aNewText.getStr(); + sal_Int32 nChgPos = 0; + while ( nChgPos < nOldLen && nChgPos < nNewLen && + pOldText[nChgPos] == pNewText[nChgPos] ) + ++nChgPos; + + const sal_Int32 nChgLen = nNewLen - nChgPos; + if (nChgLen) + { + m_aInBuffer = aNewText.copy( nChgPos, nChgLen ); + nExpandSelection = nOldLen - nChgPos; + } + else + m_aInBuffer.clear(); + } + else + { + for( sal_Int32 k = 0; k < m_aInBuffer.getLength(); ++k ) + { + const sal_Unicode cChar = m_aInBuffer[k]; + if (xISC->checkInputSequence( aNewText, nTmpPos - 1, cChar, nCheckMode )) + { + // character can be inserted: + aNewText += OUStringChar( cChar ); + ++nTmpPos; + } + } + m_aInBuffer = aNewText.copy( aOldText.getLength() ); // copy new text to be inserted to buffer + } + } + + // at this point now we will insert the buffer text 'normally' some lines below... + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + + if (m_aInBuffer.isEmpty()) + return; + + // if text prior to the original selection needs to be changed + // as well, we now expand the selection accordingly. + SwPaM &rCursor = *rSh.GetCursor(); + const sal_Int32 nCursorStartPos = rCursor.Start()->nContent.GetIndex(); + OSL_ENSURE( nCursorStartPos >= nExpandSelection, "cannot expand selection as specified!!" ); + if (nExpandSelection && nCursorStartPos >= nExpandSelection) + { + if (!rCursor.HasMark()) + rCursor.SetMark(); + rCursor.Start()->nContent -= nExpandSelection; + } + } + + uno::Reference< frame::XDispatchRecorder > xRecorder = + m_rView.GetViewFrame()->GetBindings().GetRecorder(); + if ( xRecorder.is() ) + { + // determine shell + SfxShell *pSfxShell = lcl_GetTextShellFromDispatcher( m_rView ); + // generate request and record + if (pSfxShell) + { + SfxRequest aReq( m_rView.GetViewFrame(), FN_INSERT_STRING ); + aReq.AppendItem( SfxStringItem( FN_INSERT_STRING, m_aInBuffer ) ); + aReq.Done(); + } + } + + sal_uInt16 nWhich = lcl_isNonDefaultLanguage(m_eBufferLanguage, m_rView, m_aInBuffer); + if (nWhich != INVALID_HINT ) + { + SvxLanguageItem aLangItem( m_eBufferLanguage, nWhich ); + rSh.SetAttrItem( aLangItem ); + } + + rSh.Insert( m_aInBuffer ); + m_eBufferLanguage = LANGUAGE_DONTKNOW; + m_aInBuffer.clear(); +} + +#define MOVE_LEFT_SMALL 0 +#define MOVE_UP_SMALL 1 +#define MOVE_RIGHT_BIG 2 +#define MOVE_DOWN_BIG 3 +#define MOVE_LEFT_BIG 4 +#define MOVE_UP_BIG 5 +#define MOVE_RIGHT_SMALL 6 +#define MOVE_DOWN_SMALL 7 + +// #i121236# Support for shift key in writer +#define MOVE_LEFT_HUGE 8 +#define MOVE_UP_HUGE 9 +#define MOVE_RIGHT_HUGE 10 +#define MOVE_DOWN_HUGE 11 + +void SwEditWin::ChangeFly( sal_uInt8 nDir, bool bWeb ) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + SwRect aTmp = rSh.GetFlyRect(); + if( !aTmp.HasArea() || + rSh.IsSelObjProtected( FlyProtectFlags::Pos ) != FlyProtectFlags::NONE ) + return; + + SfxItemSetFixed< + RES_FRM_SIZE, RES_FRM_SIZE, + RES_PROTECT, RES_PROTECT, + RES_VERT_ORIENT, RES_ANCHOR, + RES_COL, RES_COL, + RES_FOLLOW_TEXT_FLOW, RES_FOLLOW_TEXT_FLOW> + aSet( rSh.GetAttrPool() ); + rSh.GetFlyFrameAttr( aSet ); + RndStdIds eAnchorId = aSet.Get(RES_ANCHOR).GetAnchorId(); + Size aSnap; + bool bHuge(MOVE_LEFT_HUGE == nDir || + MOVE_UP_HUGE == nDir || + MOVE_RIGHT_HUGE == nDir || + MOVE_DOWN_HUGE == nDir); + + if(MOVE_LEFT_SMALL == nDir || + MOVE_UP_SMALL == nDir || + MOVE_RIGHT_SMALL == nDir || + MOVE_DOWN_SMALL == nDir ) + { + aSnap = PixelToLogic(Size(1,1)); + } + else + { + aSnap = rSh.GetViewOptions()->GetSnapSize(); + short nDiv = rSh.GetViewOptions()->GetDivisionX(); + if ( nDiv > 0 ) + aSnap.setWidth( std::max( sal_uLong(1), static_cast<sal_uLong>(aSnap.Width()) / nDiv ) ); + nDiv = rSh.GetViewOptions()->GetDivisionY(); + if ( nDiv > 0 ) + aSnap.setHeight( std::max( sal_uLong(1), static_cast<sal_uLong>(aSnap.Height()) / nDiv ) ); + } + + if(bHuge) + { + // #i121236# 567twips == 1cm, but just take three times the normal snap + aSnap = Size(aSnap.Width() * 3, aSnap.Height() * 3); + } + + SwRect aBoundRect; + Point aRefPoint; + // adjustment for allowing vertical position + // aligned to page for fly frame anchored to paragraph or to character. + { + const SwFormatVertOrient& aVert( aSet.Get(RES_VERT_ORIENT) ); + const bool bFollowTextFlow = + aSet.Get(RES_FOLLOW_TEXT_FLOW).GetValue(); + const SwPosition* pToCharContentPos = aSet.Get(RES_ANCHOR).GetContentAnchor(); + rSh.CalcBoundRect( aBoundRect, eAnchorId, + text::RelOrientation::FRAME, aVert.GetRelationOrient(), + pToCharContentPos, bFollowTextFlow, + false, &aRefPoint ); + } + tools::Long nLeft = std::min( aTmp.Left() - aBoundRect.Left(), aSnap.Width() ); + tools::Long nRight = std::min( aBoundRect.Right() - aTmp.Right(), aSnap.Width() ); + tools::Long nUp = std::min( aTmp.Top() - aBoundRect.Top(), aSnap.Height() ); + tools::Long nDown = std::min( aBoundRect.Bottom() - aTmp.Bottom(), aSnap.Height() ); + + switch ( nDir ) + { + case MOVE_LEFT_BIG: + case MOVE_LEFT_HUGE: + case MOVE_LEFT_SMALL: aTmp.Left( aTmp.Left() - nLeft ); + break; + + case MOVE_UP_BIG: + case MOVE_UP_HUGE: + case MOVE_UP_SMALL: aTmp.Top( aTmp.Top() - nUp ); + break; + + case MOVE_RIGHT_SMALL: + if( aTmp.Width() < aSnap.Width() + MINFLY ) + break; + nRight = aSnap.Width(); + [[fallthrough]]; + case MOVE_RIGHT_HUGE: + case MOVE_RIGHT_BIG: aTmp.Left( aTmp.Left() + nRight ); + break; + + case MOVE_DOWN_SMALL: + if( aTmp.Height() < aSnap.Height() + MINFLY ) + break; + nDown = aSnap.Height(); + [[fallthrough]]; + case MOVE_DOWN_HUGE: + case MOVE_DOWN_BIG: aTmp.Top( aTmp.Top() + nDown ); + break; + + default: OSL_ENSURE(true, "ChangeFly: Unknown direction." ); + } + bool bSet = false; + if ((RndStdIds::FLY_AS_CHAR == eAnchorId) && ( nDir % 2 )) + { + tools::Long aDiff = aTmp.Top() - aRefPoint.Y(); + if( aDiff > 0 ) + aDiff = 0; + else if ( aDiff < -aTmp.Height() ) + aDiff = -aTmp.Height(); + SwFormatVertOrient aVert( aSet.Get(RES_VERT_ORIENT) ); + sal_Int16 eNew; + if( bWeb ) + { + eNew = aVert.GetVertOrient(); + bool bDown = 0 != ( nDir & 0x02 ); + switch( eNew ) + { + case text::VertOrientation::CHAR_TOP: + if( bDown ) eNew = text::VertOrientation::CENTER; + break; + case text::VertOrientation::CENTER: + eNew = bDown ? text::VertOrientation::TOP : text::VertOrientation::CHAR_TOP; + break; + case text::VertOrientation::TOP: + if( !bDown ) eNew = text::VertOrientation::CENTER; + break; + case text::VertOrientation::LINE_TOP: + if( bDown ) eNew = text::VertOrientation::LINE_CENTER; + break; + case text::VertOrientation::LINE_CENTER: + eNew = bDown ? text::VertOrientation::LINE_BOTTOM : text::VertOrientation::LINE_TOP; + break; + case text::VertOrientation::LINE_BOTTOM: + if( !bDown ) eNew = text::VertOrientation::LINE_CENTER; + break; + default:; //prevent warning + } + } + else + { + aVert.SetPos( aDiff ); + eNew = text::VertOrientation::NONE; + } + aVert.SetVertOrient( eNew ); + aSet.Put( aVert ); + bSet = true; + } + if (bWeb && (RndStdIds::FLY_AT_PARA == eAnchorId) + && ( nDir==MOVE_LEFT_SMALL || nDir==MOVE_RIGHT_BIG )) + { + SwFormatHoriOrient aHori( aSet.Get(RES_HORI_ORIENT) ); + sal_Int16 eNew; + eNew = aHori.GetHoriOrient(); + switch( eNew ) + { + case text::HoriOrientation::RIGHT: + if( nDir==MOVE_LEFT_SMALL ) + eNew = text::HoriOrientation::LEFT; + break; + case text::HoriOrientation::LEFT: + if( nDir==MOVE_RIGHT_BIG ) + eNew = text::HoriOrientation::RIGHT; + break; + default:; //prevent warning + } + if( eNew != aHori.GetHoriOrient() ) + { + aHori.SetHoriOrient( eNew ); + aSet.Put( aHori ); + bSet = true; + } + } + rSh.StartAllAction(); + if( bSet ) + rSh.SetFlyFrameAttr( aSet ); + bool bSetPos = (RndStdIds::FLY_AS_CHAR != eAnchorId); + if(bSetPos && bWeb) + { + bSetPos = RndStdIds::FLY_AT_PAGE == eAnchorId; + } + if( bSetPos ) + rSh.SetFlyPos( aTmp.Pos() ); + rSh.EndAllAction(); + +} + +void SwEditWin::ChangeDrawing( sal_uInt8 nDir ) +{ + // start undo action in order to get only one + // undo action for this change. + SwWrtShell &rSh = m_rView.GetWrtShell(); + rSh.StartUndo(); + + tools::Long nX = 0; + tools::Long nY = 0; + const bool bOnePixel( + MOVE_LEFT_SMALL == nDir || + MOVE_UP_SMALL == nDir || + MOVE_RIGHT_SMALL == nDir || + MOVE_DOWN_SMALL == nDir); + const bool bHuge( + MOVE_LEFT_HUGE == nDir || + MOVE_UP_HUGE == nDir || + MOVE_RIGHT_HUGE == nDir || + MOVE_DOWN_HUGE == nDir); + SwMove nAnchorDir = SwMove::UP; + switch(nDir) + { + case MOVE_LEFT_SMALL: + case MOVE_LEFT_HUGE: + case MOVE_LEFT_BIG: + nX = -1; + nAnchorDir = SwMove::LEFT; + break; + case MOVE_UP_SMALL: + case MOVE_UP_HUGE: + case MOVE_UP_BIG: + nY = -1; + break; + case MOVE_RIGHT_SMALL: + case MOVE_RIGHT_HUGE: + case MOVE_RIGHT_BIG: + nX = +1; + nAnchorDir = SwMove::RIGHT; + break; + case MOVE_DOWN_SMALL: + case MOVE_DOWN_HUGE: + case MOVE_DOWN_BIG: + nY = +1; + nAnchorDir = SwMove::DOWN; + break; + } + + if(0 != nX || 0 != nY) + { + FlyProtectFlags nProtect = rSh.IsSelObjProtected( FlyProtectFlags::Pos|FlyProtectFlags::Size ); + Size aSnap( rSh.GetViewOptions()->GetSnapSize() ); + short nDiv = rSh.GetViewOptions()->GetDivisionX(); + if ( nDiv > 0 ) + aSnap.setWidth( std::max( sal_uLong(1), static_cast<sal_uLong>(aSnap.Width()) / nDiv ) ); + nDiv = rSh.GetViewOptions()->GetDivisionY(); + if ( nDiv > 0 ) + aSnap.setHeight( std::max( sal_uLong(1), static_cast<sal_uLong>(aSnap.Height()) / nDiv ) ); + + if(bOnePixel) + { + aSnap = PixelToLogic(Size(1,1)); + } + else if(bHuge) + { + // #i121236# 567twips == 1cm, but just take three times the normal snap + aSnap = Size(aSnap.Width() * 3, aSnap.Height() * 3); + } + + nX *= aSnap.Width(); + nY *= aSnap.Height(); + + SdrView *pSdrView = rSh.GetDrawView(); + const SdrHdlList& rHdlList = pSdrView->GetHdlList(); + SdrHdl* pHdl = rHdlList.GetFocusHdl(); + rSh.StartAllAction(); + if(nullptr == pHdl) + { + // now move the selected draw objects + // if the object's position is not protected + if(!(nProtect&FlyProtectFlags::Pos)) + { + // Check if object is anchored as character and move direction + bool bDummy1, bDummy2; + const bool bVertAnchor = rSh.IsFrameVertical( true, bDummy1, bDummy2 ); + bool bHoriMove = !bVertAnchor == !( nDir % 2 ); + bool bMoveAllowed = + !bHoriMove || (rSh.GetAnchorId() != RndStdIds::FLY_AS_CHAR); + if ( bMoveAllowed ) + { + pSdrView->MoveAllMarked(Size(nX, nY)); + rSh.SetModified(); + } + } + } + else + { + // move handle with index nHandleIndex + if (nX || nY) + { + if( SdrHdlKind::Anchor == pHdl->GetKind() || + SdrHdlKind::Anchor_TR == pHdl->GetKind() ) + { + // anchor move cannot be allowed when position is protected + if(!(nProtect&FlyProtectFlags::Pos)) + rSh.MoveAnchor( nAnchorDir ); + } + //now resize if size is protected + else if(!(nProtect&FlyProtectFlags::Size)) + { + // now move the Handle (nX, nY) + Point aStartPoint(pHdl->GetPos()); + Point aEndPoint(pHdl->GetPos() + Point(nX, nY)); + const SdrDragStat& rDragStat = pSdrView->GetDragStat(); + + // start dragging + pSdrView->BegDragObj(aStartPoint, nullptr, pHdl, 0); + + if(pSdrView->IsDragObj()) + { + bool bWasNoSnap = rDragStat.IsNoSnap(); + bool bWasSnapEnabled = pSdrView->IsSnapEnabled(); + + // switch snapping off + if(!bWasNoSnap) + const_cast<SdrDragStat&>(rDragStat).SetNoSnap(); + if(bWasSnapEnabled) + pSdrView->SetSnapEnabled(false); + + pSdrView->MovAction(aEndPoint); + pSdrView->EndDragObj(); + rSh.SetModified(); + + // restore snap + if(!bWasNoSnap) + const_cast<SdrDragStat&>(rDragStat).SetNoSnap(bWasNoSnap); + if(bWasSnapEnabled) + pSdrView->SetSnapEnabled(bWasSnapEnabled); + } + } + } + } + rSh.EndAllAction(); + } + + rSh.EndUndo(); +} + +/** + * KeyEvents + */ +void SwEditWin::KeyInput(const KeyEvent &rKEvt) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + + if (comphelper::LibreOfficeKit::isActive() && m_rView.GetPostItMgr()) + { + if (vcl::Window* pWindow = m_rView.GetPostItMgr()->GetActiveSidebarWin()) + { + pWindow->KeyInput(rKEvt); + return; + } + } + + if( rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE && + m_pApplyTempl && m_pApplyTempl->m_pFormatClipboard ) + { + m_pApplyTempl->m_pFormatClipboard->Erase(); + SetApplyTemplate(SwApplyTemplate()); + m_rView.GetViewFrame()->GetBindings().Invalidate(SID_FORMATPAINTBRUSH); + } + else if ( rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE && + rSh.IsHeaderFooterEdit( ) ) + { + bool bHeader = bool(FrameTypeFlags::HEADER & rSh.GetFrameType(nullptr,false)); + if ( bHeader ) + rSh.SttPg(); + else + rSh.EndPg(); + rSh.ToggleHeaderFooterEdit(); + } + + SfxObjectShell *pObjSh = m_rView.GetViewFrame()->GetObjectShell(); + if ( m_bLockInput || (pObjSh && pObjSh->GetProgress()) ) + // When the progress bar is active or a progress is + // running on a document, no order is being taken + return; + + m_pShadCursor.reset(); + // Do not reset the timer here, otherwise when flooded with events it would never time out + // if every key event stopped and started it again. + comphelper::ScopeGuard keyInputFlushTimerStop([this]() { m_aKeyInputFlushTimer.Stop(); }); + + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() && + rSh.IsCursorReadonly(); + + //if the language changes the buffer must be flushed + LanguageType eNewLanguage = GetInputLanguage(); + if(!bIsDocReadOnly && m_eBufferLanguage != eNewLanguage && !m_aInBuffer.isEmpty()) + { + FlushInBuffer(); + } + m_eBufferLanguage = eNewLanguage; + + QuickHelpData aTmpQHD; + if( s_pQuickHlpData->m_bIsDisplayed ) + { + aTmpQHD.Move( *s_pQuickHlpData ); + s_pQuickHlpData->Stop( rSh ); + } + + // OS:the DrawView also needs a readonly-Flag as well + if ( !bIsDocReadOnly && rSh.GetDrawView() && rSh.GetDrawView()->KeyInput( rKEvt, this ) ) + { + rSh.GetView().GetViewFrame()->GetBindings().InvalidateAll( false ); + rSh.SetModified(); + return; // Event evaluated by SdrView + } + + if ( m_rView.GetDrawFuncPtr() && m_bInsFrame ) + { + StopInsFrame(); + rSh.Edit(); + } + + bool bFlushBuffer = false; + bool bNormalChar = false; + bool bAppendSpace = s_pQuickHlpData->m_bAppendSpace; + s_pQuickHlpData->m_bAppendSpace = false; + + if ( getenv("SW_DEBUG") && rKEvt.GetKeyCode().GetCode() == KEY_F12 ) + { + if( rKEvt.GetKeyCode().IsShift()) + { + GetView().GetDocShell()->GetDoc()->dumpAsXml(); + return; + } + else + { + SwRootFrame* pLayout = GetView().GetDocShell()->GetWrtShell()->GetLayout(); + pLayout->dumpAsXml( ); + return; + } + } + + KeyEvent aKeyEvent( rKEvt ); + // look for vertical mappings + if( !bIsDocReadOnly && !rSh.IsSelFrameMode() && !rSh.IsObjSelected() ) + { + sal_uInt16 nKey = rKEvt.GetKeyCode().GetCode(); + + if( KEY_UP == nKey || KEY_DOWN == nKey || + KEY_LEFT == nKey || KEY_RIGHT == nKey ) + { + // In general, we want to map the direction keys if we are inside + // some vertical formatted text. + // 1. Exception: For a table cursor in a horizontal table, the + // directions should never be mapped. + // 2. Exception: For a table cursor in a vertical table, the + // directions should always be mapped. + const bool bVertText = rSh.IsInVerticalText(); + const bool bTableCursor = rSh.GetTableCursor(); + const bool bVertTable = rSh.IsTableVertical(); + if( ( bVertText && ( !bTableCursor || bVertTable ) ) || + ( bTableCursor && bVertTable ) ) + { + SvxFrameDirection eDirection = rSh.GetTextDirection(); + if (eDirection == SvxFrameDirection::Vertical_LR_BT) + { + // Map from physical to logical, so rotate clockwise. + if (KEY_UP == nKey) + nKey = KEY_RIGHT; + else if (KEY_DOWN == nKey) + nKey = KEY_LEFT; + else if (KEY_LEFT == nKey) + nKey = KEY_UP; + else /* KEY_RIGHT == nKey */ + nKey = KEY_DOWN; + } + else + { + // Attempt to integrate cursor travelling for mongolian layout does not work. + // Thus, back to previous mapping of cursor keys to direction keys. + if( KEY_UP == nKey ) nKey = KEY_LEFT; + else if( KEY_DOWN == nKey ) nKey = KEY_RIGHT; + else if( KEY_LEFT == nKey ) nKey = KEY_DOWN; + else /* KEY_RIGHT == nKey */ nKey = KEY_UP; + } + } + + if ( rSh.IsInRightToLeftText() ) + { + if( KEY_LEFT == nKey ) nKey = KEY_RIGHT; + else if( KEY_RIGHT == nKey ) nKey = KEY_LEFT; + } + + aKeyEvent = KeyEvent( rKEvt.GetCharCode(), + vcl::KeyCode( nKey, rKEvt.GetKeyCode().GetModifier() ), + rKEvt.GetRepeat() ); + } + } + + const vcl::KeyCode& rKeyCode = aKeyEvent.GetKeyCode(); + sal_Unicode aCh = aKeyEvent.GetCharCode(); + + // enable switching to notes anchor with Ctrl - Alt - Page Up/Down + // pressing this inside a note will switch to next/previous note + if ((rKeyCode.IsMod1() && rKeyCode.IsMod2()) && ((rKeyCode.GetCode() == KEY_PAGEUP) || (rKeyCode.GetCode() == KEY_PAGEDOWN))) + { + const bool bNext = rKeyCode.GetCode()==KEY_PAGEDOWN; + const SwFieldType* pFieldType = rSh.GetFieldType( 0, SwFieldIds::Postit ); + rSh.MoveFieldType( pFieldType, bNext ); + return; + } + + if (SwTextContentControl* pTextContentControl = rSh.CursorInsideContentControl()) + { + // Check if this combination of rKeyCode and pTextContentControl should open a popup. + const SwFormatContentControl& rFormatContentControl = pTextContentControl->GetContentControl(); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + if (pContentControl->ShouldOpenPopup(rKeyCode)) + { + SwShellCursor* pCursor = rSh.GetCursor_(); + if (pCursor) + { + VclPtr<SwContentControlButton> pContentControlButton = pCursor->GetContentControlButton(); + if (pContentControlButton) + { + pContentControlButton->StartPopup(); + return; + } + } + } + } + + const SwFrameFormat* pFlyFormat = rSh.GetFlyFrameFormat(); + + if (pFlyFormat) + { + // See if the fly frame's anchor is in a content control. If so, + // try to interact with it. + const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor(); + const SwPosition* pAnchorPos = rFormatAnchor.GetContentAnchor(); + if (pAnchorPos) + { + SwTextNode* pTextNode = pAnchorPos->nNode.GetNode().GetTextNode(); + if (pTextNode) + { + SwTextAttr* pAttr = pTextNode->GetTextAttrAt( + pAnchorPos->nContent.GetIndex(), RES_TXTATR_CONTENTCONTROL, SwTextNode::PARENT); + if (pAttr) + { + SwTextContentControl* pTextContentControl + = static_txtattr_cast<SwTextContentControl*>(pAttr); + const SwFormatContentControl& rFormatContentControl + = pTextContentControl->GetContentControl(); + std::shared_ptr<SwContentControl> pContentControl + = rFormatContentControl.GetContentControl(); + if (pContentControl->IsInteractingCharacter(aCh)) + { + rSh.GotoContentControl(rFormatContentControl); + return; + } + } + } + } + } + + if( pFlyFormat ) + { + SvMacroItemId nEvent; + + if( 32 <= aCh && + 0 == (( KEY_MOD1 | KEY_MOD2 ) & rKeyCode.GetModifier() )) + nEvent = SvMacroItemId::SwFrmKeyInputAlpha; + else + nEvent = SvMacroItemId::SwFrmKeyInputNoAlpha; + + const SvxMacro* pMacro = pFlyFormat->GetMacro().GetMacroTable().Get( nEvent ); + if( pMacro ) + { + SbxArrayRef xArgs = new SbxArray; + SbxVariableRef xVar = new SbxVariable; + xVar->PutString( pFlyFormat->GetName() ); + xArgs->Put(xVar.get(), 1); + + xVar = new SbxVariable; + if( SvMacroItemId::SwFrmKeyInputAlpha == nEvent ) + xVar->PutChar( aCh ); + else + xVar->PutUShort( rKeyCode.GetModifier() | rKeyCode.GetCode() ); + xArgs->Put(xVar.get(), 2); + + OUString sRet; + rSh.ExecMacro( *pMacro, &sRet, xArgs.get() ); + if( !sRet.isEmpty() && sRet.toInt32()!=0 ) + return ; + } + } + SelectionType nLclSelectionType; + //A is converted to 1 + if( rKeyCode.GetFullCode() == (KEY_A | KEY_MOD1 |KEY_SHIFT) + && rSh.HasDrawView() && + (bool(nLclSelectionType = rSh.GetSelectionType()) && + ((nLclSelectionType & (SelectionType::Frame|SelectionType::Graphic)) || + ((nLclSelectionType & (SelectionType::DrawObject|SelectionType::DbForm)) && + rSh.GetDrawView()->GetMarkedObjectList().GetMarkCount() == 1)))) + { + SdrHdlList& rHdlList = const_cast<SdrHdlList&>(rSh.GetDrawView()->GetHdlList()); + SdrHdl* pAnchor = rHdlList.GetHdl(SdrHdlKind::Anchor); + if ( ! pAnchor ) + pAnchor = rHdlList.GetHdl(SdrHdlKind::Anchor_TR); + if(pAnchor) + rHdlList.SetFocusHdl(pAnchor); + return; + } + + SvxAutoCorrCfg* pACfg = nullptr; + SvxAutoCorrect* pACorr = nullptr; + + uno::Reference< frame::XDispatchRecorder > xRecorder = + m_rView.GetViewFrame()->GetBindings().GetRecorder(); + if ( !xRecorder.is() ) + { + pACfg = &SvxAutoCorrCfg::Get(); + pACorr = pACfg->GetAutoCorrect(); + } + + SwModuleOptions* pModOpt = SW_MOD()->GetModuleConfig(); + + OUString sFormulaEntry; + + enum class SwKeyState { CheckKey, InsChar, InsTab, + NoNum, NumOff, NumOrNoNum, NumDown, NumUp, + NumIndentInc, NumIndentDec, + + OutlineLvOff, + NextCell, PrevCell, OutlineUp, OutlineDown, + GlossaryExpand, NextPrevGlossary, + AutoFormatByInput, + NextObject, PrevObject, + KeyToView, + LaunchOLEObject, GoIntoFly, GoIntoDrawing, + EnterDrawHandleMode, + CheckDocReadOnlyKeys, + CheckAutoCorrect, EditFormula, + ColLeftBig, ColRightBig, + ColLeftSmall, ColRightSmall, + ColBottomBig, + ColBottomSmall, + CellLeftBig, CellRightBig, + CellLeftSmall, CellRightSmall, + CellTopBig, CellBottomBig, + CellTopSmall, CellBottomSmall, + + Fly_Change, Draw_Change, + SpecialInsert, + EnterCharCell, + GotoNextFieldMark, + GotoPrevFieldMark, + End }; + + SwKeyState eKeyState = bIsDocReadOnly ? SwKeyState::CheckDocReadOnlyKeys : SwKeyState::CheckKey; + SwKeyState eNextKeyState = SwKeyState::End; + sal_uInt8 nDir = 0; + + if (m_nKS_NUMDOWN_Count > 0) + m_nKS_NUMDOWN_Count--; + + if (m_nKS_NUMINDENTINC_Count > 0) + m_nKS_NUMINDENTINC_Count--; + + while( SwKeyState::End != eKeyState ) + { + SwKeyState eFlyState = SwKeyState::KeyToView; + + switch( eKeyState ) + { + case SwKeyState::CheckKey: + eKeyState = SwKeyState::KeyToView; // default forward to View + + if (!comphelper::LibreOfficeKit::isActive() && + !rKeyCode.IsMod2() && '=' == aCh && + !rSh.IsTableMode() && rSh.GetTableFormat() && + rSh.IsSttPara() && + !rSh.HasReadonlySel()) + { + // at the beginning of the table's cell a '=' -> + // call EditRow (F2-functionality) + // [Avoid this for LibreOfficeKit, as the separate input window + // steals the focus & things go wrong - the user never gets + // the focus back.] + rSh.Push(); + if( !rSh.MoveSection( GoCurrSection, fnSectionStart) && + !rSh.IsTableBoxTextFormat() ) + { + // is at the beginning of the box + eKeyState = SwKeyState::EditFormula; + if( rSh.HasMark() ) + rSh.SwapPam(); + else + rSh.SttSelect(); + rSh.MoveSection( GoCurrSection, fnSectionEnd ); + rSh.Pop(); + rSh.EndSelect(); + sFormulaEntry = "="; + } + else + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + else + { + if( pACorr && aTmpQHD.HasContent() && !rSh.HasSelection() && + !rSh.HasReadonlySel() && !aTmpQHD.m_bIsAutoText && + pACorr->GetSwFlags().nAutoCmpltExpandKey == + (rKeyCode.GetModifier() | rKeyCode.GetCode()) ) + { + eKeyState = SwKeyState::GlossaryExpand; + break; + } + + switch( rKeyCode.GetModifier() | rKeyCode.GetCode() ) + { + case KEY_RIGHT | KEY_MOD2: + eKeyState = SwKeyState::ColRightBig; + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_RIGHT_SMALL; + goto KEYINPUT_CHECKTABLE; + + case KEY_LEFT | KEY_MOD2: + eKeyState = SwKeyState::ColRightSmall; + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_LEFT_SMALL; + goto KEYINPUT_CHECKTABLE; + + case KEY_RIGHT | KEY_MOD2 | KEY_SHIFT: + eKeyState = SwKeyState::ColLeftSmall; + goto KEYINPUT_CHECKTABLE; + + case KEY_LEFT | KEY_MOD2 | KEY_SHIFT: + eKeyState = SwKeyState::ColLeftBig; + goto KEYINPUT_CHECKTABLE; + + case KEY_RIGHT | KEY_MOD2 | KEY_MOD1: + eKeyState = SwKeyState::CellRightBig; + goto KEYINPUT_CHECKTABLE; + + case KEY_LEFT | KEY_MOD2 | KEY_MOD1: + eKeyState = SwKeyState::CellRightSmall; + goto KEYINPUT_CHECKTABLE; + + case KEY_RIGHT | KEY_MOD2 | KEY_SHIFT | KEY_MOD1: + eKeyState = SwKeyState::CellLeftSmall; + goto KEYINPUT_CHECKTABLE; + + case KEY_LEFT | KEY_MOD2 | KEY_SHIFT | KEY_MOD1: + eKeyState = SwKeyState::CellLeftBig; + goto KEYINPUT_CHECKTABLE; + + case KEY_UP | KEY_MOD2: + eKeyState = SwKeyState::ColBottomSmall; + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_UP_SMALL; + goto KEYINPUT_CHECKTABLE; + + case KEY_DOWN | KEY_MOD2: + eKeyState = SwKeyState::ColBottomBig; + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_DOWN_SMALL; + goto KEYINPUT_CHECKTABLE; + + case KEY_UP | KEY_MOD2 | KEY_MOD1: + eKeyState = SwKeyState::CellBottomSmall; + goto KEYINPUT_CHECKTABLE; + + case KEY_DOWN | KEY_MOD2 | KEY_MOD1: + eKeyState = SwKeyState::CellBottomBig; + goto KEYINPUT_CHECKTABLE; + + case KEY_UP | KEY_MOD2 | KEY_SHIFT | KEY_MOD1: + eKeyState = SwKeyState::CellTopBig; + goto KEYINPUT_CHECKTABLE; + + case KEY_DOWN | KEY_MOD2 | KEY_SHIFT | KEY_MOD1: + eKeyState = SwKeyState::CellTopSmall; + goto KEYINPUT_CHECKTABLE; + +KEYINPUT_CHECKTABLE: + // Resolve bugs 49091, 53190, 93402 and + // https://bz.apache.org/ooo/show_bug.cgi?id=113502 + // but provide an option for restoring interactive + // table sizing functionality when needed. + if ( + ! (Window::GetIndicatorState() & KeyIndicatorState::CAPSLOCK) + && m_rView.KeyInput( aKeyEvent ) // Keystroke is customized + ) + { + bFlushBuffer = true; + bNormalChar = false; + eKeyState = SwKeyState::End; + break ; + } + + if( rSh.IsTableMode() || !rSh.GetTableFormat() ) + { + if(!pFlyFormat && SwKeyState::KeyToView != eFlyState && + (rSh.GetSelectionType() & (SelectionType::DrawObject|SelectionType::DbForm)) && + rSh.GetDrawView()->AreObjectsMarked()) + eKeyState = SwKeyState::Draw_Change; + + if( pFlyFormat ) + eKeyState = eFlyState; + else if( SwKeyState::Draw_Change != eKeyState) + eKeyState = SwKeyState::EnterCharCell; + } + break; + + // huge object move + case KEY_RIGHT | KEY_SHIFT: + case KEY_LEFT | KEY_SHIFT: + case KEY_UP | KEY_SHIFT: + case KEY_DOWN | KEY_SHIFT: + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + if ( ( pFlyFormat + && ( nSelectionType & (SelectionType::Frame|SelectionType::Ole|SelectionType::Graphic) ) ) + || ( ( nSelectionType & (SelectionType::DrawObject|SelectionType::DbForm) ) + && rSh.GetDrawView()->AreObjectsMarked() ) ) + { + eKeyState = pFlyFormat ? SwKeyState::Fly_Change : SwKeyState::Draw_Change; + if (nSelectionType & SelectionType::DrawObject) + { + // tdf#137964: always move the DrawObject if one is selected + eKeyState = SwKeyState::Draw_Change; + } + switch ( rKeyCode.GetCode() ) + { + case KEY_RIGHT: nDir = MOVE_RIGHT_HUGE; break; + case KEY_LEFT: nDir = MOVE_LEFT_HUGE; break; + case KEY_UP: nDir = MOVE_UP_HUGE; break; + case KEY_DOWN: nDir = MOVE_DOWN_HUGE; break; + } + } + break; + } + + case KEY_LEFT: + case KEY_LEFT | KEY_MOD1: + { + bool bMod1 = 0 != (rKeyCode.GetModifier() & KEY_MOD1); + if(!bMod1) + { + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_LEFT_BIG; + } + goto KEYINPUT_CHECKTABLE_INSDEL; + } + case KEY_RIGHT | KEY_MOD1: + { + goto KEYINPUT_CHECKTABLE_INSDEL; + } + case KEY_UP: + case KEY_UP | KEY_MOD1: + { + bool bMod1 = 0 != (rKeyCode.GetModifier() & KEY_MOD1); + if(!bMod1) + { + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_UP_BIG; + } + goto KEYINPUT_CHECKTABLE_INSDEL; + } + case KEY_DOWN: + case KEY_DOWN | KEY_MOD1: + { + bool bMod1 = 0 != (rKeyCode.GetModifier() & KEY_MOD1); + if(!bMod1) + { + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_DOWN_BIG; + } + goto KEYINPUT_CHECKTABLE_INSDEL; + } + +KEYINPUT_CHECKTABLE_INSDEL: + if( rSh.IsTableMode() || !rSh.GetTableFormat() ) + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + + eKeyState = SwKeyState::KeyToView; + if(SwKeyState::KeyToView != eFlyState) + { + if((nSelectionType & (SelectionType::DrawObject|SelectionType::DbForm)) && + rSh.GetDrawView()->AreObjectsMarked()) + eKeyState = SwKeyState::Draw_Change; + else if(nSelectionType & (SelectionType::Frame|SelectionType::Ole|SelectionType::Graphic)) + eKeyState = SwKeyState::Fly_Change; + } + } + break; + + + case KEY_DELETE: + if ( !rSh.HasReadonlySel() || rSh.CursorInsideInputField()) + { + if (rSh.IsInFrontOfLabel() && rSh.NumOrNoNum()) + eKeyState = SwKeyState::NumOrNoNum; + } + else if (!rSh.IsCursorInParagraphMetadataField()) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui")); + std::unique_ptr<weld::MessageDialog> xInfo(xBuilder->weld_message_dialog("InfoReadonlyDialog")); + xInfo->run(); + eKeyState = SwKeyState::End; + } + break; + + case KEY_RETURN: + { + if ( !rSh.HasReadonlySel() + && !rSh.CursorInsideInputField() + && !rSh.CursorInsideContentControl() ) + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + if(nSelectionType & SelectionType::Ole) + eKeyState = SwKeyState::LaunchOLEObject; + else if(nSelectionType & SelectionType::Frame) + eKeyState = SwKeyState::GoIntoFly; + else if((nSelectionType & SelectionType::DrawObject) && + !(nSelectionType & SelectionType::DrawObjectEditMode) && + rSh.GetDrawView()->GetMarkedObjectList().GetMarkCount() == 1) + { + eKeyState = SwKeyState::GoIntoDrawing; + if (lcl_goIntoTextBox(*this, rSh)) + eKeyState = SwKeyState::GoIntoFly; + } + else if( aTmpQHD.HasContent() && !rSh.HasSelection() && + aTmpQHD.m_bIsAutoText ) + eKeyState = SwKeyState::GlossaryExpand; + + //RETURN and empty paragraph in numbering -> end numbering + else if( m_aInBuffer.isEmpty() && + rSh.GetNumRuleAtCurrCursorPos() && + !rSh.GetNumRuleAtCurrCursorPos()->IsOutlineRule() && + !rSh.HasSelection() && + rSh.IsSttPara() && rSh.IsEndPara() ) + { + eKeyState = SwKeyState::NumOff; + eNextKeyState = SwKeyState::OutlineLvOff; + } + //RETURN for new paragraph with AutoFormatting + else if( pACfg && pACfg->IsAutoFormatByInput() && + !(nSelectionType & (SelectionType::Graphic | + SelectionType::Ole | SelectionType::Frame | + SelectionType::TableCell | SelectionType::DrawObject | + SelectionType::DrawObjectEditMode)) ) + { + eKeyState = SwKeyState::AutoFormatByInput; + } + else + { + eNextKeyState = eKeyState; + eKeyState = SwKeyState::CheckAutoCorrect; + } + } + } + break; + case KEY_RETURN | KEY_MOD2: + { + if ( !rSh.HasReadonlySel() + && !rSh.IsSttPara() + && rSh.GetNumRuleAtCurrCursorPos() + && !rSh.CursorInsideInputField() ) + { + eKeyState = SwKeyState::NoNum; + } + else if( rSh.CanSpecialInsert() ) + eKeyState = SwKeyState::SpecialInsert; + } + break; + case KEY_BACKSPACE: + case KEY_BACKSPACE | KEY_SHIFT: + if ( !rSh.HasReadonlySel() || rSh.CursorInsideInputField()) + { + bool bDone = false; + // try to add comment for code snip: + // Remove the paragraph indent, if the cursor is at the + // beginning of a paragraph, there is no selection + // and no numbering rule found at the current paragraph + // Also try to remove indent, if current paragraph + // has numbering rule, but isn't counted and only + // key <backspace> is hit. + const bool bOnlyBackspaceKey( KEY_BACKSPACE == rKeyCode.GetFullCode() ); + if ( rSh.IsSttPara() + && !rSh.HasSelection() + && ( rSh.GetNumRuleAtCurrCursorPos() == nullptr + || ( rSh.IsNoNum() && bOnlyBackspaceKey ) ) ) + { + bDone = rSh.TryRemoveIndent(); + } + + if (bDone) + eKeyState = SwKeyState::End; + else + { + if ( rSh.IsSttPara() && !rSh.IsNoNum() ) + { + if (m_nKS_NUMDOWN_Count > 0 && + 0 < rSh.GetNumLevel()) + { + eKeyState = SwKeyState::NumUp; + m_nKS_NUMDOWN_Count = 2; + bDone = true; + } + else if (m_nKS_NUMINDENTINC_Count > 0) + { + eKeyState = SwKeyState::NumIndentDec; + m_nKS_NUMINDENTINC_Count = 2; + bDone = true; + } + } + + // If the cursor is in an empty paragraph, which has + // a numbering, but not the outline numbering, and + // there is no selection, the numbering has to be + // deleted on key <Backspace>. + // Otherwise method <SwEditShell::NumOrNoNum(..)> + // should only change the <IsCounted()> state of + // the current paragraph depending of the key. + // On <backspace> it is set to <false>, + // on <shift-backspace> it is set to <true>. + // Thus, assure that method <SwEditShell::NumOrNum(..)> + // is only called for the intended purpose. + if ( !bDone && rSh.IsSttPara() ) + { + bool bCallNumOrNoNum( false ); + if ( bOnlyBackspaceKey && !rSh.IsNoNum() ) + { + bCallNumOrNoNum = true; + } + else if ( !bOnlyBackspaceKey && rSh.IsNoNum() ) + { + bCallNumOrNoNum = true; + } + else if ( bOnlyBackspaceKey + && rSh.IsSttPara() + && rSh.IsEndPara() + && !rSh.HasSelection() ) + { + const SwNumRule* pCurrNumRule( rSh.GetNumRuleAtCurrCursorPos() ); + if ( pCurrNumRule != nullptr + && pCurrNumRule != rSh.GetOutlineNumRule() ) + { + bCallNumOrNoNum = true; + } + } + if ( bCallNumOrNoNum + && rSh.NumOrNoNum( !bOnlyBackspaceKey ) ) + { + eKeyState = SwKeyState::NumOrNoNum; + } + } + } + } + else if (!rSh.IsCursorInParagraphMetadataField()) + { + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui")); + std::unique_ptr<weld::MessageDialog> xInfo(xBuilder->weld_message_dialog("InfoReadonlyDialog")); + xInfo->run(); + eKeyState = SwKeyState::End; + } + break; + + case KEY_RIGHT: + { + eFlyState = SwKeyState::Fly_Change; + nDir = MOVE_RIGHT_BIG; + goto KEYINPUT_CHECKTABLE_INSDEL; + } + case KEY_TAB: + { + + if (rSh.IsFormProtected() || rSh.GetCurrentFieldmark() || rSh.GetChar(false)==CH_TXT_ATR_FORMELEMENT) + { + eKeyState = SwKeyState::GotoNextFieldMark; + } + else if ( !rSh.IsMultiSelection() && rSh.CursorInsideInputField() ) + { + GetView().GetViewFrame()->GetDispatcher()->Execute( FN_GOTO_NEXT_INPUTFLD ); + eKeyState = SwKeyState::End; + } + else if( rSh.GetNumRuleAtCurrCursorPos() + && rSh.IsSttOfPara() + && !rSh.HasReadonlySel() ) + { + if (numfunc::NumDownChangesIndent(rSh)) + { + eKeyState = SwKeyState::NumDown; + } + else + { + eKeyState = SwKeyState::InsTab; + } + } + else if (rSh.GetSelectionType() & + (SelectionType::Graphic | + SelectionType::Frame | + SelectionType::Ole | + SelectionType::DrawObject | + SelectionType::DbForm)) + { + eKeyState = SwKeyState::NextObject; + } + else if ( rSh.GetTableFormat() ) + { + if( rSh.HasSelection() || rSh.HasReadonlySel() ) + eKeyState = SwKeyState::NextCell; + else + { + eKeyState = SwKeyState::CheckAutoCorrect; + eNextKeyState = SwKeyState::NextCell; + } + } + else + { + eKeyState = SwKeyState::InsTab; + if( rSh.IsSttOfPara() && !rSh.HasReadonlySel() ) + { + SwTextFormatColl* pColl = rSh.GetCurTextFormatColl(); + if( pColl && + + pColl->IsAssignedToListLevelOfOutlineStyle() + && MAXLEVEL-1 > pColl->GetAssignedOutlineStyleLevel() ) + eKeyState = SwKeyState::OutlineDown; + } + } + } + break; + case KEY_TAB | KEY_SHIFT: + { + if (rSh.IsFormProtected() || rSh.GetCurrentFieldmark()|| rSh.GetChar(false)==CH_TXT_ATR_FORMELEMENT) + { + eKeyState = SwKeyState::GotoPrevFieldMark; + } + else if ( !rSh.IsMultiSelection() && rSh.CursorInsideInputField() ) + { + GetView().GetViewFrame()->GetDispatcher()->Execute( FN_GOTO_PREV_INPUTFLD ); + eKeyState = SwKeyState::End; + } + else if( rSh.GetNumRuleAtCurrCursorPos() + && rSh.IsSttOfPara() + && !rSh.HasReadonlySel() ) + { + eKeyState = SwKeyState::NumUp; + } + else if (rSh.GetSelectionType() & + (SelectionType::Graphic | + SelectionType::Frame | + SelectionType::Ole | + SelectionType::DrawObject | + SelectionType::DbForm)) + { + eKeyState = SwKeyState::PrevObject; + } + else if ( rSh.GetTableFormat() ) + { + if( rSh.HasSelection() || rSh.HasReadonlySel() ) + eKeyState = SwKeyState::PrevCell; + else + { + eKeyState = SwKeyState::CheckAutoCorrect; + eNextKeyState = SwKeyState::PrevCell; + } + } + else + { + eKeyState = SwKeyState::End; + if( rSh.IsSttOfPara() && !rSh.HasReadonlySel() ) + { + SwTextFormatColl* pColl = rSh.GetCurTextFormatColl(); + if( pColl && + pColl->IsAssignedToListLevelOfOutlineStyle() && + 0 < pColl->GetAssignedOutlineStyleLevel()) + eKeyState = SwKeyState::OutlineUp; + } + } + } + break; + case KEY_TAB | KEY_MOD1: + case KEY_TAB | KEY_MOD2: + if( !rSh.HasReadonlySel() ) + { + if( aTmpQHD.HasContent() && !rSh.HasSelection() ) + { + // Next auto-complete suggestion + aTmpQHD.Next( pACorr && + pACorr->GetSwFlags().bAutoCmpltEndless ); + eKeyState = SwKeyState::NextPrevGlossary; + } + else if( rSh.GetTableFormat() ) + eKeyState = SwKeyState::InsTab; + else if((rSh.GetSelectionType() & + (SelectionType::DrawObject|SelectionType::DbForm| + SelectionType::Frame|SelectionType::Ole|SelectionType::Graphic)) && + rSh.GetDrawView()->AreObjectsMarked()) + eKeyState = SwKeyState::EnterDrawHandleMode; + else + { + if ( !rSh.IsMultiSelection() + && numfunc::ChangeIndentOnTabAtFirstPosOfFirstListItem() ) + eKeyState = SwKeyState::NumIndentInc; + } + } + break; + + case KEY_TAB | KEY_MOD1 | KEY_SHIFT: + { + if( aTmpQHD.HasContent() && !rSh.HasSelection() && + !rSh.HasReadonlySel() ) + { + // Previous auto-complete suggestion. + aTmpQHD.Previous( pACorr && + pACorr->GetSwFlags().bAutoCmpltEndless ); + eKeyState = SwKeyState::NextPrevGlossary; + } + else if((rSh.GetSelectionType() & (SelectionType::DrawObject|SelectionType::DbForm| + SelectionType::Frame|SelectionType::Ole|SelectionType::Graphic)) && + rSh.GetDrawView()->AreObjectsMarked()) + { + eKeyState = SwKeyState::EnterDrawHandleMode; + } + else + { + if ( !rSh.IsMultiSelection() + && numfunc::ChangeIndentOnTabAtFirstPosOfFirstListItem() ) + eKeyState = SwKeyState::NumIndentDec; + } + } + break; + case KEY_F2 : + if( !rSh.HasReadonlySel() ) + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + if(nSelectionType & SelectionType::Frame) + eKeyState = SwKeyState::GoIntoFly; + else if(nSelectionType & SelectionType::DrawObject) + { + eKeyState = SwKeyState::GoIntoDrawing; + if (lcl_goIntoTextBox(*this, rSh)) + eKeyState = SwKeyState::GoIntoFly; + } + } + break; + } + } + break; + case SwKeyState::CheckDocReadOnlyKeys: + { + eKeyState = SwKeyState::KeyToView; + switch( rKeyCode.GetModifier() | rKeyCode.GetCode() ) + { + case KEY_TAB: + case KEY_TAB | KEY_SHIFT: + bNormalChar = false; + eKeyState = SwKeyState::End; + if ( rSh.GetSelectionType() & + (SelectionType::Graphic | + SelectionType::Frame | + SelectionType::Ole | + SelectionType::DrawObject | + SelectionType::DbForm)) + + { + eKeyState = (rKeyCode.GetModifier() & KEY_SHIFT) ? + SwKeyState::PrevObject : SwKeyState::NextObject; + } + else if ( !rSh.IsMultiSelection() && rSh.CursorInsideInputField() ) + { + GetView().GetViewFrame()->GetDispatcher()->Execute( + KEY_SHIFT != rKeyCode.GetModifier() ? FN_GOTO_NEXT_INPUTFLD : FN_GOTO_PREV_INPUTFLD ); + } + else + { + rSh.SelectNextPrevHyperlink( KEY_SHIFT != rKeyCode.GetModifier() ); + } + break; + case KEY_RETURN: + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + if(nSelectionType & SelectionType::Frame) + eKeyState = SwKeyState::GoIntoFly; + else + { + SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT> aSet(rSh.GetAttrPool()); + rSh.GetCurAttr(aSet); + if(SfxItemState::SET == aSet.GetItemState(RES_TXTATR_INETFMT, false)) + { + const SfxPoolItem& rItem = aSet.Get(RES_TXTATR_INETFMT); + bNormalChar = false; + eKeyState = SwKeyState::End; + rSh.ClickToINetAttr(static_cast<const SwFormatINetFormat&>(rItem)); + } + } + } + break; + } + } + break; + + case SwKeyState::EnterCharCell: + { + eKeyState = SwKeyState::KeyToView; + switch ( rKeyCode.GetModifier() | rKeyCode.GetCode() ) + { + case KEY_RIGHT | KEY_MOD2: + rSh.Right( CRSR_SKIP_CHARS, false, 1, false ); + eKeyState = SwKeyState::End; + FlushInBuffer(); + break; + case KEY_LEFT | KEY_MOD2: + rSh.Left( CRSR_SKIP_CHARS, false, 1, false ); + eKeyState = SwKeyState::End; + FlushInBuffer(); + break; + } + } + break; + + case SwKeyState::KeyToView: + { + eKeyState = SwKeyState::End; + bNormalChar = + !rKeyCode.IsMod2() && + rKeyCode.GetModifier() != KEY_MOD1 && + rKeyCode.GetModifier() != (KEY_MOD1|KEY_SHIFT) && + SW_ISPRINTABLE( aCh ); + + if( bNormalChar && rSh.IsInFrontOfLabel() ) + { + rSh.NumOrNoNum(); + } + + if( !m_aInBuffer.isEmpty() && ( !bNormalChar || bIsDocReadOnly )) + FlushInBuffer(); + + if (rSh.HasReadonlySel() + && ( rKeyCode.GetFunction() == KeyFuncType::PASTE + || rKeyCode.GetFunction() == KeyFuncType::CUT)) + { + auto xInfo(std::make_shared<weld::GenericDialogController>(GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui", "InfoReadonlyDialog")); + weld::DialogController::runAsync(xInfo, [](int) {}); + eKeyState = SwKeyState::End; + } + else if( m_rView.KeyInput( aKeyEvent ) ) + { + bFlushBuffer = true; + bNormalChar = false; + } + else + { + // Because Sfx accelerators are only called when they were + // enabled at the last status update, copy has to called + // 'forcefully' by us if necessary. + if( rKeyCode.GetFunction() == KeyFuncType::COPY ) + GetView().GetViewFrame()->GetBindings().Execute(SID_COPY); + + if( !bIsDocReadOnly && bNormalChar ) + { + const SelectionType nSelectionType = rSh.GetSelectionType(); + const bool bDrawObject = (nSelectionType & SelectionType::DrawObject) && + !(nSelectionType & SelectionType::DrawObjectEditMode) && + rSh.GetDrawView()->GetMarkedObjectList().GetMarkCount() == 1; + + bool bTextBox = false; + if (bDrawObject && lcl_goIntoTextBox(*this, rSh)) + // A draw shape was selected, but it has a TextBox, + // start editing that instead when the normal + // character is pressed. + bTextBox = true; + + if (bDrawObject && !bTextBox) + { + SdrObject* pObj = rSh.GetDrawView()->GetMarkedObjectList().GetMark(0)->GetMarkedSdrObj(); + if(pObj) + { + EnterDrawTextMode(pObj->GetLogicRect().Center()); + if ( auto pSwDrawTextShell = dynamic_cast< SwDrawTextShell *>( m_rView.GetCurShell() ) ) + pSwDrawTextShell->Init(); + rSh.GetDrawView()->KeyInput( rKEvt, this ); + } + } + else if (nSelectionType & SelectionType::Frame || bTextBox) + { + rSh.UnSelectFrame(); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + rSh.MoveSection( GoCurrSection, fnSectionEnd ); + } + eKeyState = SwKeyState::InsChar; + } + else + { + bNormalChar = false; + Window::KeyInput( aKeyEvent ); + } + } + } + break; + case SwKeyState::LaunchOLEObject: + { + rSh.LaunchOLEObj(); + eKeyState = SwKeyState::End; + } + break; + case SwKeyState::GoIntoFly: + { + rSh.UnSelectFrame(); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + rSh.MoveSection( GoCurrSection, fnSectionEnd ); + eKeyState = SwKeyState::End; + } + break; + case SwKeyState::GoIntoDrawing: + { + if (SdrMark* pMark = rSh.GetDrawView()->GetMarkedObjectList().GetMark(0)) + { + SdrObject* pObj = pMark->GetMarkedSdrObj(); + if(pObj) + { + EnterDrawTextMode(pObj->GetLogicRect().Center()); + if (auto pSwDrawTextShell = dynamic_cast< SwDrawTextShell *>( m_rView.GetCurShell() ) ) + pSwDrawTextShell->Init(); + } + } + eKeyState = SwKeyState::End; + } + break; + case SwKeyState::EnterDrawHandleMode: + { + const SdrHdlList& rHdlList = rSh.GetDrawView()->GetHdlList(); + bool bForward(!aKeyEvent.GetKeyCode().IsShift()); + + const_cast<SdrHdlList&>(rHdlList).TravelFocusHdl(bForward); + eKeyState = SwKeyState::End; + } + break; + case SwKeyState::InsTab: + if( dynamic_cast<const SwWebView*>( &m_rView) != nullptr) // no Tab for WebView + { + // then it should be passed along + Window::KeyInput( aKeyEvent ); + eKeyState = SwKeyState::End; + break; + } + aCh = '\t'; + [[fallthrough]]; + case SwKeyState::InsChar: + if (rSh.CursorInsideContentControl()) + { + const SwPosition* pStart = rSh.GetCursor()->Start(); + SwTextNode* pTextNode = pStart->nNode.GetNode().GetTextNode(); + if (pTextNode) + { + sal_Int32 nIndex = pStart->nContent.GetIndex(); + SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, SwTextNode::PARENT); + if (pAttr) + { + auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + const SwFormatContentControl& rFormatContentControl = pTextContentControl->GetContentControl(); + std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl(); + if (pContentControl->IsInteractingCharacter(aCh)) + { + rSh.GotoContentControl(rFormatContentControl); + eKeyState = SwKeyState::End; + break; + } + } + } + } + + if (rSh.GetChar(false)==CH_TXT_ATR_FORMELEMENT) + { + ::sw::mark::ICheckboxFieldmark* pFieldmark = + dynamic_cast< ::sw::mark::ICheckboxFieldmark* > + (rSh.GetCurrentFieldmark()); + OSL_ENSURE(pFieldmark, + "Where is my FieldMark??"); + if(pFieldmark) + { + pFieldmark->SetChecked(!pFieldmark->IsChecked()); + OSL_ENSURE(pFieldmark->IsExpanded(), + "where is the otherpos?"); + if (pFieldmark->IsExpanded()) + { + rSh.CalcLayout(); + } + } + eKeyState = SwKeyState::End; + } + else if ( !rSh.HasReadonlySel() + || rSh.CursorInsideInputField() ) + { + const bool bIsNormalChar = + GetAppCharClass().isLetterNumeric( OUString( aCh ), 0 ); + if( bAppendSpace && bIsNormalChar && + (!m_aInBuffer.isEmpty() || !rSh.IsSttPara() || !rSh.IsEndPara() )) + { + // insert a blank ahead of the character. this ends up + // between the expanded text and the new "non-word-separator". + m_aInBuffer += " "; + } + + const bool bIsAutoCorrectChar = SvxAutoCorrect::IsAutoCorrectChar( aCh ); + if( !aKeyEvent.GetRepeat() && pACorr && ( bIsAutoCorrectChar || rSh.IsNbspRunNext() ) && + pACfg->IsAutoFormatByInput() && + (( pACorr->IsAutoCorrFlag( ACFlags::ChgWeightUnderl ) && + ( '*' == aCh || '_' == aCh ) ) || + ( pACorr->IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == aCh ))|| + ( pACorr->IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && ( '\'' == aCh)))) + { + FlushInBuffer(); + rSh.AutoCorrect( *pACorr, aCh ); + if( '\"' != aCh && '\'' != aCh ) // only call when "*_"! + rSh.UpdateAttr(); + } + else if( !aKeyEvent.GetRepeat() && pACorr && ( bIsAutoCorrectChar || rSh.IsNbspRunNext() ) && + pACfg->IsAutoFormatByInput() && + pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord | + ACFlags::ChgOrdinalNumber | ACFlags::AddNonBrkSpace | + ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | + ACFlags::Autocorrect | ACFlags::TransliterateRTL ) && + '\"' != aCh && '\'' != aCh && '*' != aCh && '_' != aCh + ) + { + FlushInBuffer(); + rSh.AutoCorrect( *pACorr, aCh ); + } + else + { + OUStringBuffer aBuf(m_aInBuffer); + comphelper::string::padToLength(aBuf, + m_aInBuffer.getLength() + aKeyEvent.GetRepeat() + 1, aCh); + m_aInBuffer = aBuf.makeStringAndClear(); + bool delayFlush = Application::AnyInput( VclInputFlags::KEYBOARD ); + bFlushBuffer = !delayFlush; + if( delayFlush ) + { + // Start the timer, make sure to not restart it. + keyInputFlushTimerStop.dismiss(); + if( !m_aKeyInputFlushTimer.IsActive()) + m_aKeyInputFlushTimer.Start(); + } + } + eKeyState = SwKeyState::End; + } + else + { + auto xInfo(std::make_shared<weld::GenericDialogController>(GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui", "InfoReadonlyDialog")); + weld::DialogController::runAsync(xInfo, [](int) {}); + eKeyState = SwKeyState::End; + } + break; + + case SwKeyState::CheckAutoCorrect: + { + if( pACorr && pACfg->IsAutoFormatByInput() && + pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord | + ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL | + ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | + ACFlags::Autocorrect ) && + !rSh.HasReadonlySel() ) + { + FlushInBuffer(); + rSh.AutoCorrect( *pACorr, u'\0' ); + } + eKeyState = eNextKeyState; + } + break; + + default: + { + sal_uInt16 nSlotId = 0; + FlushInBuffer(); + switch( eKeyState ) + { + case SwKeyState::SpecialInsert: + rSh.DoSpecialInsert(); + break; + + case SwKeyState::NoNum: + rSh.NoNum(); + break; + + case SwKeyState::NumOff: + // shell change - so record in advance + rSh.DelNumRules(); + break; + case SwKeyState::OutlineLvOff: // delete autofmt outlinelevel later + break; + + case SwKeyState::NumDown: + rSh.NumUpDown(); + m_nKS_NUMDOWN_Count = 2; + break; + case SwKeyState::NumUp: + rSh.NumUpDown( false ); + break; + + case SwKeyState::NumIndentInc: + rSh.ChangeIndentOfAllListLevels(360); + m_nKS_NUMINDENTINC_Count = 2; + break; + + case SwKeyState::GotoNextFieldMark: + { + ::sw::mark::IFieldmark const * const pFieldmark = rSh.GetFieldmarkAfter(); + if(pFieldmark) rSh.GotoFieldmark(pFieldmark); + } + break; + + case SwKeyState::GotoPrevFieldMark: + { + ::sw::mark::IFieldmark const * const pFieldmark = rSh.GetFieldmarkBefore(); + if( pFieldmark ) + rSh.GotoFieldmark(pFieldmark); + } + break; + + case SwKeyState::NumIndentDec: + rSh.ChangeIndentOfAllListLevels(-360); + break; + + case SwKeyState::OutlineDown: + rSh.OutlineUpDown(); + break; + case SwKeyState::OutlineUp: + rSh.OutlineUpDown( -1 ); + break; + + case SwKeyState::NextCell: + // always 'flush' in tables + rSh.GoNextCell(!rSh.HasReadonlySel()); + nSlotId = FN_GOTO_NEXT_CELL; + break; + case SwKeyState::PrevCell: + rSh.GoPrevCell(); + nSlotId = FN_GOTO_PREV_CELL; + break; + case SwKeyState::AutoFormatByInput: + rSh.SplitNode( true ); + break; + + case SwKeyState::NextObject: + case SwKeyState::PrevObject: + if(rSh.GotoObj( SwKeyState::NextObject == eKeyState, GotoObjFlags::Any)) + { + if( rSh.IsFrameSelected() && + m_rView.GetDrawFuncPtr() ) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + rSh.HideCursor(); + rSh.EnterSelFrameMode(); + } + break; + case SwKeyState::GlossaryExpand: + { + // replace the word or abbreviation with the auto text + rSh.StartUndo( SwUndoId::START ); + + OUString sFnd(aTmpQHD.CurStr()); + if( aTmpQHD.m_bIsAutoText ) + { + SwGlossaryList* pList = ::GetGlossaryList(); + OUString sShrtNm; + OUString sGroup; + if(pList->GetShortName( sFnd, sShrtNm, sGroup)) + { + rSh.SttSelect(); + rSh.ExtendSelection(false, aTmpQHD.CurLen()); + SwGlossaryHdl* pGlosHdl = GetView().GetGlosHdl(); + pGlosHdl->SetCurGroup(sGroup, true); + pGlosHdl->InsertGlossary( sShrtNm); + s_pQuickHlpData->m_bAppendSpace = true; + } + } + else + { + sFnd = sFnd.copy(aTmpQHD.CurLen()); + rSh.Insert( sFnd ); + s_pQuickHlpData->m_bAppendSpace = !pACorr || + pACorr->GetSwFlags().bAutoCmpltAppendBlank; + } + rSh.EndUndo( SwUndoId::END ); + } + break; + + case SwKeyState::NextPrevGlossary: + s_pQuickHlpData->Move( aTmpQHD ); + s_pQuickHlpData->Start(rSh, false); + break; + + case SwKeyState::EditFormula: + { + const sal_uInt16 nId = SwInputChild::GetChildWindowId(); + + SfxViewFrame* pVFrame = GetView().GetViewFrame(); + pVFrame->ToggleChildWindow( nId ); + SwInputChild* pChildWin = static_cast<SwInputChild*>(pVFrame-> + GetChildWindow( nId )); + if( pChildWin ) + pChildWin->SetFormula( sFormulaEntry ); + } + break; + + case SwKeyState::ColLeftBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::ColLeft|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableHMove() ); break; + case SwKeyState::ColRightBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::ColRight|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableHMove() ); break; + case SwKeyState::ColLeftSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::ColLeft, pModOpt->GetTableHMove() ); break; + case SwKeyState::ColRightSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::ColRight, pModOpt->GetTableHMove() ); break; + case SwKeyState::ColBottomBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::RowBottom|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableVMove() ); break; + case SwKeyState::ColBottomSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::RowBottom, pModOpt->GetTableVMove() ); break; + case SwKeyState::CellLeftBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellLeft|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableHMove() ); break; + case SwKeyState::CellRightBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellRight|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableHMove() ); break; + case SwKeyState::CellLeftSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellLeft, pModOpt->GetTableHMove() ); break; + case SwKeyState::CellRightSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellRight, pModOpt->GetTableHMove() ); break; + case SwKeyState::CellTopBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellTop|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableVMove() ); break; + case SwKeyState::CellBottomBig: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellBottom|TableChgWidthHeightType::BiggerMode, pModOpt->GetTableVMove() ); break; + case SwKeyState::CellTopSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellTop, pModOpt->GetTableVMove() ); break; + case SwKeyState::CellBottomSmall: rSh.SetColRowWidthHeight( TableChgWidthHeightType::CellBottom, pModOpt->GetTableVMove() ); break; + + case SwKeyState::Fly_Change: + { + SdrView *pSdrView = rSh.GetDrawView(); + const SdrHdlList& rHdlList = pSdrView->GetHdlList(); + if(rHdlList.GetFocusHdl()) + ChangeDrawing( nDir ); + else + ChangeFly( nDir, dynamic_cast<const SwWebView*>( &m_rView) != nullptr ); + } + break; + case SwKeyState::Draw_Change : + ChangeDrawing( nDir ); + break; + default: + break; + } + if( nSlotId && m_rView.GetViewFrame()->GetBindings().GetRecorder().is() ) + { + SfxRequest aReq(m_rView.GetViewFrame(), nSlotId ); + aReq.Done(); + } + eKeyState = SwKeyState::End; + } + } + } + + // update the page number in the statusbar + sal_uInt16 nKey = rKEvt.GetKeyCode().GetCode(); + if( KEY_UP == nKey || KEY_DOWN == nKey || KEY_PAGEUP == nKey || KEY_PAGEDOWN == nKey ) + GetView().GetViewFrame()->GetBindings().Update( FN_STAT_PAGE ); + + // in case the buffered characters are inserted + if( bFlushBuffer && !m_aInBuffer.isEmpty() ) + { + FlushInBuffer(); + + // maybe show Tip-Help + if (bNormalChar) + { + const bool bAutoTextShown + = pACfg && pACfg->IsAutoTextTip() && ShowAutoText(rSh.GetChunkForAutoText()); + if (!bAutoTextShown && pACorr && pACorr->GetSwFlags().bAutoCompleteWords) + ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr); + } + } + + // get the word count dialog to update itself + SwWordCountWrapper *pWrdCnt = static_cast<SwWordCountWrapper*>(GetView().GetViewFrame()->GetChildWindow(SwWordCountWrapper::GetChildWindowId())); + if( pWrdCnt ) + pWrdCnt->UpdateCounts(); + +} + +/** + * MouseEvents + */ +void SwEditWin::RstMBDownFlags() +{ + // Not on all systems a MouseButtonUp is used ahead + // of the modal dialog (like on WINDOWS). + // So reset the statuses here and release the mouse + // for the dialog. + m_bMBPressed = false; + g_bNoInterrupt = false; + EnterArea(); + ReleaseMouse(); +} + +/** + * Determines if the current position has a clickable url over a background + * frame. In that case, ctrl-click should select the url, not the frame. + */ +static bool lcl_urlOverBackground(SwWrtShell& rSh, const Point& rDocPos) +{ + SwContentAtPos aSwContentAtPos(IsAttrAtPos::InetAttr); + SdrObject* pSelectableObj = rSh.GetObjAt(rDocPos); + + return rSh.GetContentAtPos(rDocPos, aSwContentAtPos) && pSelectableObj->GetLayer() == rSh.GetDoc()->getIDocumentDrawModelAccess().GetHellId(); +} + +void SwEditWin::MoveCursor( SwWrtShell &rSh, const Point& rDocPos, + const bool bOnlyText, bool bLockView ) +{ + const bool bTmpNoInterrupt = g_bNoInterrupt; + g_bNoInterrupt = false; + + int nTmpSetCursor = 0; + + if( !rSh.IsViewLocked() && bLockView ) + rSh.LockView( true ); + else + bLockView = false; + + { + // only temporary generate move context because otherwise + // the query to the content form doesn't work!!! + SwMvContext aMvContext( &rSh ); + nTmpSetCursor = rSh.CallSetCursor(&rDocPos, bOnlyText); + g_bValidCursorPos = !(CRSR_POSCHG & nTmpSetCursor); + } + + // notify the edit window that from now on we do not use the input language + if ( !(CRSR_POSOLD & nTmpSetCursor) ) + SetUseInputLanguage( false ); + + if( bLockView ) + rSh.LockView( false ); + + g_bNoInterrupt = bTmpNoInterrupt; +} + +void SwEditWin::MouseButtonDown(const MouseEvent& _rMEvt) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + const SwField *pCursorField = rSh.CursorInsideInputField() ? rSh.GetCurField( true ) : nullptr; + + // We have to check if a context menu is shown and we have an UI + // active inplace client. In that case we have to ignore the mouse + // button down event. Otherwise we would crash (context menu has been + // opened by inplace client and we would deactivate the inplace client, + // the context menu is closed by VCL asynchronously which in the end + // would work on deleted objects or the context menu has no parent anymore) + SfxInPlaceClient* pIPClient = rSh.GetSfxViewShell()->GetIPClient(); + bool bIsOleActive = ( pIPClient && pIPClient->IsObjectInPlaceActive() ); + + if (bIsOleActive && vcl::IsInPopupMenuExecute()) + return; + + MouseEvent aMEvt(_rMEvt); + + if (m_rView.GetPostItMgr()->IsHit(aMEvt.GetPosPixel())) + return; + + if (comphelper::LibreOfficeKit::isActive()) + { + if (vcl::Window* pWindow = m_rView.GetPostItMgr()->IsHitSidebarWindow(aMEvt.GetPosPixel())) + { + pWindow->MouseButtonDown(aMEvt); + return; + } + } + + m_rView.GetPostItMgr()->SetActiveSidebarWin(nullptr); + + GrabFocus(); + rSh.addCurrentPosition(); + + //ignore key modifiers for format paintbrush + { + bool bExecFormatPaintbrush = m_pApplyTempl && m_pApplyTempl->m_pFormatClipboard + && m_pApplyTempl->m_pFormatClipboard->HasContent(); + if( bExecFormatPaintbrush ) + aMEvt = MouseEvent(_rMEvt.GetPosPixel(), _rMEvt.GetClicks(), _rMEvt.GetMode(), + _rMEvt.GetButtons()); + } + + m_bWasShdwCursor = nullptr != m_pShadCursor; + m_pShadCursor.reset(); + + const Point aDocPos(PixelToLogic(aMEvt.GetPosPixel())); + + FrameControlType eControl; + bool bOverFly = false; + bool bPageAnchored = false; + bool bOverHeaderFooterFly = IsOverHeaderFooterFly( aDocPos, eControl, bOverFly, bPageAnchored ); + + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly(); + if (bOverHeaderFooterFly && (!bIsDocReadOnly && rSh.GetCurField())) + // We have a field here, that should have priority over header/footer fly. + bOverHeaderFooterFly = false; + + // Are we clicking on a blank header/footer area? + if ( IsInHeaderFooter( aDocPos, eControl ) || bOverHeaderFooterFly ) + { + const SwPageFrame* pPageFrame = rSh.GetLayout()->GetPageAtPos( aDocPos ); + + if ( pPageFrame ) + { + // Is it active? + bool bActive = true; + const SwPageDesc* pDesc = pPageFrame->GetPageDesc(); + + const SwFrameFormat* pFormat = pDesc->GetLeftFormat(); + if ( pPageFrame->OnRightPage() ) + pFormat = pDesc->GetRightFormat(); + + if ( pFormat ) + { + if ( eControl == FrameControlType::Header ) + bActive = pFormat->GetHeader().IsActive(); + else + bActive = pFormat->GetFooter().IsActive(); + } + + if ( !bActive ) + { + // When in Hide-Whitespace mode, we don't want header + // and footer controls. + if (!rSh.GetViewOptions()->IsHideWhitespaceMode()) + { + SwPaM aPam(*rSh.GetCurrentShellCursor().GetPoint()); + const bool bWasInHeader = aPam.GetPoint()->nNode.GetNode().FindHeaderStartNode() != nullptr; + const bool bWasInFooter = aPam.GetPoint()->nNode.GetNode().FindFooterStartNode() != nullptr; + + // Is the cursor in a part like similar to the one we clicked on? For example, + // if the cursor is in a header and we click on an empty header... don't change anything to + // keep consistent behaviour due to header edit mode (and the same for the footer as well). + + // Otherwise, we hide the header/footer control if a separator is shown, and vice versa. + if (!(bWasInHeader && eControl == FrameControlType::Header) && + !(bWasInFooter && eControl == FrameControlType::Footer)) + { + const bool bSeparatorWasVisible = rSh.IsShowHeaderFooterSeparator(eControl); + rSh.SetShowHeaderFooterSeparator(eControl, !bSeparatorWasVisible); + + // Repaint everything + Invalidate(); + + // tdf#84929. If the footer control had not been showing, do not change the cursor position, + // because the user may have scrolled to turn on the separator control and + // if the cursor cannot be positioned on-screen, then the user would need to scroll back again to use the control. + // This should only be done for the footer. The cursor can always be re-positioned near the header. tdf#134023. + if ( eControl == FrameControlType::Footer && !bSeparatorWasVisible + && rSh.GetViewOptions()->IsUseHeaderFooterMenu() && !Application::IsHeadlessModeEnabled() ) + return; + } + } + } + else + { + // Make sure we have the proper Header/Footer separators shown + // as these may be changed if clicking on an empty Header/Footer + rSh.SetShowHeaderFooterSeparator( FrameControlType::Header, eControl == FrameControlType::Header ); + rSh.SetShowHeaderFooterSeparator( FrameControlType::Footer, eControl == FrameControlType::Footer ); + + if ( !rSh.IsHeaderFooterEdit() ) + { + rSh.ToggleHeaderFooterEdit(); + + // Repaint everything + rSh.GetWin()->Invalidate(); + } + } + } + } + else + { + if ( rSh.IsHeaderFooterEdit( ) ) + rSh.ToggleHeaderFooterEdit( ); + else + { + // Make sure that the separators are hidden + rSh.SetShowHeaderFooterSeparator( FrameControlType::Header, false ); + rSh.SetShowHeaderFooterSeparator( FrameControlType::Footer, false ); + + // Repaint everything + // FIXME fdo#67358 for unknown reasons this causes painting + // problems when resizing table columns, so disable it +// rSh.GetWin()->Invalidate(); + } + + // Toggle Hide-Whitespace if between pages. + if (rSh.GetViewOptions()->CanHideWhitespace() && + rSh.GetLayout()->IsBetweenPages(aDocPos)) + { + if (_rMEvt.GetClicks() >= 2) + { + SwViewOption aOpt(*rSh.GetViewOptions()); + aOpt.SetHideWhitespaceMode(!aOpt.IsHideWhitespaceMode()); + rSh.ApplyViewOptions(aOpt); + } + + return; + } + } + + if ( IsChainMode() ) + { + SetChainMode( false ); + SwRect aDummy; + SwFlyFrameFormat *pFormat = static_cast<SwFlyFrameFormat*>(rSh.GetFlyFrameFormat()); + if ( rSh.Chainable( aDummy, *pFormat, aDocPos ) == SwChainRet::OK ) + rSh.Chain( *pFormat, aDocPos ); + UpdatePointer(aDocPos, aMEvt.GetModifier()); + return; + } + + // After GrabFocus a shell should be pushed. That should actually + // work but in practice ... + m_rView.SelectShellForDrop(); + + bool bCallBase = true; + + if( s_pQuickHlpData->m_bIsDisplayed ) + s_pQuickHlpData->Stop( rSh ); + s_pQuickHlpData->m_bAppendSpace = false; + + if( rSh.FinishOLEObj() ) + return; // end InPlace and the click doesn't count anymore + + CurrShell aCurr( &rSh ); + + SdrView *pSdrView = rSh.GetDrawView(); + if ( pSdrView ) + { + if (pSdrView->MouseButtonDown(aMEvt, GetOutDev())) + { + rSh.GetView().GetViewFrame()->GetBindings().InvalidateAll(false); + return; // SdrView's event evaluated + } + } + + m_bIsInMove = false; + m_aStartPos = aMEvt.GetPosPixel(); + m_aRszMvHdlPt.setX( 0 ); + m_aRszMvHdlPt.setY( 0 ); + + SwTab nMouseTabCol = SwTab::COL_NONE; + const bool bTmp = !rSh.IsDrawCreate() && !m_pApplyTempl && !rSh.IsInSelect() + && aMEvt.GetClicks() == 1 && MOUSE_LEFT == aMEvt.GetButtons(); + if ( bTmp && + SwTab::COL_NONE != (nMouseTabCol = rSh.WhichMouseTabCol( aDocPos ) ) && + !rSh.IsObjSelectable( aDocPos ) ) + { + // Enhanced table selection + if ( SwTab::SEL_HORI <= nMouseTabCol && SwTab::COLSEL_VERT >= nMouseTabCol ) + { + rSh.EnterStdMode(); + rSh.SelectTableRowCol( aDocPos ); + if( SwTab::SEL_HORI != nMouseTabCol && SwTab::SEL_HORI_RTL != nMouseTabCol) + { + m_xRowColumnSelectionStart = aDocPos; + m_bIsRowDrag = SwTab::ROWSEL_HORI == nMouseTabCol|| + SwTab::ROWSEL_HORI_RTL == nMouseTabCol || + SwTab::COLSEL_VERT == nMouseTabCol; + m_bMBPressed = true; + CaptureMouse(); + } + return; + } + + if ( !rSh.IsTableMode() ) + { + // comes from table columns out of the document. + if(SwTab::COL_VERT == nMouseTabCol || SwTab::COL_HORI == nMouseTabCol) + m_rView.SetTabColFromDoc( true ); + else + m_rView.SetTabRowFromDoc( true ); + + m_rView.SetTabColFromDocPos( aDocPos ); + m_rView.InvalidateRulerPos(); + SfxBindings& rBind = m_rView.GetViewFrame()->GetBindings(); + rBind.Update(); + if (RulerColumnDrag( + aMEvt, (SwTab::COL_VERT == nMouseTabCol || SwTab::ROW_HORI == nMouseTabCol))) + { + m_rView.SetTabColFromDoc( false ); + m_rView.SetTabRowFromDoc( false ); + m_rView.InvalidateRulerPos(); + rBind.Update(); + bCallBase = false; + } + else + { + return; + } + } + } + else if (bTmp && + rSh.IsNumLabel(aDocPos)) + { + SwTextNode* pNodeAtPos = rSh.GetNumRuleNodeAtPos( aDocPos ); + m_rView.SetNumRuleNodeFromDoc( pNodeAtPos ); + m_rView.InvalidateRulerPos(); + SfxBindings& rBind = m_rView.GetViewFrame()->GetBindings(); + rBind.Update(); + + if (RulerMarginDrag(aMEvt, SwFEShell::IsVerticalModeAtNdAndPos(*pNodeAtPos, aDocPos))) + { + m_rView.SetNumRuleNodeFromDoc( nullptr ); + m_rView.InvalidateRulerPos(); + rBind.Update(); + bCallBase = false; + } + else + { + // Make sure the pointer is set to 0, otherwise it may point to + // nowhere after deleting the corresponding text node. + m_rView.SetNumRuleNodeFromDoc( nullptr ); + return; + } + } + + if ( rSh.IsInSelect() ) + rSh.EndSelect(); + + // query against LEFT because otherwise for example also a right + // click releases the selection. + if (MOUSE_LEFT == aMEvt.GetButtons()) + { + bool bOnlyText = false; + m_bMBPressed = true; + g_bNoInterrupt = true; + m_nKS_NUMDOWN_Count = 0; + + CaptureMouse(); + + // reset cursor position if applicable + rSh.ResetCursorStack(); + + switch (aMEvt.GetModifier() + aMEvt.GetButtons()) + { + case MOUSE_LEFT: + case MOUSE_LEFT + KEY_SHIFT: + case MOUSE_LEFT + KEY_MOD2: + if( rSh.IsObjSelected() ) + { + SdrHdl* pHdl; + if( !bIsDocReadOnly && + !m_pAnchorMarker && + pSdrView && + nullptr != ( pHdl = pSdrView->PickHandle(aDocPos) ) && + ( pHdl->GetKind() == SdrHdlKind::Anchor || + pHdl->GetKind() == SdrHdlKind::Anchor_TR ) ) + { + // #i121463# Set selected during drag + pHdl->SetSelected(); + m_pAnchorMarker.reset( new SwAnchorMarker( pHdl ) ); + UpdatePointer(aDocPos, aMEvt.GetModifier()); + return; + } + } + if (EnterDrawMode(aMEvt, aDocPos)) + { + g_bNoInterrupt = false; + return; + } + else if ( m_rView.GetDrawFuncPtr() && m_bInsFrame ) + { + StopInsFrame(); + rSh.Edit(); + } + + // Without SHIFT because otherwise Toggle doesn't work at selection + if (aMEvt.GetClicks() == 1) + { + if ( rSh.IsSelFrameMode()) + { + SdrHdl* pHdl = rSh.GetDrawView()->PickHandle(aDocPos); + bool bHitHandle = pHdl && pHdl->GetKind() != SdrHdlKind::Anchor && + pHdl->GetKind() != SdrHdlKind::Anchor_TR; + + if ((rSh.IsInsideSelectedObj(aDocPos) || bHitHandle) + && (aMEvt.GetModifier() != KEY_SHIFT || bHitHandle)) + { + rSh.EnterSelFrameMode( &aDocPos ); + if ( !m_pApplyTempl ) + { + // only if no position to size was hit. + if (!bHitHandle) + { + StartDDTimer(); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + } + g_bFrameDrag = true; + } + g_bNoInterrupt = false; + return; + } + } + } + } + + bool bExecHyperlinks = m_rView.GetDocShell()->IsReadOnly(); + if ( !bExecHyperlinks ) + { + const bool bSecureOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink ); + if ((bSecureOption && aMEvt.GetModifier() == KEY_MOD1) + || (!bSecureOption && aMEvt.GetModifier() != KEY_MOD1)) + bExecHyperlinks = true; + } + + // Enhanced selection + sal_uInt8 nNumberOfClicks = static_cast<sal_uInt8>(aMEvt.GetClicks() % 4); + if (0 == nNumberOfClicks && 0 < aMEvt.GetClicks()) + nNumberOfClicks = 4; + + bool bExecDrawTextLink = false; + + switch (aMEvt.GetModifier() + aMEvt.GetButtons()) + { + case MOUSE_LEFT: + case MOUSE_LEFT + KEY_MOD1: + case MOUSE_LEFT + KEY_MOD2: + { + + // fdo#79604: first, check if a link has been clicked - do not + // select fly in this case! + if (1 == nNumberOfClicks) + { + UpdatePointer(aDocPos, aMEvt.GetModifier()); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + + // hit a URL in DrawText object? + if (bExecHyperlinks && pSdrView) + { + SdrViewEvent aVEvt; + pSdrView->PickAnything(aMEvt, SdrMouseEventKind::BUTTONDOWN, aVEvt); + + if (aVEvt.meEvent == SdrEventKind::ExecuteUrl) + bExecDrawTextLink = true; + } + } + + if (1 == nNumberOfClicks && !bExecDrawTextLink) + { + // only try to select frame, if pointer already was + // switched accordingly + if ( m_aActHitType != SdrHitKind::NONE && !rSh.IsSelFrameMode() && + !GetView().GetViewFrame()->GetDispatcher()->IsLocked()) + { + // Test if there is a draw object at that position and if it should be selected. + bool bShould = rSh.ShouldObjectBeSelected(aDocPos); + + if(bShould) + { + m_rView.NoRotate(); + rSh.HideCursor(); + + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + bool bSelObj + = rSh.SelectObj(aDocPos, aMEvt.IsMod1() ? SW_ENTER_GROUP : 0); + if( bUnLockView ) + rSh.LockView( false ); + + if( bSelObj ) + { + // if the frame was deselected in the macro + // the cursor just has to be displayed again + if( FrameTypeFlags::NONE == rSh.GetSelFrameType() ) + rSh.ShowCursor(); + else + { + if (rSh.IsFrameSelected() && m_rView.GetDrawFuncPtr()) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + + rSh.EnterSelFrameMode( &aDocPos ); + g_bFrameDrag = true; + UpdatePointer(aDocPos, aMEvt.GetModifier()); + } + return; + } + else + bOnlyText = rSh.IsObjSelectable( aDocPos ); + + if (!m_rView.GetDrawFuncPtr()) + rSh.ShowCursor(); + } + else + bOnlyText = KEY_MOD1 != aMEvt.GetModifier(); + } + else if ( rSh.IsSelFrameMode() && + (m_aActHitType == SdrHitKind::NONE || + !rSh.IsInsideSelectedObj( aDocPos ))) + { + m_rView.NoRotate(); + SdrHdl *pHdl; + if( !bIsDocReadOnly && !m_pAnchorMarker && nullptr != + ( pHdl = pSdrView->PickHandle(aDocPos) ) && + ( pHdl->GetKind() == SdrHdlKind::Anchor || + pHdl->GetKind() == SdrHdlKind::Anchor_TR ) ) + { + m_pAnchorMarker.reset( new SwAnchorMarker( pHdl ) ); + UpdatePointer(aDocPos, aMEvt.GetModifier()); + return; + } + else + { + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + sal_uInt8 nFlag = aMEvt.IsShift() ? SW_ADD_SELECT : 0; + if (aMEvt.IsMod1()) + nFlag = nFlag | SW_ENTER_GROUP; + + if ( rSh.IsSelFrameMode() ) + { + rSh.UnSelectFrame(); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + } + + bool bSelObj = rSh.SelectObj( aDocPos, nFlag ); + if( bUnLockView ) + rSh.LockView( false ); + + if( !bSelObj ) + { + // move cursor here so that it is not drawn in the + // frame first; ShowCursor() happens in LeaveSelFrameMode() + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPos, false)); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + bCallBase = false; + } + else + { + rSh.HideCursor(); + rSh.EnterSelFrameMode( &aDocPos ); + rSh.SelFlyGrabCursor(); + rSh.MakeSelVisible(); + g_bFrameDrag = true; + if( rSh.IsFrameSelected() && + m_rView.GetDrawFuncPtr() ) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + UpdatePointer(aDocPos, aMEvt.GetModifier()); + return; + } + } + } + } + + switch ( nNumberOfClicks ) + { + case 1: + break; + case 2: + { + g_bFrameDrag = false; + if (!bIsDocReadOnly && rSh.IsInsideSelectedObj(aDocPos) + && (FlyProtectFlags::NONE + == rSh.IsSelObjProtected(FlyProtectFlags::Content + | FlyProtectFlags::Parent) + || rSh.GetSelectionType() == SelectionType::Ole)) + { + /* This is no good: on the one hand GetSelectionType is used as flag field + * (take a look into the GetSelectionType method) and on the other hand the + * return value is used in a switch without proper masking (very nice), this must lead to trouble + */ + switch ( rSh.GetSelectionType() & ~SelectionType( SelectionType::FontWork | SelectionType::ExtrudedCustomShape ) ) + { + case SelectionType::Graphic: + RstMBDownFlags(); + if (!comphelper::LibreOfficeKit::isActive()) + { + GetView().GetViewFrame()->GetBindings().Execute( + FN_FORMAT_GRAFIC_DLG, nullptr, + SfxCallMode::RECORD|SfxCallMode::SLOT); + } + return; + + // double click on OLE object --> OLE-InPlace + case SelectionType::Ole: + RstMBDownFlags(); + rSh.LaunchOLEObj(); + return; + + case SelectionType::Frame: + RstMBDownFlags(); + if (!comphelper::LibreOfficeKit::isActive()) + { + GetView().GetViewFrame()->GetBindings().Execute( + FN_FORMAT_FRAME_DLG, nullptr, + SfxCallMode::RECORD|SfxCallMode::SLOT); + } + return; + + case SelectionType::DrawObject: + RstMBDownFlags(); + EnterDrawTextMode(aDocPos); + if ( auto pSwDrawTextShell = dynamic_cast< SwDrawTextShell *>( m_rView.GetCurShell() ) ) + pSwDrawTextShell->Init(); + return; + + default: break; + } + } + + // if the cursor position was corrected or if a Fly + // was selected in ReadOnlyMode, no word selection, except when tiled rendering. + if ((!g_bValidCursorPos || rSh.IsFrameSelected()) && !comphelper::LibreOfficeKit::isActive()) + return; + + SwField *pField; + bool bFootnote = false; + + if( !bIsDocReadOnly && + (nullptr != (pField = rSh.GetCurField(true)) || + ( bFootnote = rSh.GetCurFootnote() ) ) ) + { + RstMBDownFlags(); + if( bFootnote ) + GetView().GetViewFrame()->GetBindings().Execute( FN_EDIT_FOOTNOTE ); + else + { + SwFieldTypesEnum nTypeId = pField->GetTypeId(); + SfxViewFrame* pVFrame = GetView().GetViewFrame(); + switch( nTypeId ) + { + case SwFieldTypesEnum::Postit: + case SwFieldTypesEnum::Script: + { + // if it's a Readonly region, status has to be enabled + sal_uInt16 nSlot = SwFieldTypesEnum::Postit == nTypeId ? FN_POSTIT : FN_JAVAEDIT; + SfxBoolItem aItem(nSlot, true); + pVFrame->GetBindings().SetState(aItem); + pVFrame->GetBindings().Execute(nSlot); + break; + } + case SwFieldTypesEnum::Authority : + pVFrame->GetBindings().Execute(FN_EDIT_AUTH_ENTRY_DLG); + break; + case SwFieldTypesEnum::Input: + case SwFieldTypesEnum::Dropdown: + case SwFieldTypesEnum::SetInput: + pVFrame->GetBindings().Execute(FN_UPDATE_INPUTFIELDS); + break; + default: + pVFrame->GetBindings().Execute(FN_EDIT_FIELD); + } + } + return; + } + // in extended mode double and triple + // click has no effect. + if ( rSh.IsExtMode() || rSh.IsBlockMode() ) + return; + + // select word, AdditionalMode if applicable + if (KEY_MOD1 == aMEvt.GetModifier() && !rSh.IsAddMode()) + { + rSh.EnterAddMode(); + rSh.SelWrd( &aDocPos ); + rSh.LeaveAddMode(); + } + else + { + if (!rSh.SelWrd(&aDocPos) && comphelper::LibreOfficeKit::isActive()) + // Double click did not select any word: try to + // select the current cell in case we are in a + // table. + rSh.SelTableBox(); + } + + SwContentAtPos aContentAtPos(IsAttrAtPos::FormControl); + if( rSh.GetContentAtPos( aDocPos, aContentAtPos ) && + aContentAtPos.aFnd.pFieldmark != nullptr) + { + IFieldmark *pFieldBM = const_cast< IFieldmark* > ( aContentAtPos.aFnd.pFieldmark ); + if ( pFieldBM->GetFieldname( ) == ODF_FORMDROPDOWN || pFieldBM->GetFieldname( ) == ODF_FORMDATE ) + { + RstMBDownFlags(); + rSh.getIDocumentMarkAccess()->ClearFieldActivation(); + GetView().GetViewFrame()->GetBindings().Execute(SID_FM_CTL_PROPERTIES); + return; + } + } + + g_bHoldSelection = true; + return; + } + case 3: + case 4: + { + g_bFrameDrag = false; + // in extended mode double and triple + // click has no effect. + if ( rSh.IsExtMode() ) + return; + + // if the cursor position was corrected or if a Fly + // was selected in ReadOnlyMode, no word selection. + if ( !g_bValidCursorPos || rSh.IsFrameSelected() ) + return; + + // select line, AdditionalMode if applicable + const bool bMod = KEY_MOD1 == aMEvt.GetModifier() && !rSh.IsAddMode(); + + if ( bMod ) + rSh.EnterAddMode(); + + // Enhanced selection + if ( 3 == nNumberOfClicks ) + rSh.SelSentence( &aDocPos ); + else + rSh.SelPara( &aDocPos ); + + if ( bMod ) + rSh.LeaveAddMode(); + + g_bHoldSelection = true; + return; + } + + default: + return; + } + + [[fallthrough]]; + } + case MOUSE_LEFT + KEY_SHIFT: + case MOUSE_LEFT + KEY_SHIFT + KEY_MOD1: + { + bool bLockView = m_bWasShdwCursor; + + switch (aMEvt.GetModifier()) + { + case KEY_MOD1 + KEY_SHIFT: + { + if ( !m_bInsDraw && IsDrawObjSelectable( rSh, aDocPos ) ) + { + m_rView.NoRotate(); + rSh.HideCursor(); + if ( rSh.IsSelFrameMode() ) + rSh.SelectObj(aDocPos, SW_ADD_SELECT | SW_ENTER_GROUP); + else + { if ( rSh.SelectObj( aDocPos, SW_ADD_SELECT | SW_ENTER_GROUP ) ) + { + rSh.EnterSelFrameMode( &aDocPos ); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + g_bFrameDrag = true; + return; + } + } + } + else if( rSh.IsSelFrameMode() && + rSh.GetDrawView()->PickHandle( aDocPos )) + { + g_bFrameDrag = true; + g_bNoInterrupt = false; + return; + } + } + break; + case KEY_MOD1: + if ( !bExecDrawTextLink ) + { + if (rSh.GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + // ctrl+left-click on outline node frame + SwContentAtPos aContentAtPos(IsAttrAtPos::Outline); + if(rSh.GetContentAtPos(aDocPos, aContentAtPos)) + { + SwOutlineNodes::size_type nPos; + if (rSh.GetNodes().GetOutLineNds().Seek_Entry(aContentAtPos.aFnd.pNode, &nPos)) + { + ToggleOutlineContentVisibility(nPos, false); + return; + } + } + } + if ( !m_bInsDraw && IsDrawObjSelectable( rSh, aDocPos ) && !lcl_urlOverBackground( rSh, aDocPos ) ) + { + m_rView.NoRotate(); + rSh.HideCursor(); + if ( rSh.IsSelFrameMode() ) + rSh.SelectObj(aDocPos, SW_ENTER_GROUP); + else + { if ( rSh.SelectObj( aDocPos, SW_ENTER_GROUP ) ) + { + rSh.EnterSelFrameMode( &aDocPos ); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + g_bFrameDrag = true; + return; + } + } + } + else if( rSh.IsSelFrameMode() && + rSh.GetDrawView()->PickHandle( aDocPos )) + { + g_bFrameDrag = true; + g_bNoInterrupt = false; + return; + } + else + { + if ( !rSh.IsAddMode() && !rSh.IsExtMode() && !rSh.IsBlockMode() ) + { + rSh.PushMode(); + g_bModePushed = true; + + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + rSh.EnterAddMode(); + if( bUnLockView ) + rSh.LockView( false ); + } + bCallBase = false; + } + } + break; + case KEY_MOD2: + { + if ( !rSh.IsAddMode() && !rSh.IsExtMode() && !rSh.IsBlockMode() ) + { + rSh.PushMode(); + g_bModePushed = true; + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + rSh.EnterBlockMode(); + if( bUnLockView ) + rSh.LockView( false ); + } + bCallBase = false; + } + break; + case KEY_SHIFT: + { + if ( !m_bInsDraw && IsDrawObjSelectable( rSh, aDocPos ) ) + { + m_rView.NoRotate(); + rSh.HideCursor(); + if ( rSh.IsSelFrameMode() ) + { + rSh.SelectObj(aDocPos, SW_ADD_SELECT); + + const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); + if (rMarkList.GetMark(0) == nullptr) + { + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + g_bFrameDrag = false; + } + } + else + { if ( rSh.SelectObj( aDocPos ) ) + { + rSh.EnterSelFrameMode( &aDocPos ); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + g_bFrameDrag = true; + return; + } + } + } + else + { + if ( rSh.IsSelFrameMode() && + rSh.IsInsideSelectedObj( aDocPos ) ) + { + rSh.EnterSelFrameMode( &aDocPos ); + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + g_bFrameDrag = true; + return; + } + if ( rSh.IsSelFrameMode() ) + { + rSh.UnSelectFrame(); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + g_bFrameDrag = false; + } + if ( !rSh.IsExtMode() ) + { + // don't start a selection when an + // URL field or a graphic is clicked + bool bSttSelect = rSh.HasSelection() || + PointerStyle::RefHand != GetPointer(); + + if( !bSttSelect ) + { + bSttSelect = true; + if( bExecHyperlinks ) + { + SwContentAtPos aContentAtPos( + IsAttrAtPos::Ftn | + IsAttrAtPos::InetAttr ); + + if( rSh.GetContentAtPos( aDocPos, aContentAtPos ) ) + { + if( !rSh.IsViewLocked() && + !rSh.IsReadOnlyAvailable() && + aContentAtPos.IsInProtectSect() ) + bLockView = true; + + bSttSelect = false; + } + else if( rSh.IsURLGrfAtPos( aDocPos )) + bSttSelect = false; + } + } + + if( bSttSelect ) + rSh.SttSelect(); + } + } + bCallBase = false; + break; + } + default: + if( !rSh.IsViewLocked() ) + { + SwContentAtPos aContentAtPos( IsAttrAtPos::ClickField | + IsAttrAtPos::InetAttr ); + if( rSh.GetContentAtPos( aDocPos, aContentAtPos ) && + !rSh.IsReadOnlyAvailable() && + aContentAtPos.IsInProtectSect() ) + bLockView = true; + } + } + + if ( rSh.IsGCAttr() ) + { + rSh.GCAttr(); + rSh.ClearGCAttr(); + } + + SwContentAtPos aFieldAtPos(IsAttrAtPos::Field); + bool bEditableFieldClicked = false; + + // Are we clicking on a field? + if (rSh.GetContentAtPos(aDocPos, aFieldAtPos)) + { + bool bEditableField = (aFieldAtPos.pFndTextAttr != nullptr + && aFieldAtPos.pFndTextAttr->Which() == RES_TXTATR_INPUTFIELD); + + if (!bEditableField) + { + rSh.CallSetCursor(&aDocPos, bOnlyText); + // Unfortunately the cursor may be on field + // position or on position after field depending on which + // half of the field was clicked on. + SwTextAttr const*const pTextField(aFieldAtPos.pFndTextAttr); + if (pTextField && rSh.GetCurrentShellCursor().GetPoint()->nContent + .GetIndex() != pTextField->GetStart()) + { + assert(rSh.GetCurrentShellCursor().GetPoint()->nContent + .GetIndex() == (pTextField->GetStart() + 1)); + rSh.Left( CRSR_SKIP_CHARS, false, 1, false ); + } + // don't go into the !bOverSelect block below - it moves + // the cursor + break; + } + else + { + bEditableFieldClicked = true; + } + } + + bool bOverSelect = rSh.TestCurrPam( aDocPos ); + bool bOverURLGrf = false; + if( !bOverSelect ) + bOverURLGrf = bOverSelect = nullptr != rSh.IsURLGrfAtPos( aDocPos ); + + if ( !bOverSelect || rSh.IsInSelect() ) + { + MoveCursor( rSh, aDocPos, bOnlyText, bLockView ); + bCallBase = false; + } + if (!bOverURLGrf && !bExecDrawTextLink && !bOnlyText) + { + const SelectionType nSelType = rSh.GetSelectionType(); + // Check in general, if an object is selectable at given position. + // Thus, also text fly frames in background become selectable via Ctrl-Click. + if ( ( nSelType & SelectionType::Ole || + nSelType & SelectionType::Graphic || + rSh.IsObjSelectable( aDocPos ) ) && !lcl_urlOverBackground( rSh, aDocPos ) ) + { + SwMvContext aMvContext( &rSh ); + rSh.EnterSelFrameMode(); + bCallBase = false; + } + } + if ( !bOverSelect && bEditableFieldClicked && (!pCursorField || + pCursorField != aFieldAtPos.pFndTextAttr->GetFormatField().GetField())) + { + // select content of Input Field, but exclude CH_TXT_ATR_INPUTFIELDSTART + // and CH_TXT_ATR_INPUTFIELDEND + rSh.SttSelect(); + rSh.SelectTextModel( aFieldAtPos.pFndTextAttr->GetStart() + 1, + *(aFieldAtPos.pFndTextAttr->End()) - 1 ); + } + // don't reset here any longer so that, in case through MouseMove + // with pressed Ctrl key a multiple-selection should happen, + // the previous selection is not released in Drag. + break; + } + } + } + else if (MOUSE_RIGHT == aMEvt.GetButtons()) + { + if (rSh.GetViewOptions()->IsShowOutlineContentVisibilityButton() + && aMEvt.GetModifier() == KEY_MOD1) + { + // ctrl+right-click on outline node frame + SwContentAtPos aContentAtPos(IsAttrAtPos::Outline); + if(rSh.GetContentAtPos(aDocPos, aContentAtPos)) + { + SwOutlineNodes::size_type nPos; + if (rSh.GetNodes().GetOutLineNds().Seek_Entry(aContentAtPos.aFnd.pNode, &nPos)) + { + ToggleOutlineContentVisibility(nPos, !rSh.GetViewOptions()->IsTreatSubOutlineLevelsAsContent()); + return; + } + } + } + else if (!aMEvt.GetModifier() && static_cast<sal_uInt8>(aMEvt.GetClicks() % 4) == 1 + && !rSh.TestCurrPam(aDocPos)) + { + SwContentAtPos aFieldAtPos(IsAttrAtPos::Field); + + // Are we clicking on a field? + if (g_bValidCursorPos + && rSh.GetContentAtPos(aDocPos, aFieldAtPos) + && aFieldAtPos.pFndTextAttr != nullptr + && aFieldAtPos.pFndTextAttr->Which() == RES_TXTATR_INPUTFIELD + && (!pCursorField || pCursorField != aFieldAtPos.pFndTextAttr->GetFormatField().GetField())) + { + // Move the cursor + MoveCursor( rSh, aDocPos, rSh.IsObjSelectable( aDocPos ), m_bWasShdwCursor ); + bCallBase = false; + + // select content of Input Field, but exclude CH_TXT_ATR_INPUTFIELDSTART + // and CH_TXT_ATR_INPUTFIELDEND + rSh.SttSelect(); + rSh.SelectTextModel( aFieldAtPos.pFndTextAttr->GetStart() + 1, + *(aFieldAtPos.pFndTextAttr->End()) - 1 ); + } + } + } + + if (bCallBase) + Window::MouseButtonDown(aMEvt); +} + +bool SwEditWin::changeMousePointer(Point const & rDocPoint) +{ + SwWrtShell & rShell = m_rView.GetWrtShell(); + + SwTab nMouseTabCol; + if ( SwTab::COL_NONE != (nMouseTabCol = rShell.WhichMouseTabCol( rDocPoint ) ) && + !rShell.IsObjSelectable( rDocPoint ) ) + { + PointerStyle nPointer = PointerStyle::Null; + bool bChkTableSel = false; + + switch ( nMouseTabCol ) + { + case SwTab::COL_VERT : + case SwTab::ROW_HORI : + nPointer = PointerStyle::VSizeBar; + bChkTableSel = true; + break; + case SwTab::ROW_VERT : + case SwTab::COL_HORI : + nPointer = PointerStyle::HSizeBar; + bChkTableSel = true; + break; + // Enhanced table selection + case SwTab::SEL_HORI : + nPointer = PointerStyle::TabSelectSE; + break; + case SwTab::SEL_HORI_RTL : + case SwTab::SEL_VERT : + nPointer = PointerStyle::TabSelectSW; + break; + case SwTab::COLSEL_HORI : + case SwTab::ROWSEL_VERT : + nPointer = PointerStyle::TabSelectS; + break; + case SwTab::ROWSEL_HORI : + nPointer = PointerStyle::TabSelectE; + break; + case SwTab::ROWSEL_HORI_RTL : + case SwTab::COLSEL_VERT : + nPointer = PointerStyle::TabSelectW; + break; + default: break; // prevent compiler warning + } + + if ( PointerStyle::Null != nPointer && + // i#35543 - Enhanced table selection is explicitly allowed in table mode + ( !bChkTableSel || !rShell.IsTableMode() ) && + !comphelper::LibreOfficeKit::isActive() ) + { + SetPointer( nPointer ); + } + + return true; + } + else if (rShell.IsNumLabel(rDocPoint, RULER_MOUSE_MARGINWIDTH)) + { + // i#42921 - consider vertical mode + SwTextNode* pNodeAtPos = rShell.GetNumRuleNodeAtPos( rDocPoint ); + const PointerStyle nPointer = + SwFEShell::IsVerticalModeAtNdAndPos( *pNodeAtPos, rDocPoint ) + ? PointerStyle::VSizeBar + : PointerStyle::HSizeBar; + SetPointer( nPointer ); + + return true; + } + return false; +} + +void SwEditWin::MouseMove(const MouseEvent& _rMEvt) +{ + MouseEvent rMEvt(_rMEvt); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (vcl::Window* pWindow = m_rView.GetPostItMgr()->IsHitSidebarWindow(rMEvt.GetPosPixel())) + { + pWindow->MouseMove(rMEvt); + return; + } + } + + //ignore key modifiers for format paintbrush + { + bool bExecFormatPaintbrush = m_pApplyTempl && m_pApplyTempl->m_pFormatClipboard + && m_pApplyTempl->m_pFormatClipboard->HasContent(); + if( bExecFormatPaintbrush ) + rMEvt = MouseEvent( _rMEvt.GetPosPixel(), _rMEvt.GetClicks(), + _rMEvt.GetMode(), _rMEvt.GetButtons() ); + } + + // as long as an action is running the MouseMove should be disconnected + // otherwise bug 40102 occurs + SwWrtShell &rSh = m_rView.GetWrtShell(); + if( rSh.ActionPend() ) + return ; + + if (rSh.GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + // add/remove outline content hide button + const SwNodes& rNds = rSh.GetDoc()->GetNodes(); + SwOutlineNodes::size_type nPos; + SwContentAtPos aSwContentAtPos(IsAttrAtPos::Outline); + if (rSh.GetContentAtPos(PixelToLogic(rMEvt.GetPosPixel()), aSwContentAtPos)) + { + // mouse pointer is on an outline paragraph node + if(aSwContentAtPos.aFnd.pNode && aSwContentAtPos.aFnd.pNode->IsTextNode()) + { + // Get the outline paragraph frame and compare it to the saved outline frame. If they + // are not the same, remove the fold button from the saved outline frame, if not + // already removed, and then add a fold button to the mouse over outline frame if + // the content is not folded. + SwContentFrame* pContentFrame = + aSwContentAtPos.aFnd.pNode->GetTextNode()->getLayoutFrame(rSh.GetLayout()); + if (pContentFrame != m_pSavedOutlineFrame) + { + if (m_pSavedOutlineFrame) + { + if (m_pSavedOutlineFrame->isFrameAreaDefinitionValid()) + { + SwTextNode* pTextNode = m_pSavedOutlineFrame->GetTextNodeFirst(); + if (pTextNode && rNds.GetOutLineNds().Seek_Entry(pTextNode, &nPos) && + rSh.GetAttrOutlineContentVisible(nPos)) + { + GetFrameControlsManager().RemoveControlsByType( + FrameControlType::Outline, m_pSavedOutlineFrame); + } + } + } + m_pSavedOutlineFrame = static_cast<SwTextFrame*>(pContentFrame); + } + // show fold button if outline content is visible + if (rNds.GetOutLineNds().Seek_Entry(aSwContentAtPos.aFnd.pNode->GetTextNode(), &nPos) && + rSh.GetAttrOutlineContentVisible(nPos)) + GetFrameControlsManager().SetOutlineContentVisibilityButton(pContentFrame); + } + } + else if (m_pSavedOutlineFrame) + { + // The saved frame may not still be in the document, e.g., when an outline paragraph + // is deleted. This causes the call to GetTextNodeFirst to behave badly. Use + // isFrameAreaDefinitionValid to check if the frame is still in the document. + if (m_pSavedOutlineFrame->isFrameAreaDefinitionValid()) + { + // current pointer pos is not over an outline frame + // previous pointer pos was over an outline frame + // remove outline content visibility button if showing + SwTextNode* pTextNode = m_pSavedOutlineFrame->GetTextNodeFirst(); + if (pTextNode && rNds.GetOutLineNds().Seek_Entry(pTextNode, &nPos) && + rSh.GetAttrOutlineContentVisible(nPos)) + { + GetFrameControlsManager().RemoveControlsByType( + FrameControlType::Outline, m_pSavedOutlineFrame); + } + } + m_pSavedOutlineFrame = nullptr; + } + } + + if( m_pShadCursor && 0 != (rMEvt.GetModifier() + rMEvt.GetButtons() ) ) + { + m_pShadCursor.reset(); + } + + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly(); + + CurrShell aCurr( &rSh ); + + //aPixPt == Point in Pixel, relative to ChildWin + //aDocPt == Point in Twips, document coordinates + const Point aPixPt( rMEvt.GetPosPixel() ); + const Point aDocPt( PixelToLogic( aPixPt ) ); + + if ( IsChainMode() ) + { + UpdatePointer( aDocPt, rMEvt.GetModifier() ); + return; + } + + SdrView *pSdrView = rSh.GetDrawView(); + + const SwCallMouseEvent aLastCallEvent( m_aSaveCallEvent ); + m_aSaveCallEvent.Clear(); + + if ( !bIsDocReadOnly && pSdrView && pSdrView->MouseMove(rMEvt,GetOutDev()) ) + { + SetPointer( PointerStyle::Text ); + return; // evaluate SdrView's event + } + + const Point aOldPt( rSh.VisArea().Pos() ); + const bool bInsWin = rSh.VisArea().Contains( aDocPt ) || comphelper::LibreOfficeKit::isActive(); + + if (rSh.GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + if (m_pSavedOutlineFrame && !bInsWin) + { + // the mouse pointer has left the building (edit window) + // remove the outline content visibility button if showing + if (m_pSavedOutlineFrame->isFrameAreaDefinitionValid()) + { + const SwNodes& rNds = rSh.GetDoc()->GetNodes(); + SwOutlineNodes::size_type nPos; + SwTextNode* pTextNode = m_pSavedOutlineFrame->GetTextNodeFirst(); + if (pTextNode && rNds.GetOutLineNds().Seek_Entry(pTextNode, &nPos) && + rSh.GetAttrOutlineContentVisible(nPos)) + { + GetFrameControlsManager().RemoveControlsByType(FrameControlType::Outline, + m_pSavedOutlineFrame); + } + } + m_pSavedOutlineFrame = nullptr; + } + } + + if( m_pShadCursor && !bInsWin ) + { + m_pShadCursor.reset(); + } + + if( bInsWin && m_xRowColumnSelectionStart ) + { + EnterArea(); + Point aPos( aDocPt ); + if( rSh.SelectTableRowCol( *m_xRowColumnSelectionStart, &aPos, m_bIsRowDrag )) + return; + } + + // position is necessary for OS/2 because obviously after a MB-Down + // a MB-Move is called immediately. + if( g_bDDTimerStarted ) + { + Point aDD( SwEditWin::s_nDDStartPosX, SwEditWin::s_nDDStartPosY ); + aDD = LogicToPixel( aDD ); + tools::Rectangle aRect( aDD.X()-3, aDD.Y()-3, aDD.X()+3, aDD.Y()+3 ); + if ( !aRect.Contains( aPixPt ) ) + StopDDTimer( &rSh, aDocPt ); + } + + if(m_rView.GetDrawFuncPtr()) + { + if( m_bInsDraw ) + { + m_rView.GetDrawFuncPtr()->MouseMove( rMEvt ); + if ( !bInsWin ) + { + Point aTmp( aDocPt ); + aTmp += rSh.VisArea().Pos() - aOldPt; + LeaveArea( aTmp ); + } + else + EnterArea(); + return; + } + else if(!rSh.IsFrameSelected() && !rSh.IsObjSelected()) + { + SfxBindings &rBnd = rSh.GetView().GetViewFrame()->GetBindings(); + Point aRelPos = rSh.GetRelativePagePosition(aDocPt); + if(aRelPos.X() >= 0) + { + FieldUnit eMetric = ::GetDfltMetric(dynamic_cast<SwWebView*>( &GetView()) != nullptr ); + SW_MOD()->PutItem(SfxUInt16Item(SID_ATTR_METRIC, static_cast< sal_uInt16 >(eMetric))); + const SfxPointItem aTmp1( SID_ATTR_POSITION, aRelPos ); + rBnd.SetState( aTmp1 ); + } + else + { + rBnd.Invalidate(SID_ATTR_POSITION); + } + rBnd.Invalidate(SID_ATTR_SIZE); + const SfxStringItem aCell( SID_TABLE_CELL, OUString() ); + rBnd.SetState( aCell ); + } + } + + // determine if we only change the mouse pointer and return + if (!bIsDocReadOnly && bInsWin && !m_pApplyTempl && !rSh.IsInSelect() && changeMousePointer(aDocPt)) + { + return; + } + + bool bDelShadCursor = true; + + switch ( rMEvt.GetModifier() + rMEvt.GetButtons() ) + { + case MOUSE_LEFT: + if( m_pAnchorMarker ) + { + // Now we need to refresh the SdrHdl pointer of m_pAnchorMarker. + // This looks a little bit tricky, but it solves the following + // problem: the m_pAnchorMarker contains a pointer to an SdrHdl, + // if the FindAnchorPos-call cause a scrolling of the visible + // area, it's possible that the SdrHdl will be destroyed and a + // new one will initialized at the original position(GetHdlPos). + // So the m_pAnchorMarker has to find the right SdrHdl, if it's + // the old one, it will find it with position aOld, if this one + // is destroyed, it will find a new one at position GetHdlPos(). + + const Point aOld = m_pAnchorMarker->GetPosForHitTest( *(rSh.GetOut()) ); + Point aNew = rSh.FindAnchorPos( aDocPt ); + SdrHdl* pHdl; + if( pSdrView && (nullptr!=( pHdl = pSdrView->PickHandle( aOld ) )|| + nullptr !=(pHdl = pSdrView->PickHandle( m_pAnchorMarker->GetHdlPos()) ) ) && + ( pHdl->GetKind() == SdrHdlKind::Anchor || + pHdl->GetKind() == SdrHdlKind::Anchor_TR ) ) + { + m_pAnchorMarker->ChgHdl( pHdl ); + if( aNew.X() || aNew.Y() ) + { + m_pAnchorMarker->SetPos( aNew ); + m_pAnchorMarker->SetLastPos( aDocPt ); + } + } + else + { + m_pAnchorMarker.reset(); + } + } + if ( m_bInsDraw ) + { + if ( !m_bMBPressed ) + break; + if ( m_bIsInMove || IsMinMove( m_aStartPos, aPixPt ) ) + { + if ( !bInsWin ) + LeaveArea( aDocPt ); + else + EnterArea(); + if ( m_rView.GetDrawFuncPtr() ) + { + pSdrView->SetOrtho(false); + m_rView.GetDrawFuncPtr()->MouseMove( rMEvt ); + } + m_bIsInMove = true; + } + return; + } + + { + SwWordCountWrapper *pWrdCnt = static_cast<SwWordCountWrapper*>(GetView().GetViewFrame()->GetChildWindow(SwWordCountWrapper::GetChildWindowId())); + if (pWrdCnt) + pWrdCnt->UpdateCounts(); + } + [[fallthrough]]; + + case MOUSE_LEFT + KEY_SHIFT: + case MOUSE_LEFT + KEY_SHIFT + KEY_MOD1: + if ( !m_bMBPressed ) + break; + [[fallthrough]]; + case MOUSE_LEFT + KEY_MOD1: + if ( g_bFrameDrag && rSh.IsSelFrameMode() ) + { + if( !m_bMBPressed ) + break; + + if ( m_bIsInMove || IsMinMove( m_aStartPos, aPixPt ) ) + { + // event processing for resizing + if (pSdrView && pSdrView->AreObjectsMarked()) + { + const Point aSttPt( PixelToLogic( m_aStartPos ) ); + + // can we start? + if( SdrHdlKind::User == g_eSdrMoveHdl ) + { + SdrHdl* pHdl = pSdrView->PickHandle( aSttPt ); + g_eSdrMoveHdl = pHdl ? pHdl->GetKind() : SdrHdlKind::Move; + } + + const SwFrameFormat *const pFlyFormat(rSh.GetFlyFrameFormat()); + const SvxMacro* pMacro = nullptr; + + SvMacroItemId nEvent = SdrHdlKind::Move == g_eSdrMoveHdl + ? SvMacroItemId::SwFrmMove + : SvMacroItemId::SwFrmResize; + + if (nullptr != pFlyFormat) + pMacro = pFlyFormat->GetMacro().GetMacroTable().Get(nEvent); + if (nullptr != pMacro && + // or notify only e.g. every 20 Twip? + m_aRszMvHdlPt != aDocPt ) + { + m_aRszMvHdlPt = aDocPt; + sal_uInt32 nPos = 0; + SbxArrayRef xArgs = new SbxArray; + SbxVariableRef xVar = new SbxVariable; + xVar->PutString( pFlyFormat->GetName() ); + xArgs->Put(xVar.get(), ++nPos); + + if( SvMacroItemId::SwFrmResize == nEvent ) + { + xVar = new SbxVariable; + xVar->PutUShort( static_cast< sal_uInt16 >(g_eSdrMoveHdl) ); + xArgs->Put(xVar.get(), ++nPos); + } + + xVar = new SbxVariable; + xVar->PutLong( aDocPt.X() - aSttPt.X() ); + xArgs->Put(xVar.get(), ++nPos); + xVar = new SbxVariable; + xVar->PutLong( aDocPt.Y() - aSttPt.Y() ); + xArgs->Put(xVar.get(), ++nPos); + + OUString sRet; + + ReleaseMouse(); + + rSh.ExecMacro( *pMacro, &sRet, xArgs.get() ); + + CaptureMouse(); + + if( !sRet.isEmpty() && sRet.toInt32()!=0 ) + return ; + } + } + // event processing for resizing + + if( bIsDocReadOnly ) + break; + + bool bResizeKeepRatio = rSh.GetSelectionType() & SelectionType::Graphic || + rSh.GetSelectionType() & SelectionType::Media || + rSh.GetSelectionType() & SelectionType::Ole; + bool bisResize = g_eSdrMoveHdl != SdrHdlKind::Move; + + if (pSdrView) + { + // Resize proportionally when media is selected and the user drags on a corner + const Point aSttPt(PixelToLogic(m_aStartPos)); + SdrHdl* pHdl = pSdrView->PickHandle(aSttPt); + if (pHdl) + bResizeKeepRatio = bResizeKeepRatio && pHdl->IsCornerHdl(); + + if (pSdrView->GetDragMode() == SdrDragMode::Crop) + bisResize = false; + if (rMEvt.IsShift()) + { + pSdrView->SetAngleSnapEnabled(!bResizeKeepRatio); + if (bisResize) + pSdrView->SetOrtho(!bResizeKeepRatio); + else + pSdrView->SetOrtho(true); + } + else + { + pSdrView->SetAngleSnapEnabled(bResizeKeepRatio); + if (bisResize) + pSdrView->SetOrtho(bResizeKeepRatio); + else + pSdrView->SetOrtho(false); + } + } + + rSh.Drag( &aDocPt, rMEvt.IsShift() ); + m_bIsInMove = true; + } + else if( bIsDocReadOnly ) + break; + + if ( !bInsWin ) + { + Point aTmp( aDocPt ); + aTmp += rSh.VisArea().Pos() - aOldPt; + LeaveArea( aTmp ); + } + else if(m_bIsInMove) + EnterArea(); + return; + } + if ( !rSh.IsSelFrameMode() && !g_bDDINetAttr && + (IsMinMove( m_aStartPos,aPixPt ) || m_bIsInMove) && + (rSh.IsInSelect() || !rSh.TestCurrPam( aDocPt )) ) + { + if ( pSdrView ) + { + if ( rMEvt.IsShift() ) + pSdrView->SetOrtho(true); + else + pSdrView->SetOrtho(false); + } + if ( !bInsWin ) + { + Point aTmp( aDocPt ); + aTmp += rSh.VisArea().Pos() - aOldPt; + LeaveArea( aTmp ); + } + else + { + if( !rMEvt.IsSynthetic() && + ( MOUSE_LEFT != rMEvt.GetButtons() || + KEY_MOD1 != rMEvt.GetModifier() || + !rSh.Is_FnDragEQBeginDrag() || + rSh.IsAddMode() ) ) + { + rSh.Drag( &aDocPt, false ); + + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPt, false)); + EnterArea(); + } + } + } + g_bDDINetAttr = false; + break; + case 0: + { + if ( m_pApplyTempl ) + { + UpdatePointer(aDocPt); // maybe a frame has to be marked here + break; + } + // change ui if mouse is over SwPostItField + // TODO: do the same thing for redlines IsAttrAtPos::Redline + SwContentAtPos aContentAtPos( IsAttrAtPos::Field); + if (rSh.GetContentAtPos(aDocPt, aContentAtPos, false)) + { + const SwField* pField = aContentAtPos.aFnd.pField; + if (pField->Which()== SwFieldIds::Postit) + { + m_rView.GetPostItMgr()->SetShadowState(reinterpret_cast<const SwPostItField*>(pField),false); + } + else + m_rView.GetPostItMgr()->SetShadowState(nullptr,false); + } + else + m_rView.GetPostItMgr()->SetShadowState(nullptr,false); + [[fallthrough]]; + } + case KEY_SHIFT: + case KEY_MOD2: + case KEY_MOD1: + if ( !m_bInsDraw ) + { + bool bTstShdwCursor = true; + + UpdatePointer( aDocPt, rMEvt.GetModifier() ); + + const SwFrameFormat* pFormat = nullptr; + const SwFormatINetFormat* pINet = nullptr; + SwContentAtPos aContentAtPos( IsAttrAtPos::InetAttr ); + if( rSh.GetContentAtPos( aDocPt, aContentAtPos ) ) + pINet = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr); + + const void* pTmp = pINet; + + if( pINet || + nullptr != ( pTmp = pFormat = rSh.GetFormatFromAnyObj( aDocPt ))) + { + bTstShdwCursor = false; + if( pTmp == pINet ) + m_aSaveCallEvent.Set( pINet ); + else + { + IMapObject* pIMapObj = pFormat->GetIMapObject( aDocPt ); + if( pIMapObj ) + m_aSaveCallEvent.Set( pFormat, pIMapObj ); + else + m_aSaveCallEvent.Set( EVENT_OBJECT_URLITEM, pFormat ); + } + + // should be over an InternetField with an + // embedded macro? + if( m_aSaveCallEvent != aLastCallEvent ) + { + if( aLastCallEvent.HasEvent() ) + rSh.CallEvent( SvMacroItemId::OnMouseOut, + aLastCallEvent, true ); + // 0 says that the object doesn't have any table + if( !rSh.CallEvent( SvMacroItemId::OnMouseOver, + m_aSaveCallEvent )) + m_aSaveCallEvent.Clear(); + } + } + else if( aLastCallEvent.HasEvent() ) + { + // cursor was on an object + rSh.CallEvent( SvMacroItemId::OnMouseOut, + aLastCallEvent, true ); + } + + if( bTstShdwCursor && bInsWin && !bIsDocReadOnly && + !m_bInsFrame && + !rSh.GetViewOptions()->getBrowseMode() && + rSh.GetViewOptions()->IsShadowCursor() && + !(rMEvt.GetModifier() + rMEvt.GetButtons()) && + !rSh.HasSelection() && !GetOutDev()->GetConnectMetaFile() ) + { + SwRect aRect; + sal_Int16 eOrient; + SwFillMode eMode = rSh.GetViewOptions()->GetShdwCursorFillMode(); + if( rSh.GetShadowCursorPos( aDocPt, eMode, aRect, eOrient )) + { + if( !m_pShadCursor ) + m_pShadCursor.reset( new SwShadowCursor( *this, + SwViewOption::GetDirectCursorColor() ) ); + if( text::HoriOrientation::RIGHT != eOrient && text::HoriOrientation::CENTER != eOrient ) + eOrient = text::HoriOrientation::LEFT; + m_pShadCursor->SetPos( aRect.Pos(), aRect.Height(), static_cast< sal_uInt16 >(eOrient) ); + bDelShadCursor = false; + } + } + } + break; + case MOUSE_LEFT + KEY_MOD2: + if( rSh.IsBlockMode() && !rMEvt.IsSynthetic() ) + { + rSh.Drag( &aDocPt, false ); + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPt, false)); + EnterArea(); + } + break; + } + + if( bDelShadCursor && m_pShadCursor ) + { + m_pShadCursor.reset(); + } + m_bWasShdwCursor = false; +} + +/** + * Button Up + */ +void SwEditWin::MouseButtonUp(const MouseEvent& rMEvt) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + if (vcl::Window* pWindow = m_rView.GetPostItMgr()->IsHitSidebarWindow(rMEvt.GetPosPixel())) + { + pWindow->MouseButtonUp(rMEvt); + return; + } + } + + bool bCallBase = true; + + bool bCallShadowCursor = m_bWasShdwCursor; + m_bWasShdwCursor = false; + if( m_pShadCursor ) + { + m_pShadCursor.reset(); + } + + m_xRowColumnSelectionStart.reset(); + + SdrHdlKind eOldSdrMoveHdl = g_eSdrMoveHdl; + g_eSdrMoveHdl = SdrHdlKind::User; // for MoveEvents - reset again + + // preventively reset + m_rView.SetTabColFromDoc( false ); + m_rView.SetNumRuleNodeFromDoc(nullptr); + + SwWrtShell &rSh = m_rView.GetWrtShell(); + CurrShell aCurr( &rSh ); + SdrView *pSdrView = rSh.GetDrawView(); + if ( pSdrView ) + { + // tdf34555: ortho was always reset before being used in EndSdrDrag + // Now, it is reset only if not in Crop mode. + if (pSdrView->GetDragMode() != SdrDragMode::Crop && !rMEvt.IsShift()) + pSdrView->SetOrtho(false); + + if ( pSdrView->MouseButtonUp( rMEvt,GetOutDev() ) ) + { + rSh.GetView().GetViewFrame()->GetBindings().InvalidateAll(false); + return; // SdrView's event evaluated + } + } + // only process MouseButtonUp when the Down went to that windows as well. + if ( !m_bMBPressed ) + { + // Undo for the watering can is already in CommandHdl + // that's the way it should be! + + return; + } + + Point aDocPt( PixelToLogic( rMEvt.GetPosPixel() ) ); + + if ( g_bDDTimerStarted ) + { + StopDDTimer( &rSh, aDocPt ); + m_bMBPressed = false; + if ( rSh.IsSelFrameMode() ) + { + rSh.EndDrag( &aDocPt, false ); + g_bFrameDrag = false; + } + g_bNoInterrupt = false; + const Point aDocPos( PixelToLogic( rMEvt.GetPosPixel() ) ); + if ((PixelToLogic(m_aStartPos).Y() == (aDocPos.Y())) && (PixelToLogic(m_aStartPos).X() == (aDocPos.X())))//To make sure it was not moved + { + SdrPageView* pPV = nullptr; + SdrObject* pObj = pSdrView ? pSdrView->PickObj(aDocPos, pSdrView->getHitTolLog(), pPV, SdrSearchOptions::ALSOONMASTER) : nullptr; + if (pObj) + { + SwFrameFormat* pFormat = GetUserCall(pObj)->GetFormat(); + SwFrameFormat* pShapeFormat = SwTextBoxHelper::getOtherTextBoxFormat(pFormat, RES_FLYFRMFMT); + if (!pShapeFormat) + { + pSdrView->UnmarkAllObj(); + pSdrView->MarkObj(pObj,pPV); + } + else + { + // If the fly frame is a textbox of a shape, then select the shape instead. + SdrObject* pShape = pShapeFormat->FindSdrObject(); + pSdrView->UnmarkAllObj(); + pSdrView->MarkObj(pShape, pPV); + } + } + } + ReleaseMouse(); + return; + } + + if( m_pAnchorMarker ) + { + if(m_pAnchorMarker->GetHdl()) + { + // #i121463# delete selected after drag + m_pAnchorMarker->GetHdl()->SetSelected(false); + } + + Point aPnt( m_pAnchorMarker->GetLastPos() ); + m_pAnchorMarker.reset(); + if( aPnt.X() || aPnt.Y() ) + rSh.FindAnchorPos( aPnt, true ); + } + if ( m_bInsDraw && m_rView.GetDrawFuncPtr() ) + { + if ( m_rView.GetDrawFuncPtr()->MouseButtonUp( rMEvt ) ) + { + if (m_rView.GetDrawFuncPtr()) // could have been destroyed in MouseButtonUp + { + m_rView.GetDrawFuncPtr()->Deactivate(); + + if (!m_rView.IsDrawMode()) + { + m_rView.SetDrawFuncPtr(nullptr); + SfxBindings& rBind = m_rView.GetViewFrame()->GetBindings(); + rBind.Invalidate( SID_ATTR_SIZE ); + rBind.Invalidate( SID_TABLE_CELL ); + } + } + + if ( rSh.IsObjSelected() ) + { + rSh.EnterSelFrameMode(); + if (!m_rView.GetDrawFuncPtr()) + StdDrawMode( SdrObjKind::NONE, true ); + } + else if ( rSh.IsFrameSelected() ) + { + rSh.EnterSelFrameMode(); + StopInsFrame(); + } + else + { + const Point aDocPos( PixelToLogic( m_aStartPos ) ); + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPos, false)); + rSh.Edit(); + } + + m_rView.AttrChangedNotify(nullptr); + } + else if (rMEvt.GetButtons() == MOUSE_RIGHT && rSh.IsDrawCreate()) + m_rView.GetDrawFuncPtr()->BreakCreate(); // abort drawing + + g_bNoInterrupt = false; + if (IsMouseCaptured()) + ReleaseMouse(); + return; + } + bool bPopMode = false; + switch ( rMEvt.GetModifier() + rMEvt.GetButtons() ) + { + case MOUSE_LEFT: + if ( m_bInsDraw && rSh.IsDrawCreate() ) + { + if ( m_rView.GetDrawFuncPtr() && m_rView.GetDrawFuncPtr()->MouseButtonUp(rMEvt) ) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.AttrChangedNotify(nullptr); + if ( rSh.IsObjSelected() ) + rSh.EnterSelFrameMode(); + if ( m_rView.GetDrawFuncPtr() && m_bInsFrame ) + StopInsFrame(); + } + bCallBase = false; + break; + } + [[fallthrough]]; + case MOUSE_LEFT + KEY_MOD1: + case MOUSE_LEFT + KEY_MOD2: + case MOUSE_LEFT + KEY_SHIFT + KEY_MOD1: + if ( g_bFrameDrag && rSh.IsSelFrameMode() ) + { + if ( rMEvt.IsMod1() ) // copy and don't move. + { + // abort drag, use internal Copy instead + tools::Rectangle aRect; + rSh.GetDrawView()->TakeActionRect( aRect ); + if (!aRect.IsEmpty()) + { + rSh.BreakDrag(); + Point aEndPt, aSttPt; + if ( rSh.GetSelFrameType() & FrameTypeFlags::FLY_ATCNT ) + { + aEndPt = aRect.TopLeft(); + aSttPt = rSh.GetDrawView()->GetAllMarkedRect().TopLeft(); + } + else + { + aEndPt = aRect.Center(); + aSttPt = rSh.GetDrawView()->GetAllMarkedRect().Center(); + } + if ( aSttPt != aEndPt ) + { + rSh.StartUndo( SwUndoId::UI_DRAG_AND_COPY ); + rSh.Copy(rSh, aSttPt, aEndPt); + rSh.EndUndo( SwUndoId::UI_DRAG_AND_COPY ); + } + } + else { + rSh.EndDrag( &aDocPt, false ); + } + } + else + { + { + const SwFrameFormat *const pFlyFormat(rSh.GetFlyFrameFormat()); + const SvxMacro* pMacro = nullptr; + + SvMacroItemId nEvent = SdrHdlKind::Move == eOldSdrMoveHdl + ? SvMacroItemId::SwFrmMove + : SvMacroItemId::SwFrmResize; + + if (nullptr != pFlyFormat) + pMacro = pFlyFormat->GetMacro().GetMacroTable().Get(nEvent); + if (nullptr != pMacro) + { + const Point aSttPt( PixelToLogic( m_aStartPos ) ); + m_aRszMvHdlPt = aDocPt; + sal_uInt32 nPos = 0; + SbxArrayRef xArgs = new SbxArray; + SbxVariableRef xVar = new SbxVariable; + xVar->PutString( pFlyFormat->GetName() ); + xArgs->Put(xVar.get(), ++nPos); + + if( SvMacroItemId::SwFrmResize == nEvent ) + { + xVar = new SbxVariable; + xVar->PutUShort( static_cast< sal_uInt16 >(eOldSdrMoveHdl) ); + xArgs->Put(xVar.get(), ++nPos); + } + + xVar = new SbxVariable; + xVar->PutLong( aDocPt.X() - aSttPt.X() ); + xArgs->Put(xVar.get(), ++nPos); + xVar = new SbxVariable; + xVar->PutLong( aDocPt.Y() - aSttPt.Y() ); + xArgs->Put(xVar.get(), ++nPos); + + xVar = new SbxVariable; + xVar->PutUShort( 1 ); + xArgs->Put(xVar.get(), ++nPos); + + ReleaseMouse(); + + rSh.ExecMacro( *pMacro, nullptr, xArgs.get() ); + + CaptureMouse(); + } + + if (pFlyFormat) + { + // See if the fly frame's anchor is in a content control. If so, + // interact with it. + const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor(); + const SwPosition* pAnchorPos = rFormatAnchor.GetContentAnchor(); + if (pAnchorPos) + { + SwTextNode* pTextNode = pAnchorPos->nNode.GetNode().GetTextNode(); + if (pTextNode) + { + SwTextAttr* pAttr = pTextNode->GetTextAttrAt( + pAnchorPos->nContent.GetIndex(), RES_TXTATR_CONTENTCONTROL, + SwTextNode::PARENT); + if (pAttr) + { + SwTextContentControl* pTextContentControl + = static_txtattr_cast<SwTextContentControl*>(pAttr); + const SwFormatContentControl& rFormatContentControl + = pTextContentControl->GetContentControl(); + rSh.GotoContentControl(rFormatContentControl); + } + } + } + } + } + rSh.EndDrag( &aDocPt, false ); + } + g_bFrameDrag = false; + bCallBase = false; + break; + } + bPopMode = true; + [[fallthrough]]; + case MOUSE_LEFT + KEY_SHIFT: + if (rSh.IsSelFrameMode()) + { + + rSh.EndDrag( &aDocPt, false ); + g_bFrameDrag = false; + bCallBase = false; + break; + } + + if( g_bHoldSelection ) + { + // the EndDrag should be called in any case + g_bHoldSelection = false; + rSh.EndDrag( &aDocPt, false ); + } + else + { + SwContentAtPos aFieldAtPos ( IsAttrAtPos::Field ); + if ( !rSh.IsInSelect() && rSh.TestCurrPam( aDocPt ) && + !rSh.GetContentAtPos( aDocPt, aFieldAtPos ) ) + { + const bool bTmpNoInterrupt = g_bNoInterrupt; + g_bNoInterrupt = false; + { // create only temporary move context because otherwise + // the query to the content form doesn't work!!! + SwMvContext aMvContext( &rSh ); + const Point aDocPos( PixelToLogic( m_aStartPos ) ); + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPos, false)); + } + g_bNoInterrupt = bTmpNoInterrupt; + + } + else + { + bool bInSel = rSh.IsInSelect(); + rSh.EndDrag( &aDocPt, false ); + + // Internetfield? --> call link (load doc!!) + if( !bInSel ) + { + LoadUrlFlags nFilter = LoadUrlFlags::NONE; + if( KEY_MOD1 == rMEvt.GetModifier() ) + nFilter |= LoadUrlFlags::NewView; + + bool bExecHyperlinks = m_rView.GetDocShell()->IsReadOnly(); + if ( !bExecHyperlinks ) + { + const bool bSecureOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink ); + if ( ( bSecureOption && rMEvt.GetModifier() == KEY_MOD1 ) || + ( !bSecureOption && rMEvt.GetModifier() != KEY_MOD1 ) ) + bExecHyperlinks = true; + } + + const bool bExecSmarttags = rMEvt.GetModifier() == KEY_MOD1; + + if(m_pApplyTempl) + bExecHyperlinks = false; + + SwContentAtPos aContentAtPos( IsAttrAtPos::Field | + IsAttrAtPos::InetAttr | + IsAttrAtPos::SmartTag | IsAttrAtPos::FormControl | + IsAttrAtPos::ContentControl); + + if( rSh.GetContentAtPos( aDocPt, aContentAtPos ) ) + { + // Do it again if we're not on a field/hyperlink to update the cursor accordingly + if ( IsAttrAtPos::Field != aContentAtPos.eContentAtPos + && IsAttrAtPos::InetAttr != aContentAtPos.eContentAtPos ) + rSh.GetContentAtPos( aDocPt, aContentAtPos, true ); + + bool bViewLocked = rSh.IsViewLocked(); + if( !bViewLocked && !rSh.IsReadOnlyAvailable() && + aContentAtPos.IsInProtectSect() ) + rSh.LockView( true ); + + ReleaseMouse(); + + if( IsAttrAtPos::Field == aContentAtPos.eContentAtPos ) + { + bool bAddMode(false); + // AdditionalMode if applicable + if (KEY_MOD1 == rMEvt.GetModifier() + && !rSh.IsAddMode()) + { + bAddMode = true; + rSh.EnterAddMode(); + } + if ( aContentAtPos.pFndTextAttr != nullptr + && aContentAtPos.pFndTextAttr->Which() == RES_TXTATR_INPUTFIELD ) + { + if (!rSh.IsInSelect()) + { + // create only temporary move context because otherwise + // the query to the content form doesn't work!!! + SwMvContext aMvContext( &rSh ); + const Point aDocPos( PixelToLogic( m_aStartPos ) ); + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPos, false)); + } + else + { + g_bValidCursorPos = true; + } + } + else + { + rSh.ClickToField(*aContentAtPos.aFnd.pField, bExecHyperlinks); + // a bit of a mystery what this is good for? + // in this case we assume it's valid since we + // just selected a field + g_bValidCursorPos = true; + } + if (bAddMode) + { + rSh.LeaveAddMode(); + } + } + else if (aContentAtPos.eContentAtPos == IsAttrAtPos::ContentControl) + { + auto pTextContentControl + = static_txtattr_cast<const SwTextContentControl*>( + aContentAtPos.pFndTextAttr); + const SwFormatContentControl& rFormatContentControl + = pTextContentControl->GetContentControl(); + rSh.GotoContentControl(rFormatContentControl); + } + else if ( IsAttrAtPos::SmartTag == aContentAtPos.eContentAtPos ) + { + // execute smarttag menu + if ( bExecSmarttags && SwSmartTagMgr::Get().IsSmartTagsEnabled() ) + m_rView.ExecSmartTagPopup( aDocPt ); + } + else if ( IsAttrAtPos::FormControl == aContentAtPos.eContentAtPos ) + { + OSL_ENSURE( aContentAtPos.aFnd.pFieldmark != nullptr, "where is my field ptr???"); + if ( aContentAtPos.aFnd.pFieldmark != nullptr) + { + IFieldmark *fieldBM = const_cast< IFieldmark* > ( aContentAtPos.aFnd.pFieldmark ); + if ( fieldBM->GetFieldname( ) == ODF_FORMCHECKBOX ) + { + ICheckboxFieldmark& rCheckboxFm = dynamic_cast<ICheckboxFieldmark&>(*fieldBM); + rCheckboxFm.SetChecked(!rCheckboxFm.IsChecked()); + rCheckboxFm.Invalidate(); + rSh.InvalidateWindows( SwRect(m_rView.GetVisArea()) ); + } + } + } + else if ( IsAttrAtPos::InetAttr == aContentAtPos.eContentAtPos ) + { + if (comphelper::LibreOfficeKit::isActive()) + { + OUString val((*static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr)).GetValue()); + if (val.startsWith("#")) + bExecHyperlinks = true; + } + if ( bExecHyperlinks && aContentAtPos.aFnd.pAttr ) + rSh.ClickToINetAttr( *static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr), nFilter ); + } + + rSh.LockView( bViewLocked ); + bCallShadowCursor = false; + } + else + { + aContentAtPos = SwContentAtPos( IsAttrAtPos::Ftn ); + if( !rSh.GetContentAtPos( aDocPt, aContentAtPos, true ) && bExecHyperlinks ) + { + SdrViewEvent aVEvt; + + if (pSdrView) + pSdrView->PickAnything(rMEvt, SdrMouseEventKind::BUTTONDOWN, aVEvt); + + if (pSdrView && aVEvt.meEvent == SdrEventKind::ExecuteUrl) + { + // hit URL field + const SvxURLField *pField = aVEvt.mpURLField; + if (pField) + { + const OUString& sURL(pField->GetURL()); + const OUString& sTarget(pField->GetTargetFrame()); + ::LoadURL(rSh, sURL, nFilter, sTarget); + } + bCallShadowCursor = false; + } + else + { + // hit graphic + ReleaseMouse(); + if( rSh.ClickToINetGrf( aDocPt, nFilter )) + bCallShadowCursor = false; + } + } + } + + if( bCallShadowCursor && + rSh.GetViewOptions()->IsShadowCursor() && + MOUSE_LEFT == (rMEvt.GetModifier() + rMEvt.GetButtons()) && + !rSh.HasSelection() && + !GetOutDev()->GetConnectMetaFile() && + rSh.VisArea().Contains( aDocPt )) + { + SwUndoId nLastUndoId(SwUndoId::EMPTY); + if (rSh.GetLastUndoInfo(nullptr, & nLastUndoId)) + { + if (SwUndoId::INS_FROM_SHADOWCRSR == nLastUndoId) + { + rSh.Undo(); + } + } + SwFillMode eMode = rSh.GetViewOptions()->GetShdwCursorFillMode(); + rSh.SetShadowCursorPos( aDocPt, eMode ); + } + } + } + bCallBase = false; + + } + + // reset pushed mode in Down again if applicable + if ( bPopMode && g_bModePushed ) + { + rSh.PopMode(); + g_bModePushed = false; + bCallBase = false; + } + break; + + default: + ReleaseMouse(); + return; + } + + if( m_pApplyTempl ) + { + SelectionType eSelection = rSh.GetSelectionType(); + SwFormatClipboard* pFormatClipboard = m_pApplyTempl->m_pFormatClipboard; + if( pFormatClipboard )//apply format paintbrush + { + //get some parameters + SwWrtShell& rWrtShell = m_rView.GetWrtShell(); + SfxStyleSheetBasePool* pPool=nullptr; + bool bNoCharacterFormats = false; + bool bNoParagraphFormats = true; + { + SwDocShell* pDocSh = m_rView.GetDocShell(); + if(pDocSh) + pPool = pDocSh->GetStyleSheetPool(); + if( (rMEvt.GetModifier()&KEY_MOD1) && (rMEvt.GetModifier()&KEY_SHIFT) ) + { + bNoCharacterFormats = true; + bNoParagraphFormats = false; + } + else if( rMEvt.GetModifier() & KEY_MOD1 ) + bNoParagraphFormats = false; + } + //execute paste + pFormatClipboard->Paste( rWrtShell, pPool, bNoCharacterFormats, bNoParagraphFormats ); + + //if the clipboard is empty after paste remove the ApplyTemplate + if(!pFormatClipboard->HasContent()) + SetApplyTemplate(SwApplyTemplate()); + + //tdf#38101 remove temporary highlighting + m_pUserMarker.reset(); + } + else if( m_pApplyTempl->nColor ) + { + sal_uInt16 nId = 0; + switch( m_pApplyTempl->nColor ) + { + case SID_ATTR_CHAR_COLOR_EXT: + nId = RES_CHRATR_COLOR; + break; + case SID_ATTR_CHAR_COLOR_BACKGROUND_EXT: + nId = RES_CHRATR_BACKGROUND; + break; + } + if( nId && (SelectionType::Text|SelectionType::Table) & eSelection) + { + if( rSh.IsSelection() && !rSh.HasReadonlySel() ) + { + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + if (nId == RES_CHRATR_BACKGROUND) + ApplyCharBackground(m_aWaterCanTextBackColor, rSh); + else + rSh.SetAttrItem( SvxColorItem( m_aWaterCanTextColor, nId ) ); + rSh.UnSetVisibleCursor(); + rSh.EnterStdMode(); + rSh.SetVisibleCursor(aDocPt); + bCallBase = false; + m_aTemplateTimer.Stop(); + } + else if(rMEvt.GetClicks() == 1) + { + // no selection -> so turn off watering can + m_aTemplateTimer.Start(); + } + } + } + else + { + OUString aStyleName; + switch ( m_pApplyTempl->eType ) + { + case SfxStyleFamily::Para: + if( (( SelectionType::Text | SelectionType::Table ) + & eSelection ) && !rSh.HasReadonlySel() ) + { + rSh.SetTextFormatColl( m_pApplyTempl->aColl.pTextColl ); + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + bCallBase = false; + if ( m_pApplyTempl->aColl.pTextColl ) + aStyleName = m_pApplyTempl->aColl.pTextColl->GetName(); + } + break; + case SfxStyleFamily::Char: + if( (( SelectionType::Text | SelectionType::Table ) + & eSelection ) && !rSh.HasReadonlySel() ) + { + rSh.SetAttrItem( SwFormatCharFormat(m_pApplyTempl->aColl.pCharFormat) ); + rSh.UnSetVisibleCursor(); + rSh.EnterStdMode(); + rSh.SetVisibleCursor(aDocPt); + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + bCallBase = false; + if ( m_pApplyTempl->aColl.pCharFormat ) + aStyleName = m_pApplyTempl->aColl.pCharFormat->GetName(); + } + break; + case SfxStyleFamily::Frame : + { + const SwFrameFormat* pFormat = rSh.GetFormatFromObj( aDocPt ); + if(dynamic_cast<const SwFlyFrameFormat*>( pFormat) ) + { + rSh.SetFrameFormat( m_pApplyTempl->aColl.pFrameFormat, false, &aDocPt ); + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + bCallBase = false; + if( m_pApplyTempl->aColl.pFrameFormat ) + aStyleName = m_pApplyTempl->aColl.pFrameFormat->GetName(); + } + break; + } + case SfxStyleFamily::Page: + // no Undo with page templates + rSh.ChgCurPageDesc( *m_pApplyTempl->aColl.pPageDesc ); + if ( m_pApplyTempl->aColl.pPageDesc ) + aStyleName = m_pApplyTempl->aColl.pPageDesc->GetName(); + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + bCallBase = false; + break; + case SfxStyleFamily::Pseudo: + if( !rSh.HasReadonlySel() ) + { + rSh.SetCurNumRule( *m_pApplyTempl->aColl.pNumRule, + false, + m_pApplyTempl->aColl.pNumRule->GetDefaultListId() ); + bCallBase = false; + m_pApplyTempl->nUndo = + std::min(m_pApplyTempl->nUndo, rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()); + if( m_pApplyTempl->aColl.pNumRule ) + aStyleName = m_pApplyTempl->aColl.pNumRule->GetName(); + } + break; + default: break; + } + + uno::Reference< frame::XDispatchRecorder > xRecorder = + m_rView.GetViewFrame()->GetBindings().GetRecorder(); + if ( !aStyleName.isEmpty() && xRecorder.is() ) + { + SfxShell *pSfxShell = lcl_GetTextShellFromDispatcher( m_rView ); + if ( pSfxShell ) + { + SfxRequest aReq( m_rView.GetViewFrame(), SID_STYLE_APPLY ); + aReq.AppendItem( SfxStringItem( SID_STYLE_APPLY, aStyleName ) ); + aReq.AppendItem( SfxUInt16Item( SID_STYLE_FAMILY, static_cast<sal_uInt16>(m_pApplyTempl->eType) ) ); + aReq.Done(); + } + } + } + + } + ReleaseMouse(); + // Only processed MouseEvents arrive here; only at these this mode can + // be reset. + m_bMBPressed = false; + + // Make this call just to be sure. Selecting has finished surely by now. + // Otherwise the timeout's timer could give problems. + EnterArea(); + g_bNoInterrupt = false; + + if (bCallBase) + Window::MouseButtonUp(rMEvt); + + if (!(pSdrView && rMEvt.GetClicks() == 1 && comphelper::LibreOfficeKit::isActive())) + return; + + // When tiled rendering, single click on a shape text starts editing already. + SdrViewEvent aViewEvent; + SdrHitKind eHit = pSdrView->PickAnything(rMEvt, SdrMouseEventKind::BUTTONUP, aViewEvent); + const SdrMarkList& rMarkList = pSdrView->GetMarkedObjectList(); + if (eHit == SdrHitKind::TextEditObj && rMarkList.GetMarkCount() == 1) + { + if (SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj()) + { + EnterDrawTextMode(pObj->GetLogicRect().Center()); + if ( auto pSwDrawTextShell = dynamic_cast< SwDrawTextShell *>( m_rView.GetCurShell() ) ) + pSwDrawTextShell->Init(); + } + } +} + +/** + * Apply template + */ +void SwEditWin::SetApplyTemplate(const SwApplyTemplate &rTempl) +{ + static bool bIdle = false; + m_pApplyTempl.reset(); + SwWrtShell &rSh = m_rView.GetWrtShell(); + + if(rTempl.m_pFormatClipboard) + { + m_pApplyTempl.reset(new SwApplyTemplate( rTempl )); + m_pApplyTempl->nUndo = rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount(); + SetPointer( PointerStyle::Fill );//@todo #i20119# maybe better a new brush pointer here in future + rSh.NoEdit( false ); + bIdle = rSh.GetViewOptions()->IsIdle(); + rSh.GetViewOptions()->SetIdle( false ); + } + else if(rTempl.nColor) + { + m_pApplyTempl.reset(new SwApplyTemplate( rTempl )); + m_pApplyTempl->nUndo = rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount(); + SetPointer( PointerStyle::Fill ); + rSh.NoEdit( false ); + bIdle = rSh.GetViewOptions()->IsIdle(); + rSh.GetViewOptions()->SetIdle( false ); + } + else if( rTempl.eType != SfxStyleFamily::None ) + { + m_pApplyTempl.reset(new SwApplyTemplate( rTempl )); + m_pApplyTempl->nUndo = rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount(); + SetPointer( PointerStyle::Fill ); + rSh.NoEdit( false ); + bIdle = rSh.GetViewOptions()->IsIdle(); + rSh.GetViewOptions()->SetIdle( false ); + } + else + { + SetPointer( PointerStyle::Text ); + rSh.UnSetVisibleCursor(); + + rSh.GetViewOptions()->SetIdle( bIdle ); + if ( !rSh.IsSelFrameMode() ) + rSh.Edit(); + } + + static sal_uInt16 aInva[] = + { + SID_STYLE_WATERCAN, + SID_ATTR_CHAR_COLOR_EXT, + SID_ATTR_CHAR_COLOR_BACKGROUND_EXT, + 0 + }; + m_rView.GetViewFrame()->GetBindings().Invalidate(aInva); +} + +/** + * Ctor + */ +SwEditWin::SwEditWin(vcl::Window *pParent, SwView &rMyView): + Window(pParent, WinBits(WB_CLIPCHILDREN | WB_DIALOGCONTROL)), + DropTargetHelper( this ), + DragSourceHelper( this ), + + m_aTimer("SwEditWin"), + m_aKeyInputFlushTimer("SwEditWin m_aKeyInputFlushTimer"), + m_eBufferLanguage(LANGUAGE_DONTKNOW), + m_aTemplateTimer("SwEditWin m_aTemplateTimer"), + m_pUserMarkerObj( nullptr ), + + m_rView( rMyView ), + + m_aActHitType(SdrHitKind::NONE), + m_nDropFormat( SotClipboardFormatId::NONE ), + m_nDropAction( 0 ), + m_nDropDestination( SotExchangeDest::NONE ), + + m_eBezierMode(SID_BEZIER_INSERT), + m_nInsFrameColCount( 1 ), + m_eDrawMode(SdrObjKind::NONE), + + m_bMBPressed(false), + m_bInsDraw(false), + m_bInsFrame(false), + m_bIsInMove(false), + m_bIsInDrag(false), + m_bOldIdle(false), + m_bOldIdleSet(false), + m_bChainMode(false), + m_bWasShdwCursor(false), + m_bLockInput(false), + m_bIsRowDrag(false), + m_bUseInputLanguage(false), + m_bObjectSelect(false), + m_nKS_NUMDOWN_Count(0), + m_nKS_NUMINDENTINC_Count(0), + m_pFrameControlsManager(new SwFrameControlsManager(this)) +{ + set_id("writer_edit"); + SetHelpId(HID_EDIT_WIN); + EnableChildTransparentMode(); + SetDialogControlFlags( DialogControlFlags::Return | DialogControlFlags::WantFocus ); + + m_bMBPressed = m_bInsDraw = m_bInsFrame = + m_bIsInDrag = m_bOldIdle = m_bOldIdleSet = m_bChainMode = m_bWasShdwCursor = false; + // initially use the input language + m_bUseInputLanguage = true; + + SetMapMode(MapMode(MapUnit::MapTwip)); + + SetPointer( PointerStyle::Text ); + m_aTimer.SetInvokeHandler(LINK(this, SwEditWin, TimerHandler)); + + m_aKeyInputFlushTimer.SetTimeout( 20 ); + m_aKeyInputFlushTimer.SetInvokeHandler(LINK(this, SwEditWin, KeyInputFlushHandler)); + + // TemplatePointer for colors should be reset without + // selection after single click, but not after double-click (tdf#122442) + m_aTemplateTimer.SetTimeout(GetSettings().GetMouseSettings().GetDoubleClickTime()); + m_aTemplateTimer.SetInvokeHandler(LINK(this, SwEditWin, TemplateTimerHdl)); + + // temporary solution!!! Should set the font of the current + // insert position at every cursor movement! + if( !rMyView.GetDocShell()->IsReadOnly() ) + { + vcl::Font aFont; + SetInputContext( InputContext( aFont, InputContextFlags::Text | + InputContextFlags::ExtText ) ); + } +} + +SwEditWin::~SwEditWin() +{ + disposeOnce(); +} + +void SwEditWin::dispose() +{ + m_pShadCursor.reset(); + + if( s_pQuickHlpData->m_bIsDisplayed && m_rView.GetWrtShellPtr() ) + s_pQuickHlpData->Stop( m_rView.GetWrtShell() ); + g_bExecuteDrag = false; + m_pApplyTempl.reset(); + + m_rView.SetDrawFuncPtr(nullptr); + + m_pUserMarker.reset(); + + m_pAnchorMarker.reset(); + + m_pFrameControlsManager->dispose(); + m_pFrameControlsManager.reset(); + + DragSourceHelper::dispose(); + DropTargetHelper::dispose(); + vcl::Window::dispose(); +} + +/** + * Turn on DrawTextEditMode + */ +void SwEditWin::EnterDrawTextMode( const Point& aDocPos ) +{ + if ( m_rView.EnterDrawTextMode(aDocPos) ) + { + if (m_rView.GetDrawFuncPtr()) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + } + m_rView.NoRotate(); + m_rView.AttrChangedNotify(nullptr); + } +} + +/** + * Turn on DrawMode + */ +bool SwEditWin::EnterDrawMode(const MouseEvent& rMEvt, const Point& aDocPos) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + SdrView *pSdrView = rSh.GetDrawView(); + + if ( m_rView.GetDrawFuncPtr() ) + { + if (rSh.IsDrawCreate()) + return true; + + bool bRet = m_rView.GetDrawFuncPtr()->MouseButtonDown( rMEvt ); + m_rView.AttrChangedNotify(nullptr); + return bRet; + } + + if ( pSdrView && pSdrView->IsTextEdit() ) + { + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + + rSh.EndTextEdit(); // clicked aside, end Edit + rSh.SelectObj( aDocPos ); + if ( !rSh.IsObjSelected() && !rSh.IsFrameSelected() ) + rSh.LeaveSelFrameMode(); + else + { + SwEditWin::s_nDDStartPosY = aDocPos.Y(); + SwEditWin::s_nDDStartPosX = aDocPos.X(); + g_bFrameDrag = true; + } + if( bUnLockView ) + rSh.LockView( false ); + m_rView.AttrChangedNotify(nullptr); + return true; + } + return false; +} + +bool SwEditWin::IsDrawSelMode() const +{ + return IsObjectSelect(); +} + +void SwEditWin::GetFocus() +{ + if ( m_rView.GetPostItMgr()->HasActiveSidebarWin() ) + { + m_rView.GetPostItMgr()->GrabFocusOnActiveSidebarWin(); + } + else + { + m_rView.GotFocus(); + Window::GetFocus(); +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + m_rView.GetWrtShell().InvalidateAccessibleFocus(); +#endif + } +} + +void SwEditWin::LoseFocus() +{ +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + if (m_rView.GetWrtShellPtr()) + m_rView.GetWrtShell().InvalidateAccessibleFocus(); +#endif + Window::LoseFocus(); + if( s_pQuickHlpData && s_pQuickHlpData->m_bIsDisplayed ) + s_pQuickHlpData->Stop( m_rView.GetWrtShell() ); +} + +void SwEditWin::Command( const CommandEvent& rCEvt ) +{ + if (!m_rView.GetViewFrame() || isDisposed()) + { + // If ViewFrame dies shortly, no popup anymore! + Window::Command(rCEvt); + return; + } + + SwWrtShell &rSh = m_rView.GetWrtShell(); + + // The command event is send to the window after a possible context + // menu from an inplace client has been closed. Now we have the chance + // to deactivate the inplace client without any problem regarding parent + // windows and code on the stack. + SfxInPlaceClient* pIPClient = rSh.GetSfxViewShell()->GetIPClient(); + bool bIsOleActive = ( pIPClient && pIPClient->IsObjectInPlaceActive() ); + if ( bIsOleActive && ( rCEvt.GetCommand() == CommandEventId::ContextMenu )) + { + rSh.FinishOLEObj(); + return; + } + + bool bCallBase = true; + + switch ( rCEvt.GetCommand() ) + { + case CommandEventId::ContextMenu: + { + const sal_uInt16 nId = SwInputChild::GetChildWindowId(); + SwInputChild* pChildWin = static_cast<SwInputChild*>(GetView().GetViewFrame()-> + GetChildWindow( nId )); + + if (m_rView.GetPostItMgr()->IsHit(rCEvt.GetMousePosPixel())) + return; + + Point aDocPos( PixelToLogic( rCEvt.GetMousePosPixel() ) ); + if ( !rCEvt.IsMouseEvent() ) + aDocPos = rSh.GetCharRect().Center(); + + // Don't trigger the command on a frame anchored to header/footer is not editing it + FrameControlType eControl; + bool bOverFly = false; + bool bPageAnchored = false; + bool bOverHeaderFooterFly = IsOverHeaderFooterFly( aDocPos, eControl, bOverFly, bPageAnchored ); + // !bOverHeaderFooterFly doesn't mean we have a frame to select + if ( !bPageAnchored && rCEvt.IsMouseEvent( ) && + ( ( rSh.IsHeaderFooterEdit( ) && !bOverHeaderFooterFly && bOverFly ) || + ( !rSh.IsHeaderFooterEdit( ) && bOverHeaderFooterFly ) ) ) + { + return; + } + + if((!pChildWin || pChildWin->GetView() != &m_rView) && + !rSh.IsDrawCreate() && !IsDrawAction()) + { + CurrShell aCurr( &rSh ); + if (!m_pApplyTempl) + { + if (g_bNoInterrupt) + { + ReleaseMouse(); + g_bNoInterrupt = false; + m_bMBPressed = false; + } + if ( rCEvt.IsMouseEvent() ) + { + SelectMenuPosition(rSh, rCEvt.GetMousePosPixel()); + m_rView.StopShellTimer(); + } + const Point aPixPos = LogicToPixel( aDocPos ); + + if ( m_rView.GetDocShell()->IsReadOnly() ) + { + SwReadOnlyPopup aROPopup(aDocPos, m_rView); + + ui::ContextMenuExecuteEvent aEvent; + aEvent.SourceWindow = VCLUnoHelper::GetInterface( this ); + aEvent.ExecutePosition.X = aPixPos.X(); + aEvent.ExecutePosition.Y = aPixPos.Y(); + css::uno::Reference<css::awt::XPopupMenu> xMenu; + auto xMenuInterface = aROPopup.CreateMenuInterface(); + if (GetView().TryContextMenuInterception(xMenuInterface, "private:resource/ReadonlyContextMenu", xMenu, aEvent)) + { + if (xMenu.is()) + { + css::uno::Reference<css::awt::XWindowPeer> xParent(aEvent.SourceWindow, css::uno::UNO_QUERY); + sal_uInt16 nExecId = xMenu->execute(xParent, css::awt::Rectangle(aPixPos.X(), aPixPos.Y(), 1, 1), + css::awt::PopupMenuDirection::EXECUTE_DOWN); + if (!::ExecuteMenuCommand(xMenu, *m_rView.GetViewFrame(), nExecId)) + aROPopup.Execute(this, nExecId); + } + else + aROPopup.Execute(this, aPixPos); + } + } + else if ( !m_rView.ExecSpellPopup( aDocPos ) ) + SfxDispatcher::ExecutePopup(this, &aPixPos); + } + else if (m_pApplyTempl->nUndo < rSh.GetDoc()->GetIDocumentUndoRedo().GetUndoActionCount()) + { + // Undo until we reach the point when we entered this context. + rSh.Do(SwWrtShell::UNDO); + } + bCallBase = false; + } + } + break; + + case CommandEventId::Wheel: + case CommandEventId::StartAutoScroll: + case CommandEventId::AutoScroll: + if (m_pSavedOutlineFrame && rSh.GetViewOptions()->IsShowOutlineContentVisibilityButton()) + { + GetFrameControlsManager().RemoveControlsByType(FrameControlType::Outline, m_pSavedOutlineFrame); + m_pSavedOutlineFrame = nullptr; + } + m_pShadCursor.reset(); + bCallBase = !m_rView.HandleWheelCommands( rCEvt ); + break; + + case CommandEventId::LongPress: + case CommandEventId::Swipe: //nothing yet + break; + + case CommandEventId::StartExtTextInput: + { + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() && + rSh.IsCursorReadonly(); + if(!bIsDocReadOnly) + { + if( rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit() ) + { + bCallBase = false; + rSh.GetDrawView()->GetTextEditOutlinerView()->Command( rCEvt ); + } + else + { + if( rSh.HasSelection() ) + rSh.DelRight(); + + bCallBase = false; + LanguageType eInputLanguage = GetInputLanguage(); + rSh.CreateExtTextInput(eInputLanguage); + } + } + break; + } + case CommandEventId::EndExtTextInput: + { + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() && + rSh.IsCursorReadonly(); + if(!bIsDocReadOnly) + { + if( rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit() ) + { + bCallBase = false; + rSh.GetDrawView()->GetTextEditOutlinerView()->Command( rCEvt ); + } + else + { + bCallBase = false; + OUString sRecord = rSh.DeleteExtTextInput(); + uno::Reference< frame::XDispatchRecorder > xRecorder = + m_rView.GetViewFrame()->GetBindings().GetRecorder(); + + if ( !sRecord.isEmpty() ) + { + // convert quotes in IME text + // works on the last input character, this is especially in Korean text often done + // quotes that are inside of the string are not replaced! + const sal_Unicode aCh = sRecord[sRecord.getLength() - 1]; + SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get(); + SvxAutoCorrect* pACorr = rACfg.GetAutoCorrect(); + if(pACorr && + (( pACorr->IsAutoCorrFlag( ACFlags::ChgQuotes ) && ('\"' == aCh ))|| + ( pACorr->IsAutoCorrFlag( ACFlags::ChgSglQuotes ) && ( '\'' == aCh)))) + { + rSh.DelLeft(); + rSh.AutoCorrect( *pACorr, aCh ); + } + + if ( xRecorder.is() ) + { + // determine Shell + SfxShell *pSfxShell = lcl_GetTextShellFromDispatcher( m_rView ); + // generate request and record + if (pSfxShell) + { + SfxRequest aReq( m_rView.GetViewFrame(), FN_INSERT_STRING ); + aReq.AppendItem( SfxStringItem( FN_INSERT_STRING, sRecord ) ); + aReq.Done(); + } + } + } + } + } + } + break; + case CommandEventId::ExtTextInput: + { + bool bIsDocReadOnly = m_rView.GetDocShell()->IsReadOnly() && + rSh.IsCursorReadonly(); + if (!bIsDocReadOnly && !rSh.HasReadonlySel()) + { + if( s_pQuickHlpData->m_bIsDisplayed ) + s_pQuickHlpData->Stop( rSh ); + + if( rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit() ) + { + bCallBase = false; + rSh.GetDrawView()->GetTextEditOutlinerView()->Command( rCEvt ); + } + else + { + const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); + if( pData ) + { + bCallBase = false; + rSh.SetExtTextInputData( *pData ); + } + } + uno::Reference< frame::XDispatchRecorder > xRecorder = + m_rView.GetViewFrame()->GetBindings().GetRecorder(); + if(!xRecorder.is()) + { + SvxAutoCorrCfg& rACfg = SvxAutoCorrCfg::Get(); + if (!rACfg.IsAutoTextTip() || !ShowAutoText(rSh.GetChunkForAutoText())) + { + SvxAutoCorrect* pACorr = rACfg.GetAutoCorrect(); + if (pACorr && pACorr->GetSwFlags().bAutoCompleteWords) + ShowAutoCorrectQuickHelp(rSh.GetPrevAutoCorrWord(*pACorr), *pACorr); + } + } + } + + if (rSh.HasReadonlySel()) + { + // Inform the user that the request has been ignored. + auto xInfo = std::make_shared<weld::GenericDialogController>( + GetFrameWeld(), "modules/swriter/ui/inforeadonlydialog.ui", "InfoReadonlyDialog"); + weld::DialogController::runAsync(xInfo, [](sal_Int32 /*nResult*/) {}); + } + } + break; + case CommandEventId::CursorPos: + // will be handled by the base class + break; + + case CommandEventId::PasteSelection: + if( !m_rView.GetDocShell()->IsReadOnly() ) + { + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromPrimarySelection()); + if( !aDataHelper.GetXTransferable().is() ) + break; + + SotExchangeDest nDropDestination = GetDropDestination( rCEvt.GetMousePosPixel() ); + if( nDropDestination == SotExchangeDest::NONE ) + break; + SotClipboardFormatId nDropFormat; + sal_uInt8 nEventAction, nDropAction; + SotExchangeActionFlags nActionFlags; + nDropAction = SotExchange::GetExchangeAction( + aDataHelper.GetDataFlavorExVector(), + nDropDestination, EXCHG_IN_ACTION_COPY, + EXCHG_IN_ACTION_COPY, nDropFormat, + nEventAction, + SotClipboardFormatId::NONE, nullptr, + &nActionFlags ); + if( EXCHG_INOUT_ACTION_NONE != nDropAction ) + { + const Point aDocPt( PixelToLogic( rCEvt.GetMousePosPixel() ) ); + SwTransferable::PasteData( aDataHelper, rSh, nDropAction, nActionFlags, + nDropFormat, nDropDestination, false, + false, &aDocPt, EXCHG_IN_ACTION_COPY, + true ); + } + } + break; + case CommandEventId::ModKeyChange : + { + const CommandModKeyData* pCommandData = rCEvt.GetModKeyData(); + if (!pCommandData->IsDown() && pCommandData->IsMod1() && !pCommandData->IsMod2()) + { + sal_uInt16 nSlot = 0; + if(pCommandData->IsLeftShift() && !pCommandData->IsRightShift()) + nSlot = SID_ATTR_PARA_LEFT_TO_RIGHT; + else if(!pCommandData->IsLeftShift() && pCommandData->IsRightShift()) + nSlot = SID_ATTR_PARA_RIGHT_TO_LEFT; + if(nSlot && SW_MOD()->GetCTLOptions().IsCTLFontEnabled()) + GetView().GetViewFrame()->GetDispatcher()->Execute(nSlot); + } + } + break; + case CommandEventId::InputLanguageChange : + // i#42732 - update state of fontname if input language changes + g_bInputLanguageSwitched = true; + SetUseInputLanguage( true ); + break; + case CommandEventId::SelectionChange: + { + const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData(); + rSh.SttCursorMove(); + rSh.GoStartSentence(); + rSh.GetCursor()->GetPoint()->nContent += sal::static_int_cast<sal_uInt16, sal_uLong>(pData->GetStart()); + rSh.SetMark(); + rSh.GetCursor()->GetMark()->nContent += sal::static_int_cast<sal_uInt16, sal_uLong>(pData->GetEnd() - pData->GetStart()); + rSh.EndCursorMove( true ); + } + break; + case CommandEventId::PrepareReconversion: + if( rSh.HasSelection() ) + { + SwPaM *pCursor = rSh.GetCursor(); + + if( rSh.IsMultiSelection() ) + { + if (pCursor && !pCursor->HasMark() && + pCursor->GetPoint() == pCursor->GetMark()) + { + rSh.GoPrevCursor(); + pCursor = rSh.GetCursor(); + } + + // Cancel all selections other than the last selected one. + while( rSh.GetCursor()->GetNext() != rSh.GetCursor() ) + delete rSh.GetCursor()->GetNext(); + } + + if( pCursor ) + { + SwNodeOffset nPosNodeIdx = pCursor->GetPoint()->nNode.GetIndex(); + const sal_Int32 nPosIdx = pCursor->GetPoint()->nContent.GetIndex(); + SwNodeOffset nMarkNodeIdx = pCursor->GetMark()->nNode.GetIndex(); + const sal_Int32 nMarkIdx = pCursor->GetMark()->nContent.GetIndex(); + + if( !rSh.GetCursor()->HasMark() ) + rSh.GetCursor()->SetMark(); + + rSh.SttCursorMove(); + + if( nPosNodeIdx < nMarkNodeIdx ) + { + rSh.GetCursor()->GetPoint()->nNode = nPosNodeIdx; + rSh.GetCursor()->GetPoint()->nContent = nPosIdx; + rSh.GetCursor()->GetMark()->nNode = nPosNodeIdx; + rSh.GetCursor()->GetMark()->nContent = + rSh.GetCursor()->GetContentNode()->Len(); + } + else if( nPosNodeIdx == nMarkNodeIdx ) + { + rSh.GetCursor()->GetPoint()->nNode = nPosNodeIdx; + rSh.GetCursor()->GetPoint()->nContent = nPosIdx; + rSh.GetCursor()->GetMark()->nNode = nMarkNodeIdx; + rSh.GetCursor()->GetMark()->nContent = nMarkIdx; + } + else + { + rSh.GetCursor()->GetMark()->nNode = nMarkNodeIdx; + rSh.GetCursor()->GetMark()->nContent = nMarkIdx; + rSh.GetCursor()->GetPoint()->nNode = nMarkNodeIdx; + rSh.GetCursor()->GetPoint()->nContent = + rSh.GetCursor()->GetContentNode( false )->Len(); + } + + rSh.EndCursorMove( true ); + } + } + break; + case CommandEventId::QueryCharPosition: + { + bool bVertical = rSh.IsInVerticalText(); + const SwPosition& rPos = *rSh.GetCursor()->GetPoint(); + SwDocShell* pDocSh = m_rView.GetDocShell(); + SwDoc *pDoc = pDocSh->GetDoc(); + SwExtTextInput* pInput = pDoc->GetExtTextInput( rPos.nNode.GetNode(), rPos.nContent.GetIndex() ); + if ( pInput ) + { + const SwPosition& rStart = *pInput->Start(); + const SwPosition& rEnd = *pInput->End(); + int nSize = 0; + for ( SwIndex nIndex = rStart.nContent; nIndex < rEnd.nContent; ++nIndex ) + { + ++nSize; + } + vcl::Window& rWin = rSh.GetView().GetEditWin(); + if ( nSize == 0 ) + { + // When the composition does not exist, use Caret rect instead. + const SwRect& aCaretRect ( rSh.GetCharRect() ); + tools::Rectangle aRect( aCaretRect.Left(), aCaretRect.Top(), aCaretRect.Right(), aCaretRect.Bottom() ); + rWin.SetCompositionCharRect( &aRect, 1, bVertical ); + } + else + { + std::unique_ptr<tools::Rectangle[]> aRects(new tools::Rectangle[ nSize ]); + int nRectIndex = 0; + for ( SwIndex nIndex = rStart.nContent; nIndex < rEnd.nContent; ++nIndex ) + { + const SwPosition aPos( rStart.nNode, nIndex ); + SwRect aRect ( rSh.GetCharRect() ); + rSh.GetCharRectAt( aRect, &aPos ); + aRects[ nRectIndex ] = tools::Rectangle( aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom() ); + ++nRectIndex; + } + rWin.SetCompositionCharRect( aRects.get(), nSize, bVertical ); + } + } + bCallBase = false; + } + break; + default: + SAL_WARN("sw.ui", "unknown command."); + break; + } + if (bCallBase) + Window::Command(rCEvt); +} + +/* i#18686 select the object/cursor at the mouse + position of the context menu request */ +void SwEditWin::SelectMenuPosition(SwWrtShell& rSh, const Point& rMousePos ) +{ + const Point aDocPos( PixelToLogic( rMousePos ) ); + const bool bIsInsideSelectedObj( rSh.IsInsideSelectedObj( aDocPos ) ); + //create a synthetic mouse event out of the coordinates + MouseEvent aMEvt(rMousePos); + SdrView *pSdrView = rSh.GetDrawView(); + if ( pSdrView ) + { + // no close of insert_draw and reset of + // draw mode, if context menu position is inside a selected object. + if ( !bIsInsideSelectedObj && m_rView.GetDrawFuncPtr() ) + { + + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + SfxBindings& rBind = m_rView.GetViewFrame()->GetBindings(); + rBind.Invalidate( SID_ATTR_SIZE ); + rBind.Invalidate( SID_TABLE_CELL ); + } + + // if draw text is active and there's a text selection + // at the mouse position then do nothing + if(rSh.GetSelectionType() & SelectionType::DrawObjectEditMode) + { + OutlinerView* pOLV = pSdrView->GetTextEditOutlinerView(); + ESelection aSelection = pOLV->GetSelection(); + if(!aSelection.IsZero()) + { + SdrOutliner* pOutliner = pSdrView->GetTextEditOutliner(); + bool bVertical = pOutliner->IsVertical(); + const EditEngine& rEditEng = pOutliner->GetEditEngine(); + Point aEEPos(aDocPos); + const tools::Rectangle& rOutputArea = pOLV->GetOutputArea(); + // regard vertical mode + if(bVertical) + { + aEEPos -= rOutputArea.TopRight(); + //invert the horizontal direction and exchange X and Y + tools::Long nTemp = -aEEPos.X(); + aEEPos.setX( aEEPos.Y() ); + aEEPos.setY( nTemp ); + } + else + aEEPos -= rOutputArea.TopLeft(); + + EPosition aDocPosition = rEditEng.FindDocPosition(aEEPos); + ESelection aCompare(aDocPosition.nPara, aDocPosition.nIndex); + // make it a forward selection - otherwise the IsLess/IsGreater do not work :-( + aSelection.Adjust(); + if(!(aCompare < aSelection) && !(aCompare > aSelection)) + { + return; + } + } + + } + + if (pSdrView->MouseButtonDown( aMEvt, GetOutDev() ) ) + { + pSdrView->MouseButtonUp( aMEvt, GetOutDev() ); + rSh.GetView().GetViewFrame()->GetBindings().InvalidateAll(false); + return; + } + } + rSh.ResetCursorStack(); + + if ( EnterDrawMode( aMEvt, aDocPos ) ) + { + return; + } + if ( m_rView.GetDrawFuncPtr() && m_bInsFrame ) + { + StopInsFrame(); + rSh.Edit(); + } + + UpdatePointer( aDocPos ); + + if( !rSh.IsSelFrameMode() && + !GetView().GetViewFrame()->GetDispatcher()->IsLocked() ) + { + // Test if there is a draw object at that position and if it should be selected. + bool bShould = rSh.ShouldObjectBeSelected(aDocPos); + + if(bShould) + { + m_rView.NoRotate(); + rSh.HideCursor(); + + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + bool bSelObj = rSh.SelectObj( aDocPos ); + if( bUnLockView ) + rSh.LockView( false ); + + if( bSelObj ) + { + // in case the frame was deselected in the macro + // just the cursor has to be displayed again. + if( FrameTypeFlags::NONE == rSh.GetSelFrameType() ) + rSh.ShowCursor(); + else + { + if (rSh.IsFrameSelected() && m_rView.GetDrawFuncPtr()) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + + rSh.EnterSelFrameMode( &aDocPos ); + g_bFrameDrag = true; + UpdatePointer( aDocPos ); + return; + } + } + + if (!m_rView.GetDrawFuncPtr()) + rSh.ShowCursor(); + } + } + else if ( rSh.IsSelFrameMode() && + (m_aActHitType == SdrHitKind::NONE || + !bIsInsideSelectedObj)) + { + m_rView.NoRotate(); + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView( true ); + + if ( rSh.IsSelFrameMode() ) + { + rSh.UnSelectFrame(); + rSh.LeaveSelFrameMode(); + m_rView.AttrChangedNotify(nullptr); + } + + bool bSelObj = rSh.SelectObj( aDocPos, 0/*nFlag*/ ); + if( bUnLockView ) + rSh.LockView( false ); + + if( !bSelObj ) + { + // move cursor here so that it is not drawn in the + // frame at first; ShowCursor() happens in LeaveSelFrameMode() + g_bValidCursorPos = !(CRSR_POSCHG & rSh.CallSetCursor(&aDocPos, false)); + rSh.LeaveSelFrameMode(); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + else + { + rSh.HideCursor(); + rSh.EnterSelFrameMode( &aDocPos ); + rSh.SelFlyGrabCursor(); + rSh.MakeSelVisible(); + g_bFrameDrag = true; + if( rSh.IsFrameSelected() && + m_rView.GetDrawFuncPtr() ) + { + m_rView.GetDrawFuncPtr()->Deactivate(); + m_rView.SetDrawFuncPtr(nullptr); + m_rView.LeaveDrawCreate(); + m_rView.AttrChangedNotify(nullptr); + } + UpdatePointer( aDocPos ); + } + } + else if ( rSh.IsSelFrameMode() && bIsInsideSelectedObj ) + { + // Object at the mouse cursor is already selected - do nothing + return; + } + + if ( rSh.IsGCAttr() ) + { + rSh.GCAttr(); + rSh.ClearGCAttr(); + } + + bool bOverSelect = rSh.TestCurrPam( aDocPos ); + bool bOverURLGrf = false; + if( !bOverSelect ) + bOverURLGrf = bOverSelect = nullptr != rSh.IsURLGrfAtPos( aDocPos ); + + if ( !bOverSelect ) + { + // create only temporary move context because otherwise + // the query against the content form doesn't work!!! + SwMvContext aMvContext( &rSh ); + rSh.CallSetCursor(&aDocPos, false); + } + if( !bOverURLGrf ) + { + const SelectionType nSelType = rSh.GetSelectionType(); + if( nSelType == SelectionType::Ole || + nSelType == SelectionType::Graphic ) + { + SwMvContext aMvContext( &rSh ); + if( !rSh.IsFrameSelected() ) + rSh.GotoNextFly(); + rSh.EnterSelFrameMode(); + } + } +} + +static SfxShell* lcl_GetTextShellFromDispatcher( SwView const & rView ) +{ + // determine Shell + SfxShell* pShell; + SfxDispatcher* pDispatcher = rView.GetViewFrame()->GetDispatcher(); + for(sal_uInt16 i = 0; true; ++i ) + { + pShell = pDispatcher->GetShell( i ); + if( !pShell || dynamic_cast< const SwTextShell *>( pShell ) != nullptr ) + break; + } + return pShell; +} + +IMPL_LINK_NOARG(SwEditWin, KeyInputFlushHandler, Timer *, void) +{ + FlushInBuffer(); +} + +void SwEditWin::InitStaticData() +{ + s_pQuickHlpData = new QuickHelpData(); +} + +void SwEditWin::FinitStaticData() +{ + delete s_pQuickHlpData; +} +/* i#3370 - remove quick help to prevent saving + * of autocorrection suggestions */ +void SwEditWin::StopQuickHelp() +{ + if( HasFocus() && s_pQuickHlpData && s_pQuickHlpData->m_bIsDisplayed ) + s_pQuickHlpData->Stop( m_rView.GetWrtShell() ); +} + +IMPL_LINK_NOARG(SwEditWin, TemplateTimerHdl, Timer *, void) +{ + SetApplyTemplate(SwApplyTemplate()); +} + +void SwEditWin::SetChainMode( bool bOn ) +{ + if ( !m_bChainMode ) + StopInsFrame(); + + m_pUserMarker.reset(); + + m_bChainMode = bOn; + + static sal_uInt16 aInva[] = + { + FN_FRAME_CHAIN, FN_FRAME_UNCHAIN, 0 + }; + m_rView.GetViewFrame()->GetBindings().Invalidate(aInva); +} + +uno::Reference< css::accessibility::XAccessible > SwEditWin::CreateAccessible() +{ +#if !ENABLE_WASM_STRIP_ACCESSIBILITY + SolarMutexGuard aGuard; // this should have happened already!!! + SwWrtShell *pSh = m_rView.GetWrtShellPtr(); + OSL_ENSURE( pSh, "no writer shell, no accessible object" ); + uno::Reference< + css::accessibility::XAccessible > xAcc; + if( pSh ) + xAcc = pSh->CreateAccessible(); + + return xAcc; +#else + return nullptr; +#endif +} + +void QuickHelpData::Move( QuickHelpData& rCpy ) +{ + m_aHelpStrings.clear(); + m_aHelpStrings.swap( rCpy.m_aHelpStrings ); + + m_bIsDisplayed = rCpy.m_bIsDisplayed; + nCurArrPos = rCpy.nCurArrPos; + m_bAppendSpace = rCpy.m_bAppendSpace; + m_bIsTip = rCpy.m_bIsTip; + m_bIsAutoText = rCpy.m_bIsAutoText; +} + +void QuickHelpData::ClearContent() +{ + nCurArrPos = nNoPos; + m_bIsDisplayed = m_bAppendSpace = false; + nTipId = nullptr; + m_aHelpStrings.clear(); + m_bIsTip = true; + m_bIsAutoText = true; +} + +void QuickHelpData::Start(SwWrtShell& rSh, const bool bRestart) +{ + if (bRestart) + { + nCurArrPos = 0; + } + m_bIsDisplayed = true; + + vcl::Window& rWin = rSh.GetView().GetEditWin(); + if( m_bIsTip ) + { + Point aPt( rWin.OutputToScreenPixel( rWin.LogicToPixel( + rSh.GetCharRect().Pos() ))); + aPt.AdjustY( -3 ); + nTipId = Help::ShowPopover(&rWin, tools::Rectangle( aPt, Size( 1, 1 )), + CurStr(), + QuickHelpFlags::Left | QuickHelpFlags::Bottom); + } + else + { + OUString sStr(CurStr()); + sStr = sStr.copy(CurLen()); + sal_uInt16 nL = sStr.getLength(); + const ExtTextInputAttr nVal = ExtTextInputAttr::DottedUnderline | + ExtTextInputAttr::Highlight; + const std::vector<ExtTextInputAttr> aAttrs( nL, nVal ); + CommandExtTextInputData aCETID( sStr, aAttrs.data(), nL, + 0, false ); + + //fdo#33092. If the current input language is the default + //language that text would appear in if typed, then don't + //force a language on for the ExtTextInput. + LanguageType eInputLanguage = rWin.GetInputLanguage(); + if (lcl_isNonDefaultLanguage(eInputLanguage, + rSh.GetView(), sStr) == INVALID_HINT) + { + eInputLanguage = LANGUAGE_DONTKNOW; + } + + rSh.CreateExtTextInput(eInputLanguage); + rSh.SetExtTextInputData( aCETID ); + } +} + +void QuickHelpData::Stop( SwWrtShell& rSh ) +{ + if( !m_bIsTip ) + rSh.DeleteExtTextInput( false ); + else if( nTipId ) + { + vcl::Window& rWin = rSh.GetView().GetEditWin(); + Help::HidePopover(&rWin, nTipId); + } + ClearContent(); +} + +void QuickHelpData::FillStrArr( SwWrtShell const & rSh, const OUString& rWord ) +{ + enum Capitalization { CASE_LOWER, CASE_UPPER, CASE_SENTENCE, CASE_OTHER }; + + // Determine word capitalization + const CharClass& rCC = GetAppCharClass(); + const OUString sWordLower = rCC.lowercase( rWord ); + Capitalization aWordCase = CASE_OTHER; + if ( !rWord.isEmpty() ) + { + if ( rWord[0] == sWordLower[0] ) + { + if ( rWord == sWordLower ) + aWordCase = CASE_LOWER; + } + else + { + // First character is not lower case i.e. assume upper or title case + OUString sWordSentence = sWordLower.replaceAt( 0, 1, rtl::OUStringChar(rWord[0]) ); + if ( rWord == sWordSentence ) + aWordCase = CASE_SENTENCE; + else + { + if ( rWord == rCC.uppercase( rWord ) ) + aWordCase = CASE_UPPER; + } + } + } + + salhelper::SingletonRef<SwCalendarWrapper>* pCalendar = s_getCalendarWrapper(); + (*pCalendar)->LoadDefaultCalendar( rSh.GetCurLang() ); + + // Add matching calendar month and day names + for ( const auto& aNames : { (*pCalendar)->getMonths(), (*pCalendar)->getDays() } ) + { + for ( const auto& rName : aNames ) + { + const OUString& rStr( rName.FullName ); + // Check string longer than word and case insensitive match + if( rStr.getLength() > rWord.getLength() && + rCC.lowercase( rStr, 0, rWord.getLength() ) == sWordLower ) + { + OUString sStr; + + //fdo#61251 if it's an exact match, ensure unchanged replacement + //exists as a candidate + if (rStr.startsWith(rWord)) + m_aHelpStrings.emplace_back(rStr, rWord.getLength()); + else + sStr = rStr; // to be added if no case conversion is performed below + + if ( aWordCase == CASE_LOWER ) + sStr = rCC.lowercase(rStr); + else if ( aWordCase == CASE_SENTENCE ) + sStr = rCC.lowercase(rStr).replaceAt(0, 1, rtl::OUStringChar(rStr[0])); + else if ( aWordCase == CASE_UPPER ) + sStr = rCC.uppercase(rStr); + + if (!sStr.isEmpty()) + m_aHelpStrings.emplace_back(sStr, rWord.getLength()); + } + } + } + + // Add matching current date in ISO 8601 format, for example 2016-01-30 + OUString rStrToday; + + // do not suggest for single years, for example for "2016", + // only for "201" or "2016-..." (to avoid unintentional text + // insertion at line ending, for example typing "30 January 2016") + if (!rWord.isEmpty() && rWord.getLength() != 4 && rWord[0] == '2') + { + rStrToday = utl::toISO8601(DateTime(Date(Date::SYSTEM)).GetUNODateTime()); + if (rStrToday.startsWith(rWord)) + m_aHelpStrings.emplace_back(rStrToday, rWord.getLength()); + } + + // Add matching words from AutoCompleteWord list + const SwAutoCompleteWord& rACList = SwEditShell::GetAutoCompleteWords(); + std::vector<OUString> strings; + + if ( !rACList.GetWordsMatching( rWord, strings ) ) + return; + + for (const OUString & aCompletedString : strings) + { + // when we have a matching current date, avoid to suggest + // other words with the same matching starting characters, + // for example 2016-01-3 instead of 2016-01-30 + if (!rStrToday.isEmpty() && aCompletedString.startsWith(rWord)) + continue; + + OUString sStr; + + //fdo#61251 if it's an exact match, ensure unchanged replacement + //exists as a candidate + if (aCompletedString.startsWith(rWord)) + m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength()); + else + sStr = aCompletedString; // to be added if no case conversion is performed below + + if (aWordCase == CASE_LOWER) + sStr = rCC.lowercase(aCompletedString); + else if (aWordCase == CASE_SENTENCE) + sStr = rCC.lowercase(aCompletedString) + .replaceAt(0, 1, rtl::OUStringChar(aCompletedString[0])); + else if (aWordCase == CASE_UPPER) + sStr = rCC.uppercase(aCompletedString); + + if (!sStr.isEmpty()) + m_aHelpStrings.emplace_back(aCompletedString, rWord.getLength()); + } +} + +namespace { + +class CompareIgnoreCaseAsciiFavorExact +{ + const OUString &m_rOrigWord; +public: + explicit CompareIgnoreCaseAsciiFavorExact(const OUString& rOrigWord) + : m_rOrigWord(rOrigWord) + { + } + + bool operator()(const std::pair<OUString, sal_uInt16>& s1, + const std::pair<OUString, sal_uInt16>& s2) const + { + int nRet = s1.first.compareToIgnoreAsciiCase(s2.first); + if (nRet == 0) + { + //fdo#61251 sort stuff that starts with the exact rOrigWord before + //another ignore-case candidate + int n1StartsWithOrig = s1.first.startsWith(m_rOrigWord) ? 0 : 1; + int n2StartsWithOrig = s2.first.startsWith(m_rOrigWord) ? 0 : 1; + return n1StartsWithOrig < n2StartsWithOrig; + } + return nRet < 0; + } +}; + +struct EqualIgnoreCaseAscii +{ + bool operator()(const std::pair<OUString, sal_uInt16>& s1, + const std::pair<OUString, sal_uInt16>& s2) const + { + return s1.first.equalsIgnoreAsciiCase(s2.first); + } +}; + +} // anonymous namespace + +// TODO Implement an i18n aware sort +void QuickHelpData::SortAndFilter(const OUString &rOrigWord) +{ + std::sort( m_aHelpStrings.begin(), + m_aHelpStrings.end(), + CompareIgnoreCaseAsciiFavorExact(rOrigWord) ); + + const auto& it + = std::unique(m_aHelpStrings.begin(), m_aHelpStrings.end(), EqualIgnoreCaseAscii()); + m_aHelpStrings.erase( it, m_aHelpStrings.end() ); + + nCurArrPos = 0; +} + +// For a given chunk of typed text between 3 and 9 characters long that may start at a word boundary +// or in a whitespace and may include whitespaces, SwEditShell::GetChunkForAutoTextcreates a list of +// possible candidates for long AutoText names. Let's say, we have typed text "lorem ipsum dr f"; +// and the cursor is right after the "f". SwEditShell::GetChunkForAutoText would take " dr f", +// since it's the longest chunk to the left of the cursor no longer than 9 characters, not starting +// in the middle of a word. Then it would create this list from it (in this order, longest first): +// " dr f" +// " dr f" +// "dr f" +// It cannot add "r f", because it starts in the middle of the word "dr"; also it cannot give " f", +// because it's only 2 characters long. +// Now the result of SwEditShell::GetChunkForAutoText is passed here to SwEditWin::ShowAutoText, and +// then to SwGlossaryList::HasLongName, where all existing autotext entries' long names are tested +// if they start with one of the list elements. The matches are sorted according the position of the +// candidate that matched first, then alphabetically inside the group of suggestions for a given +// candidate. Say, if we have these AutoText entry long names: +// "Dr Frodo" +// "Dr Credo" +// "Or Bilbo" +// "dr foo" +// " Dr Fuzz" +// " dr Faust" +// the resulting list would be: +// " Dr Fuzz" -> matches the first (longest) item in the candidates list +// " dr Faust" -> matches the second candidate item +// "Dr Foo" -> first item of the two matching the third candidate; alphabetically sorted +// "Dr Frodo" -> second item of the two matching the third candidate; alphabetically sorted +// Each of the resulting suggestions knows the length of the candidate it replaces, so accepting the +// first suggestion would replace 6 characters before cursor, while tabbing to and accepting the +// last suggestion would replace only 4 characters to the left of cursor. +bool SwEditWin::ShowAutoText(const std::vector<OUString>& rChunkCandidates) +{ + s_pQuickHlpData->ClearContent(); + if (!rChunkCandidates.empty()) + { + SwGlossaryList* pList = ::GetGlossaryList(); + pList->HasLongName(rChunkCandidates, s_pQuickHlpData->m_aHelpStrings); + } + + if (!s_pQuickHlpData->m_aHelpStrings.empty()) + { + s_pQuickHlpData->Start(m_rView.GetWrtShell(), true); + } + return !s_pQuickHlpData->m_aHelpStrings.empty(); +} + +void SwEditWin::ShowAutoCorrectQuickHelp( + const OUString& rWord, SvxAutoCorrect& rACorr ) +{ + if (rWord.isEmpty()) + return; + SwWrtShell& rSh = m_rView.GetWrtShell(); + s_pQuickHlpData->ClearContent(); + + if( s_pQuickHlpData->m_aHelpStrings.empty() && + rACorr.GetSwFlags().bAutoCompleteWords ) + { + s_pQuickHlpData->m_bIsAutoText = false; + s_pQuickHlpData->m_bIsTip = rACorr.GetSwFlags().bAutoCmpltShowAsTip; + + // Get the necessary data to show help text. + s_pQuickHlpData->FillStrArr( rSh, rWord ); + } + + if( !s_pQuickHlpData->m_aHelpStrings.empty() ) + { + s_pQuickHlpData->SortAndFilter(rWord); + s_pQuickHlpData->Start(rSh, true); + } +} + +bool SwEditWin::IsInHeaderFooter( const Point &rDocPt, FrameControlType &rControl ) const +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + const SwPageFrame* pPageFrame = rSh.GetLayout()->GetPageAtPos( rDocPt ); + + if ( pPageFrame && pPageFrame->IsOverHeaderFooterArea( rDocPt, rControl ) ) + return true; + + if ( rSh.IsShowHeaderFooterSeparator( FrameControlType::Header ) || rSh.IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) + { + SwFrameControlsManager &rMgr = rSh.GetView().GetEditWin().GetFrameControlsManager(); + Point aPoint( LogicToPixel( rDocPt ) ); + + if ( rSh.IsShowHeaderFooterSeparator( FrameControlType::Header ) ) + { + SwFrameControlPtr pControl = rMgr.GetControl( FrameControlType::Header, pPageFrame ); + if ( pControl && pControl->Contains( aPoint ) ) + { + rControl = FrameControlType::Header; + return true; + } + } + + if ( rSh.IsShowHeaderFooterSeparator( FrameControlType::Footer ) ) + { + SwFrameControlPtr pControl = rMgr.GetControl( FrameControlType::Footer, pPageFrame ); + if ( pControl && pControl->Contains( aPoint ) ) + { + rControl = FrameControlType::Footer; + return true; + } + } + } + + return false; +} + +bool SwEditWin::IsOverHeaderFooterFly( const Point& rDocPos, FrameControlType& rControl, bool& bOverFly, bool& bPageAnchored ) const +{ + bool bRet = false; + Point aPt( rDocPos ); + SwWrtShell &rSh = m_rView.GetWrtShell(); + SwPaM aPam( *rSh.GetCurrentShellCursor().GetPoint() ); + rSh.GetLayout()->GetModelPositionForViewPoint( aPam.GetPoint(), aPt, nullptr, true ); + + const SwStartNode* pStartFly = aPam.GetPoint()->nNode.GetNode().FindFlyStartNode(); + if ( pStartFly ) + { + bOverFly = true; + SwFrameFormat* pFlyFormat = pStartFly->GetFlyFormat( ); + if ( pFlyFormat ) + { + const SwPosition* pAnchor = pFlyFormat->GetAnchor( ).GetContentAnchor( ); + if ( pAnchor ) + { + bool bInHeader = pAnchor->nNode.GetNode( ).FindHeaderStartNode( ) != nullptr; + bool bInFooter = pAnchor->nNode.GetNode( ).FindFooterStartNode( ) != nullptr; + + bRet = bInHeader || bInFooter; + if ( bInHeader ) + rControl = FrameControlType::Header; + else if ( bInFooter ) + rControl = FrameControlType::Footer; + } + else + bPageAnchored = pFlyFormat->GetAnchor( ).GetAnchorId( ) == RndStdIds::FLY_AT_PAGE; + } + } + else + bOverFly = false; + return bRet; +} + +void SwEditWin::SetUseInputLanguage( bool bNew ) +{ + if ( bNew || m_bUseInputLanguage ) + { + SfxBindings& rBind = GetView().GetViewFrame()->GetBindings(); + rBind.Invalidate( SID_ATTR_CHAR_FONT ); + rBind.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + } + m_bUseInputLanguage = bNew; +} + +OUString SwEditWin::GetSurroundingText() const +{ + SwWrtShell& rSh = m_rView.GetWrtShell(); + + if (rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit()) + return rSh.GetDrawView()->GetTextEditOutlinerView()->GetSurroundingText(); + + OUString sReturn; + if( rSh.HasSelection() && !rSh.IsMultiSelection() && rSh.IsSelOnePara() ) + rSh.GetSelectedText( sReturn, ParaBreakType::ToOnlyCR ); + else if( !rSh.HasSelection() ) + { + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView(true); + rSh.Push(); + + // disable accessible events for internal-only helper cursor + const bool bSendAccessibleEventOld = rSh.IsSendAccessibleCursorEvents(); + rSh.SetSendAccessibleCursorEvents(false); + + // get the sentence around the cursor + rSh.HideCursor(); + rSh.GoStartSentence(); + rSh.SetMark(); + rSh.GoEndSentence(); + rSh.GetSelectedText( sReturn, ParaBreakType::ToOnlyCR ); + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + rSh.SetSendAccessibleCursorEvents(bSendAccessibleEventOld); + rSh.HideCursor(); + + if (bUnLockView) + rSh.LockView(false); + } + + return sReturn; +} + +Selection SwEditWin::GetSurroundingTextSelection() const +{ + SwWrtShell& rSh = m_rView.GetWrtShell(); + + if (rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit()) + return rSh.GetDrawView()->GetTextEditOutlinerView()->GetSurroundingTextSelection(); + + Selection aSel(0, 0); + if( rSh.HasSelection() ) + { + OUString sReturn; + rSh.GetSelectedText( sReturn, ParaBreakType::ToOnlyCR ); + aSel = Selection( 0, sReturn.getLength() ); + } + else if (rSh.GetCursor()->GetPoint()->nNode.GetNode().GetTextNode()) + { + bool bUnLockView = !rSh.IsViewLocked(); + rSh.LockView(true); + + // Return the position of the visible cursor in the sentence + // around the visible cursor. + TextFrameIndex const nPos(rSh.GetCursorPointAsViewIndex()); + + // store shell state *before* Push + ::std::unique_ptr<SwCallLink> pLink(::std::make_unique<SwCallLink>(rSh)); + rSh.Push(); + + // disable accessible events for internal-only helper cursor + const bool bSendAccessibleEventOld = rSh.IsSendAccessibleCursorEvents(); + rSh.SetSendAccessibleCursorEvents(false); + + rSh.HideCursor(); + rSh.GoStartSentence(); + TextFrameIndex const nStartPos(rSh.GetCursorPointAsViewIndex()); + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent, ::std::move(pLink)); + rSh.SetSendAccessibleCursorEvents(bSendAccessibleEventOld); + rSh.ShowCursor(); + + if (bUnLockView) + rSh.LockView(false); + + aSel = Selection(sal_Int32(nPos - nStartPos), sal_Int32(nPos - nStartPos)); + } + + return aSel; +} + +bool SwEditWin::DeleteSurroundingText(const Selection& rSelection) +{ + SwWrtShell& rSh = m_rView.GetWrtShell(); + + if (rSh.HasDrawView() && rSh.GetDrawView()->IsTextEdit()) + return rSh.GetDrawView()->GetTextEditOutlinerView()->DeleteSurroundingText(rSelection); + + if (rSh.HasSelection()) + return false; + + // rSelection is relative to the start of the sentence, so find that and + // adjust the range by it + rSh.Push(); + + // disable accessible events for internal-only helper cursor + const bool bSendAccessibleEventOld = rSh.IsSendAccessibleCursorEvents(); + rSh.SetSendAccessibleCursorEvents(false); + + rSh.HideCursor(); + rSh.GoStartSentence(); + TextFrameIndex const nStartPos(rSh.GetCursorPointAsViewIndex()); + + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + rSh.SetSendAccessibleCursorEvents(bSendAccessibleEventOld); + rSh.ShowCursor(); + + if (rSh.SelectTextView(nStartPos + TextFrameIndex(rSelection.Min()), nStartPos + TextFrameIndex(rSelection.Max()))) + { + rSh.Delete(false); + return true; + } + + return false; +} + +void SwEditWin::LogicInvalidate(const tools::Rectangle* pRectangle) +{ + SfxLokHelper::notifyInvalidation(&m_rView, pRectangle); +} + +void SwEditWin::LogicMouseButtonDown(const MouseEvent& rMouseEvent) +{ + // When we're not doing tiled rendering, then positions must be passed as pixels. + assert(comphelper::LibreOfficeKit::isActive()); + + Point aPoint = GetPointerPosPixel(); + SetLastMousePos(rMouseEvent.GetPosPixel()); + + MouseButtonDown(rMouseEvent); + + SetPointerPosPixel(aPoint); +} + +void SwEditWin::LogicMouseButtonUp(const MouseEvent& rMouseEvent) +{ + // When we're not doing tiled rendering, then positions must be passed as pixels. + assert(comphelper::LibreOfficeKit::isActive()); + + Point aPoint = GetPointerPosPixel(); + SetLastMousePos(rMouseEvent.GetPosPixel()); + + MouseButtonUp(rMouseEvent); + + SetPointerPosPixel(aPoint); +} + +void SwEditWin::LogicMouseMove(const MouseEvent& rMouseEvent) +{ + // When we're not doing tiled rendering, then positions must be passed as pixels. + assert(comphelper::LibreOfficeKit::isActive()); + + Point aPoint = GetPointerPosPixel(); + SetLastMousePos(rMouseEvent.GetPosPixel()); + + MouseMove(rMouseEvent); + + SetPointerPosPixel(aPoint); +} + +void SwEditWin::SetCursorTwipPosition(const Point& rPosition, bool bPoint, bool bClearMark) +{ + if (SdrView* pSdrView = m_rView.GetWrtShell().GetDrawView()) + { + // Editing shape text, then route the call to editeng. + if (pSdrView->GetTextEditObject()) + { + EditView& rEditView = pSdrView->GetTextEditOutlinerView()->GetEditView(); + rEditView.SetCursorLogicPosition(rPosition, bPoint, bClearMark); + return; + } + } + + if (m_rView.GetPostItMgr()) + { + if (sw::annotation::SwAnnotationWin* pWin = m_rView.GetPostItMgr()->GetActiveSidebarWin()) + { + // Editing postit text. + pWin->SetCursorLogicPosition(rPosition, bPoint, bClearMark); + return; + } + } + + // Not an SwWrtShell, as that would make SwCursorShell::GetCursor() inaccessible. + SwEditShell& rShell = m_rView.GetWrtShell(); + + bool bCreateSelection = false; + { + SwMvContext aMvContext(&rShell); + if (bClearMark) + rShell.ClearMark(); + else + bCreateSelection = !rShell.HasMark(); + + if (bCreateSelection) + m_rView.GetWrtShell().SttSelect(); + + // If the mark is to be updated, then exchange the point and mark before + // and after, as we can't easily set the mark. + if (!bPoint) + rShell.getShellCursor(/*bBlock=*/false)->Exchange(); + rShell.SetCursor(rPosition); + if (!bPoint) + rShell.getShellCursor(/*bBlock=*/false)->Exchange(); + } + + if (bCreateSelection) + m_rView.GetWrtShell().EndSelect(); +} + +void SwEditWin::SetGraphicTwipPosition(bool bStart, const Point& rPosition) +{ + if (bStart) + { + MouseEvent aClickEvent(rPosition, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + MouseButtonDown(aClickEvent); + MouseEvent aMoveEvent(Point(rPosition.getX() + MIN_MOVE + 1, rPosition.getY()), 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT); + MouseMove(aMoveEvent); + } + else + { + MouseEvent aMoveEvent(Point(rPosition.getX() - MIN_MOVE - 1, rPosition.getY()), 0, MouseEventModifiers::SIMPLEMOVE, MOUSE_LEFT); + MouseMove(aMoveEvent); + MouseEvent aClickEvent(rPosition, 1, MouseEventModifiers::SIMPLECLICK, MOUSE_LEFT); + MouseButtonUp(aClickEvent); + } +} + +SwFrameControlsManager& SwEditWin::GetFrameControlsManager() +{ + return *m_pFrameControlsManager; +} + +void SwEditWin::ToggleOutlineContentVisibility(const size_t nOutlinePos, const bool bSubs) +{ + SwWrtShell& rSh = GetView().GetWrtShell(); + + if (GetView().GetDrawView()->IsTextEdit()) + rSh.EndTextEdit(); + if (GetView().IsDrawMode()) + GetView().LeaveDrawCreate(); + rSh.EnterStdMode(); + + if (!bSubs || rSh.GetViewOptions()->IsTreatSubOutlineLevelsAsContent()) + { + SwNode* pNode = rSh.GetNodes().GetOutLineNds()[nOutlinePos]; + bool bVisible = true; + pNode->GetTextNode()->GetAttrOutlineContentVisible(bVisible); + pNode->GetTextNode()->SetAttrOutlineContentVisible(!bVisible); + } + else if (bSubs) + { + // toggle including sub levels + SwOutlineNodes::size_type nPos = nOutlinePos; + SwOutlineNodes::size_type nOutlineNodesCount + = rSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); + int nLevel = rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(nOutlinePos); + bool bVisible = rSh.IsOutlineContentVisible(nOutlinePos); + do + { + if (rSh.IsOutlineContentVisible(nPos) == bVisible) + rSh.GetNodes().GetOutLineNds()[nPos]->GetTextNode()->SetAttrOutlineContentVisible(!bVisible); + } while (++nPos < nOutlineNodesCount + && rSh.getIDocumentOutlineNodesAccess()->getOutlineLevel(nPos) > nLevel); + } + + rSh.InvalidateOutlineContentVisibility(); + rSh.GotoOutline(nOutlinePos); + GetView().GetDocShell()->Broadcast(SfxHint(SfxHintId::DocChanged)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/edtwin2.cxx b/sw/source/uibase/docvw/edtwin2.cxx new file mode 100644 index 000000000..a7aeb028c --- /dev/null +++ b/sw/source/uibase/docvw/edtwin2.cxx @@ -0,0 +1,500 @@ +/* -*- 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 <doc.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <vcl/help.hxx> +#include <tools/urlobj.hxx> +#include <fmtrfmrk.hxx> +#include <svl/urihelper.hxx> +#include <sfx2/sfxhelp.hxx> +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <swmodule.hxx> +#include <modcfg.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <docsh.hxx> +#include <edtwin.hxx> +#include <dpage.hxx> +#include <docufld.hxx> +#include <reffld.hxx> +#include <cellatr.hxx> +#include <shdwcrsr.hxx> +#include <fmtinfmt.hxx> +#include <fmtftn.hxx> +#include <redline.hxx> +#include <tox.hxx> +#include <txatbase.hxx> +#include <uitool.hxx> +#include <viewopt.hxx> +#include <strings.hrc> + +#include <IDocumentMarkAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <comphelper/lok.hxx> +#include <authfld.hxx> + +static OUString lcl_GetRedlineHelp( const SwRangeRedline& rRedl, bool bBalloon, bool bTableChange ) +{ + TranslateId pResId; + switch( rRedl.GetType() ) + { + case RedlineType::Insert: pResId = bTableChange + ? STR_REDLINE_TABLE_ROW_INSERT + : rRedl.IsMoved() + ? STR_REDLINE_INSERT_MOVED + : STR_REDLINE_INSERT; + break; + case RedlineType::Delete: pResId = bTableChange + ? STR_REDLINE_TABLE_ROW_DELETE + : rRedl.IsMoved() + ? STR_REDLINE_DELETE_MOVED + : STR_REDLINE_DELETE; + break; + case RedlineType::Format: pResId = STR_REDLINE_FORMAT; break; + case RedlineType::Table: pResId = STR_REDLINE_TABLE; break; + case RedlineType::FmtColl: pResId = STR_REDLINE_FMTCOLL; break; + case RedlineType::ParagraphFormat: pResId = STR_REDLINE_PARAGRAPH_FORMAT; break; + case RedlineType::TableRowInsert: pResId = STR_REDLINE_TABLE_ROW_INSERT; break; + case RedlineType::TableRowDelete: pResId = STR_REDLINE_TABLE_ROW_DELETE; break; + case RedlineType::TableCellInsert: pResId = STR_REDLINE_TABLE_CELL_INSERT; break; + case RedlineType::TableCellDelete: pResId = STR_REDLINE_TABLE_CELL_DELETE; break; + default: break; + } + + OUStringBuffer sBuf; + if (pResId) + { + sBuf.append(SwResId(pResId)); + sBuf.append(": "); + sBuf.append(rRedl.GetAuthorString()); + sBuf.append(" - "); + sBuf.append(GetAppLangDateTimeString(rRedl.GetTimeStamp())); + if( bBalloon && !rRedl.GetComment().isEmpty() ) + sBuf.append('\n').append(rRedl.GetComment()); + } + return sBuf.makeStringAndClear(); +} + +OUString SwEditWin::ClipLongToolTip(const OUString& rText) +{ + OUString sDisplayText(rText); + tools::Long nTextWidth = GetTextWidth(sDisplayText); + tools::Long nMaxWidth = GetDesktopRectPixel().GetWidth() * 2 / 3; + nMaxWidth = PixelToLogic(Size(nMaxWidth, 0)).Width(); + if (nTextWidth > nMaxWidth) + sDisplayText = GetOutDev()->GetEllipsisString(sDisplayText, nMaxWidth, DrawTextFlags::CenterEllipsis); + return sDisplayText; +} + +void SwEditWin::RequestHelp(const HelpEvent &rEvt) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + bool bQuickBalloon = bool(rEvt.GetMode() & ( HelpEventMode::QUICK | HelpEventMode::BALLOON )); + if(bQuickBalloon && !rSh.GetViewOptions()->IsShowContentTips()) + return; + bool bContinue = true; + CurrShell aCurr(&rSh); + OUString sText; + Point aPos( PixelToLogic( ScreenToOutputPixel( rEvt.GetMousePosPixel() ) )); + bool bBalloon = bool(rEvt.GetMode() & HelpEventMode::BALLOON); + + SdrView *pSdrView = rSh.GetDrawView(); + + if( bQuickBalloon && pSdrView ) + { + SdrPageView* pPV = pSdrView->GetSdrPageView(); + SwDPage* pPage = pPV ? static_cast<SwDPage*>(pPV->GetPage()) : nullptr; + bContinue = pPage && pPage->RequestHelp(this, pSdrView, rEvt); + } + + if( bContinue && bQuickBalloon) + { + SwRect aFieldRect; + SwContentAtPos aContentAtPos( IsAttrAtPos::Field | + IsAttrAtPos::InetAttr | + IsAttrAtPos::Ftn | + IsAttrAtPos::Redline | + IsAttrAtPos::ToxMark | + IsAttrAtPos::RefMark | + IsAttrAtPos::SmartTag | +#ifdef DBG_UTIL + IsAttrAtPos::TableBoxValue | + ( bBalloon ? IsAttrAtPos::CurrAttrs : IsAttrAtPos::NONE) | +#endif + IsAttrAtPos::TableBoxFml | + IsAttrAtPos::TableRedline ); + + if( rSh.GetContentAtPos( aPos, aContentAtPos, false, &aFieldRect ) ) + { + QuickHelpFlags nStyle = QuickHelpFlags::NONE; // style of quick help + switch( aContentAtPos.eContentAtPos ) + { + case IsAttrAtPos::TableBoxFml: + sText = "= " + static_cast<const SwTableBoxFormula*>(aContentAtPos.aFnd.pAttr)->GetFormula(); + break; +#ifdef DBG_UTIL + case IsAttrAtPos::TableBoxValue: + { + if(aContentAtPos.aFnd.pAttr) + { + sText = OStringToOUString(OString::number( + static_cast<const SwTableBoxValue*>(aContentAtPos.aFnd.pAttr)->GetValue()), + osl_getThreadTextEncoding()); + } + break; + } + case IsAttrAtPos::CurrAttrs: + sText = aContentAtPos.sStr; + break; +#endif + + case IsAttrAtPos::InetAttr: + { + sText = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr)->GetValue(); + sText = URIHelper::removePassword( sText, + INetURLObject::EncodeMechanism::WasEncoded, + INetURLObject::DecodeMechanism::Unambiguous); + //#i63832# remove the link target type + sal_Int32 nFound = sText.indexOf(cMarkSeparator); + if( nFound != -1 && (++nFound) < sText.getLength() ) + { + std::u16string_view sSuffix( sText.subView(nFound) ); + if( sSuffix == u"table" || + sSuffix == u"frame" || + sSuffix == u"region" || + sSuffix == u"outline" || + sSuffix == u"text" || + sSuffix == u"graphic" || + sSuffix == u"ole" || + sSuffix == u"drawingobject" ) + sText = sText.copy( 0, nFound - 1); + } + // #i104300# + // special handling if target is a cross-reference bookmark + { + OUString sTmpSearchStr = sText.copy( 1 ); + IDocumentMarkAccess* pMarkAccess = rSh.getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t ppBkmk = + pMarkAccess->findBookmark( sTmpSearchStr ); + if ( ppBkmk != pMarkAccess->getBookmarksEnd() && + IDocumentMarkAccess::GetType(**ppBkmk) + == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK ) + { + SwTextNode* pTextNode = (*ppBkmk)->GetMarkStart().nNode.GetNode().GetTextNode(); + if ( pTextNode ) + { + sText = sw::GetExpandTextMerged(rSh.GetLayout(), *pTextNode, true, false, ExpandMode(0)); + + if( !sText.isEmpty() ) + { + OUStringBuffer sTmp(sText.replaceAll(u"\u00ad", "")); + for (sal_Int32 i = 0; i < sTmp.getLength(); ++i) + { + if (sTmp[i] < 0x20) + sTmp[i] = 0x20; + else if (sTmp[i] == 0x2011) + sTmp[i] = '-'; + } + sText = sTmp.makeStringAndClear(); + } + } + } + } + // #i80029# + bool bExecHyperlinks = m_rView.GetDocShell()->IsReadOnly(); + if ( !bExecHyperlinks ) + { + sText = SfxHelp::GetURLHelpText(sText); + } + break; + } + case IsAttrAtPos::SmartTag: + { + vcl::KeyCode aCode( KEY_SPACE ); + vcl::KeyCode aModifiedCode( KEY_SPACE, KEY_MOD1 ); + OUString aModStr( aModifiedCode.GetName() ); + aModStr = aModStr.replaceFirst(aCode.GetName(), ""); + aModStr = aModStr.replaceAll("+", ""); + sText = SwResId(STR_SMARTTAG_CLICK).replaceAll("%s", aModStr); + break; + } + + case IsAttrAtPos::Ftn: + if( aContentAtPos.pFndTextAttr && aContentAtPos.aFnd.pAttr ) + { + const SwFormatFootnote* pFootnote = static_cast<const SwFormatFootnote*>(aContentAtPos.aFnd.pAttr); + OUString sTmp(pFootnote->GetFootnoteText(*rSh.GetLayout())); + sText = SwResId( pFootnote->IsEndNote() + ? STR_ENDNOTE : STR_FTNNOTE ) + sTmp; + bBalloon = true; + if( aContentAtPos.IsInRTLText() ) + nStyle |= QuickHelpFlags::BiDiRtl; + } + break; + + case IsAttrAtPos::TableRedline: + case IsAttrAtPos::Redline: + { + const bool bShowTrackChanges = IDocumentRedlineAccess::IsShowChanges( m_rView.GetDocShell()->GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() ); + const bool bShowInlineTooltips = rSh.GetViewOptions()->IsShowInlineTooltips(); + if ( bShowTrackChanges && bShowInlineTooltips ) + { + sText = lcl_GetRedlineHelp(*aContentAtPos.aFnd.pRedl, bBalloon, IsAttrAtPos::TableRedline == aContentAtPos.eContentAtPos ); + } + break; + } + + case IsAttrAtPos::ToxMark: + sText = aContentAtPos.sStr; + if( !sText.isEmpty() && aContentAtPos.pFndTextAttr ) + { + const SwTOXType* pTType = aContentAtPos.pFndTextAttr-> + GetTOXMark().GetTOXType(); + if( pTType && !pTType->GetTypeName().isEmpty() ) + { + sText = ": " + sText; + sText = pTType->GetTypeName() + sText; + } + } + break; + + case IsAttrAtPos::RefMark: + if(aContentAtPos.aFnd.pAttr) + { + sText = SwResId(STR_CONTENT_TYPE_SINGLE_REFERENCE) + ": "; + sText += static_cast<const SwFormatRefMark*>(aContentAtPos.aFnd.pAttr)->GetRefName(); + } + break; + + default: + { + SwModuleOptions* pModOpt = SW_MOD()->GetModuleConfig(); + if(!pModOpt->IsHideFieldTips()) + { + const SwField* pField = aContentAtPos.aFnd.pField; + switch( pField->Which() ) + { + case SwFieldIds::SetExp: + case SwFieldIds::Table: + case SwFieldIds::GetExp: + { + sal_uInt16 nOldSubType = pField->GetSubType(); + const_cast<SwField*>(pField)->SetSubType(nsSwExtendedSubType::SUB_CMD); + sText = pField->ExpandField(true, rSh.GetLayout()); + const_cast<SwField*>(pField)->SetSubType(nOldSubType); + } + break; + + case SwFieldIds::Postit: + { + break; + } + case SwFieldIds::Input: // BubbleHelp, because the suggestion could be quite long + bBalloon = true; + [[fallthrough]]; + case SwFieldIds::Dropdown: + case SwFieldIds::JumpEdit: + sText = pField->GetPar2(); + break; + + case SwFieldIds::Database: + sText = pField->GetFieldName(); + break; + + case SwFieldIds::User: + { + OUString aTitle = pField->GetTitle(); + if (!aTitle.isEmpty()) + { + sText = aTitle; + } + else + { + sText = pField->GetPar1(); + } + break; + } + case SwFieldIds::HiddenText: + sText = pField->GetPar1(); + break; + + case SwFieldIds::DocStat: + break; + + case SwFieldIds::Macro: + sText = static_cast<const SwMacroField*>(pField)->GetMacro(); + break; + + case SwFieldIds::GetRef: + { + // #i85090# + const SwGetRefField* pRefField( dynamic_cast<const SwGetRefField*>(pField) ); + OSL_ENSURE( pRefField, + "<SwEditWin::RequestHelp(..)> - unexpected type of <pField>" ); + if ( pRefField ) + { + if ( pRefField->IsRefToHeadingCrossRefBookmark() || + pRefField->IsRefToNumItemCrossRefBookmark() ) + { + sText = pRefField->GetExpandedTextOfReferencedTextNode(*rSh.GetLayout()); + if ( sText.getLength() > 80 ) + { + sText = OUString::Concat(sText.subView(0, 80)) + "..."; + } + } + else + { + sText = pRefField->GetSetRefName(); + } + } + break; + } + case SwFieldIds::TableOfAuthorities: + { + const auto pAuthorityField + = static_cast<const SwAuthorityField*>(pField); + sText = pAuthorityField->GetAuthority(aContentAtPos.pFndTextAttr, + rSh.GetLayout()); + if (pAuthorityField->HasURL()) + { + const OUString& rURL + = pAuthorityField->GetAuthEntry()->GetAuthorField( + AUTH_FIELD_URL); + sText += "\n" + SfxHelp::GetURLHelpText(rURL); + } + break; + } + + default: break; + } + } + + if( sText.isEmpty() ) + { + const bool bShowTrackChanges = IDocumentRedlineAccess::IsShowChanges( m_rView.GetDocShell()->GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() ); + const bool bShowInlineTooltips = rSh.GetViewOptions()->IsShowInlineTooltips(); + if ( bShowTrackChanges && bShowInlineTooltips ) + { + aContentAtPos.eContentAtPos = IsAttrAtPos::Redline; + if( rSh.GetContentAtPos( aPos, aContentAtPos, false, &aFieldRect ) ) + sText = lcl_GetRedlineHelp(*aContentAtPos.aFnd.pRedl, bBalloon, /*bTableChange=*/false); + } + } + } + } + if (!sText.isEmpty()) + { + tools::Rectangle aRect( aFieldRect.SVRect() ); + Point aPt( OutputToScreenPixel( LogicToPixel( aRect.TopLeft() ))); + aRect.SetLeft( aPt.X() ); + aRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel( LogicToPixel( aRect.BottomRight() )); + aRect.SetRight( aPt.X() ); + aRect.SetBottom( aPt.Y() ); + + // tdf#136336 ensure tooltip area surrounds the current mouse position with at least a pixel margin + aRect.Union(tools::Rectangle(rEvt.GetMousePosPixel(), Size(1, 1))); + aRect.AdjustLeft(-1); + aRect.AdjustRight(1); + aRect.AdjustTop(-1); + aRect.AdjustBottom(1); + + if( bBalloon ) + Help::ShowBalloon( this, rEvt.GetMousePosPixel(), aRect, sText ); + else + { + // the show the help + OUString sDisplayText(ClipLongToolTip(sText)); + Help::ShowQuickHelp(this, aRect, sDisplayText, nStyle); + } + } + + bContinue = false; + } + + } + + if( bContinue ) + Window::RequestHelp( rEvt ); +} + +void SwEditWin::PrePaint(vcl::RenderContext& /*rRenderContext*/) +{ + SwWrtShell* pWrtShell = GetView().GetWrtShellPtr(); + + if(pWrtShell) + { + pWrtShell->PrePaint(); + } +} + +void SwEditWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + SwWrtShell* pWrtShell = GetView().GetWrtShellPtr(); + if(!pWrtShell) + return; + bool bPaintShadowCursor = false; + if( m_pShadCursor ) + { + tools::Rectangle aRect( m_pShadCursor->GetRect()); + // fully resides inside? + if( rRect.Contains( aRect ) ) + { + // then cancel + m_pShadCursor.reset(); + } + else if( rRect.Overlaps( aRect )) + { + // resides somewhat above, then everything is clipped outside + // and we have to make the "inner part" at the end of the + // Paint visible again. Otherwise Paint errors occur! + bPaintShadowCursor = true; + } + } + + if ( GetView().GetVisArea().GetWidth() <= 0 || + GetView().GetVisArea().GetHeight() <= 0 ) + Invalidate( rRect ); + else + { + pWrtShell->setOutputToWindow(true); + bool bTiledPainting = false; + if (comphelper::LibreOfficeKit::isActive()) + { + bTiledPainting = comphelper::LibreOfficeKit::isTiledPainting(); + comphelper::LibreOfficeKit::setTiledPainting(true); + } + pWrtShell->Paint(rRenderContext, rRect); + if (comphelper::LibreOfficeKit::isActive()) + { + comphelper::LibreOfficeKit::setTiledPainting(bTiledPainting); + } + pWrtShell->setOutputToWindow(false); + } + + if( bPaintShadowCursor ) + m_pShadCursor->Paint(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/edtwin3.cxx b/sw/source/uibase/docvw/edtwin3.cxx new file mode 100644 index 000000000..d417b08b0 --- /dev/null +++ b/sw/source/uibase/docvw/edtwin3.cxx @@ -0,0 +1,175 @@ +/* -*- 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 <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <svx/ruler.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <basesh.hxx> +#include <pview.hxx> +#include <mdiexp.hxx> +#include <edtwin.hxx> +#include <swmodule.hxx> +#include <modcfg.hxx> +#include <docsh.hxx> +#include <uiobject.hxx> + +// Core-Notify +void ScrollMDI( SwViewShell const * pVwSh, const SwRect &rRect, + sal_uInt16 nRangeX, sal_uInt16 nRangeY) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwView* pSwView = dynamic_cast<SwView *>(pSfxViewShell)) + pSwView->Scroll(rRect.SVRect(), nRangeX, nRangeY); +} + +// Docmdi - movable +bool IsScrollMDI( SwViewShell const * pVwSh, const SwRect &rRect ) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwView* pSwView = dynamic_cast<SwView *>(pSfxViewShell)) + return pSwView->IsScroll(rRect.SVRect()); + + return false; +} + +// Notify for size change +void SizeNotify(SwViewShell const * pVwSh, const Size &rSize) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwView* pSwView = dynamic_cast<SwView *>(pSfxViewShell)) + pSwView->DocSzChgd(rSize); + else if (SwPagePreview* pSwPageView = dynamic_cast<SwPagePreview *>(pSfxViewShell)) + pSwPageView->DocSzChgd(rSize); +} + +// Notify for page number update +void PageNumNotify(SwViewShell const * pVwSh) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwView* pSwView = dynamic_cast<SwView *>(pSfxViewShell)) + { + if (pSwView->GetCurShell()) + pSwView->UpdatePageNums(); + } +} + +void FrameNotify( SwViewShell* pVwSh, FlyMode eMode ) +{ + if (SwWrtShell* pWrtShell = dynamic_cast<SwWrtShell *>(pVwSh)) + SwBaseShell::SetFrameMode(eMode, pWrtShell); +} + +// Notify for page number update +bool SwEditWin::RulerColumnDrag( const MouseEvent& rMEvt, bool bVerticalMode) +{ + SvxRuler& rRuler = bVerticalMode ? m_rView.GetVRuler() : m_rView.GetHRuler(); + return (!rRuler.StartDocDrag( rMEvt, RulerType::Border ) && + !rRuler.StartDocDrag( rMEvt, RulerType::Margin1) && + !rRuler.StartDocDrag( rMEvt, RulerType::Margin2)); +} + +// #i23726# +// #i42921# - add 3rd parameter <bVerticalMode> in order +// to consider vertical layout +bool SwEditWin::RulerMarginDrag( const MouseEvent& rMEvt, + const bool bVerticalMode ) +{ + SvxRuler& rRuler = bVerticalMode ? m_rView.GetVRuler() : m_rView.GetHRuler(); + return !rRuler.StartDocDrag( rMEvt, RulerType::Indent); +} + +TableChgMode GetTableChgDefaultMode() +{ + SwModuleOptions* pOpt = SW_MOD()->GetModuleConfig(); + return pOpt ? pOpt->GetTableMode() : TableChgMode::VarWidthChangeAbs; +} + +void RepaintPagePreview( SwViewShell const * pVwSh, const SwRect& rRect ) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwPagePreview* pSwPagePreview = dynamic_cast<SwPagePreview *>(pSfxViewShell)) + pSwPagePreview->RepaintCoreRect(rRect); +} + +bool JumpToSwMark( SwViewShell const * pVwSh, std::u16string_view rMark ) +{ + SfxViewShell *pSfxViewShell = pVwSh->GetSfxViewShell(); + + if (SwView* pSwView = dynamic_cast<SwView *>(pSfxViewShell)) + return pSwView->JumpToSwMark(rMark); + + return false; +} + +void SwEditWin::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + SwWrtShell* pSh = GetView().GetWrtShellPtr(); + // DataChanged() is sometimes called prior to creating + // the SwWrtShell + if(!pSh) + return; + bool bViewWasLocked = pSh->IsViewLocked(), bUnlockPaint = false; + pSh->LockView( true ); + switch( rDCEvt.GetType() ) + { + case DataChangedEventType::SETTINGS: + // rearrange ScrollBars, respectively trigger resize, because + // the ScrollBar size can have change. For that, in the reset + // handler, the size of the ScrollBars also has to be queried + // from the settings. + if( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) + { + pSh->LockPaint(); + bUnlockPaint = true; + pSh->DeleteReplacementBitmaps(); + GetView().InvalidateBorder(); //Scrollbar work + } + break; + + case DataChangedEventType::PRINTER: + case DataChangedEventType::DISPLAY: + case DataChangedEventType::FONTS: + case DataChangedEventType::FONTSUBSTITUTION: + pSh->LockPaint(); + bUnlockPaint = true; + GetView().GetDocShell()->UpdateFontList(); //e.g. printer change + pSh->InvalidateLayout(true); + break; + default: break; + } + pSh->LockView( bViewWasLocked ); + if( bUnlockPaint ) + pSh->UnlockPaint(); +} + +FactoryFunction SwEditWin::GetUITestFactory() const +{ + return SwEditWinUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/frmsidebarwincontainer.cxx b/sw/source/uibase/docvw/frmsidebarwincontainer.cxx new file mode 100644 index 000000000..17da0beee --- /dev/null +++ b/sw/source/uibase/docvw/frmsidebarwincontainer.cxx @@ -0,0 +1,172 @@ +/* -*- 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 "frmsidebarwincontainer.hxx" + +#include <map> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <AnnotationWin.hxx> + +namespace { + struct SidebarWinKey + { + const sal_Int32 mnIndex; + + explicit SidebarWinKey( const sal_Int32 nIndex ) + : mnIndex( nIndex ) + {} + + bool operator < ( const SidebarWinKey& rSidebarWinKey ) const + { + return mnIndex < rSidebarWinKey.mnIndex; + } + }; + + typedef std::map < SidebarWinKey, VclPtr<sw::annotation::SwAnnotationWin> > SidebarWinContainer; + + struct FrameKey + { + const SwFrame* mpFrame; + + explicit FrameKey( const SwFrame* pFrame ) + : mpFrame( pFrame ) + {} + + bool operator < ( const FrameKey& rFrameKey ) const + { + return mpFrame < rFrameKey.mpFrame; + } + }; + + typedef std::map < FrameKey, SidebarWinContainer > FrameSidebarWinContainer_; +} + +namespace sw::sidebarwindows { + +class FrameSidebarWinContainer : public FrameSidebarWinContainer_ +{ +}; + +SwFrameSidebarWinContainer::SwFrameSidebarWinContainer() + : mpFrameSidebarWinContainer( new FrameSidebarWinContainer ) +{} + +SwFrameSidebarWinContainer::~SwFrameSidebarWinContainer() +{ + mpFrameSidebarWinContainer->clear(); + mpFrameSidebarWinContainer.reset(); +} + +bool SwFrameSidebarWinContainer::insert( const SwFrame& rFrame, + const SwFormatField& rFormatField, + sw::annotation::SwAnnotationWin& rSidebarWin ) +{ + bool bInserted( false ); + + FrameKey aFrameKey( &rFrame ); + SidebarWinContainer& rSidebarWinContainer = (*mpFrameSidebarWinContainer)[ aFrameKey ]; + + SidebarWinKey aSidebarWinKey( rFormatField.GetTextField()->GetStart() ); + if ( rSidebarWinContainer.empty() || + rSidebarWinContainer.find( aSidebarWinKey) == rSidebarWinContainer.end() ) + { + rSidebarWinContainer[ aSidebarWinKey ] = &rSidebarWin; + bInserted = true; + } + + return bInserted; +} + +bool SwFrameSidebarWinContainer::remove( const SwFrame& rFrame, + const sw::annotation::SwAnnotationWin & rSidebarWin ) +{ + bool bRemoved( false ); + + FrameKey aFrameKey( &rFrame ); + FrameSidebarWinContainer::iterator aFrameIter = mpFrameSidebarWinContainer->find( aFrameKey ); + if ( aFrameIter != mpFrameSidebarWinContainer->end() ) + { + SidebarWinContainer& rSidebarWinContainer = (*aFrameIter).second; + auto aIter = std::find_if(rSidebarWinContainer.begin(), rSidebarWinContainer.end(), + [&rSidebarWin](const SidebarWinContainer::value_type& rEntry) { return rEntry.second == &rSidebarWin; }); + if ( aIter != rSidebarWinContainer.end() ) + { + rSidebarWinContainer.erase( aIter ); + bRemoved = true; + } + } + + return bRemoved; +} + +bool SwFrameSidebarWinContainer::empty( const SwFrame& rFrame ) +{ + bool bEmpty( true ); + + FrameKey aFrameKey( &rFrame ); + FrameSidebarWinContainer::iterator aFrameIter = mpFrameSidebarWinContainer->find( aFrameKey ); + if ( aFrameIter != mpFrameSidebarWinContainer->end() ) + { + bEmpty = (*aFrameIter).second.empty(); + } + + return bEmpty; +} + +sw::annotation::SwAnnotationWin* SwFrameSidebarWinContainer::get( const SwFrame& rFrame, + const sal_Int32 nIndex ) +{ + sw::annotation::SwAnnotationWin* pRet( nullptr ); + + FrameKey aFrameKey( &rFrame ); + FrameSidebarWinContainer::iterator aFrameIter = mpFrameSidebarWinContainer->find( aFrameKey ); + if ( aFrameIter != mpFrameSidebarWinContainer->end() && nIndex >= 0 ) + { + SidebarWinContainer& rSidebarWinContainer = (*aFrameIter).second; + if (nIndex < sal_Int32(rSidebarWinContainer.size())) + { + auto aIter = rSidebarWinContainer.begin(); + std::advance(aIter, nIndex); + pRet = (*aIter).second; + } + } + return pRet; +} + +void SwFrameSidebarWinContainer::getAll( const SwFrame& rFrame, + std::vector< vcl::Window* >* pSidebarWins ) +{ + pSidebarWins->clear(); + + FrameKey aFrameKey( &rFrame ); + FrameSidebarWinContainer::iterator aFrameIter = mpFrameSidebarWinContainer->find( aFrameKey ); + if ( aFrameIter != mpFrameSidebarWinContainer->end() ) + { + SidebarWinContainer& rSidebarWinContainer = (*aFrameIter).second; + for ( const auto& rEntry : rSidebarWinContainer ) + { + pSidebarWins->push_back( rEntry.second ); + } + } +} + +} // eof of namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/frmsidebarwincontainer.hxx b/sw/source/uibase/docvw/frmsidebarwincontainer.hxx new file mode 100644 index 000000000..33ae9a9d4 --- /dev/null +++ b/sw/source/uibase/docvw/frmsidebarwincontainer.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include <memory> +#include <vector> + +class SwFrame; +class SwFormatField; +namespace vcl { class Window; } +namespace sw::annotation { class SwAnnotationWin; } + +namespace sw::sidebarwindows { + +class FrameSidebarWinContainer; + +class SwFrameSidebarWinContainer +{ + public: + SwFrameSidebarWinContainer(); + ~SwFrameSidebarWinContainer(); + + bool insert( const SwFrame& rFrame, + const SwFormatField& rFormatField, + sw::annotation::SwAnnotationWin& rSidebarWin ); + + bool remove( const SwFrame& rFrame, + const sw::annotation::SwAnnotationWin& rSidebarWin ); + + bool empty( const SwFrame& rFrame ); + + sw::annotation::SwAnnotationWin* get( const SwFrame& rFrame, + const sal_Int32 nIndex ); + + void getAll( const SwFrame& rFrame, + std::vector< vcl::Window* >* pSidebarWins ); + + private: + std::unique_ptr<FrameSidebarWinContainer> mpFrameSidebarWinContainer; +}; + +} // namespace sw::sidebarwindows + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/romenu.cxx b/sw/source/uibase/docvw/romenu.cxx new file mode 100644 index 000000000..a9319e70d --- /dev/null +++ b/sw/source/uibase/docvw/romenu.cxx @@ -0,0 +1,344 @@ +/* -*- 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 <memory> +#include <hintids.hxx> + +#include <svl/eitem.hxx> +#include <vcl/settings.hxx> +#include <vcl/transfer.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svx/gallery.hxx> +#include <svx/graphichelper.hxx> +#include <editeng/brushitem.hxx> + +#include <fmtinfmt.hxx> +#include <docsh.hxx> +#include <view.hxx> +#include <wrtsh.hxx> +#include <viewopt.hxx> +#include <swmodule.hxx> +#include "romenu.hxx" +#include <pagedesc.hxx> +#include <modcfg.hxx> + +#include <cmdid.h> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; +using namespace ::sfx2; + +SwReadOnlyPopup::~SwReadOnlyPopup() +{ + m_xMenu.disposeAndClear(); +} + +void SwReadOnlyPopup::Check( sal_uInt16 nMID, sal_uInt16 nSID, SfxDispatcher const &rDis ) +{ + std::unique_ptr<SfxPoolItem> _pItem; + SfxItemState eState = rDis.GetBindings()->QueryState( nSID, _pItem ); + if (eState >= SfxItemState::DEFAULT) + { + m_xMenu->EnableItem(nMID); + if (_pItem) + { + m_xMenu->CheckItem(nMID, !_pItem->IsVoidItem() && + dynamic_cast< const SfxBoolItem *>( _pItem.get() ) != nullptr && + static_cast<SfxBoolItem*>(_pItem.get())->GetValue()); + //remove full screen entry when not in full screen mode + if (SID_WIN_FULLSCREEN == nSID && !m_xMenu->IsItemChecked(m_nReadonlyFullscreen)) + m_xMenu->EnableItem(nMID, false); + } + } + else + m_xMenu->EnableItem(nMID, false); +} + +#define MN_READONLY_GRAPHICTOGALLERY 1000 +#define MN_READONLY_BACKGROUNDTOGALLERY 2000 + +SwReadOnlyPopup::SwReadOnlyPopup(const Point &rDPos, SwView &rV) + : m_aBuilder(nullptr, AllSettings::GetUIRootDir(), "modules/swriter/ui/readonlymenu.ui", "") + , m_xMenu(m_aBuilder.get_menu("menu")) + , m_nReadonlyOpenurl(m_xMenu->GetItemId("openurl")) + , m_nReadonlyOpendoc(m_xMenu->GetItemId("opendoc")) + , m_nReadonlyEditdoc(m_xMenu->GetItemId("edit")) + , m_nReadonlySelectionMode(m_xMenu->GetItemId("selection")) + , m_nReadonlyReload(m_xMenu->GetItemId("reload")) + , m_nReadonlyReloadFrame(m_xMenu->GetItemId("reloadframe")) + , m_nReadonlySourceview(m_xMenu->GetItemId("html")) + , m_nReadonlyBrowseBackward(m_xMenu->GetItemId("backward")) + , m_nReadonlyBrowseForward(m_xMenu->GetItemId("forward")) + , m_nReadonlySaveGraphic(m_xMenu->GetItemId("savegraphic")) + , m_nReadonlyGraphictogallery(m_xMenu->GetItemId("graphictogallery")) + , m_nReadonlyTogallerylink(m_xMenu->GetItemId("graphicaslink")) + , m_nReadonlyTogallerycopy(m_xMenu->GetItemId("graphicascopy")) + , m_nReadonlySaveBackground(m_xMenu->GetItemId("savebackground")) + , m_nReadonlyBackgroundtogallery(m_xMenu->GetItemId("backgroundtogallery")) + , m_nReadonlyBackgroundTogallerylink(m_xMenu->GetItemId("backaslink")) + , m_nReadonlyBackgroundTogallerycopy(m_xMenu->GetItemId("backascopy")) + , m_nReadonlyCopylink(m_xMenu->GetItemId("copylink")) + , m_nReadonlyLoadGraphic(m_xMenu->GetItemId("loadgraphic")) + , m_nReadonlyGraphicoff(m_xMenu->GetItemId("imagesoff")) + , m_nReadonlyFullscreen(m_xMenu->GetItemId("fullscreen")) + , m_nReadonlyCopy(m_xMenu->GetItemId("copy")) + , m_rView(rV) + , m_xBrushItem(std::make_unique<SvxBrushItem>(RES_BACKGROUND)) +{ + m_bGrfToGalleryAsLnk = SW_MOD()->GetModuleConfig()->IsGrfToGalleryAsLnk(); + SwWrtShell &rSh = m_rView.GetWrtShell(); + OUString sDescription; + rSh.IsURLGrfAtPos( rDPos, &m_sURL, &m_sTargetFrameName, &sDescription ); + if ( m_sURL.isEmpty() ) + { + SwContentAtPos aContentAtPos( IsAttrAtPos::InetAttr ); + if( rSh.GetContentAtPos( rDPos, aContentAtPos)) + { + const SwFormatINetFormat &rIItem = *static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr); + m_sURL = rIItem.GetValue(); + m_sTargetFrameName = rIItem.GetTargetFrame(); + } + } + + bool bLink = false; + const Graphic *pGrf = rSh.GetGrfAtPos( rDPos, m_sGrfName, bLink ); + if ( nullptr == pGrf ) + { + m_xMenu->EnableItem(m_nReadonlySaveGraphic, false); + } + else + { + m_aGraphic = *pGrf; + } + + bool bEnableGraphicToGallery = bLink; + if ( bEnableGraphicToGallery ) + { + if (GalleryExplorer::FillThemeList( m_aThemeList )) + { + PopupMenu *pMenu = m_xMenu->GetPopupMenu(m_nReadonlyGraphictogallery); + pMenu->CheckItem(m_nReadonlyTogallerylink, m_bGrfToGalleryAsLnk); + pMenu->CheckItem(m_nReadonlyTogallerycopy, !m_bGrfToGalleryAsLnk); + + for ( size_t i=0; i < m_aThemeList.size(); ++i ) + pMenu->InsertItem(MN_READONLY_GRAPHICTOGALLERY + i, m_aThemeList[i]); + } + else + bEnableGraphicToGallery = false; + } + + m_xMenu->EnableItem(m_nReadonlyGraphictogallery, bEnableGraphicToGallery); + + SfxViewFrame * pVFrame = rV.GetViewFrame(); + SfxDispatcher &rDis = *pVFrame->GetDispatcher(); + const SwPageDesc &rDesc = rSh.GetPageDesc( rSh.GetCurPageDesc() ); + m_xBrushItem = rDesc.GetMaster().makeBackgroundBrushItem(); + bool bEnableBackGallery = false, + bEnableBack = false; + + if ( m_xBrushItem && GPOS_NONE != m_xBrushItem->GetGraphicPos() ) + { + bEnableBack = true; + if ( !m_xBrushItem->GetGraphicLink().isEmpty() ) + { + if ( m_aThemeList.empty() ) + GalleryExplorer::FillThemeList( m_aThemeList ); + + if ( !m_aThemeList.empty() ) + { + PopupMenu *pMenu = m_xMenu->GetPopupMenu(m_nReadonlyBackgroundtogallery); + pMenu->CheckItem(m_nReadonlyBackgroundTogallerylink, m_bGrfToGalleryAsLnk); + pMenu->CheckItem(m_nReadonlyBackgroundTogallerycopy, !m_bGrfToGalleryAsLnk); + bEnableBackGallery = true; + + for ( size_t i=0; i < m_aThemeList.size(); ++i ) + pMenu->InsertItem(MN_READONLY_BACKGROUNDTOGALLERY + i, m_aThemeList[i]); + } + } + } + m_xMenu->EnableItem(m_nReadonlySaveBackground, bEnableBack); + m_xMenu->EnableItem(m_nReadonlyBackgroundtogallery, bEnableBackGallery); + + if ( !rSh.GetViewOptions()->IsGraphic() ) + m_xMenu->CheckItem(m_nReadonlyGraphicoff); + else + m_xMenu->EnableItem(m_nReadonlyLoadGraphic, false); + + m_xMenu->EnableItem(m_nReadonlyReloadFrame, false); + m_xMenu->EnableItem(m_nReadonlyReload); + + Check(m_nReadonlyEditdoc, SID_EDITDOC, rDis); + Check(m_nReadonlySelectionMode, FN_READONLY_SELECTION_MODE, rDis); + Check(m_nReadonlySourceview, SID_SOURCEVIEW, rDis); + Check(m_nReadonlyBrowseBackward, SID_BROWSE_BACKWARD, rDis); + Check(m_nReadonlyBrowseForward,SID_BROWSE_FORWARD, rDis); + Check(m_nReadonlyOpenurl, SID_OPENDOC, rDis); + Check(m_nReadonlyOpendoc, SID_OPENDOC, rDis); + + std::unique_ptr<SfxPoolItem> pState; + + SfxItemState eState = pVFrame->GetBindings().QueryState( SID_COPY, pState ); + Check(m_nReadonlyCopy, SID_COPY, rDis); + if (eState < SfxItemState::DEFAULT) + m_xMenu->EnableItem(m_nReadonlyCopy, false); + + eState = pVFrame->GetBindings().QueryState( SID_EDITDOC, pState ); + if ( + eState < SfxItemState::DEFAULT || + (rSh.IsGlobalDoc() && m_rView.GetDocShell()->IsReadOnlyUI()) + ) + { + m_xMenu->EnableItem(m_nReadonlyEditdoc, false); + } + + if ( m_sURL.isEmpty() ) + { + m_xMenu->EnableItem(m_nReadonlyOpenurl, false); + m_xMenu->EnableItem(m_nReadonlyOpendoc, false); + m_xMenu->EnableItem(m_nReadonlyCopylink, false); + } + Check(m_nReadonlyFullscreen, SID_WIN_FULLSCREEN, rDis); + + m_xMenu->RemoveDisabledEntries( true ); +} + +void SwReadOnlyPopup::Execute( vcl::Window* pWin, const Point &rPixPos ) +{ + sal_uInt16 nId = m_xMenu->Execute(pWin, rPixPos); + Execute(pWin, nId); +} + +// execute the resulting ID only - necessary to support XContextMenuInterception +void SwReadOnlyPopup::Execute( vcl::Window* pWin, sal_uInt16 nId ) +{ + SwWrtShell &rSh = m_rView.GetWrtShell(); + SfxDispatcher &rDis = *m_rView.GetViewFrame()->GetDispatcher(); + if (nId >= MN_READONLY_GRAPHICTOGALLERY) + { + OUString sTmp; + sal_uInt16 nSaveId; + if (m_xBrushItem && nId >= MN_READONLY_BACKGROUNDTOGALLERY) + { + nId -= MN_READONLY_BACKGROUNDTOGALLERY; + nSaveId = m_nReadonlySaveBackground; + sTmp = m_xBrushItem->GetGraphicLink(); + } + else + { + nId -= MN_READONLY_GRAPHICTOGALLERY; + nSaveId = m_nReadonlySaveGraphic; + sTmp = m_sGrfName; + } + if ( !m_bGrfToGalleryAsLnk ) + sTmp = SaveGraphic(nSaveId); + + if ( !sTmp.isEmpty() ) + GalleryExplorer::InsertURL( m_aThemeList[nId], sTmp ); + + return; + } + + rtl::Reference<TransferDataContainer> pClipCntnr; + + sal_uInt16 nExecId = USHRT_MAX; + bool bFilterSet = false; + LoadUrlFlags nFilter = LoadUrlFlags::NONE; + if (nId == m_nReadonlyFullscreen) + nExecId = SID_WIN_FULLSCREEN; + else if (nId == m_nReadonlyOpenurl) + { + nFilter = LoadUrlFlags::NONE; + bFilterSet = true; + } + else if (nId == m_nReadonlyOpendoc) + { + nFilter = LoadUrlFlags::NewView; + bFilterSet = true; + } + else if (nId == m_nReadonlyCopy) + nExecId = SID_COPY; + else if (nId == m_nReadonlyEditdoc) + nExecId = SID_EDITDOC; + else if (nId == m_nReadonlySelectionMode) + nExecId = FN_READONLY_SELECTION_MODE; + else if (nId == m_nReadonlyReload || nId == m_nReadonlyReloadFrame) + rSh.GetView().GetViewFrame()->GetDispatcher()->Execute(SID_RELOAD); + else if (nId == m_nReadonlyBrowseBackward) + nExecId = SID_BROWSE_BACKWARD; + else if (nId == m_nReadonlyBrowseForward) + nExecId = SID_BROWSE_FORWARD; + else if (nId == m_nReadonlySourceview) + nExecId = SID_SOURCEVIEW; + else if (nId == m_nReadonlySaveGraphic || nId == m_nReadonlySaveBackground) + SaveGraphic(nId); + else if (nId == m_nReadonlyCopylink) + { + pClipCntnr = new TransferDataContainer; + pClipCntnr->CopyString( m_sURL ); + } + else if (nId == m_nReadonlyLoadGraphic) + { + bool bModified = rSh.IsModified(); + SwViewOption aOpt( *rSh.GetViewOptions() ); + aOpt.SetGraphic( true ); + rSh.ApplyViewOptions( aOpt ); + if(!bModified) + rSh.ResetModified(); + } + else if (nId == m_nReadonlyGraphicoff) + nExecId = FN_VIEW_GRAPHIC; + else if (nId == m_nReadonlyTogallerylink || nId == m_nReadonlyBackgroundTogallerylink) + SW_MOD()->GetModuleConfig()->SetGrfToGalleryAsLnk(true); + else if (nId == m_nReadonlyTogallerycopy || nId == m_nReadonlyBackgroundTogallerycopy) + SW_MOD()->GetModuleConfig()->SetGrfToGalleryAsLnk(false); + + if( USHRT_MAX != nExecId ) + rDis.GetBindings()->Execute( nExecId ); + if( bFilterSet ) + ::LoadURL(rSh, m_sURL, nFilter, m_sTargetFrameName); + + if( pClipCntnr && pClipCntnr->HasAnyData() ) + { + pClipCntnr->CopyToClipboard( pWin ); + } +} + +OUString SwReadOnlyPopup::SaveGraphic(sal_uInt16 nId) +{ + // fish out the graphic's name + if (nId == m_nReadonlySaveBackground) + { + if ( m_xBrushItem && !m_xBrushItem->GetGraphicLink().isEmpty() ) + m_sGrfName = m_xBrushItem->GetGraphicLink(); + const Graphic *pGrf = m_xBrushItem ? m_xBrushItem->GetGraphic() : nullptr; + if ( pGrf ) + { + m_aGraphic = *pGrf; + if ( !m_xBrushItem->GetGraphicLink().isEmpty() ) + m_sGrfName = m_xBrushItem->GetGraphicLink(); + } + else + return OUString(); + } + return GraphicHelper::ExportGraphic(m_rView.GetFrameWeld(), m_aGraphic, m_sGrfName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/romenu.hxx b/sw/source/uibase/docvw/romenu.hxx new file mode 100644 index 000000000..8961b93d7 --- /dev/null +++ b/sw/source/uibase/docvw/romenu.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <editeng/brushitem.hxx> +#include <vcl/builder.hxx> +#include <vcl/graph.hxx> +#include <vcl/menu.hxx> + +class SwView; +class SfxDispatcher; +class ImageMap; +class INetImage; + +class SwReadOnlyPopup +{ + VclBuilder m_aBuilder; + ScopedVclPtr<PopupMenu> m_xMenu; + sal_uInt16 m_nReadonlyOpenurl; + sal_uInt16 m_nReadonlyOpendoc; + sal_uInt16 m_nReadonlyEditdoc; + sal_uInt16 m_nReadonlySelectionMode; + sal_uInt16 m_nReadonlyReload; + sal_uInt16 m_nReadonlyReloadFrame; + sal_uInt16 m_nReadonlySourceview; + sal_uInt16 m_nReadonlyBrowseBackward; + sal_uInt16 m_nReadonlyBrowseForward; + sal_uInt16 m_nReadonlySaveGraphic; + sal_uInt16 m_nReadonlyGraphictogallery; + sal_uInt16 m_nReadonlyTogallerylink; + sal_uInt16 m_nReadonlyTogallerycopy; + sal_uInt16 m_nReadonlySaveBackground; + sal_uInt16 m_nReadonlyBackgroundtogallery; + sal_uInt16 m_nReadonlyBackgroundTogallerylink; + sal_uInt16 m_nReadonlyBackgroundTogallerycopy; + sal_uInt16 m_nReadonlyCopylink; + sal_uInt16 m_nReadonlyLoadGraphic; + sal_uInt16 m_nReadonlyGraphicoff; + sal_uInt16 m_nReadonlyFullscreen; + sal_uInt16 m_nReadonlyCopy; + + SwView & m_rView; + std::unique_ptr<SvxBrushItem> m_xBrushItem; + Graphic m_aGraphic; + OUString m_sURL, + m_sTargetFrameName; + OUString m_sGrfName; + std::vector<OUString> m_aThemeList; + bool m_bGrfToGalleryAsLnk; + + void Check( sal_uInt16 nMID, sal_uInt16 nSID, SfxDispatcher const &rDis ); + OUString SaveGraphic( sal_uInt16 nId ); + +public: + SwReadOnlyPopup(const Point &rDPos, SwView &rV); + css::uno::Reference<css::awt::XPopupMenu> CreateMenuInterface() { return m_xMenu->CreateMenuInterface(); } + ~SwReadOnlyPopup(); + + void Execute( vcl::Window* pWin, const Point &rPPos ); + void Execute( vcl::Window* pWin, sal_uInt16 nId ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/docvw/srcedtw.cxx b/sw/source/uibase/docvw/srcedtw.cxx new file mode 100644 index 000000000..cbd0f132a --- /dev/null +++ b/sw/source/uibase/docvw/srcedtw.cxx @@ -0,0 +1,984 @@ +/* -*- 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 <sal/config.h> + +#include <hintids.hxx> +#include <cmdid.h> + +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XPropertiesChangeListener.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <cppuhelper/implbase.hxx> +#include <officecfg/Office/Common.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/textview.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/ptrstyle.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svtools/htmltokn.h> +#include <vcl/txtattr.hxx> +#include <vcl/settings.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/flstitem.hxx> +#include <vcl/metric.hxx> +#include <svtools/ctrltool.hxx> +#include <tools/time.hxx> +#include <swmodule.hxx> +#include <docsh.hxx> +#include <srcview.hxx> +#include <helpids.h> +#include <vector> + +namespace +{ + +struct TextPortion +{ + sal_uInt16 nStart, nEnd; + svtools::ColorConfigEntry eType; +}; + +} + +#define MAX_SYNTAX_HIGHLIGHT 20 +#define MAX_HIGHLIGHTTIME 200 + +typedef std::vector<TextPortion> TextPortions; + +static void lcl_Highlight(const OUString& rSource, TextPortions& aPortionList) +{ + const sal_Unicode cOpenBracket = '<'; + const sal_Unicode cCloseBracket= '>'; + const sal_Unicode cSlash = '/'; + const sal_Unicode cExclamation = '!'; + const sal_Unicode cMinus = '-'; + const sal_Unicode cSpace = ' '; + const sal_Unicode cTab = 0x09; + const sal_Unicode cLF = 0x0a; + const sal_Unicode cCR = 0x0d; + + const sal_uInt16 nStrLen = rSource.getLength(); + sal_uInt16 nInsert = 0; // number of inserted portions + sal_uInt16 nActPos = 0; // position, where '<' was found + sal_uInt16 nPortStart = USHRT_MAX; // for the TextPortion + sal_uInt16 nPortEnd = 0; + TextPortion aText; + while(nActPos < nStrLen) + { + if((nActPos < nStrLen - 2) && (rSource[nActPos] == cOpenBracket)) + { + svtools::ColorConfigEntry eFoundType = svtools::HTMLUNKNOWN; + // insert 'empty' portion + if(nPortEnd < nActPos - 1 ) + { + // don't move at the beginning + aText.nStart = nPortEnd; + if(nInsert) + aText.nStart += 1; + aText.nEnd = nActPos - 1; + aText.eType = svtools::HTMLUNKNOWN; + aPortionList.push_back( aText ); + nInsert++; + } + sal_Unicode cFollowFirst = rSource[nActPos + 1]; + sal_Unicode cFollowNext = rSource[nActPos + 2]; + if(cExclamation == cFollowFirst) + { + // "<!" SGML or comment + if(cMinus == cFollowNext && + nActPos < nStrLen - 3 && cMinus == rSource[nActPos + 3]) + { + eFoundType = svtools::HTMLCOMMENT; + } + else + eFoundType = svtools::HTMLSGML; + nPortStart = nActPos; + nPortEnd = nActPos + 1; + } + else if(cSlash == cFollowFirst) + { + // "</" ignore slash + nPortStart = nActPos; + nActPos++; + } + if(svtools::HTMLUNKNOWN == eFoundType) + { + // now here a keyword could follow + sal_uInt16 nSrchPos = nActPos; + while(++nSrchPos < nStrLen - 1) + { + sal_Unicode cNext = rSource[nSrchPos]; + if( cNext == cSpace || + cNext == cTab || + cNext == cLF || + cNext == cCR) + break; + else if(cNext == cCloseBracket) + { + break; + } + } + if(nSrchPos > nActPos + 1) + { + // some string was found + OUString sToken = rSource.copy(nActPos + 1, nSrchPos - nActPos - 1 ); + sToken = sToken.toAsciiUpperCase(); + HtmlTokenId nToken = ::GetHTMLToken(sToken); + if(nToken != HtmlTokenId::NONE) + { + eFoundType = svtools::HTMLKEYWORD; + nPortEnd = nSrchPos; + nPortStart = nActPos; + } + else + SAL_WARN("sw", "HTML token " << sToken << " not recognised!"); + } + } + // now we still have to look for '>' + if(svtools::HTMLUNKNOWN != eFoundType) + { + bool bFound = false; + for(sal_uInt16 i = nPortEnd; i < nStrLen; i++) + if(cCloseBracket == rSource[i]) + { + bFound = true; + nPortEnd = i; + break; + } + if(!bFound && (eFoundType == svtools::HTMLCOMMENT)) + { + // comment without ending in this line + bFound = true; + nPortEnd = nStrLen - 1; + } + + if(bFound ||(eFoundType == svtools::HTMLCOMMENT)) + { + TextPortion aTextPortion; + aTextPortion.nStart = nPortStart + 1; + aTextPortion.nEnd = nPortEnd; + aTextPortion.eType = eFoundType; + aPortionList.push_back( aTextPortion ); + nInsert++; + } + + } + } + nActPos++; + } + if(nInsert && nPortEnd < nActPos - 1) + { + aText.nStart = nPortEnd + 1; + aText.nEnd = nActPos - 1; + aText.eType = svtools::HTMLUNKNOWN; + aPortionList.push_back( aText ); + nInsert++; + } +} + +class SwSrcEditWindow::ChangesListener: + public cppu::WeakImplHelper< css::beans::XPropertiesChangeListener > +{ +public: + explicit ChangesListener(SwSrcEditWindow & editor): m_Editor(editor) {} + +private: + virtual ~ChangesListener() override {} + + virtual void SAL_CALL disposing(css::lang::EventObject const &) override + { + std::unique_lock g(m_Editor.mutex_); + m_Editor.m_xNotifier.clear(); + } + + virtual void SAL_CALL propertiesChange( + css::uno::Sequence< css::beans::PropertyChangeEvent > const &) override + { + SolarMutexGuard g; + m_Editor.SetFont(); + } + + SwSrcEditWindow & m_Editor; +}; + +SwSrcEditWindow::SwSrcEditWindow( vcl::Window* pParent, SwSrcView* pParentView ) : + Window( pParent, WB_BORDER|WB_CLIPCHILDREN ), + + m_pOutWin(nullptr), + m_pHScrollbar(nullptr), + m_pVScrollbar(nullptr), + + m_pSrcView(pParentView), + + m_nCurTextWidth(0), + m_nStartLine(USHRT_MAX), + m_eSourceEncoding(osl_getThreadTextEncoding()), + m_bReadonly(false), + m_bHighlighting(false), + m_aSyntaxIdle("sw uibase SwSrcEditWindow Syntax") +{ + SetHelpId(HID_SOURCE_EDITWIN); + CreateTextEngine(); + + // Using "this" in ctor is a little fishy, but should work here at least as + // long as there are no derivations: + m_xListener = new ChangesListener(*this); + css::uno::Reference< css::beans::XMultiPropertySet > n( + officecfg::Office::Common::Font::SourceViewFont::get(), + css::uno::UNO_QUERY_THROW); + { + std::unique_lock g(mutex_); + m_xNotifier = n; + } + n->addPropertiesChangeListener({ "FontHeight", "FontName" }, m_xListener); +} + +SwSrcEditWindow::~SwSrcEditWindow() +{ + disposeOnce(); +} + +void SwSrcEditWindow::dispose() +{ + css::uno::Reference< css::beans::XMultiPropertySet > n; + { + std::unique_lock g(mutex_); + n = m_xNotifier; + } + if (n.is()) { + n->removePropertiesChangeListener(m_xListener); + } + m_aSyntaxIdle.Stop(); + if ( m_pOutWin ) + m_pOutWin->SetTextView( nullptr ); + + if ( m_pTextEngine ) + { + EndListening( *m_pTextEngine ); + m_pTextEngine->RemoveView( m_pTextView.get() ); + + m_pTextView.reset(); + m_pTextEngine.reset(); + } + m_pHScrollbar.disposeAndClear(); + m_pVScrollbar.disposeAndClear(); + m_pOutWin.disposeAndClear(); + vcl::Window::dispose(); +} + +void SwSrcEditWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + switch ( rDCEvt.GetType() ) + { + case DataChangedEventType::SETTINGS: + // newly rearrange ScrollBars or trigger Resize, because + // ScrollBar size could have changed. For this, in the + // Resize handler the size of ScrollBars has to be queried + // from the settings as well. + if( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) + Resize(); + break; + default: break; + } +} + +void SwSrcEditWindow::Resize() +{ + // ScrollBars, etc. happens in Adjust... + if ( !m_pTextView ) + return; + + tools::Long nVisY = m_pTextView->GetStartDocPos().Y(); + m_pTextView->ShowCursor(); + Size aOutSz( GetOutputSizePixel() ); + tools::Long nMaxVisAreaStart = m_pTextView->GetTextEngine()->GetTextHeight() - aOutSz.Height(); + if ( nMaxVisAreaStart < 0 ) + nMaxVisAreaStart = 0; + if ( m_pTextView->GetStartDocPos().Y() > nMaxVisAreaStart ) + { + Point aStartDocPos( m_pTextView->GetStartDocPos() ); + aStartDocPos.setY( nMaxVisAreaStart ); + m_pTextView->SetStartDocPos( aStartDocPos ); + m_pTextView->ShowCursor(); + } + tools::Long nScrollStd = GetSettings().GetStyleSettings().GetScrollBarSize(); + Size aScrollSz(aOutSz.Width() - nScrollStd, nScrollStd ); + Point aScrollPos(0, aOutSz.Height() - nScrollStd); + + m_pHScrollbar->SetPosSizePixel( aScrollPos, aScrollSz); + + aScrollSz.setWidth( aScrollSz.Height() ); + aScrollSz.setHeight( aOutSz.Height() ); + aScrollPos = Point(aOutSz.Width() - nScrollStd, 0); + + m_pVScrollbar->SetPosSizePixel( aScrollPos, aScrollSz); + aOutSz.AdjustWidth( -nScrollStd ); + aOutSz.AdjustHeight( -nScrollStd ); + m_pOutWin->SetOutputSizePixel(aOutSz); + InitScrollBars(); + + // set line in first Resize + if(USHRT_MAX != m_nStartLine) + { + if(m_nStartLine < m_pTextEngine->GetParagraphCount()) + { + TextSelection aSel(TextPaM( m_nStartLine, 0 ), TextPaM( m_nStartLine, 0x0 )); + m_pTextView->SetSelection(aSel); + m_pTextView->ShowCursor(); + } + m_nStartLine = USHRT_MAX; + } + + if ( nVisY != m_pTextView->GetStartDocPos().Y() ) + Invalidate(); + + +} + +void TextViewOutWin::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + switch( rDCEvt.GetType() ) + { + case DataChangedEventType::SETTINGS: + // query settings + if( rDCEvt.GetFlags() & AllSettingsFlags::STYLE ) + { + const Color &rCol = GetSettings().GetStyleSettings().GetWindowColor(); + SetBackground( rCol ); + vcl::Font aFont( m_pTextView->GetTextEngine()->GetFont() ); + aFont.SetFillColor( rCol ); + m_pTextView->GetTextEngine()->SetFont( aFont ); + } + break; + default: break; + } +} + +void TextViewOutWin::MouseMove( const MouseEvent &rEvt ) +{ + if ( m_pTextView ) + m_pTextView->MouseMove( rEvt ); +} + +void TextViewOutWin::MouseButtonUp( const MouseEvent &rEvt ) +{ + if ( m_pTextView ) + { + m_pTextView->MouseButtonUp( rEvt ); + SfxViewFrame *pFrame = static_cast<SwSrcEditWindow*>(GetParent())->GetSrcView()->GetViewFrame(); + if ( pFrame ) + { + SfxBindings& rBindings = pFrame->GetBindings(); + rBindings.Invalidate( SID_TABLE_CELL ); + rBindings.Invalidate( SID_CUT ); + rBindings.Invalidate( SID_COPY ); + } + } +} + +void TextViewOutWin::MouseButtonDown( const MouseEvent &rEvt ) +{ + GrabFocus(); + if ( m_pTextView ) + m_pTextView->MouseButtonDown( rEvt ); +} + +void TextViewOutWin::Command( const CommandEvent& rCEvt ) +{ + switch(rCEvt.GetCommand()) + { + case CommandEventId::ContextMenu: + SfxDispatcher::ExecutePopup(); + break; + case CommandEventId::Wheel: + case CommandEventId::StartAutoScroll: + case CommandEventId::AutoScroll: + { + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if( !pWData || CommandWheelMode::ZOOM != pWData->GetMode() ) + { + static_cast<SwSrcEditWindow*>(GetParent())->HandleWheelCommand( rCEvt ); + } + } + break; + + default: + if ( m_pTextView ) + m_pTextView->Command( rCEvt ); + else + Window::Command(rCEvt); + } +} + +void TextViewOutWin::KeyInput( const KeyEvent& rKEvt ) +{ + bool bDone = false; + SwSrcEditWindow* pSrcEditWin = static_cast<SwSrcEditWindow*>(GetParent()); + bool bChange = !pSrcEditWin->IsReadonly() || !TextEngine::DoesKeyChangeText( rKEvt ); + if(bChange) + bDone = m_pTextView->KeyInput( rKEvt ); + + SfxBindings& rBindings = static_cast<SwSrcEditWindow*>(GetParent())->GetSrcView()->GetViewFrame()->GetBindings(); + if ( !bDone ) + { + if ( !SfxViewShell::Current()->KeyInput( rKEvt ) ) + Window::KeyInput( rKEvt ); + } + else + { + rBindings.Invalidate( SID_TABLE_CELL ); + if ( rKEvt.GetKeyCode().GetGroup() == KEYGROUP_CURSOR ) + rBindings.Update( SID_BASICIDE_STAT_POS ); + if (pSrcEditWin->GetTextEngine()->IsModified() ) + { + rBindings.Invalidate( SID_SAVEDOC ); + rBindings.Invalidate( SID_DOC_MODIFIED ); + } + if( rKEvt.GetKeyCode().GetCode() == KEY_INSERT ) + rBindings.Invalidate( SID_ATTR_INSERT ); + } + + rBindings.Invalidate( SID_CUT ); + rBindings.Invalidate( SID_COPY ); + + SwDocShell* pDocShell = pSrcEditWin->GetSrcView()->GetDocShell(); + if(pSrcEditWin->GetTextEngine()->IsModified()) + { + pDocShell->SetModified(); + } +} + +void TextViewOutWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + m_pTextView->Paint(rRenderContext, rRect); +} + +void SwSrcEditWindow::CreateTextEngine() +{ + // FIXME RenderContext + + const Color &rCol = GetSettings().GetStyleSettings().GetWindowColor(); + m_pOutWin = VclPtr<TextViewOutWin>::Create(this, 0); + m_pOutWin->SetBackground(Wallpaper(rCol)); + m_pOutWin->SetPointer(PointerStyle::Text); + m_pOutWin->Show(); + + // create Scrollbars + m_pHScrollbar = VclPtr<ScrollBar>::Create(this, WB_3DLOOK |WB_HSCROLL|WB_DRAG); + m_pHScrollbar->EnableRTL( false ); + m_pHScrollbar->SetScrollHdl(LINK(this, SwSrcEditWindow, ScrollHdl)); + m_pHScrollbar->Show(); + + m_pVScrollbar = VclPtr<ScrollBar>::Create(this, WB_3DLOOK |WB_VSCROLL|WB_DRAG); + m_pVScrollbar->EnableRTL( false ); + m_pVScrollbar->SetScrollHdl(LINK(this, SwSrcEditWindow, ScrollHdl)); + m_pHScrollbar->EnableDrag(); + m_pVScrollbar->Show(); + + m_pTextEngine.reset(new ExtTextEngine); + m_pTextView.reset(new TextView( m_pTextEngine.get(), m_pOutWin )); + m_pTextView->SetAutoIndentMode(true); + m_pOutWin->SetTextView(m_pTextView.get()); + + m_pTextEngine->SetUpdateMode( false ); + m_pTextEngine->InsertView( m_pTextView.get() ); + + vcl::Font aFont; + aFont.SetTransparent( false ); + aFont.SetFillColor( rCol ); + SetPointFont(*GetOutDev(), aFont); + aFont = GetFont(); + aFont.SetFillColor( rCol ); + m_pOutWin->SetFont( aFont ); + m_pTextEngine->SetFont( aFont ); + + m_aSyntaxIdle.SetInvokeHandler( LINK( this, SwSrcEditWindow, SyntaxTimerHdl ) ); + + m_pTextEngine->EnableUndo( true ); + m_pTextEngine->SetUpdateMode( true ); + + m_pTextView->ShowCursor(); + InitScrollBars(); + StartListening( *m_pTextEngine ); + + SfxBindings& rBind = GetSrcView()->GetViewFrame()->GetBindings(); + rBind.Invalidate( SID_TABLE_CELL ); +} + +void SwSrcEditWindow::SetScrollBarRanges() +{ + // Extra method, not InitScrollBars, because also for TextEngine events. + + m_pHScrollbar->SetRange( Range( 0, m_nCurTextWidth-1 ) ); + m_pVScrollbar->SetRange( Range(0, m_pTextEngine->GetTextHeight()-1) ); +} + +void SwSrcEditWindow::InitScrollBars() +{ + SetScrollBarRanges(); + + Size aOutSz( m_pOutWin->GetOutputSizePixel() ); + m_pVScrollbar->SetVisibleSize( aOutSz.Height() ); + m_pVScrollbar->SetPageSize( aOutSz.Height() * 8 / 10 ); + m_pVScrollbar->SetLineSize( m_pOutWin->GetTextHeight() ); + m_pVScrollbar->SetThumbPos( m_pTextView->GetStartDocPos().Y() ); + m_pHScrollbar->SetVisibleSize( aOutSz.Width() ); + m_pHScrollbar->SetPageSize( aOutSz.Width() * 8 / 10 ); + m_pHScrollbar->SetLineSize( m_pOutWin->GetTextWidth(OUString('x')) ); + m_pHScrollbar->SetThumbPos( m_pTextView->GetStartDocPos().X() ); + +} + +IMPL_LINK(SwSrcEditWindow, ScrollHdl, ScrollBar*, pScroll, void) +{ + if(pScroll == m_pVScrollbar) + { + tools::Long nDiff = m_pTextView->GetStartDocPos().Y() - pScroll->GetThumbPos(); + GetTextView()->Scroll( 0, nDiff ); + m_pTextView->ShowCursor( false ); + pScroll->SetThumbPos( m_pTextView->GetStartDocPos().Y() ); + } + else + { + tools::Long nDiff = m_pTextView->GetStartDocPos().X() - pScroll->GetThumbPos(); + GetTextView()->Scroll( nDiff, 0 ); + m_pTextView->ShowCursor( false ); + pScroll->SetThumbPos( m_pTextView->GetStartDocPos().X() ); + } + GetSrcView()->GetViewFrame()->GetBindings().Invalidate( SID_TABLE_CELL ); +} + +IMPL_LINK( SwSrcEditWindow, SyntaxTimerHdl, Timer*, pIdle, void ) +{ + tools::Time aSyntaxCheckStart( tools::Time::SYSTEM ); + SAL_WARN_IF(m_pTextView == nullptr, "sw", "No View yet, but syntax highlighting?!"); + + m_bHighlighting = true; + sal_uInt16 nCount = 0; + // at first the region around the cursor is processed + TextSelection aSel = m_pTextView->GetSelection(); + sal_uInt16 nCur = o3tl::narrowing<sal_uInt16>(aSel.GetStart().GetPara()); + if(nCur > 40) + nCur -= 40; + else + nCur = 0; + if(!m_aSyntaxLineTable.empty()) + for(sal_uInt16 i = 0; i < 80 && nCount < 40; i++, nCur++) + { + if(m_aSyntaxLineTable.find(nCur) != m_aSyntaxLineTable.end()) + { + DoSyntaxHighlight( nCur ); + m_aSyntaxLineTable.erase( nCur ); + nCount++; + if(m_aSyntaxLineTable.empty()) + break; + if((tools::Time( tools::Time::SYSTEM ).GetTime() - aSyntaxCheckStart.GetTime()) > MAX_HIGHLIGHTTIME ) + { + break; + } + } + } + + // when there is still anything left by then, go on from the beginning + while ( !m_aSyntaxLineTable.empty() && nCount < MAX_SYNTAX_HIGHLIGHT) + { + sal_uInt16 nLine = *m_aSyntaxLineTable.begin(); + DoSyntaxHighlight( nLine ); + m_aSyntaxLineTable.erase(nLine); + nCount ++; + if(tools::Time( tools::Time::SYSTEM ).GetTime() - aSyntaxCheckStart.GetTime() > MAX_HIGHLIGHTTIME) + { + break; + } + } + + if(!m_aSyntaxLineTable.empty() && !pIdle->IsActive()) + pIdle->Start(); + // SyntaxTimerHdl is called when text changed + // => good opportunity to determine text width! + tools::Long nPrevTextWidth = m_nCurTextWidth; + m_nCurTextWidth = m_pTextEngine->CalcTextWidth() + 25; // small tolerance + if ( m_nCurTextWidth != nPrevTextWidth ) + SetScrollBarRanges(); + m_bHighlighting = false; +} + +void SwSrcEditWindow::DoSyntaxHighlight( sal_uInt16 nPara ) +{ + // Because of DelayedSyntaxHighlight it could happen, + // that the line doesn't exist anymore! + if ( nPara >= m_pTextEngine->GetParagraphCount() ) + return; + + bool bTempModified = IsModified(); + m_pTextEngine->RemoveAttribs( nPara ); + OUString aSource( m_pTextEngine->GetText( nPara ) ); + m_pTextEngine->SetUpdateMode( false ); + ImpDoHighlight( aSource, nPara ); + TextView* pTmp = m_pTextEngine->GetActiveView(); + pTmp->SetAutoScroll(false); + m_pTextEngine->SetActiveView(nullptr); + m_pTextEngine->SetUpdateMode( true ); + m_pTextEngine->SetActiveView(pTmp); + pTmp->SetAutoScroll(true); + pTmp->ShowCursor( false/*pTmp->IsAutoScroll()*/ ); + + if(!bTempModified) + ClearModifyFlag(); + +} + +void SwSrcEditWindow::ImpDoHighlight( const OUString& rSource, sal_uInt16 nLineOff ) +{ + TextPortions aPortionList; + lcl_Highlight(rSource, aPortionList); + + size_t nCount = aPortionList.size(); + if ( !nCount ) + return; + + TextPortion& rLast = aPortionList[nCount-1]; + if ( rLast.nStart > rLast.nEnd ) // Only until Bug from MD is resolved + { + nCount--; + aPortionList.pop_back(); + if ( !nCount ) + return; + } + + { + // Only blanks and tabs have to be attributed along. + // When two identical attributes are placed consecutively, + // it optimises the TextEngine. + sal_uInt16 nLastEnd = 0; + + for ( size_t i = 0; i < nCount; i++ ) + { + TextPortion& r = aPortionList[i]; + if ( r.nStart > r.nEnd ) // only until Bug from MD is resolved + continue; + + if ( r.nStart > nLastEnd ) + { + // Can I rely on the fact that all except blank and tab + // are being highlighted?! + r.nStart = nLastEnd; + } + nLastEnd = r.nEnd+1; + if ( ( i == (nCount-1) ) && ( r.nEnd < rSource.getLength() ) ) + r.nEnd = rSource.getLength(); + } + } + + for (TextPortion & r : aPortionList) + { + if ( r.nStart > r.nEnd ) // only until Bug from MD is resolved + continue; + if(r.eType != svtools::HTMLSGML && + r.eType != svtools::HTMLCOMMENT && + r.eType != svtools::HTMLKEYWORD && + r.eType != svtools::HTMLUNKNOWN) + r.eType = svtools::HTMLUNKNOWN; + Color aColor(SW_MOD()->GetColorConfig().GetColorValue(r.eType).nColor); + m_pTextEngine->SetAttrib( TextAttribFontColor( aColor ), nLineOff, r.nStart, r.nEnd+1 ); + } +} + +void SwSrcEditWindow::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + const TextHint* pTextHint = dynamic_cast<const TextHint*>(&rHint); + if (!pTextHint) + return; + + switch (pTextHint->GetId()) + { + case SfxHintId::TextViewScrolled: + m_pHScrollbar->SetThumbPos( m_pTextView->GetStartDocPos().X() ); + m_pVScrollbar->SetThumbPos( m_pTextView->GetStartDocPos().Y() ); + break; + + case SfxHintId::TextHeightChanged: + if ( m_pTextEngine->GetTextHeight() < m_pOutWin->GetOutputSizePixel().Height() ) + m_pTextView->Scroll( 0, m_pTextView->GetStartDocPos().Y() ); + m_pVScrollbar->SetThumbPos( m_pTextView->GetStartDocPos().Y() ); + SetScrollBarRanges(); + break; + + case SfxHintId::TextParaInserted: + case SfxHintId::TextParaContentChanged: + if ( !m_bHighlighting ) + { + m_aSyntaxLineTable.insert( o3tl::narrowing<sal_uInt16>(pTextHint->GetValue()) ); + m_aSyntaxIdle.Start(); + } + break; + default: break; + } +} + +void SwSrcEditWindow::Invalidate(InvalidateFlags ) +{ + m_pOutWin->Invalidate(); + Window::Invalidate(); +} + +void SwSrcEditWindow::Command( const CommandEvent& rCEvt ) +{ + switch(rCEvt.GetCommand()) + { + case CommandEventId::Wheel: + case CommandEventId::StartAutoScroll: + case CommandEventId::AutoScroll: + { + const CommandWheelData* pWData = rCEvt.GetWheelData(); + if( !pWData || CommandWheelMode::ZOOM != pWData->GetMode() ) + HandleScrollCommand( rCEvt, m_pHScrollbar, m_pVScrollbar ); + } + break; + default: + Window::Command(rCEvt); + } +} + +void SwSrcEditWindow::HandleWheelCommand( const CommandEvent& rCEvt ) +{ + m_pTextView->Command(rCEvt); + HandleScrollCommand( rCEvt, m_pHScrollbar, m_pVScrollbar ); +} + +void SwSrcEditWindow::GetFocus() +{ + if (m_pOutWin) + m_pOutWin->GrabFocus(); +} + +static bool lcl_GetLanguagesForEncoding(rtl_TextEncoding eEnc, LanguageType aLanguages[]) +{ + switch(eEnc) + { + case RTL_TEXTENCODING_UTF7 : + case RTL_TEXTENCODING_UTF8 : + // don#t fill - all LANGUAGE_SYSTEM means unicode font has to be used + break; + + case RTL_TEXTENCODING_ISO_8859_3: + case RTL_TEXTENCODING_ISO_8859_1 : + case RTL_TEXTENCODING_MS_1252 : + case RTL_TEXTENCODING_APPLE_ROMAN : + case RTL_TEXTENCODING_IBM_850 : + case RTL_TEXTENCODING_ISO_8859_14 : + case RTL_TEXTENCODING_ISO_8859_15 : + //fill with western languages + aLanguages[0] = LANGUAGE_GERMAN; + aLanguages[1] = LANGUAGE_FRENCH; + aLanguages[2] = LANGUAGE_ITALIAN; + aLanguages[3] = LANGUAGE_SPANISH; + break; + + case RTL_TEXTENCODING_IBM_865 : + //scandinavian + aLanguages[0] = LANGUAGE_FINNISH; + aLanguages[1] = LANGUAGE_NORWEGIAN; + aLanguages[2] = LANGUAGE_SWEDISH; + aLanguages[3] = LANGUAGE_DANISH; + break; + + case RTL_TEXTENCODING_ISO_8859_10 : + case RTL_TEXTENCODING_ISO_8859_13 : + case RTL_TEXTENCODING_ISO_8859_2 : + case RTL_TEXTENCODING_IBM_852 : + case RTL_TEXTENCODING_MS_1250 : + case RTL_TEXTENCODING_APPLE_CENTEURO : + aLanguages[0] = LANGUAGE_POLISH; + aLanguages[1] = LANGUAGE_CZECH; + aLanguages[2] = LANGUAGE_HUNGARIAN; + aLanguages[3] = LANGUAGE_SLOVAK; + break; + + case RTL_TEXTENCODING_ISO_8859_4 : + case RTL_TEXTENCODING_IBM_775 : + case RTL_TEXTENCODING_MS_1257 : + aLanguages[0] = LANGUAGE_LATVIAN ; + aLanguages[1] = LANGUAGE_LITHUANIAN; + aLanguages[2] = LANGUAGE_ESTONIAN ; + break; + + case RTL_TEXTENCODING_IBM_863 : aLanguages[0] = LANGUAGE_FRENCH_CANADIAN; break; + case RTL_TEXTENCODING_APPLE_FARSI : aLanguages[0] = LANGUAGE_FARSI; break; + case RTL_TEXTENCODING_APPLE_ROMANIAN:aLanguages[0] = LANGUAGE_ROMANIAN; break; + + case RTL_TEXTENCODING_IBM_861 : + case RTL_TEXTENCODING_APPLE_ICELAND : + aLanguages[0] = LANGUAGE_ICELANDIC; + break; + + case RTL_TEXTENCODING_APPLE_CROATIAN:aLanguages[0] = LANGUAGE_CROATIAN; break; + + case RTL_TEXTENCODING_IBM_437 : + case RTL_TEXTENCODING_ASCII_US : aLanguages[0] = LANGUAGE_ENGLISH; break; + + case RTL_TEXTENCODING_IBM_862 : + case RTL_TEXTENCODING_MS_1255 : + case RTL_TEXTENCODING_APPLE_HEBREW : + case RTL_TEXTENCODING_ISO_8859_8 : + aLanguages[0] = LANGUAGE_HEBREW; + break; + + case RTL_TEXTENCODING_IBM_857 : + case RTL_TEXTENCODING_MS_1254 : + case RTL_TEXTENCODING_APPLE_TURKISH: + case RTL_TEXTENCODING_ISO_8859_9 : + aLanguages[0] = LANGUAGE_TURKISH; + break; + + case RTL_TEXTENCODING_IBM_860 : + aLanguages[0] = LANGUAGE_PORTUGUESE; + break; + + case RTL_TEXTENCODING_IBM_869 : + case RTL_TEXTENCODING_MS_1253 : + case RTL_TEXTENCODING_APPLE_GREEK : + case RTL_TEXTENCODING_ISO_8859_7 : + case RTL_TEXTENCODING_IBM_737 : + aLanguages[0] = LANGUAGE_GREEK; + break; + + case RTL_TEXTENCODING_KOI8_R : + case RTL_TEXTENCODING_ISO_8859_5 : + case RTL_TEXTENCODING_IBM_855 : + case RTL_TEXTENCODING_MS_1251 : + case RTL_TEXTENCODING_IBM_866 : + case RTL_TEXTENCODING_APPLE_CYRILLIC : + aLanguages[0] = LANGUAGE_RUSSIAN; + break; + + case RTL_TEXTENCODING_APPLE_UKRAINIAN: + case RTL_TEXTENCODING_KOI8_U: + aLanguages[0] = LANGUAGE_UKRAINIAN; + break; + + case RTL_TEXTENCODING_IBM_864 : + case RTL_TEXTENCODING_MS_1256 : + case RTL_TEXTENCODING_ISO_8859_6 : + case RTL_TEXTENCODING_APPLE_ARABIC : + aLanguages[0] = LANGUAGE_ARABIC_SAUDI_ARABIA; + break; + + case RTL_TEXTENCODING_APPLE_CHINTRAD : + case RTL_TEXTENCODING_MS_950 : + case RTL_TEXTENCODING_GBT_12345 : + case RTL_TEXTENCODING_BIG5 : + case RTL_TEXTENCODING_EUC_TW : + case RTL_TEXTENCODING_BIG5_HKSCS : + aLanguages[0] = LANGUAGE_CHINESE_TRADITIONAL; + break; + + case RTL_TEXTENCODING_EUC_JP : + case RTL_TEXTENCODING_ISO_2022_JP : + case RTL_TEXTENCODING_JIS_X_0201 : + case RTL_TEXTENCODING_JIS_X_0208 : + case RTL_TEXTENCODING_JIS_X_0212 : + case RTL_TEXTENCODING_APPLE_JAPANESE : + case RTL_TEXTENCODING_MS_932 : + case RTL_TEXTENCODING_SHIFT_JIS : + aLanguages[0] = LANGUAGE_JAPANESE; + break; + + case RTL_TEXTENCODING_GB_2312 : + case RTL_TEXTENCODING_MS_936 : + case RTL_TEXTENCODING_GBK : + case RTL_TEXTENCODING_GB_18030 : + case RTL_TEXTENCODING_APPLE_CHINSIMP : + case RTL_TEXTENCODING_EUC_CN : + case RTL_TEXTENCODING_ISO_2022_CN : + aLanguages[0] = LANGUAGE_CHINESE_SIMPLIFIED; + break; + + case RTL_TEXTENCODING_APPLE_KOREAN : + case RTL_TEXTENCODING_MS_949 : + case RTL_TEXTENCODING_EUC_KR : + case RTL_TEXTENCODING_ISO_2022_KR : + case RTL_TEXTENCODING_MS_1361 : + aLanguages[0] = LANGUAGE_KOREAN; + break; + + case RTL_TEXTENCODING_APPLE_THAI : + case RTL_TEXTENCODING_MS_874 : + case RTL_TEXTENCODING_TIS_620 : + aLanguages[0] = LANGUAGE_THAI; + break; + default: aLanguages[0] = Application::GetSettings().GetUILanguageTag().getLanguageType(); + } + return aLanguages[0] != LANGUAGE_SYSTEM; +} +void SwSrcEditWindow::SetFont() +{ + OUString sFontName( + officecfg::Office::Common::Font::SourceViewFont::FontName::get(). + value_or(OUString())); + if(sFontName.isEmpty()) + { + LanguageType aLanguages[5] = + { + LANGUAGE_SYSTEM, LANGUAGE_SYSTEM, LANGUAGE_SYSTEM, LANGUAGE_SYSTEM, LANGUAGE_SYSTEM + }; + vcl::Font aFont; + if(lcl_GetLanguagesForEncoding(m_eSourceEncoding, aLanguages)) + { + //TODO: check for multiple languages + aFont = OutputDevice::GetDefaultFont(DefaultFontType::FIXED, aLanguages[0], GetDefaultFontFlags::NONE, GetOutDev()); + } + else + aFont = OutputDevice::GetDefaultFont(DefaultFontType::SANS_UNICODE, + Application::GetSettings().GetLanguageTag().getLanguageType(), GetDefaultFontFlags::NONE, GetOutDev()); + sFontName = aFont.GetFamilyName(); + } + const SvxFontListItem* pFontListItem = + static_cast<const SvxFontListItem* >(m_pSrcView->GetDocShell()->GetItem( SID_ATTR_CHAR_FONTLIST )); + const FontList* pList = pFontListItem->GetFontList(); + FontMetric aFontMetric = pList->Get(sFontName,WEIGHT_NORMAL, ITALIC_NONE); + + const vcl::Font& rFont = GetTextEngine()->GetFont(); + vcl::Font aFont(aFontMetric); + Size aSize(rFont.GetFontSize()); + //font height is stored in point and set in twip + aSize.setHeight( + officecfg::Office::Common::Font::SourceViewFont::FontHeight::get() * 20 ); + aFont.SetFontSize(m_pOutWin->LogicToPixel(aSize, MapMode(MapUnit::MapTwip))); + GetTextEngine()->SetFont( aFont ); + m_pOutWin->SetFont(aFont); +} + +void SwSrcEditWindow::SetTextEncoding(rtl_TextEncoding eEncoding) +{ + m_eSourceEncoding = eEncoding; + SetFont(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |