Add Find Action dialog. Add icons' paths of GUI-actions to resource 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"/>
# --- 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})
QtxPopupMgr.h
QtxRubberBand.h
QtxSearchTool.h
- QtxShortcutEdit.h
QtxSlider.h
QtxSplash.h
QtxToolBar.h
QtxResourceMgr.cxx
QtxRubberBand.cxx
QtxSearchTool.cxx
- QtxShortcutEdit.cxx
QtxSlider.cxx
QtxSplash.cxx
QtxToolBar.cxx
#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>
}
-/*!
- \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.
class QtxComboBox;
class QtxColorButton;
class QtxBiColorTool;
-class QtxShortcutTree;
class QtxBackgroundTool;
class QToolBox;
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
+++ /dev/null
-// 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> " + 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> " + 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
+++ /dev/null
-// 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
<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>
<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>
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
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
SUIT_Selector.cxx
SUIT_Session.cxx
SUIT_ShortcutMgr.cxx
+ SUIT_ShortcutTree.cxx
SUIT_Study.cxx
SUIT_Tools.cxx
SUIT_TreeModel.cxx
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+#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
--- /dev/null
+// 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
// Author: Sergey TELKOV
//
#include "SUIT_PreferenceMgr.h"
+#include "SUIT_PagePrefShortcutTreeItem.h"
SUIT_PreferenceMgr::SUIT_PreferenceMgr( QtxResourceMgr* resMgr, QWidget* parent )
: QtxPagePrefMgr( resMgr, parent ),
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 );
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">`.
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`.
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. */
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);
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];
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;
}
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);
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);
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())
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())
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++) {
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));
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);
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
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;
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;
}
#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);
#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
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.
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
#include <QObject>
#include <QString>
+#include <QStringList>
#include <QIcon>
#include <map>
#include <set>
#include <memory>
#include <utility>
+#include <limits>
class QAction;
class QtxAction;
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();
\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();
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;
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;
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
--- /dev/null
+// 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> " + 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> " + 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
--- /dev/null
+// 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
<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>
<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>
<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>
}
},
"/#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",
#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>
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" );
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();
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 )
{
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,
void onCatalogGen();
void onRegDisplay();
+ void onFindAction();
void onOpenWith();
void onExtAction();
<source>MEN_DESK_REGISTRY_DISPLAY</source>
<translation>Registry &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>
<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>
<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>
<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>
<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'étude avant de quitter ?</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>
<source>MEN_DESK_REGISTRY_DISPLAY</source>
<translation>レジストリの表示(&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>