/* -*- 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 <editeng/editeng.hxx>
#include <editeng/outlobj.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdoole2.hxx>
#include <svx/svdouno.hxx>
#include <svx/ImageMapInfo.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <sc.hrc>
#include <fudraw.hxx>
#include <futext.hxx>
#include <tabvwsh.hxx>
#include <drwlayer.hxx>
#include <userdat.hxx>
#include <docsh.hxx>
#include <drawview.hxx>
#include <comphelper/lok.hxx>
#include <com/sun/star/embed/EmbedVerbs.hpp>
namespace
{
void collectUIInformation( const OUString& aevent )
{
EventDescription aDescription;
aDescription.aID = "grid_window";
aDescription.aParameters = {{ aevent , ""}};
aDescription.aAction = "COMMENT";
aDescription.aParent = "MainWindow";
aDescription.aKeyWord = "ScGridWinUIObject";
UITestLogger::getInstance().logEvent(aDescription);
}
}
// base class for draw module specific functions
FuDraw::FuDraw(ScTabViewShell& rViewSh, vcl::Window* pWin, ScDrawView* pViewP,
SdrModel* pDoc, const SfxRequest& rReq)
: FuPoor(rViewSh, pWin, pViewP, pDoc, rReq)
, aNewPointer(PointerStyle::Arrow)
, aOldPointer(PointerStyle::Arrow)
{
}
FuDraw::~FuDraw()
{
}
void FuDraw::DoModifiers(const MouseEvent& rMEvt)
{
// Shift = Ortho and AngleSnap
// Control = Snap (Toggle)
// Alt = centric
bool bShift = rMEvt.IsShift();
bool bAlt = rMEvt.IsMod2();
bool bOrtho = bShift;
bool bAngleSnap = bShift;
bool bCenter = bAlt;
// #i33136#
if(doConstructOrthogonal())
{
bOrtho = !bShift;
}
if (pView->IsOrtho() != bOrtho)
pView->SetOrtho(bOrtho);
if (pView->IsAngleSnapEnabled() != bAngleSnap)
pView->SetAngleSnapEnabled(bAngleSnap);
if (pView->IsCreate1stPointAsCenter() != bCenter)
pView->SetCreate1stPointAsCenter(bCenter);
if (pView->IsResizeAtCenter() != bCenter)
pView->SetResizeAtCenter(bCenter);
}
void FuDraw::ResetModifiers()
{
if (!pView)
return;
ScViewData& rViewData = rViewShell.GetViewData();
const ScViewOptions& rOpt = rViewData.GetOptions();
const ScGridOptions& rGrid = rOpt.GetGridOptions();
bool bGridOpt = rGrid.GetUseGridSnap();
if (pView->IsOrtho())
pView->SetOrtho(false);
if (pView->IsAngleSnapEnabled())
pView->SetAngleSnapEnabled(false);
if (pView->IsGridSnap() != bGridOpt)
pView->SetGridSnap(bGridOpt);
if (pView->IsSnapEnabled() != bGridOpt)
pView->SetSnapEnabled(bGridOpt);
if (pView->IsCreate1stPointAsCenter())
pView->SetCreate1stPointAsCenter(false);
if (pView->IsResizeAtCenter())
pView->SetResizeAtCenter(false);
}
bool FuDraw::MouseButtonDown(const MouseEvent& rMEvt)
{
// remember button state for creation of own MouseEvents
SetMouseButtonCode(rMEvt.GetButtons());
DoModifiers( rMEvt );
return false;
}
bool FuDraw::MouseMove(const MouseEvent& rMEvt)
{
// evaluate modifiers only if in a drawing layer action
// (don't interfere with keyboard shortcut handling)
if (pView->IsAction())
DoModifiers( rMEvt );
return false;
}
bool FuDraw::MouseButtonUp(const MouseEvent& rMEvt)
{
// remember button state for creation of own MouseEvents
SetMouseButtonCode(rMEvt.GetButtons());
ResetModifiers();
return false;
}
// Process Keyboard events. Return true if an event is being handled
static bool lcl_KeyEditMode( SdrObject* pObj, ScTabViewShell& rViewShell, const KeyEvent* pInitialKey )
{
bool bReturn = false;
if ( DynCastSdrTextObj( pObj) != nullptr && dynamic_cast<const SdrUnoObj*>( pObj) == nullptr )
{
assert(pObj);
// start text edit - like FuSelection::MouseButtonUp,
// but with bCursorToEnd instead of mouse position
OutlinerParaObject* pOPO = pObj->GetOutlinerParaObject();
bool bVertical = ( pOPO && pOPO->IsEffectivelyVertical() );
sal_uInt16 nTextSlotId = bVertical ? SID_DRAW_TEXT_VERTICAL : SID_DRAW_TEXT;
// don't switch shells if text shell is already active
FuPoor* pPoor = rViewShell.GetViewData().GetView()->GetDrawFuncPtr();
if ( !pPoor || pPoor->GetSlotID() != nTextSlotId )
{
rViewShell.GetViewData().GetDispatcher().
Execute(nTextSlotId, SfxCallMode::SYNCHRON | SfxCallMode::RECORD);
}
// get the resulting FuText and set in edit mode
pPoor = rViewShell.GetViewData().GetView()->GetDrawFuncPtr();
if ( pPoor && pPoor->GetSlotID() == nTextSlotId ) // no RTTI
{
FuText* pText = static_cast<FuText*>(pPoor);
pText->SetInEditMode( pObj, nullptr, true, pInitialKey );
//! set cursor to end of text
}
bReturn = true;
}
return bReturn;
}
bool FuDraw::KeyInput(const KeyEvent& rKEvt)
{
bool bReturn = false;
ScViewData& rViewData = rViewShell.GetViewData();
const SdrMarkList& rMarkList = pView->GetMarkedObjectList();
switch ( rKEvt.GetKeyCode().GetCode() )
{
case KEY_ESCAPE:
if ( rViewShell.IsDrawTextShell() || aSfxRequest.GetSlot() == SID_DRAW_NOTEEDIT )
{
collectUIInformation(u"CLOSE"_ustr);
// if object selected -> normal draw-shell, else turn off drawing
rViewData.GetDispatcher().Execute(aSfxRequest.GetSlot(), SfxCallMode::SLOT | SfxCallMode::RECORD);
bReturn = true;
}
else if ( rViewShell.IsDrawSelMode() )
{
pView->UnmarkAll();
rViewData.GetDispatcher().Execute(SID_OBJECT_SELECT, SfxCallMode::SLOT | SfxCallMode::RECORD);
bReturn = true;
}
else if ( rMarkList.GetMarkCount() != 0 )
{
// III
SdrHdlList& rHdlList = const_cast< SdrHdlList& >( pView->GetHdlList() );
if( rHdlList.GetFocusHdl() )
rHdlList.ResetFocusHdl();
else
pView->UnmarkAll();
// while bezier editing, object is selected
if (rMarkList.GetMarkCount() == 0)
rViewShell.SetDrawShell( false );
bReturn = true;
}
break;
case KEY_DELETE: //! via accelerator
pView->DeleteMarked();
bReturn = true;
break;
case KEY_RETURN:
{
if( rKEvt.GetKeyCode().GetModifier() == 0 )
{
// activate OLE object on RETURN for selected object
// put selected text object in edit mode
if( !pView->IsTextEdit() && 1 == rMarkList.GetMarkCount() )
{
bool bOle = rViewShell.GetViewFrame().GetFrame().IsInPlace();
SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();
auto pOleObj = dynamic_cast<SdrOle2Obj*>(pObj);
if( pOleObj && !bOle )
{
rViewShell.ActivateObject(pOleObj, css::embed::EmbedVerbs::MS_OLEVERB_PRIMARY);
// consumed
bReturn = true;
}
else if ( lcl_KeyEditMode( pObj, rViewShell, nullptr ) ) // start text edit for suitable object
bReturn = true;
}
}
}
break;
case KEY_F2:
{
if( rKEvt.GetKeyCode().GetModifier() == 0 )
{
// put selected text object in edit mode
// (this is not SID_SETINPUTMODE, but F2 hardcoded, like in Writer)
if( !pView->IsTextEdit() && 1 == rMarkList.GetMarkCount() )
{
SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();
bool isMobilePhone = comphelper::LibreOfficeKit::isActive() && rViewShell.isLOKMobilePhone();
// Double tapping on charts on phone may result in activating the edit mode which is not wanted.
// It happens due to the delay of selection message of the object from kit to javascript
// in that case F2 is sent instead of double click
if (isMobilePhone && ScDocument::IsChart(pObj))
{
rViewShell.ActivateObject(static_cast<SdrOle2Obj*>(pObj), css::embed::EmbedVerbs::MS_OLEVERB_PRIMARY);
break;
}
if ( lcl_KeyEditMode( pObj, rViewShell, nullptr ) ) // start text edit for suitable object
bReturn = true;
}
}
}
break;
case KEY_TAB:
{
// in calc do NOT start draw object selection using TAB/SHIFT-TAB when
// there is not yet an object selected
if(rMarkList.GetMarkCount() != 0)
{
vcl::KeyCode aCode = rKEvt.GetKeyCode();
if ( !aCode.IsMod1() && !aCode.IsMod2() )
{
// changeover to the next object
if(!pView->MarkNextObj( !aCode.IsShift() ))
{
//If there is only one object, don't do the UnmarkAllObj() & MarkNextObj().
if ( pView->HasMultipleMarkableObjects() && pView->HasMarkableObj() )
{
// No next object: go over open end and
// get first from the other side
pView->UnmarkAllObj();
pView->MarkNextObj(!aCode.IsShift());
}
}
// II
if(rMarkList.GetMarkCount() != 0)
pView->MakeVisible(pView->GetAllMarkedRect(), *pWindow);
bReturn = true;
}
// handle Mod1 and Mod2 to get travelling running on different systems
if(rKEvt.GetKeyCode().IsMod1() || rKEvt.GetKeyCode().IsMod2())
{
// II do something with a selected handle?
const SdrHdlList& rHdlList = pView->GetHdlList();
bool bForward(!rKEvt.GetKeyCode().IsShift());
const_cast<SdrHdlList&>(rHdlList).TravelFocusHdl(bForward);
// guarantee visibility of focused handle
SdrHdl* pHdl = rHdlList.GetFocusHdl();
if(pHdl)
{
Point aHdlPosition(pHdl->GetPos());
tools::Rectangle aVisRect(aHdlPosition - Point(100, 100), Size(200, 200));
pView->MakeVisible(aVisRect, *pWindow);
}
// consumed
bReturn = true;
}
}
}
break;
case KEY_END:
{
// in calc do NOT select the last draw object when
// there is not yet an object selected
if(rMarkList.GetMarkCount() != 0)
{
vcl::KeyCode aCode = rKEvt.GetKeyCode();
if ( aCode.IsMod1() )
{
// mark last object
pView->UnmarkAllObj();
pView->MarkNextObj();
// II
if(rMarkList.GetMarkCount() != 0)
pView->MakeVisible(pView->GetAllMarkedRect(), *pWindow);
bReturn = true;
}
}
}
break;
case KEY_HOME:
{
// in calc do NOT select the first draw object when
// there is not yet an object selected
if(rMarkList.GetMarkCount() != 0)
{
vcl::KeyCode aCode = rKEvt.GetKeyCode();
if ( aCode.IsMod1() )
{
// mark first object
pView->UnmarkAllObj();
pView->MarkNextObj(true);
// II
if(rMarkList.GetMarkCount() != 0)
pView->MakeVisible(pView->GetAllMarkedRect(), *pWindow);
bReturn = true;
}
}
}
break;
case KEY_UP:
case KEY_DOWN:
case KEY_LEFT:
case KEY_RIGHT:
{
// in calc do cursor travelling of draw objects only when
// there is an object selected yet
if(rMarkList.GetMarkCount() != 0)
{
if(rMarkList.GetMarkCount() == 1)
{
// disable cursor travelling on note objects as the tail connector position
// must not move.
SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();
if( ScDrawLayer::IsNoteCaption( pObj ) )
break;
}
tools::Long nX = 0;
tools::Long nY = 0;
sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
if (nCode == KEY_UP)
{
// scroll up
nX = 0;
nY =-1;
}
else if (nCode == KEY_DOWN)
{
// scroll down
nX = 0;
nY = 1;
}
else if (nCode == KEY_LEFT)
{
// scroll left
nX =-1;
nY = 0;
}
else if (nCode == KEY_RIGHT)
{
// scroll right
nX = 1;
nY = 0;
}
bool bReadOnly = rViewData.GetDocShell()->IsReadOnly();
if(!rKEvt.GetKeyCode().IsMod1() && !bReadOnly)
{
if(rKEvt.GetKeyCode().IsMod2())
{
// move in 1 pixel distance
Size aLogicSizeOnePixel = pWindow ? pWindow->PixelToLogic(Size(1,1)) : Size(100, 100);
nX *= aLogicSizeOnePixel.Width();
nY *= aLogicSizeOnePixel.Height();
}
else if(rKEvt.GetKeyCode().IsShift()) // #i121236# Support for shift key in calc
{
nX *= 1000;
nY *= 1000;
}
else
{
// old, fixed move distance
nX *= 100;
nY *= 100;
}
// is there a movement to do?
if(0 != nX || 0 != nY)
{
// II
const SdrHdlList& rHdlList = pView->GetHdlList();
SdrHdl* pHdl = rHdlList.GetFocusHdl();
if(nullptr == pHdl)
{
// only take action when move is allowed
if(pView->IsMoveAllowed())
{
// restrict movement to WorkArea
const tools::Rectangle& rWorkArea = pView->GetWorkArea();
if(!rWorkArea.IsEmpty())
{
tools::Rectangle aMarkRect(pView->GetMarkedObjRect());
aMarkRect.Move(nX, nY);
if(!aMarkRect.Contains(rWorkArea))
{
if(aMarkRect.Left() < rWorkArea.Left())
{
nX += rWorkArea.Left() - aMarkRect.Left();
}
if(aMarkRect.Right() > rWorkArea.Right())
{
nX -= aMarkRect.Right() - rWorkArea.Right();
}
if(aMarkRect.Top() < rWorkArea.Top())
{
nY += rWorkArea.Top() - aMarkRect.Top();
}
if(aMarkRect.Bottom() > rWorkArea.Bottom())
{
nY -= aMarkRect.Bottom() - rWorkArea.Bottom();
}
}
}
// now move the selected draw objects
pView->MoveAllMarked(Size(nX, nY));
// II
pView->MakeVisible(pView->GetAllMarkedRect(), *pWindow);
bReturn = true;
}
}
else
{
// move handle with index nHandleIndex
if (nX || nY)
{
// now move the Handle (nX, nY)
Point aStartPoint(pHdl->GetPos());
Point aEndPoint(pHdl->GetPos() + Point(nX, nY));
const SdrDragStat& rDragStat = pView->GetDragStat();
// start dragging
pView->BegDragObj(aStartPoint, nullptr, pHdl, 0);
if(pView->IsDragObj())
{
bool bWasNoSnap = rDragStat.IsNoSnap();
bool bWasSnapEnabled = pView->IsSnapEnabled();
// switch snapping off
if(!bWasNoSnap)
const_cast<SdrDragStat&>(rDragStat).SetNoSnap();
if(bWasSnapEnabled)
pView->SetSnapEnabled(false);
pView->MovAction(aEndPoint);
pView->EndDragObj();
// restore snap
if(!bWasNoSnap)
const_cast<SdrDragStat&>(rDragStat).SetNoSnap(bWasNoSnap);
if(bWasSnapEnabled)
pView->SetSnapEnabled(bWasSnapEnabled);
}
// make moved handle visible
tools::Rectangle aVisRect(aEndPoint - Point(100, 100), Size(200, 200));
pView->MakeVisible(aVisRect, *pWindow);
bReturn = true;
}
}
}
}
}
}
break;
case KEY_SPACE:
{
// in calc do only something when draw objects are selected
if(rMarkList.GetMarkCount() != 0)
{
const SdrHdlList& rHdlList = pView->GetHdlList();
SdrHdl* pHdl = rHdlList.GetFocusHdl();
if(pHdl)
{
if(pHdl->GetKind() == SdrHdlKind::Poly)
{
// rescue ID of point with focus
sal_uInt32 nPol(pHdl->GetPolyNum());
sal_uInt32 nPnt(pHdl->GetPointNum());
if(pView->IsPointMarked(*pHdl))
{
if(rKEvt.GetKeyCode().IsShift())
{
pView->UnmarkPoint(*pHdl);
}
}
else
{
if(!rKEvt.GetKeyCode().IsShift())
{
pView->UnmarkAllPoints();
}
pView->MarkPoint(*pHdl);
}
if(nullptr == rHdlList.GetFocusHdl())
{
// restore point with focus
SdrHdl* pNewOne = nullptr;
for(size_t a = 0; !pNewOne && a < rHdlList.GetHdlCount(); ++a)
{
SdrHdl* pAct = rHdlList.GetHdl(a);
if(pAct
&& pAct->GetKind() == SdrHdlKind::Poly
&& pAct->GetPolyNum() == nPol
&& pAct->GetPointNum() == nPnt)
{
pNewOne = pAct;
}
}
if(pNewOne)
{
const_cast<SdrHdlList&>(rHdlList).SetFocusHdl(pNewOne);
}
}
bReturn = true;
}
}
}
}
break;
}
if (!bReturn)
{
bReturn = FuPoor::KeyInput(rKEvt);
}
if (!bReturn)
{
// allow direct typing into a selected text object
if( !pView->IsTextEdit() && 1 == rMarkList.GetMarkCount() && EditEngine::IsSimpleCharInput(rKEvt) )
{
SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();
// start text edit for suitable object, pass key event to OutlinerView
if ( lcl_KeyEditMode( pObj, rViewShell, &rKEvt ) )
bReturn = true;
}
}
return bReturn;
}
// toggle mouse-pointer
static bool lcl_UrlHit( const SdrView* pView, const Point& rPosPixel, const vcl::Window* pWindow )
{
SdrViewEvent aVEvt;
MouseEvent aMEvt( rPosPixel, 1, MouseEventModifiers::NONE, MOUSE_LEFT );
SdrHitKind eHit = pView->PickAnything( aMEvt, SdrMouseEventKind::BUTTONDOWN, aVEvt );
if (eHit != SdrHitKind::NONE && aVEvt.mpObj != nullptr)
{
if ( SvxIMapInfo::GetIMapInfo(aVEvt.mpObj) && SvxIMapInfo::GetHitIMapObject(
aVEvt.mpObj, pWindow->PixelToLogic(rPosPixel), pWindow->GetOutDev() ) )
return true;
if (aVEvt.meEvent == SdrEventKind::ExecuteUrl)
return true;
}
return false;
}
void FuDraw::ForcePointer(const MouseEvent* pMEvt)
{
if ( pView->IsAction() )
return;
Point aPosPixel = pWindow->GetPointerPosPixel();
bool bAlt = pMEvt && pMEvt->IsMod2();
Point aPnt = pWindow->PixelToLogic( aPosPixel );
SdrHdl* pHdl = pView->PickHandle(aPnt);
SdrPageView* pPV;
SdrObject* pMacroPickObj;
ScMacroInfo* pInfo = nullptr;
SdrObject* pObj = pView->PickObj(aPnt, pView->getHitTolLog(), pPV, SdrSearchOptions::ALSOONMASTER);
if (pObj)
{
if ( pObj->IsGroupObject() )
{
SdrObject* pHit = pView->PickObj(aMDPos, pView->getHitTolLog(), pPV, SdrSearchOptions::DEEP);
if (pHit)
pObj = pHit;
}
pInfo = ScDrawLayer::GetMacroInfo( pObj );
}
if ( pView->IsTextEdit() )
{
rViewShell.SetActivePointer(PointerStyle::Text); // can't be ?
}
else if ( pHdl )
{
rViewShell.SetActivePointer(
pView->GetPreferredPointer( aPnt, pWindow->GetOutDev() ) );
}
else if ( pView->IsMarkedHit(aPnt) )
{
rViewShell.SetActivePointer( PointerStyle::Move );
}
else if ( !bAlt && ( !pMEvt || !pMEvt->GetButtons() )
&& lcl_UrlHit( pView, aPosPixel, pWindow ) )
{
// could be suppressed with ALT
pWindow->SetPointer( PointerStyle::RefHand ); // Text-URL / ImageMap
}
else if ( !bAlt && (pMacroPickObj = pView->PickObj(aPnt, pView->getHitTolLog(), pPV, SdrSearchOptions::PICKMACRO)) )
{
// could be suppressed with ALT
SdrObjMacroHitRec aHitRec; //! something missing ????
rViewShell.SetActivePointer(pMacroPickObj->GetMacroPointer(aHitRec));
}
else if ( !bAlt && pInfo && (!pInfo->GetMacro().isEmpty() || !pObj->getHyperlink().isEmpty()) )
pWindow->SetPointer( PointerStyle::RefHand );
else if ( IsDetectiveHit( aPnt ) )
rViewShell.SetActivePointer( PointerStyle::Detective );
else
{
const bool bIsThemed = rViewShell.GetViewData().IsThemedCursor();
rViewShell.SetActivePointer( bIsThemed ? PointerStyle::FatCross : PointerStyle::Arrow ); //! in Gridwin?
}
}
bool FuDraw::IsEditingANote() const
{
const SdrMarkList& rMarkList = pView->GetMarkedObjectList();
const size_t backval=rMarkList.GetMarkCount();
for (size_t nlv1=0; nlv1<backval; ++nlv1)
{
SdrObject* pObj = rMarkList.GetMark( nlv1 )->GetMarkedSdrObj();
if ( ScDrawLayer::IsNoteCaption( pObj ) )
{
return true;
}
}
return false;
}
bool FuDraw::IsSizingOrMovingNote( const MouseEvent& rMEvt ) const
{
bool bIsSizingOrMoving = false;
if ( rMEvt.IsLeft() )
{
const SdrMarkList& rMarkList = pView->GetMarkedObjectList();
if(rMarkList.GetMarkCount() == 1)
{
SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();
if ( ScDrawLayer::IsNoteCaption( pObj ) )
{
Point aMPos = pWindow->PixelToLogic( rMEvt.GetPosPixel() );
bIsSizingOrMoving =
pView->PickHandle( aMPos ) || // handles to resize the note
pView->IsTextEditFrameHit( aMPos ); // frame for moving the note
}
}
}
return bIsSizingOrMoving;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'Execute' is required to be utilized.
↑ V530 The return value of function 'Execute' is required to be utilized.
↑ V530 The return value of function 'Execute' is required to be utilized.