From 05662be287f5acf072a30722b696ab70545b21d6 Mon Sep 17 00:00:00 2001 From: PTyTb Date: Sat, 21 Feb 2026 11:08:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5,=20=D1=85?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5,=20=D1=83=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TTW_Bot.pro | 2 + actionmanager.cpp | 62 ++++++++++++ actionmanager.h | 30 ++++++ object_script.TTW_Bot.Debug | 2 + object_script.TTW_Bot.Release | 2 + udatabase.cpp | 172 ++++++++++++++++++++++++++++++++++ udatabase.h | 18 +++- ugeneral.cpp | 169 +++++++++++++++++++++++++++++++++ ugeneral.h | 15 ++- 9 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 actionmanager.cpp create mode 100644 actionmanager.h diff --git a/TTW_Bot.pro b/TTW_Bot.pro index dd74d90..b690e28 100644 --- a/TTW_Bot.pro +++ b/TTW_Bot.pro @@ -21,6 +21,7 @@ RC_ICONS = ico\app_icon.ico INCLUDEPATH += $$PWD SOURCES += \ + actionmanager.cpp \ commandprocessor.cpp \ countermanager.cpp \ emoteprovider.cpp \ @@ -54,6 +55,7 @@ SOURCES += \ HEADERS += \ action.h \ + actionmanager.h \ badge.h \ commandprocessor.h \ countermanager.h \ diff --git a/actionmanager.cpp b/actionmanager.cpp new file mode 100644 index 0000000..6825fa4 --- /dev/null +++ b/actionmanager.cpp @@ -0,0 +1,62 @@ +#include "actionmanager.h" + +ActionManager::ActionManager(uDataBase *db, QObject *parent) + : QObject(parent) + , m_db(db) +{ +} + +bool ActionManager::addAction(const ActionData &action) +{ + if (!m_db) return false; + if (!m_db->saveAction(action)) return false; + + // Перезагрузим все действия, чтобы получить свежий ID + loadFromDatabase(); + emit dataChanged(); + return true; +} + +bool ActionManager::updateAction(int id, const ActionData &action) +{ + if (!m_db) return false; + if (!m_db->updateAction(id, action)) return false; + + for (int i = 0; i < m_actions.size(); ++i) { + if (m_actions[i].id == id) { + m_actions[i] = action; + m_actions[i].id = id; + break; + } + } + emit dataChanged(); + return true; +} + +bool ActionManager::deleteAction(int id) +{ + if (!m_db) return false; + if (!m_db->deleteAction(id)) return false; + + for (int i = 0; i < m_actions.size(); ++i) { + if (m_actions[i].id == id) { + m_actions.removeAt(i); + break; + } + } + emit dataChanged(); + return true; +} + +QList ActionManager::getAllActions() const +{ + return m_actions; +} + +bool ActionManager::loadFromDatabase() +{ + if (!m_db) return false; + m_actions = m_db->loadAllActions(); + emit dataChanged(); + return true; +} diff --git a/actionmanager.h b/actionmanager.h new file mode 100644 index 0000000..75c142a --- /dev/null +++ b/actionmanager.h @@ -0,0 +1,30 @@ +#ifndef ACTIONMANAGER_H +#define ACTIONMANAGER_H + +#include +#include "udatabase.h" + +class ActionManager : public QObject +{ + Q_OBJECT +public: + explicit ActionManager(uDataBase *db, QObject *parent = nullptr); + + bool addAction(const ActionData &action); + bool updateAction(int id, const ActionData &action); + bool deleteAction(int id); + QList getAllActions() const; + bool loadFromDatabase(); + +signals: + void actionAdded(const ActionData &action); + void actionUpdated(int id, const ActionData &action); + void actionRemoved(int id); + void dataChanged(); + +private: + uDataBase *m_db; + QList m_actions; +}; + +#endif // ACTIONMANAGER_H diff --git a/object_script.TTW_Bot.Debug b/object_script.TTW_Bot.Debug index 4820324..8817ed1 100644 --- a/object_script.TTW_Bot.Debug +++ b/object_script.TTW_Bot.Debug @@ -1,3 +1,4 @@ +debug/actionmanager.o debug/commandprocessor.o debug/countermanager.o debug/emoteprovider.o @@ -28,6 +29,7 @@ debug/userwidget.o debug/webserverchat.o debug/webservernotify.o debug/websocketclient.o +debug/moc_actionmanager.o debug/moc_commandprocessor.o debug/moc_countermanager.o debug/moc_emoteprovider.o diff --git a/object_script.TTW_Bot.Release b/object_script.TTW_Bot.Release index fc8e454..03b4f2b 100644 --- a/object_script.TTW_Bot.Release +++ b/object_script.TTW_Bot.Release @@ -1,3 +1,4 @@ +release/actionmanager.o release/commandprocessor.o release/countermanager.o release/emoteprovider.o @@ -28,6 +29,7 @@ release/userwidget.o release/webserverchat.o release/webservernotify.o release/websocketclient.o +release/moc_actionmanager.o release/moc_commandprocessor.o release/moc_countermanager.o release/moc_emoteprovider.o diff --git a/udatabase.cpp b/udatabase.cpp index 0943f27..25df3b2 100644 --- a/udatabase.cpp +++ b/udatabase.cpp @@ -1399,3 +1399,175 @@ bool uDataBase::deleteNotification(int port) return true; } + +bool uDataBase::createActionsTable() +{ + QSqlQuery query(m_db); + QString sql = + "CREATE TABLE IF NOT EXISTS actions (" + " id INTEGER PRIMARY KEY AUTOINCREMENT," + " type INTEGER NOT NULL," + " key_combination TEXT," + " audio_file TEXT," + " notification_title TEXT," + " notification_description TEXT," + " notification_image TEXT," + " notification_sound TEXT," + " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + ")"; + + if (!query.exec(sql)) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to create actions table:" << m_lastError; + return false; + } + return true; +} + +bool uDataBase::saveAction(const ActionData &action) +{ + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + // Создаём таблицу, если ещё нет + if (!tableExists("actions")) { + if (!createActionsTable()) + return false; + } + + QSqlQuery query(m_db); + query.prepare( + "INSERT INTO actions (" + " type, key_combination, audio_file, notification_title," + " notification_description, notification_image, notification_sound" + ") VALUES (" + " :type, :key_combination, :audio_file, :notification_title," + " :notification_description, :notification_image, :notification_sound" + ")" + ); + + query.bindValue(":type", action.type); + query.bindValue(":key_combination", action.keyCombination); + query.bindValue(":audio_file", action.audioFile); + query.bindValue(":notification_title", action.notificationTitle); + query.bindValue(":notification_description", action.notificationDescription); + query.bindValue(":notification_image", action.notificationImage); + query.bindValue(":notification_sound", action.notificationSound); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to save action:" << m_lastError; + return false; + } + return true; +} + +bool uDataBase::updateAction(int id, const ActionData &action) +{ + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + QSqlQuery query(m_db); + query.prepare( + "UPDATE actions SET " + " type = :type," + " key_combination = :key_combination," + " audio_file = :audio_file," + " notification_title = :notification_title," + " notification_description = :notification_description," + " notification_image = :notification_image," + " notification_sound = :notification_sound " + "WHERE id = :id" + ); + + query.bindValue(":type", action.type); + query.bindValue(":key_combination", action.keyCombination); + query.bindValue(":audio_file", action.audioFile); + query.bindValue(":notification_title", action.notificationTitle); + query.bindValue(":notification_description", action.notificationDescription); + query.bindValue(":notification_image", action.notificationImage); + query.bindValue(":notification_sound", action.notificationSound); + query.bindValue(":id", id); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to update action:" << m_lastError; + return false; + } + return true; +} + +bool uDataBase::deleteAction(int id) +{ + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + QSqlQuery query(m_db); + query.prepare("DELETE FROM actions WHERE id = :id"); + query.bindValue(":id", id); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to delete action:" << m_lastError; + return false; + } + return true; +} + +QList uDataBase::loadAllActions() +{ + QList actions; + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return actions; + } + + if (!tableExists("actions")) { + return actions; // таблицы нет – пустой список + } + + QSqlQuery query(m_db); + query.prepare("SELECT * FROM actions ORDER BY id"); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to load actions:" << m_lastError; + return actions; + } + + while (query.next()) { + ActionData a; + a.id = query.value("id").toInt(); + a.type = query.value("type").toInt(); + a.keyCombination = query.value("key_combination").toString(); + a.audioFile = query.value("audio_file").toString(); + a.notificationTitle = query.value("notification_title").toString(); + a.notificationDescription = query.value("notification_description").toString(); + a.notificationImage = query.value("notification_image").toString(); + a.notificationSound = query.value("notification_sound").toString(); + actions.append(a); + } + return actions; +} + +bool uDataBase::clearActionsTable() +{ + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + QSqlQuery query(m_db); + if (!query.exec("DELETE FROM actions")) { + m_lastError = query.lastError().text(); + return false; + } + // Сброс автоинкремента + query.exec("DELETE FROM sqlite_sequence WHERE name='actions'"); + return true; +} diff --git a/udatabase.h b/udatabase.h index 15c6e72..5554b3e 100644 --- a/udatabase.h +++ b/udatabase.h @@ -56,7 +56,16 @@ struct NotificationSettings { QDateTime createdAt; }; - +struct ActionData { + int id = -1; + int type = 0; // 0 - клавиши, 1 - звук, 2 - уведомление + QString keyCombination; + QString audioFile; + QString notificationTitle; + QString notificationDescription; + QString notificationImage; + QString notificationSound; +}; class uDataBase : public QObject { @@ -121,6 +130,13 @@ public: // Получение последней ошибки QString lastError() const; + bool createActionsTable(); + bool saveAction(const ActionData &action); + bool updateAction(int id, const ActionData &action); + bool deleteAction(int id); + QList loadAllActions(); + bool clearActionsTable(); + private: diff --git a/ugeneral.cpp b/ugeneral.cpp index 9bac989..33d0004 100644 --- a/ugeneral.cpp +++ b/ugeneral.cpp @@ -452,7 +452,22 @@ void uGeneral::initializeManagers() m_commandProcessor->setContext(context); } + m_actionManager = new ActionManager(db, this); + // Настройка таблицы sgActions + ui->sgActions->setColumnCount(2); + ui->sgActions->setHorizontalHeaderLabels({"Тип", "Параметры"}); + ui->sgActions->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->sgActions->setSelectionMode(QAbstractItemView::SingleSelection); + ui->sgActions->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->sgActions->horizontalHeader()->setStretchLastSection(true); + ui->sgActions->setColumnWidth(0, 120); + connect(m_actionManager, &ActionManager::dataChanged, this, &uGeneral::updateActionsTable); + // Устанавливаем фильтр событий для поля ввода комбинации клавиш + ui->edtKeyLine->installEventFilter(this); + // Загружаем действия из БД + m_actionManager->loadFromDatabase(); + updateActionsTable(); } @@ -3550,3 +3565,157 @@ void uGeneral::on_btnActionAudioOpen_clicked() } +bool uGeneral::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == ui->edtKeyLine && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + int key = keyEvent->key(); + Qt::KeyboardModifiers mods = keyEvent->modifiers(); + + // Игнорируем одиночные модификаторы + if (key == Qt::Key_Control || key == Qt::Key_Shift || key == Qt::Key_Alt || key == Qt::Key_Meta) { + return true; + } + + // Обработка Backspace – очистка поля + if (key == Qt::Key_Backspace) { + ui->edtKeyLine->clear(); + return true; + } + + // Формируем строку комбинации + QString sequence = QKeySequence(mods + key).toString(); + ui->edtKeyLine->setText(sequence); + return true; // событие обработано, дальше не передаём + } + return QMainWindow::eventFilter(obj, event); +} + + +void uGeneral::on_btnAddAction_clicked() +{ + ActionData action; + action.type = ui->cbActions->currentIndex(); + + // Заполняем данные в зависимости от типа + switch (action.type) { + case 0: // клавиши + action.keyCombination = ui->edtKeyLine->text(); + break; + case 1: // звук + action.audioFile = ui->edtActionAudio->text(); + break; + case 2: // уведомление + action.notificationTitle = ui->lineEdit_3->text(); + action.notificationDescription = ui->lineEdit_4->text(); + action.notificationImage = ui->edtActionPic->text(); + action.notificationSound = ui->edtActionSound->text(); + break; + } + + // Валидация (минимальная) + if (action.type == 0 && action.keyCombination.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Введите комбинацию клавиш"); + return; + } + if (action.type == 1 && action.audioFile.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Выберите аудиофайл"); + return; + } + if (action.type == 2 && action.notificationTitle.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Введите заголовок уведомления"); + return; + } + + m_actionManager->addAction(action); + +} + +void uGeneral::on_btnDelAction_clicked() +{ + int row = ui->sgActions->currentRow(); + if (row < 0) { + QMessageBox::warning(this, "Ошибка", "Выберите действие для удаления"); + return; + } + + int id = ui->sgActions->item(row, 0)->data(Qt::UserRole).toInt(); + if (QMessageBox::question(this, "Подтверждение", "Удалить выбранное действие?") == QMessageBox::Yes) { + m_actionManager->deleteAction(id); + if (m_currentActionId == id) { + m_currentActionId = -1; + clearActionInputs(); + } + } +} + +void uGeneral::on_sgActions_cellClicked(int row, int column) +{ + Q_UNUSED(column); + int id = ui->sgActions->item(row, 0)->data(Qt::UserRole).toInt(); + auto actions = m_actionManager->getAllActions(); + for (const ActionData &a : actions) { + if (a.id == id) { + m_currentActionId = id; + // Устанавливаем тип в комбобокс + ui->cbActions->setCurrentIndex(a.type); + // Заполняем поля + ui->edtKeyLine->setText(a.keyCombination); + ui->edtActionAudio->setText(a.audioFile); + ui->lineEdit_3->setText(a.notificationTitle); + ui->lineEdit_4->setText(a.notificationDescription); + ui->edtActionPic->setText(a.notificationImage); + ui->edtActionSound->setText(a.notificationSound); + break; + } + } +} + +void uGeneral::on_sgActions_cellDoubleClicked(int row, int column) +{ + // Можно сделать то же, что и при клике, либо открыть расширенное редактирование + on_sgActions_cellClicked(row, column); +} + +void uGeneral::updateActionsTable() +{ + ui->sgActions->setRowCount(0); + auto actions = m_actionManager->getAllActions(); + + for (const ActionData &a : actions) { + int row = ui->sgActions->rowCount(); + ui->sgActions->insertRow(row); + + QString typeStr; + QString paramStr; + switch (a.type) { + case 0: + typeStr = "Нажатие"; + paramStr = a.keyCombination; + break; + case 1: + typeStr = "Звук"; + paramStr = QFileInfo(a.audioFile).fileName(); + break; + case 2: + typeStr = "Уведомление"; + paramStr = a.notificationTitle; + break; + } + + QTableWidgetItem *typeItem = new QTableWidgetItem(typeStr); + typeItem->setData(Qt::UserRole, a.id); + ui->sgActions->setItem(row, 0, typeItem); + ui->sgActions->setItem(row, 1, new QTableWidgetItem(paramStr)); + } +} + +void uGeneral::clearActionInputs() +{ + ui->edtKeyLine->clear(); + ui->edtActionAudio->clear(); + ui->lineEdit_3->clear(); + ui->lineEdit_4->clear(); + ui->edtActionPic->clear(); + ui->edtActionSound->clear(); +} diff --git a/ugeneral.h b/ugeneral.h index 73a2ab3..bc35185 100644 --- a/ugeneral.h +++ b/ugeneral.h @@ -9,6 +9,7 @@ #include #include #include +#include "actionmanager.h" #include "commandprocessor.h" #include "countermanager.h" #include "fcreatechat.h" @@ -85,7 +86,7 @@ public: SoundManager *soundManager; // Менеджер звуков UserManager* getUserManager(); // Получение менеджера пользователей TTwAPI *twitchAPI; // API для работы с Twitch - + bool eventFilter(QObject *obj, QEvent *event) override; // Методы логирования и работы с командами void toCommands(QString command, QString response); @@ -374,6 +375,13 @@ private slots: void on_btnActionAudioOpen_clicked(); + void on_btnAddAction_clicked(); + void on_btnDelAction_clicked(); + void on_sgActions_cellClicked(int row, int column); + void on_sgActions_cellDoubleClicked(int row, int column); + void updateActionsTable(); + void clearActionInputs(); + public slots: // Установка статуса подключения к Twitch void setTwitchConnected(bool connected); @@ -403,7 +411,10 @@ private: QList m_timers; // Список таймеров int m_nextTimerId = 1; // Следующий ID таймера bool m_isTwitchConnected = false; // Статус подключения к Twitch - QVector m_rewards; + QVector m_rewards; + ActionManager *m_actionManager; + // возможно, сохраняем текущее редактируемое действие + int m_currentActionId = -1; // Менеджеры веб-серверов QList m_notificationServers; QList m_chatServers;