1970 lines
64 KiB
C++
1970 lines
64 KiB
C++
/* -*- 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 <o3tl/safeint.hxx>
|
|
#include <o3tl/string_view.hxx>
|
|
#include <globstr.hrc>
|
|
#include <scresid.hxx>
|
|
|
|
#include <vcl/commandevent.hxx>
|
|
#include <vcl/decoview.hxx>
|
|
#include <vcl/event.hxx>
|
|
#include <vcl/settings.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/virdev.hxx>
|
|
#include <rtl/math.hxx>
|
|
#include <unotools/charclass.hxx>
|
|
#include <comphelper/lok.hxx>
|
|
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
|
|
#include <tools/json_writer.hxx>
|
|
#include <svl/numformat.hxx>
|
|
|
|
#include <document.hxx>
|
|
#include <viewdata.hxx>
|
|
|
|
using namespace com::sun::star;
|
|
using ::com::sun::star::uno::Reference;
|
|
|
|
ScCheckListMenuControl::MenuItemData::MenuItemData()
|
|
: mbEnabled(true)
|
|
{
|
|
}
|
|
|
|
ScCheckListMenuControl::SubMenuItemData::SubMenuItemData(ScCheckListMenuControl* pParent)
|
|
: maTimer("sc SubMenuItemData maTimer")
|
|
, mpSubMenu(nullptr)
|
|
, mnMenuPos(MENU_NOT_SELECTED)
|
|
, mpParent(pParent)
|
|
{
|
|
maTimer.SetInvokeHandler(LINK(this, ScCheckListMenuControl::SubMenuItemData, TimeoutHdl));
|
|
maTimer.SetTimeout(Application::GetSettings().GetMouseSettings().GetMenuDelay());
|
|
}
|
|
|
|
void ScCheckListMenuControl::SubMenuItemData::reset()
|
|
{
|
|
mpSubMenu = nullptr;
|
|
mnMenuPos = MENU_NOT_SELECTED;
|
|
maTimer.Stop();
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl::SubMenuItemData, TimeoutHdl, Timer *, void)
|
|
{
|
|
mpParent->handleMenuTimeout(this);
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, RowActivatedHdl, weld::TreeView&, bool)
|
|
{
|
|
executeMenuItem(mxMenu->get_selected_index());
|
|
return true;
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
|
|
{
|
|
// Assume that once the keyboard is used that focus should restore to this menu
|
|
// on dismissing a submenu
|
|
SetRestoreFocus(ScCheckListMenuControl::RestoreFocus::Menu);
|
|
|
|
const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
|
|
|
|
switch (rKeyCode.GetCode())
|
|
{
|
|
case KEY_RIGHT:
|
|
{
|
|
if (mnSelectedMenu >= maMenuItems.size() || mnSelectedMenu == MENU_NOT_SELECTED)
|
|
break;
|
|
|
|
const MenuItemData& rMenu = maMenuItems[mnSelectedMenu];
|
|
if (!rMenu.mxSubMenuWin)
|
|
break;
|
|
|
|
executeMenuItem(mnSelectedMenu);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, SelectHdl, weld::TreeView&, void)
|
|
{
|
|
sal_uInt32 nSelectedMenu = MENU_NOT_SELECTED;
|
|
if (!mxMenu->get_selected(mxScratchIter.get()))
|
|
{
|
|
// reselect current item if its submenu is up and the launching item
|
|
// became unselected by mouse moving out of the top level menu
|
|
if (mnSelectedMenu < maMenuItems.size() &&
|
|
maMenuItems[mnSelectedMenu].mxSubMenuWin &&
|
|
maMenuItems[mnSelectedMenu].mxSubMenuWin->IsVisible())
|
|
{
|
|
mxMenu->select(mnSelectedMenu);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
nSelectedMenu = mxMenu->get_iter_index_in_parent(*mxScratchIter);
|
|
|
|
setSelectedMenuItem(nSelectedMenu);
|
|
}
|
|
|
|
void ScCheckListMenuControl::addMenuItem(const OUString& rText, Action* pAction)
|
|
{
|
|
MenuItemData aItem;
|
|
aItem.mbEnabled = true;
|
|
aItem.mxAction.reset(pAction);
|
|
maMenuItems.emplace_back(std::move(aItem));
|
|
|
|
mxMenu->show();
|
|
mxMenu->append_text(rText);
|
|
mxMenu->set_image(mxMenu->n_children() - 1, css::uno::Reference<css::graphic::XGraphic>(), 1);
|
|
}
|
|
|
|
void ScCheckListMenuControl::addSeparator()
|
|
{
|
|
MenuItemData aItem;
|
|
maMenuItems.emplace_back(std::move(aItem));
|
|
|
|
mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, TreeSizeAllocHdl, const Size&, rSize, void)
|
|
{
|
|
if (maAllocatedSize == rSize)
|
|
return;
|
|
maAllocatedSize = rSize;
|
|
SetDropdownPos();
|
|
if (!mnAsyncSetDropdownPosId && Application::GetToolkitName().startsWith("gtk"))
|
|
{
|
|
// for gtk retry again later in case it didn't work (wayland)
|
|
mnAsyncSetDropdownPosId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, SetDropdownPosHdl));
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::SetDropdownPos()
|
|
{
|
|
std::vector<int> aWidths
|
|
{
|
|
o3tl::narrowing<int>(maAllocatedSize.Width() - (mxMenu->get_text_height() * 3) / 4 - 6)
|
|
};
|
|
mxMenu->set_column_fixed_widths(aWidths);
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, SetDropdownPosHdl, void*, void)
|
|
{
|
|
mnAsyncSetDropdownPosId = nullptr;
|
|
SetDropdownPos();
|
|
mxMenu->queue_resize();
|
|
}
|
|
|
|
void ScCheckListMenuControl::CreateDropDown()
|
|
{
|
|
const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
|
|
|
|
Color aSpinColor = rStyleSettings.GetDialogTextColor();
|
|
int nWidth = (mxMenu->get_text_height() * 3) / 4;
|
|
mxDropDown->SetOutputSizePixel(Size(nWidth, nWidth), /*bErase*/true, /*bAlphaMaskTransparent*/true);
|
|
DecorationView aDecoView(mxDropDown.get());
|
|
aDecoView.DrawSymbol(tools::Rectangle(Point(0, 0), Size(nWidth, nWidth)),
|
|
SymbolType::SPIN_RIGHT, aSpinColor,
|
|
DrawSymbolFlags::NONE);
|
|
}
|
|
|
|
ScListSubMenuControl* ScCheckListMenuControl::addSubMenuItem(const OUString& rText, bool bEnabled, bool bColorMenu)
|
|
{
|
|
MenuItemData aItem;
|
|
aItem.mbEnabled = bEnabled;
|
|
|
|
aItem.mxSubMenuWin.reset(new ScListSubMenuControl(mxMenu.get(), *this, bColorMenu));
|
|
maMenuItems.emplace_back(std::move(aItem));
|
|
|
|
mxMenu->show();
|
|
mxMenu->append_text(rText);
|
|
mxMenu->set_image(mxMenu->n_children() - 1, *mxDropDown, 1);
|
|
return maMenuItems.back().mxSubMenuWin.get();
|
|
}
|
|
|
|
void ScCheckListMenuControl::executeMenuItem(size_t nPos)
|
|
{
|
|
if (nPos >= maMenuItems.size())
|
|
return;
|
|
|
|
const MenuItemData& rMenu = maMenuItems[nPos];
|
|
if (rMenu.mxSubMenuWin)
|
|
{
|
|
if (rMenu.mbEnabled)
|
|
{
|
|
maOpenTimer.mnMenuPos = nPos;
|
|
maOpenTimer.mpSubMenu = rMenu.mxSubMenuWin.get();
|
|
launchSubMenu();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!maMenuItems[nPos].mxAction)
|
|
// no action is defined.
|
|
return;
|
|
|
|
const bool bClosePopup = maMenuItems[nPos].mxAction->execute();
|
|
if (bClosePopup)
|
|
terminateAllPopupMenus();
|
|
}
|
|
|
|
void ScCheckListMenuControl::setSelectedMenuItem(size_t nPos)
|
|
{
|
|
if (mnSelectedMenu == nPos)
|
|
// nothing to do.
|
|
return;
|
|
|
|
selectMenuItem(nPos, /*bSubMenuTimer*/true);
|
|
}
|
|
|
|
void ScCheckListMenuControl::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();
|
|
}
|
|
else if (pTimer == &maCloseTimer)
|
|
{
|
|
// end submenu.
|
|
if (maCloseTimer.mpSubMenu)
|
|
{
|
|
maCloseTimer.mpSubMenu->EndPopupMode();
|
|
maCloseTimer.mpSubMenu = nullptr;
|
|
|
|
// EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
|
|
if (!mnAsyncPostPopdownId)
|
|
mnAsyncPostPopdownId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, PostPopdownHdl));
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::queueLaunchSubMenu(size_t nPos, ScListSubMenuControl* pMenu)
|
|
{
|
|
if (!pMenu)
|
|
return;
|
|
|
|
// Set the submenu on launch queue.
|
|
if (maOpenTimer.mpSubMenu)
|
|
{
|
|
if (maOpenTimer.mpSubMenu != pMenu)
|
|
{
|
|
// new submenu is being requested.
|
|
queueCloseSubMenu();
|
|
}
|
|
else
|
|
{
|
|
if (pMenu == maCloseTimer.mpSubMenu)
|
|
maCloseTimer.reset();
|
|
}
|
|
}
|
|
|
|
maOpenTimer.mpSubMenu = pMenu;
|
|
maOpenTimer.mnMenuPos = nPos;
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
maOpenTimer.maTimer.Invoke();
|
|
else
|
|
maOpenTimer.maTimer.Start();
|
|
}
|
|
|
|
void ScCheckListMenuControl::queueCloseSubMenu()
|
|
{
|
|
if (!maOpenTimer.mpSubMenu)
|
|
// There is no submenu to close.
|
|
return;
|
|
|
|
// Stop any submenu on queue for opening.
|
|
maOpenTimer.maTimer.Stop();
|
|
|
|
// Flush any pending close so it doesn't get skipped
|
|
if (maCloseTimer.mpSubMenu)
|
|
{
|
|
maCloseTimer.mpSubMenu->EndPopupMode();
|
|
}
|
|
|
|
maCloseTimer.mpSubMenu = maOpenTimer.mpSubMenu;
|
|
maCloseTimer.mnMenuPos = maOpenTimer.mnMenuPos;
|
|
maOpenTimer.mpSubMenu = nullptr;
|
|
maOpenTimer.mnMenuPos = MENU_NOT_SELECTED;
|
|
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
maCloseTimer.maTimer.Invoke();
|
|
else
|
|
maCloseTimer.maTimer.Start();
|
|
}
|
|
|
|
tools::Rectangle ScCheckListMenuControl::GetSubMenuParentRect()
|
|
{
|
|
if (!mxMenu->get_selected(mxScratchIter.get()))
|
|
return tools::Rectangle();
|
|
return mxMenu->get_row_area(*mxScratchIter);
|
|
}
|
|
|
|
void ScCheckListMenuControl::launchSubMenu()
|
|
{
|
|
ScListSubMenuControl* pSubMenu = maOpenTimer.mpSubMenu;
|
|
if (!pSubMenu)
|
|
return;
|
|
|
|
if (!mxMenu->get_selected(mxScratchIter.get()))
|
|
return;
|
|
|
|
meRestoreFocus = DetermineRestoreFocus();
|
|
|
|
tools::Rectangle aRect = GetSubMenuParentRect();
|
|
pSubMenu->StartPopupMode(mxMenu.get(), aRect);
|
|
|
|
mxMenu->select(*mxScratchIter);
|
|
|
|
pSubMenu->GrabFocus();
|
|
}
|
|
|
|
ScCheckListMenuControl::RestoreFocus ScCheckListMenuControl::DetermineRestoreFocus() const
|
|
{
|
|
if (mxEdSearch->has_focus())
|
|
return RestoreFocus::EdSearch;
|
|
if (mpChecks->has_focus())
|
|
return RestoreFocus::Checks;
|
|
if (mxChkToggleAll->has_focus())
|
|
return RestoreFocus::ChkToggleAll;
|
|
if (mxChkLockChecked->has_focus())
|
|
return RestoreFocus::ChkLockChecked;
|
|
if (mxBtnSelectSingle->has_focus())
|
|
return RestoreFocus::BtnSelectSingle;
|
|
if (mxBtnUnselectSingle->has_focus())
|
|
return RestoreFocus::BtnUnselectSingle;
|
|
return RestoreFocus::Menu;
|
|
}
|
|
|
|
void ScCheckListMenuControl::RestorePreviousFocus()
|
|
{
|
|
switch (meRestoreFocus)
|
|
{
|
|
case RestoreFocus::EdSearch:
|
|
mxEdSearch->grab_focus();
|
|
break;
|
|
case RestoreFocus::Checks:
|
|
mpChecks->grab_focus();
|
|
break;
|
|
case RestoreFocus::ChkToggleAll:
|
|
mxChkToggleAll->grab_focus();
|
|
break;
|
|
case RestoreFocus::ChkLockChecked:
|
|
mxChkLockChecked->grab_focus();
|
|
break;
|
|
case RestoreFocus::BtnSelectSingle:
|
|
mxBtnSelectSingle->grab_focus();
|
|
break;
|
|
case RestoreFocus::BtnUnselectSingle:
|
|
mxBtnUnselectSingle->grab_focus();
|
|
break;
|
|
default:
|
|
mxMenu->grab_focus();
|
|
break;
|
|
}
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, PostPopdownHdl, void*, void)
|
|
{
|
|
mnAsyncPostPopdownId = nullptr;
|
|
RestorePreviousFocus();
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, MouseEnterHdl, const MouseEvent&, rMEvt, bool)
|
|
{
|
|
if (!rMEvt.IsEnterWindow())
|
|
return false;
|
|
selectMenuItem(MENU_NOT_SELECTED, true);
|
|
return false;
|
|
}
|
|
|
|
void ScCheckListMenuControl::endSubMenu(ScListSubMenuControl& rSubMenu)
|
|
{
|
|
rSubMenu.EndPopupMode();
|
|
maOpenTimer.reset();
|
|
|
|
// EndPopup sends a user event, and we want this focus to be set after that has done its conflicting focus-setting work
|
|
if (!mnAsyncPostPopdownId)
|
|
mnAsyncPostPopdownId = Application::PostUserEvent(LINK(this, ScCheckListMenuControl, PostPopdownHdl));
|
|
|
|
size_t nMenuPos = getSubMenuPos(&rSubMenu);
|
|
if (nMenuPos != MENU_NOT_SELECTED)
|
|
{
|
|
mnSelectedMenu = nMenuPos;
|
|
mxMenu->select(mnSelectedMenu);
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::addFields(const std::vector<OUString>& aFields)
|
|
{
|
|
if (!mbIsMultiField)
|
|
return;
|
|
|
|
mxFieldsCombo->clear();
|
|
|
|
for (auto& aField: aFields)
|
|
mxFieldsCombo->append_text(aField);
|
|
|
|
mxFieldsCombo->set_active(0);
|
|
}
|
|
|
|
tools::Long ScCheckListMenuControl::getField()
|
|
{
|
|
if (!mbIsMultiField)
|
|
return -1;
|
|
|
|
return mxFieldsCombo->get_active();
|
|
}
|
|
|
|
void ScCheckListMenuControl::selectMenuItem(size_t nPos, bool bSubMenuTimer)
|
|
{
|
|
mxMenu->select(nPos == MENU_NOT_SELECTED ? -1 : nPos);
|
|
mnSelectedMenu = nPos;
|
|
|
|
if (nPos >= maMenuItems.size() || nPos == MENU_NOT_SELECTED)
|
|
{
|
|
queueCloseSubMenu();
|
|
return;
|
|
}
|
|
|
|
if (!maMenuItems[nPos].mbEnabled)
|
|
{
|
|
queueCloseSubMenu();
|
|
return;
|
|
}
|
|
|
|
if (bSubMenuTimer)
|
|
{
|
|
if (maMenuItems[nPos].mxSubMenuWin && mxMenu->changed_by_hover())
|
|
{
|
|
ScListSubMenuControl* pSubMenu = maMenuItems[nPos].mxSubMenuWin.get();
|
|
queueLaunchSubMenu(nPos, pSubMenu);
|
|
}
|
|
else
|
|
queueCloseSubMenu();
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::clearSelectedMenuItem()
|
|
{
|
|
selectMenuItem(MENU_NOT_SELECTED, false);
|
|
}
|
|
|
|
size_t ScCheckListMenuControl::getSubMenuPos(const ScListSubMenuControl* pSubMenu)
|
|
{
|
|
size_t n = maMenuItems.size();
|
|
for (size_t i = 0; i < n; ++i)
|
|
{
|
|
if (maMenuItems[i].mxSubMenuWin.get() == pSubMenu)
|
|
return i;
|
|
}
|
|
return MENU_NOT_SELECTED;
|
|
}
|
|
|
|
void ScCheckListMenuControl::setSubMenuFocused(const ScListSubMenuControl* pSubMenu)
|
|
{
|
|
maCloseTimer.reset();
|
|
size_t nMenuPos = getSubMenuPos(pSubMenu);
|
|
if (mnSelectedMenu != nMenuPos)
|
|
{
|
|
mnSelectedMenu = nMenuPos;
|
|
mxMenu->select(mnSelectedMenu);
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::EndPopupMode()
|
|
{
|
|
if (!mbIsPoppedUp)
|
|
return;
|
|
mxPopover->connect_closed(Link<weld::Popover&, void>());
|
|
mxPopover->popdown();
|
|
PopupModeEndHdl(*mxPopover);
|
|
assert(mbIsPoppedUp == false);
|
|
}
|
|
|
|
void ScCheckListMenuControl::StartPopupMode(weld::Widget* pParent, const tools::Rectangle& rRect)
|
|
{
|
|
mxPopover->connect_closed(LINK(this, ScCheckListMenuControl, PopupModeEndHdl));
|
|
mbIsPoppedUp = true;
|
|
mxPopover->popup_at_rect(pParent, rRect);
|
|
GrabFocus();
|
|
}
|
|
|
|
void ScCheckListMenuControl::terminateAllPopupMenus()
|
|
{
|
|
EndPopupMode();
|
|
}
|
|
|
|
ScCheckListMenuControl::Config::Config() :
|
|
mbAllowEmptySet(true), mbRTL(false)
|
|
{
|
|
}
|
|
|
|
ScCheckListMember::ScCheckListMember()
|
|
: mnValue(0.0)
|
|
, mbVisible(true)
|
|
, mbMarked(false)
|
|
, mbCheck(true)
|
|
, mbHiddenByOtherFilter(false)
|
|
, mbDate(false)
|
|
, mbLeaf(false)
|
|
, mbValue(false)
|
|
, meDatePartType(YEAR)
|
|
{
|
|
}
|
|
|
|
// the value of border-width of FilterDropDown
|
|
constexpr int nBorderWidth = 4;
|
|
// number of rows visible in checklist
|
|
constexpr int nCheckListVisibleRows = 9;
|
|
// number of rows visible in colorlist
|
|
constexpr int nColorListVisibleRows = 9;
|
|
|
|
ScCheckListMenuControl::ScCheckListMenuControl(weld::Widget* pParent, ScViewData& rViewData,
|
|
bool bHasDates, int nWidth, bool bIsMultiField)
|
|
: mxBuilder(Application::CreateBuilder(pParent, u"modules/scalc/ui/filterdropdown.ui"_ustr))
|
|
, mxPopover(mxBuilder->weld_popover(u"FilterDropDown"_ustr))
|
|
, mxContainer(mxBuilder->weld_container(u"container"_ustr))
|
|
, mxMenu(mxBuilder->weld_tree_view(u"menu"_ustr))
|
|
, mxScratchIter(mxMenu->make_iterator())
|
|
, mxNonMenu(mxBuilder->weld_widget(u"nonmenu"_ustr))
|
|
, mxFieldsComboLabel(mxBuilder->weld_label(u"select_field_label"_ustr))
|
|
, mxFieldsCombo(mxBuilder->weld_combo_box(u"multi_field_combo"_ustr))
|
|
, mxEdSearch(mxBuilder->weld_entry(u"search_edit"_ustr))
|
|
, mxBox(mxBuilder->weld_widget(u"box"_ustr))
|
|
, mxListChecks(mxBuilder->weld_tree_view(u"check_list_box"_ustr))
|
|
, mxTreeChecks(mxBuilder->weld_tree_view(u"check_tree_box"_ustr))
|
|
, mxChkToggleAll(mxBuilder->weld_check_button(u"toggle_all"_ustr))
|
|
, mxChkLockChecked(mxBuilder->weld_check_button(u"lock_checked"_ustr))
|
|
, mxBtnSelectSingle(mxBuilder->weld_button(u"select_current"_ustr))
|
|
, mxBtnUnselectSingle(mxBuilder->weld_button(u"unselect_current"_ustr))
|
|
, mxButtonBox(mxBuilder->weld_box(u"buttonbox"_ustr))
|
|
, mxBtnOk(mxBuilder->weld_button(u"ok"_ustr))
|
|
, mxBtnCancel(mxBuilder->weld_button(u"cancel"_ustr))
|
|
, mxContextMenu(mxBuilder->weld_menu(u"contextmenu"_ustr))
|
|
, mxDropDown(mxMenu->create_virtual_device())
|
|
, mnCheckWidthReq(-1)
|
|
, mnWndWidth(0)
|
|
, mnCheckListVisibleRows(nCheckListVisibleRows)
|
|
, mePrevToggleAllState(TRISTATE_INDET)
|
|
, mnSelectedMenu(MENU_NOT_SELECTED)
|
|
, mrViewData(rViewData)
|
|
, mnAsyncPostPopdownId(nullptr)
|
|
, mnAsyncSetDropdownPosId(nullptr)
|
|
, meRestoreFocus(RestoreFocus::Menu)
|
|
, mbHasDates(bHasDates)
|
|
, mbIsPoppedUp(false)
|
|
, maOpenTimer(this)
|
|
, maCloseTimer(this)
|
|
, maSearchEditTimer("ScCheckListMenuControl maSearchEditTimer")
|
|
, mbIsMultiField(bIsMultiField)
|
|
{
|
|
mxTreeChecks->set_clicks_to_toggle(1);
|
|
mxListChecks->set_clicks_to_toggle(1);
|
|
|
|
mxNonMenu->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxEdSearch->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxListChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxTreeChecks->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxListChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
|
|
mxTreeChecks->connect_popup_menu(LINK(this, ScCheckListMenuControl, CommandHdl));
|
|
mxChkToggleAll->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxChkLockChecked->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxBtnSelectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxBtnUnselectSingle->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxBtnOk->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
mxBtnCancel->connect_mouse_move(LINK(this, ScCheckListMenuControl, MouseEnterHdl));
|
|
|
|
/*
|
|
tdf#136559 If we have no dates we don't need a tree
|
|
structure, just a list. GtkListStore can be then
|
|
used which is much faster than a GtkTreeStore, so
|
|
with no dates switch to the treeview which uses the
|
|
faster GtkListStore
|
|
*/
|
|
if (mbHasDates)
|
|
mpChecks = mxTreeChecks.get();
|
|
else
|
|
{
|
|
mxTreeChecks->hide();
|
|
mxListChecks->show();
|
|
mpChecks = mxListChecks.get();
|
|
}
|
|
|
|
int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
|
|
if (nWidth != -1)
|
|
{
|
|
mnCheckWidthReq = nWidth - nBorderWidth * 2 - 4;
|
|
mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
}
|
|
|
|
// sort ok/cancel into native order, if this was a dialog they would be auto-sorted, but this
|
|
// popup isn't a true dialog
|
|
mxButtonBox->sort_native_button_order();
|
|
|
|
mxTreeChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
|
|
mxListChecks->enable_toggle_buttons(weld::ColumnToggleType::Check);
|
|
|
|
mxBox->show();
|
|
if (mbIsMultiField)
|
|
{
|
|
mxFieldsComboLabel->show();
|
|
mxFieldsCombo->show();
|
|
}
|
|
else
|
|
{
|
|
mxFieldsComboLabel->hide();
|
|
mxFieldsCombo->hide();
|
|
}
|
|
mxEdSearch->show();
|
|
mxButtonBox->show();
|
|
|
|
mxMenu->connect_row_activated(LINK(this, ScCheckListMenuControl, RowActivatedHdl));
|
|
mxMenu->connect_changed(LINK(this, ScCheckListMenuControl, SelectHdl));
|
|
mxMenu->connect_key_press(LINK(this, ScCheckListMenuControl, MenuKeyInputHdl));
|
|
|
|
mxBtnOk->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
|
|
mxBtnCancel->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
|
|
if (mbIsMultiField)
|
|
mxFieldsCombo->connect_changed(LINK(this, ScCheckListMenuControl, ComboChangedHdl));
|
|
mxEdSearch->connect_changed(LINK(this, ScCheckListMenuControl, EdModifyHdl));
|
|
mxEdSearch->connect_activate(LINK(this, ScCheckListMenuControl, EdActivateHdl));
|
|
mxTreeChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
|
|
mxTreeChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
|
|
mxListChecks->connect_toggled(LINK(this, ScCheckListMenuControl, CheckHdl));
|
|
mxListChecks->connect_key_press(LINK(this, ScCheckListMenuControl, KeyInputHdl));
|
|
mxChkToggleAll->connect_toggled(LINK(this, ScCheckListMenuControl, TriStateHdl));
|
|
mxChkLockChecked->connect_toggled(LINK(this, ScCheckListMenuControl, LockCheckedHdl));
|
|
mxBtnSelectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
|
|
mxBtnUnselectSingle->connect_clicked(LINK(this, ScCheckListMenuControl, ButtonHdl));
|
|
|
|
CreateDropDown();
|
|
mxMenu->connect_size_allocate(LINK(this, ScCheckListMenuControl, TreeSizeAllocHdl));
|
|
|
|
// determine what width the checklist will end up with
|
|
mnCheckWidthReq = mxContainer->get_preferred_size().Width();
|
|
// make that size fixed now, we can now use mnCheckWidthReq to speed up
|
|
// bulk_insert_for_each
|
|
mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
|
|
maSearchEditTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT);
|
|
maSearchEditTimer.SetInvokeHandler(LINK(this, ScCheckListMenuControl, SearchEditTimeoutHdl));
|
|
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
mxBtnSelectSingle->hide();
|
|
mxBtnUnselectSingle->hide();
|
|
}
|
|
|
|
}
|
|
|
|
void ScCheckListMenuControl::GrabFocus()
|
|
{
|
|
if (mxEdSearch->get_visible())
|
|
{
|
|
mxEdSearch->grab_focus();
|
|
meRestoreFocus = RestoreFocus::EdSearch;
|
|
}
|
|
else
|
|
{
|
|
mxMenu->set_cursor(0);
|
|
mxMenu->grab_focus();
|
|
meRestoreFocus = RestoreFocus::Menu;
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::DropPendingEvents()
|
|
{
|
|
if (mnAsyncPostPopdownId)
|
|
{
|
|
Application::RemoveUserEvent(mnAsyncPostPopdownId);
|
|
mnAsyncPostPopdownId = nullptr;
|
|
}
|
|
if (mnAsyncSetDropdownPosId)
|
|
{
|
|
Application::RemoveUserEvent(mnAsyncSetDropdownPosId);
|
|
mnAsyncSetDropdownPosId = nullptr;
|
|
}
|
|
}
|
|
|
|
ScCheckListMenuControl::~ScCheckListMenuControl()
|
|
{
|
|
maSearchEditTimer.Stop();
|
|
EndPopupMode();
|
|
for (auto& rMenuItem : maMenuItems)
|
|
rMenuItem.mxSubMenuWin.reset();
|
|
DropPendingEvents();
|
|
}
|
|
|
|
void ScCheckListMenuControl::prepWindow()
|
|
{
|
|
mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height() + 2);
|
|
mnSelectedMenu = MENU_NOT_SELECTED;
|
|
if (mxMenu->n_children())
|
|
{
|
|
mxMenu->set_cursor(0);
|
|
mxMenu->unselect_all();
|
|
}
|
|
|
|
mnWndWidth = mxContainer->get_preferred_size().Width() + nBorderWidth * 2 + 4;
|
|
}
|
|
|
|
void ScCheckListMenuControl::setAllMemberState(bool bSet)
|
|
{
|
|
mpChecks->all_foreach([this, bSet](weld::TreeIter& rEntry){
|
|
if (mpChecks->get_sensitive(rEntry, 0))
|
|
mpChecks->set_toggle(rEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
return false;
|
|
});
|
|
|
|
if (!maConfig.mbAllowEmptySet)
|
|
{
|
|
// We need to have at least one member selected.
|
|
mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::selectCurrentMemberOnly(bool bSet)
|
|
{
|
|
setAllMemberState(!bSet);
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
|
|
if (!mpChecks->get_cursor(xEntry.get()))
|
|
return;
|
|
mpChecks->set_toggle(*xEntry, bSet ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, CommandHdl, const CommandEvent&, rCEvt, bool)
|
|
{
|
|
if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
|
|
return false;
|
|
|
|
mxContextMenu->set_sensitive(u"less"_ustr, mnCheckListVisibleRows > 4);
|
|
mxContextMenu->set_sensitive(u"more"_ustr, mnCheckListVisibleRows < 42);
|
|
|
|
OUString sCommand = mxContextMenu->popup_at_rect(mpChecks, tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1,1)));
|
|
if (sCommand.isEmpty())
|
|
return true;
|
|
|
|
if (sCommand == "more")
|
|
++mnCheckListVisibleRows;
|
|
else if (sCommand == "less")
|
|
--mnCheckListVisibleRows;
|
|
ResizeToRequest();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScCheckListMenuControl::ResizeToRequest()
|
|
{
|
|
int nChecksHeight = mxTreeChecks->get_height_rows(mnCheckListVisibleRows);
|
|
mxTreeChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
mxListChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
mxPopover->resize_to_request();
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, ButtonHdl, weld::Button&, rBtn, void)
|
|
{
|
|
if (&rBtn == mxBtnOk.get())
|
|
close(true);
|
|
else if (&rBtn == mxBtnCancel.get())
|
|
close(false);
|
|
else if (&rBtn == mxBtnSelectSingle.get() || &rBtn == mxBtnUnselectSingle.get())
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
|
|
bool bEntry = mpChecks->get_cursor(xEntry.get());
|
|
if (!bEntry)
|
|
xEntry.reset();
|
|
if (bEntry && mpChecks->get_sensitive(*xEntry, 0))
|
|
{
|
|
selectCurrentMemberOnly(&rBtn == mxBtnSelectSingle.get());
|
|
Check(xEntry.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void insertMember(weld::TreeView& rView, const weld::TreeIter& rIter, const ScCheckListMember& rMember, bool bChecked, bool bLock=false)
|
|
{
|
|
OUString aLabel = rMember.maName;
|
|
if (aLabel.isEmpty())
|
|
aLabel = ScResId(STR_EMPTYDATA);
|
|
rView.set_toggle(rIter, bChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
rView.set_text(rIter, aLabel, 0);
|
|
|
|
if (bLock)
|
|
rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter && !rMember.mbMarked);
|
|
else
|
|
rView.set_sensitive(rIter, !rMember.mbHiddenByOtherFilter);
|
|
}
|
|
|
|
void loadSearchedMembers(std::vector<int>& rSearchedMembers, std::vector<ScCheckListMember>& rMembers,
|
|
const OUString& rSearchText, bool bLock=false)
|
|
{
|
|
const OUString aSearchText = ScGlobal::getCharClass().lowercase( rSearchText );
|
|
|
|
for (size_t i = 0; i < rMembers.size(); ++i)
|
|
{
|
|
assert(!rMembers[i].mbDate);
|
|
|
|
OUString aLabelDisp = rMembers[i].maName;
|
|
if ( aLabelDisp.isEmpty() )
|
|
aLabelDisp = ScResId( STR_EMPTYDATA );
|
|
|
|
bool bPartialMatch = ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1;
|
|
|
|
if (!bPartialMatch)
|
|
continue;
|
|
if (!bLock || (!rMembers[i].mbMarked && !rMembers[i].mbHiddenByOtherFilter))
|
|
rSearchedMembers.push_back(i);
|
|
}
|
|
|
|
if (bLock)
|
|
for (size_t i = 0; i < rMembers.size(); ++i)
|
|
if (rMembers[i].mbMarked && !rMembers[i].mbHiddenByOtherFilter)
|
|
rSearchedMembers.push_back(i);
|
|
|
|
}
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, LockCheckedHdl, weld::Toggleable&, void)
|
|
{
|
|
// assume all members are checked
|
|
for (auto& aMember : maMembers)
|
|
aMember.mbCheck = true;
|
|
|
|
// go over the members visible in the popup, and remember which one is
|
|
// checked, and which one is not
|
|
mpChecks->all_foreach([this](weld::TreeIter& rEntry){
|
|
if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
|
|
{
|
|
for (auto& aMember : maMembers)
|
|
if (aMember.maName == mpChecks->get_text(rEntry))
|
|
aMember.mbMarked = true;
|
|
}
|
|
else
|
|
{
|
|
for (auto& aMember : maMembers)
|
|
if (aMember.maName == mpChecks->get_text(rEntry))
|
|
aMember.mbCheck = false;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
mpChecks->freeze();
|
|
mpChecks->clear();
|
|
mpChecks->thaw();
|
|
|
|
OUString aSearchText = mxEdSearch->get_text();
|
|
if (aSearchText.isEmpty())
|
|
{
|
|
initMembers(-1, !mxChkLockChecked->get_active());
|
|
}
|
|
else
|
|
{
|
|
std::vector<int> aShownIndexes;
|
|
loadSearchedMembers(aShownIndexes, maMembers, aSearchText, true);
|
|
std::vector<int> aFixedWidths { mnCheckWidthReq };
|
|
|
|
// insert the members, remember whether checked or unchecked.
|
|
mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes](weld::TreeIter& rIter, int i) {
|
|
size_t nIndex = aShownIndexes[i];
|
|
insertMember(*mpChecks, rIter, maMembers[nIndex], maMembers[nIndex].mbCheck, mxChkLockChecked->get_active());
|
|
}, nullptr, &aFixedWidths);
|
|
}
|
|
|
|
// unmarking should happen after the members are inserted
|
|
if (!mxChkLockChecked->get_active())
|
|
for (auto& aMember : maMembers)
|
|
aMember.mbMarked = false;
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, TriStateHdl, weld::Toggleable&, void)
|
|
{
|
|
switch (mePrevToggleAllState)
|
|
{
|
|
case TRISTATE_TRUE:
|
|
mxChkToggleAll->set_state(TRISTATE_FALSE);
|
|
setAllMemberState(false);
|
|
break;
|
|
case TRISTATE_FALSE:
|
|
case TRISTATE_INDET:
|
|
default:
|
|
mxChkToggleAll->set_state(TRISTATE_TRUE);
|
|
setAllMemberState(true);
|
|
break;
|
|
}
|
|
|
|
mePrevToggleAllState = mxChkToggleAll->get_state();
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, ComboChangedHdl, weld::ComboBox&, void)
|
|
{
|
|
if (mbIsMultiField && mxFieldChangedAction)
|
|
mxFieldChangedAction->execute();
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, SearchEditTimeoutHdl, Timer*, void)
|
|
{
|
|
OUString aSearchText = mxEdSearch->get_text();
|
|
aSearchText = ScGlobal::getCharClass().lowercase( aSearchText );
|
|
bool bSearchTextEmpty = aSearchText.isEmpty();
|
|
size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
|
|
[](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
|
|
size_t nSelCount = 0;
|
|
|
|
// This branch is the general case, the other is an optimized variant of
|
|
// this one where we can take advantage of knowing we have no hierarchy
|
|
if (mbHasDates)
|
|
{
|
|
mpChecks->freeze();
|
|
|
|
bool bSomeDateDeletes = false;
|
|
|
|
for (size_t i = 0; i < nEnableMember; ++i)
|
|
{
|
|
bool bIsDate = maMembers[i].mbDate;
|
|
bool bPartialMatch = false;
|
|
|
|
OUString aLabelDisp = maMembers[i].maName;
|
|
if ( aLabelDisp.isEmpty() )
|
|
aLabelDisp = ScResId( STR_EMPTYDATA );
|
|
|
|
if ( !bSearchTextEmpty )
|
|
{
|
|
if ( !bIsDate )
|
|
bPartialMatch = ( ScGlobal::getCharClass().lowercase( aLabelDisp ).indexOf( aSearchText ) != -1 );
|
|
else if ( maMembers[i].meDatePartType == ScCheckListMember::DAY ) // Match with both numerical and text version of month
|
|
bPartialMatch = (ScGlobal::getCharClass().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 )
|
|
{
|
|
auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i], true, maMembers[i].mbVisible);
|
|
updateMemberParents(xLeaf.get(), i);
|
|
if ( maMembers[i].mbVisible )
|
|
++nSelCount;
|
|
continue;
|
|
}
|
|
|
|
if ( bPartialMatch )
|
|
{
|
|
auto xLeaf = ShowCheckEntry(aLabelDisp, maMembers[i]);
|
|
updateMemberParents(xLeaf.get(), i);
|
|
++nSelCount;
|
|
}
|
|
else
|
|
{
|
|
ShowCheckEntry(aLabelDisp, maMembers[i], false, false);
|
|
if( bIsDate )
|
|
bSomeDateDeletes = true;
|
|
}
|
|
}
|
|
|
|
if ( bSomeDateDeletes )
|
|
{
|
|
for (size_t i = 0; i < nEnableMember; ++i)
|
|
{
|
|
if (!maMembers[i].mbDate)
|
|
continue;
|
|
if (maMembers[i].meDatePartType != ScCheckListMember::DAY)
|
|
continue;
|
|
updateMemberParents(nullptr, i);
|
|
}
|
|
}
|
|
|
|
mpChecks->thaw();
|
|
}
|
|
else
|
|
{
|
|
mpChecks->freeze();
|
|
|
|
// when there are a lot of rows, it is cheaper to simply clear the tree and either
|
|
// re-initialise or just insert the filtered lines
|
|
mpChecks->clear();
|
|
|
|
mpChecks->thaw();
|
|
|
|
if (bSearchTextEmpty)
|
|
nSelCount = initMembers();
|
|
else
|
|
{
|
|
std::vector<int> aShownIndexes;
|
|
loadSearchedMembers(aShownIndexes, maMembers, aSearchText, mxChkLockChecked->get_active());
|
|
std::vector<int> aFixedWidths { mnCheckWidthReq };
|
|
// tdf#122419 insert in the fastest order, this might be backwards.
|
|
mpChecks->bulk_insert_for_each(aShownIndexes.size(), [this, &aShownIndexes, &nSelCount](weld::TreeIter& rIter, int i) {
|
|
size_t nIndex = aShownIndexes[i];
|
|
insertMember(*mpChecks, rIter, maMembers[nIndex], true, mxChkLockChecked->get_active());
|
|
++nSelCount;
|
|
}, nullptr, &aFixedWidths);
|
|
}
|
|
}
|
|
|
|
if ( nSelCount == nEnableMember )
|
|
mxChkToggleAll->set_state( TRISTATE_TRUE );
|
|
else if ( nSelCount == 0 )
|
|
mxChkToggleAll->set_state( TRISTATE_FALSE );
|
|
else
|
|
mxChkToggleAll->set_state( TRISTATE_INDET );
|
|
|
|
if ( !maConfig.mbAllowEmptySet )
|
|
{
|
|
const bool bEmptySet( nSelCount == 0 );
|
|
mpChecks->set_sensitive(!bEmptySet);
|
|
mxChkToggleAll->set_sensitive(!bEmptySet);
|
|
mxBtnSelectSingle->set_sensitive(!bEmptySet);
|
|
mxBtnUnselectSingle->set_sensitive(!bEmptySet);
|
|
mxBtnOk->set_sensitive(!bEmptySet);
|
|
}
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, EdModifyHdl, weld::Entry&, void)
|
|
{
|
|
maSearchEditTimer.Start();
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, EdActivateHdl, weld::Entry&, bool)
|
|
{
|
|
if (mxBtnOk->get_sensitive())
|
|
close(true);
|
|
return true;
|
|
}
|
|
|
|
IMPL_LINK( ScCheckListMenuControl, CheckHdl, const weld::TreeView::iter_col&, rRowCol, void )
|
|
{
|
|
Check(&rRowCol.first);
|
|
}
|
|
|
|
void ScCheckListMenuControl::Check(const weld::TreeIter* pEntry)
|
|
{
|
|
if (pEntry)
|
|
CheckEntry(*pEntry, mpChecks->get_toggle(*pEntry) == TRISTATE_TRUE);
|
|
size_t nNumChecked = GetCheckedEntryCount();
|
|
size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
|
|
[](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
|
|
if (nNumChecked == nEnableMember)
|
|
// all members visible
|
|
mxChkToggleAll->set_state(TRISTATE_TRUE);
|
|
else if (nNumChecked == 0)
|
|
// no members visible
|
|
mxChkToggleAll->set_state(TRISTATE_FALSE);
|
|
else
|
|
mxChkToggleAll->set_state(TRISTATE_INDET);
|
|
|
|
if (!maConfig.mbAllowEmptySet)
|
|
// We need to have at least one member selected.
|
|
mxBtnOk->set_sensitive(nNumChecked != 0);
|
|
|
|
mePrevToggleAllState = mxChkToggleAll->get_state();
|
|
}
|
|
|
|
void ScCheckListMenuControl::updateMemberParents(const weld::TreeIter* 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 )
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xYearEntry;
|
|
std::unique_ptr<weld::TreeIter> xMonthEntry = mpChecks->make_iterator(pLeaf);
|
|
if (!mpChecks->iter_parent(*xMonthEntry))
|
|
xMonthEntry.reset();
|
|
else
|
|
{
|
|
xYearEntry = mpChecks->make_iterator(xMonthEntry.get());
|
|
if (!mpChecks->iter_parent(*xYearEntry))
|
|
xYearEntry.reset();
|
|
}
|
|
|
|
maMembers[nIdx].mxParent = std::move(xMonthEntry);
|
|
if ( aItr != maYearMonthMap.end() )
|
|
{
|
|
size_t nMonthIdx = aItr->second;
|
|
maMembers[nMonthIdx].mxParent = std::move(xYearEntry);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
|
|
if (aItr != maYearMonthMap.end() && !xYearEntry)
|
|
{
|
|
size_t nMonthIdx = aItr->second;
|
|
maMembers[nMonthIdx].mxParent.reset();
|
|
maMembers[nIdx].mxParent.reset();
|
|
}
|
|
else if (xYearEntry && !FindEntry(xYearEntry.get(), aMonthName))
|
|
maMembers[nIdx].mxParent.reset();
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::setMemberSize(size_t n)
|
|
{
|
|
maMembers.reserve(n);
|
|
}
|
|
|
|
void ScCheckListMenuControl::addDateMember(const OUString& rsName, double nVal, bool bVisible, bool bHiddenByOtherFilter)
|
|
{
|
|
SvNumberFormatter* pFormatter = mrViewData.GetDocument().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& rCalendar = ScGlobal::GetCalendar();
|
|
uno::Sequence<i18n::CalendarItem2> aMonths = rCalendar.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;
|
|
|
|
mpChecks->freeze();
|
|
|
|
std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, aYearName);
|
|
if (!xYearEntry)
|
|
{
|
|
xYearEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
|
|
mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xYearEntry, aYearName, 0);
|
|
mpChecks->set_sensitive(*xYearEntry, !bHiddenByOtherFilter);
|
|
ScCheckListMember aMemYear;
|
|
aMemYear.maName = aYearName;
|
|
aMemYear.maRealName = rsName;
|
|
aMemYear.mbDate = true;
|
|
aMemYear.mbLeaf = false;
|
|
aMemYear.mbVisible = bVisible;
|
|
aMemYear.mbHiddenByOtherFilter = bHiddenByOtherFilter;
|
|
aMemYear.mxParent.reset();
|
|
aMemYear.meDatePartType = ScCheckListMember::YEAR;
|
|
maMembers.emplace_back(std::move(aMemYear));
|
|
}
|
|
|
|
std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), aMonthName);
|
|
if (!xMonthEntry)
|
|
{
|
|
xMonthEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
|
|
mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xMonthEntry, aMonthName, 0);
|
|
mpChecks->set_sensitive(*xMonthEntry, !bHiddenByOtherFilter);
|
|
ScCheckListMember aMemMonth;
|
|
aMemMonth.maName = aMonthName;
|
|
aMemMonth.maRealName = rsName;
|
|
aMemMonth.mbDate = true;
|
|
aMemMonth.mbLeaf = false;
|
|
aMemMonth.mbVisible = bVisible;
|
|
aMemMonth.mbHiddenByOtherFilter = bHiddenByOtherFilter;
|
|
aMemMonth.mxParent = std::move(xYearEntry);
|
|
aMemMonth.meDatePartType = ScCheckListMember::MONTH;
|
|
maMembers.emplace_back(std::move(aMemMonth));
|
|
maYearMonthMap[aYearName + aMonthName] = maMembers.size() - 1;
|
|
}
|
|
|
|
std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), aDayName);
|
|
if (!xDayEntry)
|
|
{
|
|
xDayEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
|
|
mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xDayEntry, aDayName, 0);
|
|
mpChecks->set_sensitive(*xDayEntry, !bHiddenByOtherFilter);
|
|
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.mbHiddenByOtherFilter = bHiddenByOtherFilter;
|
|
aMemDay.mxParent = std::move(xMonthEntry);
|
|
aMemDay.meDatePartType = ScCheckListMember::DAY;
|
|
maMembers.emplace_back(std::move(aMemDay));
|
|
}
|
|
|
|
mpChecks->thaw();
|
|
}
|
|
|
|
void ScCheckListMenuControl::addMember(const OUString& rName, const double nVal, bool bVisible, bool bHiddenByOtherFilter, bool bValue)
|
|
{
|
|
ScCheckListMember aMember;
|
|
// tdf#46062 - indicate hidden whitespaces using quotes
|
|
aMember.maName = o3tl::trim(rName) != rName ? "\"" + rName + "\"" : rName;
|
|
aMember.maRealName = rName;
|
|
aMember.mnValue = nVal;
|
|
aMember.mbDate = false;
|
|
aMember.mbLeaf = true;
|
|
aMember.mbValue = bValue;
|
|
aMember.mbVisible = bVisible;
|
|
aMember.mbMarked = false;
|
|
aMember.mbCheck = true;
|
|
aMember.mbHiddenByOtherFilter = bHiddenByOtherFilter;
|
|
aMember.mxParent.reset();
|
|
maMembers.emplace_back(std::move(aMember));
|
|
}
|
|
|
|
void ScCheckListMenuControl::clearMembers()
|
|
{
|
|
maMembers.clear();
|
|
|
|
mpChecks->freeze();
|
|
mpChecks->clear();
|
|
mpChecks->thaw();
|
|
}
|
|
|
|
std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::FindEntry(const weld::TreeIter* pParent, std::u16string_view sNode)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(pParent);
|
|
bool bEntry = pParent ? mpChecks->iter_children(*xEntry) : mpChecks->get_iter_first(*xEntry);
|
|
while (bEntry)
|
|
{
|
|
if (sNode == mpChecks->get_text(*xEntry, 0))
|
|
return xEntry;
|
|
bEntry = mpChecks->iter_next_sibling(*xEntry);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ScCheckListMenuControl::GetRecursiveChecked(const weld::TreeIter* pEntry, std::unordered_set<OUString>& vOut,
|
|
OUString& rLabel)
|
|
{
|
|
if (mpChecks->get_toggle(*pEntry) != TRISTATE_TRUE)
|
|
return;
|
|
|
|
// We have to hash parents and children together.
|
|
// Per convention for easy access in getResult()
|
|
// "child;parent;grandparent" while descending.
|
|
if (rLabel.isEmpty())
|
|
rLabel = mpChecks->get_text(*pEntry, 0);
|
|
else
|
|
rLabel = mpChecks->get_text(*pEntry, 0) + ";" + 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 (!mpChecks->iter_has_child(*pEntry))
|
|
return;
|
|
|
|
std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(pEntry));
|
|
bool bChild = mpChecks->iter_children(*xChild);
|
|
while (bChild)
|
|
{
|
|
OUString aLabel = rLabel;
|
|
GetRecursiveChecked(xChild.get(), vOut, aLabel);
|
|
if (!aLabel.isEmpty() && aLabel != rLabel)
|
|
vOut.insert(aLabel);
|
|
bChild = mpChecks->iter_next_sibling(*xChild);
|
|
}
|
|
// Let the caller not add the parent alone.
|
|
rLabel.clear();
|
|
}
|
|
|
|
std::unordered_set<OUString> ScCheckListMenuControl::GetAllChecked()
|
|
{
|
|
std::unordered_set<OUString> vResults(0);
|
|
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
|
|
bool bEntry = mpChecks->get_iter_first(*xEntry);
|
|
while (bEntry)
|
|
{
|
|
OUString aLabel;
|
|
GetRecursiveChecked(xEntry.get(), vResults, aLabel);
|
|
if (!aLabel.isEmpty())
|
|
vResults.insert(aLabel);
|
|
bEntry = mpChecks->iter_next_sibling(*xEntry);
|
|
}
|
|
|
|
return vResults;
|
|
}
|
|
|
|
bool ScCheckListMenuControl::IsChecked(std::u16string_view sName, const weld::TreeIter* pParent)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
|
|
return xEntry && mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
|
|
}
|
|
|
|
void ScCheckListMenuControl::CheckEntry(std::u16string_view sName, const weld::TreeIter* pParent, bool bCheck)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = FindEntry(pParent, sName);
|
|
if (xEntry)
|
|
CheckEntry(*xEntry, bCheck);
|
|
}
|
|
|
|
// Recursively check all children of rParent
|
|
void ScCheckListMenuControl::CheckAllChildren(const weld::TreeIter& rParent, bool bCheck)
|
|
{
|
|
mpChecks->set_toggle(rParent, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator(&rParent);
|
|
bool bEntry = mpChecks->iter_children(*xEntry);
|
|
while (bEntry)
|
|
{
|
|
CheckAllChildren(*xEntry, bCheck);
|
|
bEntry = mpChecks->iter_next_sibling(*xEntry);
|
|
}
|
|
}
|
|
|
|
void ScCheckListMenuControl::CheckEntry(const weld::TreeIter& rParent, bool bCheck)
|
|
{
|
|
// recursively check all items below rParent
|
|
CheckAllChildren(rParent, bCheck);
|
|
// checking rParent can affect ancestors, e.g. if ancestor is unchecked and rParent is
|
|
// now checked then the ancestor needs to be checked also
|
|
if (!mpChecks->get_iter_depth(rParent))
|
|
return;
|
|
|
|
std::unique_ptr<weld::TreeIter> xAncestor(mpChecks->make_iterator(&rParent));
|
|
bool bAncestor = mpChecks->iter_parent(*xAncestor);
|
|
while (bAncestor)
|
|
{
|
|
// 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
|
|
std::unique_ptr<weld::TreeIter> xChild(mpChecks->make_iterator(xAncestor.get()));
|
|
bool bChild = mpChecks->iter_children(*xChild);
|
|
bool bChildChecked = false;
|
|
|
|
while (bChild)
|
|
{
|
|
if (mpChecks->get_toggle(*xChild) == TRISTATE_TRUE)
|
|
{
|
|
bChildChecked = true;
|
|
break;
|
|
}
|
|
bChild = mpChecks->iter_next_sibling(*xChild);
|
|
}
|
|
mpChecks->set_toggle(*xAncestor, bChildChecked ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
bAncestor = mpChecks->iter_parent(*xAncestor);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<weld::TreeIter> ScCheckListMenuControl::ShowCheckEntry(const OUString& sName, ScCheckListMember& rMember, bool bShow, bool bCheck)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry;
|
|
if (!rMember.mbDate || rMember.mxParent)
|
|
xEntry = FindEntry(rMember.mxParent.get(), sName);
|
|
|
|
if ( bShow )
|
|
{
|
|
if (!xEntry)
|
|
{
|
|
if (rMember.mbDate)
|
|
{
|
|
if (rMember.maDateParts.empty())
|
|
return nullptr;
|
|
|
|
std::unique_ptr<weld::TreeIter> xYearEntry = FindEntry(nullptr, rMember.maDateParts[0]);
|
|
if (!xYearEntry)
|
|
{
|
|
xYearEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(nullptr, -1, nullptr, nullptr, nullptr, nullptr, false, xYearEntry.get());
|
|
mpChecks->set_toggle(*xYearEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xYearEntry, rMember.maDateParts[0], 0);
|
|
}
|
|
std::unique_ptr<weld::TreeIter> xMonthEntry = FindEntry(xYearEntry.get(), rMember.maDateParts[1]);
|
|
if (!xMonthEntry)
|
|
{
|
|
xMonthEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(xYearEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xMonthEntry.get());
|
|
mpChecks->set_toggle(*xMonthEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xMonthEntry, rMember.maDateParts[1], 0);
|
|
}
|
|
std::unique_ptr<weld::TreeIter> xDayEntry = FindEntry(xMonthEntry.get(), rMember.maName);
|
|
if (!xDayEntry)
|
|
{
|
|
xDayEntry = mpChecks->make_iterator();
|
|
mpChecks->insert(xMonthEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xDayEntry.get());
|
|
mpChecks->set_toggle(*xDayEntry, TRISTATE_FALSE);
|
|
mpChecks->set_text(*xDayEntry, rMember.maName, 0);
|
|
}
|
|
return xDayEntry; // Return leaf node
|
|
}
|
|
|
|
xEntry = mpChecks->make_iterator();
|
|
mpChecks->append(xEntry.get());
|
|
mpChecks->set_toggle(*xEntry, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
mpChecks->set_text(*xEntry, sName, 0);
|
|
}
|
|
else
|
|
CheckEntry(*xEntry, bCheck);
|
|
}
|
|
else if (xEntry)
|
|
{
|
|
mpChecks->remove(*xEntry);
|
|
if (rMember.mxParent)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xParent(mpChecks->make_iterator(rMember.mxParent.get()));
|
|
while (xParent && !mpChecks->iter_has_child(*xParent))
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xTmp(mpChecks->make_iterator(xParent.get()));
|
|
if (!mpChecks->iter_parent(*xParent))
|
|
xParent.reset();
|
|
mpChecks->remove(*xTmp);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int ScCheckListMenuControl::GetCheckedEntryCount() const
|
|
{
|
|
int nRet = 0;
|
|
|
|
mpChecks->all_foreach([this, &nRet](weld::TreeIter& rEntry){
|
|
if (mpChecks->get_toggle(rEntry) == TRISTATE_TRUE)
|
|
++nRet;
|
|
return false;
|
|
});
|
|
|
|
return nRet;
|
|
}
|
|
|
|
IMPL_LINK(ScCheckListMenuControl, KeyInputHdl, const KeyEvent&, rKEvt, bool)
|
|
{
|
|
const vcl::KeyCode& rKey = rKEvt.GetKeyCode();
|
|
|
|
if ( rKey.GetCode() == KEY_RETURN || rKey.GetCode() == KEY_SPACE )
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
|
|
bool bEntry = mpChecks->get_cursor(xEntry.get());
|
|
if (bEntry && mpChecks->get_sensitive(*xEntry, 0))
|
|
{
|
|
bool bOldCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
|
|
CheckEntry(*xEntry, !bOldCheck);
|
|
bool bNewCheck = mpChecks->get_toggle(*xEntry) == TRISTATE_TRUE;
|
|
if (bOldCheck != bNewCheck)
|
|
Check(xEntry.get());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
size_t ScCheckListMenuControl::initMembers(int nMaxMemberWidth, bool bUnlock)
|
|
{
|
|
size_t n = maMembers.size();
|
|
size_t nEnableMember = std::count_if(maMembers.begin(), maMembers.end(),
|
|
[](const ScCheckListMember& rLMem) { return !rLMem.mbHiddenByOtherFilter; });
|
|
size_t nVisMemCount = 0;
|
|
|
|
if (nMaxMemberWidth == -1)
|
|
nMaxMemberWidth = mnCheckWidthReq;
|
|
|
|
if (!mpChecks->n_children() && !mbHasDates)
|
|
{
|
|
std::vector<int> aFixedWidths { nMaxMemberWidth };
|
|
// tdf#134038 insert in the fastest order, this might be backwards so only do it for
|
|
// the !mbHasDates case where no entry depends on another to exist before getting
|
|
// inserted. We cannot retain pre-existing treeview content, only clear and fill it.
|
|
mpChecks->bulk_insert_for_each(n, [this, &nVisMemCount, &bUnlock](weld::TreeIter& rIter, int i) {
|
|
assert(!maMembers[i].mbDate);
|
|
bool bCheck = ((mxChkLockChecked->get_active() || bUnlock) ? maMembers[i].mbMarked : maMembers[i].mbVisible);
|
|
insertMember(*mpChecks, rIter, maMembers[i], bCheck, mxChkLockChecked->get_active());
|
|
|
|
if (bCheck)
|
|
++nVisMemCount;
|
|
}, nullptr, &aFixedWidths);
|
|
}
|
|
else
|
|
{
|
|
mpChecks->freeze();
|
|
|
|
std::unique_ptr<weld::TreeIter> xEntry = mpChecks->make_iterator();
|
|
std::vector<std::unique_ptr<weld::TreeIter>> aExpandRows;
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
{
|
|
if (maMembers[i].mbDate)
|
|
{
|
|
CheckEntry(maMembers[i].maName, maMembers[i].mxParent.get(), maMembers[i].mbVisible);
|
|
// Expand first node of checked dates
|
|
if (!maMembers[i].mxParent && IsChecked(maMembers[i].maName, maMembers[i].mxParent.get()))
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xDateEntry = FindEntry(nullptr, maMembers[i].maName);
|
|
if (xDateEntry)
|
|
aExpandRows.emplace_back(std::move(xDateEntry));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mpChecks->append(xEntry.get());
|
|
insertMember(*mpChecks, *xEntry, maMembers[i], maMembers[i].mbVisible);
|
|
}
|
|
|
|
if (maMembers[i].mbVisible)
|
|
++nVisMemCount;
|
|
}
|
|
|
|
mpChecks->thaw();
|
|
|
|
for (const auto& rRow : aExpandRows)
|
|
mpChecks->expand_row(*rRow);
|
|
}
|
|
|
|
if (nVisMemCount == nEnableMember)
|
|
{
|
|
// all members visible
|
|
mxChkToggleAll->set_state(TRISTATE_TRUE);
|
|
mePrevToggleAllState = TRISTATE_TRUE;
|
|
}
|
|
else if (nVisMemCount == 0)
|
|
{
|
|
// no members visible
|
|
mxChkToggleAll->set_state(TRISTATE_FALSE);
|
|
mePrevToggleAllState = TRISTATE_FALSE;
|
|
}
|
|
else
|
|
{
|
|
mxChkToggleAll->set_state(TRISTATE_INDET);
|
|
mePrevToggleAllState = TRISTATE_INDET;
|
|
}
|
|
|
|
if (nVisMemCount)
|
|
mpChecks->set_cursor(0);
|
|
|
|
return nVisMemCount;
|
|
}
|
|
|
|
void ScCheckListMenuControl::setConfig(const Config& rConfig)
|
|
{
|
|
maConfig = rConfig;
|
|
}
|
|
|
|
bool ScCheckListMenuControl::isAllSelected() const
|
|
{
|
|
return mxChkToggleAll->get_state() == TRISTATE_TRUE;
|
|
}
|
|
|
|
void ScCheckListMenuControl::getResult(ResultType& rResult)
|
|
{
|
|
ResultType aResult;
|
|
std::unordered_set<OUString> vCheckeds = 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".
|
|
if (maMembers[i].mxParent)
|
|
{
|
|
std::unique_ptr<weld::TreeIter> xIter(mpChecks->make_iterator(maMembers[i].mxParent.get()));
|
|
do
|
|
{
|
|
aLabel.append(";" + mpChecks->get_text(*xIter));
|
|
}
|
|
while (mpChecks->iter_parent(*xIter));
|
|
}
|
|
|
|
bool bState = vCheckeds.find(aLabel.makeStringAndClear()) != vCheckeds.end();
|
|
|
|
ResultEntry aResultEntry;
|
|
aResultEntry.bValid = bState && !maMembers[i].mbHiddenByOtherFilter;
|
|
aResultEntry.aName = maMembers[i].maRealName;
|
|
aResultEntry.nValue = maMembers[i].mnValue;
|
|
aResultEntry.bDate = maMembers[i].mbDate;
|
|
aResultEntry.bValue = maMembers[i].mbValue;
|
|
aResult.insert(aResultEntry);
|
|
}
|
|
}
|
|
rResult.swap(aResult);
|
|
}
|
|
|
|
void ScCheckListMenuControl::launch(weld::Widget* pWidget, const tools::Rectangle& rRect)
|
|
{
|
|
prepWindow();
|
|
if (!maConfig.mbAllowEmptySet)
|
|
// We need to have at least one member selected.
|
|
mxBtnOk->set_sensitive(GetCheckedEntryCount() != 0);
|
|
|
|
tools::Rectangle aRect(rRect);
|
|
if (maConfig.mbRTL)
|
|
{
|
|
// In RTL mode, the logical "left" is visual "right".
|
|
if (!comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
tools::Long nLeft = aRect.Left() - aRect.GetWidth();
|
|
aRect.SetLeft( nLeft );
|
|
}
|
|
else
|
|
{
|
|
// in LOK mode, rRect is in document pixel coordinates, so width has to be added
|
|
// to place the popup next to the (visual) left aligned button.
|
|
aRect.Move(aRect.GetWidth(), 0);
|
|
}
|
|
}
|
|
else if (mnWndWidth < aRect.GetWidth())
|
|
{
|
|
// Target rectangle (i.e. cell width) is wider than the window.
|
|
// Simulate right-aligned launch by modifying the target rectangle
|
|
// size.
|
|
tools::Long nDiff = aRect.GetWidth() - mnWndWidth;
|
|
aRect.AdjustLeft(nDiff );
|
|
}
|
|
|
|
StartPopupMode(pWidget, aRect);
|
|
}
|
|
|
|
void ScCheckListMenuControl::close(bool bOK)
|
|
{
|
|
if (bOK && mxOKAction)
|
|
mxOKAction->execute();
|
|
EndPopupMode();
|
|
}
|
|
|
|
void ScCheckListMenuControl::setExtendedData(std::unique_ptr<ExtendedData> p)
|
|
{
|
|
mxExtendedData = std::move(p);
|
|
}
|
|
|
|
ScCheckListMenuControl::ExtendedData* ScCheckListMenuControl::getExtendedData()
|
|
{
|
|
return mxExtendedData.get();
|
|
}
|
|
|
|
void ScCheckListMenuControl::setOKAction(Action* p)
|
|
{
|
|
mxOKAction.reset(p);
|
|
}
|
|
|
|
void ScCheckListMenuControl::setPopupEndAction(Action* p)
|
|
{
|
|
mxPopupEndAction.reset(p);
|
|
}
|
|
|
|
void ScCheckListMenuControl::setFieldChangedAction(Action* p)
|
|
{
|
|
mxFieldChangedAction.reset(p);
|
|
}
|
|
|
|
IMPL_LINK_NOARG(ScCheckListMenuControl, PopupModeEndHdl, weld::Popover&, void)
|
|
{
|
|
mbIsPoppedUp = false;
|
|
clearSelectedMenuItem();
|
|
if (mxPopupEndAction)
|
|
mxPopupEndAction->execute();
|
|
|
|
DropPendingEvents();
|
|
}
|
|
|
|
int ScCheckListMenuControl::GetTextWidth(const OUString& rsName) const
|
|
{
|
|
return mxDropDown->GetTextWidth(rsName);
|
|
}
|
|
|
|
int ScCheckListMenuControl::IncreaseWindowWidthToFitText(int nMaxTextWidth)
|
|
{
|
|
int nBorder = nBorderWidth * 2 + 4;
|
|
int nNewWidth = nMaxTextWidth - nBorder;
|
|
if (nNewWidth > mnCheckWidthReq)
|
|
{
|
|
mnCheckWidthReq = nNewWidth;
|
|
int nChecksHeight = mpChecks->get_height_rows(nCheckListVisibleRows);
|
|
mpChecks->set_size_request(mnCheckWidthReq, nChecksHeight);
|
|
}
|
|
return mnCheckWidthReq + nBorder;
|
|
}
|
|
|
|
ScListSubMenuControl::ScListSubMenuControl(weld::Widget* pParent, ScCheckListMenuControl& rParentControl, bool bColorMenu)
|
|
: mxBuilder(Application::CreateBuilder(pParent, u"modules/scalc/ui/filtersubdropdown.ui"_ustr))
|
|
, mxPopover(mxBuilder->weld_popover(u"FilterSubDropDown"_ustr))
|
|
, mxContainer(mxBuilder->weld_container(u"container"_ustr))
|
|
, mxMenu(mxBuilder->weld_tree_view(u"menu"_ustr))
|
|
, mxBackColorMenu(mxBuilder->weld_tree_view(u"background"_ustr))
|
|
, mxTextColorMenu(mxBuilder->weld_tree_view(u"textcolor"_ustr))
|
|
, mxScratchIter(mxMenu->make_iterator())
|
|
, mrParentControl(rParentControl)
|
|
, mnBackColorMenuPrefHeight(-1)
|
|
, mnTextColorMenuPrefHeight(-1)
|
|
, mbColorMenu(bColorMenu)
|
|
{
|
|
mxMenu->hide();
|
|
mxBackColorMenu->hide();
|
|
mxTextColorMenu->hide();
|
|
|
|
if (!bColorMenu)
|
|
{
|
|
SetupMenu(*mxMenu);
|
|
mxMenu->show();
|
|
}
|
|
else
|
|
{
|
|
mxBackColorMenu->set_clicks_to_toggle(1);
|
|
mxBackColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
|
|
mxBackColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
|
|
mxTextColorMenu->set_clicks_to_toggle(1);
|
|
mxTextColorMenu->enable_toggle_buttons(weld::ColumnToggleType::Radio);
|
|
mxTextColorMenu->connect_changed(LINK(this, ScListSubMenuControl, ColorSelChangedHdl));
|
|
SetupMenu(*mxBackColorMenu);
|
|
SetupMenu(*mxTextColorMenu);
|
|
}
|
|
}
|
|
|
|
void ScListSubMenuControl::SetupMenu(weld::TreeView& rMenu)
|
|
{
|
|
rMenu.connect_row_activated(LINK(this, ScListSubMenuControl, RowActivatedHdl));
|
|
rMenu.connect_key_press(LINK(this, ScListSubMenuControl, MenuKeyInputHdl));
|
|
}
|
|
|
|
void ScListSubMenuControl::StartPopupMode(weld::Widget* pParent, const tools::Rectangle& rRect)
|
|
{
|
|
if (mxPopupStartAction)
|
|
mxPopupStartAction->execute();
|
|
|
|
mxPopover->popup_at_rect(pParent, rRect, weld::Placement::End);
|
|
|
|
weld::TreeView& rFirstMenu = mbColorMenu ? *mxBackColorMenu : *mxMenu;
|
|
rFirstMenu.set_cursor(0);
|
|
rFirstMenu.select(0);
|
|
|
|
mrParentControl.setSubMenuFocused(this);
|
|
}
|
|
|
|
void ScListSubMenuControl::EndPopupMode()
|
|
{
|
|
mxPopover->popdown();
|
|
}
|
|
|
|
void ScListSubMenuControl::GrabFocus()
|
|
{
|
|
weld::TreeView& rFirstMenu = mbColorMenu ? *mxBackColorMenu : *mxMenu;
|
|
rFirstMenu.grab_focus();
|
|
}
|
|
|
|
bool ScListSubMenuControl::IsVisible() const
|
|
{
|
|
return mxPopover->get_visible();
|
|
}
|
|
|
|
void ScListSubMenuControl::resizeToFitMenuItems()
|
|
{
|
|
if (!mbColorMenu)
|
|
mxMenu->set_size_request(-1, mxMenu->get_preferred_size().Height());
|
|
else
|
|
{
|
|
int nBackColorMenuPrefHeight = mnBackColorMenuPrefHeight;
|
|
if (nBackColorMenuPrefHeight == -1)
|
|
nBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
|
|
mxBackColorMenu->set_size_request(-1, nBackColorMenuPrefHeight);
|
|
int nTextColorMenuPrefHeight = mnTextColorMenuPrefHeight;
|
|
if (nTextColorMenuPrefHeight == -1)
|
|
nTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
|
|
mxTextColorMenu->set_size_request(-1, nTextColorMenuPrefHeight);
|
|
}
|
|
}
|
|
|
|
void ScListSubMenuControl::addItem(ScCheckListMenuControl::Action* pAction)
|
|
{
|
|
ScCheckListMenuControl::MenuItemData aItem;
|
|
aItem.mbEnabled = true;
|
|
aItem.mxAction.reset(pAction);
|
|
maMenuItems.emplace_back(std::move(aItem));
|
|
}
|
|
|
|
void ScListSubMenuControl::addMenuItem(const OUString& rText, ScCheckListMenuControl::Action* pAction)
|
|
{
|
|
addItem(pAction);
|
|
mxMenu->append(weld::toId(pAction), rText);
|
|
}
|
|
|
|
void ScListSubMenuControl::addMenuColorItem(const OUString& rText, bool bActive, VirtualDevice& rImage,
|
|
int nMenu, ScCheckListMenuControl::Action* pAction)
|
|
{
|
|
addItem(pAction);
|
|
|
|
weld::TreeView& rColorMenu = nMenu == 0 ? *mxBackColorMenu : *mxTextColorMenu;
|
|
rColorMenu.show();
|
|
|
|
OUString sId = weld::toId(pAction);
|
|
rColorMenu.insert(nullptr, -1, &rText, &sId, nullptr, nullptr, false, mxScratchIter.get());
|
|
rColorMenu.set_toggle(*mxScratchIter, bActive ? TRISTATE_TRUE : TRISTATE_FALSE);
|
|
rColorMenu.set_image(*mxScratchIter, rImage);
|
|
|
|
if (mnTextColorMenuPrefHeight == -1 &&
|
|
&rColorMenu == mxTextColorMenu.get() &&
|
|
mxTextColorMenu->n_children() == nColorListVisibleRows)
|
|
{
|
|
mnTextColorMenuPrefHeight = mxTextColorMenu->get_preferred_size().Height();
|
|
}
|
|
|
|
if (mnBackColorMenuPrefHeight == -1 &&
|
|
&rColorMenu == mxBackColorMenu.get() &&
|
|
mxBackColorMenu->n_children() == nColorListVisibleRows)
|
|
{
|
|
mnBackColorMenuPrefHeight = mxBackColorMenu->get_preferred_size().Height();
|
|
}
|
|
}
|
|
|
|
void ScListSubMenuControl::addSeparator()
|
|
{
|
|
ScCheckListMenuControl::MenuItemData aItem;
|
|
maMenuItems.emplace_back(std::move(aItem));
|
|
|
|
mxMenu->append_separator("separator" + OUString::number(maMenuItems.size()));
|
|
}
|
|
|
|
void ScListSubMenuControl::clearMenuItems()
|
|
{
|
|
maMenuItems.clear();
|
|
mxMenu->clear();
|
|
mxBackColorMenu->clear();
|
|
mnBackColorMenuPrefHeight = -1;
|
|
mxTextColorMenu->clear();
|
|
mnTextColorMenuPrefHeight = -1;
|
|
}
|
|
|
|
IMPL_LINK(ScListSubMenuControl, MenuKeyInputHdl, const KeyEvent&, rKEvt, bool)
|
|
{
|
|
bool bConsumed = false;
|
|
const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
|
|
const sal_uInt16 eKeyCode = rKeyCode.GetCode();
|
|
|
|
// Assume that once the keyboard is used that focus should restore to the
|
|
// parent menu
|
|
if (eKeyCode != KEY_ESCAPE)
|
|
mrParentControl.SetRestoreFocus(ScCheckListMenuControl::RestoreFocus::Menu);
|
|
|
|
switch (eKeyCode)
|
|
{
|
|
case KEY_ESCAPE:
|
|
case KEY_LEFT:
|
|
{
|
|
mrParentControl.endSubMenu(*this);
|
|
bConsumed = true;
|
|
break;
|
|
}
|
|
case KEY_SPACE:
|
|
case KEY_RETURN:
|
|
{
|
|
weld::TreeView& rMenu = !mbColorMenu ? *mxMenu :
|
|
(mxBackColorMenu->has_focus() ? *mxBackColorMenu : *mxTextColorMenu);
|
|
// don't toggle checkbutton, go straight to activating entry
|
|
bConsumed = RowActivatedHdl(rMenu);
|
|
break;
|
|
}
|
|
case KEY_DOWN:
|
|
{
|
|
if (mxTextColorMenu->get_visible() &&
|
|
mxBackColorMenu->has_focus() &&
|
|
mxBackColorMenu->get_selected_index() == mxBackColorMenu->n_children() - 1)
|
|
{
|
|
mxBackColorMenu->unselect_all();
|
|
mxTextColorMenu->select(0);
|
|
mxTextColorMenu->set_cursor(0);
|
|
mxTextColorMenu->grab_focus();
|
|
bConsumed = true;
|
|
}
|
|
break;
|
|
}
|
|
case KEY_UP:
|
|
{
|
|
if (mxBackColorMenu->get_visible() &&
|
|
mxTextColorMenu->has_focus() &&
|
|
mxTextColorMenu->get_selected_index() == 0)
|
|
{
|
|
mxTextColorMenu->unselect_all();
|
|
int nIndex = mxBackColorMenu->n_children() - 1;
|
|
mxBackColorMenu->select(nIndex);
|
|
mxBackColorMenu->set_cursor(nIndex);
|
|
mxBackColorMenu->grab_focus();
|
|
bConsumed = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bConsumed;
|
|
}
|
|
|
|
IMPL_LINK(ScListSubMenuControl, ColorSelChangedHdl, weld::TreeView&, rMenu, void)
|
|
{
|
|
if (rMenu.get_selected_index() == -1)
|
|
return;
|
|
if (&rMenu != mxTextColorMenu.get())
|
|
mxTextColorMenu->unselect_all();
|
|
else
|
|
mxBackColorMenu->unselect_all();
|
|
rMenu.grab_focus();
|
|
}
|
|
|
|
IMPL_LINK(ScListSubMenuControl, RowActivatedHdl, weld::TreeView&, rMenu, bool)
|
|
{
|
|
executeMenuItem(weld::fromId<ScCheckListMenuControl::Action*>(rMenu.get_selected_id()));
|
|
return true;
|
|
}
|
|
|
|
void ScListSubMenuControl::executeMenuItem(ScCheckListMenuControl::Action* pAction)
|
|
{
|
|
// if no action is defined.
|
|
if (!pAction)
|
|
return;
|
|
|
|
const bool bClosePopup = pAction->execute();
|
|
if (bClosePopup)
|
|
terminateAllPopupMenus();
|
|
}
|
|
|
|
void ScListSubMenuControl::setPopupStartAction(ScCheckListMenuControl::Action* p)
|
|
{
|
|
mxPopupStartAction.reset(p);
|
|
}
|
|
|
|
void ScListSubMenuControl::terminateAllPopupMenus()
|
|
{
|
|
EndPopupMode();
|
|
mrParentControl.terminateAllPopupMenus();
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|