1 // Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License, or (at your option) any later version.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Lesser General Public License for more details.
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #include "SUIT_FindActionDialog.h"
27 #include <QApplication>
35 #include <QHeaderView>
42 SUIT_FindActionDialog::SUIT_FindActionDialog(QWidget* theParent)
46 setWindowTitle(tr("Find action"));
47 QVBoxLayout* layout = new QVBoxLayout(this);
49 myQueryLineEdit = new QLineEdit(this);
50 layout->addWidget(myQueryLineEdit);
51 myQueryLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
52 setFocusProxy(myQueryLineEdit);
54 QHBoxLayout* searchOptionsLayout = new QHBoxLayout(this);
55 layout->addLayout(searchOptionsLayout);
56 myIncludeUnavailableActionsCB = new QCheckBox(tr("Unavailable actions"), this);
57 myIncludeUnavailableActionsCB->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
58 myIncludeUnavailableActionsCB->setCheckState(Qt::CheckState::Checked);
59 myActionSearcher.includeDisabledActions(true);
60 myIncludeInactiveModulesCB = new QCheckBox(tr("Inactive modules"), this);
61 myIncludeInactiveModulesCB->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
62 myIncludeInactiveModulesCB->setCheckState(Qt::CheckState::Unchecked);
63 searchOptionsLayout->addWidget(myIncludeUnavailableActionsCB);
64 searchOptionsLayout->addWidget(myIncludeInactiveModulesCB);
66 myFoundActionsTree = new SUIT_FoundActionTree(this);
67 layout->addWidget(myFoundActionsTree);
69 connect(myQueryLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(onQueryChanged(const QString&)));
70 connect(myIncludeUnavailableActionsCB, SIGNAL(stateChanged(int)), this, SLOT(onSearchOptionUnavailableActionsChanged(int)));
71 connect(myIncludeInactiveModulesCB, SIGNAL(stateChanged(int)), this, SLOT(onSearchOptionInactiveModulesChanged(int)));
73 myQueryLineEdit->installEventFilter(myFoundActionsTree);
76 void SUIT_FindActionDialog::setActiveModuleID(const QString& theModuleID)
78 myActiveModuleID = theModuleID;
79 if(myActionSearcher.setIncludedModuleIDs(std::set<QString>({SUIT_ShortcutMgr::ROOT_MODULE_ID, myActiveModuleID})))
83 void SUIT_FindActionDialog::onQueryChanged(const QString& theQuery)
85 if (myActionSearcher.setQuery(theQuery))
89 void SUIT_FindActionDialog::onSearchOptionUnavailableActionsChanged(int theState)
91 if (myActionSearcher.includeDisabledActions(theState == Qt::CheckState::Checked))
95 void SUIT_FindActionDialog::onSearchOptionInactiveModulesChanged(int theState)
97 bool resultsChanged = false;
98 if (theState == Qt::CheckState::Checked) {
99 myIncludeUnavailableActionsCB->setDisabled(true);
100 myIncludeUnavailableActionsCB->setCheckState(Qt::CheckState::Checked);
101 resultsChanged = myActionSearcher.setIncludedModuleIDs(SUIT_ShortcutMgr::get()->getShortcutContainer().getIDsOfAllModules());
104 myIncludeUnavailableActionsCB->setDisabled(false);
105 resultsChanged = myActionSearcher.setIncludedModuleIDs(std::set<QString>({SUIT_ShortcutMgr::ROOT_MODULE_ID, myActiveModuleID}));
112 void SUIT_FindActionDialog::updateUI()
114 myFoundActionsTree->updateItems(myActionSearcher.getSearchResults());
119 SUIT_FoundActionTree::SUIT_FoundActionTree(SUIT_FindActionDialog* theParent)
120 : QTreeWidget(theParent)
123 setSelectionMode(QAbstractItemView::SingleSelection);
124 setSortingEnabled(false); // Items
125 header()->setSectionResizeMode(QHeaderView::Interactive);
127 QMap<int, QString> labelMap;
128 labelMap[SUIT_FoundActionTree::ElementIdx::Name] = SUIT_FindActionDialog::tr("Action");
129 labelMap[SUIT_FoundActionTree::ElementIdx::ToolTip] = SUIT_FindActionDialog::tr("Description");
130 setHeaderLabels(labelMap.values());
132 setExpandsOnDoubleClick(false); // Implemented manually.
133 setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
134 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
136 setColumnWidth(SUIT_FoundActionTree::ElementIdx::Name, 120);
137 setColumnWidth(SUIT_FoundActionTree::ElementIdx::Name, 250);
138 setMinimumHeight(300);
140 setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
142 connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemExecuted(QTreeWidgetItem*, int)));
145 /*! \brief Compensates lack of std::distance(), which is introduced in C++17.
146 \returns -1, if theIt does not belong to the */
147 template <class Container>
149 const Container& theContainer,
150 const typename Container::iterator& theIt
152 auto it = theContainer.begin();
154 while (it != theContainer.end()) {
164 void SUIT_FoundActionTree::updateItems(const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& theAssets)
166 std::set<QString> shownModuleIDs; // To sort module-items by their IDs.
168 // Remove shown module items, if updated search results have no matching actions from these modules.
169 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); ) {
170 SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
171 myModuleItemExpansionStates[moduleItem->myModuleID] = moduleItem->isExpanded();
173 const auto itUpdatedAssetsOfShownModule = theAssets.find(moduleItem->myModuleID);
174 if (itUpdatedAssetsOfShownModule == theAssets.end()) {
175 delete takeTopLevelItem(moduleIdx);
179 if (itUpdatedAssetsOfShownModule->second.empty()) {
180 delete takeTopLevelItem(moduleIdx);
184 shownModuleIDs.emplace(moduleItem->myModuleID);
188 const auto shortcutMgr = SUIT_ShortcutMgr::get();
189 const QString lang = SUIT_ShortcutMgr::getLang();
191 SUIT_FoundActionTreeAction* preselectedActionItem = nullptr;
193 for (const auto& moduleIDAndAssets : theAssets) {
194 const QString& moduleID = moduleIDAndAssets.first;
195 const auto& moduleAssets = moduleIDAndAssets.second;
196 if (moduleAssets.empty())
199 const auto moduleItemAndIdx = findModuleItem(moduleID);
200 SUIT_FoundActionTreeModule* moduleItem = moduleItemAndIdx.first;
202 moduleItem = new SUIT_FoundActionTreeModule(moduleID);
203 moduleItem->setAssetsAndSearchData(SUIT_ActionSearcher::AssetsAndSearchData(shortcutMgr->getModuleAssets(moduleID)), lang);
205 const auto emplaceRes = shownModuleIDs.emplace(moduleID);
206 insertTopLevelItem(indexOf(shownModuleIDs, emplaceRes.first), moduleItem);
208 moduleItem->setFlags(Qt::ItemIsEnabled);
210 const auto itExpansionState = myModuleItemExpansionStates.find(moduleID);
211 if (itExpansionState == myModuleItemExpansionStates.end())
212 moduleItem->setExpanded(true); // Make module item expanded at first appearance.
214 moduleItem->setExpanded(itExpansionState->second);
216 else /* if the tree has the module-item */ {
217 const auto actionItems = moduleItem->takeChildren();
218 for (const auto actionItem : actionItems) {
223 // Fill module item with action items.
224 auto sortedActionItems = createActionSetWithComparator();
225 for (const auto& actionIDAndAssets : moduleAssets) {
226 const QString& inModuleActionID = actionIDAndAssets.first;
227 const SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = actionIDAndAssets.second;
229 auto actionItem = SUIT_FoundActionTreeAction::create(moduleID, inModuleActionID);
231 ShCutDbg("SUIT_FoundActionTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
235 actionItem->setAssetsAndSearchData(assetsAndSearchData, lang);
236 sortedActionItems.emplace(actionItem);
239 SUIT_FoundActionTreeAction* preselectedActionItemCand = nullptr;
240 for (const auto actionItem : sortedActionItems) {
241 moduleItem->addChild(actionItem);
243 // Consider first ranked available action in the module (if user did not collapsed it) as a candidate for preselected action.
244 if (!preselectedActionItemCand && moduleItem->isExpanded() && actionItem->isEnabledBufferedValue())
245 preselectedActionItemCand = actionItem;
248 if (preselectedActionItem) {
249 if (preselectedActionItemCand) {
250 if (preselectedActionItemCand->matchMetrics() < preselectedActionItem->matchMetrics())
251 preselectedActionItem = preselectedActionItemCand;
255 preselectedActionItem = preselectedActionItemCand;
258 if (preselectedActionItem)
259 setCurrentItem(preselectedActionItem);
262 void SUIT_FoundActionTree::sort(SUIT_FoundActionTree::SortKey theKey, SUIT_FoundActionTree::SortOrder theOrder)
264 if (theKey == mySortKey && theOrder == mySortOrder)
268 mySortOrder = theOrder;
270 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
271 const auto moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
273 auto sortedActionItems = createActionSetWithComparator();
274 for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
275 SUIT_FoundActionTreeAction* const actionItem = static_cast<SUIT_FoundActionTreeAction*>(moduleItem->child(childIdx));
276 sortedActionItems.emplace(actionItem);
279 moduleItem->takeChildren();
281 for (const auto actionItem : sortedActionItems) {
282 moduleItem->addChild(actionItem);
287 void SUIT_FoundActionTree::keyPressEvent(QKeyEvent* theEvent)
289 const auto key = theEvent->key();
290 const auto selectedItem = currentItem();
291 if ((key == Qt::Key_Enter || key == Qt::Key_Return) && selectedItem)
292 onItemExecuted(selectedItem, SUIT_FoundActionTree::ElementIdx::Name);
294 QTreeWidget::keyPressEvent(theEvent);
297 bool SUIT_FoundActionTree::eventFilter(QObject* theQObject, QEvent* theEvent)
299 if (theEvent->type() == QEvent::KeyPress) {
300 QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(theEvent);
301 const auto key = keyEvent->key();
303 if (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Up || key == Qt::Key_Down) {
304 keyPressEvent(keyEvent);
312 std::pair<SUIT_FoundActionTreeModule*, int> SUIT_FoundActionTree::findModuleItem(const QString& theModuleID) const
314 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
315 SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
316 if (moduleItem->myModuleID == theModuleID)
317 return std::pair<SUIT_FoundActionTreeModule*, int>(moduleItem, moduleIdx);
319 return std::pair<SUIT_FoundActionTreeModule*, int>(nullptr, -1);
322 std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>> SUIT_FoundActionTree::createActionSetWithComparator() const
324 QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> sortSchema = SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA;
326 for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
327 if (itSameKey->first == mySortKey) {
328 sortSchema.erase(itSameKey);
332 sortSchema.push_front(std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>(mySortKey, mySortOrder));
335 static const QCollator collator;
336 const std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)> comparator =
337 [sortSchema, &collator](const SUIT_FoundActionTreeAction* theItemA, const SUIT_FoundActionTreeAction* theItemB) {
338 for (const auto& keyAndOrder : sortSchema) {
339 const QVariant fieldOfA = theItemA->getValue(keyAndOrder.first);
340 const QVariant fieldOfB = theItemB->getValue(keyAndOrder.first);
342 bool* const fieldOfAIsDouble = new bool(false);
343 bool* const fieldOfBIsDouble = new bool(false);
344 const double matchMetricsA = fieldOfA.toDouble(fieldOfAIsDouble);
345 const double matchMetricsB = fieldOfB.toDouble(fieldOfBIsDouble);
346 if (fieldOfAIsDouble && fieldOfBIsDouble) {
347 const double res = matchMetricsA - matchMetricsB;
348 if (std::abs(res) > std::numeric_limits<double>::epsilon())
349 return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
352 const int res = collator.compare(fieldOfA.toString(), fieldOfB.toString());
354 return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
360 return std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>>(comparator);
363 void SUIT_FoundActionTree::onItemExecuted(QTreeWidgetItem* theItem, int theColIdx)
365 SUIT_FoundActionTreeItem* const item = static_cast<SUIT_FoundActionTreeItem*>(theItem);
366 if (item->type() == SUIT_FoundActionTreeItem::Type::Action) {
367 SUIT_FoundActionTreeAction* const actionItem = static_cast<SUIT_FoundActionTreeAction*>(theItem);
368 if (actionItem->trigger())
369 static_cast<SUIT_FindActionDialog*>(parentWidget())->accept();
371 else /* if (item->type() == SUIT_FoundActionTreeItem::Type::Module) */ {
372 item->setExpanded(!item->isExpanded());
376 /*static*/ const QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA =
378 {SUIT_FoundActionTree::SortKey::MatchMetrics, SUIT_FoundActionTree::SortOrder::Ascending},
379 {SUIT_FoundActionTree::SortKey::Name, SUIT_FoundActionTree::SortOrder::Ascending},
380 {SUIT_FoundActionTree::SortKey::ToolTip, SUIT_FoundActionTree::SortOrder::Ascending},
381 {SUIT_FoundActionTree::SortKey::ID, SUIT_FoundActionTree::SortOrder::Ascending}
385 SUIT_FoundActionTreeItem::SUIT_FoundActionTreeItem(const QString& theModuleID)
386 : QTreeWidgetItem(), myModuleID(theModuleID)
389 QString SUIT_FoundActionTreeItem::name() const
391 return text(SUIT_FoundActionTree::ElementIdx::Name);
394 QString SUIT_FoundActionTreeItem::toolTip() const
396 return text(SUIT_FoundActionTree::ElementIdx::ToolTip);
400 SUIT_FoundActionTreeModule::SUIT_FoundActionTreeModule(const QString& theModuleID)
401 : SUIT_FoundActionTreeItem(theModuleID)
403 QFont f = font(SUIT_FoundActionTree::ElementIdx::Name);
405 setFont(SUIT_FoundActionTree::ElementIdx::Name, f);
406 setText(SUIT_FoundActionTree::ElementIdx::Name, theModuleID);
409 void SUIT_FoundActionTreeModule::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
411 if (!theAssetsAndSD.myAssets)
414 setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
416 const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
417 if (ldaMap.empty()) {
418 setText(SUIT_FoundActionTree::ElementIdx::Name, myModuleID);
422 auto itLDA = ldaMap.find(theLang);
423 if (itLDA == ldaMap.end())
424 itLDA = ldaMap.begin();
426 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
427 const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
428 setText(SUIT_FoundActionTree::ElementIdx::Name, name);
431 QVariant SUIT_FoundActionTreeModule::getValue(SUIT_FoundActionTree::SortKey theKey) const
434 case SUIT_FoundActionTree::SortKey::MatchMetrics:
436 case SUIT_FoundActionTree::SortKey::ID:
438 case SUIT_FoundActionTree::SortKey::Name:
440 case SUIT_FoundActionTree::SortKey::ToolTip:
447 bool SUIT_FoundActionTreeModule::isEnabled() const
453 SUIT_FoundActionTreeAction::SUIT_FoundActionTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
454 : SUIT_FoundActionTreeItem(theModuleID), myInModuleActionID(theInModuleActionID),
455 myMatchMetrics(std::numeric_limits<double>::infinity()), myIsEnabledBufferedValue(false)
457 setText(SUIT_FoundActionTree::ElementIdx::Name, theInModuleActionID);
460 /*static*/ SUIT_FoundActionTreeAction* SUIT_FoundActionTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
462 if (theInModuleActionID.isEmpty()) {
463 ShCutDbg("SUIT_FoundActionTreeItem: attempt to create item with empty action ID.");
467 return new SUIT_FoundActionTreeAction(theModuleID, theInModuleActionID);
470 void SUIT_FoundActionTreeAction::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
472 if (!theAssetsAndSD.myAssets)
475 setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
477 const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
478 if (ldaMap.empty()) {
479 setText(SUIT_FoundActionTree::ElementIdx::Name, myInModuleActionID);
483 auto itLDA = ldaMap.find(theLang);
484 if (itLDA == ldaMap.end())
485 itLDA = ldaMap.begin();
487 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
488 const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
489 setText(SUIT_FoundActionTree::ElementIdx::Name, name);
491 setText(SUIT_FoundActionTree::ElementIdx::ToolTip, lda.myToolTip);
495 SUIT_FoundActionTree::ElementIdx::Name,
496 SUIT_FoundActionTree::tr("Double click to start")
500 SUIT_FoundActionTree::ElementIdx::ToolTip,
501 SUIT_FoundActionTree::tr("Double click to start")
505 static const QBrush greyedOutBrush = QBrush(Qt::gray);
506 setForeground(SUIT_FoundActionTree::ElementIdx::Name, greyedOutBrush);
507 setForeground(SUIT_FoundActionTree::ElementIdx::ToolTip, greyedOutBrush);
510 myMatchMetrics = theAssetsAndSD.matchMetrics();
513 QVariant SUIT_FoundActionTreeAction::getValue(SUIT_FoundActionTree::SortKey theKey) const
516 case SUIT_FoundActionTree::SortKey::MatchMetrics:
517 return myMatchMetrics;
518 case SUIT_FoundActionTree::SortKey::ID:
519 return myInModuleActionID;
520 case SUIT_FoundActionTree::SortKey::Name:
522 case SUIT_FoundActionTree::SortKey::ToolTip:
529 bool SUIT_FoundActionTreeAction::isEnabled() const
531 const auto& actions = SUIT_ShortcutMgr::get()->getActions(myModuleID, myInModuleActionID);
532 myIsEnabledBufferedValue = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); }) != actions.end();
533 return myIsEnabledBufferedValue;
536 bool SUIT_FoundActionTreeAction::trigger() const
539 const auto& actions = SUIT_ShortcutMgr::get()->getActions(myModuleID, myInModuleActionID);
540 for (const auto& action : actions) {
541 if (action->isEnabled()) {