diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp index 11cb682cf..92bda0e83 100644 --- a/src/common/CompletionModel.cpp +++ b/src/common/CompletionModel.cpp @@ -163,7 +163,7 @@ void CompletionModel::refresh(const QString &prefix) } // Commands - for (auto &command : getApp()->commands->items.getVector()) + for (auto &command : getApp()->commands->items_.getVector()) { addString(command.name, TaggedString::Command); } diff --git a/src/controllers/commands/Command.hpp b/src/controllers/commands/Command.hpp index 043030bbe..6c0bbea12 100644 --- a/src/controllers/commands/Command.hpp +++ b/src/controllers/commands/Command.hpp @@ -1,6 +1,9 @@ #pragma once +#include "util/RapidjsonHelpers.hpp" + #include +#include namespace chatterino { @@ -16,3 +19,49 @@ struct Command { }; } // namespace chatterino + +namespace pajlada { + +template <> +struct Serialize { + static rapidjson::Value get(const chatterino::Command &value, + rapidjson::Document::AllocatorType &a) + { + rapidjson::Value ret(rapidjson::kObjectType); + + chatterino::rj::set(ret, "name", value.name, a); + chatterino::rj::set(ret, "func", value.func, a); + + return ret; + } +}; + +template <> +struct Deserialize { + static chatterino::Command get(const rapidjson::Value &value, + bool *error = nullptr) + { + chatterino::Command command; + + if (!value.IsObject()) + { + PAJLADA_REPORT_ERROR(error); + return command; + } + + if (!chatterino::rj::getSafe(value, "name", command.name)) + { + PAJLADA_REPORT_ERROR(error); + return command; + } + if (!chatterino::rj::getSafe(value, "func", command.func)) + { + PAJLADA_REPORT_ERROR(error); + return command; + } + + return command; + } +}; + +} // namespace pajlada diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 415d92d5a..5989d962a 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -33,12 +33,13 @@ namespace chatterino { -CommandController::CommandController() +void CommandController::initialize(Settings &, Paths &paths) { + // Update commands map when the vector of commands has been updated auto addFirstMatchToMap = [this](auto args) { this->commandsMap_.remove(args.item.name); - for (const Command &cmd : this->items.getVector()) + for (const Command &cmd : this->items_.getVector()) { if (cmd.name == args.item.name) { @@ -47,70 +48,51 @@ CommandController::CommandController() } } }; + this->items_.itemInserted.connect(addFirstMatchToMap); + this->items_.itemRemoved.connect(addFirstMatchToMap); - this->items.itemInserted.connect(addFirstMatchToMap); - this->items.itemRemoved.connect(addFirstMatchToMap); -} + // Initialize setting manager for commands.json + auto path = combinePath(paths.settingsDirectory, "commands.json"); + this->sm_ = std::make_shared(); + this->sm_->setPath(path.toStdString()); -void CommandController::initialize(Settings &, Paths &paths) -{ - this->load(paths); -} + // Delayed initialization of the setting storing all commands + this->commandsSetting_.reset( + new pajlada::Settings::Setting>("/commands", + this->sm_)); -void CommandController::load(Paths &paths) -{ - this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt"); + // Update the setting when the vector of commands has been updated (most + // likely from the settings dialog) + this->items_.delayedItemsChanged.connect([this] { // + this->commandsSetting_->setValue(this->items_.getVector()); + }); - QFile textFile(this->filePath_); - if (!textFile.open(QIODevice::ReadOnly)) + // Load commands from commands.json + this->sm_->load(); + + // Add loaded commands to our vector of commands (which will update the map + // of commands) + for (const auto &command : this->commandsSetting_->getValue()) { - // No commands file created yet - return; + this->items_.appendItem(command); } - - 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)) - { - 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(); + this->sm_->save(); } CommandModel *CommandController::createModel(QObject *parent) { CommandModel *model = new CommandModel(parent); - model->init(&this->items); + model->init(&this->items_); return model; } -QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr channel, - bool dryRun) +QString CommandController::execCommand(const QString &textNoEmoji, + ChannelPtr channel, bool dryRun) { QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji); QStringList words = text.split(' ', QString::SkipEmptyParts); @@ -156,10 +138,12 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch const auto &ffzemotes = app->twitch.server->getFfzEmotes(); auto flags = MessageElementFlags(); auto emote = boost::optional{}; - for (int i = 2; i < words.length(); i++) { + for (int i = 2; i < words.length(); i++) + { { // twitch emote auto it = accemotes.emotes.find({words[i]}); - if (it != accemotes.emotes.end()) { + if (it != accemotes.emotes.end()) + { b.emplace( it->second, MessageElementFlag::TwitchEmote); continue; @@ -167,19 +151,24 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch } // twitch emote { // bttv/ffz emote - if ((emote = bttvemotes.emote({words[i]}))) { + if ((emote = bttvemotes.emote({words[i]}))) + { flags = MessageElementFlag::BttvEmote; - } else if ((emote = ffzemotes.emote({words[i]}))) { + } + else if ((emote = ffzemotes.emote({words[i]}))) + { flags = MessageElementFlag::FfzEmote; } - if (emote) { + if (emote) + { b.emplace(emote.get(), flags); continue; } } // bttv/ffz emote { // emoji/text for (auto &variant : - app->emotes->emojis.parse(words[i])) { + app->emotes->emojis.parse(words[i])) + { constexpr const static struct { void operator()(EmotePtr emote, MessageBuilder &b) const @@ -207,7 +196,8 @@ QString CommandController::execCommand(const QString &textNoEmoji, ChannelPtr ch app->twitch.server->sendMessage("jtv", text); - if (getSettings()->inlineWhispers) { + if (getSettings()->inlineWhispers) + { app->twitch.server->forEachChannel( [&messagexD](ChannelPtr _channel) { _channel->addMessage(messagexD); diff --git a/src/controllers/commands/CommandController.hpp b/src/controllers/commands/CommandController.hpp index f7b43b8f6..45be8be32 100644 --- a/src/controllers/commands/CommandController.hpp +++ b/src/controllers/commands/CommandController.hpp @@ -1,14 +1,16 @@ #pragma once +#include "common/ChatterinoSetting.hpp" +#include "common/SignalVector.hpp" #include "common/Singleton.hpp" +#include "controllers/commands/Command.hpp" #include +#include + #include #include -#include "common/SignalVector.hpp" -#include "controllers/commands/Command.hpp" - namespace chatterino { class Settings; @@ -20,26 +22,31 @@ class CommandModel; class CommandController final : public Singleton { public: - CommandController(); + UnsortedSignalVector items_; QString execCommand(const QString &text, std::shared_ptr channel, bool dryRun); QStringList getDefaultTwitchCommandList(); - virtual void initialize(Settings &settings, Paths &paths) override; + virtual void initialize(Settings &, Paths &paths) override; virtual void save() override; CommandModel *createModel(QObject *parent); - UnsortedSignalVector items; - private: void load(Paths &paths); QMap commandsMap_; std::mutex mutex_; - QString filePath_; + + std::shared_ptr sm_; + // Because the setting manager is not initialized until the initialize + // function is called (and not in the constructor), we have to + // late-initialize the setting, which is why we're storing it as a + // unique_ptr + std::unique_ptr>> + commandsSetting_; QString execCustomCommand(const QStringList &words, const Command &command); }; diff --git a/src/widgets/settingspages/CommandPage.cpp b/src/widgets/settingspages/CommandPage.cpp index b06988a57..2000ce6b1 100644 --- a/src/widgets/settingspages/CommandPage.cpp +++ b/src/widgets/settingspages/CommandPage.cpp @@ -41,7 +41,7 @@ CommandPage::CommandPage() view->setTitles({"Trigger", "Command"}); view->getTableView()->horizontalHeader()->setStretchLastSection(true); view->addButtonPressed.connect([] { - getApp()->commands->items.appendItem( + getApp()->commands->items_.appendItem( Command{"/command", "I made a new command HeyGuys"}); });