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 "QtxShortcutEdit.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 QtxKeySequenceEdit::QtxKeySequenceEdit(QWidget* parent)
52 myKeySequenceLineEdit->installEventFilter(this);
55 /*! \brief Set a key sequence to edit. */
56 void QtxKeySequenceEdit::setConfirmedKeySequence(const QKeySequence& theKeySequence)
58 myConfirmedKeySequenceString = theKeySequence.toString();
59 myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
60 myPrevKeySequenceString = myConfirmedKeySequenceString;
63 void QtxKeySequenceEdit::setEditedKeySequence(const QKeySequence& theKeySequence)
65 const QString keySequenceString = theKeySequence.toString();
66 myKeySequenceLineEdit->setText(keySequenceString);
67 myPrevKeySequenceString = keySequenceString;
70 QKeySequence QtxKeySequenceEdit::editedKeySequence() const
72 return QKeySequence::fromString(myKeySequenceLineEdit->text());
75 /*! \returns true, if the edited key sequence differs from confirmed one. */
76 bool QtxKeySequenceEdit::isKeySequenceModified() const
78 return QKeySequence(myConfirmedKeySequenceString) != editedKeySequence();
81 /*! \brief Set confirmed key sequence to line editor. */
82 void QtxKeySequenceEdit::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 QtxKeySequenceEdit::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 QtxKeySequenceEdit::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_Question ) ||
130 ( theKey >= Qt::Key_A && theKey <= Qt::Key_AsciiTilde ) )
135 /*! \brief Called when "Clear" button is clicked. */
136 void QtxKeySequenceEdit::onClear()
138 myKeySequenceLineEdit->setText("");
139 myPrevKeySequenceString = "";
140 emit editingFinished();
143 /*! \brief Called when myKeySequenceLineEdit loses focus. */
144 void QtxKeySequenceEdit::onEditingFinished()
146 if (myKeySequenceLineEdit->text().endsWith("+"))
147 myKeySequenceLineEdit->setText(myPrevKeySequenceString);
149 myPrevKeySequenceString = myKeySequenceLineEdit->text();
150 emit editingFinished();
154 \brief Custom event filter.
155 \param obj event receiver object
157 \returns \c true if further event processing should be stopped
159 bool QtxKeySequenceEdit::eventFilter(QObject* theObject, QEvent* theEvent)
161 if (theObject == myKeySequenceLineEdit) {
162 if (theEvent->type() == QEvent::KeyPress) {
163 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(theEvent);
164 QString text = parseEvent(keyEvent);
165 if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace)
166 myKeySequenceLineEdit->setText("");
168 myKeySequenceLineEdit->setText(text);
170 emit editingStarted();
173 if (theEvent->type() == QEvent::KeyRelease) {
182 \brief Perform internal intialization.
184 void QtxKeySequenceEdit::initialize()
186 static const int PIXMAP_SIZE = 30;
188 QHBoxLayout* base = new QHBoxLayout( this );
192 base->addWidget(myKeySequenceLineEdit = new QLineEdit(this));
193 setFocusProxy(myKeySequenceLineEdit);
195 QToolButton* clearBtn = new QToolButton();
196 auto clearPixmap = QPixmap(":/images/shortcut_disable.svg");
197 clearPixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
198 clearBtn->setIcon(clearPixmap);
199 clearBtn->setToolTip(tr("Disable shortcut."));
200 base->addWidget(clearBtn);
202 QToolButton* restoreBtn = new QToolButton();
203 auto restorePixmap = QPixmap(":/images/shortcut_restore.svg");
204 restorePixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
205 restoreBtn->setIcon(restorePixmap);
206 restoreBtn->setToolTip(tr("Restore the currently applied key sequence."));
207 base->addWidget(restoreBtn);
209 myKeySequenceLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
210 clearBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
211 restoreBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
213 connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear()));
214 connect(restoreBtn, SIGNAL(clicked()), this, SIGNAL(restoreFromShortcutMgrClicked()));
215 connect(myKeySequenceLineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
219 /*! \param theParent must not be nullptr. */
220 QtxEditKeySequenceDialog::QtxEditKeySequenceDialog(QtxShortcutTree* theParent)
223 setMinimumWidth(500);
224 setWindowTitle(tr("Change key sequence"));
225 QVBoxLayout* layout = new QVBoxLayout(this);
226 myActionName = new QLabel(this);
227 myActionName->setTextFormat(Qt::RichText);
228 myKeySequenceEdit = new QtxKeySequenceEdit(this);
229 myTextEdit = new QTextEdit(this);
230 layout->addWidget(myActionName);
231 layout->addWidget(myKeySequenceEdit);
232 layout->addWidget(myTextEdit);
233 myActionName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
234 myKeySequenceEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
235 myTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
236 myTextEdit->setReadOnly(true);
237 myTextEdit->setAcceptRichText(true);
238 myTextEdit->setPlaceholderText(tr("No conflicts."));
239 setFocusProxy(myKeySequenceEdit);
241 QHBoxLayout* buttonLayout = new QHBoxLayout(this);
242 layout->addLayout(buttonLayout);
243 QPushButton* confirmButton = new QPushButton(tr("Confirm"), this);
244 confirmButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
245 QPushButton* cancelButton = new QPushButton(tr("Cancel"), this);
246 cancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
247 buttonLayout->addStretch();
248 buttonLayout->addWidget(confirmButton);
249 buttonLayout->addWidget(cancelButton);
251 connect(myKeySequenceEdit, SIGNAL(editingStarted()), this, SLOT(onEditingStarted()));
252 connect(myKeySequenceEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
253 connect(myKeySequenceEdit, SIGNAL(restoreFromShortcutMgrClicked()), this, SLOT(onRestoreFromShortcutMgr()));
254 connect(confirmButton, SIGNAL(clicked()), this, SLOT(onConfirm()));
255 connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
258 void QtxEditKeySequenceDialog::setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID)
260 myModuleID = theModuleID;
261 myInModuleActionID = theInModuleActionID;
264 const QString& QtxEditKeySequenceDialog::moduleID() const { return myModuleID; }
265 const QString& QtxEditKeySequenceDialog::inModuleActionID() const { return myInModuleActionID; }
267 void QtxEditKeySequenceDialog::setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip)
269 myActionName->setText("<b>" + theModuleName + "</b> " + theActionName);
270 myActionName->setToolTip(theActionToolTip);
273 void QtxEditKeySequenceDialog::setConfirmedKeySequence(const QKeySequence& theSequence)
275 myKeySequenceEdit->setConfirmedKeySequence(theSequence);
278 QKeySequence QtxEditKeySequenceDialog::editedKeySequence() const
280 return myKeySequenceEdit->editedKeySequence();
283 int QtxEditKeySequenceDialog::exec()
285 myKeySequenceEdit->setFocus(Qt::ActiveWindowFocusReason);
286 return QDialog::exec();
289 void QtxEditKeySequenceDialog::onEditingStarted()
291 myTextEdit->setEnabled(false);
294 void QtxEditKeySequenceDialog::onEditingFinished()
296 updateConflictsMessage();
299 void QtxEditKeySequenceDialog::onRestoreFromShortcutMgr()
301 const auto shortcutMgr = SUIT_ShortcutMgr::get();
302 myKeySequenceEdit->setEditedKeySequence(shortcutMgr->getKeySequence(myModuleID, myInModuleActionID));
303 updateConflictsMessage();
306 /*! Updates message with list of actions, whose shortcuts will be disabled on Confirm. */
307 void QtxEditKeySequenceDialog::updateConflictsMessage()
309 myTextEdit->setEnabled(true);
310 QTextDocument* doc = myTextEdit->document();
312 doc = new QTextDocument(myTextEdit);
313 myTextEdit->setDocument(doc);
316 if (!myKeySequenceEdit->isKeySequenceModified()) {
321 const QKeySequence newKeySequence = editedKeySequence();
323 const auto shortcutTree = static_cast<QtxShortcutTree*>(parentWidget());
324 /** {moduleID, inModuleActionID}[] */
325 std::set<std::pair<QString, QString>> conflicts = shortcutTree->shortcutContainer()->getConflicts(myModuleID, myInModuleActionID, newKeySequence);
326 if (!conflicts.empty()) {
327 const auto shortcutMgr = SUIT_ShortcutMgr::get();
329 QString report = "<b>" + tr("These shortcuts will be disabled on confirm:") + "</b>";
332 for (const auto& conflict : conflicts) {
333 const QString conflictingModuleName = shortcutMgr->getModuleName(conflict.first);
334 const QString conflictingActionName = shortcutMgr->getActionName(conflict.first, conflict.second);
335 report += "<li><b>" + conflictingModuleName + "</b> " + conflictingActionName + "</li>";
339 doc->setHtml(report);
341 else /* if no conflicts */ {
346 void QtxEditKeySequenceDialog::onConfirm()
348 if (myKeySequenceEdit->isKeySequenceModified())
355 /*! \brief Compensates lack of std::distance(), which is introduced in C++17.
356 \returns -1, if theIt does not belong to the */
357 template <class Container>
359 const Container& theContainer,
360 const typename Container::iterator& theIt
362 auto it = theContainer.begin();
364 while (it != theContainer.end()) {
375 /*! \param theContainer Share the same container between several trees,
376 to edit them synchronously even without exchange of changes with SUIT_ShortcutMgr.
377 Pass nullptr to create non-synchronized tree. */
378 QtxShortcutTree::QtxShortcutTree(
379 std::shared_ptr<SUIT_ShortcutContainer> theContainer,
381 ) : QTreeWidget(theParent),
382 myShortcutContainer(theContainer ? theContainer : std::shared_ptr<SUIT_ShortcutContainer>(new SUIT_ShortcutContainer())),
383 mySortKey(QtxShortcutTree::SortKey::Name), mySortOrder(QtxShortcutTree::SortOrder::Ascending)
386 setSelectionMode(QAbstractItemView::SingleSelection);
387 setColumnWidth(0, COLUMN_SIZE);
388 setSortingEnabled(false); // Items are sorted in the same way, as in ShortcutContainer.
389 header()->setSectionResizeMode(QHeaderView::Interactive);
391 QMap<int, QString> labelMap;
392 labelMap[QtxShortcutTree::ElementIdx::Name] = tr("Action");
393 labelMap[QtxShortcutTree::ElementIdx::KeySequence] = tr("Key sequence");
394 setHeaderLabels(labelMap.values());
396 setExpandsOnDoubleClick(false); // Open shortcut editor on double click instead.
397 setSortingEnabled(false);
398 setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
399 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
400 myEditDialog = new QtxEditKeySequenceDialog(this);
402 this->installEventFilter(this);
403 connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
405 QtxShortcutTree::instances[myShortcutContainer.get()].emplace(this);
408 QtxShortcutTree::~QtxShortcutTree()
410 QtxShortcutTree::instances[myShortcutContainer.get()].erase(this);
411 if (QtxShortcutTree::instances[myShortcutContainer.get()].empty())
412 QtxShortcutTree::instances.erase(myShortcutContainer.get());
415 /*! \brief Copies shortcuts from ShortcutMgr. (Re)displays shortcuts of myModuleIDs. */
416 void QtxShortcutTree::setShortcutsFromManager()
418 const auto shortcutMgr = SUIT_ShortcutMgr::get();
419 *myShortcutContainer = shortcutMgr->getShortcutContainer();
420 // nb! ShortcutMgr never removes shortcuts from its container, only disables.
422 updateItems(false /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
425 /*! \brief Copies shortcuts from resources, user files are not accounted. (Re)displays shortcuts of myModuleIDs. */
426 void QtxShortcutTree::setDefaultShortcuts()
428 SUIT_ShortcutContainer defaultShortcuts;
429 SUIT_ShortcutMgr::fillContainerFromPreferences(defaultShortcuts, true /*theDefaultOnly*/);
431 myShortcutContainer->merge(defaultShortcuts, true /*theOverride*/, true /*theTreatAbsentIncomingAsDisabled*/);
432 // nb! SUIT_ShortcutContainer never erases shortcuts, only disables.
434 updateItems(true /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
437 /*! \brief Applies pending changes to ShortcutMgr. Updates other instances of QtxShortcutTree. */
438 void QtxShortcutTree::applyChangesToShortcutMgr()
440 const auto mgr = SUIT_ShortcutMgr::get();
441 mgr->mergeShortcutContainer(*myShortcutContainer);
443 // Update non-synchronized with this instances.
444 for (const auto& containerAndSyncTrees : QtxShortcutTree::instances) {
445 if (containerAndSyncTrees.first == myShortcutContainer.get())
448 const std::set<QtxShortcutTree*>& syncTrees = containerAndSyncTrees.second;
449 const auto itFirstSyncTree = syncTrees.begin();
450 if (itFirstSyncTree == syncTrees.end())
453 (*itFirstSyncTree)->setShortcutsFromManager();
454 const auto editDialog = (*itFirstSyncTree)->myEditDialog;
455 editDialog->setConfirmedKeySequence(mgr->getShortcutContainer().getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
456 editDialog->updateConflictsMessage();
460 std::shared_ptr<const SUIT_ShortcutContainer> QtxShortcutTree::shortcutContainer() const
462 return myShortcutContainer;
465 /*! \brief Does not sort modules. */
466 void QtxShortcutTree::sort(QtxShortcutTree::SortKey theKey, QtxShortcutTree::SortOrder theOrder)
468 if (theKey == mySortKey && theOrder == mySortOrder)
472 mySortOrder = theOrder;
474 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
475 const auto moduleItem = static_cast<QtxShortcutTreeFolder*>(topLevelItem(moduleIdx));
476 const auto sortedChildren = getSortedChildren(moduleItem);
477 moduleItem->takeChildren();
479 for (const auto childItem : sortedChildren) {
480 moduleItem->addChild(childItem);
485 /*! \param If theUpdateSyncTrees, trees sharing the same shortcut container are updated. */
486 void QtxShortcutTree::updateItems(bool theHighlightModified, bool theUpdateSyncTrees)
488 const auto shortcutMgr = SUIT_ShortcutMgr::get();
489 const QString lang = SUIT_ShortcutMgr::getLang();
491 for (const QString& moduleID : myModuleIDs) {
492 const auto& moduleShortcuts = myShortcutContainer->getModuleShortcutsInversed(moduleID);
493 if (moduleShortcuts.empty()) {
494 // Do not display empty module.
495 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
496 if (moduleItemAndIdx.second >= 0)
497 delete takeTopLevelItem(moduleItemAndIdx.second);
502 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
503 QtxShortcutTreeFolder* moduleItem = moduleItemAndIdx.first;
505 moduleItem = new QtxShortcutTreeFolder(moduleID);
506 moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang);
507 addTopLevelItem(moduleItem);
508 moduleItem->setFlags(Qt::ItemIsEnabled);
510 auto sortedChildren = getSortedChildren(moduleItem);
511 for (const auto& shortcut : moduleShortcuts) {
512 const QString& inModuleActionID = shortcut.first;
513 const QKeySequence& keySequence = shortcut.second;
514 const QString keySequenceString = keySequence.toString();
516 auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID);
518 ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
522 actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
523 actionItem->setKeySequence(keySequenceString);
525 if (theHighlightModified) {
526 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
527 actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
530 insertChild(moduleItem, sortedChildren, actionItem);
533 moduleItem->setExpanded(true); // Make tree expanded on first show.
535 else /* if the tree has the module-item */ {
536 for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
537 // Update exisiting items of a module.
538 QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(moduleItem->child(childIdx));
539 const auto itShortcut = moduleShortcuts.find(childItem->myInModuleActionID);
540 if (itShortcut == moduleShortcuts.end()) {
541 // Shortcut of the item has been removed from myShortcutContainer - impossible.
544 const QKeySequence& newKeySequence = itShortcut->second;
545 const QString newKeySequenceString = newKeySequence.toString();
546 if (childItem->keySequence() != newKeySequenceString)
547 childItem->setKeySequence(newKeySequenceString);
549 if (theHighlightModified) {
550 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
551 childItem->highlightKeySequenceAsModified(newKeySequence != appliedKeySequence);
554 childItem->highlightKeySequenceAsModified(false);
557 // Add new items if myShortcutContainer acquired new shortcuts, which may happen if a developer forgot
558 // to add shortcuts for registered actions to resource files.
559 if (moduleItem->childCount() < moduleShortcuts.size()) {
560 auto sortedChildren = getSortedChildren(moduleItem);
561 for (const auto& shortcut : moduleShortcuts) {
562 const QString& inModuleActionID = shortcut.first;
563 const auto predicate = [&inModuleActionID](const QtxShortcutTreeItem* const theItem) -> bool {
564 return static_cast<const QtxShortcutTreeAction* const>(theItem)->myInModuleActionID == inModuleActionID;
567 if (std::find_if(sortedChildren.begin(), sortedChildren.end(), predicate) == sortedChildren.end()) {
568 const auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID);
570 ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
574 const QKeySequence& keySequence = shortcut.second;
575 actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
576 actionItem->setKeySequence(keySequence.toString());
578 if (theHighlightModified) {
579 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
580 actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
583 insertChild(moduleItem, sortedChildren, actionItem);
590 if (theUpdateSyncTrees) {
591 const std::set<QtxShortcutTree*>& syncTrees = QtxShortcutTree::instances[myShortcutContainer.get()];
592 for (const auto syncTree: syncTrees) {
593 if (syncTree == this)
596 syncTree->updateItems(theHighlightModified, false /*theUpdateSyncTrees*/);
597 const auto editDialog = syncTree->myEditDialog;
598 editDialog->setConfirmedKeySequence(myShortcutContainer->getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
599 editDialog->updateConflictsMessage();
604 /*! \returns Pointer and index of top-level item.
605 If the tree does not contain an item with theModuleID, returns {nullptr, -1}. */
606 std::pair<QtxShortcutTreeFolder*, int> QtxShortcutTree::findModuleFolderItem(const QString& theModuleID) const
608 for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
609 QtxShortcutTreeFolder* moduleItem = static_cast<QtxShortcutTreeFolder*>(topLevelItem(moduleIdx));
610 if (moduleItem->myModuleID == theModuleID)
611 return std::pair<QtxShortcutTreeFolder*, int>(moduleItem, moduleIdx);
613 return std::pair<QtxShortcutTreeFolder*, int>(nullptr, -1);
616 /*! \returns Children of theParentItem being sorted according to current sort mode and order. */
617 std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>> QtxShortcutTree::getSortedChildren(QtxShortcutTreeFolder* theParentItem)
619 QList<std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>> sortSchema = QtxShortcutTree::DEFAULT_SORT_SCHEMA;
621 for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
622 if (itSameKey->first == mySortKey) {
623 sortSchema.erase(itSameKey);
627 sortSchema.push_front(std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>(mySortKey, mySortOrder));
630 static const QCollator collator;
631 const std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)> comparator =
632 [this, sortSchema, &collator](const QtxShortcutTreeItem* theItemA, const QtxShortcutTreeItem* theItemB) {
634 for (const auto& keyAndOrder : sortSchema) {
636 res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
638 return keyAndOrder.second == QtxShortcutTree::SortOrder::Ascending ? res < 0 : res > 0;
643 std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>> sortedChildren(comparator);
644 for (int childIdx = 0; childIdx < theParentItem->childCount(); childIdx++) {
645 QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(theParentItem->child(childIdx));
646 sortedChildren.emplace(childItem);
648 return sortedChildren;
651 /*! \brief Inserts theChildItem to theParentItem and theSortedChildren.
652 Does not check whether theSortedChildren are actually child items of theParentItem.
653 Does not check whether current item sort schema is same as one of theSortedChildren. */
654 void QtxShortcutTree::insertChild(
655 QtxShortcutTreeFolder* theParentItem,
656 std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>>& theSortedChildren,
657 QtxShortcutTreeItem* theChildItem
659 auto emplaceRes = theSortedChildren.emplace(theChildItem);
660 theParentItem->insertChild(indexOf(theSortedChildren, emplaceRes.first), theChildItem);
663 void QtxShortcutTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx)
666 QtxShortcutTreeItem* const item = static_cast<QtxShortcutTreeItem*>(theItem);
667 // Do not react if folder-item is clicked.
668 if (item->type() != QtxShortcutTreeItem::Type::Action)
672 QtxShortcutTreeAction* const actionItem = static_cast<QtxShortcutTreeAction*>(theItem);
674 myEditDialog->setModuleAndActionID(actionItem->myModuleID, actionItem->myInModuleActionID);
675 QString actionToolTip = actionItem->toolTip(QtxShortcutTree::ElementIdx::Name);
676 actionToolTip.truncate(actionToolTip.lastIndexOf('\n') + 1);
677 myEditDialog->setModuleAndActionName(
678 static_cast<QtxShortcutTreeItem*>(actionItem->parent())->name(),
682 myEditDialog->setConfirmedKeySequence(QKeySequence::fromString(actionItem->keySequence()));
683 myEditDialog->updateConflictsMessage();
684 const bool somethingChanged = myEditDialog->exec() == QDialog::Accepted;
686 if (!somethingChanged)
689 const QKeySequence newKeySequence = myEditDialog->editedKeySequence();
691 /** { moduleID, inModuleActionID }[] */
692 std::set<std::pair<QString, QString>> disabledActionIDs = myShortcutContainer->setShortcut(actionItem->myModuleID, actionItem->myInModuleActionID, newKeySequence, true /*override*/);
694 /** { moduleID, {inModuleActionID, keySequence}[] }[] */
695 std::map<QString, std::map<QString, QString>> changes;
696 changes[actionItem->myModuleID][actionItem->myInModuleActionID] = newKeySequence.toString();
697 for (const auto moduleAndActionID : disabledActionIDs) {
698 changes[moduleAndActionID.first][moduleAndActionID.second] = QString();
701 // Set new key sequences to shortcut items.
702 for (const auto& moduleIDAndChanges : changes) {
703 const QString& moduleID = moduleIDAndChanges.first;
705 const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
706 const auto moduleItem = moduleItemAndIdx.first;
710 /** {inModuleActionID, newKeySequence}[] */
711 const std::map<QString, QString>& moduleChanges = moduleIDAndChanges.second;
713 // Go through module' shortcut items, and highlight those, whose key sequences differ from applied key sequences.
714 for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
715 QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(moduleItem->child(childIdx));
716 const auto itChange = moduleChanges.find(childItem->myInModuleActionID);
717 if (itChange == moduleChanges.end()) {
718 // The shortcut has not been changed.
722 childItem->setKeySequence(itChange->second);
724 const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
725 childItem->highlightKeySequenceAsModified(QKeySequence::fromString(itChange->second) != appliedKeySequence);
730 /*static*/ const QList<std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>> QtxShortcutTree::DEFAULT_SORT_SCHEMA =
732 {QtxShortcutTree::SortKey::Name, QtxShortcutTree::SortOrder::Ascending},
733 {QtxShortcutTree::SortKey::ToolTip, QtxShortcutTree::SortOrder::Ascending},
734 {QtxShortcutTree::SortKey::KeySequence, QtxShortcutTree::SortOrder::Ascending},
735 {QtxShortcutTree::SortKey::ID, QtxShortcutTree::SortOrder::Ascending}
738 /*static*/ std::map<SUIT_ShortcutContainer*, std::set<QtxShortcutTree*>> QtxShortcutTree::instances =
739 std::map<SUIT_ShortcutContainer*, std::set<QtxShortcutTree*>>();
743 QtxShortcutTreeItem::QtxShortcutTreeItem(const QString& theModuleID)
744 : QTreeWidgetItem(), myModuleID(theModuleID)
747 QString QtxShortcutTreeItem::name() const
749 return text(QtxShortcutTree::ElementIdx::Name);
753 QtxShortcutTreeFolder::QtxShortcutTreeFolder(const QString& theModuleID)
754 : QtxShortcutTreeItem(theModuleID)
756 QFont f = font(QtxShortcutTree::ElementIdx::Name);
758 setFont(QtxShortcutTree::ElementIdx::Name, f);
759 setText(QtxShortcutTree::ElementIdx::Name, theModuleID);
762 void QtxShortcutTreeFolder::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
767 setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon);
769 const auto& ldaMap = theAssets->myLangDependentAssets;
770 if (ldaMap.empty()) {
771 setText(QtxShortcutTree::ElementIdx::Name, myModuleID);
775 auto itLDA = ldaMap.find(theLang);
776 if (itLDA == ldaMap.end())
777 itLDA = ldaMap.begin();
779 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
780 const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
781 setText(QtxShortcutTree::ElementIdx::Name, name);
784 QString QtxShortcutTreeFolder::getValue(QtxShortcutTree::SortKey theKey) const
787 case QtxShortcutTree::SortKey::ID:
789 case QtxShortcutTree::SortKey::Name:
791 case QtxShortcutTree::SortKey::ToolTip:
799 QtxShortcutTreeAction::QtxShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
800 : QtxShortcutTreeItem(theModuleID), myInModuleActionID(theInModuleActionID)
802 setText(QtxShortcutTree::ElementIdx::Name, theInModuleActionID);
804 QtxShortcutTree::ElementIdx::Name,
805 theInModuleActionID + (theInModuleActionID.at(theInModuleActionID.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.")
807 setToolTip(QtxShortcutTree::ElementIdx::KeySequence, QtxShortcutTree::tr("Double click to edit key sequence."));
810 /*static*/ QtxShortcutTreeAction* QtxShortcutTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
812 if (theInModuleActionID.isEmpty()) {
813 ShCutDbg("QtxShortcutTreeItem: attempt to create item with empty action ID.");
817 return new QtxShortcutTreeAction(theModuleID, theInModuleActionID);
820 void QtxShortcutTreeAction::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
825 setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon);
827 const auto& ldaMap = theAssets->myLangDependentAssets;
828 if (ldaMap.empty()) {
829 setText(QtxShortcutTree::ElementIdx::Name, myInModuleActionID);
833 auto itLDA = ldaMap.find(theLang);
834 if (itLDA == ldaMap.end())
835 itLDA = ldaMap.begin();
837 const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
838 const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
839 setText(QtxShortcutTree::ElementIdx::Name, name);
841 const QString& actionToolTip = lda.myToolTip.isEmpty() ? name : lda.myToolTip;
843 QtxShortcutTree::ElementIdx::Name,
844 actionToolTip + (actionToolTip.at(actionToolTip.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.")
848 QString QtxShortcutTreeAction::getValue(QtxShortcutTree::SortKey theKey) const
851 case QtxShortcutTree::SortKey::ID:
852 return myInModuleActionID;
853 case QtxShortcutTree::SortKey::Name:
855 case QtxShortcutTree::SortKey::ToolTip:
856 return toolTip(QtxShortcutTree::ElementIdx::Name);
857 case QtxShortcutTree::SortKey::KeySequence:
858 return keySequence();
864 void QtxShortcutTreeAction::setKeySequence(const QString& theKeySequence)
866 setText(QtxShortcutTree::ElementIdx::KeySequence, theKeySequence);
869 QString QtxShortcutTreeAction::keySequence() const
871 return text(QtxShortcutTree::ElementIdx::KeySequence);
874 /*! \brief Highlights text at ElementIdx::KeySequence. */
875 void QtxShortcutTreeAction::highlightKeySequenceAsModified(bool theHighlight)
877 static const QBrush bgHighlitingBrush = QBrush(Qt::darkGreen);
878 static const QBrush fgHighlitingBrush = QBrush(Qt::white);
879 static const QBrush noBrush = QBrush();
881 setBackground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? bgHighlitingBrush : noBrush);
882 setForeground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? fgHighlitingBrush : noBrush);