/* -*- 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 .
*/
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);
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);
}
}
returnfalse;
}
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);
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));
}
}
if (!maMenuItems[nPos].mxAction) // no action is defined. return;
constbool 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();
} elseif (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();
}
}
// 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));
// 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;
/* 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();
}
// 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();
// 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);
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;
}
// 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;
}
// 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;
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);
}
}
// Get the localized month name list.
CalendarWrapper& rCalendar = ScGlobal::GetCalendar();
uno::Sequence<i18n::CalendarItem2> aMonths = rCalendar.getMonths(); if (aMonths.getLength() < nMonth) 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();
}
// 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;
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();
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));
}
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);
}
} elseif (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 );
}
// 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();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.