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_ShortcutTree.h"
27 #include <QToolButton>
30 #include <QTableWidgetItem>
32 #include <QMessageBox>
33 #include <QPushButton>
36 #include <QHeaderView>
39 #include <QKeySequence>
45 #define COLUMN_SIZE 500
48 SUIT_KeySequenceEdit::SUIT_KeySequenceEdit(QWidget* parent)
52 myKeySequenceLineEdit->installEventFilter(this);
55 /*! \brief Set a key sequence to edit. */
56 void SUIT_KeySequenceEdit::setConfirmedKeySequence(const QKeySequence& theKeySequence)
58 myConfirmedKeySequenceString = theKeySequence.toString();
59 myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
60 myPrevKeySequenceString = myConfirmedKeySequenceString;
63 void SUIT_KeySequenceEdit::setEditedKeySequence(const QKeySequence& theKeySequence)
65 const QString keySequenceString = theKeySequence.toString();
66 myKeySequenceLineEdit->setText(keySequenceString);
67 myPrevKeySequenceString = keySequenceString;
70 QKeySequence SUIT_KeySequenceEdit::editedKeySequence() const
72 return QKeySequence::fromString(myKeySequenceLineEdit->text());
75 /*! \returns true, if the edited key sequence differs from confirmed one. */
76 bool SUIT_KeySequenceEdit::isKeySequenceModified() const
78 return QKeySequence(myConfirmedKeySequenceString) != editedKeySequence();
81 /*! \brief Set confirmed key sequence to line editor. */
82 void SUIT_KeySequenceEdit::restoreKeySequence()
84 myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
85 myPrevKeySequenceString = myConfirmedKeySequenceString;
89 \brief Gets the key sequence from keys that were pressed
91 \returns a string representation of the key sequence
93 /*static*/ QString SUIT_KeySequenceEdit::parseEvent(QKeyEvent* e)
95 bool isShiftPressed = e->modifiers() & Qt::ShiftModifier;
96 bool isControlPressed = e->modifiers() & Qt::ControlModifier;
97 bool isAltPressed = e->modifiers() & Qt::AltModifier;
98 bool isMetaPressed = e->modifiers() & Qt::MetaModifier;
99 bool isModifiersPressed = isControlPressed || isAltPressed || isMetaPressed; // Do not treat Shift alone as a modifier!
111 if ((isValidKey(aKey) && isModifiersPressed) || ((aKey >= Qt::Key_F1) && (aKey <= Qt::Key_F12)))
114 return QKeySequence(result).toString();
118 \brief Check if the key event contains a 'valid' key
119 \param theKey the code of the key
120 \returns \c true if the key is 'valid'
122 /*static*/ bool SUIT_KeySequenceEdit::isValidKey(int theKey)
124 if ( theKey == Qt::Key_Underscore || theKey == Qt::Key_Escape ||
125 ( theKey >= Qt::Key_Backspace && theKey <= Qt::Key_Delete ) ||
126 ( theKey >= Qt::Key_Home && theKey <= Qt::Key_PageDown ) ||
127 ( theKey >= Qt::Key_F1 && theKey <= Qt::Key_F12 ) ||
128 ( theKey >= Qt::Key_Space && theKey <= Qt::Key_Asterisk ) ||
129 ( theKey >= Qt::Key_Comma && theKey <= Qt::Key_AsciiTilde ) )
134 /*! \brief Called when "Clear" button is clicked. */
135 void SUIT_KeySequenceEdit::onClear()
137 myKeySequenceLineEdit->setText("");
138 myPrevKeySequenceString = "";
139 emit editingFinished();
142 /*! \brief Called when myKeySequenceLineEdit loses focus. */
143 void SUIT_KeySequenceEdit::onEditingFinished()
145 if (myKeySequenceLineEdit->text().endsWith("+"))
146 myKeySequenceLineEdit->setText(myPrevKeySequenceString);
148 myPrevKeySequenceString = myKeySequenceLineEdit->text();
149 emit editingFinished();
153 \brief Custom event filter.
154 \param obj event receiver object
156 \returns \c true if further event processing should be stopped
158 bool SUIT_KeySequenceEdit::eventFilter(QObject* theObject, QEvent* theEvent)
160 if (theObject == myKeySequenceLineEdit) {
161 if (theEvent->type() == QEvent::KeyPress) {
162 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(theEvent);
163 QString text = parseEvent(keyEvent);
164 if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace)
165 myKeySequenceLineEdit->setText("");
167 myKeySequenceLineEdit->setText(text);
169 emit editingStarted();
172 if (theEvent->type() == QEvent::KeyRelease) {
181 \brief Perform internal intialization.
183 void SUIT_KeySequenceEdit::initialize()
185 static const int PIXMAP_SIZE = 30;
187 QHBoxLayout* base = new QHBoxLayout( this );
191 base->addWidget(myKeySequenceLineEdit = new QLineEdit(this));
192 setFocusProxy(myKeySequenceLineEdit);
194 QToolButton* clearBtn = new QToolButton();
195 auto clearPixmap = QPixmap(":/images/shortcut_disable.svg");
196 clearPixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
197 clearBtn->setIcon(clearPixmap);
198 clearBtn->setToolTip(tr("Disable shortcut."));
199 base->addWidget(clearBtn);
201 QToolButton* restoreBtn = new QToolButton();
202 auto restorePixmap = QPixmap(":/images/shortcut_restore.svg");
203 restorePixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
204 restoreBtn->setIcon(restorePixmap);
205 restoreBtn->setToolTip(tr("Restore the currently applied key sequence."));
206 base->addWidget(restoreBtn);
208 myKeySequenceLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
209 clearBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
210 restoreBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
212 connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear()));
213 connect(restoreBtn, SIGNAL(clicked()), this, SIGNAL(restoreFromShortcutMgrClicked()));
214 connect(myKeySequenceLineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
218 /*! \param theParent must not be nullptr. */
219 SUIT_EditKeySequenceDialog::SUIT_EditKeySequenceDialog(SUIT_ShortcutTree* theParent)
222 setMinimumWidth(500);
223 setWindowTitle(tr("Change key sequence"));
224 QVBoxLayout* layout = new QVBoxLayout(this);
225 myActionName = new QLabel(this);
226 myActionName->setTextFormat(Qt::RichText);
227 myKeySequenceEdit = new SUIT_KeySequenceEdit(this);
228 myTextEdit = new QTextEdit(this);
229 layout->addWidget(myActionName);
230 layout->addWidget(myKeySequenceEdit);
231 layout->addWidget(myTextEdit);
232 myActionName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
233 myKeySequenceEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
234 myTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
235 myTextEdit->setReadOnly(true);
236 myTextEdit->setAcceptRichText(true);
237 myTextEdit->setPlaceholderText(tr("No conflicts."));
238 setFocusProxy(myKeySequenceEdit);
240 QHBoxLayout* buttonLayout = new QHBoxLayout(this);
241 layout->addLayout(buttonLayout);
242 QPushButton* confirmButton = new QPushButton(tr("Confirm"), this);
243 confirmButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
244 QPushButton* cancelButton = new QPushButton(tr("Cancel"), this);
245 cancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
246 buttonLayout->addStretch();
247 buttonLayout->addWidget(confirmButton);
248 buttonLayout->addWidget(cancelButton);
250 connect(myKeySequenceEdit, SIGNAL(editingStarted()), this, SLOT(onEditingStarted()));
251 connect(myKeySequenceEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
252 connect(myKeySequenceEdit, SIGNAL(restoreFromShortcutMgrClicked()), this, SLOT(onRestoreFromShortcutMgr()));
253 connect(confirmButton, SIGNAL(clicked()), this, SLOT(onConfirm()));
254 connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
257 void SUIT_EditKeySequenceDialog::setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID)
259 myModuleID = theModuleID;
260 myInModuleActionID = theInModuleActionID;
263 const QString& SUIT_EditKeySequenceDialog::moduleID() const { return myModuleID; }
264 const QString& SUIT_EditKeySequenceDialog::inModuleActionID() const { return myInModuleActionID; }
266 void SUIT_EditKeySequenceDialog::setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip)
268 myActionName->setText("<b>" + theModuleName + "</b> " + theActionName);
269 myActionName->setToolTip(theActionToolTip);
272 void SUIT_EditKeySequenceDialog::setConfirmedKeySequence(const QKeySequence& theSequence)
274 myKeySequenceEdit->setConfirmedKeySequence(theSequence);
277 QKeySequence SUIT_EditKeySequenceDialog::editedKeySequence() const
279 return myKeySequenceEdit->editedKeySequence();
282 int SUIT_EditKeySequenceDialog::exec()
284 myKeySequenceEdit->setFocus(Qt::ActiveWindowFocusReason);
285 return QDialog::exec();
288 void SUIT_EditKeySequenceDialog::onEditingStarted()
290 myTextEdit->setEnabled(false);
293 void SUIT_EditKeySequenceDialog::onEditingFinished()
295 updateConflictsMessage();
298 void SUIT_EditKeySequenceDialog::onRestoreFromShortcutMgr()
300 const auto shortcutMgr = SUIT_ShortcutMgr::get();
301 myKeySequenceEdit->setEditedKeySequence(shortcutMgr->getKeySequence(myModuleID, myInModuleActionID));
302 updateConflictsMessage();
305 /*! Updates message with list of actions, whose shortcuts will be disabled on Confirm. */
306 void SUIT_EditKeySequenceDialog::updateConflictsMessage()
308 myTextEdit->setEnabled(true);
309 QTextDocument* doc = myTextEdit->document();
311 doc = new QTextDocument(myTextEdit);
312 myTextEdit->setDocument(doc);
315 if (!myKeySequenceEdit->isKeySequenceModified()) {
320 const QKeySequence newKeySequence = editedKeySequence();
322 const auto shortcutTree = static_cast<SUIT_ShortcutTree*>(parentWidget());
323 /** {moduleID, inModuleActionID}[] */
324 std::set<std::pair<QString, QString>> conflicts = shortcutTree->shortcutContainer()->getConflicts(myModuleID, myInModuleActionID, newKeySequence);
325 if (!conflicts.empty()) {
326 const auto shortcutMgr = SUIT_ShortcutMgr::get();
328 QString report = "<b>" + tr("These shortcuts will be disabled on confirm:") + "</b>";
331 for (const auto& conflict : conflicts) {
332 const QString conflictingModuleName = shortcutMgr->getModuleName(conflict.first);
333 const QString conflictingActionName = shortcutMgr->getActionName(conflict.first, conflict.second);
334 report += "<li><b>" + conflictingModuleName + "</b> " + conflictingActionName + "</li>";
338 doc->setHtml(report);
340 else /* if no conflicts */ {
345 void SUIT_EditKeySequenceDialog::onConfirm()
347 if (myKeySequenceEdit->isKeySequenceModified())
354 /*! \brief Compensates lack of std::distance(), which is introduced in C++17.
355 \returns -1, if theIt does not belong to the */
356 template <class Container>
358 const Container& theContainer,
359 const typename Container::iterator& theIt
361 auto it = theContainer.begin();
363 while (it != theContainer.end()) {
374 /*! \param theContainer Share the same container between several trees,
375 to edit them synchronously even without exchange of changes with SUIT_ShortcutMgr.
376 Pass nullptr to create non-synchronized tree. */
377 SUIT_ShortcutTree::SUIT_ShortcutTree(
378 std::shared_ptr<SUIT_ShortcutContainer> theContainer,
380 ) : QTreeWidget(theParent),
381 myShortcutContainer(theContainer ? theContainer : std::shared_ptr<SUIT_ShortcutContainer>(new SUIT_ShortcutContainer())),
382 mySortKey(SUIT_ShortcutTree::SortKey::Name), mySortOrder(SUIT_ShortcutTree::SortOrder::Ascending)
385 setSelectionMode(QAbstractItemView::SingleSelection);
386 setColumnWidth(0, COLUMN_SIZE);
387 setSortingEnabled(false); // Items are sorted in the same way, as in ShortcutContainer.
388 header()->setSectionResizeMode(QHeaderView::Interactive);
390 QMap<int, QString> labelMap;
391 labelMap[SUIT_ShortcutTree::ElementIdx::Name] = tr("Action");
392 labelMap[SUIT_ShortcutTree::ElementIdx::KeySequence] = tr("Key sequence");
393 setHeaderLabels(labelMap.values());
395 setExpandsOnDoubleClick(false); // Open shortcut editor on double click instead.
396 setSortingEnabled(false);
397 setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
398 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
399 myEditDialog = new SUIT_EditKeySequenceDialog(this);
401 this->installEventFilter(this);
402 connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
404 SUIT_ShortcutTree::instances[myShortcutContainer.get()].emplace(this);
407 SUIT_ShortcutTree::~SUIT_ShortcutTree()
409 SUIT_ShortcutTree::instances[myShortcutContainer.get()].erase(this);
410 if (SUIT_ShortcutTree::instances[myShortcutContainer.get()].empty())
411 SUIT_ShortcutTree::instances.erase(myShortcutContainer.get());
414 /*! \brief Copies shortcuts from ShortcutMgr. (Re)displays shortcuts of myModuleIDs. */
415 void SUIT_ShortcutTree::setShortcutsFromManager()
417 const auto shortcutMgr = SUIT_ShortcutMgr::get();
418 *myShortcutContainer = shortcutMgr->getShortcutContainer();
419 // nb! ShortcutMgr never removes shortcuts from its container, only disables.
421 updateItems(false /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
424 /*! \brief Copies shortcuts from resources, user files are not accounted. (Re)displays shortcuts of myModuleIDs. */
425 void SUIT_ShortcutTree::setDefaultShortcuts()
427 SUIT_ShortcutContainer defaultShortcuts;
428 SUIT_ShortcutMgr::fillContainerFromPreferences(defaultShortcuts, true /*theDefaultOnly*/);
430 myShortcutContainer->merge(defaultShortcuts, true /*theOverride*/, true /*theTreatAbsentIncomingAsDisabled*/);
431 // nb! SUIT_ShortcutContainer never erases shortcuts, only disables.
433 updateItems(true /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
436 /*! \brief Applies pending changes to ShortcutMgr. Updates other instances of SUIT_ShortcutTree. */
437 void SUIT_ShortcutTree::applyChangesToShortcutMgr()
439 const auto mgr = SUIT_ShortcutMgr::get();
440 mgr->mergeShortcutContainer(*myShortcutContainer);
442 // Update non-synchronized with this instances.
443 for (const auto& containerAndSyncTrees : SUIT_ShortcutTree::instances) {
444 if (containerAndSyncTrees.first == myShortcutContainer.get())
447 const std::set<SUIT_ShortcutTree*>& syncTrees = containerAndSyncTrees.second;
448 const auto itFirstSyncTree = syncTrees.begin();
449 if (itFirstSyncTree == syncTrees.end())
452 (*itFirstSyncTree)->setShortcutsFromManager();
453 const auto editDialog = (*itFirstSyncTree)->myEditDialog;
454 editDialog->setConfirmedKeySequence(mgr->getShortcutContainer().getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
455 editDialog->updateConflictsMessage();
459 std::shared_ptr<const SUIT_ShortcutContainer> SUIT_ShortcutTree::shortcutContainer() const
461 return myShortcutContainer;
464 /*! \brief Does not sort modules. */
465 void SUIT_ShortcutTree::sort(SUIT_ShortcutTree::SortKey theKey, SUIT_ShortcutTree::SortOrder theOrder)
467 if (theKey == mySortKey && theOrder == mySortOrder)
471 mySortOrder = theOrder;
473 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
474 const auto moduleItem = static_cast<SUIT_ShortcutTreeFolder*>(topLevelItem(moduleIdx));
475 const auto sortedChildren = getSortedChildren(moduleItem);
476 moduleItem->takeChildren();
478 for (const auto childItem : sortedChildren) {
479 moduleItem->addChild(childItem);
484 /*! \param If theUpdateSyncTrees, trees sharing the same shortcut container are updated. */
485 void SUIT_ShortcutTree::updateItems(bool theHighlightModified, bool theUpdateSyncTrees)
487 const auto shortcutMgr = SUIT_ShortcutMgr::get();
488 const QString lang = SUIT_ShortcutMgr::getLang();
490 for (const QString& moduleID : myModuleIDs) {
491 const auto& moduleShortcuts = myShortcutContainer->getModuleShortcutsInversed(moduleID);
492 if (moduleShortcuts.empty()) {
493 // Do not display empty module.
494 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
495 if (moduleItemAndIdx.second >= 0)
496 delete takeTopLevelItem(moduleItemAndIdx.second);
501 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
502 SUIT_ShortcutTreeFolder* moduleItem = moduleItemAndIdx.first;
504 moduleItem = new SUIT_ShortcutTreeFolder(moduleID);
505 moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang);
506 addTopLevelItem(moduleItem);
507 moduleItem->setFlags(Qt::ItemIsEnabled);
509 auto sortedChildren = getSortedChildren(moduleItem);
510 for (const auto& shortcut : moduleShortcuts) {
511 const QString& inModuleActionID = shortcut.first;
512 const QKeySequence& keySequence = shortcut.second;
513 const QString keySequenceString = keySequence.toString();
515 auto actionItem = SUIT_ShortcutTreeAction::create(moduleID, inModuleActionID);
517 ShCutDbg("SUIT_ShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
521 actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
522 actionItem->setKeySequence(keySequenceString);
524 if (theHighlightModified) {
525 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
526 actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
529 insertChild(moduleItem, sortedChildren, actionItem);
532 moduleItem->setExpanded(true); // Make tree expanded on first show.
534 else /* if the tree has the module-item */ {
535 for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
536 // Update exisiting items of a module.
537 SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(moduleItem->child(childIdx));
538 const auto itShortcut = moduleShortcuts.find(childItem->myInModuleActionID);
539 if (itShortcut == moduleShortcuts.end()) {
540 // Shortcut of the item has been removed from myShortcutContainer - impossible.
543 const QKeySequence& newKeySequence = itShortcut->second;
544 const QString newKeySequenceString = newKeySequence.toString();
545 if (childItem->keySequence() != newKeySequenceString)
546 childItem->setKeySequence(newKeySequenceString);
548 if (theHighlightModified) {
549 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
550 childItem->highlightKeySequenceAsModified(newKeySequence != appliedKeySequence);
553 childItem->highlightKeySequenceAsModified(false);
556 // Add new items if myShortcutContainer acquired new shortcuts, which may happen if a developer forgot
557 // to add shortcuts for registered actions to resource files.
558 if (moduleItem->childCount() < moduleShortcuts.size()) {
559 auto sortedChildren = getSortedChildren(moduleItem);
560 for (const auto& shortcut : moduleShortcuts) {
561 const QString& inModuleActionID = shortcut.first;
562 const auto predicate = [&inModuleActionID](const SUIT_ShortcutTreeItem* const theItem) -> bool {
563 return static_cast<const SUIT_ShortcutTreeAction* const>(theItem)->myInModuleActionID == inModuleActionID;
566 if (std::find_if(sortedChildren.begin(), sortedChildren.end(), predicate) == sortedChildren.end()) {
567 const auto actionItem = SUIT_ShortcutTreeAction::create(moduleID, inModuleActionID);
569 ShCutDbg("SUIT_ShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
573 const QKeySequence& keySequence = shortcut.second;
574 actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
575 actionItem->setKeySequence(keySequence.toString());
577 if (theHighlightModified) {
578 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
579 actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
582 insertChild(moduleItem, sortedChildren, actionItem);
589 if (theUpdateSyncTrees) {
590 const std::set<SUIT_ShortcutTree*>& syncTrees = SUIT_ShortcutTree::instances[myShortcutContainer.get()];
591 for (const auto syncTree: syncTrees) {
592 if (syncTree == this)
595 syncTree->updateItems(theHighlightModified, false /*theUpdateSyncTrees*/);
596 const auto editDialog = syncTree->myEditDialog;
597 editDialog->setConfirmedKeySequence(myShortcutContainer->getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
598 editDialog->updateConflictsMessage();
603 /*! \returns Pointer and index of top-level item.
604 If the tree does not contain an item with theModuleID, returns {nullptr, -1}. */
605 std::pair<SUIT_ShortcutTreeFolder*, int> SUIT_ShortcutTree::findModuleFolderItem(const QString& theModuleID) const
607 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
608 SUIT_ShortcutTreeFolder* moduleItem = static_cast<SUIT_ShortcutTreeFolder*>(topLevelItem(moduleIdx));
609 if (moduleItem->myModuleID == theModuleID)
610 return std::pair<SUIT_ShortcutTreeFolder*, int>(moduleItem, moduleIdx);
612 return std::pair<SUIT_ShortcutTreeFolder*, int>(nullptr, -1);
615 /*! \returns Children of theParentItem being sorted according to current sort mode and order. */
616 std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>> SUIT_ShortcutTree::getSortedChildren(SUIT_ShortcutTreeFolder* theParentItem)
618 QList<std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>> sortSchema = SUIT_ShortcutTree::DEFAULT_SORT_SCHEMA;
620 for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
621 if (itSameKey->first == mySortKey) {
622 sortSchema.erase(itSameKey);
626 sortSchema.push_front(std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>(mySortKey, mySortOrder));
629 static const QCollator collator;
630 const std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)> comparator =
631 [this, sortSchema, &collator](const SUIT_ShortcutTreeItem* theItemA, const SUIT_ShortcutTreeItem* theItemB) {
632 for (const auto& keyAndOrder : sortSchema) {
633 const int res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
635 return keyAndOrder.second == SUIT_ShortcutTree::SortOrder::Ascending ? res < 0 : res > 0;
640 std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>> sortedChildren(comparator);
641 for (int childIdx = 0; childIdx < theParentItem->childCount(); childIdx++) {
642 SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(theParentItem->child(childIdx));
643 sortedChildren.emplace(childItem);
645 return sortedChildren;
648 /*! \brief Inserts theChildItem to theParentItem and theSortedChildren.
649 Does not check whether theSortedChildren are actually child items of theParentItem.
650 Does not check whether current item sort schema is same as one of theSortedChildren. */
651 void SUIT_ShortcutTree::insertChild(
652 SUIT_ShortcutTreeFolder* theParentItem,
653 std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>>& theSortedChildren,
654 SUIT_ShortcutTreeItem* theChildItem
656 auto emplaceRes = theSortedChildren.emplace(theChildItem);
657 theParentItem->insertChild(indexOf(theSortedChildren, emplaceRes.first), theChildItem);
660 void SUIT_ShortcutTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx)
663 SUIT_ShortcutTreeItem* const item = static_cast<SUIT_ShortcutTreeItem*>(theItem);
664 // Do not react if folder-item is clicked.
665 if (item->type() != SUIT_ShortcutTreeItem::Type::Action)
669 SUIT_ShortcutTreeAction* const actionItem = static_cast<SUIT_ShortcutTreeAction*>(theItem);
671 myEditDialog->setModuleAndActionID(actionItem->myModuleID, actionItem->myInModuleActionID);
672 QString actionToolTip = actionItem->toolTip(SUIT_ShortcutTree::ElementIdx::Name);
673 actionToolTip.truncate(actionToolTip.lastIndexOf('\n') + 1);
674 myEditDialog->setModuleAndActionName(
675 static_cast<SUIT_ShortcutTreeItem*>(actionItem->parent())->name(),
679 myEditDialog->setConfirmedKeySequence(QKeySequence::fromString(actionItem->keySequence()));
680 myEditDialog->updateConflictsMessage();
681 const bool somethingChanged = myEditDialog->exec() == QDialog::Accepted;
683 if (!somethingChanged)
686 const QKeySequence newKeySequence = myEditDialog->editedKeySequence();
688 /** { moduleID, inModuleActionID }[] */
689 std::set<std::pair<QString, QString>> disabledActionIDs = myShortcutContainer->setShortcut(actionItem->myModuleID, actionItem->myInModuleActionID, newKeySequence, true /*override*/);
691 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
692 std::map<QString, std::map<QString, QString>> changes;
693 changes[actionItem->myModuleID][actionItem->myInModuleActionID] = newKeySequence.toString();
694 for (const auto moduleAndActionID : disabledActionIDs) {
695 changes[moduleAndActionID.first][moduleAndActionID.second] = QString();
698 // Set new key sequences to shortcut items.
699 for (const auto& moduleIDAndChanges : changes) {
700 const QString& moduleID = moduleIDAndChanges.first;
702 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
703 const auto moduleItem = moduleItemAndIdx.first;
707 /** {inModuleActionID, newKeySequence}[] */
708 const std::map<QString, QString>& moduleChanges = moduleIDAndChanges.second;
710 // Go through module' shortcut items, and highlight those, whose key sequences differ from applied key sequences.
711 for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
712 SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(moduleItem->child(childIdx));
713 const auto itChange = moduleChanges.find(childItem->myInModuleActionID);
714 if (itChange == moduleChanges.end()) {
715 // The shortcut has not been changed.
719 childItem->setKeySequence(itChange->second);
721 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
722 childItem->highlightKeySequenceAsModified(QKeySequence::fromString(itChange->second) != appliedKeySequence);
727 /*static*/ const QList<std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>> SUIT_ShortcutTree::DEFAULT_SORT_SCHEMA =
729 {SUIT_ShortcutTree::SortKey::Name, SUIT_ShortcutTree::SortOrder::Ascending},
730 {SUIT_ShortcutTree::SortKey::ToolTip, SUIT_ShortcutTree::SortOrder::Ascending},
731 {SUIT_ShortcutTree::SortKey::KeySequence, SUIT_ShortcutTree::SortOrder::Ascending},
732 {SUIT_ShortcutTree::SortKey::ID, SUIT_ShortcutTree::SortOrder::Ascending}
735 /*static*/ std::map<SUIT_ShortcutContainer*, std::set<SUIT_ShortcutTree*>> SUIT_ShortcutTree::instances =
736 std::map<SUIT_ShortcutContainer*, std::set<SUIT_ShortcutTree*>>();
740 SUIT_ShortcutTreeItem::SUIT_ShortcutTreeItem(const QString& theModuleID)
741 : QTreeWidgetItem(), myModuleID(theModuleID)
744 QString SUIT_ShortcutTreeItem::name() const
746 return text(SUIT_ShortcutTree::ElementIdx::Name);
750 SUIT_ShortcutTreeFolder::SUIT_ShortcutTreeFolder(const QString& theModuleID)
751 : SUIT_ShortcutTreeItem(theModuleID)
753 QFont f = font(SUIT_ShortcutTree::ElementIdx::Name);
755 setFont(SUIT_ShortcutTree::ElementIdx::Name, f);
756 setText(SUIT_ShortcutTree::ElementIdx::Name, theModuleID);
759 void SUIT_ShortcutTreeFolder::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
764 setIcon(SUIT_ShortcutTree::ElementIdx::Name, theAssets->myIcon);
766 const auto& ldaMap = theAssets->myLangDependentAssets;
767 if (ldaMap.empty()) {
768 setText(SUIT_ShortcutTree::ElementIdx::Name, myModuleID);
772 auto itLDA = ldaMap.find(theLang);
773 if (itLDA == ldaMap.end())
774 itLDA = ldaMap.begin();
776 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
777 const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
778 setText(SUIT_ShortcutTree::ElementIdx::Name, name);
781 QString SUIT_ShortcutTreeFolder::getValue(SUIT_ShortcutTree::SortKey theKey) const
784 case SUIT_ShortcutTree::SortKey::ID:
786 case SUIT_ShortcutTree::SortKey::Name:
788 case SUIT_ShortcutTree::SortKey::ToolTip:
796 SUIT_ShortcutTreeAction::SUIT_ShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
797 : SUIT_ShortcutTreeItem(theModuleID), myInModuleActionID(theInModuleActionID)
799 setText(SUIT_ShortcutTree::ElementIdx::Name, theInModuleActionID);
801 SUIT_ShortcutTree::ElementIdx::Name,
802 theInModuleActionID + (theInModuleActionID.at(theInModuleActionID.length()-1) == "." ? "\n" : ".\n") + SUIT_ShortcutTree::tr("Double click to edit key sequence.")
804 setToolTip(SUIT_ShortcutTree::ElementIdx::KeySequence, SUIT_ShortcutTree::tr("Double click to edit key sequence."));
807 /*static*/ SUIT_ShortcutTreeAction* SUIT_ShortcutTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
809 if (theInModuleActionID.isEmpty()) {
810 ShCutDbg("SUIT_ShortcutTreeItem: attempt to create item with empty action ID.");
814 return new SUIT_ShortcutTreeAction(theModuleID, theInModuleActionID);
817 void SUIT_ShortcutTreeAction::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
822 setIcon(SUIT_ShortcutTree::ElementIdx::Name, theAssets->myIcon);
824 const auto& ldaMap = theAssets->myLangDependentAssets;
825 if (ldaMap.empty()) {
826 setText(SUIT_ShortcutTree::ElementIdx::Name, myInModuleActionID);
830 auto itLDA = ldaMap.find(theLang);
831 if (itLDA == ldaMap.end())
832 itLDA = ldaMap.begin();
834 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
835 const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
836 setText(SUIT_ShortcutTree::ElementIdx::Name, name);
838 const QString& actionToolTip = lda.myToolTip.isEmpty() ? name : lda.myToolTip;
840 SUIT_ShortcutTree::ElementIdx::Name,
841 actionToolTip + (actionToolTip.at(actionToolTip.length()-1) == "." ? "\n" : ".\n") + SUIT_ShortcutTree::tr("Double click to edit key sequence.")
845 QString SUIT_ShortcutTreeAction::getValue(SUIT_ShortcutTree::SortKey theKey) const
848 case SUIT_ShortcutTree::SortKey::ID:
849 return myInModuleActionID;
850 case SUIT_ShortcutTree::SortKey::Name:
852 case SUIT_ShortcutTree::SortKey::ToolTip:
853 return toolTip(SUIT_ShortcutTree::ElementIdx::Name);
854 case SUIT_ShortcutTree::SortKey::KeySequence:
855 return keySequence();
861 void SUIT_ShortcutTreeAction::setKeySequence(const QString& theKeySequence)
863 setText(SUIT_ShortcutTree::ElementIdx::KeySequence, theKeySequence);
866 QString SUIT_ShortcutTreeAction::keySequence() const
868 return text(SUIT_ShortcutTree::ElementIdx::KeySequence);
871 /*! \brief Highlights text at ElementIdx::KeySequence. */
872 void SUIT_ShortcutTreeAction::highlightKeySequenceAsModified(bool theHighlight)
874 static const QBrush bgHighlitingBrush = QBrush(Qt::darkGreen);
875 static const QBrush fgHighlitingBrush = QBrush(Qt::white);
876 static const QBrush noBrush = QBrush();
878 setBackground(SUIT_ShortcutTree::ElementIdx::KeySequence, theHighlight ? bgHighlitingBrush : noBrush);
879 setForeground(SUIT_ShortcutTree::ElementIdx::KeySequence, theHighlight ? fgHighlitingBrush : noBrush);