/* -*- 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 "impedit.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::linguistic2; #define SCRLRANGE 20 // Scroll 1/20 of the width/height, when in QueryDrop static void lcl_AllignToPixel( Point& rPoint, OutputDevice const * pOutDev, short nDiffX, short nDiffY ) { rPoint = pOutDev->LogicToPixel( rPoint ); if ( nDiffX ) rPoint.AdjustX(nDiffX ); if ( nDiffY ) rPoint.AdjustY(nDiffY ); rPoint = pOutDev->PixelToLogic( rPoint ); } // class ImpEditView ImpEditView::ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ) : aOutArea( Point(), pEng->GetPaperSize() ) { pEditView = pView; pEditEngine = pEng; pOutWin = pWindow; mpViewShell = nullptr; mpOtherShell = nullptr; nScrollDiffX = 0; nExtraCursorFlags = GetCursorFlags::NONE; nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; bReadOnly = false; bClickedInSelection = false; eSelectionMode = EESelectionMode::Std; eAnchorMode = EEAnchorMode::TopLeft; mpEditViewCallbacks = nullptr; nInvMore = 1; nTravelXPos = TRAVEL_X_DONTKNOW; nControl = EVControlBits::AUTOSCROLL | EVControlBits::ENABLEPASTE; bActiveDragAndDropListener = false; aEditSelection.Min() = pEng->GetEditDoc().GetStartPaM(); aEditSelection.Max() = pEng->GetEditDoc().GetEndPaM(); SelectionChanged(); } ImpEditView::~ImpEditView() { RemoveDragAndDropListeners(); if ( pOutWin && ( pOutWin->GetCursor() == pCursor.get() ) ) pOutWin->SetCursor( nullptr ); } void ImpEditView::SetBackgroundColor( const Color& rColor ) { pBackgroundColor.reset( new Color( rColor ) ); } void ImpEditView::RegisterViewShell(OutlinerViewShell* pViewShell) { mpViewShell = pViewShell; } void ImpEditView::RegisterOtherShell(OutlinerViewShell* pOtherShell) { mpOtherShell = pOtherShell; } const OutlinerViewShell* ImpEditView::GetViewShell() const { return mpViewShell; } void ImpEditView::SetEditSelection( const EditSelection& rEditSelection ) { // set state before notification aEditSelection = rEditSelection; SelectionChanged(); if (comphelper::LibreOfficeKit::isActive()) // Tiled rendering: selections are only painted when we are in selection mode. pEditEngine->SetInSelectionMode(aEditSelection.HasRange()); if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) { const EditDoc& rDoc = pEditEngine->GetEditDoc(); const EditPaM pmEnd = rDoc.GetEndPaM(); EENotifyType eNotifyType; if (rDoc.Count() > 1 && pmEnd == rEditSelection.Min() && pmEnd == rEditSelection.Max())//if move cursor to the last para. { eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA; } else { eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED; } EENotify aNotify( eNotifyType ); pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); } if(pEditEngine->pImpEditEngine->IsFormatted()) { EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS); pEditEngine->pImpEditEngine->GetNotifyHdl().Call(aNotify); } } /// Translate absolute <-> relative twips: LOK wants absolute coordinates as output and gives absolute coordinates as input. static void lcl_translateTwips(vcl::Window const & rParent, vcl::Window& rChild) { // Don't translate if we already have a non-zero origin. // This prevents multiple translate calls that negate // one another. const Point aOrigin = rChild.GetMapMode().GetOrigin(); if (aOrigin.getX() == 0 && aOrigin.getY() == 0) { // Set map mode, so that callback payloads will contain absolute coordinates instead of relative ones. Point aOffset(rChild.GetOutOffXPixel() - rParent.GetOutOffXPixel(), rChild.GetOutOffYPixel() - rParent.GetOutOffYPixel()); if (!rChild.IsMapModeEnabled()) { MapMode aMapMode(rChild.GetMapMode()); aMapMode.SetMapUnit(MapUnit::MapTwip); aMapMode.SetScaleX(rParent.GetMapMode().GetScaleX()); aMapMode.SetScaleY(rParent.GetMapMode().GetScaleY()); rChild.SetMapMode(aMapMode); rChild.EnableMapMode(); } aOffset = rChild.PixelToLogic(aOffset); MapMode aMapMode(rChild.GetMapMode()); aMapMode.SetOrigin(aOffset); aMapMode.SetMapUnit(rParent.GetMapMode().GetMapUnit()); rChild.SetMapMode(aMapMode); rChild.EnableMapMode(false); } } // EditView never had a central/secure place to react on SelectionChange since // Selection was changed in many places, often by not using SetEditSelection() // but (mis)using GetEditSelection() and manipulating this non-const return // value. Sorted this out now to have such a place, this is needed for safely // change/update the Selection visualization for enhanced mechanisms void ImpEditView::SelectionChanged() { if (getEditViewCallbacks()) { // use callback to tell about change in selection visualisation getEditViewCallbacks()->EditViewSelectionChange(); } } // renamed from DrawSelection to DrawSelectionXOR to better reflect what this // method was used for: Paint Selection in XOR, change it and again paint it in XOR. // This can be safely assumed due to the EditView only being capable of painting the // selection in XOR until today. // This also means that all places calling DrawSelectionXOR are thoroughly weighted // and chosen to make this fragile XOR-paint water-proof and thus contain some // information in this sense. // Someone thankfully expanded it to collect the SelectionRectangles when called with // the Region*, see GetSelectionRectangles below. void ImpEditView::DrawSelectionXOR( EditSelection aTmpSel, vcl::Region* pRegion, OutputDevice* pTargetDevice ) { if (getEditViewCallbacks() && !pRegion && !comphelper::LibreOfficeKit::isActive()) { // we are done, do *not* visualize self // CAUTION: do not use when comphelper::LibreOfficeKit::isActive() // due to event stuff triggered below. That *should* probably be moved // to SelectionChanged() which exists now, but I do not know enough about // that stuff to do it return; } if ( eSelectionMode == EESelectionMode::Hidden ) return; // It must be ensured before rendering the selection, that the contents of // the window is completely valid! Must be here so that in any case if // empty, then later on two-Paint Events! Must be done even before the // query from bUpdate, if after Invalidate paints still in the queue, // but someone switches the update mode! // pRegion: When not NULL, then only calculate Region. vcl::Region* pOldRegion = pRegion; vcl::Region aRegion; if (comphelper::LibreOfficeKit::isActive() && !pRegion) pRegion = &aRegion; OutputDevice* pTarget; if (pTargetDevice) pTarget = pTargetDevice; else pTarget = getEditViewCallbacks() ? &getEditViewCallbacks()->EditViewOutputDevice() : pOutWin; bool bClipRegion = pTarget->IsClipRegion(); vcl::Region aOldRegion = pTarget->GetClipRegion(); std::unique_ptr pPolyPoly; if ( !pRegion ) { if ( !pEditEngine->pImpEditEngine->GetUpdateMode() ) return; if ( pEditEngine->pImpEditEngine->IsInUndo() ) return; if ( !aTmpSel.HasRange() ) return; // aTmpOutArea: if OutputArea > Paper width and // Text > Paper width ( over large fields ) tools::Rectangle aTmpOutArea( aOutArea ); if ( aTmpOutArea.GetWidth() > pEditEngine->pImpEditEngine->GetPaperSize().Width() ) aTmpOutArea.SetRight( aTmpOutArea.Left() + pEditEngine->pImpEditEngine->GetPaperSize().Width() ); pTarget->IntersectClipRegion( aTmpOutArea ); if (pOutWin && pOutWin->GetCursor()) pOutWin->GetCursor()->Hide(); } else { pPolyPoly.reset(new tools::PolyPolygon); } DBG_ASSERT( !pEditEngine->IsIdleFormatterActive(), "DrawSelectionXOR: Not formatted!" ); aTmpSel.Adjust( pEditEngine->GetEditDoc() ); ContentNode* pStartNode = aTmpSel.Min().GetNode(); ContentNode* pEndNode = aTmpSel.Max().GetNode(); sal_Int32 nStartPara = pEditEngine->GetEditDoc().GetPos( pStartNode ); sal_Int32 nEndPara = pEditEngine->GetEditDoc().GetPos( pEndNode ); for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) { ParaPortion* pTmpPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara ); if (!pTmpPortion) { SAL_WARN( "editeng", "Portion in Selection not found!" ); continue; } DBG_ASSERT( !pTmpPortion->IsInvalid(), "Portion in Selection not formatted!" ); if ( !pTmpPortion->IsVisible() || pTmpPortion->IsInvalid() ) continue; long nParaStart = pEditEngine->GetParaPortions().GetYOffset( pTmpPortion ); if ( ( nParaStart + pTmpPortion->GetHeight() ) < GetVisDocTop() ) continue; if ( nParaStart > GetVisDocBottom() ) break; sal_uInt16 nStartLine = 0; sal_uInt16 nEndLine = pTmpPortion->GetLines().Count() -1; if ( nPara == nStartPara ) nStartLine = pTmpPortion->GetLines().FindLine( aTmpSel.Min().GetIndex(), false ); if ( nPara == nEndPara ) nEndLine = pTmpPortion->GetLines().FindLine( aTmpSel.Max().GetIndex(), true ); for ( sal_uInt16 nLine = nStartLine; nLine <= nEndLine; nLine++ ) { const EditLine& rLine = pTmpPortion->GetLines()[nLine]; bool bPartOfLine = false; sal_Int32 nStartIndex = rLine.GetStart(); sal_Int32 nEndIndex = rLine.GetEnd(); if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) && ( nStartIndex != aTmpSel.Min().GetIndex() ) ) { nStartIndex = aTmpSel.Min().GetIndex(); bPartOfLine = true; } if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) && ( nEndIndex != aTmpSel.Max().GetIndex() ) ) { nEndIndex = aTmpSel.Max().GetIndex(); bPartOfLine = true; } // Can happen if at the beginning of a wrapped line. if ( nEndIndex < nStartIndex ) nEndIndex = nStartIndex; tools::Rectangle aTmpRect( pEditEngine->pImpEditEngine->GetEditCursor( pTmpPortion, nStartIndex ) ); Point aTopLeft( aTmpRect.TopLeft() ); Point aBottomRight( aTmpRect.BottomRight() ); aTopLeft.AdjustY(nParaStart ); aBottomRight.AdjustY(nParaStart ); // Only paint if in the visible range ... if ( aTopLeft.Y() > GetVisDocBottom() ) break; if ( aBottomRight.Y() < GetVisDocTop() ) continue; // Now that we have Bidi, the first/last index doesn't have to be the 'most outside' position if ( !bPartOfLine ) { Range aLineXPosStartEnd = pEditEngine->GetLineXPosStartEnd(pTmpPortion, &rLine); aTopLeft.setX( aLineXPosStartEnd.Min() ); aBottomRight.setX( aLineXPosStartEnd.Max() ); ImplDrawHighlightRect( pTarget, aTopLeft, aBottomRight, pPolyPoly.get() ); } else { sal_Int32 nTmpStartIndex = nStartIndex; sal_Int32 nWritingDirStart, nTmpEndIndex; while ( nTmpStartIndex < nEndIndex ) { pEditEngine->pImpEditEngine->GetRightToLeft( nPara, nTmpStartIndex+1, &nWritingDirStart, &nTmpEndIndex ); if ( nTmpEndIndex > nEndIndex ) nTmpEndIndex = nEndIndex; DBG_ASSERT( nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?" ); long nX1 = pEditEngine->GetXPos(pTmpPortion, &rLine, nTmpStartIndex, true); long nX2 = pEditEngine->GetXPos(pTmpPortion, &rLine, nTmpEndIndex); Point aPt1( std::min( nX1, nX2 ), aTopLeft.Y() ); Point aPt2( std::max( nX1, nX2 ), aBottomRight.Y() ); ImplDrawHighlightRect( pTarget, aPt1, aPt2, pPolyPoly.get() ); nTmpStartIndex = nTmpEndIndex; } } } } if ( pRegion ) { *pRegion = vcl::Region( *pPolyPoly ); if (comphelper::LibreOfficeKit::isActive() && mpViewShell && !pOldRegion && pOutWin) { pOutWin->Push(PushFlags::MAPMODE); if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) { // Find the parent that is not right // on top of us to use its offset. vcl::Window* parent = pOutWin->GetParent(); while (parent && parent->GetOutOffXPixel() == pOutWin->GetOutOffXPixel() && parent->GetOutOffYPixel() == pOutWin->GetOutOffYPixel()) { parent = parent->GetParent(); } if (parent) { lcl_translateTwips(*parent, *pOutWin); } } bool bMm100ToTwip = pOutWin->GetMapMode().GetMapUnit() == MapUnit::Map100thMM; Point aOrigin; if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip) // Writer comments: they use editeng, but are separate widgets. aOrigin = pOutWin->GetMapMode().GetOrigin(); OString sRectangle; // If we are not in selection mode, then the exported own selection should be empty. // This is needed always in Online, regardless whether in "selection mode" (whatever // that is) or not, for tdf#125568, but I don't have the clout to make this completely // unconditional also for desktop LO. if (comphelper::LibreOfficeKit::isActive() || pEditEngine->pImpEditEngine->IsInSelectionMode() || mpOtherShell) { std::vector aRectangles; pRegion->GetRegionRectangles(aRectangles); if (pOutWin->IsChart()) { const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) { Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); for (tools::Rectangle& rRect : aRectangles) rRect.Move(aLogicOffset.getX(), aLogicOffset.getY()); } } if (!aRectangles.empty()) { tools::Rectangle& rStart = aRectangles.front(); tools::Rectangle aStart(rStart.Left(), rStart.Top(), rStart.Left() + 1, rStart.Bottom()); if (bMm100ToTwip) aStart = OutputDevice::LogicToLogic(aStart, MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); aStart.Move(aOrigin.getX(), aOrigin.getY()); mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, aStart.toString().getStr()); tools::Rectangle& rEnd = aRectangles.back(); tools::Rectangle aEnd(rEnd.Right() - 1, rEnd.Top(), rEnd.Right(), rEnd.Bottom()); if (bMm100ToTwip) aEnd = OutputDevice::LogicToLogic(aEnd, MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); aEnd.Move(aOrigin.getX(), aOrigin.getY()); mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, aEnd.toString().getStr()); } std::vector v; for (tools::Rectangle & rRectangle : aRectangles) { if (bMm100ToTwip) rRectangle = OutputDevice::LogicToLogic(rRectangle, MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); rRectangle.Move(aOrigin.getX(), aOrigin.getY()); v.emplace_back(rRectangle.toString().getStr()); } sRectangle = comphelper::string::join("; ", v); } if (mpOtherShell) { // Another shell wants to know about our existing selection. if (mpViewShell != mpOtherShell) mpViewShell->NotifyOtherView(mpOtherShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRectangle); } else { mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangle.getStr()); mpViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", sRectangle); } pOutWin->Pop(); } pPolyPoly.reset(); } else { if (pOutWin && pOutWin->GetCursor()) pOutWin->GetCursor()->Show(); if ( bClipRegion ) pTarget->SetClipRegion( aOldRegion ); else pTarget->SetClipRegion(); } } void ImpEditView::GetSelectionRectangles(EditSelection aTmpSel, std::vector& rLogicRects) { vcl::Region aRegion; DrawSelectionXOR(aTmpSel, &aRegion); aRegion.GetRegionRectangles(rLogicRects); } void ImpEditView::ImplDrawHighlightRect( OutputDevice* _pTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly ) { if ( rDocPosTopLeft.X() != rDocPosBottomRight.X() ) { bool bPixelMode = _pTarget->GetMapMode().GetMapUnit() == MapUnit::MapPixel; Point aPnt1( GetWindowPos( rDocPosTopLeft ) ); Point aPnt2( GetWindowPos( rDocPosBottomRight ) ); if ( !IsVertical() ) { lcl_AllignToPixel( aPnt1, _pTarget, +1, 0 ); lcl_AllignToPixel( aPnt2, _pTarget, 0, ( bPixelMode ? 0 : -1 ) ); } else { lcl_AllignToPixel( aPnt1, _pTarget, 0, +1 ); lcl_AllignToPixel( aPnt2, _pTarget, ( bPixelMode ? 0 : +1 ), 0 ); } tools::Rectangle aRect( aPnt1, aPnt2 ); if ( pPolyPoly ) { tools::Polygon aTmpPoly( 4 ); aTmpPoly[0] = aRect.TopLeft(); aTmpPoly[1] = aRect.TopRight(); aTmpPoly[2] = aRect.BottomRight(); aTmpPoly[3] = aRect.BottomLeft(); pPolyPoly->Insert( aTmpPoly ); } else { vcl::Window* pWindow = dynamic_cast< vcl::Window* >(_pTarget); if(pWindow) { pWindow->Invert( aRect ); } else { _pTarget->Push(PushFlags::LINECOLOR|PushFlags::FILLCOLOR|PushFlags::RASTEROP); _pTarget->SetLineColor(); _pTarget->SetFillColor(COL_BLACK); _pTarget->SetRasterOp(RasterOp::Invert); _pTarget->DrawRect(aRect); _pTarget->Pop(); } } } } bool ImpEditView::IsVertical() const { return pEditEngine->pImpEditEngine->IsVertical(); } bool ImpEditView::IsTopToBottom() const { return pEditEngine->pImpEditEngine->IsTopToBottom(); } tools::Rectangle ImpEditView::GetVisDocArea() const { return tools::Rectangle( GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom() ); } Point ImpEditView::GetDocPos( const Point& rWindowPos ) const { // Window Position => Position Document Point aPoint; if ( !pEditEngine->pImpEditEngine->IsVertical() ) { aPoint.setX( rWindowPos.X() - aOutArea.Left() + GetVisDocLeft() ); aPoint.setY( rWindowPos.Y() - aOutArea.Top() + GetVisDocTop() ); } else { if (pEditEngine->pImpEditEngine->IsTopToBottom()) { aPoint.setX( rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft() ); aPoint.setY( aOutArea.Right() - rWindowPos.X() + GetVisDocTop() ); } else { aPoint.setX( aOutArea.Bottom() - rWindowPos.Y() + GetVisDocLeft() ); aPoint.setY( rWindowPos.X() - aOutArea.Left() + GetVisDocTop() ); } } return aPoint; } Point ImpEditView::GetWindowPos( const Point& rDocPos ) const { // Document position => window position Point aPoint; if ( !pEditEngine->pImpEditEngine->IsVertical() ) { aPoint.setX( rDocPos.X() + aOutArea.Left() - GetVisDocLeft() ); aPoint.setY( rDocPos.Y() + aOutArea.Top() - GetVisDocTop() ); } else { if (pEditEngine->pImpEditEngine->IsTopToBottom()) { aPoint.setX( aOutArea.Right() - rDocPos.Y() + GetVisDocTop() ); aPoint.setY( rDocPos.X() + aOutArea.Top() - GetVisDocLeft() ); } else { aPoint.setX( aOutArea.Left() + rDocPos.Y() - GetVisDocTop() ); aPoint.setY( aOutArea.Bottom() - rDocPos.X() + GetVisDocLeft() ); } } return aPoint; } tools::Rectangle ImpEditView::GetWindowPos( const tools::Rectangle& rDocRect ) const { // Document position => window position Point aPos( GetWindowPos( rDocRect.TopLeft() ) ); Size aSz = rDocRect.GetSize(); tools::Rectangle aRect; if ( !pEditEngine->pImpEditEngine->IsVertical() ) { aRect = tools::Rectangle( aPos, aSz ); } else { Point aNewPos( aPos.X()-aSz.Height(), aPos.Y() ); aRect = tools::Rectangle( aNewPos, Size( aSz.Height(), aSz.Width() ) ); } return aRect; } void ImpEditView::SetSelectionMode( EESelectionMode eNewMode ) { if ( eSelectionMode != eNewMode ) { DrawSelectionXOR(); eSelectionMode = eNewMode; DrawSelectionXOR(); // redraw } } void ImpEditView::SetOutputArea( const tools::Rectangle& rRect ) { const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *pOutWin; // should be better be aligned on pixels! tools::Rectangle aNewRect(rOutDev.LogicToPixel(rRect)); aNewRect = rOutDev.PixelToLogic(aNewRect); aOutArea = aNewRect; if ( !aOutArea.IsWidthEmpty() && aOutArea.Right() < aOutArea.Left() ) aOutArea.SetRight( aOutArea.Left() ); if ( !aOutArea.IsHeightEmpty() && aOutArea.Bottom() < aOutArea.Top() ) aOutArea.SetBottom( aOutArea.Top() ); SetScrollDiffX( static_cast(aOutArea.GetWidth()) * 2 / 10 ); } void ImpEditView::InvalidateAtWindow(const tools::Rectangle& rRect) { if (getEditViewCallbacks()) { // do not invalidate and trigger a global repaint, but forward // the need for change to the applied EditViewCallback, can e.g. // be used to visualize the active edit text in an OverlayObject getEditViewCallbacks()->EditViewInvalidate(rRect); } else { // classic mode: invalidate and trigger full repaint // of the changed area GetWindow()->Invalidate(rRect); } } void ImpEditView::ResetOutputArea( const tools::Rectangle& rRect ) { // remember old out area const tools::Rectangle aOldArea(aOutArea); // apply new one SetOutputArea(rRect); // invalidate surrounding areas if update is true if(!aOldArea.IsEmpty() && pEditEngine->pImpEditEngine->GetUpdateMode()) { // #i119885# use grown area if needed; do when getting bigger OR smaller const sal_Int32 nMore(DoInvalidateMore() ? GetWindow()->PixelToLogic(Size(nInvMore, 0)).Width() : 0); if(aOldArea.Left() > aOutArea.Left()) { const tools::Rectangle aRect(aOutArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Left(), aOldArea.Bottom() + nMore); InvalidateAtWindow(aRect); } else if(aOldArea.Left() < aOutArea.Left()) { const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOutArea.Left(), aOldArea.Bottom() + nMore); InvalidateAtWindow(aRect); } if(aOldArea.Right() > aOutArea.Right()) { const tools::Rectangle aRect(aOutArea.Right(), aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); InvalidateAtWindow(aRect); } else if(aOldArea.Right() < aOutArea.Right()) { const tools::Rectangle aRect(aOldArea.Right(), aOldArea.Top() - nMore, aOutArea.Right() + nMore, aOldArea.Bottom() + nMore); InvalidateAtWindow(aRect); } if(aOldArea.Top() > aOutArea.Top()) { const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Top()); InvalidateAtWindow(aRect); } else if(aOldArea.Top() < aOutArea.Top()) { const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOutArea.Top()); InvalidateAtWindow(aRect); } if(aOldArea.Bottom() > aOutArea.Bottom()) { const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Bottom(), aOldArea.Right() + nMore, aOldArea.Bottom() + nMore); InvalidateAtWindow(aRect); } else if(aOldArea.Bottom() < aOutArea.Bottom()) { const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Bottom(), aOldArea.Right() + nMore, aOutArea.Bottom() + nMore); InvalidateAtWindow(aRect); } } } void ImpEditView::RecalcOutputArea() { Point aNewTopLeft( aOutArea.TopLeft() ); Size aNewSz( aOutArea.GetSize() ); // X: if ( DoAutoWidth() ) { if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageWidth() ) aNewSz.setWidth( pEditEngine->pImpEditEngine->GetPaperSize().Width() ); switch ( eAnchorMode ) { case EEAnchorMode::TopLeft: case EEAnchorMode::VCenterLeft: case EEAnchorMode::BottomLeft: { aNewTopLeft.setX( aAnchorPoint.X() ); } break; case EEAnchorMode::TopHCenter: case EEAnchorMode::VCenterHCenter: case EEAnchorMode::BottomHCenter: { aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() / 2 ); } break; case EEAnchorMode::TopRight: case EEAnchorMode::VCenterRight: case EEAnchorMode::BottomRight: { aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() - 1 ); } break; } } // Y: if ( DoAutoHeight() ) { if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageHeight() ) aNewSz.setHeight( pEditEngine->pImpEditEngine->GetPaperSize().Height() ); switch ( eAnchorMode ) { case EEAnchorMode::TopLeft: case EEAnchorMode::TopHCenter: case EEAnchorMode::TopRight: { aNewTopLeft.setY( aAnchorPoint.Y() ); } break; case EEAnchorMode::VCenterLeft: case EEAnchorMode::VCenterHCenter: case EEAnchorMode::VCenterRight: { aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() / 2 ); } break; case EEAnchorMode::BottomLeft: case EEAnchorMode::BottomHCenter: case EEAnchorMode::BottomRight: { aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() - 1 ); } break; } } ResetOutputArea( tools::Rectangle( aNewTopLeft, aNewSz ) ); } void ImpEditView::SetAnchorMode( EEAnchorMode eMode ) { eAnchorMode = eMode; CalcAnchorPoint(); } void ImpEditView::CalcAnchorPoint() { // GetHeight() and GetWidth() -1, because rectangle calculation not preferred. // X: switch ( eAnchorMode ) { case EEAnchorMode::TopLeft: case EEAnchorMode::VCenterLeft: case EEAnchorMode::BottomLeft: { aAnchorPoint.setX( aOutArea.Left() ); } break; case EEAnchorMode::TopHCenter: case EEAnchorMode::VCenterHCenter: case EEAnchorMode::BottomHCenter: { aAnchorPoint.setX( aOutArea.Left() + (aOutArea.GetWidth()-1) / 2 ); } break; case EEAnchorMode::TopRight: case EEAnchorMode::VCenterRight: case EEAnchorMode::BottomRight: { aAnchorPoint.setX( aOutArea.Right() ); } break; } // Y: switch ( eAnchorMode ) { case EEAnchorMode::TopLeft: case EEAnchorMode::TopHCenter: case EEAnchorMode::TopRight: { aAnchorPoint.setY( aOutArea.Top() ); } break; case EEAnchorMode::VCenterLeft: case EEAnchorMode::VCenterHCenter: case EEAnchorMode::VCenterRight: { aAnchorPoint.setY( aOutArea.Top() + (aOutArea.GetHeight()-1) / 2 ); } break; case EEAnchorMode::BottomLeft: case EEAnchorMode::BottomHCenter: case EEAnchorMode::BottomRight: { aAnchorPoint.setY( aOutArea.Bottom() - 1 ); } break; } } void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) { // No ShowCursor in an empty View ... if (aOutArea.IsEmpty()) return; if ( ( aOutArea.Left() >= aOutArea.Right() ) && ( aOutArea.Top() >= aOutArea.Bottom() ) ) return; pEditEngine->CheckIdleFormatter(); if (!pEditEngine->IsFormatted()) pEditEngine->pImpEditEngine->FormatDoc(); // For some reasons I end up here during the formatting, if the Outliner // is initialized in Paint, because no SetPool(); if ( pEditEngine->pImpEditEngine->IsFormatting() ) return; if ( !pEditEngine->pImpEditEngine->GetUpdateMode() ) return; if ( pEditEngine->pImpEditEngine->IsInUndo() ) return; if (pOutWin && pOutWin->GetCursor() != GetCursor()) pOutWin->SetCursor(GetCursor()); EditPaM aPaM( aEditSelection.Max() ); sal_Int32 nTextPortionStart = 0; sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); if (nPara == EE_PARA_NOT_FOUND) // #i94322 return; const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly; // Use CursorBidiLevel 0/1 in meaning of // 0: prefer portion end, normal mode // 1: prefer portion start if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() ) { nShowCursorFlags |= GetCursorFlags::PreferPortionStart; } tools::Rectangle aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, nShowCursorFlags ); if ( !IsInsertMode() && !aEditSelection.HasRange() ) { if ( aPaM.GetNode()->Len() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) ) { // If we are behind a portion, and the next portion has other direction, we must change position... aEditCursor.SetLeft( pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly|GetCursorFlags::PreferPortionStart ).Left() ); aEditCursor.SetRight( aEditCursor.Left() ); sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true ); const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; if ( rTextPortion.GetKind() == PortionKind::TAB ) { aEditCursor.AdjustRight(rTextPortion.GetSize().Width() ); } else { EditPaM aNext = pEditEngine->CursorRight( aPaM ); tools::Rectangle aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly ); if ( aTmpRect.Top() != aEditCursor.Top() ) aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine ); aEditCursor.SetRight( aTmpRect.Left() ); } } } long nMaxHeight = !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth(); if ( aEditCursor.GetHeight() > nMaxHeight ) { aEditCursor.SetBottom( aEditCursor.Top() + nMaxHeight - 1 ); } if ( bGotoCursor ) // && (!pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) ) { // check if scrolling is necessary... // if scrolling, then update () and Scroll ()! long nDocDiffX = 0; long nDocDiffY = 0; tools::Rectangle aTmpVisArea( GetVisDocArea() ); // aTmpOutArea: if OutputArea > Paper width and // Text > Paper width ( over large fields ) long nMaxTextWidth = !IsVertical() ? pEditEngine->pImpEditEngine->GetPaperSize().Width() : pEditEngine->pImpEditEngine->GetPaperSize().Height(); if ( aTmpVisArea.GetWidth() > nMaxTextWidth ) aTmpVisArea.SetRight( aTmpVisArea.Left() + nMaxTextWidth ); if ( aEditCursor.Bottom() > aTmpVisArea.Bottom() ) { // Scroll up, here positive nDocDiffY = aEditCursor.Bottom() - aTmpVisArea.Bottom(); } else if ( aEditCursor.Top() < aTmpVisArea.Top() ) { // Scroll down, here negative nDocDiffY = aEditCursor.Top() - aTmpVisArea.Top(); } if ( aEditCursor.Right() > aTmpVisArea.Right() ) { // Scroll left, positive nDocDiffX = aEditCursor.Right() - aTmpVisArea.Right(); // Can it be a little more? if ( aEditCursor.Right() < ( nMaxTextWidth - GetScrollDiffX() ) ) nDocDiffX += GetScrollDiffX(); else { long n = nMaxTextWidth - aEditCursor.Right(); // If MapMode != RefMapMode then the EditCursor can go beyond // the paper width! nDocDiffX += ( n > 0 ? n : -n ); } } else if ( aEditCursor.Left() < aTmpVisArea.Left() ) { // Scroll right, negative: nDocDiffX = aEditCursor.Left() - aTmpVisArea.Left(); // Can it be a little more? if ( aEditCursor.Left() > ( - static_cast(GetScrollDiffX()) ) ) nDocDiffX -= GetScrollDiffX(); else nDocDiffX -= aEditCursor.Left(); } if ( aPaM.GetIndex() == 0 ) // Olli needed for the Outliner { // But make sure that the cursor is not leaving visible area // because of this! if ( aEditCursor.Left() < aTmpVisArea.GetWidth() ) { nDocDiffX = -aTmpVisArea.Left(); } } if ( nDocDiffX | nDocDiffY ) { long nDiffX = !IsVertical() ? nDocDiffX : (IsTopToBottom() ? -nDocDiffY : nDocDiffY); long nDiffY = !IsVertical() ? nDocDiffY : (IsTopToBottom() ? nDocDiffX : -nDocDiffX); if ( nDiffX ) pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::HSCROLL; if ( nDiffY ) pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::VSCROLL; Scroll( -nDiffX, -nDiffY ); pEditEngine->pImpEditEngine->DelayedCallStatusHdl(); } } // Cursor may trim a little ... if ( ( aEditCursor.Bottom() > GetVisDocTop() ) && ( aEditCursor.Top() < GetVisDocBottom() ) ) { if ( aEditCursor.Bottom() > GetVisDocBottom() ) aEditCursor.SetBottom( GetVisDocBottom() ); if ( aEditCursor.Top() < GetVisDocTop() ) aEditCursor.SetTop( GetVisDocTop() ); } const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *pOutWin; long nOnePixel = rOutDev.PixelToLogic( Size( 1, 0 ) ).Width(); if ( ( aEditCursor.Top() + nOnePixel >= GetVisDocTop() ) && ( aEditCursor.Bottom() - nOnePixel <= GetVisDocBottom() ) && ( aEditCursor.Left() + nOnePixel >= GetVisDocLeft() ) && ( aEditCursor.Right() - nOnePixel <= GetVisDocRight() ) ) { tools::Rectangle aCursorRect = GetWindowPos( aEditCursor ); GetCursor()->SetPos( aCursorRect.TopLeft() ); Size aCursorSz( aCursorRect.GetSize() ); // Rectangle is inclusive aCursorSz.AdjustWidth( -1 ); aCursorSz.AdjustHeight( -1 ); if ( !aCursorSz.Width() || !aCursorSz.Height() ) { long nCursorSz = rOutDev.GetSettings().GetStyleSettings().GetCursorSize(); nCursorSz = rOutDev.PixelToLogic( Size( nCursorSz, 0 ) ).Width(); if ( !aCursorSz.Width() ) aCursorSz.setWidth( nCursorSz ); if ( !aCursorSz.Height() ) aCursorSz.setHeight( nCursorSz ); } // #111036# Let VCL do orientation for cursor, otherwise problem when cursor has direction flag if ( IsVertical() ) { Size aOldSz( aCursorSz ); aCursorSz.setWidth( aOldSz.Height() ); aCursorSz.setHeight( aOldSz.Width() ); GetCursor()->SetPos( aCursorRect.TopRight() ); GetCursor()->SetOrientation( IsTopToBottom() ? 2700 : 900 ); } else // #i32593# Reset correct orientation in horizontal layout GetCursor()->SetOrientation(); GetCursor()->SetSize( aCursorSz ); if (comphelper::LibreOfficeKit::isActive() && mpViewShell) { Point aPos = GetCursor()->GetPos(); if (pOutWin && pOutWin->IsChart()) { const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj(); if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin)) { Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow); Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx); aPos.Move(aLogicOffset.getX(), aLogicOffset.getY()); } } tools::Rectangle aRect(aPos.getX(), aPos.getY(), aPos.getX() + GetCursor()->GetWidth(), aPos.getY() + GetCursor()->GetHeight()); // LOK output is always in twips, convert from mm100 if necessary. if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::Map100thMM) aRect = OutputDevice::LogicToLogic(aRect, MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); else if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::MapTwip) { // Writer comments: they use editeng, but are separate widgets. Point aOrigin = rOutDev.GetMapMode().GetOrigin(); // Move the rectangle, so that we output absolute twips. aRect.Move(aOrigin.getX(), aOrigin.getY()); } // Let the LOK client decide the cursor width. aRect.setWidth(0); OString sRect = aRect.toString(); if (mpOtherShell) { // Another shell wants to know about our existing cursor. if (mpViewShell != mpOtherShell) mpViewShell->NotifyOtherView(mpOtherShell, LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); } else { SfxLokHelper::notifyVisCursorInvalidation(mpViewShell, sRect); mpViewShell->NotifyOtherViews(LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); } } CursorDirection nCursorDir = CursorDirection::NONE; if ( IsInsertMode() && !aEditSelection.HasRange() && ( pEditEngine->pImpEditEngine->HasDifferentRTLLevels( aPaM.GetNode() ) ) ) { sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, bool(nShowCursorFlags & GetCursorFlags::PreferPortionStart) ); const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion]; if (rTextPortion.IsRightToLeft()) nCursorDir = CursorDirection::RTL; else nCursorDir = CursorDirection::LTR; } GetCursor()->SetDirection( nCursorDir ); if ( bForceVisCursor ) GetCursor()->Show(); { SvxFont aFont; pEditEngine->SeekCursor( aPaM.GetNode(), aPaM.GetIndex()+1, aFont ); if (vcl::Window* pWindow = GetWindow()) { InputContextFlags const nContextFlags = InputContextFlags::Text | InputContextFlags::ExtText; pWindow->SetInputContext( InputContext( aFont, nContextFlags ) ); } } } else { pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() = pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() | EditStatusFlags::CURSOROUT; GetCursor()->Hide(); GetCursor()->SetPos( Point( -1, -1 ) ); GetCursor()->SetSize( Size( 0, 0 ) ); } } Pair ImpEditView::Scroll( long ndX, long ndY, ScrollRangeCheck nRangeCheck ) { DBG_ASSERT( pEditEngine->pImpEditEngine->IsFormatted(), "Scroll: Not formatted!" ); if ( !ndX && !ndY ) return Pair( 0, 0 ); const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); #ifdef DBG_UTIL tools::Rectangle aR( aOutArea ); aR = rOutDev.LogicToPixel( aR ); aR = rOutDev.PixelToLogic( aR ); SAL_WARN_IF( aR != aOutArea, "editeng", "OutArea before Scroll not aligned" ); #endif tools::Rectangle aNewVisArea( GetVisDocArea() ); // Vertical: if ( !IsVertical() ) { aNewVisArea.AdjustTop( -ndY ); aNewVisArea.AdjustBottom( -ndY ); } else { if( IsTopToBottom() ) { aNewVisArea.AdjustTop(ndX ); aNewVisArea.AdjustBottom(ndX ); } else { aNewVisArea.AdjustTop( -ndX ); aNewVisArea.AdjustBottom( -ndX ); } } if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Bottom() > static_cast(pEditEngine->pImpEditEngine->GetTextHeight()) ) ) { // GetTextHeight still optimizing! long nDiff = pEditEngine->pImpEditEngine->GetTextHeight() - aNewVisArea.Bottom(); // negative aNewVisArea.Move( 0, nDiff ); // could end up in the negative area... } if ( aNewVisArea.Top() < 0 ) aNewVisArea.Move( 0, -aNewVisArea.Top() ); // Horizontal: if ( !IsVertical() ) { aNewVisArea.AdjustLeft( -ndX ); aNewVisArea.AdjustRight( -ndX ); } else { if (IsTopToBottom()) { aNewVisArea.AdjustLeft( -ndY ); aNewVisArea.AdjustRight( -ndY ); } else { aNewVisArea.AdjustLeft(ndY ); aNewVisArea.AdjustRight(ndY ); } } if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Right() > static_cast(pEditEngine->pImpEditEngine->CalcTextWidth( false )) ) ) { long nDiff = pEditEngine->pImpEditEngine->CalcTextWidth( false ) - aNewVisArea.Right(); // negative aNewVisArea.Move( nDiff, 0 ); // could end up in the negative area... } if ( aNewVisArea.Left() < 0 ) aNewVisArea.Move( -aNewVisArea.Left(), 0 ); // The difference must be alignt on pixel (due to scroll!) long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : (IsTopToBottom() ? -( GetVisDocTop() - aNewVisArea.Top() ) : (GetVisDocTop() - aNewVisArea.Top())); long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : (IsTopToBottom() ? (GetVisDocLeft() - aNewVisArea.Left()) : -(GetVisDocTop() - aNewVisArea.Top())); Size aDiffs( nDiffX, nDiffY ); aDiffs = rOutDev.LogicToPixel( aDiffs ); aDiffs = rOutDev.PixelToLogic( aDiffs ); long nRealDiffX = aDiffs.Width(); long nRealDiffY = aDiffs.Height(); if ( nRealDiffX || nRealDiffY ) { vcl::Cursor* pCrsr = GetCursor(); bool bVisCursor = pCrsr->IsVisible(); pCrsr->Hide(); if (pOutWin) pOutWin->Update(); if ( !IsVertical() ) aVisDocStartPos.Move( -nRealDiffX, -nRealDiffY ); else { if (IsTopToBottom()) aVisDocStartPos.Move(-nRealDiffY, nRealDiffX); else aVisDocStartPos.Move(nRealDiffY, -nRealDiffX); } // Move by aligned value does not necessarily result in aligned // rectangle ... aVisDocStartPos = rOutDev.LogicToPixel( aVisDocStartPos ); aVisDocStartPos = rOutDev.PixelToLogic( aVisDocStartPos ); tools::Rectangle aRect( aOutArea ); if (pOutWin) { pOutWin->Scroll( nRealDiffX, nRealDiffY, aRect, ScrollFlags::Clip ); } if (comphelper::LibreOfficeKit::isActive() || getEditViewCallbacks()) { // Need to invalidate the window, otherwise no tile will be re-painted. pEditView->Invalidate(); } if (pOutWin) pOutWin->Update(); pCrsr->SetPos( pCrsr->GetPos() + Point( nRealDiffX, nRealDiffY ) ); if ( bVisCursor ) { tools::Rectangle aCursorRect( pCrsr->GetPos(), pCrsr->GetSize() ); if ( aOutArea.IsInside( aCursorRect ) ) pCrsr->Show(); } if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() ) { EENotify aNotify( EE_NOTIFY_TEXTVIEWSCROLLED ); pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify ); } } return Pair( nRealDiffX, nRealDiffY ); } Reference ImpEditView::GetClipboard() const { if (vcl::Window* pWindow = GetWindow()) return pWindow->GetClipboard(); return GetSystemClipboard(); } Reference ImpEditView::GetSelection() const { if (vcl::Window* pWindow = GetWindow()) return pWindow->GetPrimarySelection(); return GetSystemPrimarySelection(); } bool ImpEditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin ) { bool bDone = false; KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); if ( eFunc != KeyFuncType::DONTKNOW ) { switch ( eFunc ) { case KeyFuncType::CUT: { if ( !bReadOnly ) { Reference aClipBoard(GetClipboard()); CutCopy( aClipBoard, true ); bDone = true; } } break; case KeyFuncType::COPY: { Reference aClipBoard(GetClipboard()); CutCopy( aClipBoard, false ); bDone = true; } break; case KeyFuncType::PASTE: { if ( !bReadOnly && IsPasteEnabled() ) { pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); Reference aClipBoard(GetClipboard()); Paste( aClipBoard, pEditEngine->pImpEditEngine->GetStatus().AllowPasteSpecial() ); pEditEngine->pImpEditEngine->UndoActionEnd(); bDone = true; } } break; default: break; } } if( !bDone ) bDone = pEditEngine->PostKeyEvent( rKeyEvent, GetEditViewPtr(), pFrameWin ); return bDone; } bool ImpEditView::MouseButtonUp( const MouseEvent& rMouseEvent ) { nTravelXPos = TRAVEL_X_DONTKNOW; nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; nExtraCursorFlags = GetCursorFlags::NONE; bClickedInSelection = false; if (vcl::Window* pWindow = GetWindow()) { if ( rMouseEvent.IsMiddle() && !bReadOnly && ( pWindow->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) ) { Reference aClipBoard(GetSelection()); Paste( aClipBoard ); } else if ( rMouseEvent.IsLeft() && GetEditSelection().HasRange() ) { Reference aClipBoard(GetSelection()); CutCopy( aClipBoard, false ); } } return pEditEngine->pImpEditEngine->MouseButtonUp( rMouseEvent, GetEditViewPtr() ); } void ImpEditView::ReleaseMouse() { pEditEngine->pImpEditEngine->ReleaseMouse(); } bool ImpEditView::MouseButtonDown( const MouseEvent& rMouseEvent ) { pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button downs nTravelXPos = TRAVEL_X_DONTKNOW; nExtraCursorFlags = GetCursorFlags::NONE; nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW; bClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() ); return pEditEngine->pImpEditEngine->MouseButtonDown( rMouseEvent, GetEditViewPtr() ); } bool ImpEditView::MouseMove( const MouseEvent& rMouseEvent ) { return pEditEngine->pImpEditEngine->MouseMove( rMouseEvent, GetEditViewPtr() ); } void ImpEditView::Command( const CommandEvent& rCEvt ) { pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button down pEditEngine->pImpEditEngine->Command( rCEvt, GetEditViewPtr() ); } void ImpEditView::SetInsertMode( bool bInsert ) { if ( bInsert != IsInsertMode() ) { SetFlags( nControl, EVControlBits::OVERWRITE, !bInsert ); ShowCursor( DoAutoScroll(), false ); } } bool ImpEditView::IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong ) { bool bIsWrong = false; if ( rPaM.GetNode()->GetWrongList() ) { EditSelection aSel = pEditEngine->SelectWord( rPaM, css::i18n::WordType::DICTIONARY_WORD ); bIsWrong = rPaM.GetNode()->GetWrongList()->HasWrong( aSel.Min().GetIndex(), aSel.Max().GetIndex() ); if ( bIsWrong && bMarkIfWrong ) { DrawSelectionXOR(); SetEditSelection( aSel ); DrawSelectionXOR(); } } return bIsWrong; } OUString ImpEditView::SpellIgnoreWord() { OUString aWord; if ( pEditEngine->pImpEditEngine->GetSpeller().is() ) { EditPaM aPaM = GetEditSelection().Max(); if ( !HasSelection() ) { EditSelection aSel = pEditEngine->SelectWord(aPaM); aWord = pEditEngine->pImpEditEngine->GetSelected( aSel ); } else { aWord = pEditEngine->pImpEditEngine->GetSelected( GetEditSelection() ); // And deselect DrawSelectionXOR(); SetEditSelection( EditSelection( aPaM, aPaM ) ); DrawSelectionXOR(); } if ( !aWord.isEmpty() ) { Reference< XDictionary > xDic = LinguMgr::GetIgnoreAllList(); if (xDic.is()) xDic->add( aWord, false, OUString() ); EditDoc& rDoc = pEditEngine->GetEditDoc(); sal_Int32 nNodes = rDoc.Count(); for ( sal_Int32 n = 0; n < nNodes; n++ ) { ContentNode* pNode = rDoc.GetObject( n ); pNode->GetWrongList()->MarkWrongsInvalid(); } pEditEngine->pImpEditEngine->DoOnlineSpelling( aPaM.GetNode() ); pEditEngine->pImpEditEngine->StartOnlineSpellTimer(); } } return aWord; } void ImpEditView::DeleteSelected() { DrawSelectionXOR(); pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DELETE ); EditPaM aPaM = pEditEngine->pImpEditEngine->DeleteSelected( GetEditSelection() ); pEditEngine->pImpEditEngine->UndoActionEnd(); SetEditSelection( EditSelection( aPaM, aPaM ) ); DrawSelectionXOR(); pEditEngine->pImpEditEngine->FormatAndUpdate( GetEditViewPtr() ); ShowCursor( DoAutoScroll(), true ); } const SvxFieldItem* ImpEditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const { if( !GetOutputArea().IsInside( rPos ) ) return nullptr; Point aDocPos( GetDocPos( rPos ) ); EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); if ( aPaM.GetIndex() == aPaM.GetNode()->Len() ) { // Otherwise, whenever the Field at the very end and mouse under the text return nullptr; } const CharAttribList::AttribsType& rAttrs = aPaM.GetNode()->GetCharAttribs().GetAttribs(); const sal_Int32 nXPos = aPaM.GetIndex(); for (size_t nAttr = rAttrs.size(); nAttr; ) { const EditCharAttrib& rAttr = *rAttrs[--nAttr]; if (rAttr.GetStart() == nXPos) { if (rAttr.Which() == EE_FEATURE_FIELD) { DBG_ASSERT(dynamic_cast(rAttr.GetItem()), "No FieldItem..."); if ( pPara ) *pPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); if ( pPos ) *pPos = rAttr.GetStart(); return static_cast(rAttr.GetItem()); } } } return nullptr; } bool ImpEditView::IsBulletArea( const Point& rPos, sal_Int32* pPara ) { if ( pPara ) *pPara = EE_PARA_NOT_FOUND; if( !GetOutputArea().IsInside( rPos ) ) return false; Point aDocPos( GetDocPos( rPos ) ); EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); if ( aPaM.GetIndex() == 0 ) { sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); tools::Rectangle aBulletArea = pEditEngine->GetBulletArea( nPara ); long nY = pEditEngine->GetDocPosTopLeft( nPara ).Y(); const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara]; nY += pParaPortion->GetFirstLineOffset(); if ( ( aDocPos.Y() > ( nY + aBulletArea.Top() ) ) && ( aDocPos.Y() < ( nY + aBulletArea.Bottom() ) ) && ( aDocPos.X() > ( aBulletArea.Left() ) ) && ( aDocPos.X() < ( aBulletArea.Right() ) ) ) { if ( pPara ) *pPara = nPara; return true; } } return false; } void ImpEditView::CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut ) { if ( rxClipboard.is() && HasSelection() ) { uno::Reference xData = pEditEngine->CreateTransferable( GetEditSelection() ); { SolarMutexReleaser aReleaser; try { rxClipboard->setContents( xData, nullptr ); // #87756# FlushClipboard, but it would be better to become a TerminateListener to the Desktop and flush on demand... uno::Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, uno::UNO_QUERY ); if( xFlushableClipboard.is() ) xFlushableClipboard->flushClipboard(); } catch( const css::uno::Exception& ) { } } if (bCut) { pEditEngine->pImpEditEngine->UndoActionStart(EDITUNDO_CUT); DeleteSelected(); pEditEngine->pImpEditEngine->UndoActionEnd(); } } } void ImpEditView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial ) { if ( !rxClipboard.is() ) return; uno::Reference< datatransfer::XTransferable > xDataObj; try { SolarMutexReleaser aReleaser; xDataObj = rxClipboard->getContents(); } catch( const css::uno::Exception& ) { } if ( !xDataObj.is() || !EditEngine::HasValidData( xDataObj ) ) return; pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE ); EditSelection aSel( GetEditSelection() ); if ( aSel.HasRange() ) { DrawSelectionXOR(); aSel = pEditEngine->DeleteSelection(aSel); } PasteOrDropInfos aPasteOrDropInfos; aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); if ( DoSingleLinePaste() ) { datatransfer::DataFlavor aFlavor; SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); if ( xDataObj->isDataFlavorSupported( aFlavor ) ) { try { uno::Any aData = xDataObj->getTransferData( aFlavor ); OUString aTmpText; aData >>= aTmpText; OUString aText(convertLineEnd(aTmpText, LINEEND_LF)); aText = aText.replaceAll( OUStringChar(LINE_SEP), " " ); aSel = pEditEngine->InsertText(aSel, aText); } catch( ... ) { ; // #i9286# can happen, even if isDataFlavorSupported returns true... } } } else { // Prevent notifications of paragraph inserts et al that would trigger // a11y to format content in a half-ready state when obtaining // paragraphs. Collect and broadcast when done instead. aSel = pEditEngine->InsertText( xDataObj, OUString(), aSel.Min(), bUseSpecial && pEditEngine->GetInternalEditStatus().AllowPasteSpecial()); } aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); pEditEngine->pImpEditEngine->UndoActionEnd(); SetEditSelection( aSel ); pEditEngine->pImpEditEngine->UpdateSelections(); pEditEngine->pImpEditEngine->FormatAndUpdate( GetEditViewPtr() ); ShowCursor( DoAutoScroll(), true ); } bool ImpEditView::IsInSelection( const EditPaM& rPaM ) { EditSelection aSel = GetEditSelection(); if ( !aSel.HasRange() ) return false; aSel.Adjust( pEditEngine->GetEditDoc() ); sal_Int32 nStartNode = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() ); sal_Int32 nEndNode = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() ); sal_Int32 nCurNode = pEditEngine->GetEditDoc().GetPos( rPaM.GetNode() ); if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) ) return true; if ( nStartNode == nEndNode ) { if ( nCurNode == nStartNode ) if ( ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) return true; } else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) ) return true; else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) ) return true; return false; } void ImpEditView::CreateAnchor() { pEditEngine->SetInSelectionMode(true); EditSelection aNewSelection(GetEditSelection()); aNewSelection.Min() = aNewSelection.Max(); SetEditSelection(aNewSelection); // const_cast(GetEditSelection().Min()) = GetEditSelection().Max(); } void ImpEditView::DeselectAll() { pEditEngine->SetInSelectionMode(false); DrawSelectionXOR(); EditSelection aNewSelection(GetEditSelection()); aNewSelection.Min() = aNewSelection.Max(); SetEditSelection(aNewSelection); // const_cast(GetEditSelection().Min()) = GetEditSelection().Max(); } bool ImpEditView::IsSelectionAtPoint( const Point& rPosPixel ) { if ( pDragAndDropInfo && pDragAndDropInfo->pField ) return true; // Logical units ... const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); Point aMousePos = rOutDev.PixelToLogic(rPosPixel); if ( ( !GetOutputArea().IsInside( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) { return false; } Point aDocPos( GetDocPos( aMousePos ) ); EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); return IsInSelection( aPaM ); } bool ImpEditView::SetCursorAtPoint( const Point& rPointPixel ) { pEditEngine->CheckIdleFormatter(); Point aMousePos( rPointPixel ); // Logical units ... const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); aMousePos = rOutDev.PixelToLogic( aMousePos ); if ( ( !GetOutputArea().IsInside( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() ) { return false; } Point aDocPos( GetDocPos( aMousePos ) ); // Can be optimized: first go through the lines within a paragraph for PAM, // then again with the PaM for the Rect, even though the line is already // known... This must not be, though! EditPaM aPaM = pEditEngine->GetPaM(aDocPos); bool bGotoCursor = DoAutoScroll(); // aTmpNewSel: Diff between old and new, not the new selection, unless for tiled rendering EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? GetEditSelection().Min() : GetEditSelection().Max(), aPaM ); // #i27299# // work on copy of current selection and set new selection, if it has changed. EditSelection aNewEditSelection( GetEditSelection() ); aNewEditSelection.Max() = aPaM; if (!pEditEngine->GetSelectionEngine().HasAnchor()) { if ( aNewEditSelection.Min() != aPaM ) pEditEngine->CursorMoved(aNewEditSelection.Min().GetNode()); aNewEditSelection.Min() = aPaM; } else { DrawSelectionXOR( aTmpNewSel ); } // set changed text selection if ( GetEditSelection() != aNewEditSelection ) { SetEditSelection( aNewEditSelection ); } bool bForceCursor = pDragAndDropInfo == nullptr && !pEditEngine->pImpEditEngine->IsInSelectionMode(); ShowCursor( bGotoCursor, bForceCursor ); return true; } void ImpEditView::HideDDCursor() { if ( pDragAndDropInfo && pDragAndDropInfo->bVisCursor ) { OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); rOutDev.DrawOutDev( pDragAndDropInfo->aCurSavedCursor.TopLeft(), pDragAndDropInfo->aCurSavedCursor.GetSize(), Point(0,0), pDragAndDropInfo->aCurSavedCursor.GetSize(),*pDragAndDropInfo->pBackground ); pDragAndDropInfo->bVisCursor = false; } } void ImpEditView::ShowDDCursor( const tools::Rectangle& rRect ) { if ( pDragAndDropInfo && !pDragAndDropInfo->bVisCursor ) { if (pOutWin && pOutWin->GetCursor()) pOutWin->GetCursor()->Hide(); OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); Color aOldFillColor = rOutDev.GetFillColor(); rOutDev.SetFillColor( Color(4210752) ); // GRAY BRUSH_50, OLDSV, change to DDCursor! // Save background ... tools::Rectangle aSaveRect( rOutDev.LogicToPixel( rRect ) ); // prefer to save some more ... aSaveRect.AdjustRight(1 ); aSaveRect.AdjustBottom(1 ); #ifdef DBG_UTIL Size aNewSzPx( aSaveRect.GetSize() ); #endif if ( !pDragAndDropInfo->pBackground ) { pDragAndDropInfo->pBackground = VclPtr::Create(rOutDev); MapMode aMapMode( rOutDev.GetMapMode() ); aMapMode.SetOrigin( Point( 0, 0 ) ); pDragAndDropInfo->pBackground->SetMapMode( aMapMode ); } #ifdef DBG_UTIL Size aCurSzPx( pDragAndDropInfo->pBackground->GetOutputSizePixel() ); if ( ( aCurSzPx.Width() < aNewSzPx.Width() ) ||( aCurSzPx.Height() < aNewSzPx.Height() ) ) { bool bDone = pDragAndDropInfo->pBackground->SetOutputSizePixel( aNewSzPx ); DBG_ASSERT( bDone, "Virtual Device broken?" ); } #endif aSaveRect = rOutDev.PixelToLogic( aSaveRect ); pDragAndDropInfo->pBackground->DrawOutDev( Point(0,0), aSaveRect.GetSize(), aSaveRect.TopLeft(), aSaveRect.GetSize(), rOutDev ); pDragAndDropInfo->aCurSavedCursor = aSaveRect; // Draw Cursor... rOutDev.DrawRect( rRect ); pDragAndDropInfo->bVisCursor = true; pDragAndDropInfo->aCurCursor = rRect; rOutDev.SetFillColor( aOldFillColor ); } } void ImpEditView::dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& rDGE) { DBG_ASSERT( !pDragAndDropInfo, "dragGestureRecognized - DragAndDropInfo exist!" ); SolarMutexGuard aVclGuard; pDragAndDropInfo.reset(); Point aMousePosPixel( rDGE.DragOriginX, rDGE.DragOriginY ); EditSelection aCopySel( GetEditSelection() ); aCopySel.Adjust( pEditEngine->GetEditDoc() ); if ( HasSelection() && bClickedInSelection ) { pDragAndDropInfo.reset(new DragAndDropInfo()); } else { // Field?! sal_Int32 nPara; sal_Int32 nPos; Point aMousePos = GetWindow()->PixelToLogic( aMousePosPixel ); const SvxFieldItem* pField = GetField( aMousePos, &nPara, &nPos ); if ( pField ) { pDragAndDropInfo.reset(new DragAndDropInfo()); pDragAndDropInfo->pField = pField; ContentNode* pNode = pEditEngine->GetEditDoc().GetObject( nPara ); aCopySel = EditSelection( EditPaM( pNode, nPos ), EditPaM( pNode, nPos+1 ) ); SetEditSelection(aCopySel); DrawSelectionXOR(); bool bGotoCursor = DoAutoScroll(); ShowCursor( bGotoCursor, /*bForceCursor=*/false ); } else if ( IsBulletArea( aMousePos, &nPara ) ) { pDragAndDropInfo.reset(new DragAndDropInfo()); pDragAndDropInfo->bOutlinerMode = true; EditPaM aStartPaM( pEditEngine->GetEditDoc().GetObject( nPara ), 0 ); EditPaM aEndPaM( aStartPaM ); const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL ); for ( sal_Int32 n = nPara +1; n < pEditEngine->GetEditDoc().Count(); n++ ) { const SfxInt16Item& rL = pEditEngine->GetParaAttrib( n, EE_PARA_OUTLLEVEL ); if ( rL.GetValue() > rLevel.GetValue() ) { aEndPaM.SetNode( pEditEngine->GetEditDoc().GetObject( n ) ); } else { break; } } aEndPaM.SetIndex( aEndPaM.GetNode()->Len() ); SetEditSelection( EditSelection( aStartPaM, aEndPaM ) ); } } if ( pDragAndDropInfo ) { pDragAndDropInfo->bStarterOfDD = true; // Sensitive area to be scrolled. Size aSz( 5, 0 ); aSz = GetWindow()->PixelToLogic( aSz ); pDragAndDropInfo->nSensibleRange = static_cast(aSz.Width()); pDragAndDropInfo->nCursorWidth = static_cast(aSz.Width()) / 2; pDragAndDropInfo->aBeginDragSel = pEditEngine->pImpEditEngine->CreateESel( aCopySel ); uno::Reference xData = pEditEngine->CreateTransferable(aCopySel); sal_Int8 nActions = bReadOnly ? datatransfer::dnd::DNDConstants::ACTION_COPY : datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE; rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, xData, mxDnDListener ); // If Drag&Move in an Engine, then Copy&Del has to be optional! GetCursor()->Hide(); } } void ImpEditView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE ) { SolarMutexGuard aVclGuard; DBG_ASSERT( pDragAndDropInfo, "ImpEditView::dragDropEnd: pDragAndDropInfo is NULL!" ); // #123688# Shouldn't happen, but seems to happen... if ( pDragAndDropInfo ) { if ( !bReadOnly && rDSDE.DropSuccess && !pDragAndDropInfo->bOutlinerMode && ( rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE ) ) { if ( pDragAndDropInfo->bStarterOfDD && pDragAndDropInfo->bDroppedInMe ) { // DropPos: Where was it dropped, irrespective of length. ESelection aDropPos( pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos, pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos ); ESelection aToBeDelSel = pDragAndDropInfo->aBeginDragSel; ESelection aNewSel( pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos, pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos ); bool bBeforeSelection = aDropPos < pDragAndDropInfo->aBeginDragSel; sal_Int32 nParaDiff = pDragAndDropInfo->aBeginDragSel.nEndPara - pDragAndDropInfo->aBeginDragSel.nStartPara; if ( bBeforeSelection ) { // Adjust aToBeDelSel. DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara >= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); aToBeDelSel.nStartPara = aToBeDelSel.nStartPara + nParaDiff; aToBeDelSel.nEndPara = aToBeDelSel.nEndPara + nParaDiff; // To correct the character? if ( aToBeDelSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) { sal_uInt16 nMoreChars; if ( pDragAndDropInfo->aDropSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara ) nMoreChars = pDragAndDropInfo->aDropSel.nEndPos - pDragAndDropInfo->aDropSel.nStartPos; else nMoreChars = pDragAndDropInfo->aDropSel.nEndPos; aToBeDelSel.nStartPos = aToBeDelSel.nStartPos + nMoreChars; if ( aToBeDelSel.nStartPara == aToBeDelSel.nEndPara ) aToBeDelSel.nEndPos = aToBeDelSel.nEndPos + nMoreChars; } } else { // aToBeDelSel is ok, but the selection of the View // has to be adapted, if it was deleted before! DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara <= pDragAndDropInfo->aDropSel.nStartPara, "But not before? "); aNewSel.nStartPara = aNewSel.nStartPara - nParaDiff; aNewSel.nEndPara = aNewSel.nEndPara - nParaDiff; // To correct the character? if ( pDragAndDropInfo->aBeginDragSel.nEndPara == pDragAndDropInfo->aDropSel.nStartPara ) { sal_uInt16 nLessChars; if ( pDragAndDropInfo->aBeginDragSel.nStartPara == pDragAndDropInfo->aBeginDragSel.nEndPara ) nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos - pDragAndDropInfo->aBeginDragSel.nStartPos; else nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos; aNewSel.nStartPos = aNewSel.nStartPos - nLessChars; if ( aNewSel.nStartPara == aNewSel.nEndPara ) aNewSel.nEndPos = aNewSel.nEndPos - nLessChars; } } DrawSelectionXOR(); EditSelection aDelSel( pEditEngine->pImpEditEngine->CreateSel( aToBeDelSel ) ); DBG_ASSERT( !aDelSel.DbgIsBuggy( pEditEngine->GetEditDoc() ), "ToBeDel is buggy!" ); pEditEngine->DeleteSelection(aDelSel); if ( !bBeforeSelection ) { DBG_ASSERT( !pEditEngine->pImpEditEngine->CreateSel( aNewSel ).DbgIsBuggy(pEditEngine->GetEditDoc()), "Bad" ); SetEditSelection( pEditEngine->pImpEditEngine->CreateSel( aNewSel ) ); } pEditEngine->pImpEditEngine->FormatAndUpdate( pEditEngine->pImpEditEngine->GetActiveView() ); DrawSelectionXOR(); } else { // other EditEngine ... if (pEditEngine->HasText()) // #88630# SC is removing the content when switching the task DeleteSelected(); } } if ( pDragAndDropInfo->bUndoAction ) pEditEngine->pImpEditEngine->UndoActionEnd(); HideDDCursor(); ShowCursor( DoAutoScroll(), true ); pDragAndDropInfo.reset(); pEditEngine->GetEndDropHdl().Call(GetEditViewPtr()); } } void ImpEditView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE ) { SolarMutexGuard aVclGuard; DBG_ASSERT( pDragAndDropInfo, "Drop - No Drag&Drop info?!" ); if ( pDragAndDropInfo && pDragAndDropInfo->bDragAccepted ) { pEditEngine->GetBeginDropHdl().Call(GetEditViewPtr()); bool bChanges = false; HideDDCursor(); if ( pDragAndDropInfo->bStarterOfDD ) { pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DRAGANDDROP ); pDragAndDropInfo->bUndoAction = true; } if ( pDragAndDropInfo->bOutlinerMode ) { bChanges = true; GetEditViewPtr()->MoveParagraphs( Range( pDragAndDropInfo->aBeginDragSel.nStartPara, pDragAndDropInfo->aBeginDragSel.nEndPara ), pDragAndDropInfo->nOutlinerDropDest ); } else { uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable; if ( xDataObj.is() ) { bChanges = true; // remove Selection ... DrawSelectionXOR(); EditPaM aPaM( pDragAndDropInfo->aDropDest ); PasteOrDropInfos aPasteOrDropInfos; aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos); EditSelection aNewSel = pEditEngine->InsertText( xDataObj, OUString(), aPaM, pEditEngine->GetInternalEditStatus().AllowPasteSpecial()); aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos); SetEditSelection( aNewSel ); pEditEngine->pImpEditEngine->FormatAndUpdate( pEditEngine->pImpEditEngine->GetActiveView() ); if ( pDragAndDropInfo->bStarterOfDD ) { // Only set if the same engine! pDragAndDropInfo->aDropSel.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); pDragAndDropInfo->aDropSel.nStartPos = aPaM.GetIndex(); pDragAndDropInfo->aDropSel.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() ); pDragAndDropInfo->aDropSel.nEndPos = aNewSel.Max().GetIndex(); pDragAndDropInfo->bDroppedInMe = true; } } } if ( bChanges ) { rDTDE.Context->acceptDrop( rDTDE.DropAction ); } if ( !pDragAndDropInfo->bStarterOfDD ) { pDragAndDropInfo.reset(); } rDTDE.Context->dropComplete( bChanges ); } } void ImpEditView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDEE ) { SolarMutexGuard aVclGuard; if ( !pDragAndDropInfo ) pDragAndDropInfo.reset(new DragAndDropInfo()); pDragAndDropInfo->bHasValidData = false; // Check for supported format... // Only check for text, will also be there if bin or rtf datatransfer::DataFlavor aTextFlavor; SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aTextFlavor ); const css::datatransfer::DataFlavor* pFlavors = rDTDEE.SupportedDataFlavors.getConstArray(); int nFlavors = rDTDEE.SupportedDataFlavors.getLength(); for ( int n = 0; n < nFlavors; n++ ) { if( TransferableDataHelper::IsEqual( pFlavors[n], aTextFlavor ) ) { pDragAndDropInfo->bHasValidData = true; break; } } dragOver( rDTDEE ); } void ImpEditView::dragExit( const css::datatransfer::dnd::DropTargetEvent& ) { SolarMutexGuard aVclGuard; HideDDCursor(); if ( pDragAndDropInfo && !pDragAndDropInfo->bStarterOfDD ) { pDragAndDropInfo.reset(); } } void ImpEditView::dragOver(const css::datatransfer::dnd::DropTargetDragEvent& rDTDE) { SolarMutexGuard aVclGuard; const OutputDevice& rOutDev = getEditViewCallbacks() ? getEditViewCallbacks()->EditViewOutputDevice() : *GetWindow(); Point aMousePos( rDTDE.LocationX, rDTDE.LocationY ); aMousePos = rOutDev.PixelToLogic( aMousePos ); bool bAccept = false; if ( GetOutputArea().IsInside( aMousePos ) && !bReadOnly ) { if ( pDragAndDropInfo && pDragAndDropInfo->bHasValidData ) { bAccept = true; bool bAllowScroll = DoAutoScroll(); if ( bAllowScroll ) { long nScrollX = 0; long nScrollY = 0; // Check if in the sensitive area if ( ( (aMousePos.X()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Left() ) && ( ( aMousePos.X() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Left() ) ) nScrollX = GetOutputArea().GetWidth() / SCRLRANGE; else if ( ( (aMousePos.X()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Right() ) && ( ( aMousePos.X() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Right() ) ) nScrollX = -( GetOutputArea().GetWidth() / SCRLRANGE ); if ( ( (aMousePos.Y()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Top() ) && ( ( aMousePos.Y() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Top() ) ) nScrollY = GetOutputArea().GetHeight() / SCRLRANGE; else if ( ( (aMousePos.Y()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Bottom() ) && ( ( aMousePos.Y() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Bottom() ) ) nScrollY = -( GetOutputArea().GetHeight() / SCRLRANGE ); if ( nScrollX || nScrollY ) { HideDDCursor(); Scroll( nScrollX, nScrollY, ScrollRangeCheck::PaperWidthTextSize ); } } Point aDocPos( GetDocPos( aMousePos ) ); EditPaM aPaM = pEditEngine->GetPaM( aDocPos ); pDragAndDropInfo->aDropDest = aPaM; if ( pDragAndDropInfo->bOutlinerMode ) { sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() ); ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara ); if (pPPortion) { long nDestParaStartY = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); long nRel = aDocPos.Y() - nDestParaStartY; if ( nRel < ( pPPortion->GetHeight() / 2 ) ) { pDragAndDropInfo->nOutlinerDropDest = nPara; } else { pDragAndDropInfo->nOutlinerDropDest = nPara+1; } if( ( pDragAndDropInfo->nOutlinerDropDest >= pDragAndDropInfo->aBeginDragSel.nStartPara ) && ( pDragAndDropInfo->nOutlinerDropDest <= (pDragAndDropInfo->aBeginDragSel.nEndPara+1) ) ) { bAccept = false; } } } else if ( HasSelection() ) { // it must not be dropped into a selection EPaM aP = pEditEngine->pImpEditEngine->CreateEPaM( aPaM ); ESelection aDestSel( aP.nPara, aP.nIndex, aP.nPara, aP.nIndex); ESelection aCurSel = pEditEngine->pImpEditEngine->CreateESel( GetEditSelection() ); aCurSel.Adjust(); if ( !(aDestSel < aCurSel) && !(aDestSel > aCurSel) ) { bAccept = false; } } if ( bAccept ) { tools::Rectangle aEditCursor; if ( pDragAndDropInfo->bOutlinerMode ) { long nDDYPos(0); if ( pDragAndDropInfo->nOutlinerDropDest < pEditEngine->GetEditDoc().Count() ) { ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( pDragAndDropInfo->nOutlinerDropDest ); if (pPPortion) nDDYPos = pEditEngine->GetParaPortions().GetYOffset( pPPortion ); } else { nDDYPos = pEditEngine->pImpEditEngine->GetTextHeight(); } Point aStartPos( 0, nDDYPos ); aStartPos = GetWindowPos( aStartPos ); Point aEndPos( GetOutputArea().GetWidth(), nDDYPos ); aEndPos = GetWindowPos( aEndPos ); aEditCursor = rOutDev.LogicToPixel( tools::Rectangle( aStartPos, aEndPos ) ); if ( !pEditEngine->IsVertical() ) { aEditCursor.AdjustTop( -1 ); aEditCursor.AdjustBottom( 1 ); } else { if( IsTopToBottom() ) { aEditCursor.AdjustLeft( -1 ); aEditCursor.AdjustRight( 1 ); } else { aEditCursor.AdjustLeft( 1 ); aEditCursor.AdjustRight( -1 ); } } aEditCursor = rOutDev.PixelToLogic( aEditCursor ); } else { aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM ); Point aTopLeft( GetWindowPos( aEditCursor.TopLeft() ) ); aEditCursor.SetPos( aTopLeft ); aEditCursor.SetRight( aEditCursor.Left() + pDragAndDropInfo->nCursorWidth ); aEditCursor = rOutDev.LogicToPixel( aEditCursor ); aEditCursor = rOutDev.PixelToLogic( aEditCursor ); } bool bCursorChanged = !pDragAndDropInfo->bVisCursor || ( pDragAndDropInfo->aCurCursor != aEditCursor ); if ( bCursorChanged ) { HideDDCursor(); ShowDDCursor(aEditCursor ); } pDragAndDropInfo->bDragAccepted = true; rDTDE.Context->acceptDrag( rDTDE.DropAction ); } } } if ( !bAccept ) { HideDDCursor(); if (pDragAndDropInfo) pDragAndDropInfo->bDragAccepted = false; rDTDE.Context->rejectDrag(); } } void ImpEditView::AddDragAndDropListeners() { if (bActiveDragAndDropListener) return; css::uno::Reference xDropTarget; if (getEditViewCallbacks()) xDropTarget = getEditViewCallbacks()->GetDropTarget(); else if (GetWindow()) xDropTarget = GetWindow()->GetDropTarget(); if (xDropTarget.is()) { vcl::unohelper::DragAndDropWrapper* pDnDWrapper = new vcl::unohelper::DragAndDropWrapper(this); mxDnDListener = pDnDWrapper; css::uno::Reference xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); if (xDragGestureRecognizer.is()) { uno::Reference xDGL(mxDnDListener, uno::UNO_QUERY); xDragGestureRecognizer->addDragGestureListener(xDGL); } uno::Reference xDTL(mxDnDListener, uno::UNO_QUERY); xDropTarget->addDropTargetListener(xDTL); xDropTarget->setActive(true); xDropTarget->setDefaultActions(datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE); bActiveDragAndDropListener = true; } } void ImpEditView::RemoveDragAndDropListeners() { if (!bActiveDragAndDropListener) return; css::uno::Reference xDropTarget; if (getEditViewCallbacks()) xDropTarget = getEditViewCallbacks()->GetDropTarget(); else if (GetWindow()) xDropTarget = GetWindow()->GetDropTarget(); if (xDropTarget.is()) { css::uno::Reference xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY); if (xDragGestureRecognizer.is()) { uno::Reference xDGL(mxDnDListener, uno::UNO_QUERY); xDragGestureRecognizer->removeDragGestureListener(xDGL); } uno::Reference xDTL(mxDnDListener, uno::UNO_QUERY); xDropTarget->removeDropTargetListener(xDTL); if ( mxDnDListener.is() ) { mxDnDListener->disposing( lang::EventObject() ); // #95154# Empty Source means it's the Client mxDnDListener.clear(); } bActiveDragAndDropListener = false; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */