Salome HOME
[bos #40644][CEA](2024-T1) Feature search. dish/CR40644--Feature_Search 22/head
authordish <dmitrii.shvydkoi@opencascade.com>
Wed, 15 May 2024 18:23:43 +0000 (18:23 +0000)
committerdish <dmitrii.shvydkoi@opencascade.com>
Wed, 15 May 2024 18:23:43 +0000 (18:23 +0000)
Add Find Action dialog. Add icons' paths of GUI-actions to resource file.

27 files changed:
src/LightApp/resources/LightApp.xml
src/Qtx/CMakeLists.txt
src/Qtx/QtxPagePrefMgr.cxx
src/Qtx/QtxPagePrefMgr.h
src/Qtx/QtxShortcutEdit.cxx [deleted file]
src/Qtx/QtxShortcutEdit.h [deleted file]
src/Qtx/resources/Qtx_msg_fr.ts
src/Qtx/resources/Qtx_msg_ja.ts
src/SUIT/CMakeLists.txt
src/SUIT/SUIT_FindActionDialog.cxx [new file with mode: 0644]
src/SUIT/SUIT_FindActionDialog.h [new file with mode: 0644]
src/SUIT/SUIT_PagePrefShortcutTreeItem.cxx [new file with mode: 0644]
src/SUIT/SUIT_PagePrefShortcutTreeItem.h [new file with mode: 0644]
src/SUIT/SUIT_PreferenceMgr.cxx
src/SUIT/SUIT_ShortcutMgr. ReadMe.md
src/SUIT/SUIT_ShortcutMgr.cxx
src/SUIT/SUIT_ShortcutMgr.h
src/SUIT/SUIT_ShortcutTree.cxx [new file with mode: 0644]
src/SUIT/SUIT_ShortcutTree.h [new file with mode: 0644]
src/SUIT/resources/SUIT_msg_fr.ts
src/SUIT/resources/SUIT_msg_ja.ts
src/SUIT/resources/action_assets.json
src/SalomeApp/SalomeApp_Application.cxx
src/SalomeApp/SalomeApp_Application.h
src/SalomeApp/resources/SalomeApp_msg_en.ts
src/SalomeApp/resources/SalomeApp_msg_fr.ts
src/SalomeApp/resources/SalomeApp_msg_ja.ts

index 391f0f0d01882d2952095268b90a2db071138dba..b7d00fb800b709815061ed3945136d112c6f187d 100644 (file)
     <parameter name="PRP_CREATE_NEW_WINDOW_FOR_VIEWER_5" value="Alt+C"/>
     <parameter name="PRP_CREATE_NEW_WINDOW_FOR_VIEWER_6" value="Alt+A"/>
     <parameter name="PRP_CREATE_NEW_WINDOW_FOR_VIEWER_7" value="Alt+Y"/>
+    <parameter name="PRP_DESK_FIND_ACTION" value="Ctrl+Shift+Space"/>
     <parameter name="PRP_CREATE_NEW_WINDOW_FOR_VIEWER_8" value="Alt+3"/>
     <parameter name="#General/Object(s)/Show" value="Ctrl+Alt+S"/>
     <parameter name="#General/Object(s)/Hide" value="Ctrl+Alt+H"/>
index f95706ef16d400bab4cb09507e04b52fe32668ab..d62b4e1c2bce7360477067dec105c3f5f1bf6b69 100644 (file)
@@ -22,10 +22,7 @@ INCLUDE(UseQtExt)
 # --- options ---
 
 # additional include directories
-INCLUDE_DIRECTORIES(
-  ${QT_INCLUDES}
-  ${PROJECT_SOURCE_DIR}/src/SUIT
-)
+INCLUDE_DIRECTORIES(${QT_INCLUDES})
 
 # additional preprocessor / compiler flags
 ADD_DEFINITIONS(${QT_DEFINITIONS})
@@ -73,7 +70,6 @@ SET(_moc_HEADERS
   QtxPopupMgr.h
   QtxRubberBand.h
   QtxSearchTool.h
-  QtxShortcutEdit.h
   QtxSlider.h
   QtxSplash.h
   QtxToolBar.h
@@ -170,7 +166,6 @@ SET(_other_SOURCES
   QtxResourceMgr.cxx
   QtxRubberBand.cxx
   QtxSearchTool.cxx
-  QtxShortcutEdit.cxx
   QtxSlider.cxx
   QtxSplash.cxx
   QtxToolBar.cxx
index b81bcfb19d7b9e654fa316390ffc01092352c936..a561c46a4c9eec16eeb9c4ea9bcbb4059b7cac64 100644 (file)
 #include "QtxColorButton.h"
 #include "QtxBiColorTool.h"
 #include "QtxDoubleSpinBox.h"
-#include "QtxShortcutEdit.h"
 #include "QtxBackgroundTool.h"
 #include "QtxResourceMgr.h"
 
-#include "SUIT_ShortcutMgr.h"
-
 #include <QEvent>
 #include <QLayout>
 #include <QToolBox>
@@ -4457,67 +4454,6 @@ void QtxPagePrefDateTimeItem::updateDateTime()
 }
 
 
-/*!
-  \brief Creates preference item for editing of key bindings
-  \param theParent parent preference item. Must not be nullptr.
-*/
-QtxPagePrefShortcutTreeItem::QtxPagePrefShortcutTreeItem(QtxPreferenceItem* theParent)
- : QtxPagePrefItem(QString(), theParent)
-{
-  auto container = std::shared_ptr<SUIT_ShortcutContainer>();
-  const auto itContainers = QtxPagePrefShortcutTreeItem::shortcutContainers.find(rootItem());
-  if (itContainers == QtxPagePrefShortcutTreeItem::shortcutContainers.end()) {
-    container.reset(new SUIT_ShortcutContainer());
-    QtxPagePrefShortcutTreeItem::shortcutContainers.emplace(rootItem(), container);
-  }
-  else {
-    container = itContainers->second.lock();
-    if (!container) {
-      container.reset(new SUIT_ShortcutContainer());
-      itContainers->second = container;
-    }
-  }
-
-  QtxShortcutTree* tree = new QtxShortcutTree(container);
-  tree->myModuleIDs = SUIT_ShortcutMgr::get()->getShortcutModuleIDs();
-  setWidget(tree);
-}
-
-/*!
-  \brief Retrieves shortcut preferences from ShortcutMgr.
-  Updates UI of controlling widget.
-  \sa store()
-*/
-void QtxPagePrefShortcutTreeItem::retrieve()
-{
-  static_cast<QtxShortcutTree*>(widget())->setShortcutsFromManager();
-}
-
-/*!
-  \brief Retrieves shortcut preferences from resource files, ignoring user preferences.
-  Updates UI of controlling widget.
-  \sa store()
-*/
-void QtxPagePrefShortcutTreeItem::retrieveDefault()
-{
-  static_cast<QtxShortcutTree*>(widget())->setDefaultShortcuts();
-}
-
-/*!
-  \brief Applies modified shortcut preferences to ShortcutMgr.
-  Updates UI of controlling widget.
-  And ShortcutMgr, in turn, serilizes shortcut preferences using the resource manager.
-  \sa retrieve()
-*/
-void QtxPagePrefShortcutTreeItem::store()
-{
-  static_cast<QtxShortcutTree*>(widget())->applyChangesToShortcutMgr();
-}
-
-/*static*/ std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>> QtxPagePrefShortcutTreeItem::shortcutContainers =
-std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>>();
-
-
 /*!
   \class QtxPagePrefBackgroundItem
   \brief GUI implementation of the resources item to store background data.
index b2b854b429c3bd3b896ed26f60c7617951339f56..8acc9036b9c1d139c49161209cd9d08617cf612e 100644 (file)
@@ -42,7 +42,6 @@ class QtxGroupBox;
 class QtxComboBox;
 class QtxColorButton;
 class QtxBiColorTool;
-class QtxShortcutTree;
 class QtxBackgroundTool;
 
 class QToolBox;
@@ -743,37 +742,6 @@ private:
   QDateTimeEdit*   myDateTime;
 };
 
-
-class SUIT_ShortcutContainer;
-
-
-class QTX_EXPORT QtxPagePrefShortcutTreeItem : public QtxPagePrefItem
-{
-public:
-  QtxPagePrefShortcutTreeItem(QtxPreferenceItem* theParent);
-  virtual ~QtxPagePrefShortcutTreeItem() = default;
-
-  virtual void     retrieve();
-  virtual void     retrieveDefault();
-  virtual void     store();
-
-private:
-  QtxShortcutTree* myShortcutTree;
-
-  // { root item (preference window), shortcut container of synchronized trees (widgets within the same window) }
-  static std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>> shortcutContainers;
-  /** Why is this?
-   * Every QtxPagePrefMgr is eventually a preference window. Each preference window has button "Apply".
-   * When the button is pressed, all descendants of the QtxPagePrefMgr store changes they carry into preferences.
-   * The pitfall with shortcut trees is as follows: made in independent shortcut trees, changes may conflict,
-   * and merge of such changes is ambiguous. And the solution is to keep shortcut trees within the same window
-   * synchronized - all changes being made in a tree of a synchronized bundle are projected to other trees from the bundle
-   * without interacting with SUIT_ShortcutMgr.
-   *
-   * Every time shortcut preferences stored to the ShortcutMgr, all instances of QtxShortcutTree are updated.
-  */
-};
-
 class QTX_EXPORT QtxPagePrefBackgroundItem : public QObject, public QtxPageNamedPrefItem
 {
   Q_OBJECT
diff --git a/src/Qtx/QtxShortcutEdit.cxx b/src/Qtx/QtxShortcutEdit.cxx
deleted file mode 100644 (file)
index fe845b5..0000000
+++ /dev/null
@@ -1,883 +0,0 @@
-// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-//
-// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
-//
-
-#include "QtxShortcutEdit.h"
-
-#include <QWidget>
-#include <QLayout>
-#include <QList>
-#include <QMap>
-
-#include <QToolButton>
-#include <QLineEdit>
-#include <QLabel>
-#include <QTableWidgetItem>
-#include <QTextEdit>
-#include <QMessageBox>
-#include <QPushButton>
-#include <QBrush>
-#include <QColor>
-#include <QHeaderView>
-
-#include <QKeyEvent>
-#include <QKeySequence>
-#include <QCollator>
-
-#include <algorithm>
-
-
-#define COLUMN_SIZE  500
-
-
-QtxKeySequenceEdit::QtxKeySequenceEdit(QWidget* parent)
-: QFrame(parent)
-{
-  initialize();
-  myKeySequenceLineEdit->installEventFilter(this);
-}
-
-/*! \brief Set a key sequence to edit. */
-void QtxKeySequenceEdit::setConfirmedKeySequence(const QKeySequence& theKeySequence)
-{
-  myConfirmedKeySequenceString = theKeySequence.toString();
-  myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
-  myPrevKeySequenceString = myConfirmedKeySequenceString;
-}
-
-void QtxKeySequenceEdit::setEditedKeySequence(const QKeySequence& theKeySequence)
-{
-  const QString keySequenceString = theKeySequence.toString();
-  myKeySequenceLineEdit->setText(keySequenceString);
-  myPrevKeySequenceString = keySequenceString;
-}
-
-QKeySequence QtxKeySequenceEdit::editedKeySequence() const
-{
-  return QKeySequence::fromString(myKeySequenceLineEdit->text());
-}
-
-/*! \returns true, if the edited key sequence differs from confirmed one. */
-bool QtxKeySequenceEdit::isKeySequenceModified() const
-{
-  return QKeySequence(myConfirmedKeySequenceString) != editedKeySequence();
-}
-
-/*! \brief Set confirmed key sequence to line editor. */
-void QtxKeySequenceEdit::restoreKeySequence()
-{
-  myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
-  myPrevKeySequenceString = myConfirmedKeySequenceString;
-}
-
-/*!
-  \brief Gets the key sequence from keys that were pressed
-  \param e a key event
-  \returns a string representation of the key sequence
-*/
-/*static*/ QString QtxKeySequenceEdit::parseEvent(QKeyEvent* e)
-{
-  bool isShiftPressed = e->modifiers() & Qt::ShiftModifier;
-  bool isControlPressed = e->modifiers() & Qt::ControlModifier;
-  bool isAltPressed = e->modifiers() & Qt::AltModifier;
-  bool isMetaPressed = e->modifiers() & Qt::MetaModifier;
-  bool isModifiersPressed = isControlPressed || isAltPressed || isMetaPressed; // Do not treat Shift alone as a modifier!
-  int result=0;
-  if(isControlPressed)
-    result += Qt::CTRL;
-  if(isAltPressed)
-    result += Qt::ALT;
-  if(isShiftPressed)
-    result += Qt::SHIFT;
-  if(isMetaPressed)
-    result += Qt::META;
-
-  int aKey = e->key();
-  if ((isValidKey(aKey) && isModifiersPressed) || ((aKey >= Qt::Key_F1) && (aKey <= Qt::Key_F12)))
-    result += aKey;
-
-  return QKeySequence(result).toString();
-}
-
-/*!
-  \brief Check if the key event contains a 'valid' key
-  \param theKey the code of the key
-  \returns \c true if the key is 'valid'
-*/
-/*static*/ bool QtxKeySequenceEdit::isValidKey(int theKey)
-{
-  if ( theKey == Qt::Key_Underscore || theKey == Qt::Key_Escape ||
-     ( theKey >= Qt::Key_Backspace && theKey <= Qt::Key_Delete ) ||
-     ( theKey >= Qt::Key_Home && theKey <= Qt::Key_PageDown ) ||
-     ( theKey >= Qt::Key_F1 && theKey <= Qt::Key_F12 )  ||
-     ( theKey >= Qt::Key_Space && theKey <= Qt::Key_Asterisk ) ||
-     ( theKey >= Qt::Key_Comma && theKey <= Qt::Key_Question ) ||
-     ( theKey >= Qt::Key_A && theKey <= Qt::Key_AsciiTilde ) )
-    return true;
-  return false;
-}
-
-/*! \brief Called when "Clear" button is clicked. */
-void QtxKeySequenceEdit::onClear()
-{
-  myKeySequenceLineEdit->setText("");
-  myPrevKeySequenceString = "";
-  emit editingFinished();
-}
-
-/*! \brief Called when myKeySequenceLineEdit loses focus. */
-void QtxKeySequenceEdit::onEditingFinished()
-{
-  if (myKeySequenceLineEdit->text().endsWith("+"))
-    myKeySequenceLineEdit->setText(myPrevKeySequenceString);
-  else
-    myPrevKeySequenceString = myKeySequenceLineEdit->text();
-    emit editingFinished();
-}
-
-/*!
-  \brief Custom event filter.
-  \param obj event receiver object
-  \param event event
-  \returns \c true if further event processing should be stopped
-*/
-bool QtxKeySequenceEdit::eventFilter(QObject* theObject, QEvent* theEvent)
-{
-  if (theObject == myKeySequenceLineEdit) {
-    if (theEvent->type() == QEvent::KeyPress) {
-      QKeyEvent* keyEvent = static_cast<QKeyEvent*>(theEvent);
-      QString text = parseEvent(keyEvent);
-      if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace)
-        myKeySequenceLineEdit->setText("");
-      if (!text.isEmpty())
-        myKeySequenceLineEdit->setText(text);
-
-      emit editingStarted();
-      return true;
-    }
-    if (theEvent->type() == QEvent::KeyRelease) {
-      onEditingFinished();
-      return true;
-    }
-  }
-  return false;
-}
-
-/*
-  \brief Perform internal intialization.
-*/
-void QtxKeySequenceEdit::initialize()
-{
-  static const int PIXMAP_SIZE = 30;
-
-  QHBoxLayout* base = new QHBoxLayout( this );
-  base->setMargin(0);
-  base->setSpacing(5);
-
-  base->addWidget(myKeySequenceLineEdit = new QLineEdit(this));
-  setFocusProxy(myKeySequenceLineEdit);
-
-  QToolButton* clearBtn = new QToolButton();
-  auto clearPixmap = QPixmap(":/images/shortcut_disable.svg");
-  clearPixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
-  clearBtn->setIcon(clearPixmap);
-  clearBtn->setToolTip(tr("Disable shortcut."));
-  base->addWidget(clearBtn);
-
-  QToolButton* restoreBtn = new QToolButton();
-  auto restorePixmap = QPixmap(":/images/shortcut_restore.svg");
-  restorePixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
-  restoreBtn->setIcon(restorePixmap);
-  restoreBtn->setToolTip(tr("Restore the currently applied key sequence."));
-  base->addWidget(restoreBtn);
-
-  myKeySequenceLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
-  clearBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-  restoreBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-
-  connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear()));
-  connect(restoreBtn, SIGNAL(clicked()), this, SIGNAL(restoreFromShortcutMgrClicked()));
-  connect(myKeySequenceLineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
-}
-
-
-/*! \param theParent must not be nullptr. */
-QtxEditKeySequenceDialog::QtxEditKeySequenceDialog(QtxShortcutTree* theParent)
-: QDialog(theParent)
-{
-  setMinimumWidth(500);
-  setWindowTitle(tr("Change key sequence"));
-  QVBoxLayout* layout = new QVBoxLayout(this);
-  myActionName = new QLabel(this);
-  myActionName->setTextFormat(Qt::RichText);
-  myKeySequenceEdit = new QtxKeySequenceEdit(this);
-  myTextEdit = new QTextEdit(this);
-  layout->addWidget(myActionName);
-  layout->addWidget(myKeySequenceEdit);
-  layout->addWidget(myTextEdit);
-  myActionName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
-  myKeySequenceEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
-  myTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
-  myTextEdit->setReadOnly(true);
-  myTextEdit->setAcceptRichText(true);
-  myTextEdit->setPlaceholderText(tr("No conflicts."));
-  setFocusProxy(myKeySequenceEdit);
-
-  QHBoxLayout* buttonLayout = new QHBoxLayout(this);
-  layout->addLayout(buttonLayout);
-  QPushButton* confirmButton = new QPushButton(tr("Confirm"), this);
-  confirmButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-  QPushButton* cancelButton = new QPushButton(tr("Cancel"), this);
-  cancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
-  buttonLayout->addStretch();
-  buttonLayout->addWidget(confirmButton);
-  buttonLayout->addWidget(cancelButton);
-
-  connect(myKeySequenceEdit, SIGNAL(editingStarted()), this, SLOT(onEditingStarted()));
-  connect(myKeySequenceEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
-  connect(myKeySequenceEdit, SIGNAL(restoreFromShortcutMgrClicked()), this, SLOT(onRestoreFromShortcutMgr()));
-  connect(confirmButton, SIGNAL(clicked()), this, SLOT(onConfirm()));
-  connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
-}
-
-void QtxEditKeySequenceDialog::setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID)
-{
-  myModuleID = theModuleID;
-  myInModuleActionID = theInModuleActionID;
-}
-
-const QString& QtxEditKeySequenceDialog::moduleID() const { return myModuleID; }
-const QString& QtxEditKeySequenceDialog::inModuleActionID() const { return myInModuleActionID; }
-
-void QtxEditKeySequenceDialog::setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip)
-{
-  myActionName->setText("<b>" + theModuleName + "</b>&nbsp;&nbsp;" + theActionName);
-  myActionName->setToolTip(theActionToolTip);
-}
-
-void QtxEditKeySequenceDialog::setConfirmedKeySequence(const QKeySequence& theSequence)
-{
-  myKeySequenceEdit->setConfirmedKeySequence(theSequence);
-}
-
-QKeySequence QtxEditKeySequenceDialog::editedKeySequence() const
-{
-  return myKeySequenceEdit->editedKeySequence();
-}
-
-int QtxEditKeySequenceDialog::exec()
-{
-  myKeySequenceEdit->setFocus(Qt::ActiveWindowFocusReason);
-  return QDialog::exec();
-}
-
-void QtxEditKeySequenceDialog::onEditingStarted()
-{
-  myTextEdit->setEnabled(false);
-}
-
-void QtxEditKeySequenceDialog::onEditingFinished()
-{
-  updateConflictsMessage();
-}
-
-void QtxEditKeySequenceDialog::onRestoreFromShortcutMgr()
-{
-  const auto shortcutMgr = SUIT_ShortcutMgr::get();
-  myKeySequenceEdit->setEditedKeySequence(shortcutMgr->getKeySequence(myModuleID, myInModuleActionID));
-  updateConflictsMessage();
-}
-
-/*! Updates message with list of actions, whose shortcuts will be disabled on Confirm. */
-void QtxEditKeySequenceDialog::updateConflictsMessage()
-{
-  myTextEdit->setEnabled(true);
-  QTextDocument* doc = myTextEdit->document();
-  if (!doc) {
-    doc = new QTextDocument(myTextEdit);
-    myTextEdit->setDocument(doc);
-  }
-
-  if (!myKeySequenceEdit->isKeySequenceModified()) {
-    doc->clear();
-    return;
-  }
-
-  const QKeySequence newKeySequence = editedKeySequence();
-
-  const auto shortcutTree = static_cast<QtxShortcutTree*>(parentWidget());
-  /** {moduleID, inModuleActionID}[] */
-  std::set<std::pair<QString, QString>> conflicts = shortcutTree->shortcutContainer()->getConflicts(myModuleID, myInModuleActionID, newKeySequence);
-  if (!conflicts.empty()) {
-    const auto shortcutMgr = SUIT_ShortcutMgr::get();
-
-    QString report = "<b>" + tr("These shortcuts will be disabled on confirm:") + "</b>";
-    {
-      report += "<ul>";
-      for (const auto& conflict : conflicts) {
-        const QString conflictingModuleName = shortcutMgr->getModuleName(conflict.first);
-        const QString conflictingActionName = shortcutMgr->getActionName(conflict.first, conflict.second);
-        report += "<li><b>" + conflictingModuleName + "</b>&nbsp;&nbsp;" + conflictingActionName + "</li>";
-      }
-      report += "</ul>";
-    }
-    doc->setHtml(report);
-  }
-  else /* if no conflicts */ {
-    doc->clear();
-  }
-}
-
-void QtxEditKeySequenceDialog::onConfirm()
-{
-  if (myKeySequenceEdit->isKeySequenceModified())
-    accept();
-  else
-    reject();
-}
-
-
-/*! \brief Compensates lack of std::distance(), which is introduced in C++17.
-\returns -1, if theIt does not belong to the  */
-template <class Container>
-size_t indexOf(
-  const Container& theContainer,
-  const typename Container::iterator& theIt
-) {
-  auto it = theContainer.begin();
-  size_t distance = 0;
-  while (it != theContainer.end()) {
-    if (it == theIt)
-      return distance;
-
-    it++;
-    distance++;
-  }
-  return -1;
-}
-
-
-/*! \param theContainer Share the same container between several trees,
-to edit them synchronously even without exchange of changes with SUIT_ShortcutMgr.
-Pass nullptr to create non-synchronized tree. */
-QtxShortcutTree::QtxShortcutTree(
-  std::shared_ptr<SUIT_ShortcutContainer> theContainer,
-  QWidget* theParent
-) : QTreeWidget(theParent),
-myShortcutContainer(theContainer ? theContainer : std::shared_ptr<SUIT_ShortcutContainer>(new SUIT_ShortcutContainer())),
-mySortKey(QtxShortcutTree::SortKey::Name), mySortOrder(QtxShortcutTree::SortOrder::Ascending)
-{
-  setColumnCount(2);
-  setSelectionMode(QAbstractItemView::SingleSelection);
-  setColumnWidth(0, COLUMN_SIZE);
-  setSortingEnabled(false); // Items are sorted in the same way, as in ShortcutContainer.
-  header()->setSectionResizeMode(QHeaderView::Interactive);
-  {
-    QMap<int, QString> labelMap;
-    labelMap[QtxShortcutTree::ElementIdx::Name]        = tr("Action");
-    labelMap[QtxShortcutTree::ElementIdx::KeySequence] = tr("Key sequence");
-    setHeaderLabels(labelMap.values());
-  }
-  setExpandsOnDoubleClick(false); // Open shortcut editor on double click instead.
-  setSortingEnabled(false);
-  setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
-  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-  myEditDialog = new QtxEditKeySequenceDialog(this);
-
-  this->installEventFilter(this);
-  connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
-
-  QtxShortcutTree::instances[myShortcutContainer.get()].emplace(this);
-}
-
-QtxShortcutTree::~QtxShortcutTree()
-{
-  QtxShortcutTree::instances[myShortcutContainer.get()].erase(this);
-  if (QtxShortcutTree::instances[myShortcutContainer.get()].empty())
-    QtxShortcutTree::instances.erase(myShortcutContainer.get());
-}
-
-/*! \brief Copies shortcuts from ShortcutMgr. (Re)displays shortcuts of myModuleIDs. */
-void QtxShortcutTree::setShortcutsFromManager()
-{
-  const auto shortcutMgr = SUIT_ShortcutMgr::get();
-  *myShortcutContainer = shortcutMgr->getShortcutContainer();
-  // nb! ShortcutMgr never removes shortcuts from its container, only disables.
-
-  updateItems(false /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
-}
-
-/*! \brief Copies shortcuts from resources, user files are not accounted. (Re)displays shortcuts of myModuleIDs. */
-void QtxShortcutTree::setDefaultShortcuts()
-{
-  SUIT_ShortcutContainer defaultShortcuts;
-  SUIT_ShortcutMgr::fillContainerFromPreferences(defaultShortcuts, true /*theDefaultOnly*/);
-
-  myShortcutContainer->merge(defaultShortcuts, true /*theOverride*/, true /*theTreatAbsentIncomingAsDisabled*/);
-  // nb! SUIT_ShortcutContainer never erases shortcuts, only disables.
-
-  updateItems(true /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
-}
-
-/*! \brief Applies pending changes to ShortcutMgr. Updates other instances of QtxShortcutTree. */
-void QtxShortcutTree::applyChangesToShortcutMgr()
-{
-  const auto mgr = SUIT_ShortcutMgr::get();
-  mgr->mergeShortcutContainer(*myShortcutContainer);
-
-  // Update non-synchronized with this instances.
-  for (const auto& containerAndSyncTrees : QtxShortcutTree::instances) {
-    if (containerAndSyncTrees.first == myShortcutContainer.get())
-      continue;
-
-    const std::set<QtxShortcutTree*>& syncTrees = containerAndSyncTrees.second;
-    const auto itFirstSyncTree = syncTrees.begin();
-    if (itFirstSyncTree == syncTrees.end())
-      continue;
-
-    (*itFirstSyncTree)->setShortcutsFromManager();
-    const auto editDialog = (*itFirstSyncTree)->myEditDialog;
-    editDialog->setConfirmedKeySequence(mgr->getShortcutContainer().getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
-    editDialog->updateConflictsMessage();
-  }
-}
-
-std::shared_ptr<const SUIT_ShortcutContainer> QtxShortcutTree::shortcutContainer() const
-{
-  return myShortcutContainer;
-}
-
-/*! \brief Does not sort modules. */
-void QtxShortcutTree::sort(QtxShortcutTree::SortKey theKey, QtxShortcutTree::SortOrder theOrder)
-{
-  if (theKey == mySortKey && theOrder == mySortOrder)
-    return;
-
-  mySortKey == theKey;
-  mySortOrder = theOrder;
-
-  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
-    const auto moduleItem = static_cast<QtxShortcutTreeFolder*>(topLevelItem(moduleIdx));
-    const auto sortedChildren = getSortedChildren(moduleItem);
-    moduleItem->takeChildren();
-
-    for (const auto childItem : sortedChildren) {
-      moduleItem->addChild(childItem);
-    }
-  }
-}
-
-/*! \param If theUpdateSyncTrees, trees sharing the same shortcut container are updated. */
-void QtxShortcutTree::updateItems(bool theHighlightModified, bool theUpdateSyncTrees)
-{
-  const auto shortcutMgr = SUIT_ShortcutMgr::get();
-  const QString lang = SUIT_ShortcutMgr::getLang();
-
-  for (const QString& moduleID : myModuleIDs) {
-    const auto& moduleShortcuts = myShortcutContainer->getModuleShortcutsInversed(moduleID);
-    if (moduleShortcuts.empty()) {
-      // Do not display empty module.
-      const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
-      if (moduleItemAndIdx.second >= 0)
-        delete takeTopLevelItem(moduleItemAndIdx.second);
-
-      continue;
-    }
-
-    const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
-    QtxShortcutTreeFolder* moduleItem = moduleItemAndIdx.first;
-    if (!moduleItem) {
-      moduleItem = new QtxShortcutTreeFolder(moduleID);
-      moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang);
-      addTopLevelItem(moduleItem);
-      moduleItem->setFlags(Qt::ItemIsEnabled);
-
-      auto sortedChildren = getSortedChildren(moduleItem);
-      for (const auto& shortcut : moduleShortcuts) {
-        const QString& inModuleActionID = shortcut.first;
-        const QKeySequence& keySequence = shortcut.second;
-        const QString keySequenceString = keySequence.toString();
-
-        auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID);
-        if (!actionItem) {
-          ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
-          continue;
-        }
-
-        actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
-        actionItem->setKeySequence(keySequenceString);
-
-        if (theHighlightModified) {
-          const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
-          actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
-        }
-
-        insertChild(moduleItem, sortedChildren, actionItem);
-      }
-
-      moduleItem->setExpanded(true); // Make tree expanded on first show.
-    }
-    else /* if the tree has the module-item */ {
-      for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
-        // Update exisiting items of a module.
-        QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(moduleItem->child(childIdx));
-        const auto itShortcut = moduleShortcuts.find(childItem->myInModuleActionID);
-        if (itShortcut == moduleShortcuts.end()) {
-          // Shortcut of the item has been removed from myShortcutContainer - impossible.
-          continue;
-        }
-        const QKeySequence& newKeySequence = itShortcut->second;
-        const QString newKeySequenceString = newKeySequence.toString();
-        if (childItem->keySequence() != newKeySequenceString)
-          childItem->setKeySequence(newKeySequenceString);
-
-        if (theHighlightModified) {
-          const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
-          childItem->highlightKeySequenceAsModified(newKeySequence != appliedKeySequence);
-        }
-        else
-          childItem->highlightKeySequenceAsModified(false);
-      }
-
-      // Add new items if myShortcutContainer acquired new shortcuts, which may happen if a developer forgot
-      // to add shortcuts for registered actions to resource files.
-      if (moduleItem->childCount() < moduleShortcuts.size()) {
-        auto sortedChildren = getSortedChildren(moduleItem);
-        for (const auto& shortcut : moduleShortcuts) {
-          const QString& inModuleActionID = shortcut.first;
-          const auto predicate = [&inModuleActionID](const QtxShortcutTreeItem* const theItem) -> bool {
-            return static_cast<const QtxShortcutTreeAction* const>(theItem)->myInModuleActionID == inModuleActionID;
-          };
-
-          if (std::find_if(sortedChildren.begin(), sortedChildren.end(), predicate) == sortedChildren.end()) {
-            const auto actionItem = QtxShortcutTreeAction::create(moduleID, inModuleActionID);
-            if (!actionItem) {
-              ShCutDbg("QtxShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
-              continue;
-            }
-
-            const QKeySequence& keySequence = shortcut.second;
-            actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
-            actionItem->setKeySequence(keySequence.toString());
-
-            if (theHighlightModified) {
-              const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
-              actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
-            }
-
-            insertChild(moduleItem, sortedChildren, actionItem);
-          }
-        }
-      }
-    }
-  }
-
-  if (theUpdateSyncTrees) {
-    const std::set<QtxShortcutTree*>& syncTrees = QtxShortcutTree::instances[myShortcutContainer.get()];
-    for (const auto syncTree: syncTrees) {
-      if (syncTree == this)
-        continue;
-
-      syncTree->updateItems(theHighlightModified, false /*theUpdateSyncTrees*/);
-      const auto editDialog = syncTree->myEditDialog;
-      editDialog->setConfirmedKeySequence(myShortcutContainer->getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
-      editDialog->updateConflictsMessage();
-    }
-  }
-}
-
-/*! \returns Pointer and index of top-level item.
-If the tree does not contain an item with theModuleID, returns {nullptr, -1}. */
-std::pair<QtxShortcutTreeFolder*, int> QtxShortcutTree::findModuleFolderItem(const QString& theModuleID) const
-{
-  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
-    QtxShortcutTreeFolder* moduleItem = static_cast<QtxShortcutTreeFolder*>(topLevelItem(moduleIdx));
-    if (moduleItem->myModuleID == theModuleID)
-      return std::pair<QtxShortcutTreeFolder*, int>(moduleItem, moduleIdx);
-  }
-  return std::pair<QtxShortcutTreeFolder*, int>(nullptr, -1);
-}
-
-/*! \returns Children of theParentItem being sorted according to current sort mode and order. */
-std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>> QtxShortcutTree::getSortedChildren(QtxShortcutTreeFolder* theParentItem)
-{
-  QList<std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>> sortSchema = QtxShortcutTree::DEFAULT_SORT_SCHEMA;
-  {
-    for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
-      if (itSameKey->first == mySortKey) {
-        sortSchema.erase(itSameKey);
-        break;
-      }
-    }
-    sortSchema.push_front(std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>(mySortKey, mySortOrder));
-  }
-
-  static const QCollator collator;
-  const std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)> comparator =
-  [this, sortSchema, &collator](const QtxShortcutTreeItem* theItemA, const QtxShortcutTreeItem* theItemB) {
-    int res = 0;
-    for (const auto& keyAndOrder : sortSchema) {
-      int res = 0;
-      res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
-      if (res != 0)
-        return keyAndOrder.second == QtxShortcutTree::SortOrder::Ascending ? res < 0 : res > 0;
-    }
-    return false;
-  };
-
-  std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>> sortedChildren(comparator);
-  for (int childIdx = 0; childIdx < theParentItem->childCount(); childIdx++) {
-    QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(theParentItem->child(childIdx));
-    sortedChildren.emplace(childItem);
-  }
-  return sortedChildren;
-}
-
-/*! \brief Inserts theChildItem to theParentItem and theSortedChildren.
-Does not check whether theSortedChildren are actually child items of theParentItem.
-Does not check whether current item sort schema is same as one of theSortedChildren. */
-void QtxShortcutTree::insertChild(
-  QtxShortcutTreeFolder* theParentItem,
-  std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>>& theSortedChildren,
-  QtxShortcutTreeItem* theChildItem
-) {
-  auto emplaceRes = theSortedChildren.emplace(theChildItem);
-  theParentItem->insertChild(indexOf(theSortedChildren, emplaceRes.first), theChildItem);
-}
-
-void QtxShortcutTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx)
-{
-  {
-    QtxShortcutTreeItem* const item = static_cast<QtxShortcutTreeItem*>(theItem);
-    // Do not react if folder-item is clicked.
-    if (item->type() != QtxShortcutTreeItem::Type::Action)
-      return;
-  }
-
-  QtxShortcutTreeAction* const actionItem = static_cast<QtxShortcutTreeAction*>(theItem);
-
-  myEditDialog->setModuleAndActionID(actionItem->myModuleID, actionItem->myInModuleActionID);
-  QString actionToolTip = actionItem->toolTip(QtxShortcutTree::ElementIdx::Name);
-  actionToolTip.truncate(actionToolTip.lastIndexOf('\n') + 1);
-  myEditDialog->setModuleAndActionName(
-    static_cast<QtxShortcutTreeItem*>(actionItem->parent())->name(),
-    actionItem->name(),
-    actionToolTip
-  );
-  myEditDialog->setConfirmedKeySequence(QKeySequence::fromString(actionItem->keySequence()));
-  myEditDialog->updateConflictsMessage();
-  const bool somethingChanged = myEditDialog->exec() == QDialog::Accepted;
-
-  if (!somethingChanged)
-    return;
-
-  const QKeySequence newKeySequence = myEditDialog->editedKeySequence();
-
-  /** { moduleID, inModuleActionID }[] */
-  std::set<std::pair<QString, QString>> disabledActionIDs = myShortcutContainer->setShortcut(actionItem->myModuleID, actionItem->myInModuleActionID, newKeySequence, true /*override*/);
-
-  /** { moduleID, {inModuleActionID, keySequence}[] }[] */
-  std::map<QString, std::map<QString, QString>> changes;
-  changes[actionItem->myModuleID][actionItem->myInModuleActionID] = newKeySequence.toString();
-  for (const auto moduleAndActionID : disabledActionIDs) {
-    changes[moduleAndActionID.first][moduleAndActionID.second] = QString();
-  }
-
-  // Set new key sequences to shortcut items.
-  for (const auto& moduleIDAndChanges : changes) {
-    const QString& moduleID = moduleIDAndChanges.first;
-
-    const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
-    const auto moduleItem = moduleItemAndIdx.first;
-    if (!moduleItem)
-      continue;
-
-    /** {inModuleActionID, newKeySequence}[] */
-    const std::map<QString, QString>& moduleChanges = moduleIDAndChanges.second;
-
-    // Go through module' shortcut items, and highlight those, whose key sequences differ from applied key sequences.
-    for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
-      QtxShortcutTreeAction* const childItem = static_cast<QtxShortcutTreeAction*>(moduleItem->child(childIdx));
-      const auto itChange = moduleChanges.find(childItem->myInModuleActionID);
-      if (itChange == moduleChanges.end()) {
-        // The shortcut has not been changed.
-        continue;
-      }
-
-      childItem->setKeySequence(itChange->second);
-
-      const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
-      childItem->highlightKeySequenceAsModified(QKeySequence::fromString(itChange->second) != appliedKeySequence);
-    }
-  }
-}
-
-/*static*/ const QList<std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>> QtxShortcutTree::DEFAULT_SORT_SCHEMA =
-{
-  {QtxShortcutTree::SortKey::Name, QtxShortcutTree::SortOrder::Ascending},
-  {QtxShortcutTree::SortKey::ToolTip, QtxShortcutTree::SortOrder::Ascending},
-  {QtxShortcutTree::SortKey::KeySequence, QtxShortcutTree::SortOrder::Ascending},
-  {QtxShortcutTree::SortKey::ID, QtxShortcutTree::SortOrder::Ascending}
-};
-
-/*static*/ std::map<SUIT_ShortcutContainer*, std::set<QtxShortcutTree*>> QtxShortcutTree::instances =
-std::map<SUIT_ShortcutContainer*, std::set<QtxShortcutTree*>>();
-
-
-
-QtxShortcutTreeItem::QtxShortcutTreeItem(const QString& theModuleID)
-: QTreeWidgetItem(), myModuleID(theModuleID)
-{ }
-
-QString QtxShortcutTreeItem::name() const
-{
-  return text(QtxShortcutTree::ElementIdx::Name);
-}
-
-
-QtxShortcutTreeFolder::QtxShortcutTreeFolder(const QString& theModuleID)
-: QtxShortcutTreeItem(theModuleID)
-{
-  QFont f = font(QtxShortcutTree::ElementIdx::Name);
-  f.setBold(true);
-  setFont(QtxShortcutTree::ElementIdx::Name, f);
-  setText(QtxShortcutTree::ElementIdx::Name, theModuleID);
-}
-
-void QtxShortcutTreeFolder::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
-{
-  if (!theAssets)
-    return;
-
-  setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon);
-
-  const auto& ldaMap = theAssets->myLangDependentAssets;
-  if (ldaMap.empty()) {
-    setText(QtxShortcutTree::ElementIdx::Name, myModuleID);
-    return;
-  }
-
-  auto itLDA = ldaMap.find(theLang);
-  if (itLDA == ldaMap.end())
-    itLDA = ldaMap.begin();
-
-  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
-  const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
-  setText(QtxShortcutTree::ElementIdx::Name, name);
-}
-
-QString QtxShortcutTreeFolder::getValue(QtxShortcutTree::SortKey theKey) const
-{
-  switch (theKey) {
-    case QtxShortcutTree::SortKey::ID:
-      return myModuleID;
-    case QtxShortcutTree::SortKey::Name:
-      return name();
-    case QtxShortcutTree::SortKey::ToolTip:
-      return name();
-    default:
-      return QString();
-  }
-}
-
-
-QtxShortcutTreeAction::QtxShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
-: QtxShortcutTreeItem(theModuleID), myInModuleActionID(theInModuleActionID)
-{
-  setText(QtxShortcutTree::ElementIdx::Name, theInModuleActionID);
-  setToolTip(
-    QtxShortcutTree::ElementIdx::Name,
-    theInModuleActionID + (theInModuleActionID.at(theInModuleActionID.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.")
-  );
-  setToolTip(QtxShortcutTree::ElementIdx::KeySequence, QtxShortcutTree::tr("Double click to edit key sequence."));
-}
-
-/*static*/ QtxShortcutTreeAction* QtxShortcutTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
-{
-  if (theInModuleActionID.isEmpty()) {
-    ShCutDbg("QtxShortcutTreeItem: attempt to create item with empty action ID.");
-    return nullptr;
-  }
-
-  return new QtxShortcutTreeAction(theModuleID, theInModuleActionID);
-}
-
-void QtxShortcutTreeAction::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
-{
-  if (!theAssets)
-    return;
-
-  setIcon(QtxShortcutTree::ElementIdx::Name, theAssets->myIcon);
-
-  const auto& ldaMap = theAssets->myLangDependentAssets;
-  if (ldaMap.empty()) {
-    setText(QtxShortcutTree::ElementIdx::Name, myInModuleActionID);
-    return;
-  }
-
-  auto itLDA = ldaMap.find(theLang);
-  if (itLDA == ldaMap.end())
-    itLDA = ldaMap.begin();
-
-  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
-  const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
-  setText(QtxShortcutTree::ElementIdx::Name, name);
-
-  const QString& actionToolTip = lda.myToolTip.isEmpty() ? name : lda.myToolTip;
-  setToolTip(
-    QtxShortcutTree::ElementIdx::Name,
-    actionToolTip + (actionToolTip.at(actionToolTip.length()-1) == "." ? "\n" : ".\n") + QtxShortcutTree::tr("Double click to edit key sequence.")
-  );
-}
-
-QString QtxShortcutTreeAction::getValue(QtxShortcutTree::SortKey theKey) const
-{
-  switch (theKey) {
-    case QtxShortcutTree::SortKey::ID:
-      return myInModuleActionID;
-    case QtxShortcutTree::SortKey::Name:
-      return name();
-    case QtxShortcutTree::SortKey::ToolTip:
-      return toolTip(QtxShortcutTree::ElementIdx::Name);
-    case QtxShortcutTree::SortKey::KeySequence:
-      return keySequence();
-    default:
-      return QString();
-  }
-}
-
-void QtxShortcutTreeAction::setKeySequence(const QString& theKeySequence)
-{
-  setText(QtxShortcutTree::ElementIdx::KeySequence, theKeySequence);
-}
-
-QString QtxShortcutTreeAction::keySequence() const
-{
-  return text(QtxShortcutTree::ElementIdx::KeySequence);
-}
-
-/*! \brief Highlights text at ElementIdx::KeySequence. */
-void QtxShortcutTreeAction::highlightKeySequenceAsModified(bool theHighlight)
-{
-  static const QBrush bgHighlitingBrush = QBrush(Qt::darkGreen);
-  static const QBrush fgHighlitingBrush = QBrush(Qt::white);
-  static const QBrush noBrush = QBrush();
-
-  setBackground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? bgHighlitingBrush : noBrush);
-  setForeground(QtxShortcutTree::ElementIdx::KeySequence, theHighlight ? fgHighlitingBrush : noBrush);
-}
\ No newline at end of file
diff --git a/src/Qtx/QtxShortcutEdit.h b/src/Qtx/QtxShortcutEdit.h
deleted file mode 100644 (file)
index 862c80d..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
-//
-// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
-//
-
-#ifndef QTXSHORTCUTTREE_H
-#define QTXSHORTCUTTREE_H
-
-#include "Qtx.h"
-#include <QDialog>
-#include <QFrame>
-#include <QTreeWidget>
-#include "SUIT_ShortcutMgr.h"
-#include <memory>
-#include <map>
-#include <set>
-#include <functional>
-
-
-class QLineEdit;
-class QLabel;
-class QPushButton;
-class QTreeWidgetItem;
-
-class QTX_EXPORT QtxKeySequenceEdit : public QFrame
-{
-  Q_OBJECT
-
-public:
-  QtxKeySequenceEdit(QWidget* = nullptr);
-  virtual ~QtxKeySequenceEdit() = default;
-
-  void           setConfirmedKeySequence(const QKeySequence&);
-  void           setEditedKeySequence(const QKeySequence&);
-  QKeySequence   editedKeySequence() const;
-  bool           isKeySequenceModified() const;
-  void           restoreKeySequence();
-
-  static QString parseEvent(QKeyEvent*);
-  static bool    isValidKey(int);
-
-signals:
-  void           editingStarted();
-  void           editingFinished();
-  void           restoreFromShortcutMgrClicked();
-
-private slots:
-  void           onClear();
-  void           onEditingFinished();
-
-protected:
-  virtual bool   eventFilter(QObject*, QEvent*);
-
-private:
-  void           initialize();
-
-private:
-  QLineEdit*     myKeySequenceLineEdit;
-  QString        myConfirmedKeySequenceString;
-
-  // Last valid key sequence string from myKeySequenceLineEdit.
-  QString        myPrevKeySequenceString;
-};
-
-
-class QtxShortcutTree;
-class QtxShortcutTreeItem;
-class QtxShortcutTreeFolder;
-class QtxShortcutTreeAction;
-class QTextEdit;
-
-
-class QTX_EXPORT QtxEditKeySequenceDialog : public QDialog
-{
-  Q_OBJECT
-
-public:
-  QtxEditKeySequenceDialog(QtxShortcutTree* theParent);
-  QtxEditKeySequenceDialog(const QtxEditKeySequenceDialog&) = delete;
-  QtxEditKeySequenceDialog& operator=(const QtxEditKeySequenceDialog&) = delete;
-  virtual ~QtxEditKeySequenceDialog() = default;
-
-  void setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID);
-  const QString& moduleID() const;
-  const QString& inModuleActionID() const;
-
-  void setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip = "");
-
-  void setConfirmedKeySequence(const QKeySequence& theSequence);
-  QKeySequence editedKeySequence() const;
-
-  void updateConflictsMessage();
-
-  int exec();
-
-private slots:
-  void onEditingStarted();
-  void onEditingFinished();
-  void onRestoreFromShortcutMgr();
-  void onConfirm();
-
-private:
-  QString myModuleID;
-  QString myInModuleActionID;
-  QLabel* myActionName;
-  QtxKeySequenceEdit* myKeySequenceEdit;
-  QTextEdit* myTextEdit;
-};
-
-
-class QTX_EXPORT QtxShortcutTree : public QTreeWidget
-{
-  Q_OBJECT
-
-public:
-  enum ElementIdx {
-    Name = 0,
-    KeySequence = 1, // Empty, if item is folder item.
-  };
-
-  enum class SortKey {
-    ID,
-    Name,
-    ToolTip,
-    KeySequence,
-  };
-
-  enum class SortOrder {
-    Ascending,
-    Descending
-  };
-
-  QtxShortcutTree(
-    std::shared_ptr<SUIT_ShortcutContainer> theContainer = std::shared_ptr<SUIT_ShortcutContainer>(),
-    QWidget* theParent = nullptr
-  );
-  QtxShortcutTree(const QtxShortcutTree&) = delete;
-  QtxShortcutTree& operator=(const QtxShortcutTree&) = delete;
-  virtual ~QtxShortcutTree();
-
-  void setShortcutsFromManager();
-  void setDefaultShortcuts();
-  void applyChangesToShortcutMgr();
-
-  std::shared_ptr<const SUIT_ShortcutContainer> shortcutContainer() const;
-
-  void sort(QtxShortcutTree::SortKey theKey, QtxShortcutTree::SortOrder theOrder);
-
-private:
-  void updateItems(bool theHighlightModified, bool theUpdateSyncTrees);
-  std::pair<QtxShortcutTreeFolder*, int> findModuleFolderItem(const QString& theModuleID) const;
-
-  std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>> getSortedChildren(QtxShortcutTreeFolder* theParentItem);
-
-  void insertChild(
-    QtxShortcutTreeFolder* theParentItem,
-    std::set<QtxShortcutTreeItem*, std::function<bool(QtxShortcutTreeItem*, QtxShortcutTreeItem*)>>& theSortedChildren,
-    QtxShortcutTreeItem* theChildItem
-  );
-
-private slots:
-  void onItemDoubleClicked(QTreeWidgetItem* theWidgetItem, int theColIdx);
-
-public:
-  /** Keeps IDs of modules, which will are shown on setShortcutsFromManager(). */
-  std::set<QString> myModuleIDs;
-
-  static const QList<std::pair<QtxShortcutTree::SortKey, QtxShortcutTree::SortOrder>> DEFAULT_SORT_SCHEMA;
-
-private:
-  /** Allows to modify plenty of shortcuts and then apply them to SUIT_ShortcutMgr as a batch. */
-  const std::shared_ptr<SUIT_ShortcutContainer> myShortcutContainer;
-
-  QtxEditKeySequenceDialog* myEditDialog;
-
-  QtxShortcutTree::SortKey mySortKey;
-  QtxShortcutTree::SortOrder mySortOrder;
-
-  /**
-   * Ensures that, if several QtxShortcutTree instances coexist,
-   * all of them are updated when one of them applies pending changes to SUIT_ShortcutMgr.
-   *
-   * Sharing of SUIT_ShortcutContainer allows to keep some trees synchronized even without
-   * applying changes to SUIT_ShortcutMgr. Why? See QtxPagePrefShortcutTreeItem.
-   *
-   * Access is not synchronized in assumption, that all instances live in the same thread.
-  */
-  static std::map<SUIT_ShortcutContainer*, std::set<QtxShortcutTree*>> instances;
-};
-
-
-class QtxShortcutTreeItem : public QTreeWidgetItem
-{
-public:
-  enum Type {
-    Folder = 0,
-    Action = 1,
-  };
-
-protected:
-  QtxShortcutTreeItem(const QString& theModuleID);
-
-public:
-  virtual ~QtxShortcutTreeItem() = default;
-  virtual QtxShortcutTreeItem::Type type() const = 0;
-
-  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang) = 0;
-  QString name() const;
-
-  virtual QString getValue(QtxShortcutTree::SortKey theKey) const = 0;
-
-public:
-  const QString myModuleID;
-};
-
-
-class QtxShortcutTreeFolder : public QtxShortcutTreeItem
-{
-public:
-  QtxShortcutTreeFolder(const QString& theModuleID);
-  virtual ~QtxShortcutTreeFolder() = default;
-  virtual QtxShortcutTreeItem::Type type() const { return QtxShortcutTreeItem::Type::Folder; };
-
-  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
-
-  virtual QString getValue(QtxShortcutTree::SortKey theKey) const;
-};
-
-
-class QtxShortcutTreeAction : public QtxShortcutTreeItem
-{
-private:
-  QtxShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID);
-
-public:
-  static QtxShortcutTreeAction* create(const QString& theModuleID, const QString& theInModuleActionID);
-  virtual ~QtxShortcutTreeAction() = default;
-  virtual QtxShortcutTreeItem::Type type() const { return QtxShortcutTreeItem::Type::Action; };
-
-  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
-
-  virtual QString getValue(QtxShortcutTree::SortKey theKey) const;
-
-  void setKeySequence(const QString& theKeySequence);
-  QString keySequence() const;
-  void highlightKeySequenceAsModified(bool theHighlight);
-
-  const QString myInModuleActionID;
-};
-
-#endif // QTXSHORTCUTTREE_H
index 2ea62824ee145c9d24c536a9b8c6d00140f5b8cc..d61dfe7fb74c7b1b483a029ea7046f7b342c851a 100644 (file)
         <translation>%1 a été développé en utilisant %2</translation>
     </message>
 </context>
-<context>
-    <name>QtxKeySequenceEdit</name>
-    <message>
-        <source>Disable shortcut.</source>
-        <translation>Désactivez le raccourci.</translation>
-    </message>
-    <message>
-        <source>Restore the currently applied key sequence.</source>
-        <translation>Restaurez la séquence de touches actuellement appliquée.</translation>
-    </message>
-</context>
-<context>
-    <name>QtxEditKeySequenceDialog</name>
-    <message>
-        <source>Change key sequence</source>
-        <translation>Modifier la séquence de touches</translation>
-    </message>
-    <message>
-        <source>No conflicts.</source>
-        <translation>Aucun conflit.</translation>
-    </message>
-    <message>
-        <source>Confirm</source>
-        <translation>Confirmer</translation>
-    </message>
-    <message>
-        <source>Cancel</source>
-        <translation>Annuler</translation>
-    </message>
-    <message>
-        <source>These shortcuts will be disabled on confirm:</source>
-        <translation>Ces raccourcis seront désactivés lors de la confirmation :</translation>
-    </message>
-</context>
-<context>
-    <name>QtxShortcutTree</name>
-    <message>
-        <source>Action</source>
-        <translation>Action</translation>
-    </message>
-    <message>
-        <source>Key sequence</source>
-        <translation>Séquence de touches</translation>
-    </message>
-    <message>
-        <source>Double click to edit key sequence.</source>
-        <translation>Double-cliquez pour modifier la séquence de touches.</translation>
-    </message>
-</context>
 </TS>
index 36900402a7cc12635914783b079578f185c3f9b7..173c8128288fe8d0517d008310985163c6a522a8 100644 (file)
       <translation>%1 は %2 を使用して開発されています。</translation>
     </message>
   </context>
-  <context>
-    <name>QtxKeySequenceEdit</name>
-    <message>
-        <source>Disable shortcut.</source>
-        <translation>ショートカットを無効にします。</translation>
-    </message>
-    <message>
-        <source>Restore the currently applied key sequence.</source>
-        <translation>現在適用されているキー シーケンスを復元します。</translation>
-    </message>
-  </context>
-  <context>
-      <name>QtxEditKeySequenceDialog</name>
-      <message>
-          <source>Change key sequence</source>
-          <translation>キーシーケンスを変更する</translation>
-      </message>
-      <message>
-          <source>No conflicts.</source>
-          <translation>競合はありません。</translation>
-      </message>
-      <message>
-          <source>Confirm</source>
-          <translation>確認する</translation>
-      </message>
-      <message>
-          <source>Cancel</source>
-          <translation>キャンセル</translation>
-      </message>
-      <message>
-          <source>These shortcuts will be disabled on confirm:</source>
-          <translation>これらのショートカットは確認時に無効になります。</translation>
-      </message>
-  </context>
-  <context>
-      <name>QtxShortcutTree</name>
-      <message>
-        <source>Action</source>
-        <translation>アクション</translation>
-      </message>
-      <message>
-          <source>Key sequence</source>
-          <translation>キーシーケンス</translation>
-      </message>
-      <message>
-          <source>Double click to edit key sequence.</source>
-          <translation>ダブルクリックしてキー シーケンスを編集します。</translation>
-      </message>
-  </context>
 </TS>
index 6c7c3f6af0a215c70d008c62d050b781df445c5c..95015a57c70e7b1657312321c908670cac4a6984 100644 (file)
@@ -46,14 +46,17 @@ SET(_moc_HEADERS
   SUIT_DataObject.h
   SUIT_Desktop.h
   SUIT_FileDlg.h
+  SUIT_FindActionDialog.h
   SUIT_LicenseDlg.h
   SUIT_MessageBox.h
   SUIT_Operation.h
+  SUIT_PagePrefShortcutTreeItem.h
   SUIT_PopupClient.h
   SUIT_PreferenceMgr.h
   SUIT_SelectionMgr.h
   SUIT_Session.h
   SUIT_ShortcutMgr.h
+  SUIT_ShortcutTree.h
   SUIT_Study.h
   SUIT_TreeModel.h
   SUIT_ViewManager.h
@@ -121,10 +124,12 @@ SET(_other_SOURCES
   SUIT_ExceptionHandler.cxx
   SUIT_FileDlg.cxx
   SUIT_FileValidator.cxx
+  SUIT_FindActionDialog.cxx
   SUIT_LicenseDlg.cxx
   SUIT_MessageBox.cxx
   SUIT_Operation.cxx
   SUIT_OverrideCursor.cxx
+  SUIT_PagePrefShortcutTreeItem.cxx
   SUIT_PopupClient.cxx
   SUIT_PreferenceMgr.cxx
   SUIT_ResourceMgr.cxx
@@ -133,6 +138,7 @@ SET(_other_SOURCES
   SUIT_Selector.cxx
   SUIT_Session.cxx
   SUIT_ShortcutMgr.cxx
+  SUIT_ShortcutTree.cxx
   SUIT_Study.cxx
   SUIT_Tools.cxx
   SUIT_TreeModel.cxx
diff --git a/src/SUIT/SUIT_FindActionDialog.cxx b/src/SUIT/SUIT_FindActionDialog.cxx
new file mode 100644 (file)
index 0000000..1d07958
--- /dev/null
@@ -0,0 +1,557 @@
+// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "SUIT_FindActionDialog.h"
+
+#include <QAction>
+#include <QWidget>
+#include <QLayout>
+#include <QList>
+#include <QMap>
+#include <QApplication>
+
+#include <QCollator>
+
+#include <QCheckBox>
+#include <QLineEdit>
+#include <QBrush>
+#include <QColor>
+#include <QHeaderView>
+#include <QKeyEvent>
+
+#include <algorithm>
+#include <limits>
+
+
+SUIT_FindActionDialog::SUIT_FindActionDialog(QWidget* theParent)
+: QDialog(theParent)
+{
+  setMinimumWidth(500);
+  setWindowTitle(tr("Find action"));
+  QVBoxLayout* layout = new QVBoxLayout(this);
+
+  myQueryLineEdit = new QLineEdit(this);
+  layout->addWidget(myQueryLineEdit);
+  myQueryLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  setFocusProxy(myQueryLineEdit);
+
+  QHBoxLayout* searchOptionsLayout = new QHBoxLayout(this);
+  layout->addLayout(searchOptionsLayout);
+  myIncludeUnavailableActionsCB = new QCheckBox(tr("Unavailable actions"), this);
+  myIncludeUnavailableActionsCB->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+  myIncludeUnavailableActionsCB->setCheckState(Qt::CheckState::Checked);
+  myActionSearcher.includeDisabledActions(true);
+  myIncludeInactiveModulesCB = new QCheckBox(tr("Inactive modules"), this);
+  myIncludeInactiveModulesCB->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+  myIncludeInactiveModulesCB->setCheckState(Qt::CheckState::Unchecked);
+  searchOptionsLayout->addWidget(myIncludeUnavailableActionsCB);
+  searchOptionsLayout->addWidget(myIncludeInactiveModulesCB);
+
+  myFoundActionsTree = new SUIT_FoundActionTree(this);
+  layout->addWidget(myFoundActionsTree);
+
+  connect(myQueryLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(onQueryChanged(const QString&)));
+  connect(myIncludeUnavailableActionsCB, SIGNAL(stateChanged(int)), this, SLOT(onSearchOptionUnavailableActionsChanged(int)));
+  connect(myIncludeInactiveModulesCB, SIGNAL(stateChanged(int)), this, SLOT(onSearchOptionInactiveModulesChanged(int)));
+
+  myQueryLineEdit->installEventFilter(myFoundActionsTree);
+}
+
+void SUIT_FindActionDialog::setActiveModuleID(const QString& theModuleID)
+{
+  myActiveModuleID = theModuleID;
+  if(myActionSearcher.setIncludedModuleIDs(std::set<QString>({SUIT_ShortcutMgr::ROOT_MODULE_ID, myActiveModuleID})))
+    updateUI();
+}
+
+void SUIT_FindActionDialog::onQueryChanged(const QString& theQuery)
+{
+  if (myActionSearcher.setQuery(theQuery))
+    updateUI();
+}
+
+void SUIT_FindActionDialog::onSearchOptionUnavailableActionsChanged(int theState)
+{
+  if (myActionSearcher.includeDisabledActions(theState == Qt::CheckState::Checked))
+    updateUI();
+}
+
+void SUIT_FindActionDialog::onSearchOptionInactiveModulesChanged(int theState)
+{
+  bool resultsChanged = false;
+  if (theState == Qt::CheckState::Checked) {
+    myIncludeUnavailableActionsCB->setDisabled(true);
+    myIncludeUnavailableActionsCB->setCheckState(Qt::CheckState::Checked);
+    resultsChanged = myActionSearcher.setIncludedModuleIDs(SUIT_ShortcutMgr::get()->getShortcutContainer().getIDsOfAllModules());
+  }
+  else {
+    myIncludeUnavailableActionsCB->setDisabled(false);
+    resultsChanged = myActionSearcher.setIncludedModuleIDs(std::set<QString>({SUIT_ShortcutMgr::ROOT_MODULE_ID, myActiveModuleID}));
+  }
+
+  if (resultsChanged)
+    updateUI();
+}
+
+void SUIT_FindActionDialog::updateUI()
+{
+  myFoundActionsTree->updateItems(myActionSearcher.getSearchResults());
+}
+
+
+
+SUIT_FoundActionTree::SUIT_FoundActionTree(SUIT_FindActionDialog* theParent)
+: QTreeWidget(theParent)
+{
+  setColumnCount(2);
+  setSelectionMode(QAbstractItemView::SingleSelection);
+  setSortingEnabled(false);
+  header()->setSectionResizeMode(QHeaderView::Interactive);
+  {
+    QMap<int, QString> labelMap;
+    labelMap[SUIT_FoundActionTree::ElementIdx::Name]    = SUIT_FindActionDialog::tr("Action");
+    labelMap[SUIT_FoundActionTree::ElementIdx::ToolTip] = SUIT_FindActionDialog::tr("Description");
+    setHeaderLabels(labelMap.values());
+  }
+  setExpandsOnDoubleClick(false); // Implemented manually.
+  setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
+  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+  setColumnWidth(SUIT_FoundActionTree::ElementIdx::Name, 120);
+  setColumnWidth(SUIT_FoundActionTree::ElementIdx::Name, 250);
+  setMinimumHeight(300);
+
+  setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
+
+  mySortKey = SUIT_FoundActionTree::SortKey::MatchMetrics;
+  mySortOrder = SUIT_FoundActionTree::SortOrder::Ascending;
+
+  connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemExecuted(QTreeWidgetItem*, int)));
+}
+
+/*! \brief Compensates lack of std::distance(), which is introduced in C++17.
+\returns -1, if theIt does not belong to the  */
+template <class Container>
+size_t indexOf(
+  const Container& theContainer,
+  const typename Container::iterator& theIt
+) {
+  auto it = theContainer.begin();
+  size_t distance = 0;
+  while (it != theContainer.end()) {
+    if (it == theIt)
+      return distance;
+
+    it++;
+    distance++;
+  }
+  return -1;
+}
+
+void SUIT_FoundActionTree::updateItems(const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& theAssets)
+{
+  std::set<QString> shownModuleIDs; // To sort module-items by their IDs.
+
+  // Remove shown module items, if updated search results have no matching actions from these modules.
+  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); ) {
+    SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
+    myModuleItemExpansionStates[moduleItem->myModuleID] = moduleItem->isExpanded();
+
+    const auto itUpdatedAssetsOfShownModule = theAssets.find(moduleItem->myModuleID);
+    if (itUpdatedAssetsOfShownModule == theAssets.end()) {
+      delete takeTopLevelItem(moduleIdx);
+      continue;
+    }
+
+    if (itUpdatedAssetsOfShownModule->second.empty()) {
+      delete takeTopLevelItem(moduleIdx);
+      continue;
+    }
+
+    shownModuleIDs.emplace(moduleItem->myModuleID);
+    moduleIdx++;
+  }
+
+  const auto shortcutMgr = SUIT_ShortcutMgr::get();
+  const QString lang = SUIT_ShortcutMgr::getLang();
+
+  SUIT_FoundActionTreeAction* preselectedActionItem = nullptr;
+
+  for (const auto& moduleIDAndAssets : theAssets) {
+    const QString& moduleID = moduleIDAndAssets.first;
+    const auto& moduleAssets = moduleIDAndAssets.second;
+    if (moduleAssets.empty())
+      continue;
+
+    const auto moduleItemAndIdx = findModuleItem(moduleID);
+    SUIT_FoundActionTreeModule* moduleItem = moduleItemAndIdx.first;
+    if (!moduleItem) {
+      moduleItem = new SUIT_FoundActionTreeModule(moduleID);
+      moduleItem->setAssetsAndSearchData(SUIT_ActionSearcher::AssetsAndSearchData(shortcutMgr->getModuleAssets(moduleID)), lang);
+
+      const auto emplaceRes = shownModuleIDs.emplace(moduleID);
+      insertTopLevelItem(indexOf(shownModuleIDs, emplaceRes.first), moduleItem);
+
+      moduleItem->setFlags(Qt::ItemIsEnabled);
+
+      const auto itExpansionState = myModuleItemExpansionStates.find(moduleID);
+      if (itExpansionState == myModuleItemExpansionStates.end())
+        moduleItem->setExpanded(true); // Make module item expanded at first appearance.
+      else
+        moduleItem->setExpanded(itExpansionState->second);
+    }
+    else /* if the tree has the module-item */ {
+      const auto actionItems = moduleItem->takeChildren();
+      for (const auto actionItem : actionItems) {
+        delete actionItem;
+      }
+    }
+
+    // Fill module item with action items.
+    auto sortedActionItems = createActionSetWithComparator();
+    for (const auto& actionIDAndAssets : moduleAssets) {
+      const QString& inModuleActionID = actionIDAndAssets.first;
+      const SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = actionIDAndAssets.second;
+
+      auto actionItem = SUIT_FoundActionTreeAction::create(moduleID, inModuleActionID);
+      if (!actionItem) {
+        ShCutDbg("SUIT_FoundActionTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
+        continue;
+      }
+
+      actionItem->setAssetsAndSearchData(assetsAndSearchData, lang);
+      sortedActionItems.emplace(actionItem);
+    }
+
+    SUIT_FoundActionTreeAction* preselectedActionItemCand = nullptr;
+    for (const auto actionItem : sortedActionItems) {
+      moduleItem->addChild(actionItem);
+
+      // Consider first ranked available action in the module (if user did not collapsed it) as a candidate for preselected action.
+      if (!preselectedActionItemCand && moduleItem->isExpanded() && actionItem->isEnabledBufferedValue())
+        preselectedActionItemCand = actionItem;
+    }
+
+    if (preselectedActionItem) {
+      if (preselectedActionItemCand) {
+        if (preselectedActionItemCand->matchMetrics() < preselectedActionItem->matchMetrics())
+          preselectedActionItem = preselectedActionItemCand;
+      }
+    }
+    else
+      preselectedActionItem = preselectedActionItemCand;
+  }
+
+  if (preselectedActionItem)
+    setCurrentItem(preselectedActionItem);
+}
+
+void SUIT_FoundActionTree::sort(SUIT_FoundActionTree::SortKey theKey, SUIT_FoundActionTree::SortOrder theOrder)
+{
+  if (theKey == mySortKey && theOrder == mySortOrder)
+    return;
+
+  mySortKey == theKey;
+  mySortOrder = theOrder;
+
+  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
+    const auto moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
+
+    auto sortedActionItems = createActionSetWithComparator();
+    for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
+      SUIT_FoundActionTreeAction* const actionItem = static_cast<SUIT_FoundActionTreeAction*>(moduleItem->child(childIdx));
+      sortedActionItems.emplace(actionItem);
+    }
+
+    moduleItem->takeChildren();
+
+    for (const auto actionItem : sortedActionItems) {
+      moduleItem->addChild(actionItem);
+    }
+  }
+}
+
+void SUIT_FoundActionTree::keyPressEvent(QKeyEvent* theEvent)
+{
+  const auto key = theEvent->key();
+  const auto selectedItem = currentItem();
+  if ((key == Qt::Key_Enter || key == Qt::Key_Return) && selectedItem)
+    onItemExecuted(selectedItem, SUIT_FoundActionTree::ElementIdx::Name);
+  else
+    QTreeWidget::keyPressEvent(theEvent);
+}
+
+bool SUIT_FoundActionTree::eventFilter(QObject* theQObject, QEvent* theEvent)
+{
+  if (theEvent->type() == QEvent::KeyPress) {
+    QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(theEvent);
+    const auto key = keyEvent->key();
+
+    if (key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Up || key == Qt::Key_Down) {
+      keyPressEvent(keyEvent);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+std::pair<SUIT_FoundActionTreeModule*, int> SUIT_FoundActionTree::findModuleItem(const QString& theModuleID) const
+{
+  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
+    SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
+    if (moduleItem->myModuleID == theModuleID)
+      return std::pair<SUIT_FoundActionTreeModule*, int>(moduleItem, moduleIdx);
+  }
+  return std::pair<SUIT_FoundActionTreeModule*, int>(nullptr, -1);
+}
+
+template <typename Float>
+bool approximatelyEqual(Float a, Float b, Float relativeTol = std::numeric_limits<Float>::epsilon())
+{
+    return std::abs(a - b) <= ( (std::abs(a) < std::abs(b) ? std::abs(b) : std::abs(a)) * relativeTol);
+}
+
+std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>> SUIT_FoundActionTree::createActionSetWithComparator() const
+{
+  QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> sortSchema = SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA;
+  {
+    for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
+      if (itSameKey->first == mySortKey) {
+        sortSchema.erase(itSameKey);
+        break;
+      }
+    }
+    sortSchema.push_front(std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>(mySortKey, mySortOrder));
+  }
+
+  static const QCollator collator;
+  const std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)> comparator =
+  [sortSchema, &collator](const SUIT_FoundActionTreeAction* theItemA, const SUIT_FoundActionTreeAction* theItemB) {
+    for (const auto& keyAndOrder : sortSchema) {
+      const QVariant fieldOfA = theItemA->getValue(keyAndOrder.first);
+      const QVariant fieldOfB = theItemB->getValue(keyAndOrder.first);
+
+      bool* const fieldOfAIsDouble = new bool(false);
+      bool* const fieldOfBIsDouble = new bool(false);
+      const double matchMetricsA = fieldOfA.toDouble(fieldOfAIsDouble);
+      const double matchMetricsB = fieldOfB.toDouble(fieldOfBIsDouble);
+      if (*fieldOfAIsDouble && *fieldOfBIsDouble) {
+        if (!approximatelyEqual(matchMetricsA, matchMetricsB)) {
+          const double res = matchMetricsA - matchMetricsB;
+          return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
+        }
+      }
+      else {
+        const int res = collator.compare(fieldOfA.toString(), fieldOfB.toString());
+        if (res != 0)
+          return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
+      }
+    }
+    return false;
+  };
+
+  return std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>>(comparator);
+}
+
+void SUIT_FoundActionTree::onItemExecuted(QTreeWidgetItem* theItem, int theColIdx)
+{
+  SUIT_FoundActionTreeItem* const item = static_cast<SUIT_FoundActionTreeItem*>(theItem);
+  if (item->type() == SUIT_FoundActionTreeItem::Type::Action) {
+    SUIT_FoundActionTreeAction* const actionItem = static_cast<SUIT_FoundActionTreeAction*>(theItem);
+    if (actionItem->trigger())
+      static_cast<SUIT_FindActionDialog*>(parentWidget())->accept();
+  }
+  else /* if (item->type() == SUIT_FoundActionTreeItem::Type::Module) */ {
+    item->setExpanded(!item->isExpanded());
+  }
+}
+
+/*static*/ const QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA =
+{
+  {SUIT_FoundActionTree::SortKey::MatchMetrics, SUIT_FoundActionTree::SortOrder::Ascending},
+  {SUIT_FoundActionTree::SortKey::Name, SUIT_FoundActionTree::SortOrder::Ascending},
+  {SUIT_FoundActionTree::SortKey::ToolTip, SUIT_FoundActionTree::SortOrder::Ascending},
+  {SUIT_FoundActionTree::SortKey::ID, SUIT_FoundActionTree::SortOrder::Ascending}
+};
+
+
+SUIT_FoundActionTreeItem::SUIT_FoundActionTreeItem(const QString& theModuleID)
+: QTreeWidgetItem(), myModuleID(theModuleID)
+{ }
+
+QString SUIT_FoundActionTreeItem::name() const
+{
+  return text(SUIT_FoundActionTree::ElementIdx::Name);
+}
+
+QString SUIT_FoundActionTreeItem::toolTip() const
+{
+  return text(SUIT_FoundActionTree::ElementIdx::ToolTip);
+}
+
+
+SUIT_FoundActionTreeModule::SUIT_FoundActionTreeModule(const QString& theModuleID)
+: SUIT_FoundActionTreeItem(theModuleID)
+{
+  QFont f = font(SUIT_FoundActionTree::ElementIdx::Name);
+  f.setBold(true);
+  setFont(SUIT_FoundActionTree::ElementIdx::Name, f);
+  setText(SUIT_FoundActionTree::ElementIdx::Name, theModuleID);
+}
+
+void SUIT_FoundActionTreeModule::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
+{
+  if (!theAssetsAndSD.myAssets)
+    return;
+
+  setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
+
+  const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
+  if (ldaMap.empty()) {
+    setText(SUIT_FoundActionTree::ElementIdx::Name, myModuleID);
+    return;
+  }
+
+  auto itLDA = ldaMap.find(theLang);
+  if (itLDA == ldaMap.end())
+    itLDA = ldaMap.begin();
+
+  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
+  const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
+  setText(SUIT_FoundActionTree::ElementIdx::Name, name);
+}
+
+QVariant SUIT_FoundActionTreeModule::getValue(SUIT_FoundActionTree::SortKey theKey) const
+{
+  switch (theKey) {
+    case SUIT_FoundActionTree::SortKey::MatchMetrics:
+      return double(0);
+    case SUIT_FoundActionTree::SortKey::ID:
+      return myModuleID;
+    case SUIT_FoundActionTree::SortKey::Name:
+      return name();
+    case SUIT_FoundActionTree::SortKey::ToolTip:
+      return toolTip();
+    default:
+      return QString();
+  }
+}
+
+bool SUIT_FoundActionTreeModule::isEnabled() const
+{
+  return true;
+}
+
+
+SUIT_FoundActionTreeAction::SUIT_FoundActionTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
+: SUIT_FoundActionTreeItem(theModuleID), myInModuleActionID(theInModuleActionID),
+  myMatchMetrics(std::numeric_limits<double>::infinity()), myIsEnabledBufferedValue(false)
+{
+  setText(SUIT_FoundActionTree::ElementIdx::Name, theInModuleActionID);
+}
+
+/*static*/ SUIT_FoundActionTreeAction* SUIT_FoundActionTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
+{
+  if (theInModuleActionID.isEmpty()) {
+    ShCutDbg("SUIT_FoundActionTreeItem: attempt to create item with empty action ID.");
+    return nullptr;
+  }
+
+  return new SUIT_FoundActionTreeAction(theModuleID, theInModuleActionID);
+}
+
+void SUIT_FoundActionTreeAction::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
+{
+  if (!theAssetsAndSD.myAssets)
+    return;
+
+  setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
+
+  const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
+  if (ldaMap.empty()) {
+    setText(SUIT_FoundActionTree::ElementIdx::Name, myInModuleActionID);
+    return;
+  }
+
+  auto itLDA = ldaMap.find(theLang);
+  if (itLDA == ldaMap.end())
+    itLDA = ldaMap.begin();
+
+  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
+  const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
+  setText(SUIT_FoundActionTree::ElementIdx::Name, name);
+
+  setText(SUIT_FoundActionTree::ElementIdx::ToolTip, lda.myToolTip);
+
+  if (isEnabled()) {
+    setToolTip(
+      SUIT_FoundActionTree::ElementIdx::Name,
+      SUIT_FoundActionTree::tr("Double click to start")
+    );
+
+    setToolTip(
+      SUIT_FoundActionTree::ElementIdx::ToolTip,
+      SUIT_FoundActionTree::tr("Double click to start")
+    );
+  }
+  else {
+    static const QBrush greyedOutBrush = QBrush(Qt::gray);
+    setForeground(SUIT_FoundActionTree::ElementIdx::Name,    greyedOutBrush);
+    setForeground(SUIT_FoundActionTree::ElementIdx::ToolTip, greyedOutBrush);
+  }
+
+  myMatchMetrics = theAssetsAndSD.matchMetrics();
+}
+
+QVariant SUIT_FoundActionTreeAction::getValue(SUIT_FoundActionTree::SortKey theKey) const
+{
+  switch (theKey) {
+    case SUIT_FoundActionTree::SortKey::MatchMetrics:
+      return myMatchMetrics;
+    case SUIT_FoundActionTree::SortKey::ID:
+      return myInModuleActionID;
+    case SUIT_FoundActionTree::SortKey::Name:
+      return name();
+    case SUIT_FoundActionTree::SortKey::ToolTip:
+      return toolTip();
+    default:
+      return QString();
+  }
+}
+
+bool SUIT_FoundActionTreeAction::isEnabled() const
+{
+  const auto& actions = SUIT_ShortcutMgr::get()->getActions(myModuleID, myInModuleActionID);
+  myIsEnabledBufferedValue = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); }) != actions.end();
+  return myIsEnabledBufferedValue;
+}
+
+bool SUIT_FoundActionTreeAction::trigger() const
+{
+  bool res = false;
+  const auto& actions = SUIT_ShortcutMgr::get()->getActions(myModuleID, myInModuleActionID);
+  for (const auto& action : actions) {
+    if (action->isEnabled()) {
+      action->trigger();
+      res = true;
+    }
+  }
+  return res;
+}
\ No newline at end of file
diff --git a/src/SUIT/SUIT_FindActionDialog.h b/src/SUIT/SUIT_FindActionDialog.h
new file mode 100644 (file)
index 0000000..34ffbcb
--- /dev/null
@@ -0,0 +1,209 @@
+// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
+//
+// Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef SUIT_FINDACTIONDIALOG_H
+#define SUIT_FINDACTIONDIALOG_H
+
+#include "SUIT.h"
+#include "SUIT_ShortcutMgr.h"
+#include <QDialog>
+#include <QFrame>
+#include <QTreeWidget>
+#include <QList>
+#include <QVariant>
+#include <memory>
+#include <map>
+#include <set>
+#include <functional>
+#include <utility>
+
+
+class QCheckBox;
+class QLineEdit;
+class QLabel;
+class QPushButton;
+class QKeyEvent;
+class SUIT_FoundActionTree;
+
+
+class SUIT_EXPORT SUIT_FindActionDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  SUIT_FindActionDialog(QWidget* theParent);
+  SUIT_FindActionDialog(const SUIT_FindActionDialog&) = delete;
+  SUIT_FindActionDialog& operator=(const SUIT_FindActionDialog&) = delete;
+  virtual ~SUIT_FindActionDialog() = default;
+
+  void setActiveModuleID(const QString& theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID);
+
+private slots:
+  void onQueryChanged(const QString& theKeyword);
+  void onSearchOptionUnavailableActionsChanged(int);
+  void onSearchOptionInactiveModulesChanged(int);
+
+private:
+  void updateUI();
+
+  QLineEdit* myQueryLineEdit;
+  QCheckBox* myIncludeUnavailableActionsCB;
+  QCheckBox* myIncludeInactiveModulesCB;
+  SUIT_FoundActionTree* myFoundActionsTree;
+
+  QString myActiveModuleID;
+  SUIT_ActionSearcher myActionSearcher;
+};
+
+
+class SUIT_FoundActionTreeItem;
+class SUIT_FoundActionTreeModule;
+class SUIT_FoundActionTreeAction;
+
+
+class SUIT_EXPORT SUIT_FoundActionTree : public QTreeWidget
+{
+  Q_OBJECT
+
+public:
+  enum ElementIdx {
+    Name = 0,
+    ToolTip = 1
+  };
+
+  enum class SortKey {
+    MatchMetrics,
+    ID,
+    Name,
+    ToolTip
+  };
+
+  enum class SortOrder {
+    Ascending,
+    Descending
+  };
+
+  SUIT_FoundActionTree(SUIT_FindActionDialog* theParent);
+  SUIT_FoundActionTree(const SUIT_FoundActionTree&) = delete;
+  SUIT_FoundActionTree& operator=(const SUIT_FoundActionTree&) = delete;
+  virtual ~SUIT_FoundActionTree() = default;
+
+  void updateItems(const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& theAssets);
+
+  void sort(SUIT_FoundActionTree::SortKey theKey, SUIT_FoundActionTree::SortOrder theOrder);
+
+  void keyPressEvent(QKeyEvent* theEvent);
+
+protected:
+  bool eventFilter(QObject* theQObject, QEvent* theEvent);
+
+private:
+  std::pair<SUIT_FoundActionTreeModule*, int> findModuleItem(const QString& theModuleID) const;
+  std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>> createActionSetWithComparator() const;
+
+private slots:
+  void onItemExecuted(QTreeWidgetItem* theWidgetItem, int theColIdx);
+
+public:
+  static const QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> DEFAULT_SORT_SCHEMA;
+
+private:
+  SUIT_FoundActionTree::SortKey mySortKey;
+  SUIT_FoundActionTree::SortOrder mySortOrder;
+
+  /** {moduleID, isExpanded}[] */
+  std::map<QString, bool> myModuleItemExpansionStates;
+};
+
+
+class SUIT_FoundActionTreeItem : public QTreeWidgetItem
+{
+public:
+  enum Type {
+    Module = 0,
+    Action = 1,
+  };
+
+protected:
+  SUIT_FoundActionTreeItem(const QString& theModuleID);
+
+public:
+  virtual ~SUIT_FoundActionTreeItem() = default;
+  virtual SUIT_FoundActionTreeItem::Type type() const = 0;
+
+  virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang) = 0;
+  QString name() const;
+  QString toolTip() const;
+
+  virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const = 0;
+
+  virtual bool isEnabled() const = 0;
+
+public:
+  const QString myModuleID;
+};
+
+
+class SUIT_FoundActionTreeModule : public SUIT_FoundActionTreeItem
+{
+public:
+  SUIT_FoundActionTreeModule(const QString& theModuleID);
+  virtual ~SUIT_FoundActionTreeModule() = default;
+  virtual SUIT_FoundActionTreeItem::Type type() const { return SUIT_FoundActionTreeItem::Type::Module; };
+
+  /*! \brief Search data is unused. */
+  virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang);
+
+  virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const;
+
+  virtual bool isEnabled() const;
+};
+
+
+class SUIT_FoundActionTreeAction : public SUIT_FoundActionTreeItem
+{
+private:
+  SUIT_FoundActionTreeAction(const QString& theModuleID, const QString& theInModuleActionID);
+
+public:
+  static SUIT_FoundActionTreeAction* create(const QString& theModuleID, const QString& theInModuleActionID);
+  virtual ~SUIT_FoundActionTreeAction() = default;
+  virtual SUIT_FoundActionTreeItem::Type type() const { return SUIT_FoundActionTreeItem::Type::Action; };
+
+  virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang);
+
+  virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const;
+  double matchMetrics() const { return myMatchMetrics; };
+
+  virtual bool isEnabled() const;
+  bool isEnabledBufferedValue() const { return myIsEnabledBufferedValue; };
+
+  bool trigger() const;
+
+  const QString myInModuleActionID;
+
+private:
+  double myMatchMetrics;
+  mutable bool myIsEnabledBufferedValue;
+};
+
+#endif // SUIT_FINDACTIONDIALOG_H
diff --git a/src/SUIT/SUIT_PagePrefShortcutTreeItem.cxx b/src/SUIT/SUIT_PagePrefShortcutTreeItem.cxx
new file mode 100644 (file)
index 0000000..d05d511
--- /dev/null
@@ -0,0 +1,65 @@
+#include "SUIT_PagePrefShortcutTreeItem.h"
+
+#include "SUIT_ShortcutTree.h"
+#include "SUIT_ShortcutMgr.h"
+
+
+/*!
+  \brief Creates preference item for editing of key bindings
+  \param theParent parent preference item. Must not be nullptr.
+*/
+SUIT_PagePrefShortcutTreeItem::SUIT_PagePrefShortcutTreeItem(QtxPreferenceItem* theParent)
+ : QtxPagePrefItem(QString(), theParent)
+{
+  auto container = std::shared_ptr<SUIT_ShortcutContainer>();
+  const auto itContainers = SUIT_PagePrefShortcutTreeItem::shortcutContainers.find(rootItem());
+  if (itContainers == SUIT_PagePrefShortcutTreeItem::shortcutContainers.end()) {
+    container.reset(new SUIT_ShortcutContainer());
+    SUIT_PagePrefShortcutTreeItem::shortcutContainers.emplace(rootItem(), container);
+  }
+  else {
+    container = itContainers->second.lock();
+    if (!container) {
+      container.reset(new SUIT_ShortcutContainer());
+      itContainers->second = container;
+    }
+  }
+
+  SUIT_ShortcutTree* tree = new SUIT_ShortcutTree(container);
+  tree->myModuleIDs = SUIT_ShortcutMgr::get()->getShortcutModuleIDs();
+  setWidget(tree);
+}
+
+/*!
+  \brief Retrieves shortcut preferences from ShortcutMgr.
+  Updates UI of controlling widget.
+  \sa store()
+*/
+void SUIT_PagePrefShortcutTreeItem::retrieve()
+{
+  static_cast<SUIT_ShortcutTree*>(widget())->setShortcutsFromManager();
+}
+
+/*!
+  \brief Retrieves shortcut preferences from resource files, ignoring user preferences.
+  Updates UI of controlling widget.
+  \sa store()
+*/
+void SUIT_PagePrefShortcutTreeItem::retrieveDefault()
+{
+  static_cast<SUIT_ShortcutTree*>(widget())->setDefaultShortcuts();
+}
+
+/*!
+  \brief Applies modified shortcut preferences to ShortcutMgr.
+  Updates UI of controlling widget.
+  And ShortcutMgr, in turn, serilizes shortcut preferences using the resource manager.
+  \sa retrieve()
+*/
+void SUIT_PagePrefShortcutTreeItem::store()
+{
+  static_cast<SUIT_ShortcutTree*>(widget())->applyChangesToShortcutMgr();
+}
+
+/*static*/ std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>> SUIT_PagePrefShortcutTreeItem::shortcutContainers =
+std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>>();
\ No newline at end of file
diff --git a/src/SUIT/SUIT_PagePrefShortcutTreeItem.h b/src/SUIT/SUIT_PagePrefShortcutTreeItem.h
new file mode 100644 (file)
index 0000000..653a1e0
--- /dev/null
@@ -0,0 +1,64 @@
+// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
+//
+// Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
+// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef SUIT_PAGEPREFSHORTCUTTREEITEM_H
+#define SUIT_PAGEPREFSHORTCUTTREEITEM_H
+
+#include "SUIT.h"
+#include <QtxPagePrefMgr.h>
+
+#include <map>
+#include <memory>
+
+
+class SUIT_ShortcutTree;
+class SUIT_ShortcutContainer;
+
+
+class SUIT_EXPORT SUIT_PagePrefShortcutTreeItem : public QtxPagePrefItem
+{
+public:
+  SUIT_PagePrefShortcutTreeItem(QtxPreferenceItem* theParent);
+  virtual ~SUIT_PagePrefShortcutTreeItem() = default;
+
+  virtual void     retrieve();
+  virtual void     retrieveDefault();
+  virtual void     store();
+
+private:
+  SUIT_ShortcutTree* myShortcutTree;
+
+  // { root item (preference window), shortcut container of synchronized trees (widgets within the same window) }
+  static std::map<QtxPreferenceItem*, std::weak_ptr<SUIT_ShortcutContainer>> shortcutContainers;
+  /** Why is this?
+   * Every QtxPagePrefMgr is eventually a preference window. Each preference window has button "Apply".
+   * When the button is pressed, all descendants of the QtxPagePrefMgr store changes they carry into preferences.
+   * The pitfall with shortcut trees is as follows: made in independent shortcut trees, changes may conflict,
+   * and merge of such changes is ambiguous. And the solution is to keep shortcut trees within the same window
+   * synchronized - all changes being made in a tree of a synchronized bundle are projected to other trees from the bundle
+   * without interacting with SUIT_ShortcutMgr.
+   *
+   * Every time shortcut preferences stored to the ShortcutMgr, all instances of SUIT_ShortcutTree are updated.
+  */
+};
+
+#endif // SUIT_PAGEPREFSHORTCUTTREEITEM_H
\ No newline at end of file
index fa06180581967357638237140e1719a0eed26a46..7e5678266611b584318bc981e97110dd7925640a 100644 (file)
@@ -21,6 +21,7 @@
 // Author:    Sergey TELKOV
 //
 #include "SUIT_PreferenceMgr.h"
+#include "SUIT_PagePrefShortcutTreeItem.h"
 
 SUIT_PreferenceMgr::SUIT_PreferenceMgr( QtxResourceMgr* resMgr, QWidget* parent )
 : QtxPagePrefMgr( resMgr, parent ),
@@ -158,7 +159,7 @@ int SUIT_PreferenceMgr::addItem( const QString& title, const int pId,
     item = new QtxPagePrefPathListItem( Qtx::PT_Directory, title, parent, sect, param );
     break;
   case ShortcutTree:
-    item = new QtxPagePrefShortcutTreeItem( parent );
+    item = new SUIT_PagePrefShortcutTreeItem( parent );
     break;
   case BiColor:
     item = new QtxPagePrefBiColorItem( title, parent, sect, param );
index d0cc5adc8c796e0685f9f80d6633a012be80c753..9ce349bf04f3c73d0252a5c4fb8419b848f8726f 100644 (file)
@@ -2,11 +2,11 @@
 
 Hot keys must be considered as resources, being shared between all components of an application. E.g. it is unacceptable to have 'Close file' and 'Redo' actions being assigned to the same key sequence. When the SHAPER module is active, the application desktop is active too. The desktop has own hot keys, and they must not interfere with ones of SHAPER. Since the task implies granting users a right to assign shortcuts on their will, the application must track all assigned shortcuts of all modules, prevent intolerable user shortcut modifications and govern actual binding of QActions with key sequences.
 
-`SUIT_ShortcutMgr` handles shortcuts of SALOME desktop and all modules. It is solely responsible and capable to dynamically bind actions with key sequences and (de)serialize shortcut preferences using `SUIT_ResourceMgr`. `SUIT_ShortcutContainer` encapsulates logics of conflict detecting and resolving. `QtxShortcutTree` widget provides GUI to change shortcut preferences conveniently: it allows to remap plenty of shortcuts without applying, displays conflict-resolving dialog, highlights modifications until they are applied (saved into preference files).
+`SUIT_ShortcutMgr` handles shortcuts of SALOME desktop and all modules. It is solely responsible and capable to dynamically bind actions with key sequences and (de)serialize shortcut preferences using `SUIT_ResourceMgr`. `SUIT_ShortcutContainer` encapsulates logics of conflict detecting and resolving. `SUIT_ShortcutTree` widget provides GUI to change shortcut preferences conveniently: it allows to remap plenty of shortcuts without applying, displays conflict-resolving dialog, highlights modifications until they are applied (saved into preference files).
 
 To (de)serialize shortcut preferences without dependence on language environment, shortcuts must be stored as pairs {action ID, key sequence}, where action IDs must be application-unique.
 
-Since desktop shortcuts may also be changed and interfere with shortcuts of modules, `QtxShortcutTree` should always display desktop shortcuts and shortcuts of all modules altogether, even if some modules are inactive. It means, that `QtxShortcutTree` must be fed not only with shortcut data {action ID, key sequence}[], but also with dictionaries {action ID, action name}[]. `QtxShortcutTree` also requires other action assets - tool tip and icon path.
+Since desktop shortcuts may also be changed and interfere with shortcuts of modules, `SUIT_ShortcutTree` should always display desktop shortcuts and shortcuts of all modules altogether, even if some modules are inactive. It means, that `SUIT_ShortcutTree` must be fed not only with shortcut data {action ID, key sequence}[], but also with dictionaries {action ID, action name}[]. `SUIT_ShortcutTree` also requires other action assets - tool tip and icon path.
 
 Assets of actions may be retrieved from instances of actions, but there is a pitfall: if a module has not been activated yet, its actions have not been initialized either.
 Qt Linguist is no help in this case too. To retrieve an action name using `QObject::tr(actionID)`, the `tr(const char*)` method must be called with instance of the class, which is designated as a context for the actionID in *.ts files. And contexts are usually descendants of SUIT_Application and CAM_Module. Again, until a module instance is created, there is no way for `SUIT_ShortcutMgr` to get even a name of a context-class, which an action with an ID belongs to, without any additional data. Straightforward mechanism for loading of action assets in advance has been devised: for all actions, which are bound by default or may be bound by user to hotkeys, assets must be placed into asset files. People who do/refine localizations should keep this in mind and also process JSON files, which are referred in resource files in sections `<section name="action_assets">`.
@@ -77,6 +77,6 @@ Thus, ampersand-shortcuts will appear and be treated in shortcut editor as regul
 If the second option is preferable, should different ampersand-shortcuts for every target language be placed in resource files?
 
 ## Minor issues
-1. `QtxShortcutTree` widget does not take the whole available height of preference window, it only takes as mush as its items require.
-2. Selection of `QtxShortcutTree`' item shadows "modified" highlighter. Can be fixed by replacing base `QTreeWidget` of `QtxShortcutTree` with `QTreeView`, or may be by applying some style sheet.
+1. `SUIT_ShortcutTree` widget does not take the whole available height of preference window, it only takes as mush as its items require.
+2. Selection of `SUIT_ShortcutTree`' item shadows "modified" highlighter. Can be fixed by replacing base `QTreeWidget` of `SUIT_ShortcutTree` with `QTreeView`, or may be by applying some style sheet.
 3. `SUIT_ShortcutMgr` introduces concept of module, but the first module class is `CAM_Module` is introduced along with `CAM_Application`, which is descendant of `SUIT_Application`.
index bfae01ec37a16085c86d705c67fba665d7801f2e..1204d32f806c23d3b8cc003f024814a4a4cad59c 100644 (file)
@@ -78,7 +78,7 @@ static const QKeySequence NO_KEYSEQUENCE = QKeySequence(QString(""));
 static const QString NO_ACTION = QString("");
 /** Separates tokens in action ID. */
 static const QString TOKEN_SEPARATOR = QString("/");
-static const QString ROOT_MODULE_ID = QString("");
+/*static*/ const QString SUIT_ShortcutMgr::ROOT_MODULE_ID = QString("");
 static const QString META_ACTION_PREFIX = QString("#");
 
 /** Prefix of names of shortcut setting sections in preference files. */
@@ -158,7 +158,7 @@ public:
       moduleShortcuts[theInModuleActionID] = theKeySequence.toString();
 
       const QString fileName = theModuleID + DevTools::SHORTCUTS_OF_META_SUFFIX;
-      const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + ROOT_MODULE_ID;
+      const QString sectionName = SECTION_NAME_PREFIX + DevTools::XML_SECTION_TOKENS_SEPARATOR + SUIT_ShortcutMgr::ROOT_MODULE_ID;
       std::map<QString, std::map<QString, QString>> sections;
       sections[sectionName] = moduleShortcuts;
       writeToXMLFile(fileName, sections);
@@ -182,7 +182,7 @@ public:
     const QAction* theAction
   ) {
     if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID)) {
-      QString actionID = SUIT_ShortcutMgr::makeActionID(ROOT_MODULE_ID, theInModuleActionID);
+      QString actionID = SUIT_ShortcutMgr::makeActionID(SUIT_ShortcutMgr::ROOT_MODULE_ID, theInModuleActionID);
       // { actionID, assets } []
       auto& moduleAssets = myAssetsOfMetaActions[theModuleID];
 
@@ -490,21 +490,21 @@ public:
 
 SUIT_ShortcutContainer::SUIT_ShortcutContainer()
 {
-  myShortcuts.emplace(ROOT_MODULE_ID, std::map<QKeySequence, QString>());
-  myShortcutsInversed.emplace(ROOT_MODULE_ID, std::map<QString, QKeySequence>());
+  myShortcuts.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID, std::map<QKeySequence, QString>());
+  myShortcutsInversed.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID, std::map<QString, QKeySequence>());
 }
 
 std::set<QString> SUIT_ShortcutContainer::getIDsOfInterferingModules(const QString& theModuleID) const
 {
   std::set<QString> IDsOfInterferingModules;
-  if (theModuleID == ROOT_MODULE_ID) {
+  if (theModuleID == SUIT_ShortcutMgr::ROOT_MODULE_ID) {
     for (const auto& moduleIDAndShortcuts : myShortcuts) {
       IDsOfInterferingModules.emplace(moduleIDAndShortcuts.first);
     }
   }
   else {
-    IDsOfInterferingModules.emplace(ROOT_MODULE_ID);
-    if (theModuleID != ROOT_MODULE_ID)
+    IDsOfInterferingModules.emplace(SUIT_ShortcutMgr::ROOT_MODULE_ID);
+    if (theModuleID != SUIT_ShortcutMgr::ROOT_MODULE_ID)
       IDsOfInterferingModules.emplace(theModuleID);
   }
   return IDsOfInterferingModules;
@@ -532,7 +532,7 @@ std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::setShortcut(QStrin
   }
 
   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
-    theModuleID = ROOT_MODULE_ID;
+    theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
 
   auto itModuleShortcuts = myShortcuts.find(theModuleID);
   auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
@@ -629,7 +629,7 @@ std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::getConflicts(
     return std::set<std::pair<QString, QString>>();
 
   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
-    theModuleID = ROOT_MODULE_ID;
+    theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
 
   { // Check if the shortcut is set.
     const auto itModuleShortcuts = myShortcuts.find(theModuleID);
@@ -663,7 +663,7 @@ std::set<std::pair<QString, QString>> SUIT_ShortcutContainer::getConflicts(
 const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID, const QString& theInModuleActionID) const
 {
   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
-    theModuleID = ROOT_MODULE_ID;
+    theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
 
   const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
   if (itModuleShortcutsInversed == myShortcutsInversed.end())
@@ -680,7 +680,7 @@ const QKeySequence& SUIT_ShortcutContainer::getKeySequence(QString theModuleID,
 bool SUIT_ShortcutContainer::hasShortcut(QString theModuleID, const QString& theInModuleActionID) const
 {
   if (SUIT_ShortcutMgr::isInModuleMetaActionID(theInModuleActionID))
-    theModuleID = ROOT_MODULE_ID;
+    theModuleID = SUIT_ShortcutMgr::ROOT_MODULE_ID;
 
   const auto itModuleShortcutsInversed = myShortcutsInversed.find(theModuleID);
   if (itModuleShortcutsInversed == myShortcutsInversed.end())
@@ -1079,7 +1079,7 @@ SUIT_ShortcutMgr::~SUIT_ShortcutMgr()
     else
       ShCutDbg("Discovered shortcut modules: \"" + moduleIDs.join("\", \"") + ".");
   }
-  moduleIDs.push_front(ROOT_MODULE_ID); // Resource manager filters out empty section suffices.
+  moduleIDs.push_front(SUIT_ShortcutMgr::ROOT_MODULE_ID); // Resource manager filters out empty section suffices.
   moduleIDs.removeDuplicates();
 
   for (size_t i = 0; i < moduleIDs.size(); i++) {
@@ -1102,7 +1102,7 @@ SUIT_ShortcutMgr::~SUIT_ShortcutMgr()
       if (
         !SUIT_ShortcutMgr::isInModuleActionIDValid(inModuleActionID) ||
         !keySequence.first ||
-        SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != ROOT_MODULE_ID
+        SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) && moduleID != SUIT_ShortcutMgr::ROOT_MODULE_ID
       ) {
         std::list<std::pair<QString, QString>>& moduleInvalidShortcuts = invalidShortcuts[moduleID];
         moduleInvalidShortcuts.push_back(std::pair<QString, QString>(inModuleActionID, keySequenceString));
@@ -1353,7 +1353,7 @@ void SUIT_ShortcutMgr::registerAction(const QString& theActionID, QAction* theAc
   else {
     ShCutDbg(
       "Action with ID \"" +
-      (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) +
+      (SUIT_ShortcutMgr::isInModuleMetaActionID(inModuleActionID) ? SUIT_ShortcutMgr::ROOT_MODULE_ID + TOKEN_SEPARATOR + inModuleActionID : theActionID) +
       "\" is not added to default resource files."
     );
     auto conflicts = myShortcutContainer.setShortcut(moduleID, inModuleActionID, theAction->shortcut(), false);
@@ -1600,11 +1600,26 @@ std::shared_ptr<const SUIT_ActionAssets> SUIT_ShortcutMgr::getActionAssets(const
 
 std::shared_ptr<const SUIT_ActionAssets> SUIT_ShortcutMgr::getActionAssets(const QString& theActionID) const
 {
-  const auto it = myActionAssets.find(theActionID);
-  if (it == myActionAssets.end())
+  const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(theActionID);
+  const QString& moduleID = moduleIDAndActionID.first;
+  const QString& inModuleActionID = moduleIDAndActionID.second;
+
+  if (inModuleActionID.isEmpty()) {
+    ShCutDbg() && ShCutDbg("Attempt to get assets of an action with invalid ID \"" + theActionID + "\".");
     return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
-  else
-    return it->second;
+  }
+
+  const auto itModuleActionAssets = myActionAssets.find(moduleID);
+  if (itModuleActionAssets == myActionAssets.end())
+    return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
+  else {
+    const auto moduleActionAssets = itModuleActionAssets->second;
+    const auto itActionAssets = moduleActionAssets.find(inModuleActionID);
+    if (itActionAssets == moduleActionAssets.end())
+      return std::shared_ptr<const SUIT_ActionAssets>(nullptr);
+    else
+      return itActionAssets->second;
+  }
 }
 
 QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang) const
@@ -1615,8 +1630,13 @@ QString SUIT_ShortcutMgr::getActionName(const QString& theModuleID, const QStrin
     return actionID;
   }
 
-  const auto itActionAssets = myActionAssets.find(actionID);
-  if (itActionAssets != myActionAssets.end() && !itActionAssets->second->myLangDependentAssets.empty()) {
+  const auto itModuleActionAssets = myActionAssets.find(theModuleID);
+  if (itModuleActionAssets == myActionAssets.end())
+    return actionID;
+
+  const auto moduleActionAssets = itModuleActionAssets->second;
+  const auto itActionAssets = moduleActionAssets.find(theInModuleActionID);
+  if (itActionAssets != moduleActionAssets.end() && !itActionAssets->second->myLangDependentAssets.empty()) {
     const auto& ldaMap = itActionAssets->second->myLangDependentAssets;
     if (ldaMap.empty())
       return theInModuleActionID;
@@ -1839,7 +1859,11 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
       QJsonObject object = document.object();
       SUIT_ActionAssets actionAssets;
       for (const QString& actionID : object.keys()) {
-        if (!SUIT_ShortcutMgr::isActionIDValid(actionID)) {
+        const auto moduleIDAndActionID = SUIT_ShortcutMgr::splitIntoModuleIDAndInModuleID(actionID);
+        const QString& moduleID = moduleIDAndActionID.first;
+        const QString& inModuleActionID = moduleIDAndActionID.second;
+
+        if (inModuleActionID.isEmpty()) {
           ShCutDbg("Action asset file \"" + path + "\" contains invalid action ID \"" + actionID + "\".");
           continue;
         }
@@ -1875,10 +1899,11 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
           #endif
         }
 
-        auto itAssets = myActionAssets.find(actionID);
-        if (itAssets == myActionAssets.end()) {
+        auto& moduleActionAssets = myActionAssets[moduleID];
+        auto itAssets = moduleActionAssets.find(inModuleActionID);
+        if (itAssets == moduleActionAssets.end()) {
           auto pAssets = std::shared_ptr<SUIT_ActionAssets>(new SUIT_ActionAssets(actionAssets));
-          itAssets = myActionAssets.emplace(actionID, pAssets).first;
+          itAssets = moduleActionAssets.emplace(inModuleActionID, pAssets).first;
         }
         else
           itAssets->second->merge(actionAssets, true);
@@ -1893,11 +1918,14 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
   #ifdef SHORTCUT_MGR_DBG
   ShCutDbg("Parsed assets: ");
   QJsonObject object;
-  for (const auto& actionIDAndAssets : myActionAssets) {
-    actionIDAndAssets.second->toJSON(object);
-    QJsonDocument doc(object);
-    QString strJson = doc.toJson(QJsonDocument::Indented);
-    ShCutDbg(actionIDAndAssets.first + " : " +  strJson);
+  for (const auto& moduleIDAndAssets : myActionAssets) {
+    for (const auto& actionIDAndAssets : moduleIDAndAssets.second) {
+      actionIDAndAssets.second->toJSON(object);
+      QJsonDocument doc(object);
+      QString strJson = doc.toJson(QJsonDocument::Indented);
+      const QString actionID = SUIT_ShortcutMgr::makeActionID(moduleIDAndAssets.first, actionIDAndAssets.first);
+      ShCutDbg(actionID + " : " +  strJson);
+    }
   }
   #endif
 
@@ -1906,7 +1934,7 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
     const auto assets = std::shared_ptr<SUIT_ActionAssets>(new SUIT_ActionAssets());
     auto& lda = assets->myLangDependentAssets[DEFAULT_LANG];
 
-    if (moduleID == ROOT_MODULE_ID) {
+    if (moduleID == SUIT_ShortcutMgr::ROOT_MODULE_ID) {
       lda.myName = tr("General");
 
       { // Load icon.
@@ -1936,4 +1964,643 @@ void SUIT_ShortcutMgr::setAssetsFromResources(QString theLanguage)
 
     myModuleAssets.emplace(moduleID, std::move(assets));
   }
+}
+
+
+
+SUIT_SentenceMatcher::SUIT_SentenceMatcher()
+{
+  myUseExactWordOrder = false;
+  myUseFuzzyWords = true;
+  myIsCaseSensitive = false;
+}
+
+void SUIT_SentenceMatcher::setUseExactWordOrder(bool theOn)
+{
+  if (myUseExactWordOrder == theOn)
+    return;
+
+  myUseExactWordOrder = theOn;
+  if (theOn) {
+    myPermutatedSentences.clear();
+    myFuzzyPermutatedSentences.clear();
+    return;
+  }
+
+  if (myPermutatedSentences.isEmpty())
+    SUIT_SentenceMatcher::makePermutatedSentences(myWords, myPermutatedSentences);
+
+  if (myUseFuzzyWords && myFuzzyPermutatedSentences.isEmpty())
+    SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
+}
+
+void SUIT_SentenceMatcher::setUseFuzzyWords(bool theOn)
+{
+  if (myUseFuzzyWords == theOn)
+    return;
+
+  myUseFuzzyWords = theOn;
+  if (myWords.isEmpty() || !theOn) {
+    myFuzzyWords.clear();
+    myFuzzyPermutatedSentences.clear();
+    return;
+  }
+
+  myFuzzyWords.clear();
+  SUIT_SentenceMatcher::makeFuzzyWords(myWords, myFuzzyWords);
+
+  if (!myUseExactWordOrder) {
+    myFuzzyPermutatedSentences.clear();
+    SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
+  }
+}
+
+void SUIT_SentenceMatcher::setCaseSensitive(bool theOn)
+{
+  myIsCaseSensitive = theOn;
+}
+
+void SUIT_SentenceMatcher::setQuery(QString theQuery)
+{
+  theQuery = theQuery.simplified();
+  if (theQuery == myQuery)
+    return;
+
+  myQuery = theQuery;
+  myWords = theQuery.split(" ", QString::SkipEmptyParts);
+
+  { // Set permutated sentences.
+    myPermutatedSentences.clear();
+    if (!myUseExactWordOrder)
+      SUIT_SentenceMatcher::makePermutatedSentences(myWords, myPermutatedSentences);
+  }
+
+  // Set fuzzy words and sentences.
+  myFuzzyWords.clear();
+  myFuzzyPermutatedSentences.clear();
+
+  if (myUseFuzzyWords) {
+    SUIT_SentenceMatcher::makeFuzzyWords(myWords, myFuzzyWords);
+    if (!myUseExactWordOrder)
+      SUIT_SentenceMatcher::makePermutatedSentences(myFuzzyWords, myFuzzyPermutatedSentences);
+  }
+}
+
+double SUIT_SentenceMatcher::match(const QString& theInputString) const
+{
+  int n = 0;
+  if (myUseExactWordOrder) {
+    n = SUIT_SentenceMatcher::match(theInputString, myWords, myIsCaseSensitive);
+    if (n != theInputString.length() && myUseFuzzyWords) {
+      const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyWords, myIsCaseSensitive);
+      if (nFuzzy > n)
+        n = nFuzzy;
+    }
+  }
+  else /* if match with permutated query sentences */ {
+    n = SUIT_SentenceMatcher::match(theInputString, myPermutatedSentences, myIsCaseSensitive);
+    if (n != theInputString.length() && myUseFuzzyWords) {
+      const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyPermutatedSentences, myIsCaseSensitive);
+      if (nFuzzy > n)
+        n = nFuzzy;
+    }
+  }
+
+  if (n <= 0)
+    return std::numeric_limits<double>::infinity();
+
+  const auto strLength = theInputString.length() > myQuery.length() ? theInputString.length() : myQuery.length();
+
+  if (n > strLength)
+    return 0; // Exact match or almost exact.
+
+  return double(strLength - n);
+}
+
+QString SUIT_SentenceMatcher::toString() const
+{
+  QString res = QString("myUseExactWordOrder: ") + (myUseExactWordOrder ? "true" : "false") + ";\n";
+  res += QString("myUseFuzzyWords: ") + (myUseFuzzyWords ? "true" : "false") + ";\n";
+  res += QString("myIsCaseSensitive: ") + (myIsCaseSensitive ? "true" : "false") + ";\n";
+  res += QString("myQuery: ") + myQuery + ";\n";
+  res += QString("myWords: ") + myWords.join(", ") + ";\n";
+  res += QString("myFuzzyWords: ") + myFuzzyWords.join(", ") + ";\n";
+
+  res += "myPermutatedSentences:\n";
+  for (const auto& sentence : myPermutatedSentences) {
+    res += "\t" + sentence.join(", ") + ";\n";
+  }
+
+  res += "myFuzzyPermutatedSentences:\n";
+  for (const auto& sentence : myFuzzyPermutatedSentences) {
+    res += "\t" + sentence.join(", ") + ";\n";
+  }
+
+  res += ".";
+  return res;
+}
+
+/*static*/ bool SUIT_SentenceMatcher::makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences)
+{
+  theSentences.clear();
+  theSentences.push_back(theWords);
+  QStringList nextPerm = theWords;
+  QStringList prevPerm = theWords;
+
+  bool hasNextPerm = true;
+  bool hasPrevPerm = true;
+
+  while (hasNextPerm || hasPrevPerm) {
+    if (hasNextPerm)
+      hasNextPerm = std::next_permutation(nextPerm.begin(), nextPerm.end());
+
+    if (hasNextPerm && !theSentences.contains(nextPerm))
+      theSentences.push_back(nextPerm);
+
+    if (hasPrevPerm)
+      hasPrevPerm = std::prev_permutation(prevPerm.begin(), prevPerm.end());
+
+    if (hasPrevPerm && !theSentences.contains(prevPerm))
+      theSentences.push_back(prevPerm);
+  }
+
+  return theSentences.size() > 1;
+}
+
+/*static*/ void SUIT_SentenceMatcher::makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords)
+{
+  theFuzzyWords.clear();
+  for (const QString& word : theWords) {
+    QString fuzzyWord;
+    for (int i = 0; i < word.size(); i++) {
+      fuzzyWord += word[i];
+      fuzzyWord += "\\w*";
+    }
+    theFuzzyWords.push_back(fuzzyWord);
+  }
+}
+
+/*static*/ int SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive)
+{
+  const QRegExp regExp("^" + theSentence.join("\\w*\\W+"), theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+  regExp.indexIn(theInputString);
+  const int matchMetrics = regExp.matchedLength();
+  return matchMetrics > 0 ? matchMetrics : 0;
+}
+
+/*static*/ int SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive)
+{
+  int res = 0;
+  for (const QStringList& sentence : theSentences) {
+    const int matchMetrics = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, sentence, theCaseSensitive);
+    if (matchMetrics > res) {
+      res = matchMetrics;
+      if (res == theInputString.length())
+        return res;
+    }
+  }
+  return res;
+}
+
+/*static*/ int SUIT_SentenceMatcher::matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive)
+{
+  int res = 0;
+  for (const QString& word : theWords) {
+    const auto regExp = QRegExp(word, theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+    regExp.indexIn(theInputString);
+    const int matchMetrics = regExp.matchedLength();
+    // The same input word can be counted multiple times. Nobody cares.
+    if (matchMetrics > 0)
+      res += matchMetrics;
+  }
+  return res;
+}
+
+/*static*/ int SUIT_SentenceMatcher::match(
+  const QString& theInputString,
+  const QStringList& theSentence,
+  bool theCaseSensitive
+) {
+  int res = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, theSentence, theCaseSensitive);
+  if (res == theInputString.length())
+    return res;
+
+  const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentence, theCaseSensitive);
+  if (matchMetrics > res)
+    res = matchMetrics;
+
+  return res;
+}
+
+/*static*/ int SUIT_SentenceMatcher::match(
+  const QString& theInputString,
+  const QList<QStringList>& theSentences,
+  bool theCaseSensitive
+) {
+  int res = SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(theInputString, theSentences, theCaseSensitive);
+  if (res == theInputString.length())
+    return res;
+
+  if (theSentences.size() > 0) {
+    const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentences[0], theCaseSensitive);
+    if (matchMetrics > res)
+      res = matchMetrics;
+  }
+
+  return res;
+}
+
+
+SUIT_ActionSearcher::AssetsAndSearchData::AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets, double theMatchMetrics)
+: myAssets(theAssets), myMatchMetrics(theMatchMetrics)
+{
+  if (theMatchMetrics < 0) {
+    myMatchMetrics = std::numeric_limits<double>::infinity();
+    ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
+  }
+}
+
+void SUIT_ActionSearcher::AssetsAndSearchData::setMatchMetrics(double theMatchMetrics)
+{
+  if (theMatchMetrics < 0) {
+    myMatchMetrics = std::numeric_limits<double>::infinity();
+    ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
+    return;
+  }
+
+  myMatchMetrics = theMatchMetrics;
+}
+
+void SUIT_ActionSearcher::AssetsAndSearchData::toJSON(QJsonObject& oJsonObject) const
+{
+  oJsonObject["myMatchMetrics"] = myMatchMetrics;
+
+  if (myAssets) {
+    QJsonObject assetsJSON;
+    myAssets->toJSON(assetsJSON);
+    oJsonObject["myAssets"] = assetsJSON;
+  }
+}
+
+QString SUIT_ActionSearcher::AssetsAndSearchData::toString() const
+{
+  QJsonObject json;
+  toJSON(json);
+  QJsonDocument doc(json);
+  return QString(doc.toJson(QJsonDocument::Indented));
+}
+
+SUIT_ActionSearcher::SUIT_ActionSearcher()
+{
+  myIncludedModuleIDs = { SUIT_ShortcutMgr::ROOT_MODULE_ID };
+  myIncludeDisabledActions = false;
+  myFieldsToMatch = { SUIT_ActionSearcher::MatchField::Name, SUIT_ActionSearcher::MatchField::ToolTip };
+  myMatcher.setCaseSensitive(false);
+  myMatcher.setUseExactWordOrder(false);
+  myMatcher.setUseFuzzyWords(true);
+}
+
+bool SUIT_ActionSearcher::setIncludedModuleIDs(std::set<QString> theIncludedModuleIDs)
+{
+  ShCutDbg("SUIT_ActionSearcher::setIncludedModuleIDs");
+
+  if (myIncludedModuleIDs == theIncludedModuleIDs)
+    return false;
+
+  myIncludedModuleIDs = theIncludedModuleIDs;
+
+  bool res = false;
+  // Erase search results from excluded modules. Erase IDs of modules, which are already in search results, from theIncludedModuleIDs.
+  for (auto itFound = mySearchResults.begin(); itFound != mySearchResults.end(); ) {
+    const auto itModuleID = theIncludedModuleIDs.find(itFound->first);
+    if (itModuleID == theIncludedModuleIDs.end()) {
+      itFound = mySearchResults.erase(itFound);
+      res = true;
+    }
+    else {
+      itFound++;
+      theIncludedModuleIDs.erase(itModuleID);
+    }
+  }
+
+  // Filter assets of added modules.
+  const auto& allAssets = SUIT_ShortcutMgr::get()->getActionAssets();
+  for (const auto& moduleIDAndAssets : allAssets) {
+    const QString& moduleID = moduleIDAndAssets.first;
+    const auto& actionIDsAndAssets = moduleIDAndAssets.second;
+    if (theIncludedModuleIDs.find(moduleID) == theIncludedModuleIDs.end())
+      continue;
+
+    for (const auto& actionIDAndAssets : actionIDsAndAssets) {
+      const QString& inModuleActionID = actionIDAndAssets.first;
+      const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+      if (matchMetrics < std::numeric_limits<double>::infinity()) {
+        mySearchResults[moduleID][inModuleActionID] = SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics);
+        res = true;
+      }
+    }
+  }
+
+  ShCutDbg() && ShCutDbg(toString());
+
+  return res;
+}
+
+bool SUIT_ActionSearcher::includeDisabledActions(bool theOn)
+{
+  ShCutDbg("SUIT_ActionSearcher::includeDisabledActions");
+
+  if (myIncludeDisabledActions == theOn)
+    return false;
+
+  myIncludeDisabledActions = theOn;
+
+  bool res;
+  if (myIncludeDisabledActions)
+    res = extendResults();
+  else
+    res = filterResults().first;
+
+  ShCutDbg() && ShCutDbg(toString());
+  return res;
+}
+
+bool SUIT_ActionSearcher::setFieldsToMatch(const std::set<SUIT_ActionSearcher::MatchField>& theFields)
+{
+  if (myFieldsToMatch == theFields)
+    return false;
+
+  if (theFields.empty()) {
+    myFieldsToMatch = theFields;
+    mySearchResults.clear();
+    return true;
+  }
+
+  bool narrows = true;
+  for (const SUIT_ActionSearcher::MatchField field : theFields) {
+    if (myFieldsToMatch.find(field) == myFieldsToMatch.end()) {
+      narrows = false;
+      break;
+    }
+  }
+
+  bool extends = true;
+  for (const SUIT_ActionSearcher::MatchField field : myFieldsToMatch) {
+    if (theFields.find(field) == theFields.end()) {
+      extends = false;
+      break;
+    }
+  }
+
+  myFieldsToMatch = theFields;
+
+  bool res;
+  if (narrows)
+    res = filterResults().first;
+  else if (extends)
+    res = extendResults();
+  else
+    res = filter().first;
+
+  ShCutDbg() && ShCutDbg(toString());
+  return res;
+}
+
+bool SUIT_ActionSearcher::setCaseSensitive(bool theOn)
+{
+  if (myMatcher.isCaseSensitive() == theOn)
+    return false;
+
+  myMatcher.setCaseSensitive(theOn);
+
+  bool res;
+  if (theOn)
+    res = filterResults().first;
+  else
+    res = extendResults();
+
+  ShCutDbg() && ShCutDbg(toString());
+  return res;
+}
+
+bool SUIT_ActionSearcher::setQuery(const QString& theQuery)
+{
+  ShCutDbg("SUIT_ActionSearcher::setQuery");
+
+  if (theQuery.simplified() == myMatcher.getQuery().simplified())
+    return false;
+
+  myMatcher.setQuery(theQuery);
+  bool res = filter().first;
+  ShCutDbg() && ShCutDbg(toString());
+  return res;
+}
+
+const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& SUIT_ActionSearcher::getSearchResults() const
+{
+  return mySearchResults;
+}
+
+std::pair<bool, bool> SUIT_ActionSearcher::filter()
+{
+  ShCutDbg("SUIT_ActionSearcher::filter()");
+
+  auto res = std::pair<bool, bool>(false, false);
+
+  for (const auto& moduleIDAndAssets : SUIT_ShortcutMgr::get()->getActionAssets()) {
+    const auto& moduleID = moduleIDAndAssets.first;
+    if (myIncludedModuleIDs.find(moduleID) == myIncludedModuleIDs.end())
+      continue;
+
+    const auto& actionIDsAndAssets = moduleIDAndAssets.second;
+
+    auto itFoundModuleIDAndAssets = mySearchResults.find(moduleID);
+    for (const auto& actionIDAndAssets : actionIDsAndAssets) {
+      const QString& inModuleActionID = actionIDAndAssets.first;
+
+      if (itFoundModuleIDAndAssets != mySearchResults.end()) {
+        auto& foundActionIDsAndAssets = itFoundModuleIDAndAssets->second;
+        auto itFoundActionIDAndAssets = foundActionIDsAndAssets.find(inModuleActionID);
+        if (itFoundActionIDAndAssets != foundActionIDsAndAssets.end()) {
+          // Action is already in search results.
+          SUIT_ActionSearcher::AssetsAndSearchData& aAndD = itFoundActionIDAndAssets->second;
+          const double matchMetrics = matchAction(moduleID, inModuleActionID, aAndD.myAssets);
+          if (matchMetrics < std::numeric_limits<double>::infinity()) {
+            if (matchMetrics != aAndD.matchMetrics()) {
+              aAndD.setMatchMetrics(matchMetrics);
+              res.second = true;
+            }
+          }
+          else /* if n == 0 */ {
+            foundActionIDsAndAssets.erase(itFoundActionIDAndAssets);
+            res.first = true;
+          }
+          continue;
+        }
+      }
+
+      const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+      if (matchMetrics < std::numeric_limits<double>::infinity()) {
+        if (itFoundModuleIDAndAssets == mySearchResults.end())
+          itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
+
+        itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
+        res.first = true;
+      }
+    }
+  }
+
+  return res;
+}
+
+std::pair<bool, bool> SUIT_ActionSearcher::filterResults()
+{
+  auto res = std::pair<bool, bool>(false, false);
+
+  for (auto itFoundModuleIDAndAssets = mySearchResults.begin(); itFoundModuleIDAndAssets != mySearchResults.end(); ) {
+    const QString& moduleID = itFoundModuleIDAndAssets->first;
+    auto& actionIDsAndAssets = itFoundModuleIDAndAssets->second;
+    for (auto itActionIDAndAssets = actionIDsAndAssets.begin(); itActionIDAndAssets != actionIDsAndAssets.end(); ) {
+      const QString& inModuleActionID = itActionIDAndAssets->first;
+      SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = itActionIDAndAssets->second;
+      const double matchMetrics = matchAction(moduleID, inModuleActionID, assetsAndSearchData.myAssets);
+      if (matchMetrics == std::numeric_limits<double>::infinity()) {
+        itActionIDAndAssets = actionIDsAndAssets.erase(itActionIDAndAssets);
+        res.first = true;
+      }
+      else {
+        if (assetsAndSearchData.matchMetrics() != matchMetrics) {
+          assetsAndSearchData.setMatchMetrics(matchMetrics);
+          res.second = true;
+        }
+        itActionIDAndAssets++;
+      }
+    }
+
+    if (actionIDsAndAssets.empty())
+      itFoundModuleIDAndAssets = mySearchResults.erase(itFoundModuleIDAndAssets);
+    else
+      itFoundModuleIDAndAssets++;
+  }
+
+  return res;
+}
+
+bool SUIT_ActionSearcher::extendResults()
+{
+  ShCutDbg("SUIT_ActionSearcher::extendResults()");
+
+  bool res = false;
+  for (const auto& moduleIDAndAssets : SUIT_ShortcutMgr::get()->getActionAssets()) {
+    const auto& moduleID = moduleIDAndAssets.first;
+    if (myIncludedModuleIDs.find(moduleID) == myIncludedModuleIDs.end())
+      continue;
+
+    const auto& actionIDsAndAssets = moduleIDAndAssets.second;
+
+    auto itFoundModuleIDAndAssets = mySearchResults.find(moduleID);
+    for (const auto& actionIDAndAssets : actionIDsAndAssets) {
+      const QString& inModuleActionID = actionIDAndAssets.first;
+
+      if (itFoundModuleIDAndAssets != mySearchResults.end()) {
+        const auto& foundActionIDsAndAssets = itFoundModuleIDAndAssets->second;
+        if (foundActionIDsAndAssets.find(inModuleActionID) != foundActionIDsAndAssets.end())
+          continue; // Action is already in search results.
+      }
+
+      ShCutDbg() && ShCutDbg("SUIT_ActionSearcher::extendResults(): " + moduleID + "/" + inModuleActionID + "." );
+      const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+      if (matchMetrics < std::numeric_limits<double>::infinity()) {
+        ShCutDbg("SUIT_ActionSearcher::extendResults(): match, metrics = " + QString::fromStdString(std::to_string(matchMetrics)));
+        if (itFoundModuleIDAndAssets == mySearchResults.end())
+          itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
+
+        itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
+        res = true;
+      }
+    }
+  }
+  return res;
+}
+
+double SUIT_ActionSearcher::matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets)
+{
+  if (!theAssets) {
+    ShCutDbg("WARNING: SUIT_ActionSearcher::matchAction: theAssets is nullptr.");
+    return std::numeric_limits<double>::infinity();
+  }
+
+  if (!myIncludeDisabledActions) {
+    const auto& actions = SUIT_ShortcutMgr::get()->getActions(theModuleID, theInModuleActionID);
+    const bool actionEnabled = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); } ) != actions.end();
+    if (!actionEnabled)
+      return std::numeric_limits<double>::infinity();
+  }
+
+  double res = std::numeric_limits<double>::infinity();
+
+  for (const auto& langAndLDA : theAssets->myLangDependentAssets) {
+    if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ToolTip) != myFieldsToMatch.end()) {
+      const double matchMetrics = myMatcher.match(langAndLDA.second.myToolTip);
+      if (matchMetrics < res)
+        res = matchMetrics;
+    }
+
+    if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::Name) != myFieldsToMatch.end()) {
+      const double matchMetrics = myMatcher.match(langAndLDA.second.myName);
+      if (matchMetrics < res)
+        res = matchMetrics;
+    }
+  }
+
+  if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ID) != myFieldsToMatch.end()) {
+    const double matchMetrics = myMatcher.match(SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID));
+    if (matchMetrics < res)
+        res = matchMetrics;
+  }
+
+  if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::KeySequence) != myFieldsToMatch.end()) {
+    const QString keySequence = SUIT_ShortcutMgr::get()->getKeySequence(theModuleID, theInModuleActionID).toString();
+    const double matchMetrics = myMatcher.match(keySequence);
+    if (matchMetrics < res)
+        res = matchMetrics;
+  }
+
+  return res;
+}
+
+QString SUIT_ActionSearcher::toString() const
+{
+  QString res;
+
+  res += "myMatcher: {\n";
+  res += myMatcher.toString();
+  res += "};\n";
+
+  res += "myIncludedModuleIDs: ";
+  for (const QString& moduleID : myIncludedModuleIDs) {
+    res += moduleID + ", ";
+  }
+  res += ";\n";
+
+  res += QString("myIncludeDisabledActions: ") + (myIncludeDisabledActions ? "true" : "false") + ";\n";
+
+  res += "myFieldsToMatch: ";
+  for (const auto& field : myFieldsToMatch) {
+    res += QString::number(int(field)) + ", ";
+  }
+  res += ";\n";
+
+  res += "mySearchResults:\n";
+  for (const auto& moduleIDAndAssets : mySearchResults ) {
+    res += "\tModule ID: " + moduleIDAndAssets.first + ":\n";
+    for (const auto& actionIDAndAssets : moduleIDAndAssets.second) {
+      const auto& assetsAndSearchData = actionIDAndAssets.second;
+      res += "\t\tAction ID: " + actionIDAndAssets.first + ": {";
+      res += "\t\t: " + actionIDAndAssets.second.toString();
+      res += "\t\t}";
+    }
+  }
+
+  return res;
 }
\ No newline at end of file
index c8debfb81b1094fb5681980cfbe95fb7954c80b6..27504d976c4fac35e105baa785eb3e7cb629d5a7 100644 (file)
 
 #include <QObject>
 #include <QString>
+#include <QStringList>
 #include <QIcon>
 #include <map>
 #include <set>
 #include <memory>
 #include <utility>
+#include <limits>
 
 class QAction;
 class QtxAction;
@@ -238,6 +240,8 @@ protected:
   virtual ~SUIT_ShortcutMgr();
 
 public:
+  static const QString ROOT_MODULE_ID;
+
   /*! \brief Create new singleton-instance of shortcut manager, if it has not been created. */
   static void Init();
 
@@ -285,7 +289,7 @@ public:
   \returns {assetsExist, assets}. */
   static std::pair<bool, SUIT_ActionAssets> getActionAssetsFromResources(const QString& theActionID);
 
-  /*! \returns Language being set in resource manager. */
+  /*! \returns Language, which is set in resource manager. */
   static QString getLang();
 
 
@@ -357,8 +361,12 @@ public:
   if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
   std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
 
+  /*! \returns assets, which describe module's header, not its content. */
   std::shared_ptr<const SUIT_ActionAssets> getModuleAssets(const QString& theModuleID) const;
 
+  /*! \returns assets, which describe modules' headers, not their content. */
+  std::map<QString, std::shared_ptr<SUIT_ActionAssets>> getModuleAssets() const { return myModuleAssets; }
+
   /*! \brief Retrieves module name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
   QString getModuleName(const QString& theModuleID, const QString& theLang = "") const;
 
@@ -366,6 +374,8 @@ public:
 
   std::shared_ptr<const SUIT_ActionAssets> getActionAssets(const QString& theActionID) const;
 
+  std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> getActionAssets() const { return myActionAssets; }
+
   /*! \brief Retrieves action name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
   QString getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang = "") const;
 
@@ -462,13 +472,179 @@ private:
   Sets of moduleIDs and inModuleActionIDs may NOT be equal for myActions and myShortcutContainer.
   */
 
-  /* {actionID, assets}[] */
-  std::map<QString, std::shared_ptr<SUIT_ActionAssets>> myActionAssets;
+  /* { moduleID, {inModuleActionID, assets}[] }[] */
+  std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> myActionAssets;
 
   /* {moduleID, assets}[] */
   mutable std::map<QString, std::shared_ptr<SUIT_ActionAssets>> myModuleAssets;
 };
 
+
+/*!
+  \class SUIT_SentenceMatcher
+  \brief Approximate string matcher, treats strings as sentences composed of words.
+*/
+class SUIT_EXPORT SUIT_SentenceMatcher
+{
+public:
+  /*! Default config:
+    Exact word order = false;
+    Fuzzy words = true;
+    Case sensitive = false;
+    Query = ""; // matches nothing.
+  */
+  SUIT_SentenceMatcher();
+
+  void setUseExactWordOrder(bool theOn);
+  void setUseFuzzyWords(bool theOn);
+  void setCaseSensitive(bool theOn);
+  inline bool isCaseSensitive() const { return myIsCaseSensitive; };
+
+  /*! \param theQuery should not be regex. */
+  void setQuery(QString theQuery);
+
+  inline const QString& getQuery() const { return myQuery; };
+
+  /*! \returns match metrics. The metrics >= 0. INF means mismatch.
+  The class is unable to differentiate exact match with some approximate matches! */
+  double match(const QString& theInputString) const;
+
+  /** \brief For debug. */
+  QString toString() const;
+
+private:
+  static bool makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences);
+  static void makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords);
+
+  /*! \returns number of characters in matched words. The number >= 0. */
+  static int matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive);
+  /*! \returns number of characters in matched words. The number >= 0. */
+  static int matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive);
+
+  /*! \returns number of characters in matched words. The number >= 0. */
+  static int matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive);
+
+  /*! \returns number of characters in matched words. The number >= 0. */
+  static int match(
+    const QString& theInputString,
+    const QStringList& theSentence,
+    bool theCaseSensitive
+  );
+
+  /*! \returns number of characters in matched words. The number >= 0. */
+  static int match(
+    const QString& theInputString,
+    const QList<QStringList>& theSentences,
+    bool theCaseSensitive
+  );
+
+  bool myUseExactWordOrder; // If false, try to match with sentences, composed of query's words in different orders.
+  bool myUseFuzzyWords; // Try to match with sentences, composed of query's truncated words.
+  bool myIsCaseSensitive;
+  QString myQuery;
+
+  QStringList myWords; // It is also original search sentence.
+  QList<QStringList> myPermutatedSentences;
+
+  QStringList myFuzzyWords; // Regexes.
+  QList<QStringList> myFuzzyPermutatedSentences;
+};
+
+
+/*!
+  \class SUIT_ActionSearcher
+  \brief Searches in data, provided in action asset files and shortcut preferences.
+*/
+class SUIT_EXPORT SUIT_ActionSearcher
+{
+public:
+  enum MatchField {
+    ID,
+    Name,
+    ToolTip,
+    KeySequence
+  };
+
+  class AssetsAndSearchData
+  {
+  public:
+    AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets = nullptr, double theMatchMetrics = std::numeric_limits<double>::infinity());
+
+    void setMatchMetrics(double theMatchMetrics);
+    double matchMetrics() const { return myMatchMetrics; };
+
+    std::shared_ptr<const SUIT_ActionAssets> myAssets;
+
+    void toJSON(QJsonObject& oJsonObject) const;
+    QString toString() const;
+
+  private:
+    /*! \brief Ideally it should be number of weighted character permutations. Now it is just a number of characters in unmatched words. */
+    double myMatchMetrics;
+  };
+
+  /*! Default config:
+      Included modules' IDs = { ROOT_MODULE_ID };
+      Include disabled actions = false;
+      Fields to match = { Name, Tooltip };
+      Case sensitive = false;
+      Fuzzy matching = true;
+      Query = ""; // matches everything.
+  */
+  SUIT_ActionSearcher();
+  SUIT_ActionSearcher(const SUIT_ActionSearcher&) = delete;
+  SUIT_ActionSearcher& operator=(const SUIT_ActionSearcher&) = delete;
+  virtual ~SUIT_ActionSearcher() = default;
+
+  /*! \returns true, if set of results is changed. */
+  bool setIncludedModuleIDs(std::set<QString> theIncludedModuleIDs);
+
+  /*! \returns true, if set of results is changed. */
+  bool includeDisabledActions(bool theOn);
+  inline bool areDisabledActionsIncluded() const {return myIncludeDisabledActions;};
+
+  /*! \returns true, if set of results is changed. */
+  bool setFieldsToMatch(const std::set<SUIT_ActionSearcher::MatchField>& theFields);
+
+  /*! \returns true, if set of results is changed. */
+  bool setCaseSensitive(bool theOn);
+
+  /*! \returns true, if set of results is changed. */
+  bool setQuery(const QString& theQuery);
+  inline const QString& getQuery() const {return myMatcher.getQuery();};
+
+  const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& getSearchResults() const;
+
+
+private:
+  /*! \brief Applies filter to all actions, provided in asset files for SUIT_ShortcutMgr.
+  \returns { true, _ } if set of results is changed; { _ , true } if matching metrics is changed for at least one result. */
+  std::pair<bool, bool> filter();
+
+  /*! \brief Applies filter to search results only.
+  \returns { true, _ } if set of results is shrunk; { _ , true } if matching metrics is changed for at least one result. */
+  std::pair<bool, bool> filterResults();
+
+  /*! \brief Applies filter only to actions, which are not in search results.
+  \returns True, if set of results is extended. */
+  bool extendResults();
+
+  double matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets);
+
+  QString toString() const;
+
+
+  std::set<QString> myIncludedModuleIDs;
+  bool myIncludeDisabledActions;
+
+  std::set<SUIT_ActionSearcher::MatchField> myFieldsToMatch;
+  SUIT_SentenceMatcher myMatcher;
+
+  /* { moduleID, {inModuleActionID, assetsAndSearchData}[] }[]. */
+  std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>> mySearchResults;
+};
+
+
 #if defined WIN32
 #pragma warning( default: 4251 )
 #endif
diff --git a/src/SUIT/SUIT_ShortcutTree.cxx b/src/SUIT/SUIT_ShortcutTree.cxx
new file mode 100644 (file)
index 0000000..fd5d66b
--- /dev/null
@@ -0,0 +1,880 @@
+// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "SUIT_ShortcutTree.h"
+
+#include <QWidget>
+#include <QLayout>
+#include <QList>
+#include <QMap>
+
+#include <QToolButton>
+#include <QLineEdit>
+#include <QLabel>
+#include <QTableWidgetItem>
+#include <QTextEdit>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QBrush>
+#include <QColor>
+#include <QHeaderView>
+
+#include <QKeyEvent>
+#include <QKeySequence>
+#include <QCollator>
+
+#include <algorithm>
+
+
+#define COLUMN_SIZE  500
+
+
+SUIT_KeySequenceEdit::SUIT_KeySequenceEdit(QWidget* parent)
+: QFrame(parent)
+{
+  initialize();
+  myKeySequenceLineEdit->installEventFilter(this);
+}
+
+/*! \brief Set a key sequence to edit. */
+void SUIT_KeySequenceEdit::setConfirmedKeySequence(const QKeySequence& theKeySequence)
+{
+  myConfirmedKeySequenceString = theKeySequence.toString();
+  myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
+  myPrevKeySequenceString = myConfirmedKeySequenceString;
+}
+
+void SUIT_KeySequenceEdit::setEditedKeySequence(const QKeySequence& theKeySequence)
+{
+  const QString keySequenceString = theKeySequence.toString();
+  myKeySequenceLineEdit->setText(keySequenceString);
+  myPrevKeySequenceString = keySequenceString;
+}
+
+QKeySequence SUIT_KeySequenceEdit::editedKeySequence() const
+{
+  return QKeySequence::fromString(myKeySequenceLineEdit->text());
+}
+
+/*! \returns true, if the edited key sequence differs from confirmed one. */
+bool SUIT_KeySequenceEdit::isKeySequenceModified() const
+{
+  return QKeySequence(myConfirmedKeySequenceString) != editedKeySequence();
+}
+
+/*! \brief Set confirmed key sequence to line editor. */
+void SUIT_KeySequenceEdit::restoreKeySequence()
+{
+  myKeySequenceLineEdit->setText(myConfirmedKeySequenceString);
+  myPrevKeySequenceString = myConfirmedKeySequenceString;
+}
+
+/*!
+  \brief Gets the key sequence from keys that were pressed
+  \param e a key event
+  \returns a string representation of the key sequence
+*/
+/*static*/ QString SUIT_KeySequenceEdit::parseEvent(QKeyEvent* e)
+{
+  bool isShiftPressed = e->modifiers() & Qt::ShiftModifier;
+  bool isControlPressed = e->modifiers() & Qt::ControlModifier;
+  bool isAltPressed = e->modifiers() & Qt::AltModifier;
+  bool isMetaPressed = e->modifiers() & Qt::MetaModifier;
+  bool isModifiersPressed = isControlPressed || isAltPressed || isMetaPressed; // Do not treat Shift alone as a modifier!
+  int result=0;
+  if(isControlPressed)
+    result += Qt::CTRL;
+  if(isAltPressed)
+    result += Qt::ALT;
+  if(isShiftPressed)
+    result += Qt::SHIFT;
+  if(isMetaPressed)
+    result += Qt::META;
+
+  int aKey = e->key();
+  if ((isValidKey(aKey) && isModifiersPressed) || ((aKey >= Qt::Key_F1) && (aKey <= Qt::Key_F12)))
+    result += aKey;
+
+  return QKeySequence(result).toString();
+}
+
+/*!
+  \brief Check if the key event contains a 'valid' key
+  \param theKey the code of the key
+  \returns \c true if the key is 'valid'
+*/
+/*static*/ bool SUIT_KeySequenceEdit::isValidKey(int theKey)
+{
+  if ( theKey == Qt::Key_Underscore || theKey == Qt::Key_Escape ||
+     ( theKey >= Qt::Key_Backspace && theKey <= Qt::Key_Delete ) ||
+     ( theKey >= Qt::Key_Home && theKey <= Qt::Key_PageDown ) ||
+     ( theKey >= Qt::Key_F1 && theKey <= Qt::Key_F12 )  ||
+     ( theKey >= Qt::Key_Space && theKey <= Qt::Key_Asterisk ) ||
+     ( theKey >= Qt::Key_Comma && theKey <= Qt::Key_AsciiTilde ) )
+    return true;
+  return false;
+}
+
+/*! \brief Called when "Clear" button is clicked. */
+void SUIT_KeySequenceEdit::onClear()
+{
+  myKeySequenceLineEdit->setText("");
+  myPrevKeySequenceString = "";
+  emit editingFinished();
+}
+
+/*! \brief Called when myKeySequenceLineEdit loses focus. */
+void SUIT_KeySequenceEdit::onEditingFinished()
+{
+  if (myKeySequenceLineEdit->text().endsWith("+"))
+    myKeySequenceLineEdit->setText(myPrevKeySequenceString);
+  else
+    myPrevKeySequenceString = myKeySequenceLineEdit->text();
+    emit editingFinished();
+}
+
+/*!
+  \brief Custom event filter.
+  \param obj event receiver object
+  \param event event
+  \returns \c true if further event processing should be stopped
+*/
+bool SUIT_KeySequenceEdit::eventFilter(QObject* theObject, QEvent* theEvent)
+{
+  if (theObject == myKeySequenceLineEdit) {
+    if (theEvent->type() == QEvent::KeyPress) {
+      QKeyEvent* keyEvent = static_cast<QKeyEvent*>(theEvent);
+      QString text = parseEvent(keyEvent);
+      if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace)
+        myKeySequenceLineEdit->setText("");
+      if (!text.isEmpty())
+        myKeySequenceLineEdit->setText(text);
+
+      emit editingStarted();
+      return true;
+    }
+    if (theEvent->type() == QEvent::KeyRelease) {
+      onEditingFinished();
+      return true;
+    }
+  }
+  return false;
+}
+
+/*
+  \brief Perform internal intialization.
+*/
+void SUIT_KeySequenceEdit::initialize()
+{
+  static const int PIXMAP_SIZE = 30;
+
+  QHBoxLayout* base = new QHBoxLayout( this );
+  base->setMargin(0);
+  base->setSpacing(5);
+
+  base->addWidget(myKeySequenceLineEdit = new QLineEdit(this));
+  setFocusProxy(myKeySequenceLineEdit);
+
+  QToolButton* clearBtn = new QToolButton();
+  auto clearPixmap = QPixmap(":/images/shortcut_disable.svg");
+  clearPixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+  clearBtn->setIcon(clearPixmap);
+  clearBtn->setToolTip(tr("Disable shortcut."));
+  base->addWidget(clearBtn);
+
+  QToolButton* restoreBtn = new QToolButton();
+  auto restorePixmap = QPixmap(":/images/shortcut_restore.svg");
+  restorePixmap.scaled(QSize(PIXMAP_SIZE, PIXMAP_SIZE), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+  restoreBtn->setIcon(restorePixmap);
+  restoreBtn->setToolTip(tr("Restore the currently applied key sequence."));
+  base->addWidget(restoreBtn);
+
+  myKeySequenceLineEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  clearBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+  restoreBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+  connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear()));
+  connect(restoreBtn, SIGNAL(clicked()), this, SIGNAL(restoreFromShortcutMgrClicked()));
+  connect(myKeySequenceLineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
+}
+
+
+/*! \param theParent must not be nullptr. */
+SUIT_EditKeySequenceDialog::SUIT_EditKeySequenceDialog(SUIT_ShortcutTree* theParent)
+: QDialog(theParent)
+{
+  setMinimumWidth(500);
+  setWindowTitle(tr("Change key sequence"));
+  QVBoxLayout* layout = new QVBoxLayout(this);
+  myActionName = new QLabel(this);
+  myActionName->setTextFormat(Qt::RichText);
+  myKeySequenceEdit = new SUIT_KeySequenceEdit(this);
+  myTextEdit = new QTextEdit(this);
+  layout->addWidget(myActionName);
+  layout->addWidget(myKeySequenceEdit);
+  layout->addWidget(myTextEdit);
+  myActionName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myKeySequenceEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+  myTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+  myTextEdit->setReadOnly(true);
+  myTextEdit->setAcceptRichText(true);
+  myTextEdit->setPlaceholderText(tr("No conflicts."));
+  setFocusProxy(myKeySequenceEdit);
+
+  QHBoxLayout* buttonLayout = new QHBoxLayout(this);
+  layout->addLayout(buttonLayout);
+  QPushButton* confirmButton = new QPushButton(tr("Confirm"), this);
+  confirmButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+  QPushButton* cancelButton = new QPushButton(tr("Cancel"), this);
+  cancelButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+  buttonLayout->addStretch();
+  buttonLayout->addWidget(confirmButton);
+  buttonLayout->addWidget(cancelButton);
+
+  connect(myKeySequenceEdit, SIGNAL(editingStarted()), this, SLOT(onEditingStarted()));
+  connect(myKeySequenceEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished()));
+  connect(myKeySequenceEdit, SIGNAL(restoreFromShortcutMgrClicked()), this, SLOT(onRestoreFromShortcutMgr()));
+  connect(confirmButton, SIGNAL(clicked()), this, SLOT(onConfirm()));
+  connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
+}
+
+void SUIT_EditKeySequenceDialog::setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID)
+{
+  myModuleID = theModuleID;
+  myInModuleActionID = theInModuleActionID;
+}
+
+const QString& SUIT_EditKeySequenceDialog::moduleID() const { return myModuleID; }
+const QString& SUIT_EditKeySequenceDialog::inModuleActionID() const { return myInModuleActionID; }
+
+void SUIT_EditKeySequenceDialog::setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip)
+{
+  myActionName->setText("<b>" + theModuleName + "</b>&nbsp;&nbsp;" + theActionName);
+  myActionName->setToolTip(theActionToolTip);
+}
+
+void SUIT_EditKeySequenceDialog::setConfirmedKeySequence(const QKeySequence& theSequence)
+{
+  myKeySequenceEdit->setConfirmedKeySequence(theSequence);
+}
+
+QKeySequence SUIT_EditKeySequenceDialog::editedKeySequence() const
+{
+  return myKeySequenceEdit->editedKeySequence();
+}
+
+int SUIT_EditKeySequenceDialog::exec()
+{
+  myKeySequenceEdit->setFocus(Qt::ActiveWindowFocusReason);
+  return QDialog::exec();
+}
+
+void SUIT_EditKeySequenceDialog::onEditingStarted()
+{
+  myTextEdit->setEnabled(false);
+}
+
+void SUIT_EditKeySequenceDialog::onEditingFinished()
+{
+  updateConflictsMessage();
+}
+
+void SUIT_EditKeySequenceDialog::onRestoreFromShortcutMgr()
+{
+  const auto shortcutMgr = SUIT_ShortcutMgr::get();
+  myKeySequenceEdit->setEditedKeySequence(shortcutMgr->getKeySequence(myModuleID, myInModuleActionID));
+  updateConflictsMessage();
+}
+
+/*! Updates message with list of actions, whose shortcuts will be disabled on Confirm. */
+void SUIT_EditKeySequenceDialog::updateConflictsMessage()
+{
+  myTextEdit->setEnabled(true);
+  QTextDocument* doc = myTextEdit->document();
+  if (!doc) {
+    doc = new QTextDocument(myTextEdit);
+    myTextEdit->setDocument(doc);
+  }
+
+  if (!myKeySequenceEdit->isKeySequenceModified()) {
+    doc->clear();
+    return;
+  }
+
+  const QKeySequence newKeySequence = editedKeySequence();
+
+  const auto shortcutTree = static_cast<SUIT_ShortcutTree*>(parentWidget());
+  /** {moduleID, inModuleActionID}[] */
+  std::set<std::pair<QString, QString>> conflicts = shortcutTree->shortcutContainer()->getConflicts(myModuleID, myInModuleActionID, newKeySequence);
+  if (!conflicts.empty()) {
+    const auto shortcutMgr = SUIT_ShortcutMgr::get();
+
+    QString report = "<b>" + tr("These shortcuts will be disabled on confirm:") + "</b>";
+    {
+      report += "<ul>";
+      for (const auto& conflict : conflicts) {
+        const QString conflictingModuleName = shortcutMgr->getModuleName(conflict.first);
+        const QString conflictingActionName = shortcutMgr->getActionName(conflict.first, conflict.second);
+        report += "<li><b>" + conflictingModuleName + "</b>&nbsp;&nbsp;" + conflictingActionName + "</li>";
+      }
+      report += "</ul>";
+    }
+    doc->setHtml(report);
+  }
+  else /* if no conflicts */ {
+    doc->clear();
+  }
+}
+
+void SUIT_EditKeySequenceDialog::onConfirm()
+{
+  if (myKeySequenceEdit->isKeySequenceModified())
+    accept();
+  else
+    reject();
+}
+
+
+/*! \brief Compensates lack of std::distance(), which is introduced in C++17.
+\returns -1, if theIt does not belong to the  */
+template <class Container>
+size_t indexOf(
+  const Container& theContainer,
+  const typename Container::iterator& theIt
+) {
+  auto it = theContainer.begin();
+  size_t distance = 0;
+  while (it != theContainer.end()) {
+    if (it == theIt)
+      return distance;
+
+    it++;
+    distance++;
+  }
+  return -1;
+}
+
+
+/*! \param theContainer Share the same container between several trees,
+to edit them synchronously even without exchange of changes with SUIT_ShortcutMgr.
+Pass nullptr to create non-synchronized tree. */
+SUIT_ShortcutTree::SUIT_ShortcutTree(
+  std::shared_ptr<SUIT_ShortcutContainer> theContainer,
+  QWidget* theParent
+) : QTreeWidget(theParent),
+myShortcutContainer(theContainer ? theContainer : std::shared_ptr<SUIT_ShortcutContainer>(new SUIT_ShortcutContainer())),
+mySortKey(SUIT_ShortcutTree::SortKey::Name), mySortOrder(SUIT_ShortcutTree::SortOrder::Ascending)
+{
+  setColumnCount(2);
+  setSelectionMode(QAbstractItemView::SingleSelection);
+  setColumnWidth(0, COLUMN_SIZE);
+  setSortingEnabled(false); // Items are sorted in the same way, as in ShortcutContainer.
+  header()->setSectionResizeMode(QHeaderView::Interactive);
+  {
+    QMap<int, QString> labelMap;
+    labelMap[SUIT_ShortcutTree::ElementIdx::Name]        = tr("Action");
+    labelMap[SUIT_ShortcutTree::ElementIdx::KeySequence] = tr("Key sequence");
+    setHeaderLabels(labelMap.values());
+  }
+  setExpandsOnDoubleClick(false); // Open shortcut editor on double click instead.
+  setSortingEnabled(false);
+  setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
+  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  myEditDialog = new SUIT_EditKeySequenceDialog(this);
+
+  this->installEventFilter(this);
+  connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
+
+  SUIT_ShortcutTree::instances[myShortcutContainer.get()].emplace(this);
+}
+
+SUIT_ShortcutTree::~SUIT_ShortcutTree()
+{
+  SUIT_ShortcutTree::instances[myShortcutContainer.get()].erase(this);
+  if (SUIT_ShortcutTree::instances[myShortcutContainer.get()].empty())
+    SUIT_ShortcutTree::instances.erase(myShortcutContainer.get());
+}
+
+/*! \brief Copies shortcuts from ShortcutMgr. (Re)displays shortcuts of myModuleIDs. */
+void SUIT_ShortcutTree::setShortcutsFromManager()
+{
+  const auto shortcutMgr = SUIT_ShortcutMgr::get();
+  *myShortcutContainer = shortcutMgr->getShortcutContainer();
+  // nb! ShortcutMgr never removes shortcuts from its container, only disables.
+
+  updateItems(false /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
+}
+
+/*! \brief Copies shortcuts from resources, user files are not accounted. (Re)displays shortcuts of myModuleIDs. */
+void SUIT_ShortcutTree::setDefaultShortcuts()
+{
+  SUIT_ShortcutContainer defaultShortcuts;
+  SUIT_ShortcutMgr::fillContainerFromPreferences(defaultShortcuts, true /*theDefaultOnly*/);
+
+  myShortcutContainer->merge(defaultShortcuts, true /*theOverride*/, true /*theTreatAbsentIncomingAsDisabled*/);
+  // nb! SUIT_ShortcutContainer never erases shortcuts, only disables.
+
+  updateItems(true /*theHighlightModified*/, true /*theUpdateSyncTrees*/);
+}
+
+/*! \brief Applies pending changes to ShortcutMgr. Updates other instances of SUIT_ShortcutTree. */
+void SUIT_ShortcutTree::applyChangesToShortcutMgr()
+{
+  const auto mgr = SUIT_ShortcutMgr::get();
+  mgr->mergeShortcutContainer(*myShortcutContainer);
+
+  // Update non-synchronized with this instances.
+  for (const auto& containerAndSyncTrees : SUIT_ShortcutTree::instances) {
+    if (containerAndSyncTrees.first == myShortcutContainer.get())
+      continue;
+
+    const std::set<SUIT_ShortcutTree*>& syncTrees = containerAndSyncTrees.second;
+    const auto itFirstSyncTree = syncTrees.begin();
+    if (itFirstSyncTree == syncTrees.end())
+      continue;
+
+    (*itFirstSyncTree)->setShortcutsFromManager();
+    const auto editDialog = (*itFirstSyncTree)->myEditDialog;
+    editDialog->setConfirmedKeySequence(mgr->getShortcutContainer().getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
+    editDialog->updateConflictsMessage();
+  }
+}
+
+std::shared_ptr<const SUIT_ShortcutContainer> SUIT_ShortcutTree::shortcutContainer() const
+{
+  return myShortcutContainer;
+}
+
+/*! \brief Does not sort modules. */
+void SUIT_ShortcutTree::sort(SUIT_ShortcutTree::SortKey theKey, SUIT_ShortcutTree::SortOrder theOrder)
+{
+  if (theKey == mySortKey && theOrder == mySortOrder)
+    return;
+
+  mySortKey == theKey;
+  mySortOrder = theOrder;
+
+  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
+    const auto moduleItem = static_cast<SUIT_ShortcutTreeFolder*>(topLevelItem(moduleIdx));
+    const auto sortedChildren = getSortedChildren(moduleItem);
+    moduleItem->takeChildren();
+
+    for (const auto childItem : sortedChildren) {
+      moduleItem->addChild(childItem);
+    }
+  }
+}
+
+/*! \param If theUpdateSyncTrees, trees sharing the same shortcut container are updated. */
+void SUIT_ShortcutTree::updateItems(bool theHighlightModified, bool theUpdateSyncTrees)
+{
+  const auto shortcutMgr = SUIT_ShortcutMgr::get();
+  const QString lang = SUIT_ShortcutMgr::getLang();
+
+  for (const QString& moduleID : myModuleIDs) {
+    const auto& moduleShortcuts = myShortcutContainer->getModuleShortcutsInversed(moduleID);
+    if (moduleShortcuts.empty()) {
+      // Do not display empty module.
+      const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
+      if (moduleItemAndIdx.second >= 0)
+        delete takeTopLevelItem(moduleItemAndIdx.second);
+
+      continue;
+    }
+
+    const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
+    SUIT_ShortcutTreeFolder* moduleItem = moduleItemAndIdx.first;
+    if (!moduleItem) {
+      moduleItem = new SUIT_ShortcutTreeFolder(moduleID);
+      moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang);
+      addTopLevelItem(moduleItem);
+      moduleItem->setFlags(Qt::ItemIsEnabled);
+
+      auto sortedChildren = getSortedChildren(moduleItem);
+      for (const auto& shortcut : moduleShortcuts) {
+        const QString& inModuleActionID = shortcut.first;
+        const QKeySequence& keySequence = shortcut.second;
+        const QString keySequenceString = keySequence.toString();
+
+        auto actionItem = SUIT_ShortcutTreeAction::create(moduleID, inModuleActionID);
+        if (!actionItem) {
+          ShCutDbg("SUIT_ShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
+          continue;
+        }
+
+        actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
+        actionItem->setKeySequence(keySequenceString);
+
+        if (theHighlightModified) {
+          const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
+          actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
+        }
+
+        insertChild(moduleItem, sortedChildren, actionItem);
+      }
+
+      moduleItem->setExpanded(true); // Make tree expanded on first show.
+    }
+    else /* if the tree has the module-item */ {
+      for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
+        // Update exisiting items of a module.
+        SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(moduleItem->child(childIdx));
+        const auto itShortcut = moduleShortcuts.find(childItem->myInModuleActionID);
+        if (itShortcut == moduleShortcuts.end()) {
+          // Shortcut of the item has been removed from myShortcutContainer - impossible.
+          continue;
+        }
+        const QKeySequence& newKeySequence = itShortcut->second;
+        const QString newKeySequenceString = newKeySequence.toString();
+        if (childItem->keySequence() != newKeySequenceString)
+          childItem->setKeySequence(newKeySequenceString);
+
+        if (theHighlightModified) {
+          const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
+          childItem->highlightKeySequenceAsModified(newKeySequence != appliedKeySequence);
+        }
+        else
+          childItem->highlightKeySequenceAsModified(false);
+      }
+
+      // Add new items if myShortcutContainer acquired new shortcuts, which may happen if a developer forgot
+      // to add shortcuts for registered actions to resource files.
+      if (moduleItem->childCount() < moduleShortcuts.size()) {
+        auto sortedChildren = getSortedChildren(moduleItem);
+        for (const auto& shortcut : moduleShortcuts) {
+          const QString& inModuleActionID = shortcut.first;
+          const auto predicate = [&inModuleActionID](const SUIT_ShortcutTreeItem* const theItem) -> bool {
+            return static_cast<const SUIT_ShortcutTreeAction* const>(theItem)->myInModuleActionID == inModuleActionID;
+          };
+
+          if (std::find_if(sortedChildren.begin(), sortedChildren.end(), predicate) == sortedChildren.end()) {
+            const auto actionItem = SUIT_ShortcutTreeAction::create(moduleID, inModuleActionID);
+            if (!actionItem) {
+              ShCutDbg("SUIT_ShortcutTree can't create child item for action ID = \"" + SUIT_ShortcutMgr::makeActionID(moduleID, inModuleActionID) + "\".");
+              continue;
+            }
+
+            const QKeySequence& keySequence = shortcut.second;
+            actionItem->setAssets(shortcutMgr->getActionAssets(moduleID, inModuleActionID), lang);
+            actionItem->setKeySequence(keySequence.toString());
+
+            if (theHighlightModified) {
+              const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, inModuleActionID);
+              actionItem->highlightKeySequenceAsModified(keySequence != appliedKeySequence);
+            }
+
+            insertChild(moduleItem, sortedChildren, actionItem);
+          }
+        }
+      }
+    }
+  }
+
+  if (theUpdateSyncTrees) {
+    const std::set<SUIT_ShortcutTree*>& syncTrees = SUIT_ShortcutTree::instances[myShortcutContainer.get()];
+    for (const auto syncTree: syncTrees) {
+      if (syncTree == this)
+        continue;
+
+      syncTree->updateItems(theHighlightModified, false /*theUpdateSyncTrees*/);
+      const auto editDialog = syncTree->myEditDialog;
+      editDialog->setConfirmedKeySequence(myShortcutContainer->getKeySequence(editDialog->moduleID(), editDialog->inModuleActionID()));
+      editDialog->updateConflictsMessage();
+    }
+  }
+}
+
+/*! \returns Pointer and index of top-level item.
+If the tree does not contain an item with theModuleID, returns {nullptr, -1}. */
+std::pair<SUIT_ShortcutTreeFolder*, int> SUIT_ShortcutTree::findModuleFolderItem(const QString& theModuleID) const
+{
+  for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
+    SUIT_ShortcutTreeFolder* moduleItem = static_cast<SUIT_ShortcutTreeFolder*>(topLevelItem(moduleIdx));
+    if (moduleItem->myModuleID == theModuleID)
+      return std::pair<SUIT_ShortcutTreeFolder*, int>(moduleItem, moduleIdx);
+  }
+  return std::pair<SUIT_ShortcutTreeFolder*, int>(nullptr, -1);
+}
+
+/*! \returns Children of theParentItem being sorted according to current sort mode and order. */
+std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>> SUIT_ShortcutTree::getSortedChildren(SUIT_ShortcutTreeFolder* theParentItem)
+{
+  QList<std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>> sortSchema = SUIT_ShortcutTree::DEFAULT_SORT_SCHEMA;
+  {
+    for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
+      if (itSameKey->first == mySortKey) {
+        sortSchema.erase(itSameKey);
+        break;
+      }
+    }
+    sortSchema.push_front(std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>(mySortKey, mySortOrder));
+  }
+
+  static const QCollator collator;
+  const std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)> comparator =
+  [this, sortSchema, &collator](const SUIT_ShortcutTreeItem* theItemA, const SUIT_ShortcutTreeItem* theItemB) {
+    for (const auto& keyAndOrder : sortSchema) {
+      const int res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
+      if (res != 0)
+        return keyAndOrder.second == SUIT_ShortcutTree::SortOrder::Ascending ? res < 0 : res > 0;
+    }
+    return false;
+  };
+
+  std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>> sortedChildren(comparator);
+  for (int childIdx = 0; childIdx < theParentItem->childCount(); childIdx++) {
+    SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(theParentItem->child(childIdx));
+    sortedChildren.emplace(childItem);
+  }
+  return sortedChildren;
+}
+
+/*! \brief Inserts theChildItem to theParentItem and theSortedChildren.
+Does not check whether theSortedChildren are actually child items of theParentItem.
+Does not check whether current item sort schema is same as one of theSortedChildren. */
+void SUIT_ShortcutTree::insertChild(
+  SUIT_ShortcutTreeFolder* theParentItem,
+  std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>>& theSortedChildren,
+  SUIT_ShortcutTreeItem* theChildItem
+) {
+  auto emplaceRes = theSortedChildren.emplace(theChildItem);
+  theParentItem->insertChild(indexOf(theSortedChildren, emplaceRes.first), theChildItem);
+}
+
+void SUIT_ShortcutTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx)
+{
+  {
+    SUIT_ShortcutTreeItem* const item = static_cast<SUIT_ShortcutTreeItem*>(theItem);
+    // Do not react if folder-item is clicked.
+    if (item->type() != SUIT_ShortcutTreeItem::Type::Action)
+      return;
+  }
+
+  SUIT_ShortcutTreeAction* const actionItem = static_cast<SUIT_ShortcutTreeAction*>(theItem);
+
+  myEditDialog->setModuleAndActionID(actionItem->myModuleID, actionItem->myInModuleActionID);
+  QString actionToolTip = actionItem->toolTip(SUIT_ShortcutTree::ElementIdx::Name);
+  actionToolTip.truncate(actionToolTip.lastIndexOf('\n') + 1);
+  myEditDialog->setModuleAndActionName(
+    static_cast<SUIT_ShortcutTreeItem*>(actionItem->parent())->name(),
+    actionItem->name(),
+    actionToolTip
+  );
+  myEditDialog->setConfirmedKeySequence(QKeySequence::fromString(actionItem->keySequence()));
+  myEditDialog->updateConflictsMessage();
+  const bool somethingChanged = myEditDialog->exec() == QDialog::Accepted;
+
+  if (!somethingChanged)
+    return;
+
+  const QKeySequence newKeySequence = myEditDialog->editedKeySequence();
+
+  /** { moduleID, inModuleActionID }[] */
+  std::set<std::pair<QString, QString>> disabledActionIDs = myShortcutContainer->setShortcut(actionItem->myModuleID, actionItem->myInModuleActionID, newKeySequence, true /*override*/);
+
+  /** { moduleID, {inModuleActionID, keySequence}[] }[] */
+  std::map<QString, std::map<QString, QString>> changes;
+  changes[actionItem->myModuleID][actionItem->myInModuleActionID] = newKeySequence.toString();
+  for (const auto moduleAndActionID : disabledActionIDs) {
+    changes[moduleAndActionID.first][moduleAndActionID.second] = QString();
+  }
+
+  // Set new key sequences to shortcut items.
+  for (const auto& moduleIDAndChanges : changes) {
+    const QString& moduleID = moduleIDAndChanges.first;
+
+    const auto moduleItemAndIdx = findModuleFolderItem(moduleID);
+    const auto moduleItem = moduleItemAndIdx.first;
+    if (!moduleItem)
+      continue;
+
+    /** {inModuleActionID, newKeySequence}[] */
+    const std::map<QString, QString>& moduleChanges = moduleIDAndChanges.second;
+
+    // Go through module' shortcut items, and highlight those, whose key sequences differ from applied key sequences.
+    for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
+      SUIT_ShortcutTreeAction* const childItem = static_cast<SUIT_ShortcutTreeAction*>(moduleItem->child(childIdx));
+      const auto itChange = moduleChanges.find(childItem->myInModuleActionID);
+      if (itChange == moduleChanges.end()) {
+        // The shortcut has not been changed.
+        continue;
+      }
+
+      childItem->setKeySequence(itChange->second);
+
+      const QKeySequence& appliedKeySequence = SUIT_ShortcutMgr::get()->getKeySequence(moduleID, childItem->myInModuleActionID);
+      childItem->highlightKeySequenceAsModified(QKeySequence::fromString(itChange->second) != appliedKeySequence);
+    }
+  }
+}
+
+/*static*/ const QList<std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>> SUIT_ShortcutTree::DEFAULT_SORT_SCHEMA =
+{
+  {SUIT_ShortcutTree::SortKey::Name, SUIT_ShortcutTree::SortOrder::Ascending},
+  {SUIT_ShortcutTree::SortKey::ToolTip, SUIT_ShortcutTree::SortOrder::Ascending},
+  {SUIT_ShortcutTree::SortKey::KeySequence, SUIT_ShortcutTree::SortOrder::Ascending},
+  {SUIT_ShortcutTree::SortKey::ID, SUIT_ShortcutTree::SortOrder::Ascending}
+};
+
+/*static*/ std::map<SUIT_ShortcutContainer*, std::set<SUIT_ShortcutTree*>> SUIT_ShortcutTree::instances =
+std::map<SUIT_ShortcutContainer*, std::set<SUIT_ShortcutTree*>>();
+
+
+
+SUIT_ShortcutTreeItem::SUIT_ShortcutTreeItem(const QString& theModuleID)
+: QTreeWidgetItem(), myModuleID(theModuleID)
+{ }
+
+QString SUIT_ShortcutTreeItem::name() const
+{
+  return text(SUIT_ShortcutTree::ElementIdx::Name);
+}
+
+
+SUIT_ShortcutTreeFolder::SUIT_ShortcutTreeFolder(const QString& theModuleID)
+: SUIT_ShortcutTreeItem(theModuleID)
+{
+  QFont f = font(SUIT_ShortcutTree::ElementIdx::Name);
+  f.setBold(true);
+  setFont(SUIT_ShortcutTree::ElementIdx::Name, f);
+  setText(SUIT_ShortcutTree::ElementIdx::Name, theModuleID);
+}
+
+void SUIT_ShortcutTreeFolder::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
+{
+  if (!theAssets)
+    return;
+
+  setIcon(SUIT_ShortcutTree::ElementIdx::Name, theAssets->myIcon);
+
+  const auto& ldaMap = theAssets->myLangDependentAssets;
+  if (ldaMap.empty()) {
+    setText(SUIT_ShortcutTree::ElementIdx::Name, myModuleID);
+    return;
+  }
+
+  auto itLDA = ldaMap.find(theLang);
+  if (itLDA == ldaMap.end())
+    itLDA = ldaMap.begin();
+
+  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
+  const QString& name = lda.myName.isEmpty() ? myModuleID : lda.myName;
+  setText(SUIT_ShortcutTree::ElementIdx::Name, name);
+}
+
+QString SUIT_ShortcutTreeFolder::getValue(SUIT_ShortcutTree::SortKey theKey) const
+{
+  switch (theKey) {
+    case SUIT_ShortcutTree::SortKey::ID:
+      return myModuleID;
+    case SUIT_ShortcutTree::SortKey::Name:
+      return name();
+    case SUIT_ShortcutTree::SortKey::ToolTip:
+      return name();
+    default:
+      return QString();
+  }
+}
+
+
+SUIT_ShortcutTreeAction::SUIT_ShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
+: SUIT_ShortcutTreeItem(theModuleID), myInModuleActionID(theInModuleActionID)
+{
+  setText(SUIT_ShortcutTree::ElementIdx::Name, theInModuleActionID);
+  setToolTip(
+    SUIT_ShortcutTree::ElementIdx::Name,
+    theInModuleActionID + (theInModuleActionID.at(theInModuleActionID.length()-1) == "." ? "\n" : ".\n") + SUIT_ShortcutTree::tr("Double click to edit key sequence.")
+  );
+  setToolTip(SUIT_ShortcutTree::ElementIdx::KeySequence, SUIT_ShortcutTree::tr("Double click to edit key sequence."));
+}
+
+/*static*/ SUIT_ShortcutTreeAction* SUIT_ShortcutTreeAction::create(const QString& theModuleID, const QString& theInModuleActionID)
+{
+  if (theInModuleActionID.isEmpty()) {
+    ShCutDbg("SUIT_ShortcutTreeItem: attempt to create item with empty action ID.");
+    return nullptr;
+  }
+
+  return new SUIT_ShortcutTreeAction(theModuleID, theInModuleActionID);
+}
+
+void SUIT_ShortcutTreeAction::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
+{
+  if (!theAssets)
+    return;
+
+  setIcon(SUIT_ShortcutTree::ElementIdx::Name, theAssets->myIcon);
+
+  const auto& ldaMap = theAssets->myLangDependentAssets;
+  if (ldaMap.empty()) {
+    setText(SUIT_ShortcutTree::ElementIdx::Name, myInModuleActionID);
+    return;
+  }
+
+  auto itLDA = ldaMap.find(theLang);
+  if (itLDA == ldaMap.end())
+    itLDA = ldaMap.begin();
+
+  const SUIT_ActionAssets::LangDependentAssets& lda = itLDA->second;
+  const QString& name = lda.myName.isEmpty() ? myInModuleActionID : lda.myName;
+  setText(SUIT_ShortcutTree::ElementIdx::Name, name);
+
+  const QString& actionToolTip = lda.myToolTip.isEmpty() ? name : lda.myToolTip;
+  setToolTip(
+    SUIT_ShortcutTree::ElementIdx::Name,
+    actionToolTip + (actionToolTip.at(actionToolTip.length()-1) == "." ? "\n" : ".\n") + SUIT_ShortcutTree::tr("Double click to edit key sequence.")
+  );
+}
+
+QString SUIT_ShortcutTreeAction::getValue(SUIT_ShortcutTree::SortKey theKey) const
+{
+  switch (theKey) {
+    case SUIT_ShortcutTree::SortKey::ID:
+      return myInModuleActionID;
+    case SUIT_ShortcutTree::SortKey::Name:
+      return name();
+    case SUIT_ShortcutTree::SortKey::ToolTip:
+      return toolTip(SUIT_ShortcutTree::ElementIdx::Name);
+    case SUIT_ShortcutTree::SortKey::KeySequence:
+      return keySequence();
+    default:
+      return QString();
+  }
+}
+
+void SUIT_ShortcutTreeAction::setKeySequence(const QString& theKeySequence)
+{
+  setText(SUIT_ShortcutTree::ElementIdx::KeySequence, theKeySequence);
+}
+
+QString SUIT_ShortcutTreeAction::keySequence() const
+{
+  return text(SUIT_ShortcutTree::ElementIdx::KeySequence);
+}
+
+/*! \brief Highlights text at ElementIdx::KeySequence. */
+void SUIT_ShortcutTreeAction::highlightKeySequenceAsModified(bool theHighlight)
+{
+  static const QBrush bgHighlitingBrush = QBrush(Qt::darkGreen);
+  static const QBrush fgHighlitingBrush = QBrush(Qt::white);
+  static const QBrush noBrush = QBrush();
+
+  setBackground(SUIT_ShortcutTree::ElementIdx::KeySequence, theHighlight ? bgHighlitingBrush : noBrush);
+  setForeground(SUIT_ShortcutTree::ElementIdx::KeySequence, theHighlight ? fgHighlitingBrush : noBrush);
+}
\ No newline at end of file
diff --git a/src/SUIT/SUIT_ShortcutTree.h b/src/SUIT/SUIT_ShortcutTree.h
new file mode 100644 (file)
index 0000000..7d2df22
--- /dev/null
@@ -0,0 +1,265 @@
+// Copyright (C) 2007-2024  CEA, EDF, OPEN CASCADE
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef SUIT_SHORTCUTTREE_H
+#define SUIT_SHORTCUTTREE_H
+
+#include "SUIT.h"
+#include <QDialog>
+#include <QFrame>
+#include <QTreeWidget>
+#include "SUIT_ShortcutMgr.h"
+#include <memory>
+#include <map>
+#include <set>
+#include <functional>
+
+
+class QLineEdit;
+class QLabel;
+class QPushButton;
+class QTreeWidgetItem;
+
+class SUIT_EXPORT SUIT_KeySequenceEdit : public QFrame
+{
+  Q_OBJECT
+
+public:
+  SUIT_KeySequenceEdit(QWidget* = nullptr);
+  virtual ~SUIT_KeySequenceEdit() = default;
+
+  void           setConfirmedKeySequence(const QKeySequence&);
+  void           setEditedKeySequence(const QKeySequence&);
+  QKeySequence   editedKeySequence() const;
+  bool           isKeySequenceModified() const;
+  void           restoreKeySequence();
+
+  static QString parseEvent(QKeyEvent*);
+  static bool    isValidKey(int);
+
+signals:
+  void           editingStarted();
+  void           editingFinished();
+  void           restoreFromShortcutMgrClicked();
+
+private slots:
+  void           onClear();
+  void           onEditingFinished();
+
+protected:
+  virtual bool   eventFilter(QObject*, QEvent*);
+
+private:
+  void           initialize();
+
+private:
+  QLineEdit*     myKeySequenceLineEdit;
+  QString        myConfirmedKeySequenceString;
+
+  // Last valid key sequence string from myKeySequenceLineEdit.
+  QString        myPrevKeySequenceString;
+};
+
+
+class SUIT_ShortcutTree;
+class SUIT_ShortcutTreeItem;
+class SUIT_ShortcutTreeFolder;
+class SUIT_ShortcutTreeAction;
+class QTextEdit;
+
+
+class SUIT_EXPORT SUIT_EditKeySequenceDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  SUIT_EditKeySequenceDialog(SUIT_ShortcutTree* theParent);
+  SUIT_EditKeySequenceDialog(const SUIT_EditKeySequenceDialog&) = delete;
+  SUIT_EditKeySequenceDialog& operator=(const SUIT_EditKeySequenceDialog&) = delete;
+  virtual ~SUIT_EditKeySequenceDialog() = default;
+
+  void setModuleAndActionID(const QString& theModuleID, const QString& theInModuleActionID);
+  const QString& moduleID() const;
+  const QString& inModuleActionID() const;
+
+  void setModuleAndActionName(const QString& theModuleName, const QString& theActionName, const QString& theActionToolTip = "");
+
+  void setConfirmedKeySequence(const QKeySequence& theSequence);
+  QKeySequence editedKeySequence() const;
+
+  void updateConflictsMessage();
+
+  int exec();
+
+private slots:
+  void onEditingStarted();
+  void onEditingFinished();
+  void onRestoreFromShortcutMgr();
+  void onConfirm();
+
+private:
+  QString myModuleID;
+  QString myInModuleActionID;
+  QLabel* myActionName;
+  SUIT_KeySequenceEdit* myKeySequenceEdit;
+  QTextEdit* myTextEdit;
+};
+
+
+class SUIT_EXPORT SUIT_ShortcutTree : public QTreeWidget
+{
+  Q_OBJECT
+
+public:
+  enum ElementIdx {
+    Name = 0,
+    KeySequence = 1, // Empty, if item is folder item.
+  };
+
+  enum class SortKey {
+    ID,
+    Name,
+    ToolTip,
+    KeySequence,
+  };
+
+  enum class SortOrder {
+    Ascending,
+    Descending
+  };
+
+  SUIT_ShortcutTree(
+    std::shared_ptr<SUIT_ShortcutContainer> theContainer = std::shared_ptr<SUIT_ShortcutContainer>(),
+    QWidget* theParent = nullptr
+  );
+  SUIT_ShortcutTree(const SUIT_ShortcutTree&) = delete;
+  SUIT_ShortcutTree& operator=(const SUIT_ShortcutTree&) = delete;
+  virtual ~SUIT_ShortcutTree();
+
+  void setShortcutsFromManager();
+  void setDefaultShortcuts();
+  void applyChangesToShortcutMgr();
+
+  std::shared_ptr<const SUIT_ShortcutContainer> shortcutContainer() const;
+
+  void sort(SUIT_ShortcutTree::SortKey theKey, SUIT_ShortcutTree::SortOrder theOrder);
+
+private:
+  void updateItems(bool theHighlightModified, bool theUpdateSyncTrees);
+  std::pair<SUIT_ShortcutTreeFolder*, int> findModuleFolderItem(const QString& theModuleID) const;
+
+  std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>> getSortedChildren(SUIT_ShortcutTreeFolder* theParentItem);
+
+  void insertChild(
+    SUIT_ShortcutTreeFolder* theParentItem,
+    std::set<SUIT_ShortcutTreeItem*, std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)>>& theSortedChildren,
+    SUIT_ShortcutTreeItem* theChildItem
+  );
+
+private slots:
+  void onItemDoubleClicked(QTreeWidgetItem* theWidgetItem, int theColIdx);
+
+public:
+  /** Keeps IDs of modules, which will are shown on setShortcutsFromManager(). */
+  std::set<QString> myModuleIDs;
+
+  static const QList<std::pair<SUIT_ShortcutTree::SortKey, SUIT_ShortcutTree::SortOrder>> DEFAULT_SORT_SCHEMA;
+
+private:
+  /** Allows to modify plenty of shortcuts and then apply them to SUIT_ShortcutMgr as a batch. */
+  const std::shared_ptr<SUIT_ShortcutContainer> myShortcutContainer;
+
+  SUIT_EditKeySequenceDialog* myEditDialog;
+
+  SUIT_ShortcutTree::SortKey mySortKey;
+  SUIT_ShortcutTree::SortOrder mySortOrder;
+
+  /**
+   * Ensures that, if several SUIT_ShortcutTree instances coexist,
+   * all of them are updated when one of them applies pending changes to SUIT_ShortcutMgr.
+   *
+   * Sharing of SUIT_ShortcutContainer allows to keep some trees synchronized even without
+   * applying changes to SUIT_ShortcutMgr. Why? See SUIT_PagePrefShortcutTreeItem.
+   *
+   * Access is not synchronized in assumption, that all instances live in the same thread.
+  */
+  static std::map<SUIT_ShortcutContainer*, std::set<SUIT_ShortcutTree*>> instances;
+};
+
+
+class SUIT_ShortcutTreeItem : public QTreeWidgetItem
+{
+public:
+  enum Type {
+    Folder = 0,
+    Action = 1,
+  };
+
+protected:
+  SUIT_ShortcutTreeItem(const QString& theModuleID);
+
+public:
+  virtual ~SUIT_ShortcutTreeItem() = default;
+  virtual SUIT_ShortcutTreeItem::Type type() const = 0;
+
+  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang) = 0;
+  QString name() const;
+
+  virtual QString getValue(SUIT_ShortcutTree::SortKey theKey) const = 0;
+
+public:
+  const QString myModuleID;
+};
+
+
+class SUIT_ShortcutTreeFolder : public SUIT_ShortcutTreeItem
+{
+public:
+  SUIT_ShortcutTreeFolder(const QString& theModuleID);
+  virtual ~SUIT_ShortcutTreeFolder() = default;
+  virtual SUIT_ShortcutTreeItem::Type type() const { return SUIT_ShortcutTreeItem::Type::Folder; };
+
+  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
+
+  virtual QString getValue(SUIT_ShortcutTree::SortKey theKey) const;
+};
+
+
+class SUIT_ShortcutTreeAction : public SUIT_ShortcutTreeItem
+{
+private:
+  SUIT_ShortcutTreeAction(const QString& theModuleID, const QString& theInModuleActionID);
+
+public:
+  static SUIT_ShortcutTreeAction* create(const QString& theModuleID, const QString& theInModuleActionID);
+  virtual ~SUIT_ShortcutTreeAction() = default;
+  virtual SUIT_ShortcutTreeItem::Type type() const { return SUIT_ShortcutTreeItem::Type::Action; };
+
+  virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
+
+  virtual QString getValue(SUIT_ShortcutTree::SortKey theKey) const;
+
+  void setKeySequence(const QString& theKeySequence);
+  QString keySequence() const;
+  void highlightKeySequenceAsModified(bool theHighlight);
+
+  const QString myInModuleActionID;
+};
+
+#endif // SUIT_SHORTCUTTREE_H
index bdbbb196e3f99171ab9b0e145805c9b94e6b2ff5..9b8a319063d6d453eb9c4d4d7d3e55076097c154 100644 (file)
@@ -160,6 +160,29 @@ Voulez-vous l&apos;écraser ?</translation>
         <translation>Tous les fichiers (*)</translation>
     </message>
 </context>
+<context>
+    <name>SUIT_FindActionDialog</name>
+    <message>
+        <source>Find action</source>
+        <translation>Trouver une action</translation>
+    </message>
+    <message>
+        <source>Unavailable actions</source>
+        <translation>Actions indisponibles</translation>
+    </message>
+    <message>
+        <source>Inactive modules</source>
+        <translation>Modules inactifs</translation>
+    </message>
+    <message>
+        <source>Action</source>
+        <translation>Action</translation>
+    </message>
+    <message>
+        <source>Description</source>
+        <translation>Description</translation>
+    </message>
+</context>
 <context>
     <name>SUIT_ViewWindow</name>
     <message>
@@ -198,4 +221,53 @@ Voulez-vous l&apos;écraser ?</translation>
         <translation>Corrigez manuellement les entrées suivantes dans les fichiers de préférences</translation>
     </message>
 </context>
+<context>
+    <name>SUIT_KeySequenceEdit</name>
+    <message>
+        <source>Disable shortcut.</source>
+        <translation>Désactivez le raccourci.</translation>
+    </message>
+    <message>
+        <source>Restore the currently applied key sequence.</source>
+        <translation>Restaurez la séquence de touches actuellement appliquée.</translation>
+    </message>
+</context>
+<context>
+    <name>SUIT_EditKeySequenceDialog</name>
+    <message>
+        <source>Change key sequence</source>
+        <translation>Modifier la séquence de touches</translation>
+    </message>
+    <message>
+        <source>No conflicts.</source>
+        <translation>Aucun conflit.</translation>
+    </message>
+    <message>
+        <source>Confirm</source>
+        <translation>Confirmer</translation>
+    </message>
+    <message>
+        <source>Cancel</source>
+        <translation>Annuler</translation>
+    </message>
+    <message>
+        <source>These shortcuts will be disabled on confirm:</source>
+        <translation>Ces raccourcis seront désactivés lors de la confirmation :</translation>
+    </message>
+</context>
+<context>
+    <name>SUIT_ShortcutTree</name>
+    <message>
+        <source>Action</source>
+        <translation>Action</translation>
+    </message>
+    <message>
+        <source>Key sequence</source>
+        <translation>Séquence de touches</translation>
+    </message>
+    <message>
+        <source>Double click to edit key sequence.</source>
+        <translation>Double-cliquez pour modifier la séquence de touches.</translation>
+    </message>
+</context>
 </TS>
index 2fb333637a5b516eef5477ce4619f3f1d7ad3add..ad3f28ddbad97d5632ad88d8da8e0871757e452e 100644 (file)
       <translation>すべてのファイル (*)</translation>
     </message>
   </context>
+  <context>
+    <name>SUIT_FindActionDialog</name>
+    <message>
+        <source>Find action</source>
+        <translation>検索アクション</translation>
+    </message>
+    <message>
+        <source>Unavailable actions</source>
+        <translation>利用できないアクション</translation>
+    </message>
+    <message>
+        <source>Inactive modules</source>
+        <translation>非アクティブなモジュール</translation>
+    </message>
+    <message>
+        <source>Action</source>
+        <translation>アクション</translation>
+    </message>
+    <message>
+        <source>Description</source>
+        <translation>説明</translation>
+    </message>
+  </context>
 <context>
     <name>SUIT_ViewWindow</name>
     <message>
         <translation>設定ファイル内の次のエントリを手動で修正します</translation>
     </message>
 </context>
+  <context>
+    <name>SUIT_KeySequenceEdit</name>
+    <message>
+        <source>Disable shortcut.</source>
+        <translation>ショートカットを無効にします。</translation>
+    </message>
+    <message>
+        <source>Restore the currently applied key sequence.</source>
+        <translation>現在適用されているキー シーケンスを復元します。</translation>
+    </message>
+  </context>
+  <context>
+      <name>SUIT_EditKeySequenceDialog</name>
+      <message>
+          <source>Change key sequence</source>
+          <translation>キーシーケンスを変更する</translation>
+      </message>
+      <message>
+          <source>No conflicts.</source>
+          <translation>競合はありません。</translation>
+      </message>
+      <message>
+          <source>Confirm</source>
+          <translation>確認する</translation>
+      </message>
+      <message>
+          <source>Cancel</source>
+          <translation>キャンセル</translation>
+      </message>
+      <message>
+          <source>These shortcuts will be disabled on confirm:</source>
+          <translation>これらのショートカットは確認時に無効になります。</translation>
+      </message>
+  </context>
+  <context>
+      <name>SUIT_ShortcutTree</name>
+      <message>
+        <source>Action</source>
+        <translation>アクション</translation>
+      </message>
+      <message>
+          <source>Key sequence</source>
+          <translation>キーシーケンス</translation>
+      </message>
+      <message>
+          <source>Double click to edit key sequence.</source>
+          <translation>ダブルクリックしてキー シーケンスを編集します。</translation>
+      </message>
+  </context>
 </TS>
index eefb7012c9a50715fbc1a77dca7552f8ba0f33f3..70952aaecb9e263aa39d6dd4ffbf94dba774d00d 100644 (file)
@@ -17,7 +17,7 @@
         }
     },
     "/#Viewers/View/Reset": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/reset.png",
         "langDependentAssets": {
             "en": {
                 "name": "Reset",
             }
         }
     },
+    "/PRP_DESK_FIND_ACTION": {
+        "iconPath": "",
+        "langDependentAssets": {
+            "en": {
+                "name": "Find action",
+                "tooltip": "Opens action search dialog"
+            },
+            "fr": {
+                "name": "Trouver une action",
+                "tooltip": "Ouvre la boîte de dialogue de recherche d'action"
+            },
+            "ja": {
+                "name": "検索アクション",
+                "tooltip": "アクション検索ダイアログを開きます"
+            }
+        }
+    },
     "/PRP_DESK_CONNECT": {
         "iconPath": "",
         "langDependentAssets": {
         }
     },
     "/PRP_DESK_HELP_ABOUT": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/about.png",
         "langDependentAssets": {
             "en": {
                 "name": "About...",
         }
     },
     "/PRP_DESK_WINDOW_HSPLIT": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/htile.png",
         "langDependentAssets": {
             "en": {
                 "name": "Split Horizontally",
         }
     },
     "/PRP_DESK_WINDOW_VSPLIT": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/vtile.png",
         "langDependentAssets": {
             "en": {
                 "name": "Split Vertically",
         }
     },
     "/TOT_DESK_EDIT_PASTE": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/paste.png",
         "langDependentAssets": {
             "en": {
                 "name": "Paste",
         }
     },
     "/TOT_DESK_FILE_CLOSE": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/close.png",
         "langDependentAssets": {
             "en": {
                 "name": "Close",
         }
     },
     "/TOT_DESK_FILE_NEW": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/new.png",
         "langDependentAssets": {
             "en": {
                 "name": "New",
         }
     },
     "/TOT_DESK_FILE_OPEN": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/open.png",
         "langDependentAssets": {
             "en": {
                 "name": "Open...",
         }
     },
     "/TOT_DESK_FILE_SAVE": {
-        "iconPath": "",
+        "iconPath": "${GUI_ROOT_DIR}/share/salome/resources/gui/save.png",
         "langDependentAssets": {
             "en": {
                 "name": "Save",
index 6dc0e9eb59c3bb5834479ae52e1ac272ffc7abed..a8300ea62c7789956cb0aa22830b6fc367cd68bf 100644 (file)
@@ -72,6 +72,7 @@
 #include <SUIT_ViewManager.h>
 #include <SUIT_ViewModel.h>
 #include <SUIT_OverrideCursor.h>
+#include <SUIT_FindActionDialog.h>
 
 #include <QtxTreeView.h>
 
 #include <ToolsGUI_RegWidget.h>
 
 #include <vector>
+#include <iostream>
 
 #include <SALOMEDS_Tool.hxx>
 
@@ -364,6 +366,11 @@ void SalomeApp_Application::createActions()
                 tr( "MEN_DESK_REGISTRY_DISPLAY" ), tr( "PRP_DESK_REGISTRY_DISPLAY" ),
                 /*Qt::SHIFT+Qt::Key_D*/0, desk, false, this, SLOT( onRegDisplay() ) );
 
+  //! Find action dialog
+  createAction( FindActionId, tr( "TOT_DESK_FIND_ACTION" ),  QIcon(),
+                tr( "MEN_DESK_FIND_ACTION" ), tr( "PRP_DESK_FIND_ACTION" ),
+                QKeySequence::UnknownKey, desk, false, this, SLOT( onFindAction() ), "/PRP_DESK_FIND_ACTION" );
+
   createAction( ConnectId, tr( "TOT_DESK_CONNECT_STUDY" ), QIcon(),
                 tr( "MEN_DESK_CONNECT" ), tr( "PRP_DESK_CONNECT" ),
                 QKeySequence::UnknownKey, desk, false, this, SLOT( onLoadDoc() ), "/PRP_DESK_CONNECT" );
@@ -396,6 +403,7 @@ void SalomeApp_Application::createActions()
   int toolsMenu = createMenu( tr( "MEN_DESK_TOOLS" ), -1, MenuToolsId, 50 );
   createMenu( CatalogGenId, toolsMenu, 10, -1 );
   createMenu( RegDisplayId, toolsMenu, 10, -1 );
+  createMenu( FindActionId, toolsMenu, 10, -1 );
   createMenu( separator(), toolsMenu, -1, 15, -1 );
 
   createExtraActions();
@@ -1575,6 +1583,25 @@ void SalomeApp_Application::onRegDisplay()
   regWnd->activateWindow();
 }
 
+/*!Display Action Search dialog */
+void SalomeApp_Application::onFindAction()
+{
+  const auto pActiveModule = activeModule();
+  if (pActiveModule && pActiveModule->name() == "PARAVIS") {
+    return;
+    // ParaViS module has its own action search dialog (Quick Launch dialog).
+    // Keep this conditional block until ParaViS's actions are not added to ShortcutMgr resource and asset files.
+  }
+
+  SUIT_FindActionDialog aDlg( desktop() );
+  if (pActiveModule)
+    aDlg.setActiveModuleID(pActiveModule->name());
+  else
+    aDlg.setActiveModuleID();
+
+  aDlg.exec();
+}
+
 /*!find original object by double click on item */
 void SalomeApp_Application::onDblClick( SUIT_DataObject* theObj )
 {
index bd5c92019ac8ef23169399c01734e4a49ccca812..5a78e279ee3f6951017cf42827bf596cd388d455 100644 (file)
@@ -71,7 +71,7 @@ class SALOMEAPPIMPL_EXPORT SalomeApp_Application : public LightApp_Application
 public:
   enum { MenuToolsId = 5 };
   enum { DumpStudyId = LightApp_Application::UserID, LoadScriptId, PropertiesId,
-         CatalogGenId, RegDisplayId, SaveGUIStateId, ConnectId, DisconnectId,
+         CatalogGenId, RegDisplayId, FindActionId, SaveGUIStateId, ConnectId, DisconnectId,
          UserID };
 
   typedef enum { WT_NoteBook = LightApp_Application::WT_User,
@@ -192,6 +192,7 @@ private slots:
 
   void                                onCatalogGen();
   void                                onRegDisplay();
+  void                                onFindAction();
   void                                onOpenWith();
   void                                onExtAction();
 
index d75e202a2f36ed0a73ae1493c02d7d9b09aa5ab1..b130ca8970d741e87f88959f62465162c5029628 100644 (file)
@@ -164,6 +164,10 @@ Launch a new session or close the study.</translation>
         <source>MEN_DESK_REGISTRY_DISPLAY</source>
         <translation>Registry &amp;Display</translation>
     </message>
+    <message>
+        <source>MEN_DESK_FIND_ACTION</source>
+        <translation>Find action</translation>
+    </message>
     <message>
         <source>TOT_DESK_FILE_LOAD_SCRIPT</source>
         <translation>Load python script</translation>
@@ -237,6 +241,10 @@ Do you want to reload it ?</translation>
         <source>PRP_DESK_REGISTRY_DISPLAY</source>
         <translation>Displays content of the Registry CORBA server</translation>
     </message>
+    <message>
+        <source>PRP_DESK_FIND_ACTION</source>
+        <translation>Opens action search dialog</translation>
+    </message>
     <message>
         <source>APPCLOSE_DESCRIPTION</source>
         <translation>Do you want to save study before closing?</translation>
@@ -261,6 +269,10 @@ Do you want to reload it ?</translation>
         <source>TOT_DESK_REGISTRY_DISPLAY</source>
         <translation>Registry display</translation>
     </message>
+    <message>
+        <source>TOT_DESK_FIND_ACTION</source>
+        <translation>Find action</translation>
+    </message>
     <message>
         <source>OBJ_BROWSER_COLUMN_0</source>
         <translation>Entry</translation>
index ac9e08e955ca3ee08cae35e882365ff0d1b4b91d..e73006c235722901a3844ff1ad8893263de53cea 100644 (file)
@@ -164,6 +164,10 @@ Lancez une nouvelle session ou fermez l&apos;étude en cours.</translation>
         <source>MEN_DESK_REGISTRY_DISPLAY</source>
         <translation>Affichage du registre CORBA</translation>
     </message>
+    <message>
+        <source>MEN_DESK_FIND_ACTION</source>
+        <translation>Trouver une action</translation>
+    </message>
     <message>
         <source>TOT_DESK_FILE_LOAD_SCRIPT</source>
         <translation>Exécuter un script python</translation>
@@ -237,6 +241,10 @@ Voulez-vous le recharger ?</translation>
         <source>PRP_DESK_REGISTRY_DISPLAY</source>
         <translation>Visualiser le contenu du registre du serveur CORBA</translation>
     </message>
+    <message>
+        <source>PRP_DESK_FIND_ACTION</source>
+        <translation>Ouvre la boîte de dialogue de recherche d'action</translation>
+    </message>
     <message>
         <source>APPCLOSE_DESCRIPTION</source>
         <translation>Voulez-vous sauvegarder l&apos;étude avant de quitter ?</translation>
@@ -261,6 +269,10 @@ Voulez-vous le recharger ?</translation>
         <source>TOT_DESK_REGISTRY_DISPLAY</source>
         <translation>Visualiser le registre CORBA</translation>
     </message>
+    <message>
+        <source>TOT_DESK_FIND_ACTION</source>
+        <translation>Trouver une action</translation>
+    </message>
     <message>
         <source>OBJ_BROWSER_COLUMN_0</source>
         <translation>Entrée</translation>
index 7dab9b3a40afd7c1ddac45f2dfff935641692de2..6bc1b7851c04af13a2a52337cd7f35bd993510bc 100644 (file)
       <source>MEN_DESK_REGISTRY_DISPLAY</source>
       <translation>レジストリの表示(&amp;D)</translation>
     </message>
+    <message>
+      <source>MEN_DESK_FIND_ACTION</source>
+      <translation>検索アクション</translation>
+    </message>
     <message>
       <source>TOT_DESK_FILE_LOAD_SCRIPT</source>
       <translation>Python スクリプトを実行</translation>
       <source>PRP_DESK_REGISTRY_DISPLAY</source>
       <translation>CORBAサーバーの登録内容を表示</translation>
     </message>
+    <message>
+        <source>PRP_DESK_FIND_ACTION</source>
+        <translation>アクション検索ダイアログを開きます</translation>
+    </message>
     <message>
       <source>APPCLOSE_DESCRIPTION</source>
       <translation>閉じる、または閉じる前にスタディをアンロードしますか?</translation>
       <source>TOT_DESK_REGISTRY_DISPLAY</source>
       <translation>レジストリの表示</translation>
     </message>
+    <message>
+        <source>TOT_DESK_FIND_ACTION</source>
+        <translation>検索アクション</translation>
+    </message>
     <message>
       <source>OBJ_BROWSER_COLUMN_0</source>
       <translation>エントリ</translation>