diff --git a/chatterino.pro b/chatterino.pro index b8417b51b..1e400b331 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -186,7 +186,9 @@ SOURCES += \ src/widgets/settingspages/externaltoolspage.cpp \ src/widgets/helper/comboboxitemdelegate.cpp \ src/util/signalvectormodel.cpp \ - src/managers/commands/command.cpp + src/controllers/commands/command.cpp \ + src/controllers/commands/commandmodel.cpp \ + src/controllers/commands/commandcontroller.cpp HEADERS += \ src/precompiled_header.hpp \ @@ -320,7 +322,9 @@ HEADERS += \ src/util/assertinguithread.hpp \ src/util/signalvector2.hpp \ src/util/signalvectormodel.hpp \ - src/managers/commands/command.hpp + src/controllers/commands/command.hpp \ + src/controllers/commands/commandmodel.hpp \ + src/controllers/commands/commandcontroller.hpp RESOURCES += \ resources/resources.qrc diff --git a/src/application.cpp b/src/application.cpp index e59a22c1e..e2b8ce1f4 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,9 +1,9 @@ #include "application.hpp" +#include "controllers/commands/commandcontroller.hpp" #include "providers/twitch/pubsub.hpp" #include "providers/twitch/twitchserver.hpp" #include "singletons/accountmanager.hpp" -#include "singletons/commandmanager.hpp" #include "singletons/emotemanager.hpp" #include "singletons/fontmanager.hpp" #include "singletons/loggingmanager.hpp" @@ -63,7 +63,7 @@ void Application::construct() this->themes = new singletons::ThemeManager; this->windows = new singletons::WindowManager; this->logging = new singletons::LoggingManager; - this->commands = new singletons::CommandManager; + this->commands = new controllers::commands::CommandController; this->accounts = new singletons::AccountManager; this->emotes = new singletons::EmoteManager; this->settings = new singletons::SettingManager; diff --git a/src/application.hpp b/src/application.hpp index 071eedc97..55fe778bf 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -16,13 +16,18 @@ class PubSub; } // namespace twitch } // namespace providers +namespace controllers { +namespace commands { +class CommandController; +} +} + namespace singletons { class ThemeManager; class WindowManager; class LoggingManager; class PathManager; -class CommandManager; class AccountManager; class EmoteManager; class NativeMessagingManager; @@ -53,7 +58,7 @@ public: singletons::ThemeManager *themes = nullptr; singletons::WindowManager *windows = nullptr; singletons::LoggingManager *logging = nullptr; - singletons::CommandManager *commands = nullptr; + controllers::commands::CommandController *commands = nullptr; singletons::AccountManager *accounts = nullptr; singletons::EmoteManager *emotes = nullptr; singletons::NativeMessagingManager *nativeMessaging = nullptr; diff --git a/src/controllers/commands/command.cpp b/src/controllers/commands/command.cpp new file mode 100644 index 000000000..f66d1d9d4 --- /dev/null +++ b/src/controllers/commands/command.cpp @@ -0,0 +1,34 @@ +#include "command.hpp" + +namespace chatterino { +namespace controllers { +namespace commands { + +// command +Command::Command(const QString &_text) +{ + int index = _text.indexOf(' '); + + if (index == -1) { + this->name = _text; + return; + } + + this->name = _text.mid(0, index); + this->func = _text.mid(index + 1); +} + +Command::Command(const QString &_name, const QString &_func) + : name(_name) + , func(_func) +{ +} + +QString Command::toString() const +{ + return this->name + " " + this->func; +} + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/commands/command.hpp b/src/controllers/commands/command.hpp new file mode 100644 index 000000000..0495a4cd7 --- /dev/null +++ b/src/controllers/commands/command.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace chatterino { +namespace controllers { +namespace commands { + +struct Command { + QString name; + QString func; + + Command() = default; + explicit Command(const QString &text); + Command(const QString &name, const QString &func); + + QString toString() const; +}; + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/commands/commandcontroller.cpp b/src/controllers/commands/commandcontroller.cpp new file mode 100644 index 000000000..3368a0a0c --- /dev/null +++ b/src/controllers/commands/commandcontroller.cpp @@ -0,0 +1,245 @@ +#include "commandcontroller.hpp" + +#include "application.hpp" +#include "controllers/commands/command.hpp" +#include "controllers/commands/commandmodel.hpp" +#include "messages/messagebuilder.hpp" +#include "providers/twitch/twitchchannel.hpp" +#include "providers/twitch/twitchserver.hpp" +#include "singletons/accountmanager.hpp" +#include "singletons/pathmanager.hpp" +#include "util/signalvector2.hpp" + +#include +#include +#include + +using namespace chatterino::providers::twitch; + +namespace chatterino { +namespace controllers { +namespace commands { + +CommandController::CommandController() +{ + auto addFirstMatchToMap = [this](auto args) { + this->commandsMap.remove(args.item.name); + + for (const Command &cmd : this->items.getVector()) { + if (cmd.name == args.item.name) { + this->commandsMap[cmd.name] = cmd; + break; + } + } + }; + + this->items.itemInserted.connect(addFirstMatchToMap); + this->items.itemRemoved.connect(addFirstMatchToMap); +} + +void CommandController::load() +{ + auto app = getApp(); + this->filePath = app->paths->customFolderPath + "/Commands.txt"; + + QFile textFile(this->filePath); + if (!textFile.open(QIODevice::ReadOnly)) { + // No commands file created yet + return; + } + + QList test = textFile.readAll().split('\n'); + + for (const auto &command : test) { + if (command.isEmpty()) { + continue; + } + + this->items.appendItem(Command(command)); + } + + textFile.close(); +} + +void CommandController::save() +{ + QFile textFile(this->filePath); + if (!textFile.open(QIODevice::WriteOnly)) { + debug::Log("[CommandController::saveCommands] Unable to open {} for writing", + this->filePath); + return; + } + + for (const Command &cmd : this->items.getVector()) { + textFile.write((cmd.toString() + "\n").toUtf8()); + } + + textFile.close(); +} + +CommandModel *CommandController::createModel(QObject *parent) +{ + CommandModel *model = new CommandModel(parent); + model->init(&this->items); + + return model; +} + +QString CommandController::execCommand(const QString &text, ChannelPtr channel, bool dryRun) +{ + QStringList words = text.split(' ', QString::SkipEmptyParts); + Command command; + + { + std::lock_guard lock(this->mutex); + + if (words.length() == 0) { + return text; + } + + QString commandName = words[0]; + + // check if default command exists + auto *twitchChannel = dynamic_cast(channel.get()); + + if (!dryRun && twitchChannel != nullptr) { + if (commandName == "/debug-args") { + QString msg = QApplication::instance()->arguments().join(' '); + + channel->addMessage(messages::Message::createSystemMessage(msg)); + + return ""; + } else if (commandName == "/uptime") { + const auto &streamStatus = twitchChannel->GetStreamStatus(); + + QString messageText = + streamStatus.live ? streamStatus.uptime : "Channel is not live."; + + channel->addMessage(messages::Message::createSystemMessage(messageText)); + + return ""; + } else if (commandName == "/ignore" && words.size() >= 2) { + // fourtf: ignore user + // QString messageText; + + // if (IrcManager::getInstance().tryAddIgnoredUser(words.at(1), + // messageText)) { + // messageText = "Ignored user \"" + words.at(1) + "\"."; + // } + + // channel->addMessage(messages::Message::createSystemMessage(messageText)); + return ""; + } else if (commandName == "/unignore") { + // fourtf: ignore user + // QString messageText; + + // if (IrcManager::getInstance().tryRemoveIgnoredUser(words.at(1), + // messageText)) { + // messageText = "Ignored user \"" + words.at(1) + "\"."; + // } + + // channel->addMessage(messages::Message::createSystemMessage(messageText)); + return ""; + } else if (commandName == "/w") { + if (words.length() <= 2) { + return ""; + } + + auto app = getApp(); + + messages::MessageBuilder b; + + b.emplace(app->accounts->Twitch.getCurrent()->getUserName(), + messages::MessageElement::Text); + b.emplace("->", messages::MessageElement::Text); + b.emplace(words[1], messages::MessageElement::Text); + + QString rest = ""; + + for (int i = 2; i < words.length(); i++) { + rest += words[i]; + } + + b.emplace(rest, messages::MessageElement::Text); + + app->twitch.server->whispersChannel->addMessage(b.getMessage()); + } + } + + // check if custom command exists + auto it = this->commandsMap.find(commandName); + if (it == this->commandsMap.end()) { + return text; + } + + command = it.value(); + } + + return this->execCustomCommand(words, command); +} + +QString CommandController::execCustomCommand(const QStringList &words, const Command &command) +{ + QString result; + + static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}"); + + int lastCaptureEnd = 0; + + auto globalMatch = parseCommand.globalMatch(command.func); + int matchOffset = 0; + + while (true) { + QRegularExpressionMatch match = parseCommand.match(command.func, matchOffset); + + if (!match.hasMatch()) { + break; + } + + result += command.func.mid(lastCaptureEnd, match.capturedStart() - lastCaptureEnd + 1); + + lastCaptureEnd = match.capturedEnd(); + matchOffset = lastCaptureEnd - 1; + + QString wordIndexMatch = match.captured(3); + + bool plus = wordIndexMatch.at(wordIndexMatch.size() - 1) == '+'; + wordIndexMatch = wordIndexMatch.replace("+", ""); + + bool ok; + int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok); + if (!ok || wordIndex == 0) { + result += "{" + match.captured(3) + "}"; + continue; + } + + if (words.length() <= wordIndex) { + continue; + } + + if (plus) { + bool first = true; + for (int i = wordIndex; i < words.length(); i++) { + if (!first) { + result += " "; + } + result += words[i]; + first = false; + } + } else { + result += words[wordIndex]; + } + } + + result += command.func.mid(lastCaptureEnd); + + if (result.size() > 0 && result.at(0) == '{') { + result = result.mid(1); + } + + return result.replace("{{", "{"); +} + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/commands/commandcontroller.hpp b/src/controllers/commands/commandcontroller.hpp new file mode 100644 index 000000000..ececd8c9f --- /dev/null +++ b/src/controllers/commands/commandcontroller.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include "controllers/commands/command.hpp" +#include "util/signalvector2.hpp" + +namespace chatterino { +class Channel; + +namespace controllers { +namespace commands { + +class CommandModel; + +class CommandController +{ +public: + CommandController(); + + QString execCommand(const QString &text, std::shared_ptr channel, bool dryRun); + + void load(); + void save(); + + CommandModel *createModel(QObject *parent); + + util::UnsortedSignalVector items; + +private: + QMap commandsMap; + + std::mutex mutex; + QString filePath; + + QString execCustomCommand(const QStringList &words, const Command &command); +}; + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/commands/commandmodel.cpp b/src/controllers/commands/commandmodel.cpp new file mode 100644 index 000000000..15603d4c3 --- /dev/null +++ b/src/controllers/commands/commandmodel.cpp @@ -0,0 +1,42 @@ +#include "commandmodel.hpp" + +namespace chatterino { +namespace controllers { +namespace commands { + +// commandmodel +CommandModel::CommandModel(QObject *parent) + : util::SignalVectorModel(2, parent) +{ +} + +// turn a vector item into a model row +Command CommandModel::getItemFromRow(std::vector &row) +{ + return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString()); +} + +// turns a row in the model into a vector item +void CommandModel::getRowFromItem(const Command &item, std::vector &row) +{ + row[0]->setData(item.name, Qt::DisplayRole); + row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + row[1]->setData(item.func, Qt::DisplayRole); + row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); +} + +// returns the related index of the SignalVector +int CommandModel::getVectorIndexFromModelIndex(int index) +{ + return index; +} + +// returns the related index of the model +int CommandModel::getModelIndexFromVectorIndex(int index) +{ + return index; +} + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/controllers/commands/commandmodel.hpp b/src/controllers/commands/commandmodel.hpp new file mode 100644 index 000000000..07c1c5e9e --- /dev/null +++ b/src/controllers/commands/commandmodel.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "controllers/commands/command.hpp" +#include "util/signalvectormodel.hpp" + +namespace chatterino { +namespace controllers { +namespace commands { + +class CommandController; + +class CommandModel : public util::SignalVectorModel +{ + explicit CommandModel(QObject *parent); + +protected: + // turn a vector item into a model row + virtual Command getItemFromRow(std::vector &row) override; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const Command &item, std::vector &row) override; + + // returns the related index of the SignalVector + virtual int getVectorIndexFromModelIndex(int index) override; + + // returns the related index of the model + virtual int getModelIndexFromVectorIndex(int index) override; + + friend class CommandController; +}; + +} // namespace commands +} // namespace controllers +} // namespace chatterino diff --git a/src/managers/commands/command.cpp b/src/managers/commands/command.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/managers/commands/command.hpp b/src/managers/commands/command.hpp deleted file mode 100644 index d4b67fd99..000000000 --- a/src/managers/commands/command.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -namespace chatterino { -namespace managers { -namespace commands { -// code -} -} // namespace managers -} // namespace chatterino diff --git a/src/singletons/commandmanager.cpp b/src/singletons/commandmanager.cpp index 0ed1dcc02..dcb970864 100644 --- a/src/singletons/commandmanager.cpp +++ b/src/singletons/commandmanager.cpp @@ -1,294 +1,12 @@ -#include "singletons/commandmanager.hpp" +//#include "singletons/commandmanager.hpp" -#include "application.hpp" -#include "debug/log.hpp" -#include "messages/messagebuilder.hpp" -#include "providers/twitch/twitchserver.hpp" -#include "singletons/accountmanager.hpp" -#include "singletons/pathmanager.hpp" +//#include "application.hpp" +//#include "debug/log.hpp" -#include -#include -#include +//#include "channel.hpp" -#include "channel.hpp" -#include "providers/twitch/twitchchannel.hpp" +// namespace chatterino { +// namespace singletons { -using namespace chatterino::providers::twitch; - -namespace chatterino { -namespace singletons { - -CommandManager::CommandManager() -{ - auto addFirstMatchToMap = [this](auto args) { - this->commandsMap.remove(args.item.name); - - for (const Command &cmd : this->items.getVector()) { - if (cmd.name == args.item.name) { - this->commandsMap[cmd.name] = cmd; - break; - } - } - }; - - this->items.itemInserted.connect(addFirstMatchToMap); - this->items.itemRemoved.connect(addFirstMatchToMap); -} - -void CommandManager::load() -{ - auto app = getApp(); - this->filePath = app->paths->customFolderPath + "/Commands.txt"; - - QFile textFile(this->filePath); - if (!textFile.open(QIODevice::ReadOnly)) { - // No commands file created yet - return; - } - - QList test = textFile.readAll().split('\n'); - - for (const auto &command : test) { - if (command.isEmpty()) { - continue; - } - - this->items.appendItem(Command(command)); - } - - textFile.close(); -} - -void CommandManager::save() -{ - QFile textFile(this->filePath); - if (!textFile.open(QIODevice::WriteOnly)) { - debug::Log("[CommandManager::saveCommands] Unable to open {} for writing", this->filePath); - return; - } - - for (const Command &cmd : this->items.getVector()) { - textFile.write((cmd.toString() + "\n").toUtf8()); - } - - textFile.close(); -} - -CommandModel *CommandManager::createModel(QObject *parent) -{ - CommandModel *model = new CommandModel(parent); - model->init(&this->items); - - return model; -} - -QString CommandManager::execCommand(const QString &text, ChannelPtr channel, bool dryRun) -{ - QStringList words = text.split(' ', QString::SkipEmptyParts); - Command command; - - { - std::lock_guard lock(this->mutex); - - if (words.length() == 0) { - return text; - } - - QString commandName = words[0]; - - // check if default command exists - auto *twitchChannel = dynamic_cast(channel.get()); - - if (!dryRun && twitchChannel != nullptr) { - if (commandName == "/debug-args") { - QString msg = QApplication::instance()->arguments().join(' '); - - channel->addMessage(messages::Message::createSystemMessage(msg)); - - return ""; - } else if (commandName == "/uptime") { - const auto &streamStatus = twitchChannel->GetStreamStatus(); - - QString messageText = - streamStatus.live ? streamStatus.uptime : "Channel is not live."; - - channel->addMessage(messages::Message::createSystemMessage(messageText)); - - return ""; - } else if (commandName == "/ignore" && words.size() >= 2) { - // fourtf: ignore user - // QString messageText; - - // if (IrcManager::getInstance().tryAddIgnoredUser(words.at(1), - // messageText)) { - // messageText = "Ignored user \"" + words.at(1) + "\"."; - // } - - // channel->addMessage(messages::Message::createSystemMessage(messageText)); - return ""; - } else if (commandName == "/unignore") { - // fourtf: ignore user - // QString messageText; - - // if (IrcManager::getInstance().tryRemoveIgnoredUser(words.at(1), - // messageText)) { - // messageText = "Ignored user \"" + words.at(1) + "\"."; - // } - - // channel->addMessage(messages::Message::createSystemMessage(messageText)); - return ""; - } else if (commandName == "/w") { - if (words.length() <= 2) { - return ""; - } - - auto app = getApp(); - - messages::MessageBuilder b; - - b.emplace(app->accounts->Twitch.getCurrent()->getUserName(), - messages::MessageElement::Text); - b.emplace("->", messages::MessageElement::Text); - b.emplace(words[1], messages::MessageElement::Text); - - QString rest = ""; - - for (int i = 2; i < words.length(); i++) { - rest += words[i]; - } - - b.emplace(rest, messages::MessageElement::Text); - - app->twitch.server->whispersChannel->addMessage(b.getMessage()); - } - } - - // check if custom command exists - auto it = this->commandsMap.find(commandName); - if (it == this->commandsMap.end()) { - return text; - } - - command = it.value(); - } - - return this->execCustomCommand(words, command); -} - -QString CommandManager::execCustomCommand(const QStringList &words, const Command &command) -{ - QString result; - - static QRegularExpression parseCommand("(^|[^{])({{)*{(\\d+\\+?)}"); - - int lastCaptureEnd = 0; - - auto globalMatch = parseCommand.globalMatch(command.func); - int matchOffset = 0; - - while (true) { - QRegularExpressionMatch match = parseCommand.match(command.func, matchOffset); - - if (!match.hasMatch()) { - break; - } - - result += command.func.mid(lastCaptureEnd, match.capturedStart() - lastCaptureEnd + 1); - - lastCaptureEnd = match.capturedEnd(); - matchOffset = lastCaptureEnd - 1; - - QString wordIndexMatch = match.captured(3); - - bool plus = wordIndexMatch.at(wordIndexMatch.size() - 1) == '+'; - wordIndexMatch = wordIndexMatch.replace("+", ""); - - bool ok; - int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok); - if (!ok || wordIndex == 0) { - result += "{" + match.captured(3) + "}"; - continue; - } - - if (words.length() <= wordIndex) { - continue; - } - - if (plus) { - bool first = true; - for (int i = wordIndex; i < words.length(); i++) { - if (!first) { - result += " "; - } - result += words[i]; - first = false; - } - } else { - result += words[wordIndex]; - } - } - - result += command.func.mid(lastCaptureEnd); - - if (result.size() > 0 && result.at(0) == '{') { - result = result.mid(1); - } - - return result.replace("{{", "{"); -} - -// commandmodel -CommandModel::CommandModel(QObject *parent) - : util::SignalVectorModel(2, parent) -{ -} - -int CommandModel::prepareVectorInserted(const Command &item, int index, - std::vector &rowToAdd) -{ - rowToAdd[0]->setData(item.name, Qt::EditRole); - rowToAdd[1]->setData(item.func, Qt::EditRole); - - return index; -} - -int CommandModel::prepareVectorRemoved(const Command &item, int index) -{ - UNUSED(item); - - return index; -} - -int CommandModel::prepareModelItemRemoved(int index) -{ - return index; -} - -// command -Command::Command(const QString &_text) -{ - int index = _text.indexOf(' '); - - if (index == -1) { - this->name = _text; - return; - } - - this->name = _text.mid(0, index); - this->func = _text.mid(index + 1); -} - -Command::Command(const QString &_name, const QString &_func) - : name(_name) - , func(_func) -{ -} - -QString Command::toString() const -{ - return this->name + " " + this->func; -} - -} // namespace singletons -} // namespace chatterino +//} // namespace singletons +//} // namespace chatterino diff --git a/src/singletons/commandmanager.hpp b/src/singletons/commandmanager.hpp index 2fc33b1b4..f0ff1510f 100644 --- a/src/singletons/commandmanager.hpp +++ b/src/singletons/commandmanager.hpp @@ -1,70 +1,25 @@ -#pragma once +//#pragma once -#include -#include +//#include +//#include -#include -#include +//#include +//#include -#include -#include +//#include +//#include -namespace chatterino { -class Channel; +// namespace chatterino { +// class Channel; -namespace singletons { +// namespace singletons { -class CommandManager; +//// +//// this class managed the custom /commands +//// +// class CommandManager +//{ +//}; -struct Command { - QString name; - QString func; - - Command() = default; - explicit Command(const QString &text); - Command(const QString &name, const QString &func); - - QString toString() const; -}; - -class CommandModel : public util::SignalVectorModel -{ - explicit CommandModel(QObject *parent); - -protected: - virtual int prepareVectorInserted(const Command &item, int index, - std::vector &rowToAdd) override; - virtual int prepareVectorRemoved(const Command &item, int index) override; - virtual int prepareModelItemRemoved(int index) override; - - friend class CommandManager; -}; - -// -// this class managed the custom /commands -// -class CommandManager -{ -public: - CommandManager(); - - QString execCommand(const QString &text, std::shared_ptr channel, bool dryRun); - - void load(); - void save(); - - CommandModel *createModel(QObject *parent); - - util::UnsortedSignalVector items; - -private: - QMap commandsMap; - - std::mutex mutex; - QString filePath; - - QString execCustomCommand(const QStringList &words, const Command &command); -}; - -} // namespace singletons -} // namespace chatterino +//} // namespace singletons +//} // namespace chatterino diff --git a/src/util/signalvector2.hpp b/src/util/signalvector2.hpp index 44a12b3fd..2d7617a96 100644 --- a/src/util/signalvector2.hpp +++ b/src/util/signalvector2.hpp @@ -57,7 +57,8 @@ template class BaseSignalVector : public ReadOnlySignalVector { public: - virtual void appendItem(const TVectorItem &item, void *caller = 0) = 0; + // returns the actual index of the inserted item + virtual int insertItem(const TVectorItem &item, int proposedIndex = -1, void *caller = 0) = 0; void removeItem(int index, void *caller = 0) { @@ -69,26 +70,31 @@ public: typename ReadOnlySignalVector::ItemArgs args{item, index, caller}; this->itemRemoved.invoke(args); } + + int appendItem(const TVectorItem &item, void *caller = 0) + { + return this->insertItem(item, -1, caller); + } }; template class UnsortedSignalVector : public BaseSignalVector { public: - void insertItem(const TVectorItem &item, int index, void *caller = 0) + virtual int insertItem(const TVectorItem &item, int index = -1, void *caller = 0) override { util::assertInGuiThread(); - assert(index >= 0 && index <= this->vector.size()); + if (index == -1) { + index = this->vector.size(); + } else { + assert(index >= 0 && index <= this->vector.size()); + } this->vector.insert(this->vector.begin() + index, item); typename ReadOnlySignalVector::ItemArgs args{item, index, caller}; this->itemInserted.invoke(args); - } - - virtual void appendItem(const TVectorItem &item, void *caller = 0) override - { - this->insertItem(item, this->vector.size(), caller); + return index; } }; @@ -96,7 +102,7 @@ template class SortedSignalVector : public BaseSignalVector { public: - virtual void appendItem(const TVectorItem &item, void *caller = 0) override + virtual int insertItem(const TVectorItem &item, int index = -1, void *caller = 0) override { util::assertInGuiThread(); @@ -105,6 +111,7 @@ public: this->vector.begin(); typename ReadOnlySignalVector::ItemArgs args{item, index, caller}; this->itemInserted.invoke(args); + return index; } }; diff --git a/src/util/signalvectormodel.hpp b/src/util/signalvectormodel.hpp index 9f5e812e3..2590991f3 100644 --- a/src/util/signalvectormodel.hpp +++ b/src/util/signalvectormodel.hpp @@ -27,13 +27,21 @@ public: this->vector = vec; auto insert = [this](const typename BaseSignalVector::ItemArgs &args) { + if (args.caller == this) { + return; + } + + // get row index + int row = this->getModelIndexFromVectorIndex(args.index); + assert(row >= 0 && row <= this->rows.size()); + + // get row items std::vector items; for (int i = 0; i < this->_columnCount; i++) { items.push_back(new QStandardItem()); } - int row = this->prepareVectorInserted(args.item, args.index, items); - assert(row >= 0 && row <= this->rows.size()); + this->getRowFromItem(args.item, items); // insert row this->beginInsertRows(QModelIndex(), row, row); @@ -51,7 +59,11 @@ public: this->managedConnect(vec->itemInserted, insert); this->managedConnect(vec->itemRemoved, [this](auto args) { - int row = this->prepareVectorRemoved(args.item, args.index); + if (args.caller == this) { + return; + } + + int row = this->getModelIndexFromVectorIndex(args.index); assert(row >= 0 && row <= this->rows.size()); // remove row @@ -93,7 +105,15 @@ public: virtual bool setData(const QModelIndex &index, const QVariant &value, int role) { - this->rows[index.row()].items[index.column()]->setData(value, role); + int row = index.row(), column = index.column(); + assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount); + + this->rows[row].items[column]->setData(value, role); + + int vecRow = this->getVectorIndexFromModelIndex(row); + this->vector->removeItem(vecRow, this); + TVectorItem item = this->getItemFromRow(this->rows[row].items); + this->vector->insertItem(item, vecRow, this); return true; } @@ -123,6 +143,14 @@ public: return true; } + Qt::ItemFlags flags(const QModelIndex &index) const + { + int row = index.row(), column = index.column(); + assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount); + + return this->rows[index.row()].items[index.column()]->flags(); + } + virtual QStandardItem *getItem(int row, int column) { assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount); @@ -134,26 +162,22 @@ public: { assert(row >= 0 && row <= this->rows.size()); - int signalVectorRow = this->prepareModelItemRemoved(row); + int signalVectorRow = this->getVectorIndexFromModelIndex(row); this->vector->removeItem(signalVectorRow); } protected: - // gets called when an item gets inserted into the SignalVector - // - // returns the index of that the row should be inserted into and edits the rowToAdd elements - // based on the item - virtual int prepareVectorInserted(const TVectorItem &item, int index, - std::vector &rowToAdd) = 0; - // gets called when an item gets removed from a SignalVector - // - // returns the index of the row in the model that should be removed - virtual int prepareVectorRemoved(const TVectorItem &item, int index) = 0; + // turn a vector item into a model row + virtual TVectorItem getItemFromRow(std::vector &row) = 0; + + // turns a row in the model into a vector item + virtual void getRowFromItem(const TVectorItem &item, std::vector &row) = 0; - // gets called when an item gets removed from the model - // // returns the related index of the SignalVector - virtual int prepareModelItemRemoved(int index) = 0; + virtual int getVectorIndexFromModelIndex(int index) = 0; + + // returns the related index of the model + virtual int getModelIndexFromVectorIndex(int index) = 0; private: struct Row { diff --git a/src/widgets/helper/splitinput.cpp b/src/widgets/helper/splitinput.cpp index 90416f15d..a549a38f5 100644 --- a/src/widgets/helper/splitinput.cpp +++ b/src/widgets/helper/splitinput.cpp @@ -1,7 +1,7 @@ #include "widgets/helper/splitinput.hpp" #include "application.hpp" -#include "singletons/commandmanager.hpp" +#include "controllers/commands/commandcontroller.hpp" #include "singletons/ircmanager.hpp" #include "singletons/settingsmanager.hpp" #include "singletons/thememanager.hpp" diff --git a/src/widgets/settingspages/commandpage.cpp b/src/widgets/settingspages/commandpage.cpp index beb7012b5..2864d8184 100644 --- a/src/widgets/settingspages/commandpage.cpp +++ b/src/widgets/settingspages/commandpage.cpp @@ -7,7 +7,8 @@ #include #include "application.hpp" -#include "singletons/commandmanager.hpp" +#include "controllers/commands/commandcontroller.hpp" +#include "controllers/commands/commandmodel.hpp" #include "util/layoutcreator.hpp" #include "util/standarditemhelper.hpp" //#include "widgets/helper/comboboxitemdelegate.hpp" @@ -49,7 +50,7 @@ CommandPage::CommandPage() auto add = buttons.emplace("Add"); QObject::connect(*add, &QPushButton::clicked, [model, view] { getApp()->commands->items.appendItem( - singletons::Command{"/command", "I made a new command HeyGuys"}); + controllers::commands::Command{"/command", "I made a new command HeyGuys"}); view->scrollToBottom(); });