Resolves: tdf#147802 don't create a header/footer control for every page

just create a cheaper SwHeaderFooterDashedLine and only create the
"expensive" control on-demand if it becomes visible on-screen and
destroy it immediately if it goes off-screen.

Change-Id: Id876145130d394f55f54790d5e8399dca4f8c03e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/142748
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
diff --git a/sw/source/uibase/docvw/FrameControlsManager.cxx b/sw/source/uibase/docvw/FrameControlsManager.cxx
index bb2733a..03252fa 100644
--- a/sw/source/uibase/docvw/FrameControlsManager.cxx
+++ b/sw/source/uibase/docvw/FrameControlsManager.cxx
@@ -93,7 +93,7 @@ void SwFrameControlsManager::SetHeaderFooterControl( const SwPageFrame* pPageFra
    else
    {
        SwFrameControlPtr pNewControl =
                std::make_shared<SwFrameControl>( VclPtr<SwHeaderFooterWin>::Create(
                std::make_shared<SwFrameControl>( VclPtr<SwHeaderFooterDashedLine>::Create(
                                        m_pEditWin, pPageFrame, bHeader ).get() );
        const SwViewOption* pViewOpt = m_pEditWin->GetView().GetWrtShell().GetViewOptions();
        pNewControl->SetReadonly( pViewOpt->IsReadonly() );
@@ -103,7 +103,7 @@ void SwFrameControlsManager::SetHeaderFooterControl( const SwPageFrame* pPageFra

    tools::Rectangle aPageRect = m_pEditWin->LogicToPixel( pPageFrame->getFrameArea().SVRect() );

    SwHeaderFooterWin* pWin = dynamic_cast<SwHeaderFooterWin *>(pControl->GetWindow());
    SwHeaderFooterDashedLine* pWin = dynamic_cast<SwHeaderFooterDashedLine*>(pControl->GetWindow());
    assert( pWin != nullptr) ;
    assert( pWin->IsHeader() == bHeader );
    pWin->SetOffset( aOffset, aPageRect.Left(), aPageRect.Right() );
diff --git a/sw/source/uibase/docvw/HeaderFooterWin.cxx b/sw/source/uibase/docvw/HeaderFooterWin.cxx
index 24f4102..07ce071 100644
--- a/sw/source/uibase/docvw/HeaderFooterWin.cxx
+++ b/sw/source/uibase/docvw/HeaderFooterWin.cxx
@@ -160,26 +160,89 @@ void SwFrameButtonPainter::PaintButton(drawinglayer::primitive2d::Primitive2DCon
                new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(aPolygon, aLineColor)));
}

SwHeaderFooterWin::SwHeaderFooterWin( SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) :
    SwFrameMenuButtonBase(pEditWin, pFrame, "modules/swriter/ui/hfmenubutton.ui", "HFMenuButton"),
SwHeaderFooterDashedLine::SwHeaderFooterDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader)
    : SwDashedLine(pEditWin, &SwViewOption::GetHeaderFooterMarkColor)
    , m_pEditWin(pEditWin)
    , m_pFrame(pFrame)
    , m_bIsHeader(bHeader)
{
}

bool SwHeaderFooterDashedLine::IsOnScreen()
{
    tools::Rectangle aBounds(GetPosPixel(), GetSizePixel());
    tools::Rectangle aVisArea = GetEditWin()->LogicToPixel(GetEditWin()->GetView().GetVisArea());
    return aBounds.Overlaps(aVisArea);
}

void SwHeaderFooterDashedLine::EnsureWin()
{
    if (!m_pWin)
    {
        m_pWin = VclPtr<SwHeaderFooterWin>::Create(m_pEditWin, m_pFrame, m_bIsHeader);
        m_pWin->SetZOrder(this, ZOrderFlags::Before);
    }
}

void SwHeaderFooterDashedLine::ShowAll(bool bShow)
{
    Show(bShow);
    if (!m_pWin && IsOnScreen())
        EnsureWin();
    if (m_pWin)
        m_pWin->ShowAll(bShow);
}

void SwHeaderFooterDashedLine::SetReadonly(bool bReadonly)
{
    ShowAll(!bReadonly);
}

bool SwHeaderFooterDashedLine::Contains(const Point &rDocPt) const
{
    if (m_pWin && m_pWin->Contains(rDocPt))
        return true;

    ::tools::Rectangle aLineRect(GetPosPixel(), GetSizePixel());
    return aLineRect.Contains(rDocPt);
}

void SwHeaderFooterDashedLine::SetOffset(Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd)
{
    Point aLinePos(nXLineStart, aOffset.Y());
    Size aLineSize(nXLineEnd - nXLineStart, 1);
    SetPosSizePixel(aLinePos, aLineSize);

    bool bOnScreen = IsOnScreen();
    if (!m_pWin && bOnScreen)
    {
        EnsureWin();
        m_pWin->ShowAll(true);
    }
    else if (m_pWin && !bOnScreen)
        m_pWin.disposeAndClear();

    if (m_pWin)
        m_pWin->SetOffset(aOffset);
}

SwHeaderFooterWin::SwHeaderFooterWin(SwEditWin* pEditWin, const SwFrame *pFrame, bool bHeader ) :
    InterimItemWindow(pEditWin, "modules/swriter/ui/hfmenubutton.ui", "HFMenuButton"),
    m_xMenuButton(m_xBuilder->weld_menu_button("menubutton")),
    m_xPushButton(m_xBuilder->weld_button("button")),
    m_pEditWin(pEditWin),
    m_pFrame(pFrame),
    m_bIsHeader( bHeader ),
    m_pLine( nullptr ),
    m_bIsAppearing( false ),
    m_nFadeRate( 100 ),
    m_aFadeTimer("SwHeaderFooterWin m_aFadeTimer")
{
    m_xVirDev = m_xMenuButton->create_virtual_device();
    SetVirDevFont();
    SwFrameMenuButtonBase::SetVirDevFont(*m_xVirDev);

    m_xPushButton->connect_clicked(LINK(this, SwHeaderFooterWin, ClickHdl));
    m_xMenuButton->connect_selected(LINK(this, SwHeaderFooterWin, SelectHdl));

    // Create the line control
    m_pLine = VclPtr<SwDashedLine>::Create(GetEditWin(), &SwViewOption::GetHeaderFooterMarkColor);
    m_pLine->SetZOrder(this, ZOrderFlags::Before);

    // set the PopupMenu
    // Rewrite the menu entries' text
    if (m_bIsHeader)
@@ -204,20 +267,21 @@ SwHeaderFooterWin::~SwHeaderFooterWin( )

void SwHeaderFooterWin::dispose()
{
    m_pLine.disposeAndClear();
    m_xPushButton.reset();
    m_xMenuButton.reset();
    m_pEditWin.clear();
    m_xVirDev.disposeAndClear();
    SwFrameMenuButtonBase::dispose();
    InterimItemWindow::dispose();
}

void SwHeaderFooterWin::SetOffset(Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd)
void SwHeaderFooterWin::SetOffset(Point aOffset)
{
    // Compute the text to show
    const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc();
    bool bIsFirst = !pDesc->IsFirstShared() && GetPageFrame()->OnFirstPage();
    bool bIsLeft  = !pDesc->IsHeaderShared() && !GetPageFrame()->OnRightPage();
    bool bIsRight = !pDesc->IsHeaderShared() && GetPageFrame()->OnRightPage();
    const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame);
    const SwPageDesc* pDesc = pPageFrame->GetPageDesc();
    bool bIsFirst = !pDesc->IsFirstShared() && pPageFrame->OnFirstPage();
    bool bIsLeft  = !pDesc->IsHeaderShared() && !pPageFrame->OnRightPage();
    bool bIsRight = !pDesc->IsHeaderShared() && pPageFrame->OnRightPage();
    m_sLabel = SwResId(STR_HEADER_TITLE);
    if (!m_bIsHeader)
        m_sLabel = bIsFirst ? SwResId(STR_FIRST_FOOTER_TITLE)
@@ -259,13 +323,6 @@ void SwHeaderFooterWin::SetOffset(Point aOffset, tools::Long nXLineStart, tools:

    m_xVirDev->SetOutputSizePixel(aBoxSize);
    PaintButton();

    double nYLinePos = aBoxPos.Y();
    if (!m_bIsHeader)
        nYLinePos += aBoxSize.Height();
    Point aLinePos(nXLineStart, nYLinePos);
    Size aLineSize(nXLineEnd - nXLineStart, 1);
    m_pLine->SetPosSizePixel(aLinePos, aLineSize);
}

void SwHeaderFooterWin::ShowAll(bool bShow)
@@ -284,11 +341,7 @@ void SwHeaderFooterWin::ShowAll(bool bShow)
bool SwHeaderFooterWin::Contains( const Point &rDocPt ) const
{
    ::tools::Rectangle aRect(GetPosPixel(), GetSizePixel());
    if (aRect.Contains(rDocPt))
        return true;

    ::tools::Rectangle aLineRect(m_pLine->GetPosPixel(), m_pLine->GetSizePixel());
    return aLineRect.Contains(rDocPt);
    return aRect.Contains(rDocPt);
}

void SwHeaderFooterWin::PaintButton()
@@ -399,16 +452,17 @@ bool SwHeaderFooterWin::IsEmptyHeaderFooter( ) const
{
    bool bResult = true;

    if (!GetPageFrame())
    const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame);
    if (!pPageFrame)
    {
        return bResult;
    }

    // Actually check it
    const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc();
    const SwPageDesc* pDesc = pPageFrame->GetPageDesc();

    bool const bFirst(GetPageFrame()->OnFirstPage());
    const SwFrameFormat *const pFormat = (GetPageFrame()->OnRightPage())
    bool const bFirst(pPageFrame->OnFirstPage());
    const SwFrameFormat *const pFormat = (pPageFrame->OnRightPage())
        ? pDesc->GetRightFormat(bFirst)
        : pDesc->GetLeftFormat(bFirst);

@@ -425,10 +479,11 @@ bool SwHeaderFooterWin::IsEmptyHeaderFooter( ) const

void SwHeaderFooterWin::ExecuteCommand(std::string_view rIdent)
{
    SwView& rView = GetEditWin()->GetView();
    SwView& rView = m_pEditWin->GetView();
    SwWrtShell& rSh = rView.GetWrtShell();

    const OUString& rStyleName = GetPageFrame()->GetPageDesc()->GetName();
    const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame);
    const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName();
    if (rIdent == "edit")
    {
        OString sPageId = m_bIsHeader ? OString("header") : OString("footer");
@@ -436,7 +491,7 @@ void SwHeaderFooterWin::ExecuteCommand(std::string_view rIdent)
    }
    else if (rIdent == "borderback")
    {
        const SwPageDesc* pDesc = GetPageFrame()->GetPageDesc();
        const SwPageDesc* pDesc = pPageFrame->GetPageDesc();
        const SwFrameFormat& rMaster = pDesc->GetMaster();
        SwFrameFormat* pHFFormat = const_cast< SwFrameFormat* >( rMaster.GetFooter().GetFooterFormat() );
        if ( m_bIsHeader )
@@ -487,17 +542,13 @@ void SwHeaderFooterWin::ExecuteCommand(std::string_view rIdent)
    }
}

void SwHeaderFooterWin::SetReadonly( bool bReadonly )
{
    ShowAll( !bReadonly );
}

IMPL_LINK_NOARG(SwHeaderFooterWin, ClickHdl, weld::Button&, void)
{
    SwView& rView = GetEditWin()->GetView();
    SwView& rView = m_pEditWin->GetView();
    SwWrtShell& rSh = rView.GetWrtShell();

    const OUString& rStyleName = GetPageFrame()->GetPageDesc()->GetName();
    const SwPageFrame* pPageFrame = SwFrameMenuButtonBase::GetPageFrame(m_pFrame);
    const OUString& rStyleName = pPageFrame->GetPageDesc()->GetName();
    rSh.ChangeHeaderOrFooter( rStyleName, m_bIsHeader, true, false );

    m_xPushButton->hide();
@@ -520,12 +571,10 @@ IMPL_LINK_NOARG(SwHeaderFooterWin, FadeHandler, Timer *, void)
    if (m_nFadeRate != 100 && !IsVisible())
    {
        Show();
        m_pLine->Show();
    }
    else if (m_nFadeRate == 100 && IsVisible())
    {
        Show(false);
        m_pLine->Show(false);
    }
    else
        PaintButton();
diff --git a/sw/source/uibase/inc/HeaderFooterWin.hxx b/sw/source/uibase/inc/HeaderFooterWin.hxx
index d91f35e..54d8136 100644
--- a/sw/source/uibase/inc/HeaderFooterWin.hxx
+++ b/sw/source/uibase/inc/HeaderFooterWin.hxx
@@ -10,6 +10,7 @@
#define INCLUDED_SW_SOURCE_UIBASE_INC_HEADERFOOTERWIN_HXX

#include "edtwin.hxx"
#include "DashedLine.hxx"
#include "FrameControl.hxx"
#include <vcl/timer.hxx>
#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
@@ -26,39 +27,68 @@ public:
                            const tools::Rectangle& rRect, bool bOnTop);
};

class SwHeaderFooterWin;

/** Class for the header and footer separator control window.

    This control is showing the header / footer style name and provides
    a few useful actions to the user.
  */
class SwHeaderFooterWin final : public SwFrameMenuButtonBase
class SwHeaderFooterDashedLine : public SwDashedLine, public ISwFrameControl
{
private:
    VclPtr<SwHeaderFooterWin> m_pWin;
    VclPtr<SwEditWin> m_pEditWin;
    const SwFrame* m_pFrame;
    bool m_bIsHeader;

    void EnsureWin();

    bool IsOnScreen();

public:
    SwHeaderFooterDashedLine(SwEditWin* pEditWin, const SwFrame *pFrame, bool bIsHeader);

    virtual ~SwHeaderFooterDashedLine() override { disposeOnce(); }
    virtual void dispose() override { m_pWin.disposeAndClear(); m_pEditWin.clear(); SwDashedLine::dispose(); }

    virtual const SwFrame* GetFrame() override { return m_pFrame; }
    virtual SwEditWin* GetEditWin() override { return m_pEditWin; }
    virtual void ShowAll(bool bShow) override;
    virtual bool Contains(const Point &rDocPt) const override;
    virtual void SetReadonly(bool bReadonly) override;

    void SetOffset( Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd );
    bool IsHeader() const { return m_bIsHeader; };
};

class SwHeaderFooterWin final : public InterimItemWindow
{
    std::unique_ptr<weld::MenuButton> m_xMenuButton;
    std::unique_ptr<weld::Button> m_xPushButton;
    VclPtr<SwEditWin>     m_pEditWin;
    VclPtr<VirtualDevice> m_xVirDev;
    const SwFrame*        m_pFrame;
    OUString              m_sLabel;
    bool                  m_bIsHeader;
    VclPtr<vcl::Window>   m_pLine;
    bool                  m_bIsAppearing;
    int                   m_nFadeRate;
    Timer                 m_aFadeTimer;

public:
    SwHeaderFooterWin( SwEditWin *pEditWin, const SwFrame *pFrame, bool bHeader );
    SwHeaderFooterWin(SwEditWin *pEditWin, const SwFrame *pFrame, bool bHeader);
    virtual ~SwHeaderFooterWin( ) override;
    virtual void dispose() override;

    void SetOffset( Point aOffset, tools::Long nXLineStart, tools::Long nXLineEnd );

    virtual void ShowAll( bool bShow ) override;
    virtual bool Contains( const Point &rDocPt ) const override;
    void SetOffset(Point aOffset);
    void ShowAll(bool bShow);
    bool Contains(const Point &rDocPt) const;

    bool IsHeader() const { return m_bIsHeader; };
    bool IsEmptyHeaderFooter( ) const;

    void ExecuteCommand(std::string_view rIdent);

    void SetReadonly( bool bReadonly ) override;

private:
    DECL_LINK(FadeHandler, Timer *, void);
    DECL_LINK(ClickHdl, weld::Button&, void);