/* -*- 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 <fupoor.hxx>

#include <svx/svxids.hrc>
#include <svx/svdpagv.hxx>
#include <svx/svdoole2.hxx>
#include <svx/svdograf.hxx>
#include <vcl/seleng.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/request.hxx>
#include <svl/stritem.hxx>

#include <app.hrc>
#include <fusel.hxx>
#include <sdpage.hxx>
#include <DrawViewShell.hxx>
#include <Window.hxx>
#include <drawdoc.hxx>
#include <DrawDocShell.hxx>
#include <zoomlist.hxx>
#include <slideshow.hxx>
#include <LayerTabBar.hxx>

#include <com/sun/star/embed/EmbedVerbs.hpp>

#include <sfx2/viewfrm.hxx>

#include <svx/svditer.hxx>

#include <editeng/editeng.hxx>

using namespace ::com::sun::star;

namespace sd {


FuPoor::FuPoor (
    ViewShell& rViewSh,
    ::sd::Window* pWin,
    ::sd::View* pView,
    SdDrawDocument& rDrDoc,
    SfxRequest& rReq)
    : mpView(pView),
      mrViewShell(rViewSh),
      mpWindow(pWin),
      mpDocSh( rDrDoc.GetDocSh() ),
      mrDoc(rDrDoc),
      nSlotId( rReq.GetSlot() ),
      aScrollTimer("sd FuPoor aScrollTimer"),
      aDragTimer("sd FuPoor aDragTimer"),
      bIsInDragMode(false),
      bNoScrollUntilInside (true),
      aDelayToScrollTimer("sd FuPoor aDelayToScrollTimer"),
      bScrollable (false),
      bDelayActive (false),
      bFirstMouseMove (false),
      // remember MouseButton state
      mnCode(0)
{
    ReceiveRequest(rReq);

    aScrollTimer.SetInvokeHandler( LINK(this, FuPoor, ScrollHdl) );
    aScrollTimer.SetTimeout(SELENG_AUTOREPEAT_INTERVAL);

    aDragTimer.SetInvokeHandler( LINK(this, FuPoor, DragHdl) );
    aDragTimer.SetTimeout(SELENG_DRAGDROP_TIMEOUT);

    aDelayToScrollTimer.SetInvokeHandler( LINK(this, FuPoor, DelayHdl) );
    aDelayToScrollTimer.SetTimeout(2000);
}

FuPoor::~FuPoor()
{
    aDragTimer.Stop();
    aScrollTimer.Stop();
    aDelayToScrollTimer.Stop();
}

void FuPoor::Activate()
{
}

void FuPoor::Deactivate()
{
    aDragTimer.Stop();
    aScrollTimer.Stop();
    aDelayToScrollTimer.Stop ();
    bScrollable = bDelayActive = false;

    if (mpWindow && mpWindow->IsMouseCaptured())
        mpWindow->ReleaseMouse();
}

void FuPoor::SetWindow(::sd::Window* pWin)
{
    mpWindow = pWin;
}

/**
 * scroll when approached the border of the window; is called by MouseMove
 */
void FuPoor::ForceScroll(const Point& aPixPos)
{
    aScrollTimer.Stop();

    if ( mpView->IsDragHelpLine() || mpView->IsSetPageOrg() ||
         (SlideShow::IsRunning( mrViewShell.GetViewShellBase() )
            && !SlideShow::IsInteractiveSlideshow( mrViewShell.GetViewShellBase() )) ) // IASS
        return;

    Point aPos = mpWindow->OutputToScreenPixel(aPixPos);
    const ::tools::Rectangle& rRect = mrViewShell.GetAllWindowRect();

    if ( bNoScrollUntilInside )
    {
        if ( rRect.Contains(aPos) )
            bNoScrollUntilInside = false;
    }
    else
    {
        short dx = 0, dy = 0;

        if ( aPos.X() <= rRect.Left()   ) dx = -1;
        if ( aPos.X() >= rRect.Right()  ) dx =  1;
        if ( aPos.Y() <= rRect.Top()    ) dy = -1;
        if ( aPos.Y() >= rRect.Bottom() ) dy =  1;

        if ( dx != 0 || dy != 0 )
        {
            if (bScrollable)
            {
                // scroll action in derived class
                mrViewShell.ScrollLines(dx, dy);
                aScrollTimer.Start();
            }
            else if (! bDelayActive) StartDelayToScrollTimer ();
        }
    }
}

/**
 * timer handler for window scrolling
 */
IMPL_LINK_NOARG(FuPoor, ScrollHdl, Timer *, void)
{
    Point aPnt(mpWindow->GetPointerPosPixel());

    // use remembered MouseButton state to create correct
    // MouseEvents for this artificial MouseMove.
    MouseMove(MouseEvent(aPnt, 1, MouseEventModifiers::NONE, GetMouseButtonCode()));
}

/**
 * handle keyboard events
 * @returns sal_True if the event was handled, sal_False otherwise
 */
bool FuPoor::KeyInput(const KeyEvent& rKEvt)
{
    sal_uInt16          nCode = rKEvt.GetKeyCode().GetCode();
    bool            bReturn = false;
    bool bSlideShow = SlideShow::IsRunning( mrViewShell.GetViewShellBase() )
        && !SlideShow::IsInteractiveSlideshow( mrViewShell.GetViewShellBase() ); // IASS

    switch (nCode)
    {
        case KEY_RETURN:
        {
            if(rKEvt.GetKeyCode().IsMod1())
            {
                if( auto pDrawViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ))
                {
                    SdPage* pActualPage = pDrawViewShell->GetActualPage();
                    SdrTextObj* pCandidate = nullptr;

                    if(pActualPage)
                    {
                        SdrObjListIter aIter(pActualPage, SdrIterMode::DeepNoGroups);

                        while(aIter.IsMore() && !pCandidate)
                        {
                            SdrObject* pObj = aIter.Next();

                            if(auto pTextObj = DynCastSdrTextObj( pObj ))
                            {
                                SdrInventor nInv(pObj->GetObjInventor());
                                SdrObjKind nKnd(pObj->GetObjIdentifier());

                                if(SdrInventor::Default == nInv &&
                                    (SdrObjKind::TitleText == nKnd || SdrObjKind::OutlineText == nKnd || SdrObjKind::Text == nKnd))
                                {
                                    pCandidate = pTextObj;
                                }
                            }
                        }
                    }

                    if(pCandidate)
                    {
                        mpView->UnMarkAll();
                        mpView->MarkObj(pCandidate, mpView->GetSdrPageView());

                        mrViewShell.GetViewFrame()->GetDispatcher()->Execute(
                            SID_ATTR_CHAR, SfxCallMode::ASYNCHRON);
                    }
                    else
                    {
                        // insert a new page with the same page layout
                        mrViewShell.GetViewFrame()->GetDispatcher()->Execute(
                            SID_INSERTPAGE_QUICK, SfxCallMode::ASYNCHRON);
                    }

                    // consumed
                    bReturn = true;
                }
            }
            else if (!mpDocSh->IsReadOnly())
            {
                // activate OLE object on RETURN for selected object
                // activate text edit on RETURN for selected object
                const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();

                if( !mpView->IsTextEdit() && 1 == rMarkList.GetMarkCount() )
                {
                    SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj();

                    if( dynamic_cast< const SdrOle2Obj* >( pObj ) && !mpDocSh->IsUIActive() )
                    {
                        //HMHmpView->HideMarkHdl();
                        mrViewShell.ActivateObject(static_cast<SdrOle2Obj*>(pObj), css::embed::EmbedVerbs::MS_OLEVERB_PRIMARY);
                    }
                    else if( pObj && pObj->IsEmptyPresObj() && dynamic_cast< const SdrGrafObj *>( pObj ) !=  nullptr )
                    {
                        mrViewShell.GetViewFrame()->GetDispatcher()->Execute( SID_INSERT_GRAPHIC, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD );
                    }
                    else
                    {
                        mrViewShell.GetViewFrame()->GetDispatcher()->Execute( SID_ATTR_CHAR, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD );
                    }

                    // consumed
                    bReturn = true;
                }
            }
        }
        break;

        case KEY_TAB:
        {
            // handle Mod1 and Mod2 to get travelling running on different systems
            if(rKEvt.GetKeyCode().IsMod1() || rKEvt.GetKeyCode().IsMod2())
            {
                // do something with a selected handle?
                const SdrHdlList& rHdlList = mpView->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));
                    mpView->MakeVisible(aVisRect, *mpWindow);
                }

                // consumed
                bReturn = true;
            }
        }
        break;

        case KEY_ESCAPE:
        {
            bReturn = FuPoor::cancel();
        }
        break;

        case KEY_ADD:
        {
            if (!mpView->IsTextEdit() && !bSlideShow && !mpDocSh->IsUIActive())
            {
                // increase zoom
                mrViewShell.SetZoom(mpWindow->GetZoom() * 3 / 2);

                if( auto pViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ))
                    pViewShell->SetZoomOnPage(false);

                bReturn = true;
            }
        }
        break;

        case KEY_SUBTRACT:
        {
            if (!mpView->IsTextEdit() && !bSlideShow && !mpDocSh->IsUIActive())
            {
                // decrease zoom
                mrViewShell.SetZoom(mpWindow->GetZoom() * 2 / 3);

                if( auto pViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ))
                    pViewShell->SetZoomOnPage(false);

                bReturn = true;
            }
        }
        break;

        case KEY_MULTIPLY:
        {
            if (!mpView->IsTextEdit() && !bSlideShow)
            {
                // zoom to page
                mrViewShell.GetViewFrame()->GetDispatcher()->
                Execute(SID_SIZE_PAGE, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD);
                bReturn = true;
            }
        }
        break;

        case KEY_DIVIDE:
        {
            if (!mpView->IsTextEdit() && !bSlideShow)
            {
                // zoom to selected objects
                mrViewShell.GetViewFrame()->GetDispatcher()->
                Execute(SID_SIZE_OPTIMAL, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD);
                bReturn = true;
            }
        }
        break;

        case KEY_POINT:
        {
            ZoomList* pZoomList = mrViewShell.GetZoomList();

            if (!mpView->IsTextEdit() && pZoomList->IsNextPossible() && !bSlideShow && !mpDocSh->IsUIActive())
            {
                // use next ZoomRect
                mrViewShell.SetZoomRect(pZoomList->GetNextZoomRect());
                bReturn = true;
            }
        }
        break;

        case KEY_COMMA:
        {
            ZoomList* pZoomList = mrViewShell.GetZoomList();

            if (!mpView->IsTextEdit() && pZoomList->IsPreviousPossible() && !bSlideShow && !mpDocSh->IsUIActive())
            {
                // use previous ZoomRect
                mrViewShell.SetZoomRect(pZoomList->GetPreviousZoomRect());
                bReturn = true;
            }
        }
        break;

        case KEY_HOME:
        {
            if (!mpView->IsTextEdit() && !bSlideShow)
                if (auto pDrawViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ))
                {
                   // jump to first page
                   pDrawViewShell->SwitchPage(0);
                   bReturn = true;
                }
        }
        break;

        case KEY_END:
        {
            if (!mpView->IsTextEdit() && !bSlideShow)
                if (auto pDrawViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ))
                {
                    // jump to last page
                    SdPage* pPage = pDrawViewShell->GetActualPage();
                    pDrawViewShell->SwitchPage(mrDoc.GetSdPageCount(
                            pPage->GetPageKind()) - 1);
                    bReturn = true;
                }
        }
        break;

        case KEY_PAGEUP:
        {
            if( rKEvt.GetKeyCode().IsMod1() && rKEvt.GetKeyCode().IsMod2() )
                break;
            if( bSlideShow)
                break;

            if( auto pDrawViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell ) )
            {
                // The page-up key switches layers or pages depending on the
                // modifier key.
                if ( ! rKEvt.GetKeyCode().GetModifier())
                {
                    // With no modifier pressed we move to the previous
                    // slide.
                    mpView->SdrEndTextEdit();

                    // Previous page.
                    bReturn = true;
                    SdPage* pPage = pDrawViewShell->GetActualPage();
                    sal_uInt16 nSdPage = (pPage->GetPageNum() - 1) / 2;

                    if (nSdPage > 0)
                    {
                        // Switch the page and send events regarding
                        // deactivation the old page and activating the new
                        // one.
                        TabControl& rPageTabControl =
                            pDrawViewShell->GetPageTabControl();
                        if (rPageTabControl.IsReallyShown())
                            rPageTabControl.SendDeactivatePageEvent ();
                        pDrawViewShell->SwitchPage(nSdPage - 1);
                        if (rPageTabControl.IsReallyShown())
                            rPageTabControl.SendActivatePageEvent ();
                    }
                }
                else if (rKEvt.GetKeyCode().IsMod1())
                {
                    // With the CONTROL modifier we switch layers.
                    if (pDrawViewShell->IsLayerModeActive())
                    {
                        // Moves to the previous layer.
                        SwitchLayer (-1);
                    }
                }
            }
        }
        break;

        case KEY_PAGEDOWN:
        {
            if( rKEvt.GetKeyCode().IsMod1() && rKEvt.GetKeyCode().IsMod2() )
                break;
            if(dynamic_cast< const DrawViewShell *>( &mrViewShell ) !=  nullptr && !bSlideShow)
            {
                // The page-down key switches layers or pages depending on the
                // modifier key.
                if ( ! rKEvt.GetKeyCode().GetModifier())
                {
                    // With no modifier pressed we move to the next slide.
                    mpView->SdrEndTextEdit();

                    // Next page.
                    bReturn = true;
                    SdPage* pPage = static_cast<DrawViewShell*>(&mrViewShell)->GetActualPage();
                    sal_uInt16 nSdPage = (pPage->GetPageNum() - 1) / 2;

                    if (nSdPage < mrDoc.GetSdPageCount(pPage->GetPageKind()) - 1)
                    {
                        // Switch the page and send events regarding
                        // deactivation the old page and activating the new
                        // one.
                        TabControl& rPageTabControl =
                            static_cast<DrawViewShell*>(&mrViewShell)->GetPageTabControl();
                        if (rPageTabControl.IsReallyShown())
                            rPageTabControl.SendDeactivatePageEvent ();
                        static_cast<DrawViewShell*>(&mrViewShell)->SwitchPage(nSdPage + 1);
                        if (rPageTabControl.IsReallyShown())
                            rPageTabControl.SendActivatePageEvent ();
                    }
                }
                else if (rKEvt.GetKeyCode().IsMod1())
                {
                    // With the CONTROL modifier we switch layers.
                    if (static_cast<DrawViewShell*>(&mrViewShell)->IsLayerModeActive())
                    {
                        // With the layer mode active pressing page-down
                        // moves to the next layer.
                        SwitchLayer (+1);
                    }
                }
            }
        }
        break;

        // change select state when focus is on poly point
        case KEY_SPACE:
        {
            const SdrHdlList& rHdlList = mpView->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(mpView->IsPointMarked(*pHdl))
                    {
                        if(rKEvt.GetKeyCode().IsShift())
                        {
                            mpView->UnmarkPoint(*pHdl);
                        }
                    }
                    else
                    {
                        if(!rKEvt.GetKeyCode().IsShift())
                        {
                            mpView->UnmarkAllPoints();
                        }

                        mpView->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;

        case KEY_UP:
        case KEY_DOWN:
        case KEY_LEFT:
        case KEY_RIGHT:
        {
            if (!mpView->IsTextEdit() && !bSlideShow)
            {
                ::tools::Long nX = 0;
                ::tools::Long nY = 0;

                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;
                }

                const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();
                if (rMarkList.GetMarkCount() != 0 && !rKEvt.GetKeyCode().IsMod1() &&
                    !mpDocSh->IsReadOnly())
                {
                    const SdrHdlList& rHdlList = mpView->GetHdlList();
                    SdrHdl* pHdl = rHdlList.GetFocusHdl();

                    bool bIsMoveOfConnectedHandle(false);
                    bool bOldSuppress = false;
                    SdrEdgeObj* pEdgeObj = nullptr;
                    if(pHdl)
                        pEdgeObj = dynamic_cast<SdrEdgeObj *>( pHdl->GetObj() );

                    if(pEdgeObj && 0 == pHdl->GetPolyNum())
                    {
                        if(0 == pHdl->GetPointNum())
                        {
                            if(pEdgeObj->GetConnection(true).GetSdrObject())
                            {
                                bIsMoveOfConnectedHandle = true;
                            }
                        }
                        if(1 == pHdl->GetPointNum())
                        {
                            if(pEdgeObj->GetConnection(false).GetSdrObject())
                            {
                                bIsMoveOfConnectedHandle = true;
                            }
                        }
                    }

                    if(pEdgeObj)
                    {
                        // Suppress default connects to inside object and object center
                        bOldSuppress = pEdgeObj->GetSuppressDefaultConnect();
                        pEdgeObj->SetSuppressDefaultConnect(true);
                    }

                    if(bIsMoveOfConnectedHandle)
                    {
                        sal_uInt16 nMarkHdSiz(mpView->GetMarkHdlSizePixel());
                        Size aHalfConSiz(nMarkHdSiz + 1, nMarkHdSiz + 1);
                        aHalfConSiz = mpWindow->PixelToLogic(aHalfConSiz);

                        if(100 < aHalfConSiz.Width())
                            nX *= aHalfConSiz.Width();
                        else
                            nX *= 100;

                        if(100 < aHalfConSiz.Height())
                            nY *= aHalfConSiz.Height();
                        else
                            nY *= 100;
                    }
                    else if(rKEvt.GetKeyCode().IsMod2())
                    {
                        // move in 1 pixel distance
                        Size aLogicSizeOnePixel = mpWindow->PixelToLogic(Size(1,1));
                        nX *= aLogicSizeOnePixel.Width();
                        nY *= aLogicSizeOnePixel.Height();
                    }
                    else if(rKEvt.GetKeyCode().IsShift())
                    {
                        nX *= 1000;
                        nY *= 1000;
                    }
                    else
                    {
                        // old, fixed move distance
                        nX *= 100;
                        nY *= 100;
                    }

                    if(nullptr == pHdl)
                    {
                        // only take action when move is allowed
                        if(mpView->IsMoveAllowed())
                        {
                            // restrict movement to WorkArea
                            const ::tools::Rectangle& rWorkArea = mpView->GetWorkArea();

                            if(!rWorkArea.IsEmpty())
                            {
                                ::tools::Rectangle aMarkRect(mpView->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();
                                    }
                                }
                            }

                            // no handle selected
                            if(0 != nX || 0 != nY)
                            {
                                mpView->MoveAllMarked(Size(nX, nY));

                                mpView->MakeVisible(mpView->GetAllMarkedRect(), *mpWindow);
                            }
                        }
                    }
                    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 = mpView->GetDragStat();

                            // start dragging
                            mpView->BegDragObj(aStartPoint, nullptr, pHdl, 0);

                            if(mpView->IsDragObj())
                            {
                                bool bWasNoSnap = rDragStat.IsNoSnap();
                                bool bWasSnapEnabled = mpView->IsSnapEnabled();

                                // switch snapping off
                                if(!bWasNoSnap)
                                    const_cast<SdrDragStat&>(rDragStat).SetNoSnap();
                                if(bWasSnapEnabled)
                                    mpView->SetSnapEnabled(false);

                                mpView->MovAction(aEndPoint);
                                mpView->EndDragObj();

                                // restore snap
                                if(!bWasNoSnap)
                                    const_cast<SdrDragStat&>(rDragStat).SetNoSnap(bWasNoSnap);
                                if(bWasSnapEnabled)
                                    mpView->SetSnapEnabled(bWasSnapEnabled);
                            }

                            // make moved handle visible
                            ::tools::Rectangle aVisRect(aEndPoint - Point(100, 100), Size(200, 200));
                            mpView->MakeVisible(aVisRect, *mpWindow);
                        }
                    }

                    if(pEdgeObj)
                    {
                        // Restore original suppress value
                        pEdgeObj->SetSuppressDefaultConnect(bOldSuppress);
                    }
                }
                else
                {
                    // scroll page
                    mrViewShell.ScrollLines(nX, nY);
                }

                bReturn = true;
            }
        }
        break;
    }

    if (bReturn)
    {
        mpWindow->ReleaseMouse();
    }

    // when a text-editable object is selected and the
    // input character is printable, activate text edit on that object
    // and feed character to object
    if(!bReturn && !mpDocSh->IsReadOnly())
    {
        if (!mpView->IsTextEdit())
        {
            const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();

            if(1 == rMarkList.GetMarkCount())
            {
                SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj();

                // #i118485# allow TextInput for OLEs, too
                if( DynCastSdrTextObj( pObj ) !=  nullptr && pObj->HasTextEdit())
                {
                    // use common IsSimpleCharInput from the EditEngine.
                    bool bPrintable(EditEngine::IsSimpleCharInput(rKEvt));

                    if(bPrintable)
                    {
                        // try to activate textedit mode for the selected object
                        SfxStringItem aInputString(SID_ATTR_CHAR, OUString(rKEvt.GetCharCode()));

                        mrViewShell.GetViewFrame()->GetDispatcher()->ExecuteList(
                            SID_ATTR_CHAR,
                            SfxCallMode::ASYNCHRON,
                            { &aInputString });

                        // consumed
                        bReturn = true;
                    }
                }
            }
            else
            {
                // test if there is a title object there. If yes, try to
                // set it to edit mode and start typing...
                DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(&mrViewShell);
                if (pDrawViewShell && EditEngine::IsSimpleCharInput(rKEvt))
                {
                    SdPage* pActualPage = pDrawViewShell->GetActualPage();
                    SdrTextObj* pCandidate = nullptr;

                    if(pActualPage)
                    {
                        SdrObjListIter aIter(pActualPage, SdrIterMode::DeepNoGroups);

                        while(aIter.IsMore() && !pCandidate)
                        {
                            SdrObject* pObj = aIter.Next();

                            if(auto pTextObj = DynCastSdrTextObj( pObj ))
                            {
                                SdrInventor nInv(pObj->GetObjInventor());
                                SdrObjKind nKnd(pObj->GetObjIdentifier());

                                if(SdrInventor::Default == nInv && SdrObjKind::TitleText == nKnd)
                                {
                                    pCandidate = pTextObj;
                                }
                            }
                        }
                    }

                    // when candidate found and candidate is untouched, start editing text...
                    if(pCandidate && pCandidate->IsEmptyPresObj())
                    {
                        mpView->UnMarkAll();
                        mpView->MarkObj(pCandidate, mpView->GetSdrPageView());
                        SfxStringItem aInputString(SID_ATTR_CHAR, OUString(rKEvt.GetCharCode()));

                        mrViewShell.GetViewFrame()->GetDispatcher()->ExecuteList(
                            SID_ATTR_CHAR,
                            SfxCallMode::ASYNCHRON,
                            { &aInputString });

                        // consumed
                        bReturn = true;
                    }
                }
            }
        }
    }

    return bReturn;
}

bool FuPoor::MouseMove(const MouseEvent& )
{
    return false;
}

void FuPoor::SelectionHasChanged()
{
    const SdrHdlList& rHdlList = mpView->GetHdlList();
    const_cast<SdrHdlList&>(rHdlList).ResetFocusHdl();
}

/**
 * Cut object to clipboard
 */
void FuPoor::DoCut()
{
    if (mpView)
    {
        mpView->DoCut();
    }
}

/**
 * Copy object to clipboard
 */
void FuPoor::DoCopy(bool bMergeMasterPagesOnly )
{
    if (mpView)
    {
        mpView->DoCopy(bMergeMasterPagesOnly);
    }
}

/**
 * Paste object from clipboard
 */
void FuPoor::DoPaste(bool bMergeMasterPagesOnly )
{
    if (mpView)
    {
        mpView->DoPaste(mpWindow, bMergeMasterPagesOnly);
    }
}

/**
 * Paste unformatted text from clipboard
 */
void FuPoor::DoPasteUnformatted()
{
    if (mpView)
    {
        TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( mrViewShell.GetActiveWindow() ) );
        if (aDataHelper.GetTransferable().is())
        {
            sal_Int8 nAction = DND_ACTION_COPY;
            mpView->InsertData( aDataHelper,
                                mpWindow->PixelToLogic( ::tools::Rectangle( Point(), mpWindow->GetOutputSizePixel() ).Center() ),
                                nAction, false, SotClipboardFormatId::STRING);
        }
    }
}

/**
 * Timer handler for Drag&Drop
 */
IMPL_LINK_NOARG(FuPoor, DragHdl, Timer *, void)
{
    if( !mpView )
        return;

    sal_uInt16 nHitLog = sal_uInt16 ( mpWindow->PixelToLogic(Size(HITPIX,0)).Width() );
    SdrHdl* pHdl = mpView->PickHandle(aMDPos);

    if ( pHdl==nullptr && mpView->IsMarkedHit(aMDPos, nHitLog)
         && !mpView->IsPresObjSelected(false) )
    {
        mpWindow->ReleaseMouse();
        bIsInDragMode = true;
        mpView->StartDrag( aMDPos, mpWindow );
    }
}

bool FuPoor::Command(const CommandEvent& rCEvt)
{
    return mpView->Command(rCEvt,mpWindow);
}

/**
 * Timer handler for window scrolling
 */
IMPL_LINK_NOARG(FuPoor, DelayHdl, Timer *, void)
{
    aDelayToScrollTimer.Stop ();
    bScrollable = true;

    Point aPnt(mpWindow->GetPointerPosPixel());

    // use remembered MouseButton state to create correct
    // MouseEvents for this artificial MouseMove.
    MouseMove(MouseEvent(aPnt, 1, MouseEventModifiers::NONE, GetMouseButtonCode()));
}

bool FuPoor::MouseButtonUp (const MouseEvent& rMEvt)
{
    // remember button state for creation of own MouseEvents
    SetMouseButtonCode(rMEvt.GetButtons());

    aDelayToScrollTimer.Stop ();
    bScrollable = bDelayActive = false;
    return bScrollable;
}

bool FuPoor::MouseButtonDown(const MouseEvent& rMEvt)
{
    // remember button state for creation of own MouseEvents
    SetMouseButtonCode(rMEvt.GetButtons());

    return false;
}

void FuPoor::StartDelayToScrollTimer ()
{
    bDelayActive = true;
    aDelayToScrollTimer.Start ();
}

bool FuPoor::RequestHelp(const HelpEvent& rHEvt)
{
    bool bReturn = false;

    SdrPageView* pPV = mpView->GetSdrPageView();

    if (pPV)
    {
        SdPage* pPage = static_cast<SdPage*>( pPV->GetPage() );

        if (pPage)
        {
            bReturn = FmFormPage::RequestHelp(mpWindow, mpView, rHEvt);
        }
    }

    return bReturn;
}

void FuPoor::ReceiveRequest(SfxRequest& /*rReq*/)
{
}

rtl::Reference<SdrObject> FuPoor::CreateDefaultObject(const sal_uInt16, const ::tools::Rectangle& )
{
    // empty base implementation
    return nullptr;
}

void FuPoor::ImpForceQuadratic(::tools::Rectangle& rRect)
{
    if(rRect.GetWidth() > rRect.GetHeight())
    {
        rRect = ::tools::Rectangle(
            Point(rRect.Left() + ((rRect.GetWidth() - rRect.GetHeight()) / 2), rRect.Top()),
            Size(rRect.GetHeight(), rRect.GetHeight()));
    }
    else
    {
        rRect = ::tools::Rectangle(
            Point(rRect.Left(), rRect.Top() + ((rRect.GetHeight() - rRect.GetWidth()) / 2)),
            Size(rRect.GetWidth(), rRect.GetWidth()));
    }
}

void FuPoor::SwitchLayer (sal_Int32 nOffset)
{
    auto pDrawViewShell = dynamic_cast<DrawViewShell *>( &mrViewShell );
    if(!pDrawViewShell)
        return;

    // Calculate the new index.
    sal_Int32 nIndex = pDrawViewShell->GetActiveTabLayerIndex() + nOffset;

    // Make sure the new index lies inside the range of valid indices.
    if (nIndex < 0)
        nIndex = 0;
    else if (nIndex >= pDrawViewShell->GetTabLayerCount ())
        nIndex = pDrawViewShell->GetTabLayerCount() - 1;

    // Set the new active layer.
    if (nIndex != pDrawViewShell->GetActiveTabLayerIndex ())
    {
        LayerTabBar* pLayerTabControl =
            static_cast<DrawViewShell*>(&mrViewShell)->GetLayerTabControl();
        if (pLayerTabControl != nullptr)
            pLayerTabControl->SendDeactivatePageEvent ();

        pDrawViewShell->SetActiveTabLayerIndex (nIndex);

        if (pLayerTabControl != nullptr)
            pLayerTabControl->SendActivatePageEvent ();
    }
}

/** is called when the current function should be aborted. <p>
    This is used when a function gets a KEY_ESCAPE but can also
    be called directly.

    @returns true if an active function was aborted
*/
bool FuPoor::cancel()
{
    if ( dynamic_cast< const FuSelection *>( this ) ==  nullptr )
    {
        mrViewShell.GetViewFrame()->GetDispatcher()->Execute(SID_OBJECT_SELECT, SfxCallMode::ASYNCHRON);
        return true;
    }

    return false;
}

// #i33136#
bool FuPoor::doConstructOrthogonal() const
{
    // Check whether a media object is selected
    bool bResizeKeepRatio = false;
    const SdrMarkList& rMarkList = mpView->GetMarkedObjectList();
    // tdf#89758 Avoid interactive crop preview from being proportionally scaled by default.
    if (rMarkList.GetMarkCount() != 0 && mpView->GetDragMode() != SdrDragMode::Crop)
    {
        if (rMarkList.GetMarkCount() == 1)
        {
            SdrObjKind aObjIdentifier = rMarkList.GetMark(0)->GetMarkedSdrObj()->GetObjIdentifier();
            bResizeKeepRatio = aObjIdentifier == SdrObjKind::Graphic ||
                               aObjIdentifier == SdrObjKind::Media ||
                               aObjIdentifier == SdrObjKind::OLE2;
        }
    }
    SdrHdl* pHdl = mpView->PickHandle(aMDPos);
    // Resize proportionally when media is selected and the user drags on a corner
    if (pHdl)
        bResizeKeepRatio = bResizeKeepRatio && pHdl->IsCornerHdl();

    return (
        bResizeKeepRatio ||
        SID_DRAW_XLINE == nSlotId ||
        SID_DRAW_CIRCLEARC == nSlotId ||
        SID_DRAW_SQUARE == nSlotId ||
        SID_DRAW_SQUARE_NOFILL == nSlotId ||
        SID_DRAW_SQUARE_ROUND == nSlotId ||
        SID_DRAW_SQUARE_ROUND_NOFILL == nSlotId ||
        SID_DRAW_CIRCLE == nSlotId ||
        SID_DRAW_CIRCLE_NOFILL == nSlotId ||
        SID_DRAW_CIRCLEPIE == nSlotId ||
        SID_DRAW_CIRCLEPIE_NOFILL == nSlotId ||
        SID_DRAW_CIRCLECUT == nSlotId ||
        SID_DRAW_CIRCLECUT_NOFILL == nSlotId ||
        SID_DRAW_XPOLYGON == nSlotId ||
        SID_DRAW_XPOLYGON_NOFILL == nSlotId ||
        SID_3D_CUBE == nSlotId ||
        SID_3D_SPHERE == nSlotId ||
        SID_3D_SHELL == nSlotId ||
        SID_3D_HALF_SPHERE == nSlotId ||
        SID_3D_TORUS == nSlotId ||
        SID_3D_CYLINDER == nSlotId ||
        SID_3D_CONE == nSlotId ||
        SID_3D_PYRAMID == nSlotId);
}

void FuPoor::DoExecute( SfxRequest& )
{
}

} // end of namespace sd

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
