#include "CommandPage.hpp" #include "Application.hpp" #include "controllers/commands/Command.hpp" #include "controllers/commands/CommandController.hpp" #include "controllers/commands/CommandModel.hpp" #include "singletons/Settings.hpp" #include "util/CombinePath.hpp" #include "util/LayoutCreator.hpp" #include "util/Qt.hpp" #include "util/StandardItemHelper.hpp" #include "widgets/helper/EditableModelView.hpp" #include #include #include #include #include #include #include // clang-format off #define TEXT "{1} => first word     {1+} => first word and after     {{ => {     more info" // clang-format on namespace { using namespace chatterino; QString c1settingsPath() { return combinePath(qgetenv("appdata"), "Chatterino\\Custom\\Commands.txt"); } void checkCommandDuplicates(EditableModelView *view, QLabel *duplicateWarning) { bool foundDuplicateTrigger = false; // Maps command triggers to model row indices std::unordered_map> commands; for (int i = 0; i < view->getModel()->rowCount(); i++) { QString commandTrigger = view->getModel()->index(i, 0).data().toString(); commands[commandTrigger].push_back(i); } for (const auto &[commandTrigger, rowIndices] : commands) { assert(!rowIndices.empty()); if (rowIndices.size() > 1) { foundDuplicateTrigger = true; for (const auto &rowIndex : rowIndices) { view->getModel()->setData(view->getModel()->index(rowIndex, 0), QColor("yellow"), Qt::ForegroundRole); } } else { view->getModel()->setData(view->getModel()->index(rowIndices[0], 0), QColor("white"), Qt::ForegroundRole); } } if (foundDuplicateTrigger) { duplicateWarning->show(); } else { duplicateWarning->hide(); } } } // namespace namespace chatterino { CommandPage::CommandPage() { LayoutCreator layoutCreator(this); auto layout = layoutCreator.setLayoutType(); auto *view = layout .emplace( getIApp()->getCommands()->createModel(nullptr)) .getElement(); view->setTitles({"Trigger", "Command", "Show In\nMessage Menu"}); view->getTableView()->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch); // We can safely ignore this signal connection since we own the view std::ignore = view->addButtonPressed.connect([] { getIApp()->getCommands()->items.append( Command{"/command", "I made a new command HeyGuys"}); }); // TODO: asyncronously check path if (QFile(c1settingsPath()).exists()) { auto *button = new QPushButton("Import commands from Chatterino 1"); view->addCustomButton(button); QObject::connect(button, &QPushButton::clicked, this, [] { QFile c1settings = c1settingsPath(); c1settings.open(QIODevice::ReadOnly); for (auto line : QString(c1settings.readAll()) .split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts)) { if (int index = line.indexOf(' '); index != -1) { getIApp()->getCommands()->items.insert( Command(line.mid(0, index), line.mid(index + 1))); } } }); } layout.append( this->createCheckBox("Also match the trigger at the end of the message", getSettings()->allowCommandsAtEnd)); QLabel *text = layout.emplace(TEXT).getElement(); text->setWordWrap(true); text->setStyleSheet("color: #bbb"); text->setOpenExternalLinks(true); auto *duplicateWarning = layout .emplace("Multiple commands with the same trigger found. " "Only one of the commands will work.") .getElement(); duplicateWarning->setStyleSheet("color: yellow"); // NOTE: These signals mean that the duplicate check happens in the middle of a row being moved, where he index can be wrong. // This should be reconsidered, or potentially changed in the signalvectormodel. Or maybe we rely on a SignalVectorModel signal instead QObject::connect(view->getModel(), &QAbstractItemModel::rowsInserted, this, [view, duplicateWarning]() { checkCommandDuplicates(view, duplicateWarning); }); QObject::connect(view->getModel(), &QAbstractItemModel::rowsRemoved, this, [view, duplicateWarning]() { checkCommandDuplicates(view, duplicateWarning); }); QObject::connect(view->getModel(), &QAbstractItemModel::dataChanged, this, [view, duplicateWarning](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { (void)topLeft; (void)bottomRight; if (roles.contains(Qt::EditRole)) { checkCommandDuplicates(view, duplicateWarning); } }); checkCommandDuplicates(view, duplicateWarning); // ---- end of layout this->commandsEditTimer_.setSingleShot(true); } } // namespace chatterino