tdf#33603: sd: add notes panel for normal view

Change-Id: Ibc6e8a3f126c443453c5ecab52ba988a4f4f56e6
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159288
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <thorsten.behrens@allotropia.de>
diff --git a/officecfg/registry/data/org/openoffice/Office/UI/DrawImpressCommands.xcu b/officecfg/registry/data/org/openoffice/Office/UI/DrawImpressCommands.xcu
index e935522..b46b7b3 100644
--- a/officecfg/registry/data/org/openoffice/Office/UI/DrawImpressCommands.xcu
+++ b/officecfg/registry/data/org/openoffice/Office/UI/DrawImpressCommands.xcu
@@ -24,6 +24,14 @@
          <value xml:lang="en-US">S~lide</value>
        </prop>
      </node>
      <node oor:name=".uno:NotesChildWindow" oor:op="replace">
        <prop oor:name="Label" oor:type="xs:string">
          <value xml:lang="en-US">Notes Panel</value>
        </prop>
        <prop oor:name="Properties" oor:type="xs:int">
          <value>1</value>
        </prop>
      </node>
      <node oor:name=".uno:PageMenu" oor:op="replace">
        <prop oor:name="Label" oor:type="xs:string">
          <value xml:lang="en-US">~Page</value>
diff --git a/sd/Library_sd.mk b/sd/Library_sd.mk
index 01c546a..bdf7b14 100644
--- a/sd/Library_sd.mk
+++ b/sd/Library_sd.mk
@@ -231,6 +231,7 @@ $(eval $(call gb_Library_add_exception_objects,sd,\
	sd/source/ui/dlg/AnimationChildWindow \
	sd/source/ui/dlg/LayerTabBar \
	sd/source/ui/dlg/NavigatorChildWindow \
	sd/source/ui/dlg/NotesChildWindow \
	sd/source/ui/dlg/PaneChildWindows \
	sd/source/ui/dlg/PaneShells \
	sd/source/ui/dlg/SpellDialogChildWindow \
diff --git a/sd/UIConfig_simpress.mk b/sd/UIConfig_simpress.mk
index 9d98d61..ee778ea 100644
--- a/sd/UIConfig_simpress.mk
+++ b/sd/UIConfig_simpress.mk
@@ -148,6 +148,8 @@ $(eval $(call gb_UIConfig_add_uifiles,modules/simpress,\
	sd/uiconfig/simpress/ui/notebookbar_groupedbar_full \
	sd/uiconfig/simpress/ui/notebookbar_groupedbar_compact \
	sd/uiconfig/simpress/ui/notebookbar_online \
	sd/uiconfig/simpress/ui/noteschildwindow \
	sd/uiconfig/simpress/ui/notespanelcontextmenu \
	sd/uiconfig/simpress/ui/optimpressgeneralpage \
	sd/uiconfig/simpress/ui/pagesfieldbox \
	sd/uiconfig/simpress/ui/photoalbum \
diff --git a/sd/inc/strings.hrc b/sd/inc/strings.hrc
index 08a5bb7..5056a80 100644
--- a/sd/inc/strings.hrc
+++ b/sd/inc/strings.hrc
@@ -289,6 +289,7 @@
#define STR_PRESOBJ_OUTLINE                             NC_("STR_PRESOBJ_OUTLINE", "Click to add Text" )
#define STR_PRESOBJ_TEXT                                NC_("STR_PRESOBJ_TEXT", "Click to add Text" )
#define STR_PRESOBJ_NOTESTEXT                           NC_("STR_PRESOBJ_NOTESTEXT", "Click to add Notes" )
#define STR_PRESOBJ_NOTESTEXT_MISSING                   NC_("STR_PRESOBJ_NOTESTEXT_MISSING", "Notes placeholder object is missing for the current slide." )
#define STR_PRESOBJ_TITLE_MOBILE                        NC_("STR_PRESOBJ_TITLE_MOBILE", "Double-tap to add Title" )
#define STR_PRESOBJ_OUTLINE_MOBILE                      NC_("STR_PRESOBJ_OUTLINE_MOBILE", "Double-tap to add Text" )
#define STR_PRESOBJ_TEXT_MOBILE                         NC_("STR_PRESOBJ_TEXT_MOBILE", "Double-tap to add Text" )
diff --git a/sd/source/ui/app/sddll.cxx b/sd/source/ui/app/sddll.cxx
index aded023..b21ce1c5 100644
--- a/sd/source/ui/app/sddll.cxx
+++ b/sd/source/ui/app/sddll.cxx
@@ -48,6 +48,7 @@
#include <OutlineViewShell.hxx>
#include <OutlineViewShellBase.hxx>
#include <PaneChildWindows.hxx>
#include <NotesChildWindow.hxx>
#include <SpellDialogChildWindow.hxx>
#include <SlideSorterViewShell.hxx>
#include <SlideSorterViewShellBase.hxx>
@@ -178,6 +179,7 @@ void SdDLL::RegisterControllers(SdModule* pMod)
    ::sd::LeftPaneImpressChildWindow::RegisterChildWindow(false, pMod);
    ::sd::LeftPaneDrawChildWindow::RegisterChildWindow(false, pMod);
    ::sfx2::sidebar::SidebarChildWindow::RegisterChildWindow(false, pMod);
    ::sd::NotesChildWindow::RegisterChildWindow(false, pMod);
    DevelopmentToolChildWindow::RegisterChildWindow(false, pMod);

    ::sd::SdNavigatorWrapper::RegisterChildWindow(false, pMod, SfxChildWindowFlags::NEVERHIDE);
diff --git a/sd/source/ui/dlg/NotesChildWindow.cxx b/sd/source/ui/dlg/NotesChildWindow.cxx
new file mode 100644
index 0000000..ebcc94d
--- /dev/null
+++ b/sd/source/ui/dlg/NotesChildWindow.cxx
@@ -0,0 +1,632 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * 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/.
 */

#include <DrawDocShell.hxx>
#include <DrawViewShell.hxx>
#include <EventMultiplexer.hxx>
#include <NotesChildWindow.hxx>
#include <Outliner.hxx>
#include <ViewShellBase.hxx>
#include <app.hrc>
#include <config_wasm_strip.h>
#include <drawdoc.hxx>
#include <drawview.hxx>
#include <sdpage.hxx>
#include <sdresid.hxx>

#include <editeng/crossedoutitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editstat.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/numitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wghtitem.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/viewfrm.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/commandinfoprovider.hxx>
#include <vcl/ptrstyle.hxx>
#include <strings.hrc>

namespace sd
{
SFX_IMPL_DOCKINGWINDOW_WITHID(NotesChildWindow, SID_NOTES_WINDOW);

NotesChildWindow::NotesChildWindow(vcl::Window* pParentWindow, sal_uInt16 nId,
                                   SfxBindings* pBindings, SfxChildWinInfo* pInfo)
    : SfxChildWindow(pParentWindow, nId)
{
    VclPtr<NotesChildDockingWindow> pWin
        = VclPtr<NotesChildDockingWindow>::Create(pBindings, this, pParentWindow);
    SetWindow(pWin);
    SetAlignment(SfxChildAlignment::BOTTOM);
    pWin->setDeferredProperties();
    pWin->SetSizePixel({ 200, 200 });
    pWin->set_border_width(3);
    pWin->set_margin_top(11);
    pWin->Initialize(pInfo);
}

NotesChildDockingWindow::NotesChildDockingWindow(SfxBindings* _pBindings,
                                                 SfxChildWindow* pChildWindow, Window* pParent)
    : SfxDockingWindow(_pBindings, pChildWindow, pParent, "NotesChildEditWindow",
                       "modules/simpress/ui/noteschildwindow.ui")
    , mpViewShellBase(ViewShellBase::GetViewShellBase(_pBindings->GetDispatcher()->GetFrame()))
    , m_xEditWindow(
          new NotesEditWindow(*this, m_xBuilder->weld_scrolled_window("scrolledwin", true)))
{
    mpOutliner = std::make_unique<Outliner>(&mpViewShellBase->GetDocShell()->GetPool(),
                                            OutlinerMode::TextObject);

    mpOutlinerView = std::make_unique<OutlinerView>(mpOutliner.get(), nullptr);
    mpOutliner->InsertView(mpOutlinerView.get());

    m_xEditWindowWeld
        = std::make_unique<weld::CustomWeld>(*m_xBuilder, "noteschildeditview", *m_xEditWindow);
}

DrawViewShell* NotesChildDockingWindow::GetDrawViewShell()
{
    auto pDocShell = GetViewShellBase()->GetDocShell();
    if (!pDocShell)
        return nullptr;

    return dynamic_cast<DrawViewShell*>(pDocShell->GetViewShell());
}

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

void NotesChildDockingWindow::dispose()
{
    m_xEditWindow.reset();
    m_xEditWindowWeld.reset();
    SfxDockingWindow::dispose();
}

NotesEditWindow::NotesEditWindow(NotesChildDockingWindow& rParentWindow,
                                 std::unique_ptr<weld::ScrolledWindow> pScrolledWindow)
    : mrParentWindow(rParentWindow)
    , m_xScrolledWindow(std::move(pScrolledWindow))
    , aModifyIdle("NotesEditWindow ModifyIdle")
{
    aModifyIdle.SetInvokeHandler(LINK(this, NotesEditWindow, ModifyTimerHdl));
    aModifyIdle.SetPriority(TaskPriority::LOWEST);

    SetAcceptsTab(true);
    m_xScrolledWindow->connect_vadjustment_changed(LINK(this, NotesEditWindow, ScrollHdl));
}

NotesEditWindow::~NotesEditWindow()
{
    aModifyIdle.Stop();
    m_xScrolledWindow.reset();
    if (!mrParentWindow.GetViewShellBase())
        return;

    mrParentWindow.GetViewShellBase()->GetEventMultiplexer()->RemoveEventListener(
        LINK(this, NotesEditWindow, EventMultiplexerListener));
}

IMPL_LINK(NotesEditWindow, EventMultiplexerListener, tools::EventMultiplexerEvent&, rEvent, void)
{
    switch (rEvent.meEventId)
    {
        case EventMultiplexerEventId::CurrentPageChanged:
        case EventMultiplexerEventId::MainViewRemoved:
        case EventMultiplexerEventId::MainViewAdded:
            provideNoteText();
            break;
        default:
            break;
    }
}

void NotesEditWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea)
{
    Size aSize(pDrawingArea->get_size_request());
    if (aSize.Width() == -1)
        aSize.setWidth(500);
    if (aSize.Height() == -1)
        aSize.setHeight(100);
    pDrawingArea->set_size_request(aSize.Width(), aSize.Height());

    SetOutputSizePixel(aSize);

    weld::CustomWidgetController::SetDrawingArea(pDrawingArea);

    EnableRTL(false);

    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    Color aBgColor = rStyleSettings.GetWindowColor();

    OutputDevice& rDevice = pDrawingArea->get_ref_device();

    rDevice.SetMapMode(MapMode(MapUnit::MapTwip));
    rDevice.SetBackground(aBgColor);

    Size aOutputSize(rDevice.PixelToLogic(aSize));

    EditView* pEditView = GetEditView();
    pEditView->setEditViewCallbacks(this);

    EditEngine* pEditEngine = GetEditEngine();
    pEditEngine->SetPaperSize(aOutputSize);
    pEditEngine->SetRefDevice(&rDevice);

    pEditEngine->SetControlWord(pEditEngine->GetControlWord() | EEControlBits::MARKFIELDS);
    pEditView->SetOutputArea(::tools::Rectangle(Point(0, 0), aOutputSize));

    pEditView->SetBackgroundColor(aBgColor);

    pDrawingArea->set_cursor(PointerStyle::Text);

#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    InitAccessible();
#endif

    mrParentWindow.GetViewShellBase()->GetEventMultiplexer()->AddEventListener(
        LINK(this, NotesEditWindow, EventMultiplexerListener));

    // For setGlobalScale to work correctly EEControlBits::STRETCHING must be set.
    mrParentWindow.GetOutliner()->SetControlWord(mrParentWindow.GetOutliner()->GetControlWord()
                                                 | EEControlBits::STRETCHING);
    mrParentWindow.GetOutliner()->setGlobalScale(30.0, 30.0);

    provideNoteText();

    GetEditEngine()->SetStatusEventHdl(LINK(this, NotesEditWindow, EditStatusHdl));
}

void NotesEditWindow::doScroll()
{
    if (m_xEditView)
    {
        auto currentDocPos = m_xEditView->GetVisArea().Top();
        auto nDiff = currentDocPos - m_xScrolledWindow->vadjustment_get_value();
        // we expect SetScrollBarRange callback to be triggered by Scroll
        // to set where we ended up
        m_xEditView->Scroll(0, nDiff);
    }
}

void NotesEditWindow::setScrollBarRange()
{
    EditEngine* pEditEngine = GetEditEngine();
    if (!pEditEngine)
        return;
    if (!m_xScrolledWindow)
        return;
    EditView* pEditView = GetEditView();
    if (!pEditView)
        return;

    int nVUpper = pEditEngine->GetTextHeight();
    int nVCurrentDocPos = pEditView->GetVisArea().Top();
    const Size aOut(pEditView->GetOutputArea().GetSize());
    int nVStepIncrement = aOut.Height() * 2 / 10;
    int nVPageIncrement = aOut.Height() * 8 / 10;
    int nVPageSize = aOut.Height();

    /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has
       effectively...

       lower = gtk_adjustment_get_lower
       upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size

       and requires that upper > lower or the deceleration animation never ends
    */
    nVPageSize = std::min(nVPageSize, nVUpper);

    m_xScrolledWindow->vadjustment_configure(nVCurrentDocPos, 0, nVUpper, nVStepIncrement,
                                             nVPageIncrement, nVPageSize);
}

void NotesEditWindow::showContextMenu(const Point& rPos)
{
    ::tools::Rectangle aRect(rPos, Size(1, 1));
    weld::Widget* pPopupParent = GetDrawingArea();
    std::unique_ptr<weld::Builder> xBuilder(
        Application::CreateBuilder(pPopupParent, "modules/simpress/ui/notespanelcontextmenu.ui"));
    std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu"));

    auto xFrame = mrParentWindow.GetDrawViewShell()->GetViewFrame()->GetFrame().GetFrameInterface();
    OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame));

    SfxItemSet aSet(mrParentWindow.GetOutlinerView()->GetAttribs());
    int nInsertPos = 0;
    xMenu->insert(nInsertPos++, ".uno:Bold",
                  vcl::CommandInfoProvider::GetMenuLabelForCommand(
                      vcl::CommandInfoProvider::GetCommandProperties(".uno:Bold", aModuleName)),
                  nullptr, nullptr,
                  vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Bold", xFrame),
                  TRISTATE_TRUE);

    if (aSet.GetItemState(EE_CHAR_WEIGHT) == SfxItemState::SET)
    {
        if (aSet.Get(EE_CHAR_WEIGHT).GetWeight() == WEIGHT_BOLD)
            xMenu->set_active(".uno:Bold", true);
    }

    xMenu->insert(nInsertPos++, ".uno:Italic",
                  vcl::CommandInfoProvider::GetMenuLabelForCommand(
                      vcl::CommandInfoProvider::GetCommandProperties(".uno:Italic", aModuleName)),
                  nullptr, nullptr,
                  vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Italic", xFrame),
                  TRISTATE_TRUE);

    if (aSet.GetItemState(EE_CHAR_ITALIC) == SfxItemState::SET)
    {
        if (aSet.Get(EE_CHAR_ITALIC).GetPosture() != ITALIC_NONE)
            xMenu->set_active(".uno:Italic", true);
    }

    xMenu->insert(
        nInsertPos++, ".uno:Underline",
        vcl::CommandInfoProvider::GetMenuLabelForCommand(
            vcl::CommandInfoProvider::GetCommandProperties(".uno:Underline", aModuleName)),
        nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Underline", xFrame),
        TRISTATE_TRUE);

    if (aSet.GetItemState(EE_CHAR_UNDERLINE) == SfxItemState::SET)
    {
        if (aSet.Get(EE_CHAR_UNDERLINE).GetLineStyle() != LINESTYLE_NONE)
            xMenu->set_active(".uno:Underline", true);
    }

    xMenu->insert(
        nInsertPos++, ".uno:Strikeout",
        vcl::CommandInfoProvider::GetMenuLabelForCommand(
            vcl::CommandInfoProvider::GetCommandProperties(".uno:Strikeout", aModuleName)),
        nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Strikeout", xFrame),
        TRISTATE_TRUE);

    if (aSet.GetItemState(EE_CHAR_STRIKEOUT) == SfxItemState::SET)
    {
        if (aSet.Get(EE_CHAR_STRIKEOUT).GetStrikeout() != STRIKEOUT_NONE)
            xMenu->set_active(".uno:Strikeout", true);
    }

    xMenu->insert_separator(nInsertPos++, "separator2");

    xMenu->insert(nInsertPos++, ".uno:Copy",
                  vcl::CommandInfoProvider::GetMenuLabelForCommand(
                      vcl::CommandInfoProvider::GetCommandProperties(".uno:Copy", aModuleName)),
                  nullptr, nullptr,
                  vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Copy", xFrame),
                  TRISTATE_INDET);

    xMenu->insert(nInsertPos++, ".uno:Paste",
                  vcl::CommandInfoProvider::GetMenuLabelForCommand(
                      vcl::CommandInfoProvider::GetCommandProperties(".uno:Paste", aModuleName)),
                  nullptr, nullptr,
                  vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Paste", xFrame),
                  TRISTATE_INDET);

    bool bCanPaste = false;
    {
        TransferableDataHelper aDataHelper(
            TransferableDataHelper::CreateFromClipboard(GetClipboard()));
        bCanPaste = aDataHelper.GetFormatCount() != 0;
    }

    xMenu->insert_separator(nInsertPos++, "separator3");

    xMenu->insert(
        nInsertPos++, ".uno:DefaultBullet",
        vcl::CommandInfoProvider::GetMenuLabelForCommand(
            vcl::CommandInfoProvider::GetCommandProperties(".uno:DefaultBullet", aModuleName)),
        nullptr, nullptr,
        vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:DefaultBullet", xFrame),
        TRISTATE_TRUE);

    ESelection aSel(GetEditView()->GetSelection());
    aSel.Adjust();
    bool bBulletsEnabled = true;
    for (sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++)
    {
        if (mrParentWindow.GetOutliner()->GetDepth(nPara) == -1)
        {
            bBulletsEnabled = false;
            break;
        }
    }

    if (bBulletsEnabled)
        xMenu->set_active(".uno:DefaultBullet", true);

    xMenu->insert(
        nInsertPos++, ".uno:OutlineLeft",
        vcl::CommandInfoProvider::GetMenuLabelForCommand(
            vcl::CommandInfoProvider::GetCommandProperties(".uno:OutlineLeft", aModuleName)),
        nullptr, nullptr,
        vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:OutlineLeft", xFrame),
        TRISTATE_INDET);

    xMenu->insert(
        nInsertPos++, ".uno:OutlineRight",
        vcl::CommandInfoProvider::GetMenuLabelForCommand(
            vcl::CommandInfoProvider::GetCommandProperties(".uno:OutlineRight", aModuleName)),
        nullptr, nullptr,
        vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:OutlineRight", xFrame),
        TRISTATE_INDET);

    xMenu->set_sensitive(".uno:Copy", mrParentWindow.GetOutlinerView()->HasSelection());
    xMenu->set_sensitive(".uno:Paste", bCanPaste);

    auto sId = xMenu->popup_at_rect(pPopupParent, aRect);

    if (sId == ".uno:Copy")
    {
        mrParentWindow.GetOutlinerView()->Copy();
    }
    else if (sId == ".uno:Paste")
    {
        mrParentWindow.GetOutlinerView()->PasteSpecial();
    }
    else if (sId == ".uno:DefaultBullet")
    {
        mrParentWindow.GetOutlinerView()->ToggleBullets();
    }
    else if (sId == ".uno:OutlineLeft" || sId == ".uno:OutlineRight")
    {
        bool isOutlineLeft = sId == ".uno:OutlineLeft";
        for (sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++)
        {
            auto nCurrentDepth = mrParentWindow.GetOutliner()->GetDepth(nPara);
            if (nCurrentDepth == -1)
                continue;

            mrParentWindow.GetOutlinerView()->SetDepth(nPara, isOutlineLeft ? --nCurrentDepth
                                                                            : ++nCurrentDepth);
        }
    }
    else if (!sId.isEmpty())
    {
        SfxItemSet aEditAttr(mrParentWindow.GetOutlinerView()->GetAttribs());
        SfxItemSet aNewAttr(mrParentWindow.GetOutliner()->GetEmptyItemSet());

        if (sId == ".uno:Bold")
        {
            FontWeight eFW = aEditAttr.Get(EE_CHAR_WEIGHT).GetWeight();
            aNewAttr.Put(
                SvxWeightItem(eFW == WEIGHT_NORMAL ? WEIGHT_BOLD : WEIGHT_NORMAL, EE_CHAR_WEIGHT));
        }
        else if (sId == ".uno:Italic")
        {
            FontItalic eFI = aEditAttr.Get(EE_CHAR_ITALIC).GetPosture();
            aNewAttr.Put(
                SvxPostureItem(eFI == ITALIC_NORMAL ? ITALIC_NONE : ITALIC_NORMAL, EE_CHAR_ITALIC));
        }
        else if (sId == ".uno:Underline")
        {
            FontLineStyle eFU = aEditAttr.Get(EE_CHAR_UNDERLINE).GetLineStyle();
            aNewAttr.Put(SvxUnderlineItem(
                eFU == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, EE_CHAR_UNDERLINE));
        }
        else if (sId == ".uno:Strikeout")
        {
            FontStrikeout eFSO = aEditAttr.Get(EE_CHAR_STRIKEOUT).GetStrikeout();
            aNewAttr.Put(SvxCrossedOutItem(
                eFSO == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, EE_CHAR_STRIKEOUT));
        }

        mrParentWindow.GetOutlinerView()->SetAttribs(aNewAttr);
    }
}

void NotesEditWindow::EditViewScrollStateChange() { setScrollBarRange(); }

EditView* NotesEditWindow::GetEditView() const
{
    if (!mrParentWindow.GetOutlinerView())
        return nullptr;
    return &mrParentWindow.GetOutlinerView()->GetEditView();
}

EditEngine* NotesEditWindow::GetEditEngine() const
{
    if (!mrParentWindow.GetOutlinerView())
        return nullptr;
    return &mrParentWindow.GetOutlinerView()->GetEditView().getEditEngine();
}

void NotesEditWindow::GetFocus()
{
    if (auto pDrawViewShell = mrParentWindow.GetDrawViewShell())
    {
        if (auto pDrawView = pDrawViewShell->GetDrawView())
        {
            pDrawView->EndTextEditCurrentView();
            pDrawView->UnmarkAll();
        }
    }

    if (mpTextObj && mpTextObj->IsEmptyPresObj())
    {
        // clear the "Click to add Notes" text on enter of the window.
        mrParentWindow.GetOutliner()->SetToEmptyText();
    }
    WeldEditView::GetFocus();
}

bool NotesEditWindow::CanFocus() const
{
    auto pDocShell = mrParentWindow.GetViewShellBase()->GetDocShell();
    if (pDocShell && pDocShell->IsReadOnly())
        return false;

    return mpTextObj;
}

void NotesEditWindow::LoseFocus()
{
    aModifyIdle.Stop();
    if (mpTextObj)
    {
        if (GetText().getLength() == 0)
        {
            // if the notes are empty restore the placeholder text and state.
            SdPage* pPage = dynamic_cast<SdPage*>(mpTextObj->getSdrPageFromSdrObject());
            if (pPage)
                pPage->RestoreDefaultText(mpTextObj);
        }
        else
            setNotesToDoc();
    }

    WeldEditView::LoseFocus();
}

bool NotesEditWindow::Command(const CommandEvent& rCEvt)
{
    if (rCEvt.GetCommand() == CommandEventId::ContextMenu)
    {
        showContextMenu(rCEvt.GetMousePosPixel());
        return true;
    }

    return WeldEditView::Command(rCEvt);
}

void NotesEditWindow::provideNoteText()
{
    removeListener();
    mpTextObj = nullptr;
    SetText(SdResId(STR_PRESOBJ_NOTESTEXT_MISSING));

    const auto pDrawViewShell = mrParentWindow.GetDrawViewShell();
    if (!pDrawViewShell)
        return;

    SdPage* pCurrentPage = pDrawViewShell->getCurrentPage();
    if (!pCurrentPage)
        return;

    SdDrawDocument* pDoc = pDrawViewShell->GetDoc();
    if (!pDoc)
        return;

    SdPage* pNotesPage = pDoc->GetSdPage((pCurrentPage->GetPageNum() - 1) >> 1, PageKind::Notes);
    if (!pNotesPage)
        return;

    SdrObject* pNotesObj = pNotesPage->GetPresObj(PresObjKind::Notes);
    if (!pNotesObj)
        return;

    mpTextObj = dynamic_cast<SdrTextObj*>(pNotesObj);
    addListener();
    getNotesFromDoc();
}

void NotesEditWindow::removeListener()
{
    if (mpTextObj)
        mpTextObj->RemoveListener(*this);
}
void NotesEditWindow::addListener()
{
    if (mpTextObj)
        mpTextObj->AddListener(*this);
}

void NotesEditWindow::setListenerIgnored(bool bIgnore) { mbIgnoreNotifications = bIgnore; }
bool NotesEditWindow::isListenerIgnored() { return mbIgnoreNotifications; }

void NotesEditWindow::getNotesFromDoc()
{
    if (!mpTextObj)
        return;

    // Ignore notifications that will rebound from updating the text
    SetModifyHdl(Link<LinkParamNone*, void>());
    setListenerIgnored(true);

    if (OutlinerParaObject* pPara = mpTextObj->GetOutlinerParaObject())
        mrParentWindow.GetOutliner()->SetText(*pPara);

    setListenerIgnored(false);
    SetModifyHdl(LINK(this, NotesEditWindow, EditModifiedHdl));
}

void NotesEditWindow::setNotesToDoc()
{
    if (!mpTextObj)
        return;

    setListenerIgnored(true);

    std::optional<OutlinerParaObject> pNewText = mrParentWindow.GetOutliner()->CreateParaObject();
    mpTextObj->SetOutlinerParaObject(std::move(pNewText));
    if (mpTextObj->IsEmptyPresObj())
        mpTextObj->SetEmptyPresObj(false);

    setListenerIgnored(false);
}

void NotesEditWindow::Notify(SfxBroadcaster&, const SfxHint& rHint)
{
    if (isListenerIgnored())
        return;

    if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
    {
        const SdrHint& rSdrHint = reinterpret_cast<const SdrHint&>(rHint);
        switch (rSdrHint.GetKind())
        {
            case SdrHintKind::ObjectRemoved:
            case SdrHintKind::ModelCleared:
                provideNoteText();
                break;
            case SdrHintKind::ObjectChange:
            case SdrHintKind::EndEdit:
                getNotesFromDoc();
                break;
            default:
                break;
        }
    }
}

IMPL_LINK_NOARG(NotesEditWindow, EditStatusHdl, EditStatus&, void) { Resize(); }

IMPL_LINK_NOARG(NotesEditWindow, EditModifiedHdl, LinkParamNone*, void)
{
    // EditEngine calls ModifyHdl many times in succession for some edits.
    // (e.g. when deleting multiple lines)
    // Debounce the rapid ModifyHdl calls using a timer.
    aModifyIdle.Start();
    return;
}

IMPL_LINK_NOARG(NotesEditWindow, ModifyTimerHdl, Timer*, void)
{
    setNotesToDoc();
    aModifyIdle.Stop();
}

IMPL_LINK(NotesEditWindow, ScrollHdl, weld::ScrolledWindow&, rScrolledWindow, void)
{
    if (EditView* pEditView = GetEditView())
    {
        pEditView->SetVisArea(::tools::Rectangle(Point(0, rScrolledWindow.vadjustment_get_value()),
                                                 pEditView->GetVisArea().GetSize()));
        pEditView->Invalidate();
    }
    doScroll();
}

} // end of namespace ::sd

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sd/source/ui/inc/NotesChildWindow.hxx b/sd/source/ui/inc/NotesChildWindow.hxx
new file mode 100644
index 0000000..5242595
--- /dev/null
+++ b/sd/source/ui/inc/NotesChildWindow.hxx
@@ -0,0 +1,108 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * 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/.
 */
#pragma once

#include <svx/weldeditview.hxx>
#include <sfx2/dockwin.hxx>

class Outliner;
class OutlinerView;
class SdrTextObj;

namespace sd::tools
{
class EventMultiplexerEvent;
}

namespace sd
{
class ViewShellBase;
class DrawViewShell;
class NotesChildWindow;
class NotesEditWindow;
class NotesChildDockingWindow;

class NotesEditWindow : public WeldEditView, public SfxListener
{
    NotesChildDockingWindow& mrParentWindow;
    std::unique_ptr<weld::ScrolledWindow> m_xScrolledWindow;
    Idle aModifyIdle;

    SdrTextObj* mpTextObj = nullptr;
    bool mbIgnoreNotifications = false;

    void doScroll();
    void setScrollBarRange();
    void showContextMenu(const Point& rPos);

    DECL_LINK(ScrollHdl, weld::ScrolledWindow&, void);
    DECL_LINK(EditStatusHdl, EditStatus&, void);
    DECL_LINK(EditModifiedHdl, LinkParamNone*, void);
    DECL_LINK(ModifyTimerHdl, Timer*, void);
    DECL_LINK(EventMultiplexerListener, tools::EventMultiplexerEvent&, void);

    void removeListener();
    void addListener();

    void setListenerIgnored(bool bIgnore);
    bool isListenerIgnored();

    void getNotesFromDoc();
    void setNotesToDoc();

public:
    NotesEditWindow(NotesChildDockingWindow& rParentWindow,
                    std::unique_ptr<weld::ScrolledWindow> pScrolledWindow);
    virtual ~NotesEditWindow() override;

    void provideNoteText();
    bool HasNotesPlaceholder() { return mpTextObj; }

    virtual void EditViewScrollStateChange() override;
    virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override;
    virtual EditView* GetEditView() const override;
    virtual EditEngine* GetEditEngine() const override;
    virtual void GetFocus() override;
    virtual bool CanFocus() const override;
    virtual void LoseFocus() override;
    virtual bool Command(const CommandEvent& rCEvt) override;
    virtual void Notify(SfxBroadcaster& rBC, const SfxHint& rHint) override;
};

class NotesChildDockingWindow final : public SfxDockingWindow
{
    ViewShellBase* mpViewShellBase;
    std::unique_ptr<NotesEditWindow> m_xEditWindow;
    std::unique_ptr<weld::CustomWeld> m_xEditWindowWeld;

    std::unique_ptr<OutlinerView> mpOutlinerView;
    std::unique_ptr<Outliner> mpOutliner;

public:
    NotesChildDockingWindow(SfxBindings* pBindings, SfxChildWindow* pChildWindow, Window* pParent);
    virtual ~NotesChildDockingWindow() override;
    void dispose() override;

    OutlinerView* GetOutlinerView() { return mpOutlinerView.get(); }
    ::Outliner* GetOutliner() { return mpOutliner.get(); }
    ViewShellBase* GetViewShellBase() { return mpViewShellBase; }
    DrawViewShell* GetDrawViewShell();
};

class NotesChildWindow final : public SfxChildWindow
{
public:
    SFX_DECL_CHILDWINDOW_WITHID(NotesChildWindow);
    NotesChildWindow(vcl::Window* pParentWindow, sal_uInt16 nId, SfxBindings* pBindings,
                     SfxChildWinInfo* pInfo);
};

} // end of namespace ::sd

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sd/source/ui/view/ViewShellBase.cxx b/sd/source/ui/view/ViewShellBase.cxx
index 70691ce..fd319fe 100644
--- a/sd/source/ui/view/ViewShellBase.cxx
+++ b/sd/source/ui/view/ViewShellBase.cxx
@@ -630,6 +630,15 @@ void ViewShellBase::Execute (SfxRequest& rRequest)
                framework::FrameworkHelper::msSlideSorterURL);
            break;

        case SID_NOTES_WINDOW:
        {
            SfxViewShell* pViewShell = SfxViewShell::Current();
            SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
            auto nID = rRequest.GetSlot();
            rViewFrame.ToggleChildWindow(nID);
            break;
        }

        case SID_TOGGLE_TABBAR_VISIBILITY:
        {
            SdOptions* pOptions = SD_MOD()->GetSdOptions(GetDocument()->GetDocumentType());
@@ -1314,6 +1323,21 @@ void ViewShellBase::Implementation::GetSlotState (SfxItemSet& rSet)
                        bState = xConfiguration->hasResource(xResourceId);
                        break;

                    case SID_NOTES_WINDOW:
                    {
                        bState = false;
                        auto* pViewShell = SfxViewShell::Current();
                        if (pViewShell)
                        {
                            auto& rViewFrame = pViewShell->GetViewFrame();
                            if (rViewFrame.KnowsChildWindow(nItemId))
                            {
                                bState = rViewFrame.HasChildWindow(nItemId);
                            }
                        }
                        break;
                    }

                    case SID_DRAWINGMODE:
                    case SID_NORMAL_MULTI_PANE_GUI:
                    case SID_SLIDE_MASTER_MODE:
diff --git a/sd/source/ui/view/drvwshrg.cxx b/sd/source/ui/view/drvwshrg.cxx
index 792d5b8..9871db2 100644
--- a/sd/source/ui/view/drvwshrg.cxx
+++ b/sd/source/ui/view/drvwshrg.cxx
@@ -40,6 +40,7 @@
#include <SpellDialogChildWindow.hxx>
#include <GraphicViewShell.hxx>
#include <AnimationChildWindow.hxx>
#include <NotesChildWindow.hxx>

using namespace sd;
#define ShellClass_DrawViewShell
@@ -77,6 +78,7 @@ void DrawViewShell::InitInterface_Impl()
    GetStaticInterface()->RegisterChildWindow(
        sfx2::sidebar::SidebarChildWindow::GetChildWindowId());
    GetStaticInterface()->RegisterChildWindow(DevelopmentToolChildWindow::GetChildWindowId());
    GetStaticInterface()->RegisterChildWindow(::sd::NotesChildWindow::GetChildWindowId());
}

// SdGraphicViewShell
@@ -103,6 +105,7 @@ void GraphicViewShell::InitInterface_Impl()
    GetStaticInterface()->RegisterChildWindow(
        sfx2::sidebar::SidebarChildWindow::GetChildWindowId());
    GetStaticInterface()->RegisterChildWindow(DevelopmentToolChildWindow::GetChildWindowId());
    GetStaticInterface()->RegisterChildWindow(::sd::NotesChildWindow::GetChildWindowId());
}

} // end of namespace sd
diff --git a/sd/uiconfig/simpress/menubar/menubar.xml b/sd/uiconfig/simpress/menubar/menubar.xml
index d0155ba..1ee7c70 100644
--- a/sd/uiconfig/simpress/menubar/menubar.xml
+++ b/sd/uiconfig/simpress/menubar/menubar.xml
@@ -125,6 +125,7 @@
      <menu:menuitem menu:id=".uno:StatusBarVisible" menu:style="text"/>
      <menu:menuitem menu:id=".uno:LeftPaneImpress" menu:style="text"/>
      <menu:menuitem menu:id=".uno:ToggleTabBarVisibility" menu:style="text"/>
      <menu:menuitem menu:id=".uno:NotesChildWindow" menu:style="text"/>
      <menu:menuseparator/>
      <menu:menuitem menu:id=".uno:ShowRuler"/>
      <menu:menu menu:id=".uno:GridMenu">
diff --git a/sd/uiconfig/simpress/ui/noteschildwindow.ui b/sd/uiconfig/simpress/ui/noteschildwindow.ui
new file mode 100644
index 0000000..de09708
--- /dev/null
+++ b/sd/uiconfig/simpress/ui/noteschildwindow.ui
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkBox" id="NotesChildEditWindow">
    <property name="visible">True</property>
    <property name="can-focus">False</property>
    <property name="hexpand">True</property>
    <property name="vexpand">True</property>
    <property name="spacing">6</property>
    <child>
      <object class="GtkScrolledWindow" id="scrolledwin">
        <property name="visible">True</property>
        <property name="can-focus">True</property>
        <property name="hexpand">True</property>
        <property name="border-width">0</property>
        <property name="hscrollbar-policy">never</property>
        <property name="vscrollbar-policy">always</property>
        <property name="shadow-type">etched-out</property>
        <child>
          <object class="GtkViewport">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
            <child>
              <object class="GtkDrawingArea" id="noteschildeditview">
                <property name="visible">True</property>
                <property name="can-focus">True</property>
                <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK</property>
              </object>
            </child>
          </object>
        </child>
      </object>
      <packing>
        <property name="expand">False</property>
        <property name="fill">True</property>
        <property name="position">0</property>
      </packing>
    </child>
  </object>
</interface>
diff --git a/sd/uiconfig/simpress/ui/notespanelcontextmenu.ui b/sd/uiconfig/simpress/ui/notespanelcontextmenu.ui
new file mode 100644
index 0000000..e6ca46e
--- /dev/null
+++ b/sd/uiconfig/simpress/ui/notespanelcontextmenu.ui
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface domain="sd">
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkMenu" id="menu">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
  </object>
</interface>