добавил создание, хранение, удаление действий

This commit is contained in:
2026-02-21 11:08:06 +03:00
parent b430b36e87
commit 05662be287
9 changed files with 469 additions and 3 deletions
+2
View File
@@ -21,6 +21,7 @@ RC_ICONS = ico\app_icon.ico
INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD
SOURCES += \ SOURCES += \
actionmanager.cpp \
commandprocessor.cpp \ commandprocessor.cpp \
countermanager.cpp \ countermanager.cpp \
emoteprovider.cpp \ emoteprovider.cpp \
@@ -54,6 +55,7 @@ SOURCES += \
HEADERS += \ HEADERS += \
action.h \ action.h \
actionmanager.h \
badge.h \ badge.h \
commandprocessor.h \ commandprocessor.h \
countermanager.h \ countermanager.h \
+62
View File
@@ -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<ActionData> ActionManager::getAllActions() const
{
return m_actions;
}
bool ActionManager::loadFromDatabase()
{
if (!m_db) return false;
m_actions = m_db->loadAllActions();
emit dataChanged();
return true;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include <QObject>
#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<ActionData> 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<ActionData> m_actions;
};
#endif // ACTIONMANAGER_H
+2
View File
@@ -1,3 +1,4 @@
debug/actionmanager.o
debug/commandprocessor.o debug/commandprocessor.o
debug/countermanager.o debug/countermanager.o
debug/emoteprovider.o debug/emoteprovider.o
@@ -28,6 +29,7 @@ debug/userwidget.o
debug/webserverchat.o debug/webserverchat.o
debug/webservernotify.o debug/webservernotify.o
debug/websocketclient.o debug/websocketclient.o
debug/moc_actionmanager.o
debug/moc_commandprocessor.o debug/moc_commandprocessor.o
debug/moc_countermanager.o debug/moc_countermanager.o
debug/moc_emoteprovider.o debug/moc_emoteprovider.o
+2
View File
@@ -1,3 +1,4 @@
release/actionmanager.o
release/commandprocessor.o release/commandprocessor.o
release/countermanager.o release/countermanager.o
release/emoteprovider.o release/emoteprovider.o
@@ -28,6 +29,7 @@ release/userwidget.o
release/webserverchat.o release/webserverchat.o
release/webservernotify.o release/webservernotify.o
release/websocketclient.o release/websocketclient.o
release/moc_actionmanager.o
release/moc_commandprocessor.o release/moc_commandprocessor.o
release/moc_countermanager.o release/moc_countermanager.o
release/moc_emoteprovider.o release/moc_emoteprovider.o
+172
View File
@@ -1399,3 +1399,175 @@ bool uDataBase::deleteNotification(int port)
return true; 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<ActionData> uDataBase::loadAllActions()
{
QList<ActionData> 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;
}
+17 -1
View File
@@ -56,7 +56,16 @@ struct NotificationSettings {
QDateTime createdAt; 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 class uDataBase : public QObject
{ {
@@ -121,6 +130,13 @@ public:
// Получение последней ошибки // Получение последней ошибки
QString lastError() const; QString lastError() const;
bool createActionsTable();
bool saveAction(const ActionData &action);
bool updateAction(int id, const ActionData &action);
bool deleteAction(int id);
QList<ActionData> loadAllActions();
bool clearActionsTable();
private: private:
+169
View File
@@ -452,7 +452,22 @@ void uGeneral::initializeManagers()
m_commandProcessor->setContext(context); 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<QKeyEvent*>(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();
}
+12 -1
View File
@@ -9,6 +9,7 @@
#include <ulink.h> #include <ulink.h>
#include <udatabase.h> #include <udatabase.h>
#include <soundmanager.h> #include <soundmanager.h>
#include "actionmanager.h"
#include "commandprocessor.h" #include "commandprocessor.h"
#include "countermanager.h" #include "countermanager.h"
#include "fcreatechat.h" #include "fcreatechat.h"
@@ -85,7 +86,7 @@ public:
SoundManager *soundManager; // Менеджер звуков SoundManager *soundManager; // Менеджер звуков
UserManager* getUserManager(); // Получение менеджера пользователей UserManager* getUserManager(); // Получение менеджера пользователей
TTwAPI *twitchAPI; // API для работы с Twitch TTwAPI *twitchAPI; // API для работы с Twitch
bool eventFilter(QObject *obj, QEvent *event) override;
// Методы логирования и работы с командами // Методы логирования и работы с командами
void toCommands(QString command, QString response); void toCommands(QString command, QString response);
@@ -374,6 +375,13 @@ private slots:
void on_btnActionAudioOpen_clicked(); 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: public slots:
// Установка статуса подключения к Twitch // Установка статуса подключения к Twitch
void setTwitchConnected(bool connected); void setTwitchConnected(bool connected);
@@ -404,6 +412,9 @@ private:
int m_nextTimerId = 1; // Следующий ID таймера int m_nextTimerId = 1; // Следующий ID таймера
bool m_isTwitchConnected = false; // Статус подключения к Twitch bool m_isTwitchConnected = false; // Статус подключения к Twitch
QVector<TCustomReward*> m_rewards; QVector<TCustomReward*> m_rewards;
ActionManager *m_actionManager;
// возможно, сохраняем текущее редактируемое действие
int m_currentActionId = -1;
// Менеджеры веб-серверов // Менеджеры веб-серверов
QList<HttpServer*> m_notificationServers; QList<HttpServer*> m_notificationServers;
QList<HttpServerChat*> m_chatServers; QList<HttpServerChat*> m_chatServers;