/* -*- 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 <LayerTabBar.hxx>
#include <svx/svdlayer.hxx>
#include <svx/svdpagv.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>

#include <helpids.h>
#include <app.hrc>
#include <strings.hrc>

#include <DrawViewShell.hxx>
#include <View.hxx>
#include <drawdoc.hxx>
#include <sdresid.hxx>
#include <unokywds.hxx>
#include <DrawDocShell.hxx>
#include <drawview.hxx>
#include <undolayer.hxx>

#include <svx/sdr/overlay/overlaymanager.hxx>
#include <svx/sdr/overlay/overlayselection.hxx>
#include <svx/svditer.hxx>
#include <sdpage.hxx>
#include <svx/sdrpaintwindow.hxx>

#include <officecfg/Office/Draw.hxx>

namespace sd {

/**
 * default constructor
 */
LayerTabBar::LayerTabBar(DrawViewShell* pViewSh, vcl::Window* pParent)
    : TabBar( pParent, WinBits( WB_BORDER | WB_3DLOOK | WB_SCROLL ) ),
    DropTargetHelper( this ),
    pDrViewSh(pViewSh),
    m_aBringLayerObjectsToAttentionDelayTimer("LayerTabBar m_aBringLayerObjectsToAttentionDelayTimer")
{
    EnableEditMode();
    SetSizePixel(Size(0, 0));
    SetMaxPageWidth( 150 );
    SetHelpId( HID_SD_TABBAR_LAYERS );

    m_aBringLayerObjectsToAttentionDelayTimer.SetInvokeHandler(
                LINK(this, LayerTabBar, BringLayerObjectsToAttentionDelayTimerHdl));
    m_aBringLayerObjectsToAttentionDelayTimer.SetTimeout(500);
}

LayerTabBar::~LayerTabBar()
{
    disposeOnce();
}

void LayerTabBar::dispose()
{
    DropTargetHelper::dispose();
    TabBar::dispose();
}

OUString LayerTabBar::convertToLocalizedName(const OUString& rName)
{
    if ( rName == sUNO_LayerName_background )
        return SdResId( STR_LAYER_BCKGRND );

    if ( rName == sUNO_LayerName_background_objects )
        return SdResId( STR_LAYER_BCKGRNDOBJ );

    if ( rName == sUNO_LayerName_layout )
        return SdResId( STR_LAYER_LAYOUT );

    if ( rName == sUNO_LayerName_controls )
        return SdResId( STR_LAYER_CONTROLS );

    if ( rName == sUNO_LayerName_measurelines )
        return SdResId( STR_LAYER_MEASURELINES );

    return rName;
}

// Use a method name, that is specific to LayerTabBar to make code better readable
const OUString & LayerTabBar::GetLayerName(sal_uInt16 nPageId) const
{
    return GetAuxiliaryText(nPageId);
}

void LayerTabBar::SetLayerName( sal_uInt16 nPageId, const OUString& rText )
{
    SetAuxiliaryText(nPageId, rText);
}

// Here "Page" is a tab in the LayerTabBar.
void LayerTabBar::InsertPage( sal_uInt16 nPageId, const OUString& rText,
                                TabBarPageBits nBits, sal_uInt16 nPos)
{
    OUString sLocalizedName(convertToLocalizedName(rText));
    TabBar::InsertPage(nPageId, sLocalizedName, nBits, nPos );
    SetLayerName(nPageId, rText);
}

void LayerTabBar::SetPageText( sal_uInt16 nPageId, const OUString& rText )
{
    OUString sLocalizedName(convertToLocalizedName(rText));
    SetLayerName(nPageId, rText);
    TabBar::SetPageText(nPageId, sLocalizedName);
}

bool LayerTabBar::IsLocalizedNameOfStandardLayer(std::u16string_view rName)
{
    return (   rName == SdResId(STR_LAYER_LAYOUT)
            || rName == SdResId(STR_LAYER_CONTROLS)
            || rName == SdResId(STR_LAYER_MEASURELINES)
            || rName == SdResId(STR_LAYER_BCKGRND)
            || rName == SdResId(STR_LAYER_BCKGRNDOBJ) );
}

bool LayerTabBar::IsRealNameOfStandardLayer(std::u16string_view rName)
{
    return (   rName == sUNO_LayerName_layout
            || rName == sUNO_LayerName_controls
            || rName == sUNO_LayerName_measurelines
            || rName == sUNO_LayerName_background
            || rName == sUNO_LayerName_background_objects );
}

void LayerTabBar::Select()
{
    if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
    {
        SfxDispatcher* pDispatcher = pFrame->GetDispatcher();
        pDispatcher->Execute(SID_SWITCHLAYER, SfxCallMode::SYNCHRON);
    }
}

void LayerTabBar::MouseMove(const MouseEvent &rMEvt)
{
    sal_uInt16 nPageId = 0;
    if (!rMEvt.IsLeaveWindow())
        nPageId = GetPageId(rMEvt.GetPosPixel());
    BringLayerObjectsToAttention(nPageId);
    return;
}

void LayerTabBar::BringLayerObjectsToAttention(const sal_uInt16 nPageId)
{
    if (nPageId == m_nBringLayerObjectsToAttentionLastPageId)
        return;

    m_aBringLayerObjectsToAttentionDelayTimer.Stop();

    if (m_xOverlayObject && m_xOverlayObject->getOverlayManager())
        m_xOverlayObject->getOverlayManager()->remove(*m_xOverlayObject);

    m_nBringLayerObjectsToAttentionLastPageId = nPageId;

    std::vector<basegfx::B2DRange> aRanges;

    if (nPageId != 0)
    {
        sal_uInt16 nDisableLayerObjectsOverlay
            = officecfg::Office::Draw::Misc::DisableLayerHighlighting::get();
        OUString aLayerName(GetLayerName(nPageId));
        if (nDisableLayerObjectsOverlay > 0 // don't show tooltip message when 0 - meaning feature is turned off
            && pDrViewSh->GetView()->GetSdrPageView()->IsLayerVisible(aLayerName))
        {
            SdrLayerAdmin& rLayerAdmin = pDrViewSh->GetDoc()->GetLayerAdmin();
            SdrObjListIter aIter(pDrViewSh->GetActualPage(), SdrIterMode::DeepWithGroups);
            while (aIter.IsMore())
            {
                SdrObject* pObj = aIter.Next();
                assert(pObj != nullptr);
                if (!pObj)
                    continue;
                const SdrLayer* pSdrLayer = rLayerAdmin.GetLayerPerID(pObj->GetLayer());
                if (!pSdrLayer)
                    continue;
                if (aLayerName == pSdrLayer->GetName())
                {
                    ::tools::Rectangle aRect(pObj->GetLogicRect());
                    if (!aRect.IsEmpty())
                    {
                        aRanges.emplace_back(aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom());
                        if (aRanges.size() > nDisableLayerObjectsOverlay)
                        {
                            OUString sHelpText = SdResId(STR_LAYER_HIGHLIGHTING_DISABLED);
                            sHelpText = sHelpText.replaceFirst(
                                "%1", OUString::number(nDisableLayerObjectsOverlay));
                            SetQuickHelpText(sHelpText);
                            m_xOverlayObject.reset();
                            return;
                        }
                    }
                    // skip over objects in groups
                    if (pObj->IsGroupObject())
                    {
                        SdrObjListIter aSubListIter(pObj->GetSubList(), SdrIterMode::DeepWithGroups);
                        while (aSubListIter.IsMore())
                        {
                            aIter.Next();
                            aSubListIter.Next();
                        }
                    }
                }
            }
        }
    }

    SetQuickHelpText(u""_ustr);

    if (aRanges.empty())
        m_xOverlayObject.reset();
    else
    {
        m_xOverlayObject.reset(new sdr::overlay::OverlaySelection(
                                   sdr::overlay::OverlayType::Invert,
                                   Color(), std::move(aRanges), true/*unused for Invert type*/));
        m_aBringLayerObjectsToAttentionDelayTimer.Start();
    }
}

IMPL_LINK_NOARG(LayerTabBar, BringLayerObjectsToAttentionDelayTimerHdl, Timer *, void)
{
    m_aBringLayerObjectsToAttentionDelayTimer.Stop();
    if (m_xOverlayObject)
    {
        if (SdrView* pView = pDrViewSh->GetDrawView())
        {
            if (SdrPaintWindow* pPaintWindow = pView->GetPaintWindow(0))
            {
                const rtl::Reference<sdr::overlay::OverlayManager>& xOverlayManager =
                        pPaintWindow->GetOverlayManager();
                xOverlayManager->add(*m_xOverlayObject);
            }
        }
    }
}

void LayerTabBar::MouseButtonDown(const MouseEvent& rMEvt)
{
    bool bSetPageID=false;

    if (rMEvt.IsLeft())
    {
        Point aPosPixel = rMEvt.GetPosPixel();
        sal_uInt16 aTabId = GetPageId( PixelToLogic(aPosPixel) );
        if (aTabId == 0)
        {
            if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
            {
                SfxDispatcher* pDispatcher = pFrame->GetDispatcher();
                pDispatcher->Execute(SID_INSERTLAYER, SfxCallMode::SYNCHRON);

                bSetPageID=true;
            }
        }
        else if (rMEvt.IsMod2())
        {
            // direct editing of tab text
            // make sure the clicked tab is the current tab otherwise Edit() acts on the wrong tab
            if ( aTabId != GetCurPageId())
            {
                MouseEvent aSyntheticEvent (rMEvt.GetPosPixel(), 1, MouseEventModifiers::SYNTHETIC, MOUSE_LEFT, 0);
                TabBar::MouseButtonDown(aSyntheticEvent);
            }
        }
        else if (rMEvt.IsMod1() || rMEvt.IsShift())
        {
            // keyboard Shortcuts to change layer attributes

            OUString aName(GetLayerName(aTabId));
            SdrPageView* pPV = pDrViewSh->GetView()->GetSdrPageView();

            // Save old state

            bool bOldPrintable = pPV->IsLayerPrintable(aName);
            bool bOldVisible = pPV->IsLayerVisible(aName);
            bool bOldLocked = pPV->IsLayerLocked(aName);

            bool bNewPrintable = bOldPrintable;
            bool bNewVisible = bOldVisible;
            bool bNewLocked = bOldLocked;

            if (rMEvt.IsMod1() && rMEvt.IsShift())
            {
                // Shift+Ctrl: Toggle between layer printable / not printable
                bNewPrintable = !bOldPrintable;
                pPV->SetLayerPrintable(aName, bNewPrintable);
            }
            else if (rMEvt.IsShift())
            {
                // Shift: Toggle between layer visible / hidden
                // see also SID_TOGGLELAYERVISIBILITY / tdf#113439
                bNewVisible = !bOldVisible;
                pPV->SetLayerVisible(aName, bNewVisible);
            }
            else // if (rMEvt.IsMod1())
            {
                // Ctrl: Toggle between layer locked / unlocked
                bNewLocked = !bOldLocked;
                pPV->SetLayerLocked(aName, bNewLocked);
            }

            pDrViewSh->ResetActualLayer();

            // Add Undo action

            ::sd::View* pView = pDrViewSh->GetView();
            DrawView* pDrView = dynamic_cast<DrawView*>(pView);

            SdDrawDocument& rDoc = pView->GetDoc();
            SdrLayer* pLayer = rDoc.GetLayerAdmin().GetLayer(aName);

            if (pLayer)
            {
                assert (pDrView && "Change layer attribute undo action is only working with a SdDrawView");
                if(pDrView)
                {
                    SfxUndoManager* pManager = rDoc.GetDocSh()->GetUndoManager();
                    std::unique_ptr<SdLayerModifyUndoAction> pAction(new SdLayerModifyUndoAction(
                        rDoc,
                        pLayer,
                        aName,
                        pLayer->GetTitle(),
                        pLayer->GetDescription(),
                        bOldVisible,
                        bOldLocked,
                        bOldPrintable,
                        aName,
                        pLayer->GetTitle(),
                        pLayer->GetDescription(),
                        bNewVisible,
                        bNewLocked,
                        bNewPrintable
                        ));
                    pManager->AddUndoAction(std::move(pAction));
                }
            }

            // Mark document changed

            pView->GetDoc().SetChanged();
        }
    }

    // If you insert a new layer you must not call TabBar::MouseButtonDown(rMEvt);
    // because you want to activate the new layer
    if( !bSetPageID )
        TabBar::MouseButtonDown(rMEvt);
}

void LayerTabBar::DoubleClick()
{
    if (GetCurPageId() != 0)
    {
        if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
        {
            SfxDispatcher* pDispatcher = pFrame->GetDispatcher();
            pDispatcher->Execute( SID_MODIFYLAYER, SfxCallMode::SYNCHRON );
        }
    }
}

/**
 * AcceptDrop-Event
 */

sal_Int8 LayerTabBar::AcceptDrop( const AcceptDropEvent& rEvt )
{
    sal_Int8 nRet = DND_ACTION_NONE;

    if( rEvt.mbLeaving )
        EndSwitchPage();

    if( !pDrViewSh->GetDocSh()->IsReadOnly() )
    {
        Point         aPos( PixelToLogic( rEvt.maPosPixel ) );
        OUString      sLayerName( GetLayerName(GetPageId(aPos)) );
        SdrLayerID    nLayerId = pDrViewSh->GetView()->GetDoc().GetLayerAdmin().GetLayerID(sLayerName);

        nRet = pDrViewSh->AcceptDrop( rEvt, *this, nullptr, SDRPAGE_NOTFOUND, nLayerId );

        SwitchPage( aPos );
    }

    return nRet;
}

/**
 * ExecuteDrop-Event
 */
sal_Int8 LayerTabBar::ExecuteDrop( const ExecuteDropEvent& rEvt )
{
    Point         aPos( PixelToLogic(rEvt.maPosPixel) );
    OUString      sLayerName( GetLayerName(GetPageId(aPos)) );
    SdrLayerID    nLayerId = pDrViewSh->GetView()->GetDoc().GetLayerAdmin().GetLayerID(sLayerName);

    sal_Int8        nRet = pDrViewSh->ExecuteDrop( rEvt, *this, nullptr, SDRPAGE_NOTFOUND, nLayerId );

    EndSwitchPage();

    return nRet;

}

void  LayerTabBar::Command(const CommandEvent& rCEvt)
{
    if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
    {
        BringLayerObjectsToAttention();
        if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
        {
            SfxDispatcher* pDispatcher = pFrame->GetDispatcher();
            pDispatcher->ExecutePopup(u"layertab"_ustr);
        }
    }
}

bool LayerTabBar::StartRenaming()
{
    bool bOK = true;
    OUString aLayerName = GetLayerName( GetEditPageId() );

    if ( IsRealNameOfStandardLayer(aLayerName))
    {
        // It is not allowed to change these names
        bOK = false;
    }
    else
    {
        ::sd::View* pView = pDrViewSh->GetView();

        if ( pView->IsTextEdit() )
        {
            pView->SdrEndTextEdit();
        }
    }

    return bOK;
}

TabBarAllowRenamingReturnCode LayerTabBar::AllowRenaming()
{
    bool bOK = true;

    // Check if names already exists
    ::sd::View* pView = pDrViewSh->GetView();
    SdDrawDocument& rDoc = pView->GetDoc();
    OUString aLayerName = pView->GetActiveLayer();
    SdrLayerAdmin& rLayerAdmin = rDoc.GetLayerAdmin();
    OUString aNewName( GetEditText() );

    if (aNewName.isEmpty() ||
        (rLayerAdmin.GetLayer( aNewName ) && aLayerName != aNewName) )
    {
        if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
        {
            // Name already exists.
            std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pFrame->GetFrameWeld(),
                                                       VclMessageType::Warning, VclButtonsType::Ok,
                                                       SdResId(STR_WARN_NAME_DUPLICATE)));
            xWarn->run();
            bOK = false;
        }
    }

    if (bOK)
    {
        if ( IsLocalizedNameOfStandardLayer(aNewName) || IsRealNameOfStandardLayer(aNewName) )
        {
            // Standard layer names may not be changed.
            bOK = false;
        }
    }

    return bOK ? TABBAR_RENAMING_YES : TABBAR_RENAMING_NO;
}

void LayerTabBar::EndRenaming()
{
    if( IsEditModeCanceled() )
        return;

    ::sd::View* pView = pDrViewSh->GetView();
    DrawView* pDrView = dynamic_cast<DrawView*>( pView  );

    SdDrawDocument& rDoc = pView->GetDoc();
    OUString aLayerName = pView->GetActiveLayer();
    SdrLayerAdmin& rLayerAdmin = rDoc.GetLayerAdmin();
    SdrLayer* pLayer = rLayerAdmin.GetLayer(aLayerName);

    if (!pLayer)
        return;

    OUString aNewName( GetEditText() );
    assert (pDrView && "Rename layer undo action is only working with a SdDrawView");
    if( pDrView )
    {
        SfxUndoManager* pManager = rDoc.GetDocSh()->GetUndoManager();
        std::unique_ptr<SdLayerModifyUndoAction> pAction(new SdLayerModifyUndoAction(
            rDoc,
            pLayer,
            aLayerName,
            pLayer->GetTitle(),
            pLayer->GetDescription(),
            pDrView->IsLayerVisible(aLayerName),
            pDrView->IsLayerLocked(aLayerName),
            pDrView->IsLayerPrintable(aLayerName),
            aNewName,
            pLayer->GetTitle(),
            pLayer->GetDescription(),
            pDrView->IsLayerVisible(aLayerName),
            pDrView->IsLayerLocked(aLayerName),
            pDrView->IsLayerPrintable(aLayerName)
            ));
        pManager->AddUndoAction( std::move(pAction) );
    }

    // First notify View since SetName() calls ResetActualLayer() and
    // the View then already has to know the Layer
    pView->SetActiveLayer(aNewName);
    pLayer->SetName(aNewName);
    rDoc.SetChanged();
}

void LayerTabBar::ActivatePage()
{
    if (pDrViewSh!=nullptr)
    {
        if (SfxViewFrame* pFrame = pDrViewSh->GetViewFrame())
        {
            SfxDispatcher* pDispatcher = pFrame->GetDispatcher();
            pDispatcher->Execute(SID_SWITCHLAYER, SfxCallMode::ASYNCHRON);
        }
    }
}

void LayerTabBar::SendActivatePageEvent()
{
    CallEventListeners (VclEventId::TabbarPageActivated,
        reinterpret_cast<void*>(GetCurPageId()));
}

void LayerTabBar::SendDeactivatePageEvent()
{
    CallEventListeners (VclEventId::TabbarPageDeactivated,
        reinterpret_cast<void*>(GetCurPageId()));
}

} // end of namespace sd

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