#include <QDebug>
#include <QJsonObject>
#include <QJsonDocument>
+#include <QJsonArray>
#include <QJsonParseError>
#include <QFile>
+const QString SECTION_NAME_ACTION_ID_MUTATION_FILE_PATHS = "actionID_mutations";
-class SUIT_EXPORT SUIT_ActionIDEvolver
+/*static*/ const std::vector<QString> SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION = {"shortcuts_vA1.0", "shortcuts"};
+
+/*static*/ const QString SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_MUTATIONS = "mutations";
+/*static*/ const QString SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_PREFIX_OLD = "sectionPrefixOld";
+/*static*/ const QString SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_PREFIX_NEW = "sectionPrefixNew";
+/*static*/ const QString SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_NEW_TO_OLD_ACTION_ID_MAP = "newToOldActionIDMap";
+
+/*static*/ bool SUIT_ShortcutHistorian::AIDSMutation::isPairOfNewAndOldActionIDsValid(const QString& theSectionPrefixNew, const QString& theSectionPrefixOld)
{
-public:
- struct SUIT_EXPORT Schema
- {
- bool fromJSON(const QJsonObject& theJsonObject);
+ if (theSectionPrefixNew == theSectionPrefixOld) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation: new section prefix is the same as old one - \"" + theSectionPrefixNew + "\".");
+ return false;
+ }
+}
- QString sectionPrefix_evolutionFrom;
- QString sectionPrefix_evolutionInto;
+SUIT_ShortcutHistorian::AIDSMutation::AIDSMutation(const QString& theSectionPrefixNew, const QString& theSectionPrefixOld)
+{
+ if (!SUIT_ShortcutHistorian::AIDSMutation::isPairOfNewAndOldActionIDsValid(theSectionPrefixNew, theSectionPrefixOld))
+ throw std::invalid_argument("AIDSMutation::AIDSMutation: invalid prefixes.");
- /** The map only keeps changes between identification schemes. It means, there is no need to add entries like {theSameActionID: theSameActionID}. */
- std::map<QString, QString> newActionID_to_oldActionID_map;
- };
+ mySectionPrefixOld = theSectionPrefixOld;
+ mySectionPrefixNew = theSectionPrefixNew;
+}
- /** {sectionPrefix_evolutionFrom}[]. Defined from newest to oldest. */
- static const std::vector<QString> evolutionChronology;
+SUIT_ShortcutHistorian::AIDSMutation::AIDSMutation(const QJsonObject& theJsonObject, const bool theParseMap)
+{
+ mySectionPrefixOld = theJsonObject[SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_PREFIX_OLD].toString();
+ mySectionPrefixNew = theJsonObject[SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_PREFIX_NEW].toString();
+ if (!SUIT_ShortcutHistorian::AIDSMutation::isPairOfNewAndOldActionIDsValid(mySectionPrefixNew, mySectionPrefixOld))
+ throw std::invalid_argument("AIDSMutation::AIDSMutation: invalid prefixes.");
- /*!
- \param theActionID Action ID in the latest identification schema (as elsewhere in code).
- \returns {false, _ }, if shortcut is not defined in any outdated shortcut section of user preference files. */
- std::pair<bool, QKeySequence> getUserDefinedKeySequence(const QString& theActionID) const;
+ if (!theParseMap)
+ return;
-private:
- void parseSchemas();
- std::pair<bool, QKeySequence> getUserDefinedKeySequenceInOldSection(const QString& theActionID, const QString& theSectionPrefix) const;
+ const auto actionIDMapJSONObject = theJsonObject[SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_NEW_TO_OLD_ACTION_ID_MAP].toObject();
+ for (const QString& newActionID : actionIDMapJSONObject.keys()) {
+ if (!SUIT_ShortcutMgr::isActionIDValid(newActionID)) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::fromJSON: invalid action ID \"" + newActionID + "\" has been encountered.");
+ continue;
+ }
-private:
- /** {sectionPrefix_evolutionFrom, {schema}[] }[]. Sorted from newest to oldest. */
- std::list<std::pair<QString, std::set<std::shared_ptr<SUIT_ActionIDEvolver::Schema>>> schemas;
-};
+ const QString oldActionID = actionIDMapJSONObject[newActionID].toString();
+ if (!SUIT_ShortcutMgr::isActionIDValid(oldActionID)) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::fromJSON: invalid action ID \"" + oldActionID + "\" has been encountered.");
+ continue;
+ }
+
+ if (myOldToNewActionIDMap.find(oldActionID) != myOldToNewActionIDMap.end()) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::fromJSON: old action ID \"" + oldActionID + "\" is not unique within mutation. Ignored.");
+ continue;
+ }
+
+ if (myNewToOldActionIDMap.find(newActionID) != myNewToOldActionIDMap.end()) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::fromJSON: new action ID \"" + newActionID + "\" is not unique within mutation. Ignored.");
+ continue;
+ }
+
+ myOldToNewActionIDMap.emplace(oldActionID, newActionID);
+ myNewToOldActionIDMap.emplace(newActionID, oldActionID);
+ }
+}
+
+bool SUIT_ShortcutHistorian::AIDSMutation::isConcurrent(const AIDSMutation& theOther) const
+{
+ return mySectionPrefixOld == theOther.mySectionPrefixOld && mySectionPrefixNew == theOther.mySectionPrefixNew;
+}
-const QString SECTION_NAME_ACTION_ID_MIGRATION_SCHEMA_FILE_PATHS = "TODO";
+bool SUIT_ShortcutHistorian::AIDSMutation::merge(const AIDSMutation& theOther)
+{
+ if (!isConcurrent(theOther))
+ return false;
-static const std::vector<QString> SUIT_ActionIDEvolver::evolutionChronology = {"shortcuts_vA1.0", "shortcuts"};
+ bool areMutationMapsExtended = false;
+ for (const auto& newAndOldActionIDOfOther : theOther.myNewToOldActionIDMap) {
+ const QString& newActionIDOfOther = newAndOldActionIDOfOther.first;
+ const QString& oldActionIDOfOther = newAndOldActionIDOfOther.second;
+
+ if (myOldToNewActionIDMap.find(oldActionIDOfOther) != myOldToNewActionIDMap.end()) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::merge: old action ID \"" + oldActionIDOfOther + "\" is not unique within mutation. Ignored.");
+ continue;
+ }
+ if (myNewToOldActionIDMap.find(newActionIDOfOther) != myNewToOldActionIDMap.end()) {
+ Warning("SUIT_ShortcutHistorian::AIDSMutation::merge: new action ID \"" + newActionIDOfOther + "\" is not unique within mutation. Ignored.");
+ continue;
+ }
-void SUIT_ActionIDEvolver::parseSchemas() {
- ShCutDbg() && ShCutDbg("Parsing action ID evolution schemas.");
+ myOldToNewActionIDMap.emplace(oldActionIDOfOther, newActionIDOfOther);
+ myNewToOldActionIDMap.emplace(newActionIDOfOther, oldActionIDOfOther);
+ areMutationMapsExtended = true;
+ }
- if (SUIT_ActionIDEvolver::evolutionChronology.size() < 2) {
- Warning("SUIT_ActionIDEvolver: evolution chronology is not sufficient.");
+ return areMutationMapsExtended;
+}
+
+
+std::pair<bool, QKeySequence> SUIT_ShortcutHistorian::getOldUserDefinedKeySequence(const QString& theActionID) const
+{
+ auto result = std::pair<bool, QKeySequence>(false, QKeySequence());
+
+ /** ID of the same action before a mutation (migration) happened. */
+ QString oldActionID = theActionID;
+ for (const auto& oldPrefixAndMutation : myOldPrefixToMutationList) {
+ const auto& mutation = oldPrefixAndMutation.second;
+ const auto itNewAndOldActionIDs = mutation.getNewToOldActionIDMap().find(theActionID);
+ if (itNewAndOldActionIDs != mutation.getNewToOldActionIDMap().end())
+ oldActionID = itNewAndOldActionIDs->second;
+
+ std::pair<bool, QKeySequence> oldKeySequence = getUserDefinedKeySequenceInSection(oldActionID, mutation.getSectionPrefixOld());
+ if (oldKeySequence.first) {
+ // The old shortcut is defined in the old section. No need to dig deeper into action ID evolution.
+ return result;
+ }
+ }
+ return result;
+}
+
+void SUIT_ShortcutHistorian::parseMutations()
+{
+ ShCutDbg() && ShCutDbg("Parsing action ID mutation files.");
+
+ if (SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION.size() < 2) {
+ Warning("SUIT_ShortcutHistorian: shortcut settings' preference section name evolution is too short.");
return;
}
- if (SUIT_ActionIDEvolver::evolutionChronology.front() != SECTION_NAME_PREFIX) {
- Warning("SUIT_ActionIDEvolver: evolution chronology starts with a shortcuts' preference section name prefix, which differs from actual one.");
+ if (SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION.front() != SECTION_NAME_PREFIX) {
+ Warning("SUIT_ShortcutHistorian: shortcut settings' preference section name evolution starts with a prefix, which differs from the actual one.");
return;
}
- for (size_t idx = 0; idx < SUIT_ActionIDEvolver::evolutionChronology.size() - 1; idx++) {
- schemas.emplace(SUIT_ActionIDEvolver::evolutionChronology[idx], std::set<std::shared_ptr<SUIT_ActionIDEvolver::Schema>>);
+ for (size_t idx = 0; idx < SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION.size() - 1; idx++) {
+ const QString& newPrefix = SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION[idx];
+ const QString& oldPrefix = SUIT_ShortcutHistorian::SECTION_PREFIX_EVOLUTION[idx + 1];
+ myOldPrefixToMutationList.emplace_back(std::pair<QString, SUIT_ShortcutHistorian::AIDSMutation>(oldPrefix, SUIT_ShortcutHistorian::AIDSMutation(newPrefix, oldPrefix)));
}
SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
if (!resMgr) {
- Warning("SUIT_ActionIDEvolver can't retrieve resource manager!");
+ Warning("SUIT_ShortcutHistorian can't retrieve resource manager!");
return;
}
- QStringList schemaFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ID_MIGRATION_SCHEMA_FILE_PATHS);
+ QStringList mutationFilePaths = resMgr->parameters(SECTION_NAME_ACTION_ID_MUTATION_FILE_PATHS);
#ifdef SHORTCUT_MGR_DBG
- ShCutDbg("Action ID evolution schema files: " + schemaFilePaths.join(", ") + ".");
+ ShCutDbg("Action ID mutation files: " + mutationFilePaths.join(", ") + ".");
#endif
- for (const QString& schemaFilePath : schemaFilePaths) {
- const QString path = ::SUIT_tools::substituteVars(schemaFilePath);
- ShCutDbg("Parsing action ID evolution file \"" + path + "\".");
- QFile schemaFile(path);
- if (!schemaFile.open(QIODevice::ReadOnly)) {
- Warning("SUIT_ActionIDEvolver can't open action ID evolution file \"" + path + "\"!");
+ for (const QString& mutationFilePath : mutationFilePaths) {
+ const QString path = ::SUIT_tools::substituteVars(mutationFilePath);
+ ShCutDbg("Parsing action ID mutation file \"" + path + "\".");
+ QFile mutationFile(path);
+ if (!mutationFile.open(QIODevice::ReadOnly)) {
+ Warning("SUIT_ShortcutHistorian can't open action ID mutation file \"" + path + "\"!");
continue;
}
QJsonParseError jsonError;
- QJsonDocument document = QJsonDocument::fromJson(schemaFile.readAll(), &jsonError);
- schemaFile.close();
+ QJsonDocument document = QJsonDocument::fromJson(mutationFile.readAll(), &jsonError);
+ mutationFile.close();
if (jsonError.error != QJsonParseError::NoError) {
- Warning("SUIT_ShortcutMgr: error during parsing of action ID evolution file \"" + path + "\"!");
+ Warning("SUIT_ShortcutHistorian: error during parsing of action ID mutation file \"" + path + "\"!");
continue;
}
if (document.isObject()) {
- QJsonObject documentJSONObject = document.object();
- for (const QString& key : documentJSONObject.keys()) {
- if (key != "schemas") // TODO
- continue;
+ const QJsonObject documentJSONObject = document.object();
+ const auto itMutationsJSONVal = documentJSONObject.find(SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_MUTATIONS);
+ if (itMutationsJSONVal == documentJSONObject.end()) {
+ Warning("Action ID mutation file \"" + path + "\" does not contain \"" + SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_MUTATIONS + "\" array.");
+ continue;
+ }
- const auto& schemasJSONValue = documentJSONObject[key];
- if (!schemasJSONValue.isArray()) {
- ShCutDbg("Action ID evolution file \"" + path + "\" does not contain array \"" + key + "\".");
+ const auto& mutationsJSONVal = itMutationsJSONVal.value();
+ if (!mutationsJSONVal.isArray()) {
+ Warning("Action ID mutation file \"" + path + "\" has a property \"" + SUIT_ShortcutHistorian::AIDSMutation::PROP_ID_MUTATIONS + "\", but it is not array.");
+ continue;
+ }
+
+ const auto& mutationsJSONArray = mutationsJSONVal.toArray();
+ for (const auto& mutationJSONVal : mutationsJSONArray) {
+ auto mutation = std::unique_ptr<SUIT_ShortcutHistorian::AIDSMutation>(nullptr);
+ try {
+ mutation.reset(new SUIT_ShortcutHistorian::AIDSMutation(mutationJSONVal.toObject()));
+ }
+ catch (const std::invalid_argument& e) {
+ Warning(e.what());
continue;
}
- const auto& schemasJSONArray = schemasJSONValue.toArray();
- for (const auto& schemaJSONVal : schemasJSONArray) {
- if (!schemaJSONVal.isObject()) {
- ShCutDbg("Action ID evolution file \"" + path + "\" contains invalid schema object.");
- continue;
- }
-
- auto schema = std::shared_ptr<SUIT_ActionIDEvolver::Schema>(new SUIT_ActionIDEvolver::Schema());
- if (!schema->fromJSON(schemaJSONVal.toObject())) {
- ShCutDbg("Action ID evolution file \"" + path + "\" contains invalid schema object.");
- continue;
- }
-
- if (schema->sectionPrefix_evolutionFrom == schema->sectionPrefix_evolutionInto) {
- ShCutDbg("Action ID evolution file \"" + path + "\" contains invalid evolution schema (from == into). From/into \"" + schema->sectionPrefix_evolutionFrom + "\".");
- continue;
- }
-
- const auto itEvolutionFrom = std::find(SUIT_ActionIDEvolver::evolutionChronology.begin(), SUIT_ActionIDEvolver::evolutionChronology.end(); schema->sectionPrefix_evolutionFrom);
- const auto itEvolutionInto = std::find(SUIT_ActionIDEvolver::evolutionChronology.begin(), SUIT_ActionIDEvolver::evolutionChronology.end(); schema->sectionPrefix_evolutionInto);
- if (itEvolutionFrom == SUIT_ActionIDEvolver::evolutionChronology.end() || itEvolutionInto == SUIT_ActionIDEvolver::evolutionChronology.end()) {
- ShCutDbg("Action ID evolution file \"" + path + "\" contains evolution schema, which is absent in evolution chronology. From \"" + schema->sectionPrefix_evolutionFrom + "\" into \"" + schema->sectionPrefix_evolutionInto + "\".");
- continue;
- }
-
- {
- const auto itEvolutionFromNext = ++itEvolutionFrom;
- if (itEvolutionFromNext != itEvolutionInto) {
- ShCutDbg("Action ID evolution file \"" + path + "\" contains evolution schema, which is absent in evolution chronology. From \"" + schema->sectionPrefix_evolutionFrom + "\" into \"" + schema->sectionPrefix_evolutionInto + "\".");
- continue;
- }
- }
-
- schemas[schema->sectionPrefix_evolutionFrom].insert(schema);
+ const auto predicate = [&mutation] (const std::pair<QString, SUIT_ShortcutHistorian::AIDSMutation>& thePair) { return thePair.first == mutation->getSectionPrefixOld(); };
+ const auto itOldPrefixToMutationList = std::find_if(myOldPrefixToMutationList.begin(), myOldPrefixToMutationList.end(), predicate);
+ if (itOldPrefixToMutationList == myOldPrefixToMutationList.end() || !mutation->isConcurrent(itOldPrefixToMutationList->second)) {
+ Warning("Action ID mutation file \"" + path + "\" contains a Mutation, which is not concurrent with mutations from evolution. Old prefix \"" + mutation->getSectionPrefixOld() + "\"; new prefix \"" + mutation->getSectionPrefixNew() + "\".");
+ continue;
}
+
+ itOldPrefixToMutationList->second.merge(*mutation);
}
}
}
}
-std::pair<bool, QKeySequence> SUIT_ActionIDEvolver::getUserDefinedKeySequence(const QString& theActionID) const {
+std::pair<bool, QKeySequence> SUIT_ShortcutHistorian::getUserDefinedKeySequenceInSection(const QString& theActionID, const QString& theSectionPrefix) const
+{
auto result = std::pair<bool, QKeySequence>(false, QKeySequence());
- for (const auto& sectionPrefixAndSchemas : schemas) {
- const auto& schemasOfCurrentEvolution = sectionPrefixAndSchemas.second;
- for (const auto& schemaOfCurrentEvolution : schemasOfCurrentEvolution ) {
- const auto it = schemaOfCurrentEvolution.newActionID_to_oldActionID_map.find(theActionID);
- if (it == schemaOfCurrentEvolution.newActionID_to_oldActionID_map.end())
- continue;
-
- const QString& olderActionID = it->second;
- std::pair<bool, QKeySequence> olderKS = getUserDefinedKeySequenceInOldSection(olderActionID, schemaOfCurrentEvolution.sectionPrefix_evolutionFrom);
- if (olderKS.first) {
- result = SUIT_ShortcutMgr::toKeySequenceIfValid(olderKS.second);
- if (result.first)
- return result; // Even if there are other
- else
- ShCutDbg("Old settings file contains invalid key sequence string \"" + olderKS.second + "\". From \"" + schemaOfCurrentEvolution->sectionPrefix_evolutionFrom + "\" into \"" + schemaOfCurrentEvolution->sectionPrefix_evolutionInto + "\".");
- }
- }
- }
+ // TODO
+ return result;
}
\ No newline at end of file
};
+/*! \brief Sometimes developers change IDs of actions. And at the moment of release of these changes
+users may already have cutomized shortcuts. This class assits with user defined shorcut settings' migrations.
+It retrieves keysequences, assigned by user using older Salome versions,
+which operated with old action ID sets. "Action ID set" is also referred as AIDS for brevity. */
+class SUIT_EXPORT SUIT_ShortcutHistorian
+{
+public:
+ /** {name prefix of a section in user preference files}[]. Sorted from the newest to the oldest.
+ * The list is hardcoded. Update it every time, a new AIDS and appropriate AIDSMutation are added. */
+ static const std::vector<QString> SECTION_PREFIX_EVOLUTION;
+
+private:
+ /*! \brief Describes how action IDs evolved between consequtive versions of action ID sets.
+ PS. Once upon a time I also wanted to cure AIDS. But instead I spawned new ones. With mutations. */
+ class SUIT_EXPORT AIDSMutation
+ {
+ public:
+ static const QString PROP_ID_MUTATIONS;
+ static const QString PROP_ID_PREFIX_OLD;
+ static const QString PROP_ID_PREFIX_NEW;
+ static const QString PROP_ID_NEW_TO_OLD_ACTION_ID_MAP;
+
+ static bool isPairOfNewAndOldActionIDsValid(const QString& theSectionPrefixNew, const QString& theSectionPrefixOld);
+
+ AIDSMutation(const QString& theSectionPrefixNew, const QString& theSectionPrefixOld);
+ AIDSMutation(const QJsonObject& theJsonObject, const bool theParseMap = true);
+
+ const QString& getSectionPrefixOld() const { return mySectionPrefixOld; };
+ const QString& getSectionPrefixNew() const { return mySectionPrefixNew; };
+ const std::map<QString, QString>& getNewToOldActionIDMap() const { return myNewToOldActionIDMap; };
+
+ /*! \returns True, if both old and new prefixes are the same as ones of theOther. */
+ bool isConcurrent(const AIDSMutation& theOther) const;
+
+ /*! \returns True. if mutation maps are extended. */
+ bool merge(const AIDSMutation& theOther);
+
+ private:
+ /** Old name prefix of a section in user preference files. */
+ QString mySectionPrefixOld;
+
+ /** New name prefix of a section in user preference files. */
+ QString mySectionPrefixNew;
+
+ /** The map only keeps changes between AIDS versions. It means, there is no need to add entries like {theSameActionID, theSameActionID}. */
+ std::map<QString, QString> myNewToOldActionIDMap;
+
+ /** The map only keeps changes between AIDS versions. It means, there is no need to add entries like {theSameActionID, theSameActionID}. */
+ std::map<QString, QString> myOldToNewActionIDMap;
+ };
+
+public:
+ /*!
+ \param theActionID Action ID in latest version (as elsewhere in ShortcutMgr code).
+ \returns {false, _ }, if shortcut is not defined in any outdated shortcut section of user preference files. */
+ std::pair<bool, QKeySequence> getOldUserDefinedKeySequence(const QString& theActionID) const;
+
+private:
+ void parseMutations();
+
+ /*!
+ \param theSectionPrefix User preferences' section name with shortcuts.
+ \returns {false, _ }, is the shortcut is not defined in the section. */
+ std::pair<bool, QKeySequence> getUserDefinedKeySequenceInSection(const QString& theActionID, const QString& theSectionPrefix) const;
+
+private:
+ /** {sectionNamePrefixOld, mutation}[]. Sorted from the newest to the oldest. */
+ std::list<std::pair<QString, SUIT_ShortcutHistorian::AIDSMutation>> myOldPrefixToMutationList;
+};
+
+
namespace SUIT_tools {
class SUIT_SentenceMatcher;
}