/* -*- 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 <checklistmenu.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <strings.hrc>
#include <bitmaps.hlst>
 
#include <vcl/decoview.hxx>
#include <vcl/settings.hxx>
#include <tools/wintypes.hxx>
 
#include <AccessibleFilterMenu.hxx>
#include <AccessibleFilterTopWindow.hxx>
 
#include <com/sun/star/accessibility/XAccessible.hpp>
#include <com/sun/star/accessibility/XAccessibleContext.hpp>
#include <svtools/fmtfield.hxx>
#include <svtools/svlbitm.hxx>
#include <svtools/treelistentry.hxx>
#include <document.hxx>
 
using namespace com::sun::star;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::accessibility::XAccessible;
using ::com::sun::star::accessibility::XAccessibleContext;
using ::std::vector;
 
ScMenuFloatingWindow::MenuItemData::MenuItemData() :
    mbEnabled(true), mbSeparator(false),
    mpAction(static_cast<ScCheckListMenuWindow::Action*>(nullptr)),
    mpSubMenuWin(static_cast<ScMenuFloatingWindow*>(nullptr))
{
}
 
ScMenuFloatingWindow::SubMenuItemData::SubMenuItemData(ScMenuFloatingWindow* pParent) :
    mpSubMenu(nullptr),
    mnMenuPos(MENU_NOT_SELECTED),
    mpParent(pParent)
{
    maTimer.SetInvokeHandler( LINK(this, ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl) );
    maTimer.SetTimeout(mpParent->GetSettings().GetMouseSettings().GetMenuDelay());
}
 
void ScMenuFloatingWindow::SubMenuItemData::reset()
{
    mpSubMenu = nullptr;
    mnMenuPos = MENU_NOT_SELECTED;
    maTimer.Stop();
}
 
IMPL_LINK_NOARG(ScMenuFloatingWindow::SubMenuItemData, TimeoutHdl, Timer *, void)
{
    mpParent->handleMenuTimeout(this);
}
 
ScMenuFloatingWindow::ScMenuFloatingWindow(vcl::Window* pParent, ScDocument* pDoc, sal_uInt16 nMenuStackLevel) :
    PopupMenuFloatingWindow(pParent),
    maOpenTimer(this),
    maCloseTimer(this),
    maName("ScMenuFloatingWindow"),
    mnSelectedMenu(MENU_NOT_SELECTED),
    mnClickedMenu(MENU_NOT_SELECTED),
    mpDoc(pDoc),
    mpParentMenu(dynamic_cast<ScMenuFloatingWindow*>(pParent))
{
    SetMenuStackLevel(nMenuStackLevel);
    SetText("ScMenuFloatingWindow");
 
    const StyleSettings& rStyle = GetSettings().GetStyleSettings();
 
    const sal_uInt16 nPopupFontHeight = 12 * GetDPIScaleFactor();
    maLabelFont = rStyle.GetLabelFont();
    maLabelFont.SetFontHeight(nPopupFontHeight);
}
 
ScMenuFloatingWindow::~ScMenuFloatingWindow()
{
    disposeOnce();
}
 
void ScMenuFloatingWindow::dispose()
{
    EndPopupMode();
    for (auto i = maMenuItems.begin(); i != maMenuItems.end(); ++i)
        i->mpSubMenuWin.disposeAndClear();
    mpParentMenu.clear();
    PopupMenuFloatingWindow::dispose();
}
 
void ScMenuFloatingWindow::PopupModeEnd()
{
    handlePopupEnd();
}
 
void ScMenuFloatingWindow::MouseMove(const MouseEvent& rMEvt)
{
    const Point& rPos = rMEvt.GetPosPixel();
    size_t nSelectedMenu = getEnclosingMenuItem(rPos);
    setSelectedMenuItem(nSelectedMenu, true, false);
 
    Window::MouseMove(rMEvt);
}
 
void ScMenuFloatingWindow::MouseButtonDown(const MouseEvent& rMEvt)
{
    const Point& rPos = rMEvt.GetPosPixel();
    mnClickedMenu = getEnclosingMenuItem(rPos);
    Window::MouseButtonDown(rMEvt);
}
 
void ScMenuFloatingWindow::MouseButtonUp(const MouseEvent& rMEvt)
{
    executeMenuItem(mnClickedMenu);
    mnClickedMenu = MENU_NOT_SELECTED;
    Window::MouseButtonUp(rMEvt);
}
 
void ScMenuFloatingWindow::KeyInput(const KeyEvent& rKEvt)
{
    if (maMenuItems.empty())
    {
        Window::KeyInput(rKEvt);
        return;
    }
 
    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
    bool bHandled = true;
    size_t nSelectedMenu = mnSelectedMenu;
    size_t nLastMenuPos = maMenuItems.size() - 1;
    switch (rKeyCode.GetCode())
    {
        case KEY_UP:
        {
            if (nLastMenuPos == 0)
                // There is only one menu item.  Do nothing.
                break;
 
            size_t nOldPos = nSelectedMenu;
 
            if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == 0)
                nSelectedMenu = nLastMenuPos;
            else
                --nSelectedMenu;
 
            // Loop until a non-separator menu item is found.
            while (nSelectedMenu != nOldPos)
            {
                if (maMenuItems[nSelectedMenu].mbSeparator)
                {
                    if (nSelectedMenu)
                        --nSelectedMenu;
                    else
                        nSelectedMenu = nLastMenuPos;
                }
                else
                    break;
            }
 
            setSelectedMenuItem(nSelectedMenu, false, false);
        }
        break;
        case KEY_DOWN:
        {
            if (nLastMenuPos == 0)
                // There is only one menu item.  Do nothing.
                break;
 
            size_t nOldPos = nSelectedMenu;
 
            if (nSelectedMenu == MENU_NOT_SELECTED || nSelectedMenu == nLastMenuPos)
                nSelectedMenu = 0;
            else
                ++nSelectedMenu;
 
            // Loop until a non-separator menu item is found.
            while (nSelectedMenu != nOldPos)
            {
                if (maMenuItems[nSelectedMenu].mbSeparator)
                {
                    if (nSelectedMenu == nLastMenuPos)
                        nSelectedMenu = 0;
                    else
                        ++nSelectedMenu;
                }
                else
                    break;
            }
 
            setSelectedMenuItem(nSelectedMenu, false, false);
        }
        break;
        case KEY_LEFT:
            if (mpParentMenu)
                mpParentMenu->endSubMenu(this);
        break;
        case KEY_RIGHT:
        {
            if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
                break;
 
            const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
            if (!rMenu.mbEnabled || !rMenu.mpSubMenuWin)
                break;
 
            maOpenTimer.mnMenuPos = mnSelectedMenu;
            maOpenTimer.mpSubMenu = rMenu.mpSubMenuWin.get();
            launchSubMenu(true);
        }
        break;
        case KEY_RETURN:
            if (nSelectedMenu != MENU_NOT_SELECTED)
                executeMenuItem(nSelectedMenu);
        break;
        default:
            bHandled = false;
    }
 
    if (!bHandled)
        Window::KeyInput(rKEvt);
}
 
void ScMenuFloatingWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
{
    const StyleSettings& rStyle = GetSettings().GetStyleSettings();
 
    SetFont(maLabelFont);
 
    Color aBackColor = rStyle.GetMenuColor();
    Color aBorderColor = rStyle.GetShadowColor();
 
    tools::Rectangle aCtrlRect(Point(0, 0), GetOutputSizePixel());
 
    // Window background
    bool bNativeDrawn = true;
    if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
    {
        rRenderContext.SetClipRegion();
        bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
                                                        ControlState::ENABLED, ImplControlValue(), OUString());
    }
    else
        bNativeDrawn = false;
 
    if (!bNativeDrawn)
    {
        rRenderContext.SetFillColor(aBackColor);
        rRenderContext.SetLineColor(aBorderColor);
        rRenderContext.DrawRect(aCtrlRect);
    }
 
    // Menu items
    rRenderContext.SetTextColor(rStyle.GetMenuTextColor());
    drawAllMenuItems(rRenderContext);
}
 
Reference<XAccessible> ScMenuFloatingWindow::CreateAccessible()
{
    if (!mxAccessible.is())
    {
        Reference<XAccessible> xAccParent = mpParentMenu ?
            mpParentMenu->GetAccessible() : GetAccessibleParentWindow()->GetAccessible();
 
        mxAccessible.set(new ScAccessibleFilterMenu(xAccParent, this, maName, 999));
        ScAccessibleFilterMenu* p = static_cast<ScAccessibleFilterMenu*>(
            mxAccessible.get());
 
        vector<MenuItemData>::const_iterator itr, itrBeg = maMenuItems.begin(), itrEnd = maMenuItems.end();
        for (itr = itrBeg; itr != itrEnd; ++itr)
        {
            size_t nPos = ::std::distance(itrBeg, itr);
            p->appendMenuItem(itr->maText, nPos);
        }
    }
 
    return mxAccessible;
}
 
void ScMenuFloatingWindow::addMenuItem(const OUString& rText, Action* pAction)
{
    MenuItemData aItem;
    aItem.maText = rText;
    aItem.mbEnabled = true;
    aItem.mpAction.reset(pAction);
    maMenuItems.push_back(aItem);
}
 
void ScMenuFloatingWindow::addSeparator()
{
    MenuItemData aItem;
    aItem.mbSeparator = true;
    maMenuItems.push_back(aItem);
}
 
ScMenuFloatingWindow* ScMenuFloatingWindow::addSubMenuItem(const OUString& rText, bool bEnabled)
{
    MenuItemData aItem;
    aItem.maText = rText;
    aItem.mbEnabled = bEnabled;
    aItem.mpSubMenuWin.reset(VclPtr<ScMenuFloatingWindow>::Create(this, mpDoc, GetMenuStackLevel()+1));
    aItem.mpSubMenuWin->setName(rText);
    maMenuItems.push_back(aItem);
    return aItem.mpSubMenuWin.get();
}
 
void ScMenuFloatingWindow::handlePopupEnd()
{
    clearSelectedMenuItem();
}
 
Size ScMenuFloatingWindow::getMenuSize() const
{
    if (maMenuItems.empty())
        return Size();
 
    vector<MenuItemData>::const_iterator itr = maMenuItems.begin(), itrEnd = maMenuItems.end();
    long nTextWidth = 0;
    for (; itr != itrEnd; ++itr)
    {
        if (itr->mbSeparator)
            continue;
 
        nTextWidth = ::std::max(GetTextWidth(itr->maText), nTextWidth);
    }
 
    size_t nLastPos = maMenuItems.size()-1;
    Point aPos;
    Size aSize;
    getMenuItemPosSize(nLastPos, aPos, aSize);
    aPos.AdjustX(nTextWidth + 15 );
    aPos.AdjustY(aSize.Height() + 5 );
    return Size(aPos.X(), aPos.Y());
}
 
void ScMenuFloatingWindow::drawMenuItem(vcl::RenderContext& rRenderContext, size_t nPos)
{
    if (nPos >= maMenuItems.size())
        return;
 
    Point aPos;
    Size aSize;
    getMenuItemPosSize(nPos, aPos, aSize);
 
    DecorationView aDecoView(&rRenderContext);
    long const nXOffset = 5;
    long nYOffset = (aSize.Height() - maLabelFont.GetFontHeight())/2;
    rRenderContext. DrawCtrlText(Point(aPos.X()+nXOffset, aPos.Y() + nYOffset), maMenuItems[nPos].maText, 0,
                                 maMenuItems[nPos].maText.getLength(),
                                 maMenuItems[nPos].mbEnabled ? DrawTextFlags::Mnemonic : DrawTextFlags::Disable);
 
    if (maMenuItems[nPos].mpSubMenuWin)
    {
        long nFontHeight = maLabelFont.GetFontHeight();
        Point aMarkerPos = aPos;
        aMarkerPos.AdjustY(aSize.Height() / 2 - nFontHeight / 4 + 1 );
        aMarkerPos.AdjustX(aSize.Width() - nFontHeight + nFontHeight / 4 );
        Size aMarkerSize(nFontHeight / 2, nFontHeight / 2);
        aDecoView.DrawSymbol(tools::Rectangle(aMarkerPos, aMarkerSize), SymbolType::SPIN_RIGHT, GetTextColor());
    }
}
 
void ScMenuFloatingWindow::drawSeparator(vcl::RenderContext& rRenderContext, size_t nPos)
{
    Point aPos;
    Size aSize;
    getMenuItemPosSize(nPos, aPos, aSize);
    tools::Rectangle aRegion(aPos,aSize);
 
    if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
    {
        rRenderContext.Push(PushFlags::CLIPREGION);
        rRenderContext.IntersectClipRegion(aRegion);
        tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
        rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect,
                                         ControlState::ENABLED, ImplControlValue(), OUString());
 
        rRenderContext.Pop();
    }
 
    bool bNativeDrawn = false;
    if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Separator))
    {
        ControlState nState = ControlState::NONE;
        const MenuItemData& rData = maMenuItems[nPos];
        if (rData.mbEnabled)
            nState |= ControlState::ENABLED;
 
        bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Separator,
                                                        aRegion, nState, ImplControlValue(), OUString());
    }
 
    if (!bNativeDrawn)
    {
        const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
        Point aTmpPos = aPos;
        aTmpPos.AdjustY(aSize.Height() / 2 );
        rRenderContext.SetLineColor(rStyle.GetShadowColor());
        rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
        aTmpPos.AdjustY( 1 );
        rRenderContext.SetLineColor(rStyle.GetLightColor());
        rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y()));
        rRenderContext.SetLineColor();
    }
}
 
void ScMenuFloatingWindow::drawAllMenuItems(vcl::RenderContext& rRenderContext)
{
    size_t n = maMenuItems.size();
 
    for (size_t i = 0; i < n; ++i)
    {
        if (maMenuItems[i].mbSeparator)
        {
            // Separator
            drawSeparator(rRenderContext, i);
        }
        else
        {
            // Normal menu item
            highlightMenuItem(rRenderContext, i, i == mnSelectedMenu);
        }
    }
}
 
void ScMenuFloatingWindow::executeMenuItem(size_t nPos)
{
    if (nPos >= maMenuItems.size())
        return;
 
    if (!maMenuItems[nPos].mpAction)
        // no action is defined.
        return;
 
    maMenuItems[nPos].mpAction->execute();
    terminateAllPopupMenus();
}
 
void ScMenuFloatingWindow::setSelectedMenuItem(size_t nPos, bool bSubMenuTimer, bool bEnsureSubMenu)
{
    if (mnSelectedMenu == nPos)
        // nothing to do.
        return;
 
    if (bEnsureSubMenu)
    {
        // Dismiss any child popup menu windows.
        if (mnSelectedMenu < maMenuItems.size() &&
            maMenuItems[mnSelectedMenu].mpSubMenuWin &&
            maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
        {
            maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
        }
 
        // The popup is not visible, yet a menu item is selected.  The request
        // most likely comes from the accessible object.  Make sure this
        // window, as well as all its parent windows are visible.
        if (!IsVisible() && mpParentMenu)
            mpParentMenu->ensureSubMenuVisible(this);
    }
 
    selectMenuItem(mnSelectedMenu, false, bSubMenuTimer);
    selectMenuItem(nPos, true, bSubMenuTimer);
    mnSelectedMenu = nPos;
 
    fireMenuHighlightedEvent();
}
 
void ScMenuFloatingWindow::handleMenuTimeout(const SubMenuItemData* pTimer)
{
    if (pTimer == &maOpenTimer)
    {
        // Close any open submenu immediately.
        if (maCloseTimer.mpSubMenu)
        {
            maCloseTimer.mpSubMenu->EndPopupMode();
            maCloseTimer.mpSubMenu = nullptr;
            maCloseTimer.maTimer.Stop();
        }
 
        launchSubMenu(false);
    }
    else if (pTimer == &maCloseTimer)
    {
        // end submenu.
        if (maCloseTimer.mpSubMenu)
        {
            maOpenTimer.mpSubMenu = nullptr;
 
            maCloseTimer.mpSubMenu->EndPopupMode();
            maCloseTimer.mpSubMenu = nullptr;
 
            Invalidate();
            maOpenTimer.mnMenuPos = MENU_NOT_SELECTED;
        }
    }
}
 
void ScMenuFloatingWindow::queueLaunchSubMenu(size_t nPos, ScMenuFloatingWindow* pMenu)
{
    if (!pMenu)
        return;
 
    // Set the submenu on launch queue.
    if (maOpenTimer.mpSubMenu)
    {
        if (maOpenTimer.mpSubMenu == pMenu)
        {
            if (pMenu == maCloseTimer.mpSubMenu)
                maCloseTimer.reset();
            return;
        }
 
        // new submenu is being requested.
        queueCloseSubMenu();
    }
 
    maOpenTimer.mpSubMenu = pMenu;
    maOpenTimer.mnMenuPos = nPos;
    maOpenTimer.maTimer.Start();
}
 
void ScMenuFloatingWindow::queueCloseSubMenu()
{
    if (!maOpenTimer.mpSubMenu)
        // There is no submenu to close.
        return;
 
    // Stop any submenu on queue for opening.
    maOpenTimer.maTimer.Stop();
 
    maCloseTimer.mpSubMenu = maOpenTimer.mpSubMenu;
    maCloseTimer.mnMenuPos = maOpenTimer.mnMenuPos;
    maCloseTimer.maTimer.Start();
}
 
void ScMenuFloatingWindow::launchSubMenu(bool bSetMenuPos)
{
    Point aPos;
    Size aSize;
    getMenuItemPosSize(maOpenTimer.mnMenuPos, aPos, aSize);
    ScMenuFloatingWindow* pSubMenu = maOpenTimer.mpSubMenu;
 
    if (!pSubMenu)
        return;
 
    FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
    SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
    pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
    pSubMenu->StartPopupMode(
        tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
    pSubMenu->AddPopupModeWindow(this);
    if (bSetMenuPos)
        pSubMenu->setSelectedMenuItem(0, false, false); // select menu item after the popup becomes fully visible.
    SetPopupModeFlags(nOldFlags);
}
 
void ScMenuFloatingWindow::endSubMenu(ScMenuFloatingWindow* pSubMenu)
{
    if (!pSubMenu)
        return;
 
    pSubMenu->EndPopupMode();
    maOpenTimer.reset();
 
    size_t nMenuPos = getSubMenuPos(pSubMenu);
    if (nMenuPos != MENU_NOT_SELECTED)
    {
        mnSelectedMenu = nMenuPos;
        Invalidate();
        fireMenuHighlightedEvent();
    }
}
 
void ScMenuFloatingWindow::fillMenuItemsToAccessible(ScAccessibleFilterMenu* pAccMenu) const
{
    vector<MenuItemData>::const_iterator itr, itrBeg = maMenuItems.begin(), itrEnd = maMenuItems.end();
    for (itr = itrBeg; itr != itrEnd; ++itr)
    {
        size_t nPos = ::std::distance(itrBeg, itr);
        pAccMenu->appendMenuItem(itr->maText, nPos);
    }
}
 
void ScMenuFloatingWindow::resizeToFitMenuItems()
{
    SetOutputSizePixel(getMenuSize());
}
 
void ScMenuFloatingWindow::selectMenuItem(size_t nPos, bool bSelected, bool bSubMenuTimer)
{
    if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
    {
        queueCloseSubMenu();
        return;
    }
 
    if (!maMenuItems[nPos].mbEnabled)
    {
        queueCloseSubMenu();
        return;
    }
 
    Invalidate();
 
    if (bSelected)
    {
        if (mpParentMenu)
            mpParentMenu->setSubMenuFocused(this);
 
        if (bSubMenuTimer)
        {
            if (maMenuItems[nPos].mpSubMenuWin)
            {
                ScMenuFloatingWindow* pSubMenu = maMenuItems[nPos].mpSubMenuWin.get();
                queueLaunchSubMenu(nPos, pSubMenu);
            }
            else
                queueCloseSubMenu();
        }
    }
}
 
void ScMenuFloatingWindow::clearSelectedMenuItem()
{
    selectMenuItem(mnSelectedMenu, false, false);
    mnSelectedMenu = MENU_NOT_SELECTED;
}
 
ScMenuFloatingWindow* ScMenuFloatingWindow::getSubMenuWindow(size_t nPos) const
{
    if (maMenuItems.size() <= nPos)
        return nullptr;
 
    return maMenuItems[nPos].mpSubMenuWin.get();
}
 
bool ScMenuFloatingWindow::isMenuItemSelected(size_t nPos) const
{
    return nPos == mnSelectedMenu;
}
 
void ScMenuFloatingWindow::setName(const OUString& rName)
{
    maName = rName;
}
 
void ScMenuFloatingWindow::highlightMenuItem(vcl::RenderContext& rRenderContext, size_t nPos, bool bSelected)
{
    if (nPos == MENU_NOT_SELECTED)
        return;
 
    const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings();
    Color aBackColor = rStyle.GetMenuColor();
    rRenderContext.SetFillColor(aBackColor);
    rRenderContext.SetLineColor(aBackColor);
 
    Point aPos;
    Size aSize;
    getMenuItemPosSize(nPos, aPos, aSize);
    tools::Rectangle aRegion(aPos,aSize);
 
    if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::Entire))
    {
        rRenderContext.Push(PushFlags::CLIPREGION);
        rRenderContext.IntersectClipRegion(tools::Rectangle(aPos, aSize));
        tools::Rectangle aCtrlRect(Point(0,0), GetOutputSizePixel());
        rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::Entire, aCtrlRect, ControlState::ENABLED,
                                         ImplControlValue(), OUString());
        rRenderContext.Pop();
    }
 
    bool bNativeDrawn = true;
    if (rRenderContext.IsNativeControlSupported(ControlType::MenuPopup, ControlPart::MenuItem))
    {
        ControlState nState = bSelected ? ControlState::SELECTED : ControlState::NONE;
        if (maMenuItems[nPos].mbEnabled)
            nState |= ControlState::ENABLED;
        bNativeDrawn = rRenderContext.DrawNativeControl(ControlType::MenuPopup, ControlPart::MenuItem,
                                                        aRegion, nState, ImplControlValue(), OUString());
    }
    else
        bNativeDrawn = false;
 
    if (!bNativeDrawn)
    {
        if (bSelected)
        {
            aBackColor = rStyle.GetMenuHighlightColor();
            rRenderContext.SetFillColor(aBackColor);
            rRenderContext.SetLineColor(aBackColor);
        }
        rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
    }
 
    Color aTextColor = bSelected ? rStyle.GetMenuHighlightTextColor() : rStyle.GetMenuTextColor();
    rRenderContext.SetTextColor(aTextColor);
    drawMenuItem(rRenderContext, nPos);
}
 
void ScMenuFloatingWindow::getMenuItemPosSize(size_t nPos, Point& rPos, Size& rSize) const
{
    size_t nCount = maMenuItems.size();
    if (nPos >= nCount)
        return;
 
    const sal_uInt16 nLeftMargin = 5;
    const sal_uInt16 nTopMargin = 5;
    const sal_uInt16 nMenuItemHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*1.8);
    const sal_uInt16 nSepHeight = static_cast<sal_uInt16>(maLabelFont.GetFontHeight()*0.8);
 
    Point aPos1(nLeftMargin, nTopMargin);
    rPos = aPos1;
    for (size_t i = 0; i < nPos; ++i)
        rPos.AdjustY(maMenuItems[i].mbSeparator ? nSepHeight : nMenuItemHeight );
 
    Size aWndSize = GetSizePixel();
    sal_uInt16 nH = maMenuItems[nPos].mbSeparator ? nSepHeight : nMenuItemHeight;
    rSize = Size(aWndSize.Width() - nLeftMargin*2, nH);
}
 
size_t ScMenuFloatingWindow::getEnclosingMenuItem(const Point& rPos) const
{
    size_t n = maMenuItems.size();
    for (size_t i = 0; i < n; ++i)
    {
        Point aPos;
        Size aSize;
        getMenuItemPosSize(i, aPos, aSize);
        tools::Rectangle aRect(aPos, aSize);
        if (aRect.IsInside(rPos))
            return maMenuItems[i].mbSeparator ? MENU_NOT_SELECTED : i;
    }
    return MENU_NOT_SELECTED;
}
 
size_t ScMenuFloatingWindow::getSubMenuPos(const ScMenuFloatingWindow* pSubMenu)
{
    size_t n = maMenuItems.size();
    for (size_t i = 0; i < n; ++i)
    {
        if (maMenuItems[i].mpSubMenuWin.get() == pSubMenu)
            return i;
    }
    return MENU_NOT_SELECTED;
}
 
void ScMenuFloatingWindow::fireMenuHighlightedEvent()
{
    if (mnSelectedMenu == MENU_NOT_SELECTED)
        return;
 
    if (!mxAccessible.is())
        return;
 
    Reference<XAccessibleContext> xAccCxt = mxAccessible->getAccessibleContext();
    if (!xAccCxt.is())
        return;
 
    Reference<XAccessible> xAccMenu = xAccCxt->getAccessibleChild(mnSelectedMenu);
    if (!xAccMenu.is())
        return;
 
    VclAccessibleEvent aEvent(VclEventId::MenuHighlight, xAccMenu);
    FireVclEvent(aEvent);
}
 
void ScMenuFloatingWindow::setSubMenuFocused(const ScMenuFloatingWindow* pSubMenu)
{
    maCloseTimer.reset();
    size_t nMenuPos = getSubMenuPos(pSubMenu);
    if (mnSelectedMenu != nMenuPos)
    {
        mnSelectedMenu = nMenuPos;
        Invalidate();
    }
}
 
void ScMenuFloatingWindow::ensureSubMenuVisible(ScMenuFloatingWindow* pSubMenu)
{
    if (mpParentMenu)
        mpParentMenu->ensureSubMenuVisible(this);
 
    if (pSubMenu->IsVisible())
        return;
 
    // Find the menu position of the submenu.
    size_t nMenuPos = getSubMenuPos(pSubMenu);
    if (nMenuPos != MENU_NOT_SELECTED)
    {
        setSelectedMenuItem(nMenuPos, false, false);
 
        Point aPos;
        Size aSize;
        getMenuItemPosSize(nMenuPos, aPos, aSize);
 
        FloatWinPopupFlags nOldFlags = GetPopupModeFlags();
        SetPopupModeFlags(nOldFlags | FloatWinPopupFlags::NoAppFocusClose);
        pSubMenu->resizeToFitMenuItems(); // set the size before launching the popup to get it positioned correctly.
        pSubMenu->StartPopupMode(
            tools::Rectangle(aPos,aSize), (FloatWinPopupFlags::Right | FloatWinPopupFlags::GrabFocus));
        pSubMenu->AddPopupModeWindow(this);
        SetPopupModeFlags(nOldFlags);
    }
}
 
void ScMenuFloatingWindow::ensureSubMenuNotVisible()
{
    if (mnSelectedMenu <= maMenuItems.size() &&
        maMenuItems[mnSelectedMenu].mpSubMenuWin &&
        maMenuItems[mnSelectedMenu].mpSubMenuWin->IsVisible())
    {
        maMenuItems[mnSelectedMenu].mpSubMenuWin->ensureSubMenuNotVisible();
    }
 
    EndPopupMode();
}
 
void ScMenuFloatingWindow::terminateAllPopupMenus()
{
    EndPopupMode();
    if (mpParentMenu)
        mpParentMenu->terminateAllPopupMenus();
}
 
ScCheckListMenuWindow::Config::Config() :
    mbAllowEmptySet(true), mbRTL(false)
{
}
 
ScCheckListMember::ScCheckListMember()
    : mbVisible(true)
    , mbDate(false)
    , mbLeaf(false)
    , meDatePartType(YEAR)
    , mpParent(nullptr)
{
}
 
ScCheckListMenuWindow::CancelButton::CancelButton(ScCheckListMenuWindow* pParent) :
    ::CancelButton(pParent), mpParent(pParent) {}
 
ScCheckListMenuWindow::CancelButton::~CancelButton()
{
    disposeOnce();
}
 
void ScCheckListMenuWindow::CancelButton::dispose()
{
    mpParent.clear();
    ::CancelButton::dispose();
}
 
void ScCheckListMenuWindow::CancelButton::Click()
{
    mpParent->EndPopupMode();
    ::CancelButton::Click();
}
 
ScCheckListMenuWindow::ScCheckListMenuWindow(vcl::Window* pParent, ScDocument* pDoc) :
    ScMenuFloatingWindow(pParent, pDoc),
    maEdSearch(VclPtr<ScSearchEdit>::Create(this)),
    maChecks(VclPtr<ScCheckListBox>::Create(this)),
    maChkToggleAll(VclPtr<TriStateBox>::Create(this, 0)),
    maBtnSelectSingle(VclPtr<ImageButton>::Create(this, 0)),
    maBtnUnselectSingle(VclPtr<ImageButton>::Create(this, 0)),
    maBtnOk(VclPtr<OKButton>::Create(this)),
    maBtnCancel(VclPtr<CancelButton>::Create(this)),
    maWndSize(),
    mePrevToggleAllState(TRISTATE_INDET),
    maTabStops(this)
{
    float fScaleFactor = GetDPIScaleFactor();
 
    maWndSize = Size(200 * fScaleFactor, 330 * fScaleFactor);
 
    maTabStops.AddTabStop( this );
    maTabStops.AddTabStop( maEdSearch.get() );
    maTabStops.AddTabStop( maChecks.get() );
    maTabStops.AddTabStop( maChkToggleAll.get() );
    maTabStops.AddTabStop( maBtnSelectSingle.get() );
    maTabStops.AddTabStop( maBtnUnselectSingle.get() );
    maTabStops.AddTabStop( maBtnOk.get() );
    maTabStops.AddTabStop( maBtnCancel.get() );
 
    maEdSearch->SetTabStopsContainer( &maTabStops );
    maChecks->SetTabStopsContainer( &maTabStops );
 
    set_id("check_list_menu");
    maChkToggleAll->set_id("toggle_all");
    maBtnSelectSingle->set_id("select_current");
    maBtnUnselectSingle->set_id("unselect_current");
}
 
ScCheckListMenuWindow::~ScCheckListMenuWindow()
{
    disposeOnce();
}
 
void ScCheckListMenuWindow::dispose()
{
    maTabStops.clear();
    maEdSearch.disposeAndClear();
    maChecks.disposeAndClear();
    maChkToggleAll.disposeAndClear();
    maBtnSelectSingle.disposeAndClear();
    maBtnUnselectSingle.disposeAndClear();
    maBtnOk.disposeAndClear();
    maBtnCancel.disposeAndClear();
    ScMenuFloatingWindow::dispose();
}
 
void ScCheckListMenuWindow::getSectionPosSize(
    Point& rPos, Size& rSize, SectionType eType) const
{
    float fScaleFactor = GetDPIScaleFactor();
 
    // constant parameters.
    const long nSearchBoxMargin = 10 *fScaleFactor;
    const long nListBoxMargin = 5 * fScaleFactor;            // horizontal distance from the side of the dialog to the listbox border.
    const long nListBoxInnerPadding = 5 * fScaleFactor;
    const long nTopMargin = 5 * fScaleFactor;
    const long nMenuHeight = maMenuSize.getHeight();
    const long nSingleItemBtnAreaHeight = 32 * fScaleFactor; // height of the middle area below the list box where the single-action buttons are.
    const long nBottomBtnAreaHeight = 50 * fScaleFactor;     // height of the bottom area where the OK and Cancel buttons are.
    const long nBtnWidth = 90 * fScaleFactor;
    const long nLabelHeight = getLabelFont().GetFontHeight();
    const long nBtnHeight = nLabelHeight * 2;
    const long nBottomMargin = 10 * fScaleFactor;
    const long nMenuListMargin = 5 * fScaleFactor;
    const long nSearchBoxHeight = nLabelHeight * 2;
 
    // parameters calculated from constants.
    const long nListBoxWidth = maWndSize.Width() - nListBoxMargin*2;
    const long nListBoxHeight = maWndSize.Height() - nTopMargin - nMenuHeight -
        nMenuListMargin - nSearchBoxHeight - nSearchBoxMargin - nSingleItemBtnAreaHeight - nBottomBtnAreaHeight;
 
    const long nSingleBtnAreaY = nTopMargin + nMenuHeight + nListBoxHeight + nMenuListMargin + nSearchBoxHeight + nSearchBoxMargin - 1;
 
    switch (eType)
    {
        case WHOLE:
        {
            rPos  = Point(0, 0);
            rSize = maWndSize;
        }
        break;
        case EDIT_SEARCH:
        {
            rPos = Point(nSearchBoxMargin, nTopMargin + nMenuHeight + nMenuListMargin);
            rSize = Size(maWndSize.Width() - 2*nSearchBoxMargin, nSearchBoxHeight);
        }
        break;
        case LISTBOX_AREA_OUTER:
        {
            rPos = Point(nListBoxMargin, nTopMargin + nMenuHeight + nMenuListMargin + nSearchBoxHeight + nSearchBoxMargin);
            rSize = Size(nListBoxWidth, nListBoxHeight);
        }
        break;
        case LISTBOX_AREA_INNER:
        {
            rPos = Point(nListBoxMargin, nTopMargin + nMenuHeight + nMenuListMargin + nSearchBoxHeight + nSearchBoxMargin);
            rPos.AdjustX(nListBoxInnerPadding );
            rPos.AdjustY(nListBoxInnerPadding );
 
            rSize = Size(nListBoxWidth, nListBoxHeight);
            rSize.AdjustWidth( -(nListBoxInnerPadding*2) );
            rSize.AdjustHeight( -(nListBoxInnerPadding*2) );
        }
        break;
        case SINGLE_BTN_AREA:
        {
            rPos = Point(nListBoxMargin, nSingleBtnAreaY);
            rSize = Size(nListBoxWidth, nSingleItemBtnAreaHeight);
        }
        break;
        case CHECK_TOGGLE_ALL:
        {
            long h = std::min(maChkToggleAll->CalcMinimumSize().Height(), 26L);
            rPos = Point(nListBoxMargin, nSingleBtnAreaY);
            rPos.AdjustX(5 );
            rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
            rSize = Size(70, h);
        }
        break;
        case BTN_SINGLE_SELECT:
        {
            long h = 26 * fScaleFactor;
            rPos = Point(nListBoxMargin, nSingleBtnAreaY);
            rPos.AdjustX(nListBoxWidth - h - 10 - h - 10 );
            rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
            rSize = Size(h, h);
        }
        break;
        case BTN_SINGLE_UNSELECT:
        {
            long h = 26 * fScaleFactor;
            rPos = Point(nListBoxMargin, nSingleBtnAreaY);
            rPos.AdjustX(nListBoxWidth - h - 10 );
            rPos.AdjustY((nSingleItemBtnAreaHeight - h)/2 );
            rSize = Size(h, h);
        }
        break;
        case BTN_OK:
        {
            long x = (maWndSize.Width() - nBtnWidth*2)/3;
            long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
            rPos = Point(x, y);
            rSize = Size(nBtnWidth, nBtnHeight);
        }
        break;
        case BTN_CANCEL:
        {
            long x = (maWndSize.Width() - nBtnWidth*2)/3*2 + nBtnWidth;
            long y = maWndSize.Height() - nBottomMargin - nBtnHeight;
            rPos = Point(x, y);
            rSize = Size(nBtnWidth, nBtnHeight);
        }
        break;
        default:
            ;
    }
}
 
void ScCheckListMenuWindow::packWindow()
{
    maMenuSize = getMenuSize();
 
    if (maWndSize.Width() < maMenuSize.Width())
        // Widen the window to fit the menu items.
        maWndSize.setWidth( maMenuSize.Width() );
 
    // Set proper window height based on the number of menu items.
    if (maWndSize.Height() < maMenuSize.Height()*2.8)
        maWndSize.setHeight( maMenuSize.Height()*2.8 );
 
    // TODO: Make sure the window height never exceeds the height of the
    // screen. Also do adjustment based on the number of check box items.
 
    SetOutputSizePixel(maWndSize);
 
    const StyleSettings& rStyle = GetSettings().GetStyleSettings();
 
    Point aPos;
    Size aSize;
    getSectionPosSize(aPos, aSize, WHOLE);
    SetOutputSizePixel(aSize);
 
    getSectionPosSize(aPos, aSize, BTN_OK);
    maBtnOk->SetPosSizePixel(aPos, aSize);
    maBtnOk->SetFont(getLabelFont());
    maBtnOk->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
    maBtnOk->Show();
 
    getSectionPosSize(aPos, aSize, BTN_CANCEL);
    maBtnCancel->SetPosSizePixel(aPos, aSize);
    maBtnCancel->SetFont(getLabelFont());
    maBtnCancel->Show();
 
    getSectionPosSize(aPos, aSize, EDIT_SEARCH);
    maEdSearch->SetPosSizePixel(aPos, aSize);
    maEdSearch->SetFont(getLabelFont());
    maEdSearch->SetControlBackground(rStyle.GetFieldColor());
    maEdSearch->SetPlaceholderText(ScResId(STR_EDIT_SEARCH_ITEMS));
    maEdSearch->SetModifyHdl( LINK(this, ScCheckListMenuWindow, EdModifyHdl) );
    maEdSearch->Show();
 
    getSectionPosSize(aPos, aSize, LISTBOX_AREA_INNER);
    maChecks->SetPosSizePixel(aPos, aSize);
    maChecks->SetFont(getLabelFont());
    maChecks->SetCheckButtonHdl( LINK(this, ScCheckListMenuWindow, CheckHdl) );
    maChecks->Show();
 
    getSectionPosSize(aPos, aSize, CHECK_TOGGLE_ALL);
    maChkToggleAll->SetPosSizePixel(aPos, aSize);
    maChkToggleAll->SetFont(getLabelFont());
    maChkToggleAll->SetText(ScResId(STR_BTN_TOGGLE_ALL));
    maChkToggleAll->SetTextColor(rStyle.GetMenuTextColor());
    maChkToggleAll->SetControlBackground(rStyle.GetMenuColor());
    maChkToggleAll->SetClickHdl( LINK(this, ScCheckListMenuWindow, TriStateHdl) );
    maChkToggleAll->Show();
 
    float fScaleFactor = GetDPIScaleFactor();
 
    BitmapEx aSingleSelectBmp(RID_BMP_SELECT_CURRENT);
    if (fScaleFactor > 1)
        aSingleSelectBmp.Scale(fScaleFactor, fScaleFactor, BmpScaleFlag::Fast);
    Image aSingleSelect(aSingleSelectBmp);
 
    getSectionPosSize(aPos, aSize, BTN_SINGLE_SELECT);
    maBtnSelectSingle->SetPosSizePixel(aPos, aSize);
    maBtnSelectSingle->SetQuickHelpText(ScResId(STR_BTN_SELECT_CURRENT));
    maBtnSelectSingle->SetModeImage(aSingleSelect);
    maBtnSelectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
    maBtnSelectSingle->Show();
 
    BitmapEx aSingleUnselectBmp(RID_BMP_UNSELECT_CURRENT);
    if (fScaleFactor > 1)
        aSingleUnselectBmp.Scale(fScaleFactor, fScaleFactor, BmpScaleFlag::Fast);
    Image aSingleUnselect(aSingleUnselectBmp);
 
    getSectionPosSize(aPos, aSize, BTN_SINGLE_UNSELECT);
    maBtnUnselectSingle->SetPosSizePixel(aPos, aSize);
    maBtnUnselectSingle->SetQuickHelpText(ScResId(STR_BTN_UNSELECT_CURRENT));
    maBtnUnselectSingle->SetModeImage(aSingleUnselect);
    maBtnUnselectSingle->SetClickHdl( LINK(this, ScCheckListMenuWindow, ButtonHdl) );
    maBtnUnselectSingle->Show();
}
 
void ScCheckListMenuWindow::setAllMemberState(bool bSet)
{
    size_t n = maMembers.size();
    std::set<SvTreeListEntry*> aParents;
    for (size_t i = 0; i < n; ++i)
    {
        aParents.insert(maMembers[i].mpParent);
    }
 
    for (auto itr = aParents.begin(), itrEnd = aParents.end(); itr != itrEnd; ++itr)
    {
        if (!(*itr))
        {
            sal_uInt32 nCount = maChecks->GetEntryCount();
            for( sal_uInt32 i = 0; i < nCount; ++i)
            {
                SvTreeListEntry* pEntry = maChecks->GetEntry(i);
                if (!pEntry)
                    continue;
 
                maChecks->CheckEntry(pEntry, bSet);
            }
        }
        else
        {
            SvTreeListEntries& rEntries = (*itr)->GetChildEntries();
            for (auto it = rEntries.begin(), itEnd = rEntries.end(); it != itEnd; ++ it)
            {
                maChecks->CheckEntry(*itr, bSet);
            }
        }
    }
 
    if (!maConfig.mbAllowEmptySet)
        // We need to have at least one member selected.
        maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
}
 
void ScCheckListMenuWindow::selectCurrentMemberOnly(bool bSet)
{
    setAllMemberState(!bSet);
    SvTreeListEntry* pEntry = maChecks->GetCurEntry();
    if (!pEntry)
        return;
    maChecks->CheckEntry(pEntry, bSet );
}
 
IMPL_LINK( ScCheckListMenuWindow, ButtonHdl, Button*, pBtn, void )
{
    if (pBtn == maBtnOk.get())
        close(true);
    else if (pBtn == maBtnSelectSingle.get())
    {
        selectCurrentMemberOnly(true);
        CheckHdl(maChecks.get());
    }
    else if (pBtn == maBtnUnselectSingle.get())
    {
        selectCurrentMemberOnly(false);
        CheckHdl(maChecks.get());
    }
}
 
IMPL_LINK_NOARG(ScCheckListMenuWindow, TriStateHdl, Button*, void)
{
    switch (mePrevToggleAllState)
    {
        case TRISTATE_FALSE:
            maChkToggleAll->SetState(TRISTATE_TRUE);
            setAllMemberState(true);
        break;
        case TRISTATE_TRUE:
            maChkToggleAll->SetState(TRISTATE_FALSE);
            setAllMemberState(false);
        break;
        case TRISTATE_INDET:
        default:
            maChkToggleAll->SetState(TRISTATE_TRUE);
            setAllMemberState(true);
        break;
    }
 
    mePrevToggleAllState = maChkToggleAll->GetState();
}
 
IMPL_LINK_NOARG(ScCheckListMenuWindow, EdModifyHdl, Edit&, void)
{
    OUString aSearchText = maEdSearch->GetText();
    aSearchText = ScGlobal::pCharClass->lowercase( aSearchText );
    bool bSearchTextEmpty = aSearchText.isEmpty();
    size_t n = maMembers.size();
    size_t nSelCount = 0;
    OUString aLabelDisp;
    bool bSomeDateDeletes = false;
 
    for (size_t i = 0; i < n; ++i)
    {
        bool bIsDate = maMembers[i].mbDate;
        bool bPartialMatch = false;
 
        aLabelDisp = maMembers[i].maName;
        if ( aLabelDisp.isEmpty() )
            aLabelDisp = ScResId( STR_EMPTYDATA );
 
        if ( !bSearchTextEmpty )
        {
            if ( !bIsDate )
                bPartialMatch = ( ScGlobal::pCharClass->lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
            else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
                bPartialMatch = (ScGlobal::pCharClass->lowercase( OUString(
                                maMembers[i].maRealName + maMembers[i].maDateParts[1] )).indexOf( aSearchText ) != -1);
            else
                continue;
        }
        else if ( bIsDate && maMembers[i].meDatePartType != ScCheckListMember::DAY )
            continue;
 
        if ( bSearchTextEmpty )
        {
            SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], true, maMembers[i].mbVisible );
            updateMemberParents( pLeaf, i );
            if ( maMembers[i].mbVisible )
                ++nSelCount;
            continue;
        }
 
        if ( bPartialMatch )
        {
            SvTreeListEntry* pLeaf = maChecks->ShowCheckEntry( aLabelDisp, maMembers[i] );
            updateMemberParents( pLeaf, i );
            ++nSelCount;
        }
        else
        {
            maChecks->ShowCheckEntry( aLabelDisp, maMembers[i], false, false );
            if( bIsDate )
                bSomeDateDeletes = true;
        }
    }
 
    if ( bSomeDateDeletes )
    {
        for (size_t i = 0; i < n; ++i)
        {
            if ( !maMembers[i].mbDate ) continue;
            if ( maMembers[i].meDatePartType != ScCheckListMember::DAY ) continue;
            updateMemberParents( nullptr, i );
        }
    }
 
    if ( nSelCount == n )
        maChkToggleAll->SetState( TRISTATE_TRUE );
    else if ( nSelCount == 0 )
        maChkToggleAll->SetState( TRISTATE_FALSE );
    else
        maChkToggleAll->SetState( TRISTATE_INDET );
 
    if ( !maConfig.mbAllowEmptySet )
        maBtnOk->Enable( nSelCount != 0);
}
 
IMPL_LINK( ScCheckListMenuWindow, CheckHdl, SvTreeListBox*, pChecks, void )
{
    if (pChecks != maChecks.get())
        return;
    SvTreeListEntry* pEntry = pChecks->GetHdlEntry();
    if ( pEntry )
        maChecks->CheckEntry( pEntry,  ( pChecks->GetCheckButtonState( pEntry ) == SvButtonState::Checked ) );
    size_t nNumChecked = maChecks->GetCheckedEntryCount();
    if (nNumChecked == maMembers.size())
        // all members visible
        maChkToggleAll->SetState(TRISTATE_TRUE);
    else if (nNumChecked == 0)
        // no members visible
        maChkToggleAll->SetState(TRISTATE_FALSE);
    else
        maChkToggleAll->SetState(TRISTATE_INDET);
 
    if (!maConfig.mbAllowEmptySet)
        // We need to have at least one member selected.
        maBtnOk->Enable(nNumChecked != 0);
 
    mePrevToggleAllState = maChkToggleAll->GetState();
}
 
void ScCheckListMenuWindow::MouseMove(const MouseEvent& rMEvt)
{
    ScMenuFloatingWindow::MouseMove(rMEvt);
 
    size_t nSelectedMenu = getSelectedMenuItem();
    if (nSelectedMenu == MENU_NOT_SELECTED)
        queueCloseSubMenu();
}
 
bool ScCheckListMenuWindow::EventNotify(NotifyEvent& rNEvt)
{
    if (rNEvt.GetType() == MouseNotifyEvent::KEYUP)
    {
        const KeyEvent* pKeyEvent = rNEvt.GetKeyEvent();
        const vcl::KeyCode& rCode = pKeyEvent->GetKeyCode();
        bool bShift = rCode.IsShift();
        if (rCode.GetCode() == KEY_TAB)
        {
            maTabStops.CycleFocus(bShift);
            return true;
        }
    }
    return ScMenuFloatingWindow::EventNotify(rNEvt);
}
 
void ScCheckListMenuWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    ScMenuFloatingWindow::Paint(rRenderContext, rRect);
 
    const StyleSettings& rStyle = GetSettings().GetStyleSettings();
    Color aMemberBackColor = rStyle.GetFieldColor();
    Color aBorderColor = rStyle.GetShadowColor();
 
    Point aPos;
    Size aSize;
    getSectionPosSize(aPos, aSize, LISTBOX_AREA_OUTER);
 
    // Member list box background
    rRenderContext.SetFillColor(aMemberBackColor);
    rRenderContext.SetLineColor(aBorderColor);
    rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
 
    // Single-action button box
    getSectionPosSize(aPos, aSize, SINGLE_BTN_AREA);
    rRenderContext.SetFillColor(rStyle.GetMenuColor());
    rRenderContext.DrawRect(tools::Rectangle(aPos,aSize));
}
 
void ScCheckListMenuWindow::updateMemberParents( const SvTreeListEntry* pLeaf, size_t nIdx )
{
 
    if ( !maMembers[nIdx].mbDate || maMembers[nIdx].meDatePartType != ScCheckListMember::DAY )
        return;
 
    OUString aYearName  = maMembers[nIdx].maDateParts[0];
    OUString aMonthName = maMembers[nIdx].maDateParts[1];
    auto aItr = maYearMonthMap.find(aYearName + aMonthName);
 
    if ( pLeaf )
    {
        SvTreeListEntry* pMonthEntry = pLeaf->GetParent();
        SvTreeListEntry* pYearEntry = pMonthEntry ? pMonthEntry->GetParent() : nullptr;
 
        maMembers[nIdx].mpParent = pMonthEntry;
        if ( aItr != maYearMonthMap.end() )
        {
            size_t nMonthIdx = aItr->second;
            maMembers[nMonthIdx].mpParent = pYearEntry;
        }
    }
    else
    {
        SvTreeListEntry* pYearEntry = maChecks->FindEntry( nullptr, aYearName );
        if ( aItr != maYearMonthMap.end() && !pYearEntry )
        {
            size_t nMonthIdx = aItr->second;
            maMembers[nMonthIdx].mpParent = nullptr;
            maMembers[nIdx].mpParent = nullptr;
        }
        else if ( pYearEntry && !maChecks->FindEntry( pYearEntry, aMonthName ) )
            maMembers[nIdx].mpParent = nullptr;
    }
}
 
Reference<XAccessible> ScCheckListMenuWindow::CreateAccessible()
{
    if (!mxAccessible.is() && maEdSearch)
    {
        mxAccessible.set(new ScAccessibleFilterTopWindow(
            GetAccessibleParentWindow()->GetAccessible(), this, getName()));
        ScAccessibleFilterTopWindow* pAccTop = static_cast<ScAccessibleFilterTopWindow*>(mxAccessible.get());
        fillMenuItemsToAccessible(pAccTop);
 
        pAccTop->setAccessibleChild(
            maEdSearch->CreateAccessible(), ScAccessibleFilterTopWindow::EDIT_SEARCH_BOX);
        pAccTop->setAccessibleChild(
            maChecks->CreateAccessible(), ScAccessibleFilterTopWindow::LISTBOX);
        pAccTop->setAccessibleChild(
            maChkToggleAll->CreateAccessible(), ScAccessibleFilterTopWindow::TOGGLE_ALL);
        pAccTop->setAccessibleChild(
            maBtnSelectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_ON_BTN);
        pAccTop->setAccessibleChild(
            maBtnUnselectSingle->CreateAccessible(), ScAccessibleFilterTopWindow::SINGLE_OFF_BTN);
        pAccTop->setAccessibleChild(
            maBtnOk->CreateAccessible(), ScAccessibleFilterTopWindow::OK_BTN);
        pAccTop->setAccessibleChild(
            maBtnCancel->CreateAccessible(), ScAccessibleFilterTopWindow::CANCEL_BTN);
    }
 
    return mxAccessible;
}
 
void ScCheckListMenuWindow::setMemberSize(size_t n)
{
    maMembers.reserve(n);
}
 
void ScCheckListMenuWindow::addDateMember(const OUString& rsName, double nVal, bool bVisible)
{
    ScDocument* pDoc = getDoc();
    SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
 
    // Convert the numeric date value to a date object.
    Date aDate = pFormatter->GetNullDate();
    aDate.AddDays(rtl::math::approxFloor(nVal));
 
    sal_Int16 nYear = aDate.GetYear();
    sal_uInt16 nMonth = aDate.GetMonth();
    sal_uInt16 nDay = aDate.GetDay();
 
    // Get the localized month name list.
    CalendarWrapper* pCalendar = ScGlobal::GetCalendar();
    uno::Sequence<i18n::CalendarItem2> aMonths = pCalendar->getMonths();
    if (aMonths.getLength() < nMonth)
        return;
 
    OUString aYearName = OUString::number(nYear);
    OUString aMonthName = aMonths[nMonth-1].FullName;
    OUString aDayName = OUString::number(nDay);
 
    if ( aDayName.getLength() == 1 )
        aDayName = "0" + aDayName;
 
    maChecks->SetUpdateMode(false);
 
    SvTreeListEntry* pYearEntry = maChecks->FindEntry(nullptr, aYearName);
    if (!pYearEntry)
    {
        pYearEntry = maChecks->InsertEntry(aYearName, nullptr, true);
        ScCheckListMember aMemYear;
        aMemYear.maName = aYearName;
        aMemYear.maRealName = rsName;
        aMemYear.mbDate = true;
        aMemYear.mbLeaf = false;
        aMemYear.mbVisible = bVisible;
        aMemYear.mpParent = nullptr;
        aMemYear.meDatePartType = ScCheckListMember::YEAR;
        maMembers.push_back(aMemYear);
    }
 
    SvTreeListEntry* pMonthEntry = maChecks->FindEntry(pYearEntry, aMonthName);
    if (!pMonthEntry)
    {
        pMonthEntry = maChecks->InsertEntry(aMonthName, pYearEntry, true);
        ScCheckListMember aMemMonth;
        aMemMonth.maName = aMonthName;
        aMemMonth.maRealName = rsName;
        aMemMonth.mbDate = true;
        aMemMonth.mbLeaf = false;
        aMemMonth.mbVisible = bVisible;
        aMemMonth.mpParent = pYearEntry;
        aMemMonth.meDatePartType = ScCheckListMember::MONTH;
        maMembers.push_back(aMemMonth);
        maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
    }
 
    SvTreeListEntry* pDayEntry = maChecks->FindEntry(pMonthEntry, aDayName);
    if (!pDayEntry)
    {
        maChecks->InsertEntry(aDayName, pMonthEntry);
        ScCheckListMember aMemDay;
        aMemDay.maName = aDayName;
        aMemDay.maRealName = rsName;
        aMemDay.maDateParts.resize(2);
        aMemDay.maDateParts[0] = aYearName;
        aMemDay.maDateParts[1] = aMonthName;
        aMemDay.mbDate = true;
        aMemDay.mbLeaf = true;
        aMemDay.mbVisible = bVisible;
        aMemDay.mpParent = pMonthEntry;
        aMemDay.meDatePartType = ScCheckListMember::DAY;
        maMembers.push_back(aMemDay);
    }
 
    maChecks->SetUpdateMode(true);
}
 
void ScCheckListMenuWindow::addMember(const OUString& rName, bool bVisible)
{
    ScCheckListMember aMember;
    aMember.maName = rName;
    aMember.mbDate = false;
    aMember.mbLeaf = true;
    aMember.mbVisible = bVisible;
    aMember.mpParent = nullptr;
    maMembers.push_back(aMember);
}
 
ScTabStops::ScTabStops( ScCheckListMenuWindow* pMenuWin ) :
    mpMenuWindow( pMenuWin ),
    maControlToPos( ControlToPosMap() ),
    mnCurTabStop(0)
{
    maControls.reserve( 8 );
}
 
ScTabStops::~ScTabStops()
{}
 
void ScTabStops::AddTabStop( vcl::Window* pWin )
{
    maControls.emplace_back(pWin );
    maControlToPos[pWin] = maControls.size() - 1;
}
 
void ScTabStops::SetTabStop( vcl::Window* pWin )
{
    if ( !maControls.size() )
        return;
    ControlToPosMap::const_iterator aIter = maControlToPos.find( pWin );
    if ( aIter == maControlToPos.end() )
        return;
    if ( aIter->second == mnCurTabStop )
        return;
    if ( mnCurTabStop < maControls.size() )
    {
        maControls[mnCurTabStop]->SetFakeFocus( false );
        maControls[mnCurTabStop]->LoseFocus();
    }
    mnCurTabStop = aIter->second;
    maControls[mnCurTabStop]->SetFakeFocus( true );
    maControls[mnCurTabStop]->GrabFocus();
}
 
void ScTabStops::CycleFocus( bool bReverse )
{
    if (!maControls.size())
        return;
    if ( mnCurTabStop < maControls.size() )
    {
        maControls[mnCurTabStop]->SetFakeFocus( false );
        maControls[mnCurTabStop]->LoseFocus();
    }
    else
        mnCurTabStop = 0;
 
    if ( mpMenuWindow && mnCurTabStop == 0 )
        mpMenuWindow->clearSelectedMenuItem();
 
    size_t nIterCount = 0;
 
    if ( bReverse )
    {
        do
        {
            if ( mnCurTabStop > 0 )
                --mnCurTabStop;
            else
                mnCurTabStop = maControls.size() - 1;
            ++nIterCount;
        } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
    }
    else
    {
        do
        {
            ++mnCurTabStop;
            if ( mnCurTabStop >= maControls.size() )
                mnCurTabStop = 0;
            ++nIterCount;
        } while ( nIterCount <= maControls.size() && !maControls[mnCurTabStop]->IsEnabled() );
    }
 
    if ( nIterCount <= maControls.size() )
    {
        maControls[mnCurTabStop]->SetFakeFocus( true );
        maControls[mnCurTabStop]->GrabFocus();
    }
    // else : all controls are disabled, so can't do anything
}
 
void ScTabStops::clear()
{
    mnCurTabStop = 0;
    maControlToPos.clear();
    maControls.clear();
}
 
ScCheckListBox::ScCheckListBox( vcl::Window* pParent )
    :  SvTreeListBox( pParent, 0 ), mbSeenMouseButtonDown( false )
{
    Init();
    set_id("check_list_box");
}
 
SvTreeListEntry* ScCheckListBox::FindEntry( SvTreeListEntry* pParent, const OUString& sNode )
{
    sal_uInt32 nRootPos = 0;
    SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : GetEntry( nRootPos );
    while ( pEntry )
    {
        if ( sNode == GetEntryText( pEntry ) )
            return pEntry;
 
        pEntry = pParent ? pEntry->NextSibling() : GetEntry( ++nRootPos );
    }
    return nullptr;
}
 
void ScCheckListBox::Init()
{
    mpCheckButton.reset( new SvLBoxButtonData( this ) );
    EnableCheckButton( mpCheckButton.get() );
    SetNodeDefaultImages();
}
 
void ScCheckListBox::GetRecursiveChecked( SvTreeListEntry* pEntry, std::unordered_set<OUString>& vOut,
        OUString& rLabel )
{
    if (GetCheckButtonState(pEntry) == SvButtonState::Checked)
    {
        // We have to hash parents and children together.
        // Per convention for easy access in getResult()
        // "child;parent;grandparent" while descending.
        if (rLabel.isEmpty())
            rLabel = GetEntryText(pEntry);
        else
            rLabel = GetEntryText(pEntry) + ";" + rLabel;
 
        // Prerequisite: the selection mechanism guarantees that if a child is
        // selected then also the parent is selected, so we only have to
        // inspect the children in case the parent is selected.
        if (pEntry->HasChildren())
        {
            const SvTreeListEntries& rChildren = pEntry->GetChildEntries();
            for (auto& rChild : rChildren)
            {
                OUString aLabel = rLabel;
                GetRecursiveChecked( rChild.get(), vOut, aLabel);
                if (!aLabel.isEmpty() && aLabel != rLabel)
                    vOut.insert( aLabel);
            }
            // Let the caller not add the parent alone.
            rLabel.clear();
        }
    }
}
 
std::unordered_set<OUString> ScCheckListBox::GetAllChecked()
{
    std::unordered_set<OUString> vResults(0);
    sal_uInt32 nRootPos = 0;
    SvTreeListEntry* pEntry = GetEntry(nRootPos);
    while (pEntry)
    {
        OUString aLabel;
        GetRecursiveChecked( pEntry, vResults, aLabel);
        if (!aLabel.isEmpty())
            vResults.insert( aLabel);
        pEntry = GetEntry(++nRootPos);
    }
 
    return vResults;
}
 
bool ScCheckListBox::IsChecked( const OUString& sName, SvTreeListEntry* pParent )
{
    SvTreeListEntry* pEntry = FindEntry( pParent, sName );
    return pEntry && GetCheckButtonState( pEntry ) == SvButtonState::Checked;
}
 
void ScCheckListBox::CheckEntry( const OUString& sName, SvTreeListEntry* pParent, bool bCheck )
{
    SvTreeListEntry* pEntry = FindEntry( pParent, sName );
    if ( pEntry )
        CheckEntry(  pEntry, bCheck );
}
 
// Recursively check all children of pParent
void ScCheckListBox::CheckAllChildren( SvTreeListEntry* pParent, bool bCheck )
{
    if ( pParent )
    {
        SetCheckButtonState(
            pParent, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked );
    }
    SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
    while ( pEntry )
    {
        CheckAllChildren( pEntry, bCheck );
        pEntry = pEntry->NextSibling();
    }
}
 
void ScCheckListBox::CheckEntry( SvTreeListEntry* pParent, bool bCheck )
{
    // recursively check all items below pParent
    CheckAllChildren( pParent, bCheck );
    // checking pParent can affect ancestors, e.g. if ancestor is unchecked and pParent is
    // now checked then the ancestor needs to be checked also
    SvTreeListEntry* pAncestor = GetParent(pParent);
    if ( pAncestor )
    {
        while ( pAncestor )
        {
            // if any first level children checked then ancestor
            // needs to be checked, similarly if no first level children
            // checked then ancestor needs to be unchecked
            SvTreeListEntry* pChild = FirstChild( pAncestor );
            bool bChildChecked = false;
 
            while ( pChild )
            {
                if ( GetCheckButtonState( pChild ) == SvButtonState::Checked )
                {
                    bChildChecked = true;
                    break;
                }
                pChild = pChild->NextSibling();
            }
            SetCheckButtonState( pAncestor, bChildChecked ? SvButtonState::Checked : SvButtonState::Unchecked );
            pAncestor = GetParent(pAncestor);
        }
    }
}
 
SvTreeListEntry* ScCheckListBox::ShowCheckEntry( const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck )
{
    SvTreeListEntry* pEntry = nullptr;
    if (!rMember.mbDate || rMember.mpParent)
        pEntry = FindEntry( rMember.mpParent, sName );
 
    if ( bShow )
    {
        if ( !pEntry )
        {
            if (rMember.mbDate)
            {
                if (rMember.maDateParts.empty())
                    return nullptr;
 
                SvTreeListEntry* pYearEntry = FindEntry( nullptr, rMember.maDateParts[0] );
                if ( !pYearEntry )
                    pYearEntry = InsertEntry( rMember.maDateParts[0], nullptr, true );
                SvTreeListEntry* pMonthEntry = FindEntry( pYearEntry, rMember.maDateParts[1] );
                if ( !pMonthEntry )
                    pMonthEntry = InsertEntry( rMember.maDateParts[1], pYearEntry, true );
                SvTreeListEntry* pDayEntry = FindEntry( pMonthEntry, rMember.maName );
                if ( !pDayEntry )
                    pDayEntry = InsertEntry( rMember.maName, pMonthEntry );
 
                return pDayEntry; // Return leaf node
            }
 
            pEntry = InsertEntry(
                sName);
 
            SetCheckButtonState(
                pEntry, bCheck ? SvButtonState::Checked : SvButtonState::Unchecked);
        }
        else
            CheckEntry( pEntry, bCheck );
    }
    else if ( pEntry )
    {
        GetModel()->Remove( pEntry );
        SvTreeListEntry* pParent = rMember.mpParent;
        while ( pParent && !pParent->HasChildren() )
        {
            SvTreeListEntry* pTmp = pParent;
            pParent = pTmp->GetParent();
            GetModel()->Remove( pTmp );
        }
    }
    return nullptr;
}
 
void ScCheckListBox::CountCheckedEntries( SvTreeListEntry* pParent, sal_uLong& nCount ) const
{
    if ( pParent && GetCheckButtonState( pParent ) == SvButtonState::Checked  )
        nCount++;
    // Iterate over the children
    SvTreeListEntry* pEntry = pParent ? FirstChild( pParent ) : First();
    while ( pEntry )
    {
        CountCheckedEntries( pEntry, nCount );
        pEntry = pEntry->NextSibling();
    }
}
 
sal_uInt16 ScCheckListBox::GetCheckedEntryCount() const
{
    sal_uLong nCount = 0;
    CountCheckedEntries( nullptr,  nCount );
    return nCount;
}
 
void ScCheckListBox::KeyInput( const KeyEvent& rKEvt )
{
    const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
 
    if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
    {
        SvTreeListEntry* pEntry = GetCurEntry();
        if ( pEntry )
        {
            bool bCheck = ( GetCheckButtonState( pEntry ) == SvButtonState::Checked );
            CheckEntry( pEntry, !bCheck );
            if ( bCheck != ( GetCheckButtonState( pEntry ) == SvButtonState::Checked ) )
                CheckButtonHdl();
        }
    }
    else if ( GetEntryCount() )
        SvTreeListBox::KeyInput( rKEvt );
}
 
void ScCheckListBox::MouseButtonDown(const MouseEvent& rMEvt)
{
    SvTreeListBox::MouseButtonDown( rMEvt );
    if ( rMEvt.IsLeft() )
        mbSeenMouseButtonDown = true;
}
 
void ScCheckListBox::MouseButtonUp(const MouseEvent& rMEvt)
{
    SvTreeListBox::MouseButtonUp( rMEvt );
    if ( mpTabStops && mbSeenMouseButtonDown && rMEvt.IsLeft() )
    {
        mpTabStops->SetTabStop( this );
        mbSeenMouseButtonDown = false;
    }
}
 
void ScSearchEdit::MouseButtonDown(const MouseEvent& rMEvt)
{
    Edit::MouseButtonDown( rMEvt );
    if ( mpTabStops && rMEvt.IsLeft() && rMEvt.GetClicks() >= 1 )
        mpTabStops->SetTabStop( this );
}
 
void ScCheckListMenuWindow::setHasDates(bool bHasDates)
{
    // Enables type-ahead search in the check list box.
    maChecks->SetQuickSearch(true);
    if (bHasDates)
        maChecks->SetStyle(WB_HASBUTTONS | WB_HASLINES | WB_HASLINESATROOT | WB_HASBUTTONSATROOT);
    else
        maChecks->SetStyle(WB_HASBUTTONS);
}
 
void ScCheckListMenuWindow::initMembers()
{
    size_t n = maMembers.size();
    size_t nVisMemCount = 0;
 
    maChecks->SetUpdateMode(false);
    maChecks->GetModel()->EnableInvalidate(false);
 
    for (size_t i = 0; i < n; ++i)
    {
        if (maMembers[i].mbDate)
        {
            maChecks->CheckEntry(maMembers[i].maName, maMembers[i].mpParent, maMembers[i].mbVisible);
            // Expand first node of checked dates
            if (!maMembers[i].mpParent && maChecks->IsChecked(maMembers[i].maName,  maMembers[i].mpParent))
            {
                SvTreeListEntry* pEntry = maChecks->FindEntry(nullptr, maMembers[i].maName);
                if (pEntry)
                    maChecks->Expand(pEntry);
            }
        }
        else
        {
            OUString aLabel = maMembers[i].maName;
            if (aLabel.isEmpty())
                aLabel = ScResId(STR_EMPTYDATA);
            SvTreeListEntry* pEntry = maChecks->InsertEntry(
                aLabel);
 
            maChecks->SetCheckButtonState(
                pEntry, maMembers[i].mbVisible ? SvButtonState::Checked : SvButtonState::Unchecked);
        }
 
        if (maMembers[i].mbVisible)
            ++nVisMemCount;
    }
    if (nVisMemCount == n)
    {
        // all members visible
        maChkToggleAll->SetState(TRISTATE_TRUE);
        mePrevToggleAllState = TRISTATE_TRUE;
    }
    else if (nVisMemCount == 0)
    {
        // no members visible
        maChkToggleAll->SetState(TRISTATE_FALSE);
        mePrevToggleAllState = TRISTATE_FALSE;
    }
    else
    {
        maChkToggleAll->SetState(TRISTATE_INDET);
        mePrevToggleAllState = TRISTATE_INDET;
    }
 
    maChecks->GetModel()->EnableInvalidate(true);
    maChecks->SetUpdateMode(true);
}
 
void ScCheckListMenuWindow::setConfig(const Config& rConfig)
{
    maConfig = rConfig;
}
 
bool ScCheckListMenuWindow::isAllSelected() const
{
    return maChkToggleAll->IsChecked();
}
 
void ScCheckListMenuWindow::getResult(ResultType& rResult)
{
    ResultType aResult;
    std::unordered_set<OUString> vCheckeds = maChecks->GetAllChecked();
    size_t n = maMembers.size();
    for (size_t i = 0; i < n; ++i)
    {
        if ( maMembers[i].mbLeaf )
        {
            OUStringBuffer aLabel = maMembers[i].maName;
            if (aLabel.isEmpty())
                aLabel = ScResId(STR_EMPTYDATA);
 
            /* TODO: performance-wise this looks suspicious, concatenating to
             * do the lookup for each leaf item seems wasteful. */
            // Checked labels are in the form "child;parent;grandparent".
            for (SvTreeListEntry* pParent = maMembers[i].mpParent;
                    pParent && pParent->GetFirstItem( SvLBoxItemType::String);
                    pParent = pParent->GetParent())
            {
                aLabel.append(";").append(maChecks->GetEntryText( pParent));
            }
            bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
 
            ResultEntry aResultEntry;
            aResultEntry.bValid = bState;
            if ( maMembers[i].mbDate )
                aResultEntry.aName = maMembers[i].maRealName;
            else
                aResultEntry.aName = maMembers[i].maName;
            aResultEntry.bDate = maMembers[i].mbDate;
            aResult.insert(aResultEntry);
        }
    }
    rResult.swap(aResult);
}
 
void ScCheckListMenuWindow::launch(const tools::Rectangle& rRect)
{
    packWindow();
    if (!maConfig.mbAllowEmptySet)
        // We need to have at least one member selected.
        maBtnOk->Enable(maChecks->GetCheckedEntryCount() != 0);
 
    tools::Rectangle aRect(rRect);
    if (maConfig.mbRTL)
    {
        // In RTL mode, the logical "left" is visual "right".
        long nLeft = aRect.Left() - aRect.GetWidth();
        aRect.SetLeft( nLeft );
    }
    else if (maWndSize.Width() < aRect.GetWidth())
    {
        // Target rectangle (i.e. cell width) is wider than the window.
        // Simulate right-aligned launch by modifying the target rectangle
        // size.
        long nDiff = aRect.GetWidth() - maWndSize.Width();
        aRect.AdjustLeft(nDiff );
    }
 
    StartPopupMode(aRect, (FloatWinPopupFlags::Down | FloatWinPopupFlags::GrabFocus));
    maTabStops.CycleFocus(); // Set initial focus to the search box ( index = 1 )
}
 
void ScCheckListMenuWindow::close(bool bOK)
{
    if (bOK && mpOKAction.get())
        mpOKAction->execute();
 
    EndPopupMode();
}
 
void ScCheckListMenuWindow::setExtendedData(ExtendedData* p)
{
    mpExtendedData.reset(p);
}
 
ScCheckListMenuWindow::ExtendedData* ScCheckListMenuWindow::getExtendedData()
{
    return mpExtendedData.get();
}
 
void ScCheckListMenuWindow::setOKAction(Action* p)
{
    mpOKAction.reset(p);
}
 
void ScCheckListMenuWindow::setPopupEndAction(Action* p)
{
    mpPopupEndAction.reset(p);
}
 
void ScCheckListMenuWindow::handlePopupEnd()
{
    clearSelectedMenuItem();
    if (mpPopupEndAction)
        mpPopupEndAction->execute();
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V557 Array overrun is possible. The 'mnSelectedMenu' index is pointing beyond array bound.

V557 Array overrun is possible. The 'mnSelectedMenu' index is pointing beyond array bound.

V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: mpTabStops.