| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include<vector> |
| |
|
| | #include <QRegularExpression> |
| | #include <QTextStream> |
| |
|
| | #include "lc_commandItems.h" |
| | #include "rs_commands.h" |
| |
|
| | #include <QFileInfo> |
| |
|
| | #include "rs_debug.h" |
| | #include "rs_dialogfactory.h" |
| | #include "rs_dialogfactoryinterface.h" |
| | #include "rs_settings.h" |
| |
|
| | #include "rs_system.h" |
| |
|
| | namespace { |
| |
|
| | const char* g_FnPrefix = "Fn"; |
| | const char* g_AltPrefix = "Alt-"; |
| | const char* g_MetaPrefix = "Meta-"; |
| |
|
| | struct LC_CommandItem { |
| | std::vector<std::pair<QString, QString>> const fullCmdList; |
| | std::vector<std::pair<QString, QString>> const shortCmdList; |
| | RS2::ActionType actionType; |
| | }; |
| |
|
| | |
| | template<typename T1, typename T2> |
| | bool isCollisionFree(std::map<T1, T2> const& lookUp, T1 const& key, T2 const& value, QString cmd = {}) |
| | { |
| | if (key == cmd) |
| | return false; |
| |
|
| | if(lookUp.count(key) == 0 || lookUp.at(key) == value) |
| | return true; |
| |
|
| | |
| | QString msg = __FILE__ + QObject::tr(": duplicated command: %1 is already taken by %2"); |
| | if constexpr (std::is_same_v<T2, RS2::ActionType>) |
| | msg = msg.arg(key).arg(cmd); |
| | else |
| | msg = msg.arg(key).arg(value); |
| |
|
| | RS_DEBUG->print(RS_Debug::D_ERROR, "%s\n", msg.toStdString().c_str()); |
| | return false; |
| | } |
| |
|
| | |
| | void writeAliasFile(const QString& aliasName, |
| | const std::map<QString, RS2::ActionType>& m_shortCommands, |
| | const std::map<QString, RS2::ActionType>& m_mainCommands |
| | ) |
| | { |
| | LC_LOG<<__func__<<"(): begin"; |
| | LC_LOG<<"Creating "<<QFileInfo(aliasName).absoluteFilePath(); |
| |
|
| | QFile aliasFile{aliasName}; |
| | if (!aliasFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { |
| | LC_ERR<<__func__<<"(): line "<<__LINE__<<": failed to create "<<QFileInfo(aliasName).absoluteFilePath(); |
| | return; |
| | } |
| | QTextStream ts(&aliasFile); |
| | ts << "#LibreCAD alias v1\n\n"; |
| | ts << "# lines starting with # are comments\n"; |
| | ts << "# format are:\n"; |
| | ts << R"(# <alias>\t<command-untranslated>)" "\n"; |
| | ts << "# the alias cannot be an existing command"; |
| | ts << "# example\n"; |
| | ts << "# l\tline\n\n"; |
| |
|
| | |
| | std::map<RS2::ActionType, QString> actionToMain; |
| |
|
| | |
| | for(const auto& item: g_commandList) { |
| | for(const auto& [fullCmd, translation]: item.fullCmdList) |
| | actionToMain.emplace(item.actionType, fullCmd); |
| | } |
| |
|
| | for(auto const& [cmd, action]: m_mainCommands) |
| | if (actionToMain.count(action) == 0) |
| | actionToMain.emplace(action, cmd); |
| | for(auto const& [alias, action]: m_shortCommands) { |
| | if (actionToMain.count(action) == 1) |
| | ts<<alias<<'\t'<<actionToMain.at(action)<<Qt::endl; |
| | } |
| | LC_LOG<<__func__<<"(): end"; |
| | } |
| | } |
| |
|
| | RS_Commands* RS_Commands::instance() { |
| | static RS_Commands* uniqueInstance = new RS_Commands(); |
| | return uniqueInstance; |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | RS_Commands::RS_Commands() { |
| |
|
| | for(auto const& [fullCmdList, aliasList, action]: g_commandList){ |
| | |
| | for(auto const& [fullCmd, cmdTranslation]: fullCmdList){ |
| | if (fullCmd == cmdTranslation) |
| | continue; |
| | |
| | if (isCollisionFree(m_cmdTranslation, fullCmd, cmdTranslation)) |
| | m_cmdTranslation.emplace(fullCmd, cmdTranslation); |
| | if (isCollisionFree(m_mainCommands, cmdTranslation, action, m_actionToCommand.count(action) ? m_actionToCommand[action] : QString{})) { |
| | m_mainCommands.emplace(cmdTranslation, action); |
| | m_actionToCommand.emplace(action, cmdTranslation); |
| | } |
| | } |
| | for(auto const& [fullCmd, cmdTranslation]: fullCmdList){ |
| | if(isCollisionFree(m_mainCommands, fullCmd, action, m_actionToCommand.count(action) ? m_actionToCommand[action] : QString{})) { |
| | |
| | m_mainCommands.emplace(fullCmd, action); |
| | m_actionToCommand.emplace(action, fullCmd); |
| | } |
| | } |
| | |
| | for(auto const& [alias, aliasTranslation]: aliasList){ |
| | if (alias == aliasTranslation) |
| | continue; |
| | |
| | if(isCollisionFree(m_cmdTranslation, alias, aliasTranslation)) |
| | m_cmdTranslation.emplace(alias, aliasTranslation); |
| | if(isCollisionFree(m_shortCommands, aliasTranslation, action, m_actionToCommand.count(action) ? m_actionToCommand[action] : QString{})) { |
| | m_shortCommands.emplace(aliasTranslation, action); |
| | if (m_actionToCommand.count(action) == 0) |
| | m_actionToCommand.emplace(action, aliasTranslation); |
| | } |
| | } |
| | for(auto const& [alias, aliasTranslation]: aliasList){ |
| | if(isCollisionFree(m_shortCommands, alias, action, m_actionToCommand.count(action) ? m_actionToCommand[action] : QString{})) { |
| | |
| | m_shortCommands.emplace(alias, action); |
| | if (m_actionToCommand.count(action) == 0) |
| | m_actionToCommand.emplace(action, aliasTranslation); |
| | } |
| | } |
| | } |
| |
|
| | |
| | for(auto const& [command, translation]: g_transList) { |
| | m_cmdTranslation[command] = translation; |
| | } |
| |
|
| | |
| | for (const auto& [command, translation]: m_cmdTranslation) { |
| | m_revTranslation[translation] = command; |
| | if (m_shortCommands.count(translation) == 1) |
| | m_shortCommands[command] = m_shortCommands[translation]; |
| | } |
| |
|
| | |
| | for(const auto& [command, action]: m_mainCommands) { |
| | m_actionToCommand[action] = command; |
| | } |
| | } |
| |
|
| | QString RS_Commands::getAliasFile() |
| | { |
| | QString settingsDir = LC_GET_ONE_STR("Paths","OtherSettingsDir", RS_System::instance()->getAppDataDir()).trimmed(); |
| | if (settingsDir.isEmpty()) { |
| | LC_ERR << __func__ << "(): line "<<__LINE__<<": empty alias folder name: aborting"; |
| | return {}; |
| | } |
| | QString aliasName = settingsDir + "/librecad.alias"; |
| | return aliasName; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | void RS_Commands::updateAlias() |
| | { |
| | LC_LOG << __func__ << "(): begin"; |
| |
|
| | QString aliasName = getAliasFile(); |
| | if (aliasName.isEmpty()) { |
| | LC_ERR << __func__ << "(): line "<<__LINE__<<": empty alias folder name: aborting"; |
| | return; |
| | } |
| |
|
| | std::map<QString, QString> aliasList = readAliasFile(aliasName); |
| | if (aliasList.empty()) { |
| | |
| | LC_ERR<<"Writing alias file"; |
| | writeAliasFile(aliasName, m_shortCommands, m_mainCommands); |
| | } |
| |
|
| | |
| |
|
| | |
| | for(auto const& [alias, cmd]: aliasList){ |
| | |
| | if(m_mainCommands.count(alias) == 1) { |
| | LC_ERR<<__func__<<"(): "<<QObject::tr("cannot change meaning of commands. Refused to reuse command %1 to mean %2").arg(alias, cmd); |
| | continue; |
| | } |
| |
|
| | if(m_mainCommands.count(cmd) == 1){ |
| | RS_DEBUG->print("adding command alias: %s\t%s\n", alias.toStdString().c_str(), cmd.toStdString().c_str()); |
| | m_shortCommands[alias]=m_mainCommands[cmd]; |
| | }else if(m_cmdTranslation.count(cmd) == 1){ |
| | RS_DEBUG->print("adding command alias: %s\t%s\n", alias.toStdString().c_str(), m_cmdTranslation[cmd].toStdString().c_str()); |
| | m_shortCommands[alias]=m_mainCommands[m_cmdTranslation[cmd]]; |
| | } |
| | } |
| | LC_LOG << __func__ << "(): done"; |
| | } |
| |
|
| | std::map<QString, QString> RS_Commands::readAliasFile(const QString& aliasName) |
| | { |
| | LC_ERR<<__func__<<"(): Command alias file: "<<aliasName; |
| | std::map<QString, QString> aliasList; |
| |
|
| | QFile aliasFile{aliasName}; |
| | if (!aliasFile.exists() || !aliasFile.open(QIODevice::ReadOnly)) |
| | return aliasList; |
| |
|
| | |
| | QTextStream ts(&aliasFile); |
| | |
| | while(!ts.atEnd()) |
| | { |
| | |
| | static QRegularExpression re(R"(\s)"); |
| | QStringList txtList=ts.readLine().trimmed().split(re, Qt::SkipEmptyParts); |
| | if (txtList.size() < 2 || txtList.front().startsWith('#') || txtList[0] == txtList[1]) |
| | continue; |
| |
|
| | const QString& alias = txtList[0]; |
| | const QString& cmd = txtList[1]; |
| | const RS2::ActionType action = commandToAction(cmd); |
| | if (action == RS2::ActionNone) { |
| | LC_ERR<<__func__<<"(): "<<QObject::tr("requesting alias(%1) for unknown command(%2): ignored").arg(alias, cmd); |
| | continue; |
| | } |
| |
|
| | |
| | if (m_actionToCommand.count(action) == 0) |
| | m_actionToCommand[action] = cmd; |
| | |
| | const RS2::ActionType actionAlias = commandToAction(alias); |
| | if (actionAlias != action && actionAlias != RS2::ActionNone) { |
| | LC_ERR<<__func__<<"(): "<<QObject::tr("reusing an existing alias: was %1=%2, changed to %1=%3").arg(alias, m_actionToCommand.at(actionAlias), m_actionToCommand[action]); |
| | } |
| | if (alias != m_actionToCommand[action]) { |
| | aliasList.emplace(alias, cmd); |
| | } else { |
| | |
| | LC_ERR<<__func__<<"(): "<<QObject::tr("cannot change meaning of commands. Refused to reuse command %1 to mean %2").arg(alias, cmd); |
| | } |
| | } |
| | return aliasList; |
| | } |
| |
|
| | RS2::ActionType RS_Commands::commandToAction(const QString& command) const |
| | { |
| | if (m_mainCommands.count(command)==1) |
| | return m_mainCommands.at(command); |
| | if (m_shortCommands.count(command)==1) |
| | return m_shortCommands.at(command); |
| | if (m_cmdTranslation.count(command) == 1) { |
| | QString translated = m_cmdTranslation.at(command); |
| | if (m_mainCommands.count(translated) == 1) |
| | return m_mainCommands.at(translated); |
| | if (m_shortCommands.count(translated) == 1) |
| | return m_shortCommands.at(translated); |
| | } |
| | return RS2::ActionNone; |
| | } |
| |
|
| | |
| | |
| | |
| | QStringList RS_Commands::complete(const QString& cmd) const { |
| | QStringList ret; |
| | for(auto const& p: m_mainCommands){ |
| | if(p.first.startsWith(cmd, Qt::CaseInsensitive)){ |
| | ret << p.first; |
| | } |
| | } |
| | ret.sort(); |
| |
|
| | return ret; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS2::ActionType RS_Commands::cmdToAction(const QString& cmd, bool verbose) const { |
| | QString full = cmd.toLower(); |
| | RS2::ActionType ret = RS2::ActionNone; |
| |
|
| | |
| | for(const auto& table: {m_mainCommands, m_shortCommands}) |
| | { |
| | if (table.count(cmd)) { |
| | ret = table.at(cmd); |
| | break; |
| | } |
| | } |
| | if (ret==RS2::ActionNone) |
| | return ret; |
| |
|
| | if (!verbose) return ret; |
| | |
| | for(auto const& p: m_mainCommands){ |
| | if(p.second==ret){ |
| | RS_DEBUG->print("RS_Commands::cmdToAction: commandMessage"); |
| | |
| | |
| | |
| | RS_DEBUG->print("RS_Commands::cmdToAction: " |
| | "commandMessage: ok"); |
| | return ret; |
| | } |
| | } |
| | RS_DEBUG->print(QObject::tr("RS_Commands:: command not found: %1").arg(full).toStdString().c_str()); |
| | return ret; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | RS2::ActionType RS_Commands::keycodeToAction(const QString& code) const { |
| | if(code.size() < 1) |
| | return RS2::ActionNone; |
| |
|
| | if(!(code.startsWith(g_FnPrefix) || |
| | code.startsWith(g_AltPrefix) || |
| | code.startsWith(g_MetaPrefix))) { |
| | if(code.size() < 1 || code.contains(QRegularExpression("^[a-zA-Z].*")) == false ) |
| | return RS2::ActionNone; |
| | } |
| |
|
| | auto action = commandToAction(code); |
| |
|
| | if (action != RS2::ActionNone) { |
| | |
| | const QString& cmd = (m_actionToCommand.count(action) == 1) ? m_actionToCommand.at(action) : QString{}; |
| | |
| | RS_DIALOGFACTORY->commandMessage(QObject::tr("keycode: %1 (%2)").arg(code).arg(cmd)); |
| | } else { |
| | RS_DIALOGFACTORY->commandMessage(QObject::tr("invalid keycode: %1").arg(code)); |
| | } |
| |
|
| | return action; |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | QString RS_Commands::command(const QString& cmd) { |
| | auto it= instance()->m_cmdTranslation.find(cmd); |
| | if(it != instance()->m_cmdTranslation.end()){ |
| | return instance()->m_cmdTranslation[cmd]; |
| | } |
| | RS_DIALOGFACTORY->commandMessage(QObject::tr("Command not found: %1").arg(cmd)); |
| | RS_DEBUG->print(RS_Debug::D_WARNING, |
| | "RS_Commands::command: command '%s' unknown", cmd.toLatin1().data()); |
| | return ""; |
| | } |
| |
|
| |
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | bool RS_Commands::checkCommand(const QString& cmd, const QString& str, |
| | RS2::ActionType ) { |
| |
|
| | QString const& strl = str.toLower(); |
| | QString const& cmdLower = cmd.toLower(); |
| | auto it = instance()->m_cmdTranslation.find(cmdLower); |
| | if(it != instance()->m_cmdTranslation.end()){ |
| | RS2::ActionType type0=instance()->cmdToAction(it->second, false); |
| | if( type0 != RS2::ActionNone ) { |
| | return type0 ==instance()->cmdToAction(strl); |
| | } |
| | } |
| |
|
| | it = instance()->m_cmdTranslation.find(strl); |
| | if(it != instance()->m_cmdTranslation.end()) return it->second == cmdLower; |
| | return false; |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | QString RS_Commands::msgAvailableCommands() { |
| | return QObject::tr("Available commands:"); |
| | } |
| |
|