first commit

This commit is contained in:
2026-01-26 22:26:19 +03:00
commit 31fccd85f2
95 changed files with 115400 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# Игнорировать пользовательские файлы и каталоги
*.user
*.user.*
*.suo
*.ncb
*.sdf
*.opensdf
*.log
*.idb
*.pdb
*.obj
*.pch
*.sln
*.vcxproj.*
*.vcxproj.user
# Собранные файлы и каталоги
build-*/
build/
Debug/
Release/
*.exe
*.app
*.dll
*.so
*.dylib
# Qt Creator файлы
.cproject
.project
.autotools
*.autosave
# Скомпилированные интерфейсы
ui_*.h
moc_*.cpp
moc_*.h
qrc_*.cpp
# Метаобъектный код
moc_*.cpp
moc_*.h
# Ресурсы
qrc_*.cpp
# Переводы
*.qm
# Файлы сборки Qt
Makefile*
*.o
*.o.d
*.a
*.la
*.lib
*.dll.a
# Файлы метаинформации CMake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
install_manifest.txt
compile_commands.json
*.cmake
*.ninja
.ninja_deps
.ninja_log
# QML cache
*.qmlc
*.jsc
# Скрытые файлы и папки
.*
!/.gitignore
!/.gitattributes
# Временные файлы
*~
*.swp
*.swo
# MacOS файлы
.DS_Store
._*
# Папки зависимостей (если используются)
3rdparty/
libs/
+92
View File
@@ -0,0 +1,92 @@
QT += core gui websockets network sql multimedia
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11 debug
CONFIG -= app_bundle
# Для MinGW
win32: {
LIBS += -lole32 -loleaut32 -luuid -lstrmiids -lquartz -ldsound
LIBS += -lwinmm
}
# Для MSVC (если используете Visual Studio)
# win32:msvc {
# LIBS += strmiids.lib quartz.lib ole32.lib oleaut32.lib uuid.lib
# }
RC_ICONS = ico\app_icon.ico
INCLUDEPATH += $$PWD
SOURCES += \
emoteprovider.cpp \
fcolorsetting.cpp \
fcreatechat.cpp \
fcreatenotify.cpp \
ffontsetting.cpp \
fsettingsws.cpp \
fsinglegrid.cpp \
main.cpp \
neuralnetworkmanager.cpp \
soundmanager.cpp \
tauth.cpp \
ttw_api.cpp \
twitchmessage.cpp \
udatabase.cpp \
ugeneral.cpp \
ulink.cpp \
user.cpp \
user_manager.cpp \
userwidget.cpp \
webserverchat.cpp \
webservernotify.cpp \
websocketclient.cpp
HEADERS += \
badge.h \
emoteprovider.h \
fcolorsetting.h \
fcreatechat.h \
fcreatenotify.h \
ffontsetting.h \
fsettingsws.h \
fsinglegrid.h \
miniaudio.h \
neuralnetworkmanager.h \
soundmanager.h \
tauth.h \
timerinfo.h \
ttw_api.h \
ttw_types.h \
twitchmessage.h \
udatabase.h \
ugeneral.h \
ulink.h \
user.h \
user_manager.h \
userwidget.h \
webserverchat.h \
webservernotify.h \
websocketclient.h
FORMS += \
fcolorsetting.ui \
fcreatechat.ui \
fcreatenotify.ui \
ffontsetting.ui \
fsettingsws.ui \
fsinglegrid.ui \
ugeneral.ui \
ulink.ui
TRANSLATIONS += \
TTW_Bot_ru_RU.ts
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
DISTFILES +=
+37
View File
@@ -0,0 +1,37 @@
#include <windows.h>
IDI_ICON1 ICON DISCARDABLE "C:/Users/PTyTb/Documents/cpp/TTW_Bot/ico/app_icon.ico"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,0,0,0
PRODUCTVERSION 0,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "\0"
VALUE "FileDescription", "\0"
VALUE "FileVersion", "0.0.0.0\0"
VALUE "LegalCopyright", "\0"
VALUE "OriginalFilename", "TTW_Bot.exe\0"
VALUE "ProductName", "TTW_Bot\0"
VALUE "ProductVersion", "0.0.0.0\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1200
END
END
/* End of Version info */
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="TTW_Bot_app_ru_RU"></TS>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

+21
View File
@@ -0,0 +1,21 @@
#ifndef BADGE_H
#define BADGE_H
#include <QString>
#include <QVector>
struct BadgeVersion {
QString id;
QString imageUrl1x;
QString imageUrl2x;
QString imageUrl4x;
QString title;
QString description;
};
struct ChatBadge {
QString setId;
QVector<BadgeVersion> versions;
};
#endif // BADGE_H
+418
View File
@@ -0,0 +1,418 @@
// emoteprovider.cpp
#include "emoteprovider.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
// EmoteProvider implementation
EmoteProvider::EmoteProvider(QObject *parent)
: QObject(parent)
, m_networkManager(new QNetworkAccessManager(this)) {
}
EmoteProvider::~EmoteProvider() {
// QNetworkAccessManager будет удален автоматически как дочерний объект
}
void EmoteProvider::setLogCallback(LogCallback callback) {
m_logCallback = callback;
}
void EmoteProvider::log(const QString &method, const QString &message, LogLevel level) {
if (m_logCallback) {
m_logCallback(metaObject()->className(), method, message, level);
}
}
// BTTVProvider implementation
BTTVProvider::BTTVProvider(QObject *parent)
: EmoteProvider(parent) {
}
BTTVProvider::~BTTVProvider() {
m_emotes.clear();
}
void BTTVProvider::fetchGlobal() {
log("fetchGlobal", "Fetching global BTTV emotes", LOG_INFO);
QNetworkRequest request(QUrl(getBaseUrl() + "emotes/global"));
request.setHeader(QNetworkRequest::UserAgentHeader,
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36");
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished,
this, [this, reply]() { onGlobalReplyFinished(reply); });
}
void BTTVProvider::fetchCustom(const QString &userId) {
if (userId.isEmpty()) {
log("fetchCustom", "User ID is empty", LOG_WARNING);
return;
}
log("fetchCustom", QString("Fetching custom BTTV emotes for user: %1").arg(userId), LOG_INFO);
m_lastUserId = userId;
QNetworkRequest request(QUrl(getBaseUrl() + "users/twitch/" + userId));
request.setHeader(QNetworkRequest::UserAgentHeader,
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36");
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished,
this, [this, reply]() { onCustomReplyFinished(reply); });
}
QString BTTVProvider::getEmoteUrl(const QString &emoteName) {
for (const auto &emote : m_emotes) {
if (emote.code == emoteName) {
return emote.url();
}
}
return QString();
}
void BTTVProvider::onGlobalReplyFinished(QNetworkReply *reply) {
if (reply->error() != QNetworkReply::NoError) {
log("onGlobalReplyFinished",
QString("Network error: %1").arg(reply->errorString()),
LOG_ERROR);
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
parseGlobalResponse(data);
reply->deleteLater();
emit emotesLoaded();
}
void BTTVProvider::onCustomReplyFinished(QNetworkReply *reply) {
if (reply->error() != QNetworkReply::NoError) {
log("onCustomReplyFinished",
QString("Network error: %1").arg(reply->errorString()),
LOG_ERROR);
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
parseCustomResponse(data, m_lastUserId);
reply->deleteLater();
emit emotesLoaded();
}
void BTTVProvider::parseGlobalResponse(const QByteArray &data) {
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
log("parseGlobalResponse",
QString("JSON parse error: %1").arg(parseError.errorString()),
LOG_ERROR);
return;
}
if (!doc.isArray()) {
log("parseGlobalResponse", "Expected JSON array", LOG_ERROR);
return;
}
QJsonArray array = doc.array();
m_emotes.clear();
for (const QJsonValue &value : array) {
if (!value.isObject()) continue;
QJsonObject obj = value.toObject();
BTTVEmote emote;
emote.id = obj.value("id").toString();
emote.code = obj.value("code").toString();
if (!emote.id.isEmpty() && !emote.code.isEmpty()) {
m_emotes.append(emote);
}
}
log("parseGlobalResponse",
QString("Loaded %1 global emotes").arg(m_emotes.size()),
LOG_INFO);
}
void BTTVProvider::parseCustomResponse(const QByteArray &data, const QString &userId) {
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
log("parseCustomResponse",
QString("JSON parse error: %1").arg(parseError.errorString()),
LOG_ERROR);
return;
}
if (!doc.isObject()) {
log("parseCustomResponse", "Expected JSON object", LOG_ERROR);
return;
}
QJsonObject root = doc.object();
auto parseEmotesArray = [this](const QJsonArray &array) {
for (const QJsonValue &value : array) {
if (!value.isObject()) continue;
QJsonObject obj = value.toObject();
BTTVEmote emote;
emote.id = obj.value("id").toString();
emote.code = obj.value("code").toString();
if (!emote.id.isEmpty() && !emote.code.isEmpty()) {
m_emotes.append(emote);
}
}
};
// Очищаем только пользовательские эмодзи (можно оптимизировать)
// В реальном приложении нужно хранить отдельно глобальные и пользовательские
// Парсим channelEmotes
if (root.contains("channelEmotes") && root["channelEmotes"].isArray()) {
parseEmotesArray(root["channelEmotes"].toArray());
}
// Парсим sharedEmotes
if (root.contains("sharedEmotes") && root["sharedEmotes"].isArray()) {
parseEmotesArray(root["sharedEmotes"].toArray());
}
log("parseCustomResponse",
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
LOG_INFO);
}
// SevenTVProvider implementation
SevenTVProvider::SevenTVProvider(QObject *parent)
: EmoteProvider(parent) {
}
SevenTVProvider::~SevenTVProvider() {
m_emotes.clear();
}
void SevenTVProvider::fetchGlobal() {
log("fetchGlobal", "Fetching global 7TV emotes", LOG_INFO);
QNetworkRequest request(QUrl(getBaseUrl() + "emote-sets/global"));
request.setHeader(QNetworkRequest::UserAgentHeader,
"Mozilla/5.0");
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished,
this, [this, reply]() { onGlobalReplyFinished(reply); });
}
void SevenTVProvider::fetchCustom(const QString &userId) {
if (userId.isEmpty()) {
log("fetchCustom", "User ID is empty", LOG_WARNING);
return;
}
log("fetchCustom", QString("Fetching custom 7TV emotes for user: %1").arg(userId), LOG_INFO);
m_lastUserId = userId;
QNetworkRequest request(QUrl(getBaseUrl() + "users/twitch/" + userId));
request.setHeader(QNetworkRequest::UserAgentHeader,
"Mozilla/5.0");
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::finished,
this, [this, reply]() { onCustomReplyFinished(reply); });
}
QString SevenTVProvider::getEmoteUrl(const QString &emoteName) {
for (const auto &emote : m_emotes) {
if (emote.code == emoteName) {
return emote.url;
}
}
return QString();
}
void SevenTVProvider::onGlobalReplyFinished(QNetworkReply *reply) {
if (reply->error() != QNetworkReply::NoError) {
log("onGlobalReplyFinished",
QString("Network error: %1").arg(reply->errorString()),
LOG_ERROR);
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
parseGlobalResponse(data);
reply->deleteLater();
emit emotesLoaded();
}
void SevenTVProvider::onCustomReplyFinished(QNetworkReply *reply) {
if (reply->error() != QNetworkReply::NoError) {
log("onCustomReplyFinished",
QString("Network error: %1").arg(reply->errorString()),
LOG_ERROR);
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
parseCustomResponse(data, m_lastUserId);
reply->deleteLater();
emit emotesLoaded();
}
void SevenTVProvider::parseGlobalResponse(const QByteArray &data) {
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
log("parseGlobalResponse",
QString("JSON parse error: %1").arg(parseError.errorString()),
LOG_ERROR);
return;
}
if (!doc.isObject()) {
log("parseGlobalResponse", "Expected JSON object", LOG_ERROR);
return;
}
QJsonObject root = doc.object();
m_emotes.clear();
if (!root.contains("emotes") || !root["emotes"].isArray()) {
log("parseGlobalResponse", "No emotes array found", LOG_ERROR);
return;
}
QJsonArray emotesArray = root["emotes"].toArray();
for (const QJsonValue &emoteValue : emotesArray) {
if (!emoteValue.isObject()) continue;
QJsonObject emoteObj = emoteValue.toObject();
SevenTVEmote emote;
emote.id = emoteObj.value("id").toString();
emote.code = emoteObj.value("name").toString();
// Парсим URL
if (emoteObj.contains("data") && emoteObj["data"].isObject()) {
QJsonObject dataObj = emoteObj["data"].toObject();
if (dataObj.contains("host") && dataObj["host"].isObject()) {
QJsonObject hostObj = dataObj["host"].toObject();
if (hostObj.contains("url")) {
QString baseUrl = "https:" + hostObj["url"].toString();
if (hostObj.contains("files") && hostObj["files"].isArray()) {
QJsonArray filesArray = hostObj["files"].toArray();
if (!filesArray.isEmpty() && filesArray[0].isObject()) {
QJsonObject fileObj = filesArray[0].toObject();
if (fileObj.contains("name")) {
emote.url = baseUrl + "/" + fileObj["name"].toString();
}
}
}
}
}
}
if (!emote.id.isEmpty() && !emote.code.isEmpty() && !emote.url.isEmpty()) {
m_emotes.append(emote);
}
}
log("parseGlobalResponse",
QString("Loaded %1 global emotes").arg(m_emotes.size()),
LOG_INFO);
}
void SevenTVProvider::parseCustomResponse(const QByteArray &data, const QString &userId) {
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError) {
log("parseCustomResponse",
QString("JSON parse error: %1").arg(parseError.errorString()),
LOG_ERROR);
return;
}
if (!doc.isObject()) {
log("parseCustomResponse", "Expected JSON object", LOG_ERROR);
return;
}
QJsonObject root = doc.object();
if (!root.contains("emote_set") || !root["emote_set"].isObject()) {
log("parseCustomResponse", "No emote_set found", LOG_ERROR);
return;
}
QJsonObject emoteSet = root["emote_set"].toObject();
if (!emoteSet.contains("emotes") || !emoteSet["emotes"].isArray()) {
log("parseCustomResponse", "No emotes array in emote_set", LOG_ERROR);
return;
}
QJsonArray emotesArray = emoteSet["emotes"].toArray();
for (const QJsonValue &emoteValue : emotesArray) {
if (!emoteValue.isObject()) continue;
QJsonObject emoteObj = emoteValue.toObject();
SevenTVEmote emote;
emote.id = emoteObj.value("id").toString();
emote.code = emoteObj.value("name").toString();
// Парсим URL (аналогично parseGlobalResponse)
if (emoteObj.contains("data") && emoteObj["data"].isObject()) {
QJsonObject dataObj = emoteObj["data"].toObject();
if (dataObj.contains("host") && dataObj["host"].isObject()) {
QJsonObject hostObj = dataObj["host"].toObject();
if (hostObj.contains("url")) {
QString baseUrl = "https:" + hostObj["url"].toString();
if (hostObj.contains("files") && hostObj["files"].isArray()) {
QJsonArray filesArray = hostObj["files"].toArray();
if (!filesArray.isEmpty() && filesArray[0].isObject()) {
QJsonObject fileObj = filesArray[0].toObject();
if (fileObj.contains("name")) {
emote.url = baseUrl + "/" + fileObj["name"].toString();
}
}
}
}
}
}
if (!emote.id.isEmpty() && !emote.code.isEmpty() && !emote.url.isEmpty()) {
m_emotes.append(emote);
}
}
log("parseCustomResponse",
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
LOG_INFO);
}
+110
View File
@@ -0,0 +1,110 @@
// emoteprovider.h
#ifndef EMOTEPROVIDER_H
#define EMOTEPROVIDER_H
#include <QObject>
#include <QList>
#include <QString>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <functional>
enum LogLevel {
LOG_INFO = 0,
LOG_WARNING = 1,
LOG_ERROR = 2
};
using LogCallback = std::function<void(const QString& module,
const QString& method,
const QString& message,
LogLevel level)>;
struct BTTVEmote {
QString id;
QString code;
QString url() const {
return QString("https://cdn.betterttv.net/emote/%1/1x").arg(id);
}
};
struct SevenTVEmote {
QString id;
QString code;
QString url;
};
// Абстрактный базовый класс для провайдеров эмодзи
class EmoteProvider : public QObject {
Q_OBJECT
public:
explicit EmoteProvider(QObject *parent = nullptr);
virtual ~EmoteProvider();
void setLogCallback(LogCallback callback);
virtual void fetchGlobal() = 0;
virtual void fetchCustom(const QString &userId) = 0;
virtual QString getEmoteUrl(const QString &emoteName) = 0;
signals:
void emotesLoaded();
protected:
void log(const QString &method, const QString &message, LogLevel level);
virtual QString getBaseUrl() const = 0;
virtual void parseGlobalResponse(const QByteArray &data) = 0;
virtual void parseCustomResponse(const QByteArray &data, const QString &userId) = 0;
QNetworkAccessManager *m_networkManager;
LogCallback m_logCallback;
};
// Провайдер для BetterTTV
class BTTVProvider : public EmoteProvider {
Q_OBJECT
public:
explicit BTTVProvider(QObject *parent = nullptr);
~BTTVProvider();
void fetchGlobal() override;
void fetchCustom(const QString &userId) override;
QString getEmoteUrl(const QString &emoteName) override;
private slots:
void onGlobalReplyFinished(QNetworkReply *reply);
void onCustomReplyFinished(QNetworkReply *reply);
private:
QString getBaseUrl() const override { return "https://api.betterttv.net/3/cached/"; }
void parseGlobalResponse(const QByteArray &data) override;
void parseCustomResponse(const QByteArray &data, const QString &userId) override;
QList<BTTVEmote> m_emotes;
QString m_lastUserId;
};
// Провайдер для 7TV
class SevenTVProvider : public EmoteProvider {
Q_OBJECT
public:
explicit SevenTVProvider(QObject *parent = nullptr);
~SevenTVProvider();
void fetchGlobal() override;
void fetchCustom(const QString &userId) override;
QString getEmoteUrl(const QString &emoteName) override;
private slots:
void onGlobalReplyFinished(QNetworkReply *reply);
void onCustomReplyFinished(QNetworkReply *reply);
private:
QString getBaseUrl() const override { return "https://api.7tv.app/v3/"; }
void parseGlobalResponse(const QByteArray &data) override;
void parseCustomResponse(const QByteArray &data, const QString &userId) override;
QList<SevenTVEmote> m_emotes;
QString m_lastUserId;
};
#endif // EMOTEPROVIDER_H
+255
View File
@@ -0,0 +1,255 @@
#include "fcolorsetting.h"
#include "ui_fcolorsetting.h"
FColorSetting::FColorSetting(QWidget *parent)
: QWidget(parent)
, cbBlockColor(nullptr)
, cbBorderColor(nullptr)
, cbBackgroundColor(nullptr)
, sbBorderSize(nullptr)
, sbPadding(nullptr)
, hsBlockTransparant(nullptr)
, ui(new Ui::FColorSetting)
{
ui->setupUi(this);
setupColorComboBox(ui->cbBackgroundColor);
setupColorComboBox(ui->cbBlockColor);
setupColorComboBox(ui->cbBorderColor);
cbBlockColor = ui->cbBlockColor;
cbBorderColor = ui->cbBorderColor;
cbBackgroundColor = ui->cbBackgroundColor;
sbBorderSize = ui->sbBorderSize;
sbPadding = ui->sbPadding;
hsBlockTransparant = ui->hsBlockTransparant;
setDefaultColor(ui->cbBackgroundColor, "lime");
}
FColorSetting::~FColorSetting()
{
delete ui;
}
void FColorSetting::setupColorComboBox(QComboBox* comboBox)
{
comboBox->clear();
// Добавляем цвета
QStringList colorNames = QColor::colorNames();
foreach (const QString &name, colorNames) {
QColor color(name);
QPixmap pixmap(20, 20);
pixmap.fill(color);
comboBox->addItem(QIcon(pixmap), name, color);
}
}
QString FColorSetting::hexToColorName(const QString &hexColor)
{
// Создаем QColor из HEX
QColor color(hexColor);
if (!color.isValid()) {
return hexColor; // Возвращаем HEX, если цвет невалиден
}
// Получаем все названия цветов из Qt
QStringList colorNames = QColor::colorNames();
// Ищем ближайшее название по RGB
QString closestName;
int minDistance = INT_MAX;
for (const QString &name : colorNames) {
QColor namedColor(name);
if (!namedColor.isValid()) continue;
// Вычисляем евклидово расстояние в RGB пространстве
int distance =
qAbs(color.red() - namedColor.red()) +
qAbs(color.green() - namedColor.green()) +
qAbs(color.blue() - namedColor.blue());
if (distance < minDistance) {
minDistance = distance;
closestName = name;
}
}
// Если нашли достаточно близкий цвет (разница небольшая)
if (minDistance < 50 && !closestName.isEmpty()) {
return closestName;
}
// Если точного соответствия нет, пытаемся найти по HEX в таблице соответствий
static QMap<QString, QString> hexToName = {
{"#f0f8ff", "aliceblue"},
{"#faebd7", "antiquewhite"},
{"#00ffff", "aqua"},
{"#7fffd4", "aquamarine"},
{"#f0ffff", "azure"},
{"#f5f5dc", "beige"},
{"#ffe4c4", "bisque"},
{"#000000", "black"},
{"#ffebcd", "blanchedalmond"},
{"#0000ff", "blue"},
{"#8a2be2", "blueviolet"},
{"#a52a2a", "brown"},
{"#deb887", "burlywood"},
{"#5f9ea0", "cadetblue"},
{"#7fff00", "chartreuse"},
{"#d2691e", "chocolate"},
{"#ff7f50", "coral"},
{"#6495ed", "cornflowerblue"},
{"#fff8dc", "cornsilk"},
{"#dc143c", "crimson"},
{"#00ffff", "cyan"},
{"#00008b", "darkblue"},
{"#008b8b", "darkcyan"},
{"#b8860b", "darkgoldenrod"},
{"#a9a9a9", "darkgray"},
{"#006400", "darkgreen"},
{"#a9a9a9", "darkgrey"},
{"#bdb76b", "darkkhaki"},
{"#8b008b", "darkmagenta"},
{"#556b2f", "darkolivegreen"},
{"#ff8c00", "darkorange"},
{"#9932cc", "darkorchid"},
{"#8b0000", "darkred"},
{"#e9967a", "darksalmon"},
{"#8fbc8f", "darkseagreen"},
{"#483d8b", "darkslateblue"},
{"#2f4f4f", "darkslategray"},
{"#2f4f4f", "darkslategrey"},
{"#00ced1", "darkturquoise"},
{"#9400d3", "darkviolet"},
{"#ff1493", "deeppink"},
{"#00bfff", "deepskyblue"},
{"#696969", "dimgray"},
{"#696969", "dimgrey"},
{"#1e90ff", "dodgerblue"},
{"#b22222", "firebrick"},
{"#fffaf0", "floralwhite"},
{"#228b22", "forestgreen"},
{"#dcdcdc", "gainsboro"},
{"#f8f8ff", "ghostwhite"},
{"#ffd700", "gold"},
{"#daa520", "goldenrod"},
{"#808080", "gray"},
{"#008000", "green"},
{"#adff2f", "greenyellow"},
{"#808080", "grey"},
{"#f0fff0", "honeydew"},
{"#ff69b4", "hotpink"},
{"#cd5c5c", "indianred"},
{"#4b0082", "indigo"},
{"#fffff0", "ivory"},
{"#f0e68c", "khaki"},
{"#e6e6fa", "lavender"},
{"#fff0f5", "lavenderblush"},
{"#7cfc00", "lawngreen"},
{"#fffacd", "lemonchiffon"},
{"#add8e6", "lightblue"},
{"#f08080", "lightcoral"},
{"#e0ffff", "lightcyan"},
{"#fafad2", "lightgoldenrodyellow"},
{"#d3d3d3", "lightgray"},
{"#90ee90", "lightgreen"},
{"#d3d3d3", "lightgrey"},
{"#ffb6c1", "lightpink"},
{"#ffa07a", "lightsalmon"},
{"#20b2aa", "lightseagreen"},
{"#87cefa", "lightskyblue"},
{"#778899", "lightslategray"},
{"#778899", "lightslategrey"},
{"#b0c4de", "lightsteelblue"},
{"#ffffe0", "lightyellow"},
{"#00ff00", "lime"},
{"#32cd32", "limegreen"},
{"#faf0e6", "linen"},
{"#800000", "maroon"},
{"#66cdaa", "mediumaquamarine"},
{"#0000cd", "mediumblue"},
{"#ba55d3", "mediumorchid"},
{"#9370db", "mediumpurple"},
{"#3cb371", "mediumseagreen"},
{"#7b68ee", "mediumslateblue"},
{"#00fa9a", "mediumspringgreen"},
{"#48d1cc", "mediumturquoise"},
{"#c71585", "mediumvioletred"},
{"#191970", "midnightblue"},
{"#f5fffa", "mintcream"},
{"#ffe4e1", "mistyrose"},
{"#ffe4b5", "moccasin"},
{"#ffdead", "navajowhite"},
{"#000080", "navy"},
{"#fdf5e6", "oldlace"},
{"#808000", "olive"},
{"#6b8e23", "olivedrab"},
{"#ffa500", "orange"},
{"#ff4500", "orangered"},
{"#da70d6", "orchid"},
{"#eee8aa", "palegoldenrod"},
{"#98fb98", "palegreen"},
{"#afeeee", "paleturquoise"},
{"#db7093", "palevioletred"},
{"#ffefd5", "papayawhip"},
{"#ffdab9", "peachpuff"},
{"#cd853f", "peru"},
{"#ffc0cb", "pink"},
{"#dda0dd", "plum"},
{"#b0e0e6", "powderblue"},
{"#800080", "purple"},
{"#663399", "rebeccapurple"},
{"#ff0000", "red"},
{"#bc8f8f", "rosybrown"},
{"#4169e1", "royalblue"},
{"#8b4513", "saddlebrown"},
{"#fa8072", "salmon"},
{"#f4a460", "sandybrown"},
{"#2e8b57", "seagreen"},
{"#fff5ee", "seashell"},
{"#a0522d", "sienna"},
{"#c0c0c0", "silver"},
{"#87ceeb", "skyblue"},
{"#6a5acd", "slateblue"},
{"#708090", "slategray"},
{"#708090", "slategrey"},
{"#fffafa", "snow"},
{"#00ff7f", "springgreen"},
{"#4682b4", "steelblue"},
{"#d2b48c", "tan"},
{"#008080", "teal"},
{"#d8bfd8", "thistle"},
{"#ff6347", "tomato"},
{"#40e0d0", "turquoise"},
{"#ee82ee", "violet"},
{"#f5deb3", "wheat"},
{"#ffffff", "white"},
{"#f5f5f5", "whitesmoke"},
{"#ffff00", "yellow"},
{"#9acd32", "yellowgreen"}
};
QString lowerHex = hexColor.toLower();
if (hexToName.contains(lowerHex)) {
return hexToName[lowerHex];
}
// Если ничего не нашли, возвращаем HEX
return hexColor;
}
void FColorSetting::setDefaultColor(QComboBox* comboBox, const QString& colorName)
{
// Ищем индекс цвета в комбобоксе
int index = comboBox->findText(colorName, Qt::MatchFixedString);
if (index != -1) {
comboBox->setCurrentIndex(index);
} else {
comboBox->setCurrentIndex(0); // Устанавливаем первый цвет как запасной вариант
}
}
+33
View File
@@ -0,0 +1,33 @@
#ifndef FCOLORSETTING_H
#define FCOLORSETTING_H
#include "qspinbox.h"
#include <QWidget>
#include <QComboBox>
namespace Ui {
class FColorSetting;
}
class FColorSetting : public QWidget
{
Q_OBJECT
public:
explicit FColorSetting(QWidget *parent = nullptr);
~FColorSetting();
QComboBox *cbBlockColor;
QComboBox *cbBorderColor;
QComboBox *cbBackgroundColor;
QSpinBox *sbBorderSize;
QSpinBox *sbPadding;
QSlider *hsBlockTransparant;
QString hexToColorName(const QString &hexColor);
private:
Ui::FColorSetting *ui;
void setupColorComboBox(QComboBox* comboBox);
void setDefaultColor(QComboBox* comboBox, const QString& colorName);
};
#endif // FCOLORSETTING_H
+115
View File
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FColorSetting</class>
<widget class="QWidget" name="FColorSetting">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>104</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>331</width>
<height>101</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Цвет рамки</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cbBorderColor"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Цвет фона</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Цвет блока</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="cbBackgroundColor"/>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="cbBlockColor"/>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Отступы</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="sbPadding">
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="sbBorderSize">
<property name="maximum">
<number>10</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Толщина рамки</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Прозрачность блока</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QSlider" name="hsBlockTransparant">
<property name="maximum">
<number>255</number>
</property>
<property name="singleStep">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+344
View File
@@ -0,0 +1,344 @@
#include "fcreatechat.h"
#include "ui_fcreatechat.h"
#include <QMessageBox>
#include <QDebug>
FCreateChat::FCreateChat(QWidget *parent)
: QDialog(parent)
, m_chatServer(nullptr)
, m_StyleChat(nullptr)
, ui(new Ui::FCreateChat)
, m_isEditMode(false)
, m_existingServerName("")
{
ui->setupUi(this);
setWindowTitle("TTW Bot app: Создать чат");
m_StyleChat = nullptr;
QStringList fontList;
// Создаем сервер чата на порту 7998 (можно сделать настраиваемым)
m_chatServer = nullptr;
// Подключаем кнопки
connect(ui->btnTest, &QPushButton::clicked, this, &FCreateChat::onBtnTestClicked);
connect(ui->btnAdd, &QPushButton::clicked, this, &FCreateChat::onBtnAddClicked);
}
FCreateChat::~FCreateChat()
{
if (m_StyleChat) {
delete m_StyleChat;
}
delete ui;
}
void FCreateChat::setEditMode(bool isEditMode)
{
m_isEditMode = isEditMode;
if (isEditMode) {
ui->btnAdd->setText("Изменить");
setWindowTitle("TTW Bot app: Редактировать чат");
} else {
ui->btnAdd->setText("Создать");
setWindowTitle("TTW Bot app: Создать чат");
}
}
void FCreateChat::loadExistingServer(HttpServerChat *server, const QString &name)
{
if (!server) return;
m_isEditMode = true;
m_chatServer = server;
m_existingServerName = name;
setEditMode(true);
FSettingsWS *settingsWS = ui->widget;
if (settingsWS) {
settingsWS->sbPort->setValue(server->port());
settingsWS->cbFreez->setChecked(server->isFreez());
settingsWS->sbTime->setValue(server->getMessageTimeout());
settingsWS->sbCount->setValue(server->getMaxMsgCount());
}
ui->lineEdit->setText(name);
FColorSetting *colorSetting = ui->wBlock;
if (colorSetting) {
QString backgroundColor = colorSetting->hexToColorName(server->getBackgroundColor());
colorSetting->cbBackgroundColor->setCurrentText(backgroundColor);
QString blockColor = colorSetting->hexToColorName(server->getBlockColor());
colorSetting->cbBlockColor->setCurrentText(blockColor);
QString borderColor = colorSetting->hexToColorName(server->getBorderColor());
colorSetting->cbBorderColor->setCurrentText(borderColor);
colorSetting->sbBorderSize->setValue(server->getBorderSize());
colorSetting->sbPadding->setValue(server->getPadding());
colorSetting->hsBlockTransparant->setValue(server->getTransparency());
}
FFontSetting *fontSetting = ui->wFont;
if (fontSetting) {
QString fontFamily = colorSetting->hexToColorName(server->getFontFamily());
fontSetting->cbFontStyle->setCurrentText(fontFamily);
fontSetting->sbFontSize->setValue(server->getFontSize());
QString fontColor = colorSetting->hexToColorName(server->getFontColor());
fontSetting->cbFontColor->setCurrentText(fontColor);
}
}
void FCreateChat::createServer()
{
if (m_chatServer) {
m_chatServer->stop();
delete m_chatServer;
m_chatServer = nullptr;
}
// Получаем настройки из формы
FSettingsWS *settingsWS = ui->widget;
if (!settingsWS) {
QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера");
return;
}
// Получаем порт из настроек
int port = settingsWS->sbPort->value();
// Получаем список шрифтов из FFontSetting
FFontSetting *fontSetting = ui->wFont;
QStringList fontList;
if (fontSetting && fontSetting->cbFontStyle) {
// Добавляем все шрифты из комбобокса
for (int i = 0; i < fontSetting->cbFontStyle->count(); ++i) {
fontList.append(fontSetting->cbFontStyle->itemText(i));
}
}
// Получаем цвет фона из FColorSetting
FColorSetting *colorSetting = ui->wBlock;
QString backgroundColor = "#89F336"; // По умолчанию черный
if (colorSetting && colorSetting->cbBackgroundColor) {
QColor bgColor = colorSetting->cbBackgroundColor->currentData().value<QColor>();
if (!bgColor.isValid()) {
bgColor = QColor(colorSetting->cbBackgroundColor->currentText());
}
backgroundColor = bgColor.name();
}
// Создаем сервер с полученными настройками
m_chatServer = new HttpServerChat(fontList, port, backgroundColor, nullptr);
// Запускаем сервер
if (!m_chatServer->start()) {
QMessageBox::warning(this, "Ошибка",
QString("Не удалось запустить сервер чата на порту %1").arg(port));
} else {
}
}
void FCreateChat::onBtnTestClicked()
{
// Для теста создаем временный сервер или используем существующий
if (!m_chatServer || !m_isEditMode) {
// Если нет сервера или это не режим редактирования, создаем временный
if (m_chatServer && !m_isEditMode) {
m_chatServer->stop();
delete m_chatServer;
}
createServer();
}
if (!m_chatServer) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
// Создаем тестовое сообщение
createTestMessage(true);
}
void FCreateChat::onBtnAddClicked()
{
if (m_isEditMode && m_chatServer) {
FSettingsWS *settingsWS = ui->widget;
FColorSetting *colorSetting = ui->wBlock;
FFontSetting *fontSetting = ui->wFont;
if (!settingsWS || !colorSetting || !fontSetting) {
QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера");
return;
}
int newPort = settingsWS->sbPort->value();
bool portChanged = (newPort != m_chatServer->port());
if (portChanged) {
m_chatServer->stop();
delete m_chatServer;
m_chatServer = nullptr;
createServer();
}
if (!m_chatServer) {
QMessageBox::warning(this, "Ошибка", "Не удалось обновить сервер");
return;
}
// Применяем все настройки
QColor bgColor = colorSetting->cbBackgroundColor->currentData().value<QColor>();
if (!bgColor.isValid()) {
bgColor = QColor(colorSetting->cbBackgroundColor->currentText());
}
m_chatServer->changeBackground(bgColor.name());
m_chatServer->setBlockColor(colorSetting->cbBlockColor->currentText());
m_chatServer->setBorderColor(colorSetting->cbBorderColor->currentText());
m_chatServer->setBorderSize(colorSetting->sbBorderSize->value());
m_chatServer->setPadding(colorSetting->sbPadding->value());
m_chatServer->setTransparency(colorSetting->hsBlockTransparant->value());
m_chatServer->setFontFamily(fontSetting->cbFontStyle->currentText());
m_chatServer->setFontSize(fontSetting->sbFontSize->value());
m_chatServer->setFontColor(fontSetting->cbFontColor->currentText());
bool freez = settingsWS->cbFreez->isChecked();
m_chatServer->setFreez(freez);
// Удаление по времени = НЕ freez
bool deleteByTime = !freez;
int maxCount = settingsWS->sbCount->value();
m_chatServer->setDeleteMode(deleteByTime, maxCount);
m_chatServer->setMessageTimeout(settingsWS->sbTime->value());
QString newName = ui->lineEdit->text();
if (newName.isEmpty()) {
newName = QString("Чат сервер (порт %1)").arg(m_chatServer->port());
}
emit serverUpdated(m_chatServer, newName);
accept();
} else {
// Режим создания
createServer();
if (!m_chatServer) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
createTestMessage(false);
QString name = ui->lineEdit->text();
if (name.isEmpty()) {
name = QString("Чат сервер (порт %1)").arg(m_chatServer->port());
}
emit serverCreated(m_chatServer, name);
m_chatServer = nullptr;
accept();
}
}
void FCreateChat::createTestMessage(bool isTest)
{
if (!m_chatServer) {
QMessageBox::warning(this, "Ошибка", "Сервер чата не запущен");
return;
}
StyleChat style;
// Получаем настройки из виджетов
// 1. Получаем настройки цвета блока из FColorSetting
FColorSetting *colorSetting = ui->wBlock;
if (colorSetting) {
QColor blockColor = colorSetting->cbBlockColor->currentData().value<QColor>();
if (!blockColor.isValid()) {
blockColor = QColor(colorSetting->cbBlockColor->currentText());
}
style.blockColor = blockColor.name();
QColor borderColor = colorSetting->cbBorderColor->currentData().value<QColor>();
if (!borderColor.isValid()) {
borderColor = QColor(colorSetting->cbBorderColor->currentText());
}
style.borderColor = borderColor.name();
style.borderSize = colorSetting->sbBorderSize->value();
style.padding = colorSetting->sbPadding->value();
// Прозрачность
style.transparency = colorSetting->hsBlockTransparant->value();
// Цвет фона страницы
QColor pageBackgroundColor = colorSetting->cbBackgroundColor->currentData().value<QColor>();
if (!pageBackgroundColor.isValid()) {
pageBackgroundColor = QColor(colorSetting->cbBackgroundColor->currentText());
}
style.bColor = pageBackgroundColor.name();
// Обновляем цвет фона в сервере
m_chatServer->changeBackground(style.bColor);
}
// 2. Получаем настройки шрифта из FFontSetting
FFontSetting *fontSetting = ui->wFont;
if (fontSetting) {
style.fontFamily = fontSetting->cbFontStyle->currentText();
style.fontSize = fontSetting->sbFontSize->value();
QColor fontColor = fontSetting->cbFontColor->currentData().value<QColor>();
if (!fontColor.isValid()) {
fontColor = QColor(fontSetting->cbFontColor->currentText());
}
style.fontColor = fontColor.name();
}
// 3. Получаем настройки из FSettingsWS
FSettingsWS *settingsWS = ui->widget;
if (settingsWS) {
// Используем sbTime как время отображения сообщения
style.timeMsg = settingsWS->sbTime->value();
// Получаем состояние чекбокса "Вечно" (freez)
style.freez = settingsWS->cbFreez->isChecked();
// Настраиваем режим удаления
bool deleteByTime = !settingsWS->cbFreez->isChecked(); // Если "Вечно" выбрано, то НЕ удаляем по времени
int maxCount = settingsWS->sbCount->value();
m_chatServer->setDeleteMode(deleteByTime, maxCount);
}
// Заполняем тестовые данные
style.nick = isTest ? "Тестовый пользователь" : "Пользователь";
style.context = ui->lineEdit->text().isEmpty() ?
(isTest ? "Это тестовое сообщение!" : "Новое сообщение") :
ui->lineEdit->text();
// Добавляем сообщение в сервер
m_chatServer->addMessage(style);
// Очищаем поле ввода, если это не тест
if (!isTest) {
ui->lineEdit->clear();
}
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef FCREATECHAT_H
#define FCREATECHAT_H
#include "webserverchat.h"
#include <QDialog>
namespace Ui {
class FCreateChat;
}
class FCreateChat : public QDialog
{
Q_OBJECT
signals:
void serverCreated(HttpServerChat *server, const QString &name);
void serverUpdated(HttpServerChat *server, const QString &name);
public:
explicit FCreateChat(QWidget *parent = nullptr);
~FCreateChat();
HttpServerChat *m_chatServer;
StyleChat *m_StyleChat;
void loadExistingServer(HttpServerChat *server, const QString &name);
void setEditMode(bool isEditMode);
private slots:
void onBtnTestClicked();
void onBtnAddClicked();
private:
Ui::FCreateChat *ui;
bool m_isEditMode;
QString m_existingServerName;
void createServer();
void createTestMessage(bool isTest = false);
};
#endif // FCREATECHAT_H
+157
View File
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FCreateChat</class>
<widget class="QDialog" name="FCreateChat">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>753</width>
<height>270</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>351</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>Блок</string>
</property>
<widget class="FColorSetting" name="wBlock" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>121</height>
</rect>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>10</x>
<y>150</y>
<width>351</width>
<height>111</height>
</rect>
</property>
<property name="title">
<string>Шрифт</string>
</property>
<widget class="FFontSetting" name="wFont" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>81</height>
</rect>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_3">
<property name="geometry">
<rect>
<x>370</x>
<y>0</y>
<width>351</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>Настройки</string>
</property>
<widget class="FSettingsWS" name="widget" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>121</height>
</rect>
</property>
</widget>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>370</x>
<y>150</y>
<width>131</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Тестовое сообщение</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>370</x>
<y>170</y>
<width>231</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="btnTest">
<property name="geometry">
<rect>
<x>610</x>
<y>170</y>
<width>75</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Отправить</string>
</property>
</widget>
<widget class="QPushButton" name="btnAdd">
<property name="geometry">
<rect>
<x>370</x>
<y>200</y>
<width>75</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Создать</string>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>FColorSetting</class>
<extends>QWidget</extends>
<header location="global">fcolorsetting.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FFontSetting</class>
<extends>QWidget</extends>
<header location="global">ffontsetting.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FSettingsWS</class>
<extends>QWidget</extends>
<header location="global">fsettingsws.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+338
View File
@@ -0,0 +1,338 @@
#include "fcreatenotify.h"
#include "ui_fcreatenotify.h"
#include <QFileDialog>
#include <QDebug>
#include <QMessageBox>
#include <QDir>
#include <QCloseEvent>
#include <QDesktopServices>
#include <QApplication>
FCreateNotify::FCreateNotify(QWidget *parent) :
QDialog(parent),
ui(new Ui::FCreateNotify),
m_server(nullptr),
m_notificationCounter(0)
{
ui->setupUi(this);
// Создаем структуру папок для статических файлов
QDir appDir(QApplication::applicationDirPath());
appDir.mkpath("sounds");
appDir.mkpath("fonts");
appDir.mkpath("imgs");
// Заполняем ComboBox событиями
QStringList events = {
"donation", "follow", "subscription", "raid",
"bits", "host", "merch", "goal", "poll", "prediction"
};
ui->cbEvent->addItems(events);
ui->cbEvent->setCurrentIndex(0);
// Устанавливаем значения по умолчанию
ui->edtHeader->setText("Тестовый пользователь");
ui->edtMessage->setText("Это тестовое уведомление!");
// Включаем кнопки
ui->btnTest->setEnabled(true);
ui->btnAdd->setEnabled(true);
// Информация для пользователя
ui->btnTest->setToolTip("Создать тестовое уведомление и открыть браузер");
ui->btnAdd->setToolTip("Добавить уведомление для отображения");
m_server = new HttpServer(nullptr);
}
FCreateNotify::~FCreateNotify()
{
delete ui;
}
void FCreateNotify::closeEvent(QCloseEvent *event)
{
// Останавливаем сервер при закрытии окна
if (m_server) {
m_server->stop();
}
QDialog::closeEvent(event);
}
void FCreateNotify::createServer()
{
if (m_server) {
m_server->stop();
delete m_server;
m_server = nullptr;
}
// Получаем настройки из формы
FSettingsWS *settingsWS = ui->widget;
if (!settingsWS) {
QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера");
return;
}
// Получаем порт из настроек
int port = settingsWS->sbPort->value();
// Создаем сервер с полученным портом
m_server = new HttpServer( this);
// Запускаем сервер
if (m_server->start(port)) {
} else {
QMessageBox::warning(this, "Ошибка",
QString("Не удалось запустить сервер уведомлений на порту %1").arg(port));
}
}
void FCreateNotify::on_btnTest_clicked()
{
// Создаем сервер с текущими настройками
createServer();
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
// Создаем тестовое уведомление
createNotification(true);
}
void FCreateNotify::on_btnAdd_clicked()
{
// Создаем сервер с текущими настройками
createServer();
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
// Получаем название сервера
QString name = QString("Уведомления (порт %1)").arg(m_server->port());
// Отправляем сигнал с созданным сервером
emit serverCreated(m_server, name);
// Закрываем окно
accept();
}
void FCreateNotify::createNotification(bool isTest)
{
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Сервер не запущен");
return;
}
m_notificationCounter++;
Notification notif;
// Заголовок
notif.nickname = ui->edtHeader->text().isEmpty() ?
QString("Тест %1").arg(m_notificationCounter) :
ui->edtHeader->text();
// Сообщение
notif.content = ui->edtMessage->text().isEmpty() ?
QString("Тестовое сообщение #%1").arg(m_notificationCounter) :
ui->edtMessage->text();
// Добавляем тип события
notif.content = QString("[%1] %2")
.arg(ui->cbEvent->currentText())
.arg(notif.content);
// Картинка
QString imgPath = ui->edtFileImg->text();
if (!imgPath.isEmpty() && QFile::exists(imgPath)) {
QFileInfo imgInfo(imgPath);
QString destFileName = imgInfo.fileName();
QString destPath = getAbsolutePath("imgs/" + destFileName);
// Копируем, если файл еще не существует
if (!QFile::exists(destPath)) {
QFile::copy(imgPath, destPath);
}
notif.url = "/imgs/" + destFileName;
}
// Звук
QString soundPath = ui->edtFileSong->text();
if (!soundPath.isEmpty() && QFile::exists(soundPath)) {
QFileInfo soundInfo(soundPath);
QString destFileName = soundInfo.fileName();
QString destPath = getAbsolutePath("sounds/" + destFileName);
// Копируем, если файл еще не существует
if (!QFile::exists(destPath)) {
QFile::copy(soundPath, destPath);
}
notif.soundURL = "/sounds/" + destFileName;
}
// ПОЛУЧАЕМ НАСТРОЙКИ ИЗ ВИДЖЕТОВ
// 1. Получаем настройки цвета блока из FColorSetting
FColorSetting *colorSetting = ui->wBlock;
if (colorSetting) {
// Получаем выбранный цвет блока
QColor blockColor = colorSetting->cbBlockColor->currentData().value<QColor>();
if (!blockColor.isValid()) {
blockColor = QColor(colorSetting->cbBlockColor->currentText());
}
// ПРИМЕНЯЕМ ПРОЗРАЧНОСТЬ БЛОКА УВЕДОМЛЕНИЯ
int blockTransparency = colorSetting->hsBlockTransparant->value(); // 0-100
// Преобразуем 0-100 в 0-255
int alpha = (blockTransparency * 255) / 100;
blockColor.setAlpha(alpha);
notif.blockColor = blockColor.name(QColor::HexArgb); // Включаем альфа-канал
// Получаем выбранный цвет границы
QColor borderColor = colorSetting->cbBorderColor->currentData().value<QColor>();
if (!borderColor.isValid()) {
borderColor = QColor(colorSetting->cbBorderColor->currentText());
}
notif.borderColor = borderColor.name();
// Получаем размер границы
notif.borderSize = colorSetting->sbBorderSize->value();
// Получаем цвет фона СТРАНИЦЫ (не блока!)
QColor pageBackgroundColor = colorSetting->cbBackgroundColor->currentData().value<QColor>();
if (!pageBackgroundColor.isValid()) {
pageBackgroundColor = QColor(colorSetting->cbBackgroundColor->currentText());
}
notif.pageBackgroundColor = pageBackgroundColor.name();
}
// 2. Получаем настройки шрифта заголовка из FFontSetting
FFontSetting *fontHeaderSetting = ui->wFont;
if (fontHeaderSetting) {
// Получаем выбранный шрифт заголовка
notif.titleFamily = fontHeaderSetting->cbFontStyle->currentText();
// Получаем размер шрифта заголовка
notif.titleSize = fontHeaderSetting->sbFontSize->value();
// Получаем цвет шрифта заголовка
QColor titleColor = fontHeaderSetting->cbFontColor->currentData().value<QColor>();
if (!titleColor.isValid()) {
titleColor = QColor(fontHeaderSetting->cbFontColor->currentText());
}
notif.titleColor = titleColor.name();
}
// 3. Получаем настройки шрифта сообщения из FFontSetting
FFontSetting *fontMessageSetting = ui->wFont_2;
if (fontMessageSetting) {
// Получаем выбранный шрифт сообщения
notif.contentFamily = fontMessageSetting->cbFontStyle->currentText();
// Получаем размер шрифта сообщения
notif.contentSize = fontMessageSetting->sbFontSize->value();
// Получаем цвет шрифта сообщения
QColor contentColor = fontMessageSetting->cbFontColor->currentData().value<QColor>();
if (!contentColor.isValid()) {
contentColor = QColor(fontMessageSetting->cbFontColor->currentText());
}
notif.contentColor = contentColor.name();
}
// 4. Получаем настройки веб-сервера из FSettingsWS
FSettingsWS *settingsWS = ui->widget;
if (settingsWS) {
// Получаем время отображения уведомления
notif.duration = settingsWS->sbTime->value();
} else {
// Значение по умолчанию, если виджет не найден
notif.duration = 15;
}
// Добавляем уведомление на сервер
m_server->addNotification(notif);
if (!isTest) {
// Очищаем только если это не тест
ui->edtHeader->clear();
ui->edtMessage->clear();
}
}
void FCreateNotify::copyFileToAppDir(const QString &sourcePath, const QString &destSubDir)
{
QFileInfo sourceInfo(sourcePath);
QString destPath = getAbsolutePath(destSubDir + "/" + sourceInfo.fileName());
// Копируем файл, если он еще не существует или изменился
if (!QFile::exists(destPath) ||
QFileInfo(sourcePath).lastModified() > QFileInfo(destPath).lastModified()) {
QFile::remove(destPath); // Удаляем старую версию
if (QFile::copy(sourcePath, destPath)) {
} else {
}
}
}
QString FCreateNotify::getAbsolutePath(const QString &relativePath)
{
return QApplication::applicationDirPath() + "/" + relativePath;
}
void FCreateNotify::on_btnOpenImg_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,
"Выберите изображение",
"",
"Images (*.png *.jpg *.jpeg *.gif *.bmp *.svg)");
if (!fileName.isEmpty()) {
ui->edtFileImg->setText(fileName);
}
}
void FCreateNotify::on_btnOpenSong_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,
"Выберите звуковой файл",
"",
"Audio files (*.mp3 *.wav *.ogg *.flac)");
if (!fileName.isEmpty()) {
ui->edtFileSong->setText(fileName);
}
}
void FCreateNotify::onServerStarted(bool success)
{
if (success) {
ui->btnTest->setEnabled(true);
ui->btnAdd->setEnabled(true);
QString url = QString("http://localhost:%1").arg(m_server->port());
ui->btnTest->setText("Тест (" + url + ")");
} else {
ui->btnTest->setEnabled(false);
ui->btnAdd->setEnabled(false);
QMessageBox::warning(this, "Ошибка", "Не удалось запустить веб-сервер");
}
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef FCREATENOTIFY_H
#define FCREATENOTIFY_H
#include "webservernotify.h"
#include <QDialog>
namespace Ui {
class FCreateNotify;
}
class FCreateNotify : public QDialog
{
Q_OBJECT
signals:
void serverCreated(HttpServer *server, const QString &name);
public:
explicit FCreateNotify(QWidget *parent = nullptr);
~FCreateNotify();
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void on_btnOpenImg_clicked();
void on_btnOpenSong_clicked();
void on_btnTest_clicked();
void on_btnAdd_clicked();
void onServerStarted(bool success);
private:
Ui::FCreateNotify *ui;
void createNotification(bool isTest = false);
QString getAbsolutePath(const QString &relativePath);
void copyFileToAppDir(const QString &sourcePath, const QString &destSubDir);
// Сервер
HttpServer *m_server;
int m_notificationCounter;
void createServer();
};
#endif // FCREATENOTIFY_H
+252
View File
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FCreateNotify</class>
<widget class="QDialog" name="FCreateNotify">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>728</width>
<height>439</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QGroupBox" name="groupBox_3">
<property name="geometry">
<rect>
<x>370</x>
<y>0</y>
<width>351</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>Настройки</string>
</property>
<widget class="FSettingsWS" name="widget" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>121</height>
</rect>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>351</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>Блок</string>
</property>
<widget class="FColorSetting" name="wBlock" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>121</height>
</rect>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>10</x>
<y>150</y>
<width>351</width>
<height>141</height>
</rect>
</property>
<property name="title">
<string>Заголовок</string>
</property>
<widget class="FFontSetting" name="wFont" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>331</width>
<height>81</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="edtHeader">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>24</height>
</rect>
</property>
<property name="placeholderText">
<string>[NICK] </string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_4">
<property name="geometry">
<rect>
<x>10</x>
<y>290</y>
<width>351</width>
<height>141</height>
</rect>
</property>
<property name="title">
<string>Сообщение</string>
</property>
<widget class="FFontSetting" name="wFont_2" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>331</width>
<height>81</height>
</rect>
</property>
</widget>
<widget class="QLineEdit" name="edtMessage">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>24</height>
</rect>
</property>
<property name="placeholderText">
<string>Сообщение</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_5">
<property name="geometry">
<rect>
<x>370</x>
<y>150</y>
<width>351</width>
<height>181</height>
</rect>
</property>
<property name="title">
<string>Персональное</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>19</y>
<width>331</width>
<height>152</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLineEdit" name="edtFileSong"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Звук</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="btnOpenSong">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="edtFileImg"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Картинка</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="btnOpenImg">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Событие</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QComboBox" name="cbEvent"/>
</item>
</layout>
</widget>
</widget>
<widget class="QPushButton" name="btnTest">
<property name="geometry">
<rect>
<x>370</x>
<y>340</y>
<width>80</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Тест</string>
</property>
</widget>
<widget class="QPushButton" name="btnAdd">
<property name="geometry">
<rect>
<x>460</x>
<y>340</y>
<width>81</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Создать</string>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>FColorSetting</class>
<extends>QWidget</extends>
<header location="global">fcolorsetting.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FFontSetting</class>
<extends>QWidget</extends>
<header location="global">ffontsetting.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>FSettingsWS</class>
<extends>QWidget</extends>
<header location="global">fsettingsws.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+105
View File
@@ -0,0 +1,105 @@
#include "ffontsetting.h"
#include "qdebug.h"
#include "qdir.h"
#include "ui_ffontsetting.h"
#include <QFontDatabase>
#include <QApplication>
FFontSetting::FFontSetting(QWidget *parent)
: QWidget(parent)
, cbFontStyle(nullptr)
, cbFontColor(nullptr)
, sbFontSize(nullptr)
, ui(new Ui::FFontSetting)
{
ui->setupUi(this);
setupColorComboBox(ui->cbFontColor);
cbFontStyle = ui->cbFontStyle;
cbFontColor = ui->cbFontColor;
sbFontSize = ui->sbFontSize;
// Загружаем шрифты при создании виджета
loadFonts();
}
FFontSetting::~FFontSetting()
{
delete ui;
}
void FFontSetting::setupColorComboBox(QComboBox* comboBox)
{
comboBox->clear();
// Добавляем цвета
QStringList colorNames = QColor::colorNames();
foreach (const QString &name, colorNames) {
QColor color(name);
QPixmap pixmap(20, 20);
pixmap.fill(color);
comboBox->addItem(QIcon(pixmap), name, color);
}
// Устанавливаем первый цвет по умолчанию
if (comboBox->count() > 0) {
comboBox->setCurrentIndex(0);
}
}
void FFontSetting::loadFonts()
{
QString fontsPath = "fonts";
QDir fontsDir(fontsPath);
if (fontsDir.exists()) {
QStringList fontFiles = fontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc",
QDir::Files);
foreach (const QString &fontFile, fontFiles) {
QString fontPath = fontsDir.absoluteFilePath(fontFile);
int fontId = QFontDatabase::addApplicationFont(fontPath);
if (fontId != -1) {
QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId);
} else {
}
}
// ОБНОВЛЯЕМ СПИСОК ШРИФТОВ В КОМБОБОКСЕ
ui->cbFontStyle->clear(); // Очищаем старый список
// Получаем все шрифты через объект QFontDatabase
QFontDatabase fontDatabase;
QStringList fontFamilies = fontDatabase.families(); // Получаем все шрифты
// ИЛИ используйте статический метод с параметром:
// QStringList fontFamilies = QFontDatabase::families(QFontDatabase::Any);
ui->cbFontStyle->addItems(fontFamilies); // Добавляем все шрифты
// Устанавливаем шрифт по умолчанию
int defaultIndex = ui->cbFontStyle->findText("Arial");
if (defaultIndex >= 0) {
ui->cbFontStyle->setCurrentIndex(defaultIndex);
}
// Показываем количество доступных шрифтов
} else {
// Загружаем системные шрифты, если папки нет
QFontDatabase fontDatabase;
QStringList fontFamilies = fontDatabase.families();
ui->cbFontStyle->clear();
ui->cbFontStyle->addItems(fontFamilies);
int defaultIndex = ui->cbFontStyle->findText("Arial");
if (defaultIndex >= 0) {
ui->cbFontStyle->setCurrentIndex(defaultIndex);
}
// Проверяем альтернативные пути
QString appDir = QApplication::applicationDirPath();
QString altPath = appDir + "/fonts";
}
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef FFONTSETTING_H
#define FFONTSETTING_H
#include "qspinbox.h"
#include <QWidget>
#include <QFontComboBox>
#include <QComboBox>
namespace Ui {
class FFontSetting;
}
class FFontSetting : public QWidget
{
Q_OBJECT
public:
explicit FFontSetting(QWidget *parent = nullptr);
~FFontSetting();
QFontComboBox *cbFontStyle;
QComboBox *cbFontColor;
QSpinBox *sbFontSize;
private:
Ui::FFontSetting *ui;
void setupColorComboBox(QComboBox* comboBox);
void loadFonts(); // Убедитесь, что метод объявлен как public или private
};
#endif // FFONTSETTING_H
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FFontSetting</class>
<widget class="QWidget" name="FFontSetting">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>64</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>331</width>
<height>61</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Стиль</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Цвет</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QFontComboBox" name="cbFontStyle"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cbFontColor"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Размер</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="sbFontSize">
<property name="value">
<number>14</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+22
View File
@@ -0,0 +1,22 @@
#include "fsettingsws.h"
#include "ui_fsettingsws.h"
FSettingsWS::FSettingsWS(QWidget *parent)
: QWidget(parent)
, sbPort(nullptr)
, sbTime(nullptr)
, sbCount(nullptr)
, cbFreez(nullptr)
, ui(new Ui::FSettingsWS)
{
ui->setupUi(this);
sbPort = ui->sbPort;
sbTime = ui->sbTime;
sbCount = ui->sbCount;
cbFreez = ui->cbFreez;
}
FSettingsWS::~FSettingsWS()
{
delete ui;
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef FSETTINGSWS_H
#define FSETTINGSWS_H
#include "qcheckbox.h"
#include "qspinbox.h"
#include <QWidget>
namespace Ui {
class FSettingsWS;
}
class FSettingsWS : public QWidget
{
Q_OBJECT
public:
explicit FSettingsWS(QWidget *parent = nullptr);
~FSettingsWS();
QSpinBox *sbPort;
QSpinBox *sbTime;
QSpinBox *sbCount;
QCheckBox *cbFreez;
private:
Ui::FSettingsWS *ui;
};
#endif // FSETTINGSWS_H
+83
View File
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FSettingsWS</class>
<widget class="QWidget" name="FSettingsWS">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>402</width>
<height>132</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>401</width>
<height>131</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Время отображения</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Максимальное количество</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="sbTime">
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="sbPort">
<property name="minimum">
<number>8000</number>
</property>
<property name="maximum">
<number>65000</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Порт</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="sbCount">
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="cbFreez">
<property name="text">
<string>Вечно</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+136
View File
@@ -0,0 +1,136 @@
#include "fsinglegrid.h"
#include "ui_fsinglegrid.h"
#include <QMessageBox>
#include <QFileInfo>
#include <QFileDialog>
FSingleGrid::FSingleGrid(QWidget *parent) :
QWidget(parent),
ui(new Ui::FSingleGrid)
{
ui->setupUi(this);
}
FSingleGrid::~FSingleGrid()
{
delete ui;
}
QTableWidget* FSingleGrid::tableWidget() const
{
return ui->sg;
}
void FSingleGrid::initForm(QString aBlockName, QString aNewName, bool btnOpen)
{
ui->groupBox->setTitle(aBlockName);
QStringList headers;
headers << "Название" << "Файл";
ui->sg->setHorizontalHeaderLabels(headers);
ui->sg->setSelectionBehavior(QAbstractItemView::SelectRows); // Выделение строк
ui->sg->setSelectionMode(QAbstractItemView::SingleSelection); // Одиночное выделение
ui->sg->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->sg->setColumnWidth(0, 100);
ui->sg->setColumnWidth(1, 170);
ui->sg->setObjectName(aNewName);
ui->btnOpen->setVisible(btnOpen);
}
void FSingleGrid::toGrid(QString aName, QString aFile)
{
// Добавляем новую строку в таблицу
int row = ui->sg->rowCount();
ui->sg->insertRow(row);
QTableWidgetItem *R1 = new QTableWidgetItem(aName);
QTableWidgetItem *R2 = new QTableWidgetItem(aFile);
ui->sg->setItem(row, 0, R1);
ui->sg->setItem(row, 1, R2);
ui->sg->scrollToBottom();
}
void FSingleGrid::on_btnAdd_clicked()
{
toGrid(ui->edtName->text(), ui->edtFileName->text());
db->SaveTableWidget(ui->sg);
}
void FSingleGrid::setDatabase(uDataBase *database)
{
db = database;
}
void FSingleGrid::on_btnDel_clicked()
{
// Проверяем, есть ли выделенная строка
if (!ui->sg->currentItem()) {
QMessageBox::warning(this, "Внимание", "Выберите строку для удаления!");
return;
}
// Получаем индекс выделенной строки
int row = ui->sg->currentItem()->row();
// Удаляем строку
ui->sg->removeRow(row);
db->SaveTableWidget(ui->sg);
}
void FSingleGrid::on_btnEdt_clicked()
{
// Проверяем, есть ли выделенная строка
if (!ui->sg->currentItem()) {
QMessageBox::warning(this, "Внимание", "Выберите строку для редактирования!");
return;
}
// Получаем индекс выделенной строки
int row = ui->sg->currentItem()->row();
// Получаем данные из выбранной строки
ui->sg->item(row, 0)->setText(ui->edtName->text());
ui->sg->item(row, 1)->setText(ui->edtFileName->text());
db->SaveTableWidget(ui->sg);
}
void FSingleGrid::on_btnOpen_clicked()
{
// Диалог выбора файла
QString fileName = QFileDialog::getOpenFileName(
this, // родительское окно
"Выберите файл", // заголовок окна
QDir::homePath(), // начальная директория
"Все файлы (*.*);;" // фильтры файлов
"Текстовые файлы (*.txt);;"
"Аудио (*.mp3);;"
"Приложения (*.exe *.bat *.cmd)"
);
// Если файл выбран (не нажата кнопка "Отмена")
if (!fileName.isEmpty()) {
// Записываем путь к файлу в поле ввода
ui->edtFileName->setText(fileName);
// Опционально: автоматически заполняем поле имени из названия файла
if (ui->edtName->text().isEmpty()) {
QFileInfo fileInfo(fileName);
QString baseName = fileInfo.baseName(); // Имя файла без расширения
ui->edtName->setText(baseName);
}
}
}
void FSingleGrid::on_sg_cellClicked(int row, int column)
{
ui->edtName->setText(ui->sg->item(row,0)->text());
ui->edtFileName->setText(ui->sg->item(row,1)->text());
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef FSINGLEGRID_H
#define FSINGLEGRID_H
#include <QWidget>
#include <QTableWidget>
#include "udatabase.h"
namespace Ui {
class FSingleGrid;
}
class FSingleGrid : public QWidget
{
Q_OBJECT
public:
explicit FSingleGrid(QWidget *parent = nullptr);
~FSingleGrid();
void initForm(QString aBlockName, QString aNewName, bool btnOpen = false);
void toGrid(QString aName, QString aFile);
QTableWidget* tableWidget() const;
void setDatabase(uDataBase *database);
private slots:
void on_btnAdd_clicked();
void on_btnDel_clicked();
void on_btnEdt_clicked();
void on_btnOpen_clicked();
void on_sg_cellClicked(int row, int column);
private:
Ui::FSingleGrid *ui;
uDataBase *db = nullptr;
};
#endif // FSINGLEGRID_H
+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FSingleGrid</class>
<widget class="QWidget" name="FSingleGrid">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>311</width>
<height>234</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>309</width>
<height>231</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
<widget class="QTableWidget" name="sg">
<property name="geometry">
<rect>
<x>5</x>
<y>21</y>
<width>301</width>
<height>131</height>
</rect>
</property>
<property name="columnCount">
<number>2</number>
</property>
<column/>
<column/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>160</y>
<width>291</width>
<height>61</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="edtName"/>
</item>
<item>
<widget class="QLineEdit" name="edtFileName"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnOpen">
<property name="text">
<string>Открыть</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btnAdd">
<property name="text">
<string>Добавить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnEdt">
<property name="text">
<string>Изменить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDel">
<property name="text">
<string>Удалить</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
<resources/>
<connections/>
</ui>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+576
View File
@@ -0,0 +1,576 @@
/*
AMOLED Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 01/10/2021, 15:49.
Available at: https://github.com/GTRONICK/QSS/blob/master/AMOLED.qss
*/
QMainWindow {
background-color:#000000;
}
QDialog {
background-color:#000000;
}
QColorDialog {
background-color:#000000;
}
QTextEdit {
background-color:#000000;
color: #a9b7c6;
}
QPlainTextEdit {
selection-background-color:#f39c12;
background-color:#000000;
border: 1px solid #FF00FF;
color: #a9b7c6;
}
QPushButton{
border: 1px transparent;
color: #a9b7c6;
padding: 2px;
background-color: #000000;
}
QPushButton::default{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #e67e22;
border-width: 1px;
color: #a9b7c6;
padding: 2px;
background-color: #000000;
}
QPushButton:hover{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #FF00FF;
border-bottom-width: 1px;
border-bottom-radius: 6px;
border-style: solid;
color: #FFFFFF;
padding-bottom: 2px;
background-color: #000000;
}
QPushButton:pressed{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #FF00FF;
border-bottom-width: 2px;
border-bottom-radius: 6px;
border-style: solid;
color: #e67e22;
padding-bottom: 1px;
background-color: #000000;
}
QPushButton:disabled{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 2px;
border-bottom-radius: 6px;
border-style: solid;
color: #808086;
padding-bottom: 1px;
background-color: #000000;
}
QToolButton {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #e67e22;
border-bottom-width: 1px;
border-style: solid;
color: #a9b7c6;
padding: 2px;
background-color: #000000;
}
QToolButton:hover{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #e67e22;
border-bottom-width: 2px;
border-bottom-radius: 6px;
border-style: solid;
color: #FFFFFF;
padding-bottom: 1px;
background-color: #000000;
}
QLineEdit {
border-width: 1px; border-radius: 4px;
border-color: rgb(58, 58, 58);
border-style: inset;
padding: 0 8px;
color: #a9b7c6;
background:#000000;
selection-background-color:#007b50;
selection-color: #FFFFFF;
}
QLabel {
color: #a9b7c6;
}
QLCDNumber {
color: #e67e22;
}
QProgressBar {
text-align: center;
color: rgb(240, 240, 240);
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
background-color:#000000;
}
QProgressBar::chunk {
background-color: #e67e22;
border-radius: 5px;
}
QMenu{
background-color:#000000;
}
QMenuBar {
background:rgb(0, 0, 0);
color: #a9b7c6;
}
QMenuBar::item {
spacing: 3px;
padding: 1px 4px;
background: transparent;
}
QMenuBar::item:selected {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #e67e22;
border-bottom-width: 1px;
border-bottom-radius: 6px;
border-style: solid;
color: #FFFFFF;
padding-bottom: 0px;
background-color: #000000;
}
QMenu::item:selected {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: #e67e22;
border-bottom-color: transparent;
border-left-width: 2px;
color: #FFFFFF;
padding-left:15px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
background-color:#000000;
}
QMenu::item {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 1px;
border-style: solid;
color: #a9b7c6;
padding-left:17px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
background-color:#000000;
}
QTabWidget {
color:rgb(0,0,0);
background-color:#000000;
}
QTabWidget::pane {
border-color: rgb(77,77,77);
background-color:#000000;
border-style: solid;
border-width: 1px;
border-radius: 6px;
}
QTabBar::tab {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 1px;
border-style: solid;
color: #808086;
padding: 3px;
margin-left:3px;
background-color:#000000;
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #e67e22;
border-bottom-width: 2px;
border-style: solid;
color: #FFFFFF;
padding-left: 3px;
padding-bottom: 2px;
margin-left:3px;
background-color:#000000;
}
QCheckBox {
color: #a9b7c6;
padding: 2px;
}
QCheckBox:disabled {
color: #808086;
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: rgb(87, 97, 106);
background-color:#000000;
}
QCheckBox::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #e67e22;
color: #a9b7c6;
background-color: #e67e22;
}
QCheckBox::indicator:unchecked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #e67e22;
color: #a9b7c6;
background-color: transparent;
}
QRadioButton {
color: #a9b7c6;
background-color:#000000;
padding: 1px;
}
QRadioButton::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #e67e22;
color: #a9b7c6;
background-color: #e67e22;
}
QRadioButton::indicator:!checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #e67e22;
color: #a9b7c6;
background-color: transparent;
}
QStatusBar {
color:#34e8eb;
}
QSpinBox {
color: #a9b7c6;
background-color:#000000;
}
QDoubleSpinBox {
color: #a9b7c6;
background-color:#000000;
}
QTimeEdit {
color: #a9b7c6;
background-color:#000000;
}
QDateTimeEdit {
color: #a9b7c6;
background-color:#000000;
}
QDateEdit {
color: #a9b7c6;
background-color:#000000;
}
QComboBox {
color: #a9b7c6;
background: #1e1d23;
}
QComboBox:editable {
background: #1e1d23;
color: #a9b7c6;
selection-background-color:#000000;
}
QComboBox QAbstractItemView {
color: #a9b7c6;
background: #1e1d23;
selection-color: #FFFFFF;
selection-background-color:#000000;
}
QComboBox:!editable:on, QComboBox::drop-down:editable:on {
color: #a9b7c6;
background: #1e1d23;
}
QFontComboBox {
color: #a9b7c6;
background-color:#000000;
}
QToolBox {
color: #a9b7c6;
background-color:#000000;
}
QToolBox::tab {
color: #a9b7c6;
background-color:#000000;
}
QToolBox::tab:selected {
color: #FFFFFF;
background-color:#000000;
}
QScrollArea {
color: #FFFFFF;
background-color:#000000;
}
QSlider::groove:horizontal {
height: 5px;
background: #e67e22;
}
QSlider::groove:vertical {
width: 5px;
background: #e67e22;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);
border: 1px solid #5c5c5c;
width: 14px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: qlineargradient(x1:1, y1:1, x2:0, y2:0, stop:0 #b4b4b4, stop:1 #8f8f8f);
border: 1px solid #5c5c5c;
height: 14px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page:horizontal {
background: white;
}
QSlider::add-page:vertical {
background: white;
}
QSlider::sub-page:horizontal {
background: #e67e22;
}
QSlider::sub-page:vertical {
background: #e67e22;
}
QScrollBar:horizontal {
max-height: 20px;
background: rgb(0,0,0);
border: 1px transparent grey;
margin: 0px 20px 0px 20px;
}
QScrollBar::handle:horizontal {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
border-style: solid;
border-width: 1px;
border-color: rgb(0,0,0);
min-width: 25px;
}
QScrollBar::handle:horizontal:hover {
background: rgb(230, 126, 34);
border-style: solid;
border-width: 1px;
border-color: rgb(0,0,0);
min-width: 25px;
}
QScrollBar::add-line:horizontal {
border: 1px solid;
border-color: rgb(0,0,0);
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:hover {
border: 1px solid;
border-color: rgb(0,0,0);
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:pressed {
border: 1px solid;
border-color: grey;
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal {
border: 1px solid;
border-color: rgb(0,0,0);
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:hover {
border: 1px solid;
border-color: rgb(0,0,0);
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:pressed {
border: 1px solid;
border-color: grey;
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::left-arrow:horizontal {
border: 1px transparent grey;
border-radius: 3px;
width: 6px;
height: 6px;
background: rgb(0,0,0);
}
QScrollBar::right-arrow:horizontal {
border: 1px transparent grey;
border-radius: 3px;
width: 6px;
height: 6px;
background: rgb(0,0,0);
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar:vertical {
max-width: 20px;
background: rgb(0,0,0);
border: 1px transparent grey;
margin: 20px 0px 20px 0px;
}
QScrollBar::add-line:vertical {
border: 1px solid;
border-color: rgb(0,0,0);
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:hover {
border: 1px solid;
border-color: rgb(0,0,0);
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:pressed {
border: 1px solid;
border-color: grey;
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical {
border: 1px solid;
border-color: rgb(0,0,0);
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:hover {
border: 1px solid;
border-color: rgb(0,0,0);
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:pressed {
border: 1px solid;
border-color: grey;
border-radius: 8px;
background: rgb(230, 126, 34);
height: 16px;
width: 16px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::handle:vertical {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 rgba(255, 0, 0, 0), stop:0.7 rgba(255, 0, 0, 0), stop:0.71 rgb(230, 126, 34), stop:1 rgb(230, 126, 34));
border-style: solid;
border-width: 1px;
border-color: rgb(0,0,0);
min-height: 25px;
}
QScrollBar::handle:vertical:hover {
background: rgb(230, 126, 34);
border-style: solid;
border-width: 1px;
border-color: rgb(0,0,0);
min-heigth: 25px;
}
QScrollBar::up-arrow:vertical {
border: 1px transparent grey;
border-radius: 3px;
width: 6px;
height: 6px;
background: rgb(0,0,0);
}
QScrollBar::down-arrow:vertical {
border: 1px transparent grey;
border-radius: 3px;
width: 6px;
height: 6px;
background: rgb(0,0,0);
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
+559
View File
@@ -0,0 +1,559 @@
/*
Aqua Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 22/01/2019, 07:55.
Available at: https://github.com/GTRONICK/QSS/blob/master/Aqua.qss
*/
QMainWindow {
background-color:#ececec;
}
QTextEdit {
border-width: 1px;
border-style: solid;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QPlainTextEdit {
border-width: 1px;
border-style: solid;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QToolButton {
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QToolButton:hover{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(197, 197, 197), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(197, 197, 197));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QToolButton:pressed{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(142,142,142);
}
QPushButton{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QPushButton::default{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QPushButton:hover{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(197, 197, 197), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(197, 197, 197));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QPushButton:pressed{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(142,142,142);
}
QPushButton:disabled{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: #808086;
padding: 2px;
background-color: rgb(142,142,142);
}
QLineEdit {
border-width: 1px; border-radius: 4px;
border-style: solid;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QLabel {
color: #000000;
}
QLCDNumber {
color: rgb(0, 113, 255, 255);
}
QProgressBar {
text-align: center;
color: rgb(240, 240, 240);
border-width: 1px;
border-radius: 10px;
border-color: rgb(230, 230, 230);
border-style: solid;
background-color:rgb(207,207,207);
}
QProgressBar::chunk {
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(49, 147, 250, 255), stop:1 rgba(34, 142, 255, 255));
border-radius: 10px;
}
QMenuBar {
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(207, 209, 207, 255), stop:1 rgba(230, 229, 230, 255));
}
QMenuBar::item {
color: #000000;
spacing: 3px;
padding: 1px 4px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(207, 209, 207, 255), stop:1 rgba(230, 229, 230, 255));
}
QMenuBar::item:selected {
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
color: #FFFFFF;
}
QMenu::item:selected {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
border-bottom-color: transparent;
border-left-width: 2px;
color: #000000;
padding-left:15px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
}
QMenu::item {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 1px;
color: #000000;
padding-left:17px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
}
QTabWidget {
color:rgb(0,0,0);
background-color:#000000;
}
QTabWidget::pane {
border-color: rgb(223,223,223);
background-color:rgb(226,226,226);
border-style: solid;
border-width: 2px;
border-radius: 6px;
}
QTabBar::tab:first {
border-style: solid;
border-left-width:1px;
border-right-width:0px;
border-top-width:1px;
border-bottom-width:1px;
border-top-color: rgb(209,209,209);
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-bottom-color: rgb(229,229,229);
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
color: #000000;
padding: 3px;
margin-left:0px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(247, 247, 247, 255), stop:1 rgba(255, 255, 255, 255));
}
QTabBar::tab:last {
border-style: solid;
border-width:1px;
border-top-color: rgb(209,209,209);
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-right-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-bottom-color: rgb(229,229,229);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
color: #000000;
padding: 3px;
margin-left:0px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(247, 247, 247, 255), stop:1 rgba(255, 255, 255, 255));
}
QTabBar::tab {
border-style: solid;
border-top-width:1px;
border-bottom-width:1px;
border-left-width:1px;
border-top-color: rgb(209,209,209);
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-bottom-color: rgb(229,229,229);
color: #000000;
padding: 3px;
margin-left:0px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(247, 247, 247, 255), stop:1 rgba(255, 255, 255, 255));
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
border-style: solid;
border-left-width:1px;
border-right-color: transparent;
border-top-color: rgb(209,209,209);
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-bottom-color: rgb(229,229,229);
color: #FFFFFF;
padding: 3px;
margin-left:0px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QTabBar::tab:selected, QTabBar::tab:first:selected, QTabBar::tab:hover {
border-style: solid;
border-left-width:1px;
border-bottom-width:1px;
border-top-width:1px;
border-right-color: transparent;
border-top-color: rgb(209,209,209);
border-left-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(209, 209, 209, 209), stop:1 rgba(229, 229, 229, 229));
border-bottom-color: rgb(229,229,229);
color: #FFFFFF;
padding: 3px;
margin-left:0px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QCheckBox {
color: #000000;
padding: 2px;
}
QCheckBox:disabled {
color: #808086;
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: transparent;
}
QCheckBox::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
color: #000000;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QCheckBox::indicator:unchecked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
color: #000000;
}
QRadioButton {
color: 000000;
padding: 1px;
}
QRadioButton::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
color: #a9b7c6;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QRadioButton::indicator:!checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
color: #a9b7c6;
background-color: transparent;
}
QStatusBar {
color:#027f7f;
}
QSpinBox {
border-style: solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QDoubleSpinBox {
border-style: solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QTimeEdit {
border-style: solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QDateTimeEdit {
border-style: solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QDateEdit {
border-style: solid;
border-width: 1px;
border-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(0, 113, 255, 255), stop:1 rgba(91, 171, 252, 255));
}
QToolBox {
color: #a9b7c6;
background-color:#000000;
}
QToolBox::tab {
color: #a9b7c6;
background-color:#000000;
}
QToolBox::tab:selected {
color: #FFFFFF;
background-color:#000000;
}
QScrollArea {
color: #FFFFFF;
background-color:#000000;
}
QSlider::groove:horizontal {
height: 5px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(49, 147, 250, 255), stop:1 rgba(34, 142, 255, 255));
}
QSlider::groove:vertical {
width: 5px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(49, 147, 250, 255), stop:1 rgba(34, 142, 255, 255));
}
QSlider::handle:horizontal {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
width: 12px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
height: 12px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page:horizontal {
background: rgb(181,181,181);
}
QSlider::add-page:vertical {
background: rgb(181,181,181);
}
QSlider::sub-page:horizontal {
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(49, 147, 250, 255), stop:1 rgba(34, 142, 255, 255));
}
QSlider::sub-page:vertical {
background-color: qlineargradient(spread:pad, y1:0.5, x1:1, y2:0.5, x2:0, stop:0 rgba(49, 147, 250, 255), stop:1 rgba(34, 142, 255, 255));
}
QScrollBar:horizontal {
max-height: 20px;
border: 1px transparent grey;
margin: 0px 20px 0px 20px;
}
QScrollBar:vertical {
max-width: 20px;
border: 1px transparent grey;
margin: 20px 0px 20px 0px;
}
QScrollBar::handle:horizontal {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
border-radius: 7px;
min-width: 25px;
}
QScrollBar::handle:horizontal:hover {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(147, 200, 200);
border-radius: 7px;
min-width: 25px;
}
QScrollBar::handle:vertical {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
border-radius: 7px;
min-height: 25px;
}
QScrollBar::handle:vertical:hover {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(147, 200, 200);
border-radius: 7px;
min-height: 25px;
}
QScrollBar::add-line:horizontal {
border: 2px transparent grey;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
background: rgba(34, 142, 255, 255);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:pressed {
border: 2px transparent grey;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
background: rgb(181,181,181);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical {
border: 2px transparent grey;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
background: rgba(34, 142, 255, 255);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:pressed {
border: 2px transparent grey;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
background: rgb(181,181,181);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal {
border: 2px transparent grey;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgba(34, 142, 255, 255);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:pressed {
border: 2px transparent grey;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(181,181,181);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical {
border: 2px transparent grey;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
background: rgba(34, 142, 255, 255);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:pressed {
border: 2px transparent grey;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
background: rgb(181,181,181);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::left-arrow:horizontal {
border: 1px transparent grey;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
width: 6px;
height: 6px;
background: white;
}
QScrollBar::right-arrow:horizontal {
border: 1px transparent grey;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
width: 6px;
height: 6px;
background: white;
}
QScrollBar::up-arrow:vertical {
border: 1px transparent grey;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
width: 6px;
height: 6px;
background: white;
}
QScrollBar::down-arrow:vertical {
border: 1px transparent grey;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
width: 6px;
height: 6px;
background: white;
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
+181
View File
@@ -0,0 +1,181 @@
/*
Dark Console Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 24/05/2018, 17:12.
Available at: https://github.com/GTRONICK/QSS/blob/master/ConsoleStyle.qss
*/
QWidget {
background-color:rgb(0, 0, 0);
color: rgb(240, 240, 240);
border-color: rgb(58, 58, 58);
}
QPlainTextEdit {
background-color:rgb(0, 0, 0);
color: rgb(200, 200, 200);
selection-background-color: rgb(255, 153, 0);
selection-color: rgb(0, 0, 0);
}
QTabWidget::pane {
border-top: 1px solid #000000;
}
QTabBar::tab {
background-color:rgb(0, 0, 0);
border-style: outset;
border-width: 1px;
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-top-width: 0px;
border-style: solid;
color: rgb(255, 153, 0);
padding: 4px;
}
QTabBar::tab:selected, QTabBar::tab:hover {
color: rgb(255, 255, 255);
background-color:rgb(0, 0, 0);
border-color:rgb(42, 42, 42);
margin-left: 0px;
margin-right: 0px;
border-bottom-right-radius:4px;
border-bottom-left-radius:4px;
}
QTabBar::tab:last:selected {
background-color:rgb(0, 0, 0);
border-color:rgb(42, 42, 42);
margin-left: 0px;
margin-right: 0px;
border-bottom-right-radius:4px;
border-bottom-left-radius:4px;
}
QTabBar::tab:!selected {
margin-bottom: 4px;
border-bottom-right-radius:4px;
border-bottom-left-radius:4px;
}
QPushButton{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 6px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255));
}
QPushButton:hover{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-bottom-color: rgb(115, 115, 115);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 6px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(107, 107, 107, 255), stop:1 rgba(157, 157, 157, 255));
}
QPushButton:pressed{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(62, 62, 62, 255), stop:1 rgba(22, 22, 22, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 6px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255));
}
QPushButton:disabled{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(0, 0, 0);
padding: 6px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(57, 57, 57, 255), stop:1 rgba(77, 77, 77, 255));
}
QLineEdit {
border-width: 1px; border-radius: 4px;
border-color: rgb(58, 58, 58);
border-style: inset;
padding: 0 8px;
color: rgb(255, 255, 255);
background:rgb(101, 101, 101);
selection-background-color: rgb(187, 187, 187);
selection-color: rgb(60, 63, 65);
}
QProgressBar {
text-align: center;
color: rgb(255, 255, 255);
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
}
QProgressBar::chunk {
background-color: qlineargradient(spread:pad, x1:0.5, y1:0.7, x2:0.5, y2:0.3, stop:0 rgba(0, 200, 0, 255), stop:1 rgba(30, 230, 30, 255));
border-radius: 10px;
}
QMenuBar {
background:rgb(0, 0, 0);
color: rgb(255, 153, 0);
}
QMenuBar::item {
spacing: 3px;
padding: 1px 4px;
background: transparent;
}
QMenuBar::item:selected {
background:rgb(115, 115, 115);
}
QMenu {
border-width: 2px;
border-radius: 10px;
border-color: rgb(255, 153, 0);
border-style: outset;
}
QMenu::item {
spacing: 3px;
padding: 3px 15px;
}
QMenu::item:selected {
spacing: 3px;
padding: 3px 15px;
background:rgb(115, 115, 115);
color:rgb(255, 255, 255);
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
}
+196
View File
@@ -0,0 +1,196 @@
/*
ElegantDark Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 17/04/2018
Available at: https://github.com/GTRONICK/QSS/blob/master/ElegantDark.qss
*/
QMainWindow {
background-color:rgb(82, 82, 82);
}
QTextEdit {
background-color:rgb(42, 42, 42);
color: rgb(0, 255, 0);
}
QPushButton{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255));
}
QPushButton:hover{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(180, 180, 180, 255), stop:1 rgba(110, 110, 110, 255));
border-bottom-color: rgb(115, 115, 115);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(107, 107, 107, 255), stop:1 rgba(157, 157, 157, 255));
}
QPushButton:pressed{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(62, 62, 62, 255), stop:1 rgba(22, 22, 22, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(255, 255, 255);
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255));
}
QPushButton:disabled{
border-style: outset;
border-width: 2px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
border-style: solid;
color: rgb(0, 0, 0);
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(57, 57, 57, 255), stop:1 rgba(77, 77, 77, 255));
}
QLineEdit {
border-width: 1px; border-radius: 4px;
border-color: rgb(58, 58, 58);
border-style: inset;
padding: 0 8px;
color: rgb(255, 255, 255);
background:rgb(100, 100, 100);
selection-background-color: rgb(187, 187, 187);
selection-color: rgb(60, 63, 65);
}
QLabel {
color:rgb(255,255,255);
}
QProgressBar {
text-align: center;
color: rgb(240, 240, 240);
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
background-color:rgb(77,77,77);
}
QProgressBar::chunk {
background-color: qlineargradient(spread:pad, x1:0.5, y1:0.7, x2:0.5, y2:0.3, stop:0 rgba(87, 97, 106, 255), stop:1 rgba(93, 103, 113, 255));
border-radius: 5px;
}
QMenuBar {
background:rgb(82, 82, 82);
}
QMenuBar::item {
color:rgb(223,219,210);
spacing: 3px;
padding: 1px 4px;
background: transparent;
}
QMenuBar::item:selected {
background:rgb(115, 115, 115);
}
QMenu::item:selected {
color:rgb(255,255,255);
border-width:2px;
border-style:solid;
padding-left:18px;
padding-right:8px;
padding-top:2px;
padding-bottom:3px;
background:qlineargradient(spread:pad, x1:0.5, y1:0.7, x2:0.5, y2:0.3, stop:0 rgba(87, 97, 106, 255), stop:1 rgba(93, 103, 113, 255));
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(62, 62, 62, 255));
border-bottom-color: rgb(58, 58, 58);
border-bottom-width: 1px;
}
QMenu::item {
color:rgb(223,219,210);
background-color:rgb(78,78,78);
padding-left:20px;
padding-top:4px;
padding-bottom:4px;
padding-right:10px;
}
QMenu{
background-color:rgb(78,78,78);
}
QTabWidget {
color:rgb(0,0,0);
background-color:rgb(247,246,246);
}
QTabWidget::pane {
border-color: rgb(77,77,77);
background-color:rgb(101,101,101);
border-style: solid;
border-width: 1px;
border-radius: 6px;
}
QTabBar::tab {
padding:2px;
color:rgb(250,250,250);
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(77, 77, 77, 255), stop:1 rgba(97, 97, 97, 255));
border-style: solid;
border-width: 2px;
border-top-right-radius:4px;
border-top-left-radius:4px;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:0.6, x2:0.5, y2:0.4, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(95, 92, 93, 255));
border-right-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(95, 92, 93, 255));
border-left-color: qlineargradient(spread:pad, x1:0.6, y1:0.5, x2:0.4, y2:0.5, stop:0 rgba(115, 115, 115, 255), stop:1 rgba(95, 92, 93, 255));
border-bottom-color: rgb(101,101,101);
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
background-color:rgb(101,101,101);
margin-left: 0px;
margin-right: 1px;
}
QTabBar::tab:!selected {
margin-top: 1px;
margin-right: 1px;
}
QCheckBox {
color:rgb(223,219,210);
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: rgb(87, 97, 106);
background-color:qlineargradient(spread:pad, x1:0.5, y1:0.7, x2:0.5, y2:0.3, stop:0 rgba(87, 97, 106, 150), stop:1 rgba(93, 103, 113, 150));
}
QCheckBox::indicator:checked {
border-radius:4px;
border-style:solid;
border-width:1px;
border-color: rgb(180,180,180);
background-color:qlineargradient(spread:pad, x1:0.5, y1:0.7, x2:0.5, y2:0.3, stop:0 rgba(87, 97, 106, 255), stop:1 rgba(93, 103, 113, 255));
}
QCheckBox::indicator:unchecked {
border-radius:4px;
border-style:solid;
border-width:1px;
border-color: rgb(87, 97, 106);
background-color:rgb(255,255,255);
}
QStatusBar {
color:rgb(240,240,240);
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Jaime Quiroga
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+434
View File
@@ -0,0 +1,434 @@
/*
* MacOS Style Sheet for QT Applications
* Author: Jaime A. Quiroga P.
* Company: GTRONICK
* Last updated: 25/12/2020, 23:10.
* Available at: https://github.com/GTRONICK/QSS/blob/master/MacOS.qss
*/
QMainWindow {
background-color:#ececec;
}
QPushButton, QToolButton, QCommandLinkButton{
padding: 0 5px 0 5px;
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-right-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-bottom-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-left-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-width: 2px;
border-radius: 8px;
color: #616161;
font-weight: bold;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #fbfdfd, stop:0.5 #ffffff, stop:1 #fbfdfd);
}
QPushButton::default, QToolButton::default, QCommandLinkButton::default{
border: 2px solid transparent;
color: #FFFFFF;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #84afe5, stop:1 #1168e4);
}
QPushButton:hover, QToolButton:hover, QCommandLinkButton:hover{
color: #3d3d3d;
}
QPushButton:pressed, QToolButton:pressed, QCommandLinkButton:pressed{
color: #aeaeae;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #ffffff, stop:0.5 #fbfdfd, stop:1 #ffffff);
}
QPushButton:disabled, QToolButton:disabled, QCommandLinkButton:disabled{
color: #616161;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #dce7eb, stop:0.5 #e0e8eb, stop:1 #dee7ec);
}
QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox, QTimeEdit, QDateEdit, QDateTimeEdit {
border-width: 2px;
border-radius: 8px;
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-right-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
background-color: #f4f4f4;
color: #3d3d3d;
}
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QTimeEdit:focus, QDateEdit:focus, QDateTimeEdit:focus {
border-width: 2px;
border-radius: 8px;
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 #85b7e3, stop:1 #9ec1db);
border-right-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #85b7e3, stop:1 #9ec1db);
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #85b7e3, stop:1 #9ec1db);
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #85b7e3, stop:1 #9ec1db);
background-color: #f4f4f4;
color: #3d3d3d;
}
QLineEdit:disabled, QTextEdit:disabled, QPlainTextEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QTimeEdit:disabled, QDateEdit:disabled, QDateTimeEdit:disabled {
color: #b9b9b9;
}
QSpinBox::up-button, QDoubleSpinBox::up-button, QTimeEdit::up-button, QDateEdit::up-button, QDateTimeEdit::up-button {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 15px;
color: #272727;
border-left-width: 1px;
border-left-color: darkgray;
border-left-style: solid;
border-top-right-radius: 3px;
padding: 3px;
}
QSpinBox::down-button, QDoubleSpinBox::down-button, QTimeEdit::down-button, QDateEdit::down-button, QDateTimeEdit::down-button {
subcontrol-origin: padding;
subcontrol-position: bottom right;
width: 15px;
color: #272727;
border-left-width: 1px;
border-left-color: darkgray;
border-left-style: solid;
border-bottom-right-radius: 3px;
padding: 3px;
}
QSpinBox::up-button:pressed, QDoubleSpinBox::up-button:pressed, QTimeEdit::up-button:pressed, QDateEdit::up-button:pressed, QDateTimeEdit::up-button:pressed {
color: #aeaeae;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #ffffff, stop:0.5 #fbfdfd, stop:1 #ffffff);
}
QSpinBox::down-button:pressed, QDoubleSpinBox::down-button:pressed, QTimeEdit::down-button:pressed, QDateEdit::down-button:pressed, QDateTimeEdit::down-button:pressed {
color: #aeaeae;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #ffffff, stop:0.5 #fbfdfd, stop:1 #ffffff);
}
QSpinBox::up-button:hover, QDoubleSpinBox::up-button:hover, QTimeEdit::up-button:hover, QDateEdit::up-button:hover, QDateTimeEdit::up-button:hover {
color: #FFFFFF;
border-top-right-radius: 5px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #84afe5, stop:1 #1168e4);
}
QSpinBox::down-button:hover, QDoubleSpinBox::down-button:hover, QTimeEdit::down-button:hover, QDateEdit::down-button:hover, QDateTimeEdit::down-button:hover {
color: #FFFFFF;
border-bottom-right-radius: 5px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #84afe5, stop:1 #1168e4);
}
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow, QTimeEdit::up-arrow, QDateEdit::up-arrow, QDateTimeEdit::up-arrow {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-up-symbolic.symbolic.png);
}
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow, QTimeEdit::down-arrow, QDateEdit::down-arrow, QDateTimeEdit::down-arrow {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-down-symbolic.symbolic.png);
}
QProgressBar {
max-height: 8px;
text-align: center;
font: italic bold 11px;
color: #3d3d3d;
border: 1px solid transparent;
border-radius:4px;
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #ddd5d5, stop:0.5 #dad3d3, stop:1 #ddd5d5);
}
QProgressBar::chunk {
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #467dd1, stop:0.5 #3b88fc, stop:1 #467dd1);
border-radius: 4px;
}
QProgressBar:disabled {
color: #616161;
}
QProgressBar::chunk:disabled {
background-color: #aeaeae;
}
QSlider::groove {
border: 1px solid #bbbbbb;
background-color: #52595d;
border-radius: 4px;
}
QSlider::groove:horizontal {
height: 6px;
}
QSlider::groove:vertical {
width: 6px;
}
QSlider::handle:horizontal {
background: #ffffff;
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
width: 12px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: #ffffff;
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
height: 12px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page, QSlider::sub-page {
border: 1px transparent;
background-color: #52595d;
border-radius: 4px;
}
QSlider::add-page:horizontal {
background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #ddd5d5, stop:0.5 #dad3d3, stop:1 #ddd5d5);
}
QSlider::sub-page:horizontal {
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #467dd1, stop:0.5 #3b88fc, stop:1 #467dd1);
}
QSlider::add-page:vertical {
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #467dd1, stop:0.5 #3b88fc, stop:1 #467dd1);
}
QSlider::sub-page:vertical {
background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #ddd5d5, stop:0.5 #dad3d3, stop:1 #ddd5d5);
}
QSlider::add-page:horizontal:disabled, QSlider::sub-page:horizontal:disabled, QSlider::add-page:vertical:disabled, QSlider::sub-page:vertical:disabled {
background: #b9b9b9;
}
QComboBox, QFontComboBox {
border-width: 2px;
border-radius: 8px;
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-right-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #c1c9cf, stop:1 #d2d8dd);
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 #c1c9cf, stop:1 #d2d8dd);
background-color: #f4f4f4;
color: #272727;
padding-left: 5px;
}
QComboBox:editable, QComboBox:!editable, QComboBox::drop-down:editable, QComboBox:!editable:on, QComboBox::drop-down:editable:on {
background: #ffffff;
}
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;
width: 15px;
color: #272727;
border-left-width: 1px;
border-left-color: darkgray;
border-left-style: solid;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
QComboBox::down-arrow {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-down-symbolic.symbolic.png); /*Adawaita icon thene*/
}
QComboBox::down-arrow:on {
top: 1px;
left: 1px;
}
QComboBox QAbstractItemView {
border: 1px solid darkgray;
border-radius: 8px;
selection-background-color: #dadada;
selection-color: #272727;
color: #272727;
background: white;
}
QLabel, QCheckBox, QRadioButton {
color: #272727;
}
QCheckBox {
padding: 2px;
}
QCheckBox:disabled, QRadioButton:disabled {
color: #808086;
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: transparent;
}
QCheckBox::indicator:checked {
image: url(/usr/share/icons/Adwaita/16x16/actions/object-select-symbolic.symbolic.png);
height: 15px;
width: 15px;
border-style:solid;
border-width: 1px;
border-color: #48a5fd;
color: #ffffff;
border-radius: 3px;
background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #48a5fd, stop:0.5 #329cfb, stop:1 #48a5fd);
}
QCheckBox::indicator:unchecked {
height: 15px;
width: 15px;
border-style:solid;
border-width: 1px;
border-color: #48a5fd;
border-radius: 3px;
background-color: #fbfdfa;
}
QLCDNumber {
color: #616161;;
}
QMenuBar {
background-color: #ececec;
}
QMenuBar::item {
color: #616161;
spacing: 3px;
padding: 1px 4px;
background-color: #ececec;
}
QMenuBar::item:selected {
background-color: #dadada;
color: #3d3d3d;
}
QMenu {
background-color: #ececec;
}
QMenu::item:selected {
background-color: #dadada;
color: #3d3d3d;
}
QMenu::item {
color: #616161;;
background-color: #e0e0e0;
}
QTabWidget {
color:rgb(0,0,0);
background-color:#000000;
}
QTabWidget::pane {
border-color: #050a0e;
background-color: #e0e0e0;
border-width: 1px;
border-radius: 4px;
position: absolute;
top: -0.5em;
padding-top: 0.5em;
}
QTabWidget::tab-bar {
alignment: center;
}
QTabBar::tab {
border-bottom: 1px solid #c0c0c0;
padding: 3px;
color: #272727;
background-color: #fefefc;
margin-left:0px;
}
QTabBar::tab:!last {
border-right: 1px solid;
border-right-color: #c0c0c0;
border-bottom-color: #c0c0c0;
}
QTabBar::tab:first {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
QTabBar::tab:last {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
color: #FFFFFF;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #84afe5, stop:1 #1168e4);
}
QRadioButton::indicator {
height: 14px;
width: 14px;
border-style:solid;
border-radius:7px;
border-width: 1px;
}
QRadioButton::indicator:checked {
border-color: #48a5fd;
background-color: qradialgradient(cx:0.5, cy:0.5, radius:0.4,fx:0.5, fy:0.5, stop:0 #ffffff, stop:0.5 #ffffff, stop:0.6 #48a5fd, stop:1 #48a5fd);
}
QRadioButton::indicator:!checked {
border-color: #a9b7c6;
background-color: #fbfdfa;
}
QStatusBar {
color:#027f7f;
}
QDial {
background: #16a085;
}
QToolBox {
color: #a9b7c6;
background-color: #222b2e;
}
QToolBox::tab {
color: #a9b7c6;
background-color:#222b2e;
}
QToolBox::tab:selected {
color: #FFFFFF;
background-color:#222b2e;
}
QScrollArea {
color: #FFFFFF;
background-color:#222b2e;
}
QScrollBar:horizontal {
max-height: 10px;
border: 1px transparent grey;
margin: 0px 20px 0px 20px;
background: transparent;
}
QScrollBar:vertical {
max-width: 10px;
border: 1px transparent grey;
margin: 20px 0px 20px 0px;
background: transparent;
}
QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
background: #52595d;
border-style: transparent;
border-radius: 4px;
min-height: 25px;
}
QScrollBar::handle:horizontal:hover, QScrollBar::handle:vertical:hover {
background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #467dd1, stop:0.5 #3b88fc, stop:1 #467dd1);
}
QScrollBar::add-line, QScrollBar::sub-line {
border: 2px transparent grey;
border-radius: 4px;
subcontrol-origin: margin;
background: #b9b9b9;
}
QScrollBar::add-line:horizontal {
width: 20px;
subcontrol-position: right;
}
QScrollBar::add-line:vertical {
height: 20px;
subcontrol-position: bottom;
}
QScrollBar::sub-line:horizontal {
width: 20px;
subcontrol-position: left;
}
QScrollBar::sub-line:vertical {
height: 20px;
subcontrol-position: top;
}
QScrollBar::add-line:vertical:pressed, QScrollBar::add-line:horizontal:pressed, QScrollBar::sub-line:horizontal:pressed, QScrollBar::sub-line:vertical:pressed {
background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 #467dd1, stop:0.5 #3b88fc, stop:1 #467dd1);
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal, QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
QScrollBar::up-arrow:vertical {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-up-symbolic.symbolic.png);
}
QScrollBar::down-arrow:vertical {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-down-symbolic.symbolic.png);
}
QScrollBar::left-arrow:horizontal {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-previous-symbolic.symbolic.png);
}
QScrollBar::right-arrow:horizontal {
image: url(/usr/share/icons/Adwaita/16x16/actions/go-next-symbolic.symbolic.png);
}
+531
View File
@@ -0,0 +1,531 @@
/*
ManjaroMix Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 25/02/2020, 15:42.
Available at: https://github.com/GTRONICK/QSS/blob/master/ManjaroMix.qss
*/
QMainWindow {
background-color:#151a1e;
}
QCalendar {
background-color: #151a1e;
}
QTextEdit {
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
background-color: #222b2e;
color: #d3dae3;
}
QPlainTextEdit {
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
background-color: #222b2e;
color: #d3dae3;
}
QToolButton {
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: #d3dae3;
padding: 2px;
background-color: rgb(255,255,255);
}
QToolButton:hover{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(197, 197, 197), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(197, 197, 197));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(195, 195, 195), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(255,255,255);
}
QToolButton:pressed{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: rgb(0,0,0);
padding: 2px;
background-color: rgb(142,142,142);
}
QPushButton{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #d3dae3;
padding: 2px;
background-color: #151a1e;
}
QPushButton::default{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #FFFFFF;
padding: 2px;
background-color: #151a1e;;
}
QPushButton:hover{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #d3dae3;
padding: 2px;
background-color: #1c1f1f;
}
QPushButton:pressed{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #d3dae3;
padding: 2px;
background-color: #2c2f2f;
}
QPushButton:disabled{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-right-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(217, 217, 217), stop:1 rgb(227, 227, 227));
border-left-color: qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgb(227, 227, 227), stop:1 rgb(217, 217, 217));
border-bottom-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgb(215, 215, 215), stop:1 rgb(222, 222, 222));
border-width: 1px;
border-radius: 5px;
color: #808086;
padding: 2px;
background-color: rgb(142,142,142);
}
QLineEdit {
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
background-color: #222b2e;
color: #d3dae3;
}
QLabel {
color: #d3dae3;
}
QLCDNumber {
color: #4d9b87;
}
QProgressBar {
text-align: center;
color: #d3dae3;
border-radius: 10px;
border-color: transparent;
border-style: solid;
background-color: #52595d;
}
QProgressBar::chunk {
background-color: #214037 ;
border-radius: 10px;
}
QMenuBar {
background-color: #151a1e;
}
QMenuBar::item {
color: #d3dae3;
spacing: 3px;
padding: 1px 4px;
background-color: #151a1e;
}
QMenuBar::item:selected {
background-color: #252a2e;
color: #FFFFFF;
}
QMenu {
background-color: #151a1e;
}
QMenu::item:selected {
background-color: #252a2e;
color: #FFFFFF;
}
QMenu::item {
color: #d3dae3;
background-color: #151a1e;
}
QTabWidget {
color:rgb(0,0,0);
background-color:#000000;
}
QTabWidget::pane {
border-color: #050a0e;
background-color: #1e282c;
border-style: solid;
border-width: 1px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
QTabBar::tab:first {
border-style: solid;
border-left-width:1px;
border-right-width:0px;
border-top-width:1px;
border-bottom-width:0px;
border-top-color: #050a0e;
border-left-color: #050a0e;
border-bottom-color: #050a0e;
border-top-left-radius: 4px;
color: #d3dae3;
padding: 3px;
margin-left:0px;
background-color: #151a1e;
}
QTabBar::tab:last {
border-style: solid;
border-top-width:1px;
border-left-width:1px;
border-right-width:1px;
border-bottom-width:0px;
border-color: #050a0e;
border-top-right-radius: 4px;
color: #d3dae3;
padding: 3px;
margin-left:0px;
background-color: #151a1e;
}
QTabBar::tab {
border-style: solid;
border-top-width:1px;
border-bottom-width:0px;
border-left-width:1px;
border-top-color: #050a0e;
border-left-color: #050a0e;
border-bottom-color: #050a0e;
color: #d3dae3;
padding: 3px;
margin-left:0px;
background-color: #151a1e;
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
border-style: solid;
border-left-width:1px;
border-bottom-width:0px;
border-right-color: transparent;
border-top-color: #050a0e;
border-left-color: #050a0e;
border-bottom-color: #050a0e;
color: #FFFFFF;
padding: 3px;
margin-left:0px;
background-color: #1e282c;
}
QTabBar::tab:selected, QTabBar::tab:first:selected, QTabBar::tab:hover {
border-style: solid;
border-left-width:1px;
border-bottom-width:0px;
border-top-width:1px;
border-right-color: transparent;
border-top-color: #050a0e;
border-left-color: #050a0e;
border-bottom-color: #050a0e;
color: #FFFFFF;
padding: 3px;
margin-left:0px;
background-color: #1e282c;
}
QCheckBox {
color: #d3dae3;
padding: 2px;
}
QCheckBox:disabled {
color: #808086;
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: transparent;
}
QCheckBox::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #4fa08b;
color: #000000;
background-color: qradialgradient(cx:0.4, cy:0.4, radius: 1.5,fx:0, fy:0, stop:0 #1e282c, stop:0.3 #1e282c, stop:0.4 #4fa08b, stop:0.5 #1e282c, stop:1 #1e282c);
}
QCheckBox::indicator:unchecked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #4fa08b;
color: #000000;
}
QRadioButton {
color: #d3dae3;
padding: 1px;
}
QRadioButton::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #4fa08b;
color: #a9b7c6;
background-color: qradialgradient(cx:0.5, cy:0.5, radius:0.4,fx:0.5, fy:0.5, stop:0 #4fa08b, stop:1 #1e282c);
}
QRadioButton::indicator:!checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #4fa08b;
color: #a9b7c6;
background-color: transparent;
}
QStatusBar {
color:#027f7f;
}
QSpinBox {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QDoubleSpinBox {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QTimeEdit {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QDateTimeEdit {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QDateEdit {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QFontComboBox {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QComboBox {
color: #d3dae3;
background-color: #222b2e;
border-width: 1px;
border-style: solid;
border-color: #4fa08b;
}
QDial {
background: #16a085;
}
QToolBox {
color: #a9b7c6;
background-color: #222b2e;
}
QToolBox::tab {
color: #a9b7c6;
background-color:#222b2e;
}
QToolBox::tab:selected {
color: #FFFFFF;
background-color:#222b2e;
}
QScrollArea {
color: #FFFFFF;
background-color:#222b2e;
}
QSlider::groove:horizontal {
height: 5px;
background-color: #52595d;
}
QSlider::groove:vertical {
width: 5px;
background-color: #52595d;
}
QSlider::handle:horizontal {
background: #1a2224;
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
width: 12px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: #1a2224;
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
height: 12px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page:horizontal {
background: #52595d;
}
QSlider::add-page:vertical {
background: #52595d;
}
QSlider::sub-page:horizontal {
background-color: #15433a;
}
QSlider::sub-page:vertical {
background-color: #15433a;
}
QScrollBar:horizontal {
max-height: 10px;
border: 1px transparent grey;
margin: 0px 20px 0px 20px;
background: transparent;
}
QScrollBar:vertical {
max-width: 10px;
border: 1px transparent grey;
margin: 20px 0px 20px 0px;
background: transparent;
}
QScrollBar::handle:horizontal {
background: #52595d;
border-style: transparent;
border-radius: 4px;
min-width: 25px;
}
QScrollBar::handle:horizontal:hover {
background: #58a492;
border-style: transparent;
border-radius: 4px;
min-width: 25px;
}
QScrollBar::handle:vertical {
background: #52595d;
border-style: transparent;
border-radius: 4px;
min-height: 25px;
}
QScrollBar::handle:vertical:hover {
background: #58a492;
border-style: transparent;
border-radius: 4px;
min-height: 25px;
}
QScrollBar::add-line:horizontal {
border: 2px transparent grey;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
background: #15433a;
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:pressed {
border: 2px transparent grey;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
background: rgb(181,181,181);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical {
border: 2px transparent grey;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background: #15433a;
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:pressed {
border: 2px transparent grey;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
background: rgb(181,181,181);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal {
border: 2px transparent grey;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
background: #15433a;
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:pressed {
border: 2px transparent grey;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
background: rgb(181,181,181);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical {
border: 2px transparent grey;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background: #15433a;
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:pressed {
border: 2px transparent grey;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background: rgb(181,181,181);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
+390
View File
@@ -0,0 +1,390 @@
/*
Material Dark Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Inspired on https://github.com/jxfwinter/qt-material-stylesheet
Company: GTRONICK
Last updated: 04/12/2018, 15:00.
Available at: https://github.com/GTRONICK/QSS/blob/master/MaterialDark.qss
*/
QMainWindow {
background-color:#1e1d23;
}
QDialog {
background-color:#1e1d23;
}
QColorDialog {
background-color:#1e1d23;
}
QTextEdit {
background-color:#1e1d23;
color: #a9b7c6;
}
QPlainTextEdit {
selection-background-color:#007b50;
background-color:#1e1d23;
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-width: 1px;
color: #a9b7c6;
}
QPushButton{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-width: 1px;
border-style: solid;
color: #a9b7c6;
padding: 2px;
background-color: #1e1d23;
}
QPushButton::default{
border-style: inset;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #04b97f;
border-width: 1px;
color: #a9b7c6;
padding: 2px;
background-color: #1e1d23;
}
QToolButton {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #04b97f;
border-bottom-width: 1px;
border-style: solid;
color: #a9b7c6;
padding: 2px;
background-color: #1e1d23;
}
QToolButton:hover{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #37efba;
border-bottom-width: 2px;
border-style: solid;
color: #FFFFFF;
padding-bottom: 1px;
background-color: #1e1d23;
}
QPushButton:hover{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #37efba;
border-bottom-width: 1px;
border-style: solid;
color: #FFFFFF;
padding-bottom: 2px;
background-color: #1e1d23;
}
QPushButton:pressed{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #37efba;
border-bottom-width: 2px;
border-style: solid;
color: #37efba;
padding-bottom: 1px;
background-color: #1e1d23;
}
QPushButton:disabled{
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #808086;
border-bottom-width: 2px;
border-style: solid;
color: #808086;
padding-bottom: 1px;
background-color: #1e1d23;
}
QLineEdit {
border-width: 1px; border-radius: 4px;
border-color: rgb(58, 58, 58);
border-style: inset;
padding: 0 8px;
color: #a9b7c6;
background:#1e1d23;
selection-background-color:#007b50;
selection-color: #FFFFFF;
}
QLabel {
color: #a9b7c6;
}
QLCDNumber {
color: #37e6b4;
}
QProgressBar {
text-align: center;
color: rgb(240, 240, 240);
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
background-color:#1e1d23;
}
QProgressBar::chunk {
background-color: #04b97f;
border-radius: 5px;
}
QMenuBar {
background-color: #1e1d23;
}
QMenuBar::item {
color: #a9b7c6;
spacing: 3px;
padding: 1px 4px;
background: #1e1d23;
}
QMenuBar::item:selected {
background:#1e1d23;
color: #FFFFFF;
}
QMenu::item:selected {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: #04b97f;
border-bottom-color: transparent;
border-left-width: 2px;
color: #FFFFFF;
padding-left:15px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
background-color: #1e1d23;
}
QMenu::item {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 1px;
border-style: solid;
color: #a9b7c6;
padding-left:17px;
padding-top:4px;
padding-bottom:4px;
padding-right:7px;
background-color: #1e1d23;
}
QMenu{
background-color:#1e1d23;
}
QTabWidget {
color:rgb(0,0,0);
background-color:#1e1d23;
}
QTabWidget::pane {
border-color: rgb(77,77,77);
background-color:#1e1d23;
border-style: solid;
border-width: 1px;
border-radius: 6px;
}
QTabBar::tab {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 1px;
border-style: solid;
color: #808086;
padding: 3px;
margin-left:3px;
background-color: #1e1d23;
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
border-style: solid;
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
border-bottom-color: #04b97f;
border-bottom-width: 2px;
border-style: solid;
color: #FFFFFF;
padding-left: 3px;
padding-bottom: 2px;
margin-left:3px;
background-color: #1e1d23;
}
QCheckBox {
color: #a9b7c6;
padding: 2px;
}
QCheckBox:disabled {
color: #808086;
padding: 2px;
}
QCheckBox:hover {
border-radius:4px;
border-style:solid;
padding-left: 1px;
padding-right: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-width:1px;
border-color: rgb(87, 97, 106);
background-color:#1e1d23;
}
QCheckBox::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #04b97f;
color: #a9b7c6;
background-color: #04b97f;
}
QCheckBox::indicator:unchecked {
height: 10px;
width: 10px;
border-style:solid;
border-width: 1px;
border-color: #04b97f;
color: #a9b7c6;
background-color: transparent;
}
QRadioButton {
color: #a9b7c6;
background-color: #1e1d23;
padding: 1px;
}
QRadioButton::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #04b97f;
color: #a9b7c6;
background-color: #04b97f;
}
QRadioButton::indicator:!checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: #04b97f;
color: #a9b7c6;
background-color: transparent;
}
QStatusBar {
color:#027f7f;
}
QSpinBox {
color: #a9b7c6;
background-color: #1e1d23;
}
QDoubleSpinBox {
color: #a9b7c6;
background-color: #1e1d23;
}
QTimeEdit {
color: #a9b7c6;
background-color: #1e1d23;
}
QDateTimeEdit {
color: #a9b7c6;
background-color: #1e1d23;
}
QDateEdit {
color: #a9b7c6;
background-color: #1e1d23;
}
QComboBox {
color: #a9b7c6;
background: #1e1d23;
}
QComboBox:editable {
background: #1e1d23;
color: #a9b7c6;
selection-background-color: #1e1d23;
}
QComboBox QAbstractItemView {
color: #a9b7c6;
background: #1e1d23;
selection-color: #FFFFFF;
selection-background-color: #1e1d23;
}
QComboBox:!editable:on, QComboBox::drop-down:editable:on {
color: #a9b7c6;
background: #1e1d23;
}
QFontComboBox {
color: #a9b7c6;
background-color: #1e1d23;
}
QToolBox {
color: #a9b7c6;
background-color: #1e1d23;
}
QToolBox::tab {
color: #a9b7c6;
background-color: #1e1d23;
}
QToolBox::tab:selected {
color: #FFFFFF;
background-color: #1e1d23;
}
QScrollArea {
color: #FFFFFF;
background-color: #1e1d23;
}
QSlider::groove:horizontal {
height: 5px;
background: #04b97f;
}
QSlider::groove:vertical {
width: 5px;
background: #04b97f;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);
border: 1px solid #5c5c5c;
width: 14px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: qlineargradient(x1:1, y1:1, x2:0, y2:0, stop:0 #b4b4b4, stop:1 #8f8f8f);
border: 1px solid #5c5c5c;
height: 14px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page:horizontal {
background: white;
}
QSlider::add-page:vertical {
background: white;
}
QSlider::sub-page:horizontal {
background: #04b97f;
}
QSlider::sub-page:vertical {
background: #04b97f;
}
+47
View File
@@ -0,0 +1,47 @@
/*
Neon Style Sheet for QT Applications (QpushButton)
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 24/10/2020, 15:42.
Available at: https://github.com/GTRONICK/QSS/blob/master/NeonButtons.qss
*/
QPushButton{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #d3dae3;
padding: 2px;
background-color: #100E19;
}
QPushButton::default{
border-style: solid;
border-color: #050a0e;
border-width: 1px;
border-radius: 5px;
color: #FFFFFF;
padding: 2px;
background-color: #151a1e;
}
QPushButton:hover{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:1, stop:0 #C0DB50, stop:0.4 #C0DB50, stop:0.5 #100E19, stop:1 #100E19);
border-bottom-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:1, stop:0 #100E19, stop:0.5 #100E19, stop:0.6 #C0DB50, stop:1 #C0DB50);
border-left-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #C0DB50, stop:0.3 #C0DB50, stop:0.7 #100E19, stop:1 #100E19);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #C0DB50, stop:0.3 #C0DB50, stop:0.7 #100E19, stop:1 #100E19);
border-width: 2px;
border-radius: 1px;
color: #d3dae3;
padding: 2px;
}
QPushButton:pressed{
border-style: solid;
border-top-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:1, stop:0 #d33af1, stop:0.4 #d33af1, stop:0.5 #100E19, stop:1 #100E19);
border-bottom-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:1, stop:0 #100E19, stop:0.5 #100E19, stop:0.6 #d33af1, stop:1 #d33af1);
border-left-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #d33af1, stop:0.3 #d33af1, stop:0.7 #100E19, stop:1 #100E19);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #d33af1, stop:0.3 #d33af1, stop:0.7 #100E19, stop:1 #100E19);
border-width: 2px;
border-radius: 1px;
color: #d3dae3;
padding: 2px;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

+45
View File
@@ -0,0 +1,45 @@
### QT StyleSheet templates ###
Themes available:
1. [Ubuntu](https://github.com/GTRONICK/QSS/blob/master/Ubuntu.qss)
![Ubuntu theme screenshot](https://i.imgur.com/i8zVYwL.png)
2. [ElegantDark](https://github.com/GTRONICK/QSS/blob/master/ElegantDark.qss)
![ElegantDark theme screenshot](https://i.imgur.com/AUb7R7P.png)
3. [MaterialDark](https://github.com/GTRONICK/QSS/blob/master/MaterialDark.qss)
![MaterialDark theme screenshot](https://i.imgur.com/ViEQxdh.png)
4. [ConsoleStyle](https://github.com/GTRONICK/QSS/blob/master/ConsoleStyle.qss)
![ConsoleStyle theme screenshot](https://i.imgur.com/E10ukaA.png)
5. [AMOLED](https://github.com/GTRONICK/QSS/blob/master/AMOLED.qss)
![AMOLED theme screenshot](https://i.imgur.com/M7RIx4c.png)
6. [Aqua](https://github.com/GTRONICK/QSS/blob/master/Aqua.qss)
![Aqua theme screenshot](https://i.imgur.com/i8zVYwL.png)
## The ManjaroMix Theme!: Includes a radial gradient for Checkboxes, and minimalist arrows for scrollbars. ##
7. [ManjaroMix](https://github.com/GTRONICK/QSS/blob/master/ManjaroMix.qss)
![ManjaroMix theme screenshot](https://i.imgur.com/7zrMDMH.png)
8. [NeonButtons](https://github.com/GTRONICK/QSS/blob/master/NeonButtons.qss)
![NeonButtons screenshot](https://i.imgur.com/IqTSQG2.png)
![NeonButtons screenshot](https://i.imgur.com/l4im5Ve.png)
## MacOS Theme!: Reduced code, image integration through URL resources. ##
9. [MacOS](https://github.com/GTRONICK/QSS/blob/master/MacOS.qss)
![MacOS](https://i.imgur.com/quEgiVe.png)
**Added images in QSS_IMG folder**
Stay tunned!, this files are being updated frequently.
*Consider donating :)* **PayPal Account:** gtronick@gmail.com
+496
View File
@@ -0,0 +1,496 @@
/*
Ubuntu Style Sheet for QT Applications
Author: Jaime A. Quiroga P.
Company: GTRONICK
Last updated: 01/10/2021 (dd/mm/yyyy), 15:18.
Available at: https://github.com/GTRONICK/QSS/blob/master/Ubuntu.qss
*/
QMainWindow {
background-color:#f0f0f0;
}
QCheckBox {
padding:2px;
}
QCheckBox:hover {
border:1px solid rgb(255,150,60);
border-radius:4px;
padding: 1px;
background-color:qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(190, 90, 50, 50), stop:1 rgba(250, 130, 40, 50));
}
QCheckBox::indicator:checked {
border:1px solid rgb(246, 134, 86);
border-radius:4px;
background-color:rgb(246, 134, 86)
}
QCheckBox::indicator:unchecked {
border-width:1px solid rgb(246, 134, 86);
border-radius:4px;
background-color:rgb(255,255,255);
}
QColorDialog {
background-color:#f0f0f0;
}
QComboBox {
color:rgb(81,72,65);
background: #ffffff;
}
QComboBox:editable {
selection-color:rgb(81,72,65);
selection-background-color: #ffffff;
}
QComboBox QAbstractItemView {
selection-color: #ffffff;
selection-background-color: rgb(246, 134, 86);
}
QComboBox:!editable:on, QComboBox::drop-down:editable:on {
color: #1e1d23;
}
QDateTimeEdit, QDateEdit, QDoubleSpinBox, QFontComboBox {
color:rgb(81,72,65);
background-color: #ffffff;
}
QDialog {
background-color:#f0f0f0;
}
QLabel,QLineEdit {
color:rgb(17,17,17);
}
QLineEdit {
background-color:rgb(255,255,255);
selection-background-color:rgb(236,116,64);
}
QMenuBar {
color:rgb(223,219,210);
background-color:rgb(65,64,59);
}
QMenuBar::item {
padding-top:4px;
padding-left:4px;
padding-right:4px;
color:rgb(223,219,210);
background-color:rgb(65,64,59);
}
QMenuBar::item:selected {
color:rgb(255,255,255);
padding-top:2px;
padding-left:2px;
padding-right:2px;
border-top-width:2px;
border-left-width:2px;
border-right-width:2px;
border-top-right-radius:4px;
border-top-left-radius:4px;
border-style:solid;
background-color:rgb(65,64,59);
border-top-color: rgb(47,47,44);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(90, 87, 78, 255), stop:1 rgba(47,47,44, 255));
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(90, 87, 78, 255), stop:1 rgba(47,47,44, 255));
}
QMenu {
color:rgb(223,219,210);
background-color:rgb(65,64,59);
}
QMenu::item {
color:rgb(223,219,210);
padding:4px 10px 4px 20px;
}
QMenu::item:selected {
color:rgb(255,255,255);
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(225, 108, 54, 255), stop:1 rgba(246, 134, 86, 255));
border-style:solid;
border-width:3px;
padding:4px 7px 4px 17px;
border-bottom-color:qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(175,85,48,255), stop:1 rgba(236,114,67, 255));
border-top-color:qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
border-right-color:qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
border-left-color:qlineargradient(spread:pad, x1:1, y1:0.5, x2:0, y2:0.5, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
}
QPlainTextEdit {
border: 1px solid transparent;
color:rgb(17,17,17);
selection-background-color:rgb(236,116,64);
background-color: #FFFFFF;
}
QProgressBar {
text-align: center;
color: rgb(0, 0, 0);
border: 1px inset rgb(150,150,150);
border-radius: 10px;
background-color:rgb(221,221,219);
}
QProgressBar::chunk:horizontal {
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(225, 108, 54, 255), stop:1 rgba(246, 134, 86, 255));
border:1px solid;
border-radius:8px;
border-bottom-color:qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(175,85,48,255), stop:1 rgba(236,114,67, 255));
border-top-color:qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
border-right-color:qlineargradient(spread:pad, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
border-left-color:qlineargradient(spread:pad, x1:1, y1:0.5, x2:0, y2:0.5, stop:0 rgba(253,156,113,255), stop:1 rgba(205,90,46, 255));
}
QPushButton{
color:rgb(17,17,17);
border-width: 1px;
border-radius: 6px;
border-bottom-color: rgb(150,150,150);
border-right-color: rgb(165,165,165);
border-left-color: rgb(165,165,165);
border-top-color: rgb(180,180,180);
border-style: solid;
padding: 4px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));
}
QPushButton:hover{
color:rgb(17,17,17);
border-width: 1px;
border-radius:6px;
border-top-color: rgb(255,150,60);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));
border-bottom-color: rgb(200,70,20);
border-style: solid;
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));
}
QPushButton:default{
color:rgb(17,17,17);
border-width: 1px;
border-radius:6px;
border-top-color: rgb(255,150,60);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));
border-bottom-color: rgb(200,70,20);
border-style: solid;
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));
}
QPushButton:pressed{
color:rgb(17,17,17);
border-width: 1px;
border-radius: 6px;
border-width: 1px;
border-top-color: rgba(255,150,60,200);
border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 200));
border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 200));
border-bottom-color: rgba(200,70,20,200);
border-style: solid;
padding: 2px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));
}
QPushButton:disabled{
color:rgb(174,167,159);
border-width: 1px;
border-radius: 6px;
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(200, 200, 200, 255), stop:1 rgba(230, 230, 230, 255));
}
QRadioButton {
padding: 1px;
}
QRadioButton::indicator:checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: rgba(246, 134, 86, 255);
color: #a9b7c6;
background-color:rgba(246, 134, 86, 255);
}
QRadioButton::indicator:!checked {
height: 10px;
width: 10px;
border-style:solid;
border-radius:5px;
border-width: 1px;
border-color: rgb(246, 134, 86);
color: #a9b7c6;
background-color: transparent;
}
QScrollArea {
color: white;
background-color:#f0f0f0;
}
QSlider::groove {
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
}
QSlider::groove:horizontal {
height: 5px;
background: rgb(246, 134, 86);
}
QSlider::groove:vertical {
width: 5px;
background: rgb(246, 134, 86);
}
QSlider::handle:horizontal {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
width: 12px;
margin: -5px 0;
border-radius: 7px;
}
QSlider::handle:vertical {
background: rgb(253,253,253);
border-style: solid;
border-width: 1px;
border-color: rgb(207,207,207);
height: 12px;
margin: 0 -5px;
border-radius: 7px;
}
QSlider::add-page:horizontal, QSlider::add-page:vertical {
background: white;
}
QSlider::sub-page:horizontal, QSlider::sub-page:vertical {
background: rgb(246, 134, 86);
}
QStatusBar, QSpinBox {
color:rgb(81,72,65);
}
QSpinBox {
background-color: #ffffff;
}
QScrollBar:horizontal {
max-height: 20px;
border: 1px transparent;
margin: 0px 20px 0px 20px;
}
QScrollBar::handle:horizontal {
background: rgb(253,253,253);
border: 1px solid rgb(207,207,207);
border-radius: 7px;
min-width: 25px;
}
QScrollBar::handle:horizontal:hover {
background: rgb(253,253,253);
border: 1px solid rgb(255,150,60);
border-radius: 7px;
min-width: 25px;
}
QScrollBar::add-line:horizontal {
border: 1px solid rgb(207,207,207);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-right-radius: 7px;
background: rgb(255, 255, 255);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:hover {
border: 1px solid rgb(255,150,60);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-right-radius: 7px;
background: rgb(255, 255, 255);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::add-line:horizontal:pressed {
border: 1px solid grey;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
border-bottom-right-radius: 7px;
background: rgb(231,231,231);
width: 20px;
subcontrol-position: right;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal {
border: 1px solid rgb(207,207,207);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(255, 255, 255);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:hover {
border: 1px solid rgb(255,150,60);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(255, 255, 255);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::sub-line:horizontal:pressed {
border: 1px solid grey;
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(231,231,231);
width: 20px;
subcontrol-position: left;
subcontrol-origin: margin;
}
QScrollBar::left-arrow:horizontal {
border: 1px transparent grey;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
width: 6px;
height: 6px;
background: rgb(230,230,230);
}
QScrollBar::right-arrow:horizontal {
border: 1px transparent grey;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
width: 6px;
height: 6px;
background: rgb(230,230,230);
}
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
background: none;
}
QScrollBar:vertical {
max-width: 20px;
border: 1px transparent grey;
margin: 20px 0px 20px 0px;
}
QScrollBar::add-line:vertical {
border: 1px solid;
border-color: rgb(207,207,207);
border-bottom-right-radius: 7px;
border-bottom-left-radius: 7px;
border-top-left-radius: 7px;
background: rgb(255, 255, 255);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:hover {
border: 1px solid;
border-color: rgb(255,150,60);
border-bottom-right-radius: 7px;
border-bottom-left-radius: 7px;
border-top-left-radius: 7px;
background: rgb(255, 255, 255);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::add-line:vertical:pressed {
border: 1px solid grey;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
border-top-left-radius: 7px;
background: rgb(231,231,231);
height: 20px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical {
border: 1px solid rgb(207,207,207);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(255, 255, 255);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:hover {
border: 1px solid rgb(255,150,60);
border-top-right-radius: 7px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
background: rgb(255, 255, 255);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical:pressed {
border: 1px solid grey;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
background: rgb(231,231,231);
height: 20px;
subcontrol-position: top;
subcontrol-origin: margin;
}
QScrollBar::handle:vertical {
background: rgb(253,253,253);
border: 1px solid rgb(207,207,207);
border-radius: 7px;
min-height: 25px;
}
QScrollBar::handle:vertical:hover {
background: rgb(253,253,253);
border: 1px solid rgb(255,150,60);
border-radius: 7px;
min-height: 25px;
}
QScrollBar::up-arrow:vertical {
border: 1px transparent grey;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
width: 6px;
height: 6px;
background: rgb(230,230,230);
}
QScrollBar::down-arrow:vertical {
border: 1px transparent grey;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
width: 6px;
height: 6px;
background: rgb(230,230,230);
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
QTabWidget {
color:rgb(0,0,0);
background-color:rgb(247,246,246);
}
QTabWidget::pane {
border-color: rgb(180,180,180);
background-color:rgb(247,246,246);
border-style: solid;
border-width: 1px;
border-radius: 6px;
}
QTabBar::tab {
padding-left:4px;
padding-right:4px;
padding-bottom:2px;
padding-top:2px;
color:rgb(81,72,65);
background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(221,218,217,255), stop:1 rgba(240,239,238,255));
border-style: solid;
border-width: 1px;
border-top-right-radius:4px;
border-top-left-radius:4px;
border-top-color: rgb(180,180,180);
border-left-color: rgb(180,180,180);
border-right-color: rgb(180,180,180);
border-bottom-color: transparent;
}
QTabBar::tab:selected, QTabBar::tab:last:selected, QTabBar::tab:hover {
background-color:rgb(247,246,246);
margin-left: 0px;
margin-right: 1px;
}
QTabBar::tab:!selected {
margin-top: 1px;
margin-right: 1px;
}
QTextEdit {
border-width: 1px;
border-style: solid;
border-color:transparent;
color:rgb(17,17,17);
selection-background-color:rgb(236,116,64);
}
QTimeEdit, QToolBox, QToolBox::tab, QToolBox::tab:selected {
color:rgb(81,72,65);
background-color: #ffffff;
}
+11
View File
@@ -0,0 +1,11 @@
#include "ugeneral.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
uGeneral w;
w.show();
return a.exec();
}
+95649
View File
File diff suppressed because it is too large Load Diff
+639
View File
@@ -0,0 +1,639 @@
// neuralnetworkmanager.cpp
#include "neuralnetworkmanager.h"
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QUrl>
#include <QDebug>
#include <QDateTime>
#include <QTimer>
#include <QSslCipher>
#include <QSslSocket>
#include <QFile>
#include <QElapsedTimer>
NeuralNetworkManager::NeuralNetworkManager(QObject *parent)
: QObject(parent)
, networkManager(new QNetworkAccessManager(this))
{
// Инициализируем SSL конфигурацию
setupSSL();
// Инициализируем конфигурации сетей
setupDefaultConfigs();
// Инициализируем GigaChat токен как невалидный
gigaChatAuth.tokenExpiry = 0;
// Подключаем обработчик сетевых ответов
connect(networkManager, &QNetworkAccessManager::finished,
this, &NeuralNetworkManager::handleNetworkReply);
}
NeuralNetworkManager::~NeuralNetworkManager()
{
// Очищаем очередь сообщений
pendingMessages.clear();
}
void NeuralNetworkManager::setupSSL()
{
// Настройка стандартной SSL конфигурации
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
// Устанавливаем протокол TLS 1.2 или новее
sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
// Загружаем российские корневые сертификаты если доступны
QStringList certPaths = {
"/etc/ssl/certs/russian_trusted_root_ca.pem",
":/certs/russian_ca.pem",
"./certs/russian_ca.pem"
};
QList<QSslCertificate> russianCerts;
for (const QString &path : certPaths) {
if (QFile::exists(path)) {
QFile certFile(path);
if (certFile.open(QIODevice::ReadOnly)) {
QByteArray certData = certFile.readAll();
russianCerts += QSslCertificate::fromData(certData);
certFile.close();
}
}
}
if (!russianCerts.isEmpty()) {
QList<QSslCertificate> defaultCerts = sslConfig.caCertificates();
defaultCerts += russianCerts;
sslConfig.setCaCertificates(defaultCerts);
}
// Устанавливаем как конфигурацию по умолчанию
QSslConfiguration::setDefaultConfiguration(sslConfig);
}
void NeuralNetworkManager::setupSSLForGigaChat()
{
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
// Критически важные настройки для GigaChat
config.setProtocol(QSsl::TlsV1_2OrLater);
config.setPeerVerifyMode(QSslSocket::VerifyPeer);
// Дополнительные настройки для совместимости
config.setSslOption(QSsl::SslOptionDisableSessionTickets, false);
config.setSslOption(QSsl::SslOptionDisableCompression, false);
config.setSslOption(QSsl::SslOptionDisableServerNameIndication, false);
config.setPeerVerifyDepth(2);
QSslConfiguration::setDefaultConfiguration(config);
}
void NeuralNetworkManager::setupDefaultConfigs()
{
// DeepSeek
NetworkConfig deepseekConfig;
deepseekConfig.baseUrl = "https://api.deepseek.com";
deepseekConfig.endpoint = "/chat/completions";
deepseekConfig.model = "deepseek-chat";
networkConfigs[DeepSeek] = deepseekConfig;
// GigaChat
NetworkConfig gigachatConfig;
gigachatConfig.baseUrl = "https://gigachat.devices.sberbank.ru";
gigachatConfig.endpoint = "/api/v1/chat/completions";
gigachatConfig.model = "GigaChat";
networkConfigs[GigaChat] = gigachatConfig;
// ChatGPT
NetworkConfig chatgptConfig;
chatgptConfig.baseUrl = "https://api.openai.com";
chatgptConfig.endpoint = "/v1/chat/completions";
chatgptConfig.model = "gpt-3.5-turbo";
networkConfigs[ChatGPT] = chatgptConfig;
// Ollama
NetworkConfig ollamaConfig;
ollamaConfig.baseUrl = "http://localhost:11434";
ollamaConfig.endpoint = "/api/chat";
ollamaConfig.model = "zephyr:7b";
networkConfigs[Ollama] = ollamaConfig;
}
void NeuralNetworkManager::sendMessage(const QString &message, NetworkType networkType)
{
QMutexLocker locker(&mutex);
if (!networkConfigs.contains(networkType)) {
emit errorOccurred("Неизвестный тип нейросети");
return;
}
// Для GigaChat проверяем токен
if (networkType == GigaChat) {
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
if (!isGigaChatTokenValid()) {
// Добавляем сообщение в очередь
PendingMessage pendingMsg;
pendingMsg.message = message;
pendingMsg.networkType = networkType;
pendingMessages.append(pendingMsg);
// Запрашиваем новый токен если еще не запрашиваем
if (!isProcessingGigaChatToken) {
requestGigaChatToken();
}
return;
}
}
// Если токен валиден, отправляем сообщение сразу
sendMessageInternal(message, networkType);
}
void NeuralNetworkManager::sendMessageInternal(const QString &message, NetworkType networkType)
{
QElapsedTimer timer;
timer.start();
QString fullMessage = currentPrefix.isEmpty() ? message : currentPrefix + " " + message;
QNetworkRequest request = prepareRequest(networkType);
QByteArray requestData = prepareRequestBody(networkType, fullMessage);
// Устанавливаем таймаут
// request.setTransferTimeout(30000);
QNetworkReply *reply = networkManager->post(request, requestData);
reply->setProperty("networkType", networkType);
// Для GigaChat обрабатываем SSL ошибки
if (networkType == GigaChat) {
connect(reply, &QNetworkReply::sslErrors,
this, [reply](const QList<QSslError> &errors) {
for (const QSslError &error : errors) {
}
reply->ignoreSslErrors();
});
}
}
void NeuralNetworkManager::setPrefix(const QString &prefix)
{
QMutexLocker locker(&mutex);
currentPrefix = prefix;
}
void NeuralNetworkManager::setApiKey(NetworkType networkType, const QString &apiKey)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].apiKey = apiKey;
}
}
void NeuralNetworkManager::setModel(NetworkType networkType, const QString &model)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].model = model;
}
}
void NeuralNetworkManager::setBaseUrl(NetworkType networkType, const QString &url)
{
QMutexLocker locker(&mutex);
if (networkConfigs.contains(networkType)) {
networkConfigs[networkType].baseUrl = url;
}
}
void NeuralNetworkManager::setOllamaUrl(const QString &url)
{
setBaseUrl(Ollama, url);
}
void NeuralNetworkManager::setGigaChatCredentials(const QString &clientId,
const QString &authorizationCode)
{
QMutexLocker locker(&mutex);
gigaChatAuth.clientId = clientId;
gigaChatAuth.authorizationCode = authorizationCode;
}
void NeuralNetworkManager::refreshGigaChatToken()
{
QMutexLocker locker(&mutex);
if (!gigaChatAuth.clientId.isEmpty() && !gigaChatAuth.authorizationCode.isEmpty()) {
requestGigaChatToken();
}
}
QNetworkRequest NeuralNetworkManager::prepareRequest(NetworkType type)
{
if (!networkConfigs.contains(type)) {
qWarning() << "Попытка подготовить запрос для неизвестного типа:" << type;
return QNetworkRequest();
}
NetworkConfig config = networkConfigs[type];
// Проверяем и корректируем URL
QString baseUrl = config.baseUrl;
QString endpoint = config.endpoint;
if (baseUrl.isEmpty()) {
qWarning() << "Base URL пуст для типа" << type;
return QNetworkRequest();
}
// Убедимся, что baseUrl имеет схему
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
baseUrl = "https://" + baseUrl;
}
QString urlStr = baseUrl + endpoint;
QUrl url(urlStr);
if (!url.isValid()) {
qWarning() << "Некорректный URL:" << urlStr;
return QNetworkRequest();
}
QNetworkRequest request(url);
// ОСОБАЯ обработка для GigaChat
if (type == GigaChat) {
// SSL конфигурация для GigaChat
setupSSLForGigaChat();
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
request.setSslConfiguration(sslConfig);
// Заголовки для GigaChat
request.setRawHeader("Accept-Encoding", "gzip, deflate, br");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Content-Type", "application/json");
if (!gigaChatAuth.accessToken.isEmpty()) {
QString authHeader = QString("Bearer %1").arg(gigaChatAuth.accessToken);
request.setRawHeader("Authorization", authHeader.toUtf8());
} else {
qWarning() << "Токен GigaChat отсутствует!";
}
} else {
// Общая настройка для других сервисов
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer);
request.setSslConfiguration(sslConfig);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (type == DeepSeek || type == ChatGPT) {
if (!config.apiKey.isEmpty()) {
request.setRawHeader("Authorization",
QString("Bearer %1").arg(config.apiKey).toUtf8());
}
}
}
// Общие заголовки
request.setRawHeader("User-Agent", "TwitchBot/1.0 (Qt Network)");
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
return request;
}
QByteArray NeuralNetworkManager::prepareRequestBody(NetworkType type, const QString &message)
{
QJsonObject requestBody;
QJsonArray messages;
QJsonObject messageObj;
messageObj["role"] = "user";
messageObj["content"] = message;
messages.append(messageObj);
if (type == Ollama) {
// Формат запроса для Ollama
requestBody["model"] = networkConfigs[type].model;
requestBody["messages"] = messages;
requestBody["stream"] = false;
QJsonObject options;
options["temperature"] = 0.7;
options["num_predict"] = 200;
requestBody["options"] = options;
} else {
// Формат запроса для остальных API (OpenAI-совместимый)
requestBody["model"] = networkConfigs[type].model;
requestBody["messages"] = messages;
requestBody["max_tokens"] = 300;
requestBody["temperature"] = 0.7;
requestBody["stream"] = false;
}
return QJsonDocument(requestBody).toJson();
}
void NeuralNetworkManager::handleNetworkReply(QNetworkReply *reply)
{
QElapsedTimer timer;
timer.start();
// Определяем тип запроса
bool isTokenRequest = reply->property("isTokenRequest").toBool();
QString url = reply->url().toString();
// Если это запрос токена GigaChat
if (isTokenRequest || url.contains("oauth")) {
handleGigaChatTokenReply(reply);
reply->deleteLater();
return;
}
// Обработка обычных ответов от нейросетей
NetworkType type = static_cast<NetworkType>(reply->property("networkType").toInt());
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = QString("Ошибка сети (%1): %2")
.arg(type)
.arg(reply->errorString());
// Для GigaChat проверяем, не истек ли токен
if (type == GigaChat &&
(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401 ||
reply->error() == QNetworkReply::AuthenticationRequiredError)) {
gigaChatAuth.tokenExpiry = 0; // Помечаем токен как устаревший
// Запрашиваем новый токен
if (!isProcessingGigaChatToken) {
requestGigaChatToken();
}
} else {
emit errorOccurred(errorMsg);
}
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
QString response = parseResponse(type, data);
if (!response.isEmpty()) {
QString truncatedResponse = truncateResponse(response);
emit responseReceived(truncatedResponse);
} else {
emit errorOccurred("Не удалось получить ответ от нейросети");
}
reply->deleteLater();
}
void NeuralNetworkManager::handleGigaChatTokenReply(QNetworkReply *reply)
{
QMutexLocker locker(&mutex);
QByteArray data = reply->readAll();
isProcessingGigaChatToken = false;
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = QString("Ошибка получения токена GigaChat: %1").arg(reply->errorString());
// Очищаем очередь сообщений если не удалось получить токен
pendingMessages.clear();
emit errorOccurred(errorMsg);
reply->deleteLater();
return;
}
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull()) {
pendingMessages.clear();
emit errorOccurred("Неверный формат ответа от GigaChat");
reply->deleteLater();
return;
}
QJsonObject json = doc.object();
if (json.contains("access_token")) {
gigaChatAuth.accessToken = json["access_token"].toString();
int expiresIn = json.contains("expires_in")
? json["expires_in"].toInt(1800)
: 1800;
// Устанавливаем время истечения (текущее время + expiresIn секунд)
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
gigaChatAuth.tokenExpiry = currentTime + expiresIn - 300; // Вычитаем 5 минут для запаса
emit gigaChatTokenRefreshed();
// Обрабатываем сообщения из очереди
processPendingMessages();
} else if (json.contains("error")) {
QString errorMsg = json["error"].toString();
// Очищаем очередь сообщений
pendingMessages.clear();
emit errorOccurred("Ошибка GigaChat: " + errorMsg);
} else {
pendingMessages.clear();
emit errorOccurred("Неизвестный формат ответа от GigaChat");
}
reply->deleteLater();
}
bool NeuralNetworkManager::isGigaChatTokenValid() const
{
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
// Проверяем есть ли токен и не истек ли он
bool isValid = !gigaChatAuth.accessToken.isEmpty() &&
gigaChatAuth.tokenExpiry > currentTime;
qDebug() << "Проверка токена GigaChat:"
<< "Токен:" << (!gigaChatAuth.accessToken.isEmpty() ? "есть" : "нет")
<< "Текущее время:" << currentTime
<< "Истекает:" << gigaChatAuth.tokenExpiry
<< "Валиден:" << (isValid ? "да" : "нет");
return isValid;
}
bool NeuralNetworkManager::shouldRefreshGigaChatToken() const
{
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
// Проверяем, нужно ли обновить токен (осталось меньше 5 минут)
bool shouldRefresh = gigaChatAuth.tokenExpiry > 0 &&
(gigaChatAuth.tokenExpiry - currentTime) < 300;
qDebug() << "Нужно ли обновить токен:"
<< "Текущее время:" << currentTime
<< "Истекает:" << gigaChatAuth.tokenExpiry
<< "Осталось секунд:" << (gigaChatAuth.tokenExpiry - currentTime)
<< "Обновить:" << (shouldRefresh ? "да" : "нет");
return shouldRefresh;
}
void NeuralNetworkManager::requestGigaChatToken()
{
if (isProcessingGigaChatToken) {
return;
}
if (gigaChatAuth.clientId.isEmpty() || gigaChatAuth.authorizationCode.isEmpty()) {
emit errorOccurred("Не установлены учетные данные GigaChat");
// Очищаем очередь сообщений
pendingMessages.clear();
return;
}
isProcessingGigaChatToken = true;
QString tokenUrl = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth";
QNetworkRequest request((QUrl(tokenUrl)));
// Настройка SSL для GigaChat
setupSSLForGigaChat();
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
request.setSslConfiguration(sslConfig);
// Заголовки
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
request.setRawHeader("RqUID", gigaChatAuth.clientId.toUtf8());
request.setRawHeader("Authorization",
QString("Basic %1").arg(gigaChatAuth.authorizationCode).toUtf8());
// Тело запроса
QByteArray postData = "scope=" + gigaChatScope.toUtf8();
QNetworkReply *reply = networkManager->post(request, postData);
reply->setProperty("isTokenRequest", true);
// Обработка SSL ошибок
connect(reply, &QNetworkReply::sslErrors,
this, [reply](const QList<QSslError> &errors) {
reply->ignoreSslErrors();
});
}
void NeuralNetworkManager::processPendingMessages()
{
while (!pendingMessages.isEmpty()) {
PendingMessage pendingMsg = pendingMessages.takeFirst();
// Проверяем, что токен все еще валиден
if (isGigaChatTokenValid()) {
sendMessageInternal(pendingMsg.message, pendingMsg.networkType);
} else {
pendingMessages.prepend(pendingMsg); // Возвращаем сообщение в начало очереди
break;
}
}
}
QString NeuralNetworkManager::parseResponse(NetworkType type, const QByteArray &data)
{
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull()) {
return QString();
}
QJsonObject json = doc.object();
if (type == Ollama) {
// Парсинг ответа Ollama
if (json.contains("message")) {
QJsonObject message = json["message"].toObject();
if (message.contains("content")) {
return message["content"].toString().trimmed();
}
}
} else {
// Парсинг ответа OpenAI-совместимых API
if (json.contains("choices") && json["choices"].isArray()) {
QJsonArray choices = json["choices"].toArray();
if (!choices.isEmpty()) {
QJsonObject choice = choices[0].toObject();
if (choice.contains("message")) {
QJsonObject message = choice["message"].toObject();
if (message.contains("content")) {
return message["content"].toString().trimmed();
}
}
}
}
// Проверка на ошибки API
if (json.contains("error")) {
QJsonObject error = json["error"].toObject();
QString errorMsg = error.contains("message")
? error["message"].toString()
: "Неизвестная ошибка API";
return QString();
}
}
return QString();
}
QString NeuralNetworkManager::truncateResponse(const QString &response)
{
// Ограничиваем длину ответа 400 символами
if (response.length() > 400) {
QString truncated = response.left(400);
// Пытаемся обрезать по последнему предложению
int lastDot = truncated.lastIndexOf('.');
int lastExcl = truncated.lastIndexOf('!');
int lastQuest = truncated.lastIndexOf('?');
int lastBreak = qMax(lastDot, qMax(lastExcl, lastQuest));
if (lastBreak > 300) {
truncated = truncated.left(lastBreak + 1);
} else {
truncated += "...";
}
return truncated;
}
return response;
}
+107
View File
@@ -0,0 +1,107 @@
// neuralnetworkmanager.h
#ifndef NEURALNETWORKMANAGER_H
#define NEURALNETWORKMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonArray>
#include <QMutex>
#include <QTimer>
#include <QSslConfiguration>
class NeuralNetworkManager : public QObject
{
Q_OBJECT
public:
enum NetworkType {
DeepSeek,
GigaChat,
ChatGPT,
Ollama
};
explicit NeuralNetworkManager(QObject *parent = nullptr);
~NeuralNetworkManager();
// Основные методы
void sendMessage(const QString &message, NetworkType networkType);
void setPrefix(const QString &prefix);
void setApiKey(NetworkType networkType, const QString &apiKey);
void setModel(NetworkType networkType, const QString &model);
void setBaseUrl(NetworkType networkType, const QString &url);
// Настройки Ollama
void setOllamaUrl(const QString &url);
// Настройки GigaChat OAuth
void setGigaChatCredentials(const QString &clientId,
const QString &authorizationCode);
void refreshGigaChatToken();
private slots:
void handleNetworkReply(QNetworkReply *reply);
void handleGigaChatTokenReply(QNetworkReply *reply);
private:
struct NetworkConfig {
QString apiKey;
QString model;
QString baseUrl;
QString endpoint;
};
struct GigaChatAuth {
QString clientId;
QString authorizationCode;
QString accessToken;
qint64 tokenExpiry = 0; // Unix timestamp
};
// SSL настройки
void setupSSL();
void setupSSLForGigaChat();
// Основные переменные
QNetworkAccessManager *networkManager;
QMap<NetworkType, NetworkConfig> networkConfigs;
QString currentPrefix;
QMutex mutex;
GigaChatAuth gigaChatAuth;
QString gigaChatScope = "GIGACHAT_API_PERS";
bool isProcessingGigaChatToken = false;
// Методы для подготовки запросов
QNetworkRequest prepareRequest(NetworkType type);
QByteArray prepareRequestBody(NetworkType type, const QString &message);
// Методы для парсинга ответов
QString parseResponse(NetworkType type, const QByteArray &data);
QString truncateResponse(const QString &response);
// Методы для работы с GigaChat OAuth
void requestGigaChatToken();
bool isGigaChatTokenValid() const;
bool shouldRefreshGigaChatToken() const;
// Очередь сообщений для GigaChat
struct PendingMessage {
QString message;
NetworkType networkType;
};
QList<PendingMessage> pendingMessages;
void processPendingMessages();
void sendMessageInternal(const QString &message, NetworkType networkType);
// Конфигурация по умолчанию
void setupDefaultConfigs();
signals:
void responseReceived(const QString &response);
void errorOccurred(const QString &errorMessage);
void gigaChatTokenRefreshed();
};
#endif // NEURALNETWORKMANAGER_H
+41
View File
@@ -0,0 +1,41 @@
debug/emoteprovider.o
debug/fcolorsetting.o
debug/fcreatechat.o
debug/fcreatenotify.o
debug/ffontsetting.o
debug/fsettingsws.o
debug/fsinglegrid.o
debug/main.o
debug/neuralnetworkmanager.o
debug/soundmanager.o
debug/tauth.o
debug/ttw_api.o
debug/twitchmessage.o
debug/udatabase.o
debug/ugeneral.o
debug/ulink.o
debug/user.o
debug/user_manager.o
debug/userwidget.o
debug/webserverchat.o
debug/webservernotify.o
debug/websocketclient.o
debug/moc_emoteprovider.o
debug/moc_fcolorsetting.o
debug/moc_fcreatechat.o
debug/moc_fcreatenotify.o
debug/moc_ffontsetting.o
debug/moc_fsettingsws.o
debug/moc_fsinglegrid.o
debug/moc_neuralnetworkmanager.o
debug/moc_soundmanager.o
debug/moc_tauth.o
debug/moc_ttw_api.o
debug/moc_udatabase.o
debug/moc_ugeneral.o
debug/moc_ulink.o
debug/moc_user_manager.o
debug/moc_userwidget.o
debug/moc_webserverchat.o
debug/moc_webservernotify.o
debug/moc_websocketclient.o
+41
View File
@@ -0,0 +1,41 @@
release/emoteprovider.o
release/fcolorsetting.o
release/fcreatechat.o
release/fcreatenotify.o
release/ffontsetting.o
release/fsettingsws.o
release/fsinglegrid.o
release/main.o
release/neuralnetworkmanager.o
release/soundmanager.o
release/tauth.o
release/ttw_api.o
release/twitchmessage.o
release/udatabase.o
release/ugeneral.o
release/ulink.o
release/user.o
release/user_manager.o
release/userwidget.o
release/webserverchat.o
release/webservernotify.o
release/websocketclient.o
release/moc_emoteprovider.o
release/moc_fcolorsetting.o
release/moc_fcreatechat.o
release/moc_fcreatenotify.o
release/moc_ffontsetting.o
release/moc_fsettingsws.o
release/moc_fsinglegrid.o
release/moc_neuralnetworkmanager.o
release/moc_soundmanager.o
release/moc_tauth.o
release/moc_ttw_api.o
release/moc_udatabase.o
release/moc_ugeneral.o
release/moc_ulink.o
release/moc_user_manager.o
release/moc_userwidget.o
release/moc_webserverchat.o
release/moc_webservernotify.o
release/moc_websocketclient.o
+274
View File
@@ -0,0 +1,274 @@
#include "soundmanager.h"
#include <QDebug>
#include <QFileInfo>
SoundManager::SoundManager(QObject *parent) : QObject(parent)
{
// Инициализация COM
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// Инициализация каналов
for (int i = 0; i < 2; i++) {
m_channels[i].graphBuilder = NULL;
m_channels[i].mediaControl = NULL;
m_channels[i].mediaPosition = NULL;
m_channels[i].basicAudio = NULL;
m_channels[i].audioRenderer = NULL;
m_channels[i].sessionManager = NULL;
m_channels[i].sessionControl = NULL;
m_channels[i].isLoaded = false;
}
}
SoundManager::~SoundManager()
{
// Очистка ресурсов
for (int i = 0; i < 2; i++) {
cleanupChannel(static_cast<SoundChannel>(i));
}
CoUninitialize();
}
IBaseFilter* SoundManager::createAudioRenderer()
{
IBaseFilter* renderer = NULL;
// Создаем отдельный аудио рендерер для каждого канала
HRESULT hr = CoCreateInstance(CLSID_DSoundRender, NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void**)&renderer);
if (FAILED(hr)) {
return NULL;
}
return renderer;
}
bool SoundManager::initializeChannel(SoundChannel channel)
{
int idx = static_cast<int>(channel);
// Создаем GraphBuilder
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
(void**)&m_channels[idx].graphBuilder);
if (FAILED(hr)) {
return false;
}
// Создаем отдельный аудио рендерер для этого канала
m_channels[idx].audioRenderer = createAudioRenderer();
if (!m_channels[idx].audioRenderer) {
cleanupChannel(channel);
return false;
}
// Добавляем рендерер в граф
WCHAR filterName[64];
swprintf_s(filterName, 64, L"Audio Renderer Channel %d", idx + 1);
hr = m_channels[idx].graphBuilder->AddFilter(m_channels[idx].audioRenderer, filterName);
if (FAILED(hr)) {
cleanupChannel(channel);
return false;
}
// Пытаемся получить интерфейс аудио сессии для отдельного управления
IAudioSessionManager2* pSessionManager = NULL;
hr = m_channels[idx].audioRenderer->QueryInterface(__uuidof(IAudioSessionManager2),
(void**)&m_channels[idx].sessionManager);
if (SUCCEEDED(hr)) {
// Получаем контроль над сессией
hr = m_channels[idx].sessionManager->GetAudioSessionControl(NULL, 0,
&m_channels[idx].sessionControl);
if (SUCCEEDED(hr)) {
// Устанавливаем уникальное отображаемое имя для сессии
WCHAR sessionName[64];
swprintf_s(sessionName, 64, L"Channel %d", idx + 1);
m_channels[idx].sessionControl->SetDisplayName(sessionName, NULL);
}
}
// Получаем интерфейсы управления
hr = m_channels[idx].graphBuilder->QueryInterface(IID_IMediaControl,
(void**)&m_channels[idx].mediaControl);
if (FAILED(hr)) {
cleanupChannel(channel);
return false;
}
hr = m_channels[idx].graphBuilder->QueryInterface(IID_IMediaPosition,
(void**)&m_channels[idx].mediaPosition);
if (FAILED(hr)) {
cleanupChannel(channel);
return false;
}
hr = m_channels[idx].graphBuilder->QueryInterface(IID_IBasicAudio,
(void**)&m_channels[idx].basicAudio);
if (FAILED(hr)) {
cleanupChannel(channel);
return false;
}
return true;
}
void SoundManager::cleanupChannel(SoundChannel channel)
{
int idx = static_cast<int>(channel);
if (m_channels[idx].mediaControl) {
m_channels[idx].mediaControl->Stop();
m_channels[idx].mediaControl->Release();
m_channels[idx].mediaControl = NULL;
}
if (m_channels[idx].mediaPosition) {
m_channels[idx].mediaPosition->Release();
m_channels[idx].mediaPosition = NULL;
}
if (m_channels[idx].basicAudio) {
m_channels[idx].basicAudio->Release();
m_channels[idx].basicAudio = NULL;
}
if (m_channels[idx].sessionControl) {
m_channels[idx].sessionControl->Release();
m_channels[idx].sessionControl = NULL;
}
if (m_channels[idx].sessionManager) {
m_channels[idx].sessionManager->Release();
m_channels[idx].sessionManager = NULL;
}
if (m_channels[idx].audioRenderer) {
m_channels[idx].audioRenderer->Release();
m_channels[idx].audioRenderer = NULL;
}
if (m_channels[idx].graphBuilder) {
m_channels[idx].graphBuilder->Release();
m_channels[idx].graphBuilder = NULL;
}
m_channels[idx].isLoaded = false;
}
bool SoundManager::loadSound(SoundChannel channel, const QString &filePath)
{
int idx = static_cast<int>(channel);
// Проверяем существование файла
if (!QFileInfo(filePath).exists()) {
return false;
}
// Очищаем предыдущий звук на канале
cleanupChannel(channel);
// Инициализируем канал
if (!initializeChannel(channel)) {
return false;
}
// Конвертируем QString в WCHAR
std::wstring wideFilePath = filePath.toStdWString();
// Рендерим файл
HRESULT hr = m_channels[idx].graphBuilder->RenderFile(wideFilePath.c_str(), NULL);
if (FAILED(hr)) {
cleanupChannel(channel);
return false;
}
m_channels[idx].isLoaded = true;
return true;
}
bool SoundManager::playSound(SoundChannel channel)
{
int idx = static_cast<int>(channel);
if (!m_channels[idx].isLoaded || !m_channels[idx].mediaControl) {
return false;
}
HRESULT hr = m_channels[idx].mediaControl->Run();
if (FAILED(hr)) {
return false;
}
return true;
}
bool SoundManager::pauseSound(SoundChannel channel)
{
int idx = static_cast<int>(channel);
if (!m_channels[idx].isLoaded || !m_channels[idx].mediaControl) {
return false;
}
HRESULT hr = m_channels[idx].mediaControl->Pause();
return SUCCEEDED(hr);
}
bool SoundManager::stopSound(SoundChannel channel)
{
int idx = static_cast<int>(channel);
if (!m_channels[idx].isLoaded || !m_channels[idx].mediaControl) {
return false;
}
HRESULT hr = m_channels[idx].mediaControl->Stop();
// Сбрасываем позицию воспроизведения
if (m_channels[idx].mediaPosition) {
m_channels[idx].mediaPosition->put_CurrentPosition(0);
}
return SUCCEEDED(hr);
}
void SoundManager::setVolume(SoundChannel channel, int volume)
{
int idx = static_cast<int>(channel);
if (!m_channels[idx].isLoaded || !m_channels[idx].basicAudio) {
return;
}
// Конвертируем 0-100 в диапазон DirectShow (-10000 до 0)
long directShowVolume = (volume - 100) * 100; // 0 = -10000, 100 = 0
m_channels[idx].basicAudio->put_Volume(directShowVolume);
}
bool SoundManager::isPlaying(SoundChannel channel)
{
int idx = static_cast<int>(channel);
if (!m_channels[idx].isLoaded || !m_channels[idx].mediaControl) {
return false;
}
OAFilterState state;
HRESULT hr = m_channels[idx].mediaControl->GetState(10, &state);
if (SUCCEEDED(hr)) {
return (state == State_Running);
}
return false;
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef SOUNDMANAGER_H
#define SOUNDMANAGER_H
#include <QObject>
#include <QString>
#include <windows.h>
#include <dshow.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <audiopolicy.h>
class SoundManager : public QObject
{
Q_OBJECT
public:
explicit SoundManager(QObject *parent = 0);
~SoundManager();
enum SoundChannel {
Channel1 = 0,
Channel2 = 1
};
bool loadSound(SoundChannel channel, const QString &filePath);
bool playSound(SoundChannel channel);
bool pauseSound(SoundChannel channel);
bool stopSound(SoundChannel channel);
void setVolume(SoundChannel channel, int volume); // 0-100
bool isPlaying(SoundChannel channel);
private:
struct SoundDevice {
IGraphBuilder *graphBuilder;
IMediaControl *mediaControl;
IMediaPosition *mediaPosition;
IBasicAudio *basicAudio;
IBaseFilter *audioRenderer;
IAudioSessionManager2 *sessionManager;
IAudioSessionControl *sessionControl;
bool isLoaded;
};
SoundDevice m_channels[2];
bool initializeChannel(SoundChannel channel);
void cleanupChannel(SoundChannel channel);
IBaseFilter* createAudioRenderer();
};
#endif // SOUNDMANAGER_H
+371
View File
@@ -0,0 +1,371 @@
#include "tauth.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <QUrl>
#include <QUrlQuery>
#include <QDesktopServices>
#include <QHostAddress>
#include <QByteArray>
#include <QDebug>
#include <QStringList>
#include <QTimer>
TAuth::TAuth(QObject *parent)
: QObject(parent)
, m_server(nullptr)
, m_clientSocket(nullptr)
, m_serverPort(0)
, m_autoOpenBrowser(true)
{
}
TAuth::~TAuth()
{
stopServer();
}
void TAuth::startServer(const QString &authUrl, bool openBrowser)
{
stopServer();
m_authUrl = authUrl;
m_autoOpenBrowser = openBrowser;
m_server = new QTcpServer(this);
// Пытаемся слушать на портах, начиная с 8080
QVector<int> portsToTry = {8089};
for (int port : portsToTry) {
if (m_server->listen(QHostAddress::LocalHost, port)) {
m_serverPort = port;
emit serverStarted(port);
break;
}
}
if (!m_server->isListening()) {
emit errorOccurred("Не удалось запустить сервер на доступных портах");
delete m_server;
m_server = nullptr;
return;
}
connect(m_server, &QTcpServer::newConnection,
this, &TAuth::handleNewConnection);
// Открываем браузер только если указано
if (openBrowser && !authUrl.isEmpty()) {
QDesktopServices::openUrl(QUrl(authUrl));
}
}
void TAuth::stopServer()
{
if (m_server) {
m_server->close();
m_server->deleteLater();
m_server = nullptr;
}
if (m_clientSocket) {
m_clientSocket->disconnect();
m_clientSocket->close();
m_clientSocket->deleteLater();
m_clientSocket = nullptr;
}
m_serverPort = 0;
}
void TAuth::handleNewConnection()
{
if (!m_server) return;
m_clientSocket = m_server->nextPendingConnection();
if (m_clientSocket) {
connect(m_clientSocket, &QTcpSocket::readyRead,
this, &TAuth::readClientData);
connect(m_clientSocket, &QTcpSocket::disconnected,
m_clientSocket, &QTcpSocket::deleteLater);
}
}
void TAuth::readClientData()
{
if (!m_clientSocket || !m_clientSocket->bytesAvailable()) return;
QByteArray requestData = m_clientSocket->readAll();
QString request = QString::fromUtf8(requestData);
QStringList lines = request.split("\r\n");
if (lines.isEmpty()) return;
QString requestLine = lines[0];
QStringList parts = requestLine.split(" ");
if (parts.size() >= 2 && parts[0].toUpper() == "GET") {
QString document = parts[1];
// Проверяем, есть ли в запросе параметры (они могут быть в теле запроса)
// или в реферере
QString referer;
for (const QString &line : lines) {
if (line.startsWith("Referer:", Qt::CaseInsensitive)) {
referer = line.mid(9).trimmed();
break;
}
}
// Обрабатываем обычный redirect
if (document.startsWith("/redirect")) {
handleRedirectRequest(document, m_clientSocket);
}
// Обрабатываем корневой запрос
else if (document == "/" || document == "/?") {
// Проверяем, есть ли хэш в URL
QString fullUrl = document;
if (!referer.isEmpty() && referer.contains("#")) {
// Извлекаем хэш из реферера
QString hash = referer.mid(referer.indexOf("#") + 1);
fullUrl = "/redirect?" + hash;
handleRedirectRequest(fullUrl, m_clientSocket);
} else {
// Или проверяем, есть ли параметры в самом запросе
// (хэш не передается, но могут быть другие параметры)
QString params;
if (document.contains("#")) {
params = document.mid(document.indexOf("#") + 1);
fullUrl = "/redirect?" + params;
handleRedirectRequest(fullUrl, m_clientSocket);
} else {
handleRootRequest(m_clientSocket);
}
}
}
else if (document.startsWith("/da")) {
QString html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Redirecting...</title>\n</head>\n<body>\n"
" <p>получаю код</p>\n<script>\n"
"var paragraph = window.location.href;\n"
"var urrl = paragraph.replace('localhost:8089/da','localhost:8089/redirect');\n"
"urrl = urrl.replace('#','?');\n"
"console.log(urrl);\n"
"window.location.href = urrl;\n"
"</script>\n</body>\n</html>";
sendResponse(m_clientSocket, html);
}
else {
// Показываем страницу с JavaScript для извлечения хэша
QString html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Processing...</title>\n</head>\n<body>\n"
" <p>Обработка авторизации...</p>\n<script>\n"
"try {\n"
" // Проверяем, есть ли хэш в URL\n"
" var hash = window.location.hash;\n"
" if (hash) {\n"
" // Преобразуем # в ? для передачи на сервер\n"
" var params = hash.substring(1); // убираем #\n"
" // Перенаправляем на /redirect с параметрами\n"
" window.location.href = '/redirect?' + params;\n"
" } else {\n"
" document.body.innerHTML = '<h2>Нет данных авторизации</h2>';\n"
" }\n"
"} catch(e) {\n"
" document.body.innerHTML = '<h2>Ошибка: ' + e.message + '</h2>';\n"
"}\n"
"</script>\n</body>\n</html>";
sendResponse(m_clientSocket, html);
}
} else {
sendErrorResponse(m_clientSocket, "Invalid request method");
}
}
void TAuth::handleRootRequest(QTcpSocket *socket)
{
QString html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Processing...</title>\n</head>\n<body>\n"
" <p>Обработка авторизации...</p>\n<script>\n"
"try {\n"
" var hash = window.location.hash;\n"
" if (hash) {\n"
" var params = hash.substring(1);\n"
" window.location.href = '/redirect?' + params;\n"
" } else {\n"
" document.body.innerHTML = '<h2>Нет данных авторизации в URL</h2>';\n"
" }\n"
"} catch(e) {\n"
" document.body.innerHTML = '<h2>Ошибка: ' + e.message + '</h2>';\n"
"}\n"
"</script>\n</body>\n</html>";
sendResponse(socket, html);
}
void TAuth::handleRedirectRequest(const QString &request, QTcpSocket *socket)
{
QString params;
if (request.contains('?')) {
params = request.mid(request.indexOf('?') + 1);
}
QString html;
QString token, code, expiresIn;
// Проверяем наличие access_token
if (params.contains("access_token=")) {
token = extractParam(params, "access_token");
expiresIn = extractExpiresIn(params);
// Формируем полный токен с временем жизни (если есть)
QString fullToken = token;
if (!expiresIn.isEmpty()) {
fullToken += "|" + expiresIn;
}
emit tokenReceived(fullToken);
html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Success</title>\n</head>\n<body>\n"
"<h2>Authorization Successful!</h2>\n"
"<p>Token received. You can close this window.</p>\n"
"</body>\n</html>";
}
// Проверяем наличие error_description
else if (params.contains("error_description=")) {
QString error = extractErrorDescription(params);
emit errorOccurred(error);
html =
QString("<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Error</title>\n</head>\n<body>\n"
"<h2>Authorization Failed</h2>\n"
"<p>Error: %1</p>\n</body>\n</html>").arg(error);
}
// Проверяем наличие code (для OAuth code flow)
else if (params.contains("code=")) {
code = extractParam(params, "code");
emit codeReceived(code);
html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>Success</title>\n</head>\n<body>\n"
"<h2>Authorization Successful!</h2>\n"
"<p>Code received. You can close this window.</p>\n"
"</body>\n</html>";
}
else {
html =
"<!DOCTYPE html>\n<html>\n<head>\n"
" <title>No Data</title>\n</head>\n<body>\n"
"<h2>No authorization data received</h2>\n"
"<p>Try again or check your authorization URL.</p>\n"
"</body>\n</html>";
sendResponse(socket, html);
return;
}
sendResponse(socket, html);
// Останавливаем сервер после обработки
QTimer::singleShot(1000, this, &TAuth::stopServer);
}
QString TAuth::extractParam(const QString &params, const QString &paramName)
{
QStringList paramList = params.split('&');
for (const QString &param : paramList) {
if (param.startsWith(paramName + "=")) {
QString value = param.mid(paramName.length() + 1);
// Декодируем URL-encoded значения
value = QUrl::fromPercentEncoding(value.toUtf8());
return value;
}
}
return QString();
}
QString TAuth::extractExpiresIn(const QString &params)
{
return extractParam(params, "expires_in");
}
void TAuth::extractAndEmitToken(const QString &params)
{
QString token = extractParam(params, "access_token");
QString expiresIn = extractExpiresIn(params);
if (!token.isEmpty()) {
QString fullToken = token;
if (!expiresIn.isEmpty()) {
fullToken += "|" + expiresIn;
}
emit tokenReceived(fullToken);
}
}
void TAuth::extractAndEmitError(const QString &params)
{
QString error = extractErrorDescription(params);
if (!error.isEmpty()) {
emit errorOccurred(error);
}
}
void TAuth::extractAndEmitCode(const QString &params)
{
QString code = extractParam(params, "code");
if (!code.isEmpty()) {
emit codeReceived(code);
}
}
QString TAuth::extractErrorDescription(const QString &params)
{
QString error = extractParam(params, "error_description");
if (error.isEmpty()) {
error = extractParam(params, "error");
}
if (error.isEmpty()) {
error = "Unknown error";
}
return error;
}
void TAuth::sendResponse(QTcpSocket *socket, const QString &content)
{
if (!socket) return;
QString response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: " + QString::number(content.toUtf8().size()) + "\r\n"
"Connection: close\r\n"
"\r\n" + content;
socket->write(response.toUtf8());
socket->flush();
}
void TAuth::sendErrorResponse(QTcpSocket *socket, const QString &error)
{
if (!socket) return;
QString content = "<html><body><h1>" + error + "</h1></body></html>";
QString response =
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: " + QString::number(content.toUtf8().size()) + "\r\n"
"Connection: close\r\n"
"\r\n" + content;
socket->write(response.toUtf8());
socket->flush();
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef TAUTH_H
#define TAUTH_H
#include <QObject>
#include <QString>
class QTcpServer;
class QTcpSocket;
class TAuth : public QObject
{
Q_OBJECT
public:
explicit TAuth(QObject *parent = nullptr);
~TAuth();
// Запуск сервера с указанием режима работы
void startServer(const QString &authUrl, bool openBrowser = true);
void stopServer();
// Получить URL для отображения (если не открываем браузер)
QString getAuthUrl() const { return m_authUrl; }
// Получить порт, на котором запущен сервер
int getServerPort() const { return m_serverPort; }
signals:
void tokenReceived(const QString &token);
void errorOccurred(const QString &error);
// Новый сигнал для передачи кода авторизации
void codeReceived(const QString &code);
// Сигнал о запуске сервера
void serverStarted(int port);
private slots:
void handleNewConnection();
void readClientData();
private:
QTcpServer *m_server;
QTcpSocket *m_clientSocket;
QString m_authUrl;
int m_serverPort;
bool m_autoOpenBrowser;
void handleRootRequest(QTcpSocket *socket);
void handleRedirectRequest(const QString &request, QTcpSocket *socket);
void sendResponse(QTcpSocket *socket, const QString &content);
void sendErrorResponse(QTcpSocket *socket, const QString &error);
void extractAndEmitToken(const QString &params);
void extractAndEmitError(const QString &params);
void extractAndEmitCode(const QString &params);
QString extractErrorDescription(const QString &params);
// Вспомогательные методы для извлечения параметров
QString extractParam(const QString &params, const QString &paramName);
QString extractExpiresIn(const QString &params);
};
#endif // TAUTH_H
+18
View File
@@ -0,0 +1,18 @@
#ifndef TIMERINFO_H
#define TIMERINFO_H
#include <QTimer>
#include <QString>
struct TimerInfo {
int id;
QString message;
int interval; // в минутах
bool isActive;
bool isAnnouncement;
QTimer* timer;
TimerInfo() : id(-1), interval(10), isActive(true), isAnnouncement(false), timer(nullptr) {}
};
#endif // TIMERINFO_H
+593
View File
@@ -0,0 +1,593 @@
#include "ttw_api.h"
#include "qeventloop.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QDateTime>
#include <QDebug>
TTwAPI::TTwAPI(QObject *parent)
: QObject(parent)
, m_networkManager(new QNetworkAccessManager(this))
{
// Настройка таймаутов
//m_networkManager->Timeout(30000); // 30 секунд
}
TTwAPI::~TTwAPI()
{
m_networkManager->deleteLater();
}
void TTwAPI::init(const QString &clientId,
const QString &token,
const QString &streamerToken,
const QString &channel,
const QString &botName)
{
m_clientId = clientId;
m_tokenApi = token;
m_tokenApiStreamer = streamerToken;
m_channelName = channel;
m_botName = botName;
toLog(1, "TTwAPI::init", "API инициализирован для канала: " + channel);
}
QString TTwAPI::sendRequest(const QString &url,
const QString &method,
const QByteArray &data,
const QString &token)
{
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Client-ID", m_clientId.toUtf8());
QString authHeader = token.startsWith("Bearer") ? token : "Bearer " + token;
request.setRawHeader("Authorization", authHeader.toUtf8());
QNetworkReply *reply = nullptr;
if (method.toUpper() == "GET") {
reply = m_networkManager->get(request);
} else if (method.toUpper() == "POST") {
reply = m_networkManager->post(request, data);
} else if (method.toUpper() == "DELETE") {
reply = m_networkManager->sendCustomRequest(request, "DELETE", data);
} else if (method.toUpper() == "PATCH") {
reply = m_networkManager->sendCustomRequest(request, "PATCH", data);
} else if (method.toUpper() == "PUT") {
reply = m_networkManager->sendCustomRequest(request, "PUT", data);
}
if (!reply) {
toLog(2, "TTwAPI::sendRequest", "Не удалось создать запрос: " + url);
return QString();
}
// Ожидание завершения запроса (синхронно)
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
// Проверка ошибок
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = QString("Ошибка %1: %2").arg(reply->error()).arg(reply->errorString());
toLog(2, "TTwAPI::sendRequest", errorMsg);
if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
emit tokenExpired(errorMsg);
}
reply->deleteLater();
return QString();
}
QString response = reply->readAll();
reply->deleteLater();
return response;
}
QString TTwAPI::getTTW(const QString &method,
const QString &clientId,
bool isStreamer)
{
Q_UNUSED(clientId);
QString baseUrl = "https://api.twitch.tv/helix/";
QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi;
return sendRequest(baseUrl + method, "GET", QByteArray(), token);
}
QString TTwAPI::deleteTTW(const QString &method,
const QString &clientId,
bool isStreamer)
{
Q_UNUSED(clientId);
QString baseUrl = "https://api.twitch.tv/helix/";
QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi;
return sendRequest(baseUrl + method, "DELETE", QByteArray(), token);
}
QString TTwAPI::postTTW(const QString &method,
const QString &clientId,
const QByteArray &data,
bool isStreamer)
{
Q_UNUSED(clientId);
QString baseUrl = "https://api.twitch.tv/helix/";
QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi;
return sendRequest(baseUrl + method, "POST", data, token);
}
QString TTwAPI::patchTTW(const QString &method,
const QString &clientId,
const QByteArray &data,
bool isStreamer)
{
Q_UNUSED(clientId);
QString baseUrl = "https://api.twitch.tv/helix/";
QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi;
return sendRequest(baseUrl + method, "PATCH", data, token);
}
void TTwAPI::getTTWStat(const QString &channel,
int &avgViewers,
int &maxViewers,
int &hoursWatched,
int &followers,
int &followersTotal)
{
Q_UNUSED(channel);
QString response = getTTW("channels?broadcaster_id=" + getRoomId(), m_clientId);
if (response.isEmpty()) {
toLog(2, "TTwAPI::getTTWStat", "Пустой ответ от API");
return;
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (!doc.isObject()) {
toLog(2, "TTwAPI::getTTWStat", "Неверный JSON формат");
return;
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
toLog(2, "TTwAPI::getTTWStat", "Нет данных в ответе");
return;
}
QJsonObject channelData = data[0].toObject();
// Парсинг данных (заглушка - реальные поля могут отличаться)
avgViewers = channelData["average_viewers"].toInt();
maxViewers = channelData["peak_viewers"].toInt();
hoursWatched = channelData["hours_watched"].toInt();
followers = channelData["followers_gained"].toInt();
followersTotal = channelData["followers_total"].toInt();
}
QDate TTwAPI::getFollow(const QString &id)
{
// Правильный endpoint для проверки, является ли пользователь фоловером
QString response = getTTW(
QString("channels/followers?user_id=%1&broadcaster_id=%2")
.arg(id)
.arg(getRoomId()),
m_clientId,
true
);
if (response.isEmpty()) {
return QDate();
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (!doc.isObject()) {
return QDate();
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
// Пользователь не фоловер
return QDate();
}
QJsonObject followData = data[0].toObject();
QString followedAt = followData["followed_at"].toString();
return QDate::fromString(followedAt, Qt::ISODate);
}
User TTwAPI::getUserByLogin(const QString &login)
{
User user;
user.login = login.toLower();
QString response = getTTW("users?login=" + login, m_clientId);
if (response.isEmpty()) {
toLog(2, "TTwAPI::getUserByLogin", "Не удалось получить данные пользователя: " + login);
return user;
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (!doc.isObject()) {
return user;
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
return user;
}
QJsonObject userData = data[0].toObject();
user.id = userData["id"].toString();
user.displayName = userData["display_name"].toString();
user.createdAt = QDate::fromString(userData["created_at"].toString(), Qt::ISODate);
// Получаем информацию о подписке
user.followAt = getFollow(user.id);
return user;
}
void TTwAPI::setModerator(const QString &id)
{
QJsonDocument doc((QJsonObject()));
postTTW("moderation/moderators?broadcaster_id=" + getRoomId() +
"&user_id=" + id,
m_clientId,
doc.toJson(),
true);
}
void TTwAPI::delModerator(const QString &id)
{
deleteTTW("moderation/moderators?broadcaster_id=" + getRoomId() +
"&user_id=" + id, m_clientId, true);
}
void TTwAPI::setVIP(const QString &id)
{
QJsonDocument doc((QJsonObject()));
postTTW("channels/vips?broadcaster_id=" + getRoomId() +
"&user_id=" + id,
m_clientId,
doc.toJson(),
true);
}
void TTwAPI::delVIP(const QString &id)
{
deleteTTW("channels/vips?broadcaster_id=" + getRoomId() +
"&user_id=" + id, m_clientId, true);
}
void TTwAPI::banUser(const QString &id)
{
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::banUser", "Не удалось получить roomId");
return;
}
QString botId = getBotId(); // Нужно получить ID бота
if (botId.isEmpty()) {
toLog(2, "TTwAPI::banUser", "Не удалось получить botId");
return;
}
QJsonObject innerData;
innerData["user_id"] = id;
innerData["reason"] = "Нарушение правил чата";
QJsonObject dataObj;
dataObj["data"] = innerData;
QJsonDocument doc(dataObj);
postTTW("moderation/bans?broadcaster_id=" + roomId +
"&moderator_id=" + botId,
m_clientId,
doc.toJson(),
false);
}
void TTwAPI::banUserTime(const QString &id, int timeMinutes)
{
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::banUserTime", "Не удалось получить roomId");
return;
}
QString botId = getBotId();
if (botId.isEmpty()) {
toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId");
return;
}
QJsonObject innerData;
innerData["user_id"] = id;
innerData["duration"] = timeMinutes * 60;
innerData["reason"] = "Таймаут";
QJsonObject dataObj;
dataObj["data"] = innerData;
QJsonDocument doc(dataObj);
postTTW("moderation/bans?broadcaster_id=" + roomId +
"&moderator_id=" + botId,
m_clientId,
doc.toJson(),
false);
}
void TTwAPI::warnUser(const QString &id)
{
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::warnUser", "Не удалось получить roomId");
return;
}
QString botId = getBotId();
if (botId.isEmpty()) {
toLog(2, "TTwAPI::warnUser", "Не удалось получить botId");
return;
}
QJsonObject innerData;
innerData["user_id"] = id;
QJsonObject dataObj;
dataObj["data"] = innerData;
QJsonDocument doc(dataObj);
postTTW("moderation/warnings?broadcaster_id=" + roomId +
"&moderator_id=" + botId,
m_clientId,
doc.toJson(),
false);
}
void TTwAPI::unbanUser(const QString &id)
{
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::unbanUser", "Не удалось получить roomId");
return;
}
QString botId = getBotId();
if (botId.isEmpty()) {
toLog(2, "TTwAPI::unbanUser", "Не удалось получить botId");
return;
}
deleteTTW("moderation/bans?broadcaster_id=" + roomId +
"&moderator_id=" + botId +
"&user_id=" + id,
m_clientId,
false);
}
QString TTwAPI::getBotId()
{
static QString cachedBotId;
if (!cachedBotId.isEmpty()) {
return cachedBotId;
}
if (m_botName.isEmpty()) {
toLog(2, "TTwAPI::getBotId", "Имя бота не установлено");
return QString();
}
QString response = getTTW("users?login=" + m_botName, m_clientId);
if (response.isEmpty()) {
toLog(2, "TTwAPI::getBotId", "Не удалось получить данные бота");
return QString();
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (!doc.isObject()) {
toLog(2, "TTwAPI::getBotId", "Неверный JSON формат");
return QString();
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
toLog(2, "TTwAPI::getBotId", "Нет данных о боте");
return QString();
}
cachedBotId = data[0].toObject()["id"].toString();
return cachedBotId;
}
QString TTwAPI::getFollowedAtFromJson(const QString &jsonString)
{
QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());
if (!doc.isObject()) {
return QString();
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
return QString();
}
QJsonObject followData = data[0].toObject();
return followData["followed_at"].toString();
}
QString TTwAPI::getRoomId()
{
// Кэширование ID комнаты
static QString cachedRoomId;
if (!cachedRoomId.isEmpty()) {
return cachedRoomId;
}
QString response = getTTW("users?login=" + m_channelName, m_clientId);
if (response.isEmpty()) {
return QString();
}
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (!doc.isObject()) {
return QString();
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (data.isEmpty()) {
return QString();
}
cachedRoomId = data[0].toObject()["id"].toString();
return cachedRoomId;
}
QString TTwAPI::getRoomAndBot()
{
QString roomId = getRoomId();
QString botId;
QString response = getTTW("users?login=" + m_botName, m_clientId);
if (!response.isEmpty()) {
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
if (!data.isEmpty()) {
botId = data[0].toObject()["id"].toString();
}
}
return QString("RoomID: %1, BotID: %2").arg(roomId).arg(botId);
}
void TTwAPI::toLog(int level, const QString &method, const QString &message)
{
Q_UNUSED(level);
Q_UNUSED(method);
Q_UNUSED(message);
// uGeneral.toLog("ttw_api", method, message, level);
}
void TTwAPI::sendAnnouncement(const QString &message) {
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::sendAnnouncement", "Не удалось получить roomId");
return;
}
QString botId = getBotId();
if (botId.isEmpty()) {
toLog(2, "TTwAPI::sendAnnouncement", "Не удалось получить botId");
return;
}
QJsonObject json;
json["message"] = message;
json["color"] = "primary"; // Можно сделать настраиваемым: "primary", "blue", "green", "orange", "purple"
QJsonDocument doc(json);
QString otv = postTTW("chat/announcements?broadcaster_id=" + roomId +
"&moderator_id=" + botId,
m_clientId,
doc.toJson(),
false);
}
void TTwAPI::sendAnnouncementToChat(const QString &message) {
// Этот метод может использоваться для обычных сообщений в чат
// Но для Twitch API это тот же endpoint
sendAnnouncement(message);
}
void TTwAPI::getGlobalChatBadges(QVector<ChatBadge> &badges)
{
QString response = getTTW("chat/badges/global", m_clientId, false);
parseBadgesFromApi(response, badges);
}
void TTwAPI::getCustomChatBadges(QVector<ChatBadge> &badges)
{
QString roomId = getRoomId();
if (roomId.isEmpty()) {
toLog(2, "TTwAPI::getCustomChatBadges", "Не удалось получить roomId");
return;
}
QString response = getTTW("chat/badges?broadcaster_id=" + roomId, m_clientId, false);
parseBadgesFromApi(response, badges);
}
void TTwAPI::parseBadgesFromApi(const QString &jsonString, QVector<ChatBadge> &badges)
{
if (jsonString.isEmpty()) {
return;
}
QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());
if (!doc.isObject()) {
toLog(2, "TTwAPI::parseBadgesFromApi", "Неверный JSON формат");
return;
}
QJsonObject root = doc.object();
QJsonArray data = root["data"].toArray();
for (const QJsonValue &value : data) {
QJsonObject badgeObj = value.toObject();
ChatBadge badge;
badge.setId = badgeObj["set_id"].toString();
QJsonArray versions = badgeObj["versions"].toArray();
for (const QJsonValue &versionValue : versions) {
QJsonObject versionObj = versionValue.toObject();
BadgeVersion version;
version.id = versionObj["id"].toString();
version.imageUrl1x = versionObj["image_url_1x"].toString();
version.imageUrl2x = versionObj["image_url_2x"].toString();
version.imageUrl4x = versionObj["image_url_4x"].toString();
version.title = versionObj["title"].toString();
version.description = versionObj["description"].toString();
badge.versions.append(version);
}
badges.append(badge);
}
}
+138
View File
@@ -0,0 +1,138 @@
#ifndef TTW_API_H
#define TTW_API_H
#include <QObject>
#include <QString>
#include <QDate>
#include <QVector>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "user.h"
#include "badge.h"
class TCustomReward;
class TCustomRewardEvent;
class ChatBadge;
class Emote;
class TTwAPI : public QObject
{
Q_OBJECT
public:
explicit TTwAPI(QObject *parent = nullptr);
~TTwAPI();
// Инициализация
void init(const QString &clientId,
const QString &token,
const QString &streamerToken,
const QString &channel,
const QString &botName);
// Основные методы
void getTTWStat(const QString &channel,
int &avgViewers,
int &maxViewers,
int &hoursWatched,
int &followers,
int &followersTotal);
void shoutouts(const QString &id);
void sendNotify(const QString &text);
void banUser(const QString &id);
void banUserTime(const QString &id, int timeMinutes);
void unbanUser(const QString &id);
void warnUser(const QString &id);
void raid(const QString &id);
void unraid();
void setModerator(const QString &id);
void delModerator(const QString &id);
void setVIP(const QString &id);
void delVIP(const QString &id);
// Custom Rewards
void getCustomRewards(QVector<TCustomReward*> &rewards);
TCustomReward* createCustomReward(const QString &title,
const QString &cost,
const QString &prompt = "",
bool isUserInput = false);
void updateCustomReward(TCustomReward* reward);
void updateRedemptionStatus(TCustomRewardEvent* event);
void deleteCustomReward(const QString &id);
// Пользователи
User getUserByLogin(const QString &login);
QDate getFollow(const QString &id);
// Эмоции и бейджи
void getGlobalChatBadges(QVector<ChatBadge> &badges); // Изменено
void getCustomChatBadges(QVector<ChatBadge> &badges); // Изменено
void getChannelEmotes(QVector<Emote*> &emotes);
void getGlobalEmotes(QVector<Emote*> &emotes);
// Валидация токена
bool validateTwitchToken(const QString &tokenName,
const QString &tokenValue,
int &daysValid);
// Вспомогательные методы
QString getRoomId();
QString getRoomAndBot();
void sendAnnouncement(const QString &message);
// Сигналы
void logMessage(int level, const QString &method, const QString &message);
private:
// HTTP методы
QString getTTW(const QString &method,
const QString &clientId,
bool isStreamer = false);
QString deleteTTW(const QString &method,
const QString &clientId,
bool isStreamer = false);
QString postTTW(const QString &method,
const QString &clientId,
const QByteArray &data,
bool isStreamer = false);
QString patchTTW(const QString &method,
const QString &clientId,
const QByteArray &data,
bool isStreamer = false);
// Вспомогательные методы
QString getFollowedAtFromJson(const QString &jsonString);
QString sendRequest(const QString &url,
const QString &method,
const QByteArray &data = QByteArray(),
const QString &token = "");
// Парсинг бейджей
void parseBadgesFromApi(const QString &jsonString, QVector<ChatBadge> &badges);
// Члены данных
QString m_tokenApi;
QString m_tokenApiStreamer;
QString m_clientId;
QString m_channelName;
QString m_botName;
void sendAnnouncementToChat(const QString &message);
QNetworkAccessManager *m_networkManager;
QString getBotId();
// Логгирование
void toLog(int level, const QString &method, const QString &message);
signals:
void apiError(const QString &method, const QString &error);
void tokenExpired(const QString &error);
void rateLimitExceeded();
};
#endif // TTW_API_H
+78
View File
@@ -0,0 +1,78 @@
#ifndef TTW_TYPES_H
#define TTW_TYPES_H
#include "qdatetime.h"
#include <QString>
#include <QColor>
struct TCustomReward {
QString id;
QString title;
QString prompt;
int cost;
QString backgroundColor;
bool isEnabled;
bool isUserInputRequired;
bool isMaxPerStreamEnabled;
int maxPerStream;
bool isMaxPerUserPerStreamEnabled;
int maxPerUserPerStream;
bool isGlobalCooldownEnabled;
int globalCooldownSeconds;
bool isPaused;
bool isInStock;
bool shouldRedemptionsSkipRequestQueue;
TCustomReward()
: cost(0)
, isEnabled(true)
, isUserInputRequired(false)
, isMaxPerStreamEnabled(false)
, maxPerStream(0)
, isMaxPerUserPerStreamEnabled(false)
, maxPerUserPerStream(0)
, isGlobalCooldownEnabled(false)
, globalCooldownSeconds(0)
, isPaused(false)
, isInStock(true)
, shouldRedemptionsSkipRequestQueue(false)
{}
};
struct TCustomRewardEvent {
QString id;
QString rewardId;
QString userId;
QString userLogin;
QString userDisplayName;
QString userInput;
QString status;
QDateTime redeemedAt;
TCustomRewardEvent() {}
};
struct ChatBadge {
QString setId;
QString versionId;
QString title;
QString description;
QString smallImageUrl;
QString mediumImageUrl;
QString largeImageUrl;
ChatBadge() {}
};
struct Emote {
QString id;
QString name;
QString imageUrl;
QString format;
QString scale;
QString themeMode;
Emote() {}
};
#endif // TTW_TYPES_H
+31
View File
@@ -0,0 +1,31 @@
// twitchchatmessage.h - структура сообщения
#ifndef TWITCHCHATMESSAGE_H
#define TWITCHCHATMESSAGE_H
#include <QString>
#include <QDateTime>
#include <QMap>
#include <QVariant>
struct TwitchChatMessage {
QString id; // ID сообщения
QString channel; // Канал
QString sender; // Отправитель
QString message; // Текст сообщения
QDateTime timestamp; // Время отправки
QString userType; // Тип пользователя (mod, broadcaster и т.д.)
bool isModerator; // Модератор
bool isSubscriber; // Сабскрайбер
bool isBroadcaster; // Вещатель (стример)
QString badgeInfo; // Информация о бейджах
QString color; // Цвет ника
QString displayName; // Отображаемое имя
QMap<QString, QString> emotes; // Эмоции
QString roomId; // ID комнаты
QString userId; // ID пользователя
QString rawMessage; // Сырое сообщение
};
Q_DECLARE_METATYPE(TwitchChatMessage)
#endif // TWITCHCHATMESSAGE_H
+307
View File
@@ -0,0 +1,307 @@
#include "twitchclient.h"
#include <QDebug>
#include <QCryptographicHash>
#include <QRandomGenerator>
#include <QCoreApplication>
#include <QDateTime>
TwitchClient::TwitchClient(QObject *parent)
: QObject(parent)
, m_socket(nullptr)
, m_webSocketHandshakeDone(false)
, m_ircAuthenticated(false)
, m_channelJoined(false)
{
m_writeTimer = new QTimer(this);
m_writeTimer->setInterval(10); // 10ms для отправки данных
connect(m_writeTimer, &QTimer::timeout, this, &TwitchClient::sendQueuedData);
m_pingTimer = new QTimer(this);
m_pingTimer->setInterval(30000); // 30 секунд
connect(m_pingTimer, &QTimer::timeout, this, &TwitchClient::sendPing);
}
TwitchClient::~TwitchClient()
{
disconnect();
}
void TwitchClient::connectToTwitch(const QString &oauthToken,
const QString &nickname,
const QString &channel)
{
if (m_socket) {
disconnect();
delete m_socket;
}
m_socket = new QSslSocket(this);
m_nickname = nickname;
m_channel = channel;
m_oauthToken = oauthToken;
m_webSocketHandshakeDone = false;
m_ircAuthenticated = false;
m_channelJoined = false;
m_receiveBuffer.clear();
// Подключаем сигналы
connect(m_socket, &QSslSocket::connected, this, &TwitchClient::onConnected);
connect(m_socket, &QSslSocket::readyRead, this, &TwitchClient::onReadyRead);
connect(m_socket, &QSslSocket::disconnected, this, &TwitchClient::onDisconnected);
connect(m_socket, QOverload<const QList<QSslError>&>::of(&QSslSocket::sslErrors),
this, &TwitchClient::onSslErrors);
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error),
this, &TwitchClient::onSocketError);
// Настраиваем SSL
m_socket->setPeerVerifyMode(QSslSocket::VerifyNone);
m_socket->connectToHostEncrypted("irc-ws.chat.twitch.tv", 443);
}
void TwitchClient::onConnected()
{
// SSL handshake начинается автоматически
}
void TwitchClient::onReadyRead()
{
m_receiveBuffer.append(m_socket->readAll());
if (!m_webSocketHandshakeDone) {
// Обработка WebSocket handshake
int headerEnd = m_receiveBuffer.indexOf("\r\n\r\n");
if (headerEnd != -1) {
QString response = QString::fromUtf8(m_receiveBuffer.left(headerEnd));
if (response.contains("101 Switching Protocols")) {
m_webSocketHandshakeDone = true;
m_receiveBuffer = m_receiveBuffer.mid(headerEnd + 4);
// Запускаем таймеры
m_writeTimer->start();
m_pingTimer->start();
emit systemMessage("WebSocket connected");
emit connected();
// Отправляем PING для проверки
sendPing();
}
}
} else {
// Обработка WebSocket фреймов
while (m_receiveBuffer.size() >= 2) {
// Простой парсинг WebSocket фреймов
// Для продуктивного использования нужен полноценный парсер
// Ищем конец фрейма (предполагаем текстовые сообщения)
QString data = QString::fromUtf8(m_receiveBuffer);
QStringList lines = data.split("\r\n", Qt::SkipEmptyParts);
foreach (const QString &line, lines) {
if (line.startsWith("PING")) {
// Отвечаем на PING
sendWebSocketFrame("PONG :tmi.twitch.tv");
emit systemMessage("PING/PONG");
} else if (!line.isEmpty() && line != "\n") {
parseIrcMessage(line);
}
}
m_receiveBuffer.clear();
break;
}
}
}
void TwitchClient::sendWebSocketFrame(const QByteArray &data, quint8 opcode)
{
QByteArray frame = createWebSocketFrame(data, opcode);
m_writeQueue.enqueue(frame);
if (!m_writeTimer->isActive()) {
m_writeTimer->start();
}
}
QByteArray TwitchClient::createWebSocketFrame(const QByteArray &data, quint8 opcode)
{
QByteArray frame;
// FIN = 1, opcode
frame.append(static_cast<char>(0x80 | opcode));
// Маска и длина
if (data.size() <= 125) {
frame.append(static_cast<char>(0x80 | data.size()));
} else if (data.size() <= 65535) {
frame.append(static_cast<char>(0x80 | 126));
frame.append(static_cast<char>((data.size() >> 8) & 0xFF));
frame.append(static_cast<char>(data.size() & 0xFF));
} else {
frame.append(static_cast<char>(0x80 | 127));
// Для больших размеров (не нужно для Twitch)
}
// Маскирующий ключ (4 случайных байта)
QByteArray mask(4, 0);
for (int i = 0; i < 4; ++i) {
mask[i] = QRandomGenerator::global()->generate() & 0xFF;
}
frame.append(mask);
// Маскированные данные
QByteArray maskedData = data;
for (int i = 0; i < maskedData.size(); ++i) {
maskedData[i] = maskedData[i] ^ mask[i % 4];
}
frame.append(maskedData);
return frame;
}
void TwitchClient::sendIrcCommand(const QString &command, const QString &params)
{
QString message = command;
if (!params.isEmpty()) {
message += " " + params;
}
message += "\r\n";
sendWebSocketFrame(message.toUtf8());
}
void TwitchClient::parseIrcMessage(const QString &message)
{
if (message.startsWith("PING")) {
sendWebSocketFrame("PONG :tmi.twitch.tv");
}
else if (message.contains("001")) {
// Добро пожаловать сообщение
m_ircAuthenticated = true;
emit systemMessage("Authenticated with Twitch");
// Присоединяемся к каналу
sendIrcCommand("JOIN", "#" + m_channel.toLower());
}
else if (message.contains("JOIN")) {
if (message.contains("#" + m_channel.toLower())) {
m_channelJoined = true;
emit systemMessage("Joined channel: " + m_channel);
}
}
else if (message.contains("PRIVMSG")) {
// Парсим сообщение чата
// Формат: :nick!nick@nick.tmi.twitch.tv PRIVMSG #channel :message
QStringList parts = message.split(" ", Qt::SkipEmptyParts);
if (parts.size() >= 4) {
QString sender = parts[0].mid(1).split("!")[0];
QString channel = parts[2];
QString chatMessage = message.mid(message.indexOf(" :") + 2);
emit newMessage(sender, channel, chatMessage);
}
}
else if (message.contains("NOTICE")) {
emit systemMessage("NOTICE: " + message);
}
emit systemMessage(message);
}
void TwitchClient::onSslErrors(const QList<QSslError> &errors)
{
QStringList errorList;
for (const QSslError &error : errors) {
errorList.append(error.errorString());
}
// Игнорируем SSL ошибки для разработки
m_socket->ignoreSslErrors();
// После игнорирования ошибок, отправляем WebSocket handshake
if (m_socket->isEncrypted()) {
QString handshake = QString(
"GET / HTTP/1.1\r\n"
"Host: irc-ws.chat.twitch.tv\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: %1\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"
).arg(QString(generateWebSocketKey()));
m_socket->write(handshake.toUtf8());
}
}
void TwitchClient::onSocketError(QAbstractSocket::SocketError error)
{
QString errorMsg = QString("Socket error: %1 - %2")
.arg(error)
.arg(m_socket ? m_socket->errorString() : "Unknown");
}
void TwitchClient::onDisconnected()
{
m_writeTimer->stop();
m_pingTimer->stop();
emit disconnected();
}
void TwitchClient::sendQueuedData()
{
if (!m_writeQueue.isEmpty() && m_socket && m_socket->state() == QAbstractSocket::ConnectedState) {
QByteArray data = m_writeQueue.dequeue();
m_socket->write(data);
}
if (m_writeQueue.isEmpty()) {
m_writeTimer->stop();
}
}
void TwitchClient::sendPing()
{
if (m_webSocketHandshakeDone) {
sendWebSocketFrame("PING :tmi.twitch.tv");
}
}
QByteArray TwitchClient::generateWebSocketKey()
{
QByteArray key(16, 0);
for (int i = 0; i < 16; ++i) {
key[i] = QRandomGenerator::global()->generate() & 0xFF;
}
return key.toBase64();
}
void TwitchClient::sendMessage(const QString &message)
{
if (m_channelJoined) {
sendIrcCommand("PRIVMSG", "#" + m_channel.toLower() + " :" + message);
}
}
void TwitchClient::disconnect()
{
if (m_socket) {
if (m_channelJoined) {
sendIrcCommand("PART", "#" + m_channel.toLower());
}
m_socket->disconnectFromHost();
m_writeTimer->stop();
m_pingTimer->stop();
}
}
bool TwitchClient::isConnected() const
{
return m_socket && m_socket->state() == QAbstractSocket::ConnectedState && m_webSocketHandshakeDone;
}
+74
View File
@@ -0,0 +1,74 @@
#ifndef TWITCHCLIENT_H
#define TWITCHCLIENT_H
#include <QObject>
#include <QSslSocket>
#include <QTimer>
#include <QByteArray>
#include <QQueue>
class TwitchClient : public QObject
{
Q_OBJECT
public:
explicit TwitchClient(QObject *parent = nullptr);
~TwitchClient();
// Основные методы
void connectToTwitch(const QString &oauthToken,
const QString &nickname,
const QString &channel);
void sendMessage(const QString &message);
void disconnect();
bool isConnected() const;
signals:
void newMessage(const QString &sender, const QString &channel, const QString &message);
void systemMessage(const QString &message);
void connected();
void disconnected();
void error(const QString &errorMessage);
private slots:
void onConnected();
void onReadyRead();
void onSslErrors(const QList<QSslError> &errors);
void onSocketError(QAbstractSocket::SocketError error);
void onDisconnected();
void sendQueuedData();
void sendPing();
private:
// WebSocket методы
void sendWebSocketFrame(const QByteArray &data, quint8 opcode = 0x01);
QByteArray createWebSocketFrame(const QByteArray &data, quint8 opcode);
void processWebSocketFrame(const QByteArray &frame);
// IRC методы
void sendIrcCommand(const QString &command, const QString &params = "");
void parseIrcMessage(const QString &message);
// Вспомогательные методы
QByteArray generateWebSocketKey();
QByteArray base64Encode(const QByteArray &data);
QByteArray sha1Hash(const QByteArray &data);
private:
QSslSocket *m_socket;
QTimer *m_writeTimer;
QTimer *m_pingTimer;
QQueue<QByteArray> m_writeQueue;
QString m_nickname;
QString m_channel;
QString m_oauthToken;
bool m_webSocketHandshakeDone;
bool m_ircAuthenticated;
bool m_channelJoined;
QByteArray m_receiveBuffer;
};
#endif // TWITCHCLIENT_H
+117
View File
@@ -0,0 +1,117 @@
// TwitchMessage.cpp
#include "twitchmessage.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
TwitchMessage TwitchMessage::parse(const QString &rawMessage)
{
TwitchMessage msg;
// Basic IRC parsing
QString remaining = rawMessage.trimmed();
// Parse tags (start with @)
if (remaining.startsWith('@')) {
int spaceIndex = remaining.indexOf(' ');
if (spaceIndex != -1) {
QString tagsStr = remaining.mid(1, spaceIndex - 1);
remaining = remaining.mid(spaceIndex + 1);
// Parse individual tags
QStringList tags = tagsStr.split(';');
for (const QString &tag : tags) {
int eqIndex = tag.indexOf('=');
if (eqIndex != -1) {
QString key = tag.left(eqIndex);
QString value = tag.mid(eqIndex + 1);
if (key == "badges") {
if (!value.isEmpty()) {
QStringList badgeList = value.split(',');
for (const QString &badge : badgeList) {
QStringList parts = badge.split('/');
if (parts.size() == 2) {
TwitchMessage::Badge b;
b.name = parts[0];
b.version = parts[1].toInt();
msg.badges.append(b);
if (b.name == "vip") {
msg.isVIP = true;
}
}
}
}
} else if (key == "badge-info") {
msg.badgeInfo = value;
} else if (key == "client-nonce") {
msg.clientNonce = value;
} else if (key == "color") {
msg.color = value;
} else if (key == "display-name") {
msg.displayName = value;
} else if (key == "emotes") {
msg.emotes = value;
} else if (key == "first-msg") {
msg.firstMsg = (value == "1");
} else if (key == "flags") {
msg.flags = value;
} else if (key == "id") {
msg.messageId = value;
} else if (key == "mod") {
msg.isMod = (value == "1");
} else if (key == "vip") {
msg.isVIP = (value == "1");
} else if (key == "returning-chatter") {
msg.returningChatter = (value == "1");
} else if (key == "room-id") {
msg.roomId = value.toLongLong();
} else if (key == "subscriber") {
msg.isSubscriber = (value == "1");
} else if (key == "tmi-sent-ts") {
msg.tmiSentTimestamp = value.toLongLong();
} else if (key == "turbo") {
msg.isTurbo = (value == "1");
} else if (key == "user-id") {
msg.userId = value.toLongLong();
} else if (key == "user-type") {
msg.userType = value;
}
}
}
}
}
// Parse prefix (starts with : after tags)
if (remaining.startsWith(':')) {
int spaceIndex = remaining.indexOf(' ');
if (spaceIndex != -1) {
msg.ircPrefix = remaining.mid(1, spaceIndex - 1);
remaining = remaining.mid(spaceIndex + 1);
}
}
// Parse command
int spaceIndex = remaining.indexOf(' ');
if (spaceIndex != -1) {
msg.ircCommand = remaining.left(spaceIndex);
remaining = remaining.mid(spaceIndex + 1);
}
// Parse channel (starts with #)
if (remaining.startsWith('#')) {
spaceIndex = remaining.indexOf(' ');
if (spaceIndex != -1) {
msg.channel = remaining.left(spaceIndex);
remaining = remaining.mid(spaceIndex + 1);
}
}
// Parse message (starts with :)
if (remaining.startsWith(':')) {
msg.message = remaining.mid(1);
}
return msg;
}
+64
View File
@@ -0,0 +1,64 @@
// TwitchMessage.h
#ifndef TWITCHMESSAGE_H
#define TWITCHMESSAGE_H
#include <QString>
#include <QDateTime>
#include <QMap>
struct TwitchMessage
{
// Tags (после @)
struct Badge {
QString name;
int version;
};
QList<Badge> badges;
QString badgeInfo;
QString clientNonce;
QString color;
QString displayName;
QString emotes;
bool firstMsg;
QString flags;
QString messageId;
bool isMod;
bool isVIP;
bool returningChatter;
qint64 roomId;
bool isSubscriber;
qint64 tmiSentTimestamp;
bool isTurbo;
qint64 userId;
QString userType;
// IRC parts
QString ircPrefix;
QString ircCommand;
QString channel;
QString message;
// Timestamp as QDateTime
QDateTime timestamp() const {
return QDateTime::fromMSecsSinceEpoch(tmiSentTimestamp);
}
// Constructor
TwitchMessage()
: firstMsg(false)
, isMod(false)
, isVIP(false)
, returningChatter(false)
, roomId(0)
, isSubscriber(false)
, tmiSentTimestamp(0)
, isTurbo(false)
, userId(0)
{}
// Parse from raw IRC message
static TwitchMessage parse(const QString &rawMessage);
};
#endif // TWITCHMESSAGE_H
+928
View File
@@ -0,0 +1,928 @@
#include "udatabase.h"
#include <QMetaProperty>
#include <QSqlRecord>
#include <QDateTime>
#include <QTableWidget>
#include "qlistwidget.h"
uDataBase::uDataBase(const QString& dbFileName, QObject* parent)
: QObject(parent)
, m_dbFileName(dbFileName)
{
// Создаем уникальное имя подключения
QString connectionName = QString("settings_connection_%1").arg((quintptr)this);
// Открываем базу данных
m_db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
m_db.setDatabaseName(m_dbFileName);
if (!m_db.open()) {
m_lastError = m_db.lastError().text();
qWarning() << "Failed to open database:" << m_lastError;
return;
}
// Инициализируем базу данных
if (!initializeDatabase()) {
qWarning() << "Failed to initialize database";
}
}
uDataBase::~uDataBase()
{
if (m_db.isOpen()) {
QString connectionName = m_db.connectionName();
m_db.close();
m_db = QSqlDatabase(); // сбрасываем объект базы данных
QSqlDatabase::removeDatabase(connectionName); // удаляем соединение
}
}
bool uDataBase::close()
{
if (!isConnected()) {
return true; // Уже закрыто
}
// Сохраняем имя соединения перед закрытием
QString connectionName = m_db.connectionName();
// Закрываем базу данных
m_db.close();
// Очищаем объект базы данных
m_db = QSqlDatabase();
// Удаляем соединение из пула Qt
QSqlDatabase::removeDatabase(connectionName);
return true;
}
bool uDataBase::initializeDatabase()
{
// Создаем таблицу настроек, если она не существует
QString createTableQuery =
"CREATE TABLE IF NOT EXISTS params ("
"name TEXT PRIMARY KEY,"
"value TEXT"
")";
QSqlQuery query(m_db);
if (!query.exec(createTableQuery)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to create table:" << m_lastError;
return false;
}
return true;
}
QString uDataBase::readSetting(const QString& aName, const QString& aDefault)
{
if (!m_db.isOpen()) {
qWarning() << "Database is not open";
return aDefault;
}
QSqlQuery query(m_db);
query.prepare("SELECT value FROM params WHERE name = :name");
query.bindValue(":name", aName);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to read setting" << aName << ":" << m_lastError;
return aDefault;
}
if (query.next()) {
return query.value(0).toString();
}
// Если настройка не найдена, возвращаем значение по умолчанию
return aDefault;
}
bool uDataBase::writeSetting(const QString& aName, const QString& aValue)
{
if (!m_db.isOpen()) {
qWarning() << "Database is not open";
return false;
}
// Используем INSERT OR REPLACE для обновления существующей записи или создания новой
QSqlQuery query(m_db);
query.prepare(
"INSERT OR REPLACE INTO params (name, value) "
"VALUES (:name, :value)"
);
query.bindValue(":name", aName);
query.bindValue(":value", aValue);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to write setting" << aName << ":" << m_lastError;
return false;
}
return true;
}
// В классе uDataBase добавьте эти методы:
/**
* @brief Специальная загрузка для таблицы таймеров с сохранением состояния
* @param tableWidget Таблица для загрузки
* @param timers Список таймеров для заполнения
* @return true если успешно
*/
bool uDataBase::LoadTimers(QTableWidget *tableWidget, QList<TimerInfo> &timers)
{
if (!tableWidget) {
m_lastError = "Table widget is null";
qWarning() << m_lastError;
return false;
}
// Получаем имя таблицы из objectName виджета
QString tableName = tableWidget->objectName();
if (tableName.isEmpty()) {
m_lastError = "Table widget has no object name";
qWarning() << m_lastError;
return false;
}
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Очищаем таблицу и список таймеров
tableWidget->setRowCount(0);
timers.clear();
// Выполняем SQL запрос
QSqlQuery query(m_db);
QString sql = QString("SELECT * FROM %1 ORDER BY id").arg(tableName);
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Query failed:" << m_lastError;
qWarning() << "SQL:" << sql;
return false;
}
int rowCount = 0;
while (query.next()) {
// Создаем объект таймера
TimerInfo timer;
timer.id = query.value("id").toInt();
timer.message = query.value("message").toString();
timer.interval = query.value("interval").toInt();
timer.isActive = query.value("is_active").toBool();
timer.isAnnouncement = query.value("is_announcement").toBool();
timer.timer = nullptr;
// Добавляем в список
timers.append(timer);
// Добавляем строку в таблицу
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
// Чекбокс "Вкл"
QTableWidgetItem* enabledItem = new QTableWidgetItem();
enabledItem->setCheckState(timer.isActive ? Qt::Checked : Qt::Unchecked);
tableWidget->setItem(row, 0, enabledItem);
// Сообщение
QTableWidgetItem* messageItem = new QTableWidgetItem(timer.message);
tableWidget->setItem(row, 1, messageItem);
// Интервал
QTableWidgetItem* intervalItem = new QTableWidgetItem(QString::number(timer.interval));
tableWidget->setItem(row, 2, intervalItem);
// Чекбокс "О"
QTableWidgetItem* announcementItem = new QTableWidgetItem();
announcementItem->setCheckState(timer.isAnnouncement ? Qt::Checked : Qt::Unchecked);
tableWidget->setItem(row, 3, announcementItem);
rowCount++;
}
return true;
}
/**
* @brief Специальное сохранение для таблицы таймеров
* @param tableWidget Таблица для сохранения
* @param timers Список таймеров
* @return true если успешно
*/
bool uDataBase::SaveTimers(QTableWidget *tableWidget, const QList<TimerInfo> &timers)
{
if (!tableWidget) {
m_lastError = "Table widget is null";
qWarning() << m_lastError;
return false;
}
// Получаем имя таблицы из objectName виджета
QString tableName = tableWidget->objectName();
if (tableName.isEmpty()) {
m_lastError = "Table widget has no object name";
qWarning() << m_lastError;
return false;
}
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Создаем таблицу если не существует
QString createTableQuery = QString(
"CREATE TABLE IF NOT EXISTS %1 ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"message TEXT NOT NULL,"
"interval INTEGER NOT NULL,"
"is_active BOOLEAN DEFAULT 1,"
"is_announcement BOOLEAN DEFAULT 0"
")"
).arg(tableName);
QSqlQuery createQuery(m_db);
if (!createQuery.exec(createTableQuery)) {
m_lastError = createQuery.lastError().text();
qWarning() << "Failed to create table:" << m_lastError;
return false;
}
// Начинаем транзакцию
if (!m_db.transaction()) {
m_lastError = m_db.lastError().text();
qWarning() << "Failed to start transaction:" << m_lastError;
return false;
}
// Очищаем таблицу
QSqlQuery clearQuery(m_db);
QString clearSql = QString("DELETE FROM %1").arg(tableName);
if (!clearQuery.exec(clearSql)) {
m_lastError = clearQuery.lastError().text();
qWarning() << "Failed to clear table:" << m_lastError;
m_db.rollback();
return false;
}
// Сохраняем таймеры
QSqlQuery insertQuery(m_db);
insertQuery.prepare(QString(
"INSERT INTO %1 (message, interval, is_active, is_announcement) "
"VALUES (:message, :interval, :is_active, :is_announcement)"
).arg(tableName));
int savedRows = 0;
for (const TimerInfo &timer : timers) {
insertQuery.bindValue(":message", timer.message);
insertQuery.bindValue(":interval", timer.interval);
insertQuery.bindValue(":is_active", timer.isActive);
insertQuery.bindValue(":is_announcement", timer.isAnnouncement);
if (!insertQuery.exec()) {
m_lastError = insertQuery.lastError().text();
qWarning() << "Failed to insert timer:" << m_lastError;
m_db.rollback();
return false;
}
savedRows++;
}
// Завершаем транзакцию
if (!m_db.commit()) {
m_lastError = m_db.lastError().text();
qWarning() << "Failed to commit transaction:" << m_lastError;
m_db.rollback();
return false;
}
qDebug() << "Saved" << savedRows << "timers to database";
return true;
}
bool uDataBase::LoadTableWidget(QTableWidget *tableWidget)
{
if (!tableWidget) {
m_lastError = "Table widget is null";
qWarning() << m_lastError;
return false;
}
// Получаем имя таблицы из objectName виджета
QString tableName = tableWidget->objectName();
if (tableName.isEmpty()) {
m_lastError = "Table widget has no object name";
qWarning() << m_lastError;
return false;
}
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Очищаем таблицу (но сохраняем заголовки)
tableWidget->setRowCount(0);
// Выполняем SQL запрос
QSqlQuery query(m_db);
QString sql = QString("SELECT * FROM %1 ORDER BY id").arg(tableName);
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Query failed:" << m_lastError;
qWarning() << "SQL:" << sql;
return false;
}
// Получаем информацию о колонках в результате запроса
QSqlRecord record = query.record();
int dbColumnCount = record.count();
// Определяем сколько колонок нам нужно в QTableWidget
int widgetColumnCount = tableWidget->columnCount();
int columnsToLoad = qMin(dbColumnCount - 1, widgetColumnCount); // -1 потому что пропускаем id
int rowCount = 0;
while (query.next()) {
// Добавляем новую строку
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
// Заполняем ячейки (начиная с col0, пропускаем id)
for (int col = 0; col < columnsToLoad; col++) {
QString columnName = QString("col%1").arg(col);
QVariant value = query.value(columnName);
QTableWidgetItem *item = new QTableWidgetItem();
if (value.isNull()) {
item->setText("");
} else {
item->setText(value.toString());
}
// Устанавливаем выравнивание по умолчанию
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
tableWidget->setItem(row, col, item);
}
rowCount++;
}
return true;
}
bool uDataBase::SaveTableWidget(QTableWidget *tableWidget)
{
if (!tableWidget) {
m_lastError = "Table widget is null";
qWarning() << m_lastError;
return false;
}
// Получаем имя таблицы из objectName виджета
QString tableName = tableWidget->objectName();
if (tableName.isEmpty()) {
m_lastError = "Table widget has no object name";
qWarning() << m_lastError;
return false;
}
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Проверяем существует ли таблица
if (!tableExists(tableName)) {
// Создаем таблицу с нужным количеством колонок
if (!createTableForWidget(tableName, tableWidget->columnCount())) {
return false;
}
} else {
// Очищаем существующую таблицу
if (!clearTable(tableName)) {
return false;
}
}
// Сохраняем данные из таблицы
QSqlQuery query(m_db);
// Начинаем транзакцию для быстрой вставки
if (!m_db.transaction()) {
m_lastError = m_db.lastError().text();
qWarning() << "Failed to start transaction:" << m_lastError;
return false;
}
int rowCount = tableWidget->rowCount();
int colCount = tableWidget->columnCount();
if (colCount == 0) {
m_lastError = "Table widget has no columns";
qWarning() << m_lastError;
m_db.rollback();
return false;
}
// Подготавливаем SQL запрос для вставки
QStringList columnNames;
QStringList valuePlaceholders;
// Добавляем id (автоинкремент)
columnNames << "id";
valuePlaceholders << "NULL";
// Добавляем col0, col1, col2 и т.д.
for (int col = 0; col < colCount; col++) {
columnNames << QString("col%1").arg(col);
valuePlaceholders << QString(":col%1").arg(col);
}
QString sql = QString("INSERT INTO %1 (%2) VALUES (%3)")
.arg(tableName)
.arg(columnNames.join(", "))
.arg(valuePlaceholders.join(", "));
query.prepare(sql);
int savedRows = 0;
for (int row = 0; row < rowCount; row++) {
// Пропускаем пустые строки (если все ячейки пустые)
bool isEmptyRow = true;
for (int col = 0; col < colCount; col++) {
QTableWidgetItem *item = tableWidget->item(row, col);
if (item && !item->text().trimmed().isEmpty()) {
isEmptyRow = false;
break;
}
}
if (isEmptyRow) {
continue; // Пропускаем полностью пустые строки
}
// Биндим значения для каждой колонки
for (int col = 0; col < colCount; col++) {
QString paramName = QString(":col%1").arg(col);
QTableWidgetItem *item = tableWidget->item(row, col);
if (item && !item->text().isEmpty()) {
query.bindValue(paramName, item->text());
} else {
query.bindValue(paramName, QVariant(QVariant::String));
}
}
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to insert row:" << m_lastError;
qWarning() << "SQL:" << sql;
m_db.rollback();
return false;
}
savedRows++;
}
// Завершаем транзакцию
if (!m_db.commit()) {
m_lastError = m_db.lastError().text();
qWarning() << "Failed to commit transaction:" << m_lastError;
m_db.rollback();
return false;
}
return true;
}
// Вспомогательные методы
bool uDataBase::tableExists(const QString &tableName)
{
QSqlQuery query(m_db);
QString sql = QString("SELECT name FROM sqlite_master WHERE type='table' AND name='%1'")
.arg(tableName);
if (query.exec(sql) && query.next()) {
return true;
}
return false;
}
bool uDataBase::createTableForWidget(const QString &tableName, int columnCount)
{
if (columnCount <= 0) {
m_lastError = "Invalid column count";
return false;
}
QSqlQuery query(m_db);
// Создаем SQL для создания таблицы
QStringList columns;
columns << "id INTEGER PRIMARY KEY AUTOINCREMENT";
for (int i = 0; i < columnCount; i++) {
columns << QString("col%1 TEXT").arg(i);
}
QString sql = QString("CREATE TABLE %1 (%2)").arg(tableName).arg(columns.join(", "));
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to create table:" << m_lastError;
qWarning() << "SQL:" << sql;
return false;
}
return true;
}
bool uDataBase::clearTable(const QString &tableName)
{
QSqlQuery query(m_db);
QString sql = QString("DELETE FROM %1").arg(tableName);
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to clear table:" << m_lastError;
return false;
}
// Сбрасываем автоинкремент
sql = QString("DELETE FROM sqlite_sequence WHERE name='%1'").arg(tableName);
query.exec(sql); // Игнорируем ошибки, т.к. может не быть sqlite_sequence
return true;
}
bool uDataBase::LoadRandomGroups(QListWidget *listWidget)
{
if (!listWidget) {
m_lastError = "List widget is null";
qWarning() << m_lastError;
return false;
}
listWidget->clear();
m_lastError.clear();
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Проверяем существование таблицы
if (!tableExists("GroupResponse")) {
qWarning() << "Table GroupResponse doesn't exist";
// Можно создать таблицу автоматически
if (!createGroupResponseTable()) {
return false;
}
}
// Выполняем SQL запрос для получения уникальных имен
QSqlQuery query(m_db);
QString sql = "SELECT DISTINCT Name FROM GroupResponse ORDER BY Name";
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Query failed:" << m_lastError;
qWarning() << "SQL:" << sql;
return false;
}
int count = 0;
while (query.next()) {
QString groupName = query.value(0).toString();
if (!groupName.isEmpty()) {
listWidget->addItem(groupName);
count++;
}
}
return true;
}
// Метод для загрузки ответов конкретной группы
bool uDataBase::LoadRandomResponses(const QString &groupName, QListWidget *listWidget)
{
if (!listWidget) {
m_lastError = "List widget is null";
qWarning() << m_lastError;
return false;
}
if (groupName.isEmpty()) {
m_lastError = "Group name is empty";
qWarning() << m_lastError;
return false;
}
listWidget->clear();
m_lastError.clear();
// Проверяем соединение с БД
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
qWarning() << m_lastError;
return false;
}
// Проверяем существование таблицы
if (!tableExists("GroupResponse")) {
m_lastError = "Table GroupResponse doesn't exist";
qWarning() << m_lastError;
return false;
}
// Выполняем SQL запрос для получения ответов группы
QSqlQuery query(m_db);
query.prepare("SELECT Response FROM GroupResponse WHERE Name = :name ORDER BY id");
query.bindValue(":name", groupName);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Query failed:" << m_lastError;
return false;
}
int count = 0;
while (query.next()) {
QString response = query.value(0).toString();
listWidget->addItem(response);
count++;
}
return true;
}
// Метод для получения случайного ответа из группы
QString uDataBase::GetRandomResponse(const QString &groupName)
{
if (groupName.isEmpty()) {
qWarning() << "Group name is empty";
return QString();
}
if (!m_db.isOpen()) {
qWarning() << "Database is not open";
return QString();
}
if (!tableExists("GroupResponse")) {
qWarning() << "Table GroupResponse doesn't exist";
return QString();
}
QSqlQuery query(m_db);
query.prepare("SELECT Response FROM GroupResponse WHERE Name = :name ORDER BY RANDOM() LIMIT 1");
query.bindValue(":name", groupName);
if (!query.exec()) {
qWarning() << "Query failed for group:" << groupName << "Error:" << query.lastError().text();
return QString();
}
if (!query.next()) {
qWarning() << "No responses found for group:" << groupName;
return QString();
}
return query.value(0).toString();
}
// Метод для обработки текста с заменой {{grN}}
QString uDataBase::ProcessResponseTemplate(const QString &templateText)
{
QRegularExpression re("\\{\\{(.+?)\\}\\}");
QRegularExpressionMatchIterator i = re.globalMatch(templateText);
QString result = templateText;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString fullMatch = match.captured(0); // {{ANY_GROUP_NAME}}
QString groupName = match.captured(1).trimmed(); // ANY_GROUP_NAME
if (groupName.isEmpty()) {
continue; // Пропускаем пустые имена групп
}
QString replacement = GetRandomResponse(groupName);
if (replacement.isEmpty()) {
qWarning() << "No response found for group:" << groupName;
// Можно оставить оригинальный текст или заменить на что-то другое
replacement = QString("[Группа '%1' не найдена]").arg(groupName);
}
result.replace(fullMatch, replacement);
}
return result;
}
// Методы для добавления/удаления групп и ответов
bool uDataBase::AddGroupResponse(const QString &groupName, const QString &response)
{
if (groupName.isEmpty() || response.isEmpty()) {
m_lastError = "Group name or response is empty";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
// Создаем таблицу если она не существует
if (!tableExists("GroupResponse")) {
if (!createGroupResponseTable()) {
return false;
}
}
QSqlQuery query(m_db);
query.prepare("INSERT INTO GroupResponse (Name, Response) VALUES (:name, :response)");
query.bindValue(":name", groupName);
query.bindValue(":response", response);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to add response:" << m_lastError;
return false;
}
return true;
}
bool uDataBase::DeleteGroup(const QString &groupName)
{
if (groupName.isEmpty()) {
m_lastError = "Group name is empty";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
QSqlQuery query(m_db);
query.prepare("DELETE FROM GroupResponse WHERE Name = :name");
query.bindValue(":name", groupName);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to delete group:" << m_lastError;
return false;
}
return true;
}
bool uDataBase::DeleteResponse(const QString &groupName, const QString &ResponseName)
{
if (groupName.isEmpty()) {
m_lastError = "Group name is empty";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
// Получаем ID ответа по индексу в группе
QSqlQuery query(m_db);
// Удаляем ответ по ID
query.prepare("DELETE FROM GroupResponse WHERE Name = :name and Response = :response");
query.bindValue(":name", groupName);
query.bindValue(":response", ResponseName);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to delete response:" << m_lastError;
return false;
}
return true;
}
// Вспомогательные методы
bool uDataBase::createGroupResponseTable()
{
QSqlQuery query(m_db);
QString sql =
"CREATE TABLE IF NOT EXISTS GroupResponse ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" Name TEXT NOT NULL,"
" Response TEXT NOT NULL"
")";
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to create GroupResponse table:" << m_lastError;
return false;
}
// Создаем индекс для быстрого поиска по имени группы
sql = "CREATE INDEX IF NOT EXISTS idx_groupresponse_name ON GroupResponse (Name)";
query.exec(sql); // Игнорируем ошибки, если индекс уже существует
return true;
}
// Метод для сохранения списка ответов группы (заменяет все существующие)
bool uDataBase::SaveGroupResponses(const QString &groupName, const QStringList &responses)
{
if (groupName.isEmpty()) {
m_lastError = "Group name is empty";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
// Начинаем транзакцию
if (!m_db.transaction()) {
m_lastError = m_db.lastError().text();
return false;
}
// Удаляем старые ответы группы
QSqlQuery query(m_db);
query.prepare("DELETE FROM GroupResponse WHERE Name = :name");
query.bindValue(":name", groupName);
if (!query.exec()) {
m_lastError = query.lastError().text();
m_db.rollback();
return false;
}
// Добавляем новые ответы
query.prepare("INSERT INTO GroupResponse (Name, Response) VALUES (:name, :response)");
foreach (const QString &response, responses) {
if (!response.trimmed().isEmpty()) {
query.bindValue(":name", groupName);
query.bindValue(":response", response);
if (!query.exec()) {
m_lastError = query.lastError().text();
m_db.rollback();
return false;
}
}
}
if (!m_db.commit()) {
m_lastError = m_db.lastError().text();
m_db.rollback();
return false;
}
return true;
}
bool uDataBase::isConnected() const
{
return m_db.isOpen();
}
QString uDataBase::lastError() const
{
return m_lastError;
}
+71
View File
@@ -0,0 +1,71 @@
#ifndef UDATABASE_H
#define UDATABASE_H
#include "qlistwidget.h"
#include <QObject>
#include <QString>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QVariant>
#include <QDebug>
#include <QFile>
#include <QTableWidget>
#include "timerinfo.h"
class uDataBase : public QObject
{
Q_OBJECT
// Информация о таймере
public:
// Конструктор с указанием файла базы данных
explicit uDataBase(const QString& dbFileName, QObject* parent = nullptr);
// Деструктор
~uDataBase();
// Чтение настройки
QString readSetting(const QString& aName, const QString& aDefault = "");
// Запись настройки
bool writeSetting(const QString& aName, const QString& aValue);
bool LoadTableWidget(QTableWidget *tableWidget);
bool clearTable(const QString &tableName);
bool createTableForWidget(const QString &tableName, int columnCount);
bool tableExists(const QString &tableName);
bool SaveTableWidget(QTableWidget *tableWidget);
bool SaveTimers(QTableWidget *tableWidget, const QList<TimerInfo> &timers);
bool LoadTimers(QTableWidget *tableWidget, QList<TimerInfo> &timers);
bool SaveGroupResponses(const QString &groupName, const QStringList &responses);
bool createGroupResponseTable();
bool DeleteResponse(const QString &groupName, const QString &ResponseName);
bool DeleteGroup(const QString &groupName);
bool AddGroupResponse(const QString &groupName, const QString &response);
QString ProcessResponseTemplate(const QString &templateText);
QString GetRandomResponse(const QString &groupName);
bool LoadRandomResponses(const QString &groupName, QListWidget *listWidget);
bool LoadRandomGroups(QListWidget *listWidget);
QString ProcessResponseTemplateRecursive(const QString &templateText, int depth);
// Проверка подключения к БД
bool isConnected() const;
bool close();
// Получение последней ошибки
QString lastError() const;
private:
// Инициализация базы данных (создание таблицы, если нужно)
bool initializeDatabase();
QSqlDatabase m_db;
QString m_dbFileName;
QString m_lastError;
};
#endif // DBSETTINGS_H
+3672
View File
File diff suppressed because it is too large Load Diff
+485
View File
@@ -0,0 +1,485 @@
#ifndef UGENERAL_H
#define UGENERAL_H
// ============================================================================
// ВКЛЮЧЕНИЕ ЗАГОЛОВОЧНЫХ ФАЙЛОВ
// ============================================================================
#include <QMainWindow>
#include <tauth.h>
#include <ulink.h>
#include <udatabase.h>
#include <soundmanager.h>
#include "fcreatechat.h"
#include "fcreatenotify.h"
#include "neuralnetworkmanager.h"
#include "ttw_api.h"
#include "user_manager.h"
#include "webserverchat.h"
#include "webservernotify.h"
#include "websocketclient.h"
#include "userwidget.h"
#include <QTimer>
#include "emoteprovider.h"
#include "timerinfo.h"
QT_BEGIN_NAMESPACE
namespace Ui { class uGeneral; }
QT_END_NAMESPACE
// Структура для отправки сообщений
struct MessageData {
QString nickname;
QString content;
QString eventType; // Для уведомлений
QVariantMap styleSettings; // Настройки стилей
};
// ============================================================================
// КЛАСС UGeneral - ГЛАВНОЕ ОКНО ПРИЛОЖЕНИЯ
// ============================================================================
class uGeneral : public QMainWindow
{
Q_OBJECT
protected:
// Переопределение обработчика закрытия окна
void closeEvent(QCloseEvent *event) override;
public:
// Конструктор и деструктор
uGeneral(QWidget *parent = nullptr);
~uGeneral();
// ========================================================================
// СТРУКТУРЫ ДАННЫХ
// ========================================================================
// Данные для авторизации Twitch
struct TTTVAuth {
QString botName;
QString botToken;
QString strimerToken;
QString ClientID;
QString channelName;
};
// Структура для хранения пар команд (вопрос-ответ)
struct RListCommands {
QString R1;
QString R2;
RListCommands() = default;
RListCommands(const QString &r1, const QString &r2) : R1(r1), R2(r2) {}
};
// Запись лога
struct LogEntry {
QDateTime time;
QString module;
QString method;
QString message;
int code;
QString msgType;
QColor color;
};
// ========================================================================
// ПУБЛИЧНЫЕ ЧЛЕНЫ КЛАССА
// ========================================================================
QList<RListCommands> TCommends; // Список команд
BTTVProvider bttvProvider;
SevenTVProvider sevenTVProvider;
uDataBase *db; // Указатель на базу данных
SoundManager *soundManager; // Менеджер звуков
UserManager* getUserManager(); // Получение менеджера пользователей
TTwAPI *twitchAPI; // API для работы с Twitch
// Методы логирования и работы с командами
void toLog(QString aModule, QString aMethod, QString aMessage, int aCode);
void toCommands(QString command, QString response);
private slots:
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ТАЙМЕРАМИ
// ========================================================================
void on_btnTimerAdd_clicked();
void on_btnTimerEdit_clicked();
void on_btnTimerDelete_clicked();
void on_btnTimerTest_clicked();
void on_sgTimers_cellClicked(int row, int column);
void on_sgTimers_cellDoubleClicked(int row, int column);
void onTimerTimeout(int timerId);
// ========================================================================
// СЛОТЫ ДЛЯ АВТОРИЗАЦИИ И НАСТРОЕК
// ========================================================================
void on_btnGetTockenBot_clicked(); // Получение токена бота
void on_btnOpenFolderSettings_clicked(); // Открытие папки настроек
void on_btnGetTokenStreamer_clicked(); // Получение токена стримера
void on_btnDAGetCode_clicked(); // Получение кода DA
// Обработчики получения токенов
void onTokenReceived(const QString &token);
void onTokenReceived2(const QString &token);
void onTokenReceived3(const QString &token);
void onAuthError(const QString &error); // Ошибка авторизации
// Работа с настройками
void loadSettings(); // Загрузка настроек
void on_btnImportSettings_clicked(); // Импорт настроек
void on_btnExportSettings_clicked(); // Экспорт настроек
void on_btnSaveSettings_clicked(); // Сохранение настроек
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С УВЕДОМЛЕНИЯМИ
// ========================================================================
void on_btnNotifyCheck_clicked(); // Проверка обычных уведомлений
void on_btnNotifyCheckMod_clicked(); // Проверка уведомлений для модераторов
void on_btnNotifyCheckVip_clicked(); // Проверка уведомлений для VIP
void on_btnNotifyCheckSub_clicked(); // Проверка уведомлений для подписчиков
void on_btnNotifyOpen_clicked(); // Открытие обычных уведомлений
void on_btnNotifyOpenMod_clicked(); // Открытие уведомлений для модераторов
void on_btnNotifyOpenVip_clicked(); // Открытие уведомлений для VIP
void on_btnNotifyOpenSub_clicked(); // Открытие уведомлений для подписчиков
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С WEBSOCKET (TWITCH)
// ========================================================================
void onStatus(const QString &statusText, int statusCode);
void onDisconnected(const QString &reason);
void onJoined(const QString &nickname);
void onMessage(const QString &message);
void handleNewMessage(const QString &message);
void handleError(const QString &errorMessage);
void handleConnected();
void handleDisconnected();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ
// ========================================================================
void execCommand(const QString &sender, const QString &message);
QString ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText);
QString ResponsParserRandoms(const QString &inMess);
QString ResponsParserSounds(const QString &inMess);
QString ResponsParserText(const QString &inMess);
QString ResponsParserAI(const QString &response, const QString &pall);
QString responsParserAPI(const QString &inMess, const QString &adName);
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ
// ========================================================================
void onAIResponseReceived(const QString &response);
void onAIErrorOccurred(const QString &error);
void onNeuralResponse(const QString &response);
void onNeuralError(const QString &error);
void sendRequestToAI(const QString &q);
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С TWITCH API
// ========================================================================
void onApiError(const QString &method, const QString &error);
void onTokenExpired(const QString &error);
void onRateLimit();
void initTwitchAPI();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ИНТЕРФЕЙСОМ
// ========================================================================
// Радиокнопки выбора режима
void on_RBCustom_pressed();
void on_rbGC_clicked();
void on_rbDS_clicked();
void on_rbCG_clicked();
// Кнопки управления
void on_pushButton_2_clicked();
void on_pushButton_clicked();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ЛОГАМИ
// ========================================================================
void applyLogFilter();
void addLogToTable(const LogEntry &entry, int row);
bool shouldShowLogEntry(int code);
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ТАБЛИЦАМИ И ГРИДАМИ
// ========================================================================
void on_sgCommands_cellDoubleClicked(int row, int column);
void onSoundGridDoubleClicked();
void onFilesGridDoubleClicked();
void onNeiroGridDoubleClicked();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С РАНДОМНЫМИ ОТВЕТАМИ
// ========================================================================
void on_lbRandomGroup_itemClicked(QListWidgetItem *item);
void on_lbRandomGroup_doubleClicked(const QModelIndex &index);
void on_lbRandomRespons_itemDoubleClicked(QListWidgetItem *item);
void on_btnRandAdd_clicked();
void on_btnRandDel_clicked();
void on_btnRandomAdd_clicked();
void on_btnRandomDel_clicked();
void on_btnRmGroup_clicked();
void on_sgRandomInt_cellClicked(int row, int column);
void on_sgRandomInt_cellDoubleClicked(int row, int column);
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ПОЛЬЗОВАТЕЛЯМИ
// ========================================================================
void on_btnAddUserName_clicked();
void on_btnGetDateFollow_clicked();
void on_btnGetAgeAccaunt_clicked();
void on_btnRandomUserName_clicked();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ ЧАТА
// ========================================================================
void on_btnAddCommand_clicked();
void on_btnRmCommand_clicked();
void on_btnEdtCommand_clicked();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С КАНАЛОМ И ДОП. ФУНКЦИЯМИ
// ========================================================================
void on_btnGetChannelStat_clicked();
void on_btnGPT_clicked();
void on_btnAIPic_clicked();
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ТЕМАМИ
// ========================================================================
void on_cbTheme_currentIndexChanged(int index);
// ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ВЕБ СЕРВЕРАМИ
// ========================================================================
void on_btnWSCreateChat_clicked();
void on_btnWSCreateNotify_clicked();
void onChatServerUpdated(HttpServerChat *server, const QString &name);
void on_sgWebServers_cellDoubleClicked(int row, int column);
void onChatServerCreated(HttpServerChat *server, const QString &name);
void onNotifyServerCreated(HttpServer *server, const QString &name);
// Методы для внешнего добавления уведомлений и сообщений
void addNotification(const QString &nickname = "", double amount = 0, const QString &type = "donation");
QString processTwitchMessage(const TwitchMessage &msg);
QString replaceTwitchEmotes(const QString &emotesData, const QString &message);
QString replaceCustomEmotes(const QString &message);
void sendMessageToServer(HttpServerChat *server, const QString &nickname,
const QString &message);
// Обновленный метод addChatMessage
void addChatMessage(const QString &nickname,
const QString &message);
void on_www_currentChanged(int index);
void on_edtBotName_selectionChanged();
void on_edtBotName_editingFinished();
void on_edtBotToken_textEdited(const QString &arg1);
void on_edtBotToken_editingFinished();
void on_edtBotTokenStreamer_editingFinished();
void on_edtBotClientID_editingFinished();
void on_edtChannel_editingFinished();
void on_edtDAClientID_editingFinished();
void on_edtDAClientSecret_editingFinished();
void on_edtDARedirectURL_editingFinished();
void on_edtDACode_editingFinished();
void on_cbDAAutoLogin_checkStateChanged(const Qt::CheckState &arg1);
void on_cbDAAutoLogin_stateChanged(int arg1);
void on_edtGPTPrefix_editingFinished();
void on_edtAIP1_editingFinished();
void on_edtAIP2_editingFinished();
void on_edtAIP3_editingFinished();
void on_edtKandiKey_editingFinished();
void on_edtKandiSecret_editingFinished();
void on_cbOllama_stateChanged(int arg1);
void on_tbNotifyVolume_valueChanged(int value);
void on_edtNotifyFileName_editingFinished();
void on_edtNotifyFileNameMod_editingFinished();
void on_edtNotifyFileNameVip_editingFinished();
void on_edtNotifyFileNameSub_editingFinished();
void on_chEnNotify_stateChanged(int arg1);
void on_chEnNotifyMod_stateChanged(int arg1);
void on_chEnNotifyVip_stateChanged(int arg1);
void on_chEnNotifySub_stateChanged(int arg1);
void on_tbNotifyVolume_sliderPressed();
void on_tbNotifyVolume_sliderReleased();
void on_tbNotifyVolumeMod_sliderReleased();
void on_tbNotifyVolumeVip_sliderReleased();
void on_tbNotifyVolumeSub_sliderReleased();
void on_cbNotifyFileAgain1_stateChanged(int arg1);
void on_cbNotifyFileAgain2_stateChanged(int arg1);
void on_cbNotifyFileAgain3_stateChanged(int arg1);
void on_btnThemesFolder_clicked();
public slots:
// Установка статуса подключения к Twitch
void setTwitchConnected(bool connected);
private:
// ========================================================================
// ПРИВАТНЫЕ ЧЛЕНЫ КЛАССА
// ========================================================================
Ui::uGeneral *ui; // Указатель на интерфейс
// Объекты для авторизации
TAuth *m_authBot; // Авторизация бота
TAuth *m_authStreamer; // Авторизация стримера
TAuth *m_authDA; // Авторизация DA
uLink *fLinkForm; // Форма ссылок
TTTVAuth *TTVAuth; // Данные авторизации Twitch
UserManager *m_userManager; // Менеджер пользователей
QList<LogEntry> allLogs; // Все записи логов
WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch
UserWidget* m_userWidget; // Виджет пользователя
NeuralNetworkManager *nnManager; // Менеджер нейронных сетей
QList<TimerInfo> m_timers; // Список таймеров
int m_nextTimerId = 1; // Следующий ID таймера
bool m_isTwitchConnected = false; // Статус подключения к Twitch
// Менеджеры веб-серверов
QList<HttpServer*> m_notificationServers;
QList<HttpServerChat*> m_chatServers;
// Для связи с окнами создания
FCreateNotify *m_createNotifyDialog;
FCreateChat *m_createChatDialog;
QVector<ChatBadge> m_chatBadges;
// Методы для обработки бейджей и форматирования ника
QString formatNicknameWithBadges(const TwitchMessage &msg);
QString getBadgesHTML(const TwitchMessage &msg);
QString getBadgeUrl(const QString &setId, int versionId);
QVector<QIcon> tabIcons;
// Структура для замены эмодзи
struct EmoteReplacement {
int start;
int length;
QString html;
};
// Добавление серверов в списки и таблицу
void addNotificationServer(HttpServer *server, const QString &name);
void addChatServer(HttpServerChat *server, const QString &name);
void addServerToTable(const QString &name, const QString &type, quint16 port,
const QString &url, QObject *serverObj);
// Вспомогательные методы
int findNotificationServerRow(HttpServer *server);
int findChatServerRow(HttpServerChat *server);
QString generateServerId() const;
// Текущие настройки для формы
QVariantMap m_currentSettings;
// ========================================================================
// ПРИВАТНЫЕ МЕТОДЫ
// ========================================================================
// Метод для загрузки списка QSS файлов
void loadQssFiles();
// Метод для применения QSS темы
void applyStyleSheet(const QString &filename);
QString getUserFilePath(const QString &type, const QString &fileName) const;
QString getUserDataPath() const;
QString getSystemPath() const;
void setupButtonIcons();
void loadStylesFromDirectory(const QDir &stylesDir, const QString &category);
void copyDefaultFiles();
void initializeAppDataStructure();
// Настройка OAuth для Twitch
void setupTwitchOAuth();
// Инициализация конфигурации ИИ
void initAIConfig();
void updateAIConfig();
QString processAITag(const QString &input, const QString &sender);
// Работа с таймерами
void updateTimerTable();
void startTimer(TimerInfo& timerInfo);
void stopTimer(TimerInfo& timerInfo);
void sendTimedMessage(const TimerInfo& timerInfo);
// Воспроизведение уведомлений
void playNotify(const bool &isMod, const bool &isVip, const bool &isSub);
// Инициализация базы данных
void initializeDatabase();
// Настройка пользовательского интерфейса
void setupUI();
// Настройка таблиц интерфейса
void setupTables();
// Инициализация менеджеров
void initializeManagers();
// Настройка компонентов Twitch
void setupTwitchComponents();
// Настройка виджета пользователей
void setupUserWidget();
// Настройка обработчиков авторизации
void setupAuthHandlers();
// Инициализация нейронной сети
void initializeNeuralNetwork();
// Загрузка эмодзи
void loadEmoties();
void loadChatBadges();
// Обработка эмодзи
void processEmotes(QString &message);
};
#endif // UGENERAL_H
+1900
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
#include "ulink.h"
#include "ui_ulink.h"
uLink::uLink(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::uLink)
{
ui->setupUi(this);
setWindowTitle("Получение токена бота");
}
uLink::~uLink()
{
delete ui;
}
void uLink::setLinkText(const QString &text)
{
ui->mLink->setText(text);
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef ULINK_H
#define ULINK_H
#include <QMainWindow>
namespace Ui {
class uLink;
}
class uLink : public QMainWindow
{
Q_OBJECT
public:
explicit uLink(QWidget *parent = nullptr);
~uLink();
void setLinkText(const QString &text);
private:
Ui::uLink *ui;
};
#endif // ULINK_H
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>uLink</class>
<widget class="QWidget" name="uLink">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Откройте ссылку в браузере бота</string>
</property>
</widget>
<widget class="QTextEdit" name="mLink">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>381</width>
<height>261</height>
</rect>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+43
View File
@@ -0,0 +1,43 @@
#include "user.h"
#include "twitchmessage.h"
#include <QStringList>
void User::updateFromTwitchMessage(const TwitchMessage &msg)
{
// Базовые данные
if (!msg.displayName.isEmpty()) {
displayName = msg.displayName;
}
if (!msg.color.isEmpty()) {
color = msg.color;
}
if (msg.userId > 0) {
id = QString::number(msg.userId);
login = msg.displayName.toLower(); // Логин обычно в нижнем регистре
}
// Статусы
isModerator = msg.isMod;
isVIP = msg.isVIP;
isSubscriber = msg.isSubscriber;
isTurbo = msg.isTurbo;
returningChatter = msg.returningChatter;
if (msg.firstMsg) {
firstMsg = true;
}
// Бейджи
badges.clear();
for (const auto &badge : msg.badges) {
badges.append(UserBadge(badge.name, badge.version));
}
// Время последнего сообщения
lastMessageTime = msg.timestamp();
// Увеличиваем счетчик сообщений
messageCount++;
}
+98
View File
@@ -0,0 +1,98 @@
#ifndef USER_H
#define USER_H
#include <QString>
#include <QDate>
#include <QDateTime>
#include <QVector>
struct UserBadge {
QString name;
int version;
UserBadge(const QString &name = "", int version = 0)
: name(name), version(version) {}
};
struct User {
QString id;
QString login;
QString displayName;
QString color;
QDate followAt;
QDate createdAt;
QDateTime lastMessageTime;
QVector<UserBadge> badges;
// Статусы
bool isModerator;
bool isVIP;
bool isSubscriber;
bool isTurbo;
bool returningChatter;
bool firstMsg;
// Статистика
int messageCount;
int warnCount;
User()
: followAt(QDate(1900, 1, 1))
, createdAt(QDate(1900, 1, 1))
, lastMessageTime(QDateTime::fromSecsSinceEpoch(0))
, isModerator(false)
, isVIP(false)
, isSubscriber(false)
, isTurbo(false)
, returningChatter(false)
, firstMsg(false)
, messageCount(0)
, warnCount(0)
{}
// Проверка наличия бейджа
bool hasBadge(const QString &badgeName) const {
for (const auto &badge : badges) {
if (badge.name == badgeName) {
return true;
}
}
return false;
}
// Получение версии бейджа
int badgeVersion(const QString &badgeName) const {
for (const auto &badge : badges) {
if (badge.name == badgeName) {
return badge.version;
}
}
return 0;
}
// Обновление из TwitchMessage
void updateFromTwitchMessage(const class TwitchMessage &msg);
// Строковое представление статусов
QString getStatusString() const {
QStringList statuses;
if (isModerator) statuses << "Модератор";
if (isVIP) statuses << "VIP";
if (isSubscriber) statuses << "Подписчик";
if (returningChatter) statuses << "Возвращающийся";
if (firstMsg) statuses << "Первое сообщение";
return statuses.join(", ");
}
// Полный список бейджей как строка
QString getBadgesString() const {
QStringList badgeStrings;
for (const auto &badge : badges) {
badgeStrings << QString("%1/%2").arg(badge.name).arg(badge.version);
}
return badgeStrings.join(",");
}
};
#endif // USER_H
+316
View File
@@ -0,0 +1,316 @@
#include "user_manager.h"
#include "twitchmessage.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QDebug>
UserManager::UserManager(QObject *parent)
: QObject(parent)
, m_totalMessages(0)
, m_maxUsers(10000)
, m_cleanupInterval(60)
, m_lastCleanup(QDateTime::currentDateTime())
{
}
UserManager::~UserManager()
{
clear();
}
QString UserManager::checkUser(const QString &displayName, const TwitchMessage &msg)
{
QString normalizedDisplayName = displayName.trimmed();
// Проверяем, есть ли пользователь
if (m_users.contains(normalizedDisplayName)) {
User &user = m_users[normalizedDisplayName];
// Обновляем из сообщения, если передано
if (!msg.displayName.isEmpty()) {
updateUserFromMessage(normalizedDisplayName, msg);
}
emit userMessageCountChanged(user.id, user.messageCount);
emit userUpdated(user);
return user.id;
}
// Пользователя нет - создаем нового
User newUser;
if (!msg.displayName.isEmpty()) {
// Заполняем из сообщения
newUser.updateFromTwitchMessage(msg);
} else {
// Минимальные данные
newUser.displayName = normalizedDisplayName;
newUser.login = normalizedDisplayName.toLower();
newUser.id = generateUserId();
}
// Добавляем в базу
addUser(newUser);
return newUser.id;
}
User* UserManager::findUser(const QString &displayName)
{
if (m_users.contains(displayName)) {
return &m_users[displayName];
}
// Пробуем найти по login
if (m_loginToDisplayName.contains(displayName.toLower())) {
QString actualDisplayName = m_loginToDisplayName[displayName.toLower()];
return &m_users[actualDisplayName];
}
return nullptr;
}
User* UserManager::findUserById(const QString &userId)
{
if (m_userIdToDisplayName.contains(userId)) {
QString displayName = m_userIdToDisplayName[userId];
return &m_users[displayName];
}
return nullptr;
}
void UserManager::updateUserFromMessage(const QString &displayName, const TwitchMessage &msg)
{
if (!m_users.contains(displayName)) {
return;
}
User &user = m_users[displayName];
// ВАЖНО: передаем displayName как параметр, а не берем из msg
// если displayName из параметра отличается от msg.displayName,
// нам нужно обновить ключ в map
QString newDisplayName = msg.displayName;
if (!newDisplayName.isEmpty() && newDisplayName != displayName) {
// Display name изменился - нужно переместить пользователя
User userCopy = user;
userCopy.updateFromTwitchMessage(msg);
// Удаляем старую запись
removeUser(displayName);
// Добавляем с новым displayName
userCopy.displayName = newDisplayName;
addUser(userCopy);
return;
}
// Display name не изменился - просто обновляем
user.updateFromTwitchMessage(msg);
// Обновляем индексы
updateIndexes(displayName, user);
emit userUpdated(user);
}
void UserManager::updateUserStatus(const QString &userId, bool isMod, bool isVIP, bool isSubscriber)
{
User* user = findUserById(userId);
if (user) {
user->isModerator = isMod;
user->isVIP = isVIP;
user->isSubscriber = isSubscriber;
emit userUpdated(*user);
}
}
void UserManager::addUser(const User &user)
{
QString displayName = user.displayName;
if (displayName.isEmpty()) {
qWarning() << "Cannot add user with empty display name";
return;
}
// Очистка старых пользователей, если нужно
if (m_users.size() >= m_maxUsers) {
cleanupOldUsers();
}
m_users[displayName] = user;
updateIndexes(displayName, user);
emit userAdded(user);
}
void UserManager::removeUser(const QString &displayName)
{
if (m_users.contains(displayName)) {
User user = m_users[displayName];
// Удаляем из индексов
m_userIdToDisplayName.remove(user.id);
m_loginToDisplayName.remove(user.login.toLower());
// Удаляем из основного списка
m_users.remove(displayName);
emit userRemoved(user.id, displayName); // Передаем и id, и displayName
}
}
void UserManager::clear()
{
m_users.clear();
m_userIdToDisplayName.clear();
m_loginToDisplayName.clear();
m_totalMessages = 0;
}
QVector<User*> UserManager::getModerators() const
{
QVector<User*> moderators;
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
if (it.value().isModerator) {
moderators.append(const_cast<User*>(&it.value()));
}
}
return moderators;
}
QVector<User*> UserManager::getVIPs() const
{
QVector<User*> vips;
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
if (it.value().isVIP) {
vips.append(const_cast<User*>(&it.value()));
}
}
return vips;
}
QVector<User*> UserManager::getSubscribers() const
{
QVector<User*> subscribers;
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
if (it.value().isSubscriber) {
subscribers.append(const_cast<User*>(&it.value()));
}
}
return subscribers;
}
QVector<User*> UserManager::getActiveUsers(int minutes) const
{
QVector<User*> activeUsers;
QDateTime cutoff = QDateTime::currentDateTime().addSecs(-minutes * 60);
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
if (it.value().lastMessageTime >= cutoff) {
activeUsers.append(const_cast<User*>(&it.value()));
}
}
return activeUsers;
}
void UserManager::updateIndexes(const QString &displayName, const User &user)
{
if (!user.id.isEmpty()) {
m_userIdToDisplayName[user.id] = displayName;
}
if (!user.login.isEmpty()) {
m_loginToDisplayName[user.login.toLower()] = displayName;
}
}
void UserManager::cleanupOldUsers()
{
QDateTime cutoff = QDateTime::currentDateTime().addDays(-7); // Удаляем неактивных неделю
QVector<QString> usersToRemove;
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
// Удаляем если:
// 1. Не писал больше недели
// 2. Не модератор/VIP/подписчик
if (it.value().lastMessageTime < cutoff &&
!it.value().isModerator &&
!it.value().isVIP &&
!it.value().isSubscriber &&
it.value().messageCount < 5) { // И написано меньше 5 сообщений
usersToRemove.append(it.key());
}
}
for (const QString &displayName : usersToRemove) {
removeUser(displayName);
}
m_lastCleanup = QDateTime::currentDateTime();
}
int UserManager::getUserCount() const
{
return m_users.size();
}
int UserManager::getMessageCount() const
{
return m_totalMessages;
}
QVector<User*> UserManager::findUsersByLogin(const QString &login)
{
QVector<User*> result;
QString lowerLogin = login.toLower();
for (auto it = m_users.begin(); it != m_users.end(); ++it) {
if (it.value().login.toLower().contains(lowerLogin)) {
result.append(&it.value());
}
}
return result;
}
User* UserManager::getUserByIndex(int index)
{
if (index >= 0 && index < m_users.size()) {
auto it = m_users.begin();
std::advance(it, index);
return &it.value();
}
return nullptr;
}
const QMap<QString, User>& UserManager::getAllUsers() const
{
return m_users;
}
QString UserManager::generateUserId() const
{
return QString("user_%1").arg(QDateTime::currentMSecsSinceEpoch());
}
+81
View File
@@ -0,0 +1,81 @@
#ifndef USER_MANAGER_H
#define USER_MANAGER_H
#include <QObject>
#include <QString>
#include <QMap>
#include <QVector>
#include <QDateTime>
#include "user.h"
#include "twitchmessage.h"
class UserManager : public QObject
{
Q_OBJECT
public:
explicit UserManager(QObject *parent = nullptr);
~UserManager();
// Основные методы
QString checkUser(const QString &displayName, const TwitchMessage &msg = TwitchMessage());
User* findUser(const QString &displayName);
User* findUserById(const QString &userId);
// Статистика
int m_totalMessages;
// Обновление пользователя
void updateUserFromMessage(const QString &displayName, const TwitchMessage &msg);
void updateUserStatus(const QString &userId, bool isMod, bool isVIP, bool isSubscriber);
// Управление списком
void addUser(const User &user);
void removeUser(const QString &displayName);
void clear();
// Поиск
QVector<User*> findUsersByLogin(const QString &login);
QVector<User*> getModerators() const;
QVector<User*> getVIPs() const;
QVector<User*> getSubscribers() const;
QVector<User*> getActiveUsers(int minutes = 10) const;
// Статистика
int getUserCount() const;
int getMessageCount() const;
// Сохранение/загрузка
bool saveToFile(const QString &filename);
bool loadFromFile(const QString &filename);
// Геттеры
User* getUserByIndex(int index);
const QMap<QString, User>& getAllUsers() const;
signals:
void userAdded(const User &user);
void userUpdated(const User &user);
void userRemoved(const QString &userId, const QString &displayName);
void userMessageCountChanged(const QString &userId, int count);
private:
// Основное хранилище (DisplayName -> User)
QMap<QString, User> m_users;
// Индексы для быстрого поиска
QMap<QString, QString> m_userIdToDisplayName; // userId -> displayName
QMap<QString, QString> m_loginToDisplayName; // login -> displayName
// Вспомогательные методы
QString generateUserId() const;
void updateIndexes(const QString &displayName, const User &user);
void cleanupOldUsers();
// Конфигурация
int m_maxUsers; // Максимальное количество пользователей в памяти
int m_cleanupInterval; // Интервал очистки в минутах
QDateTime m_lastCleanup;
};
#endif // USER_MANAGER_H
+708
View File
@@ -0,0 +1,708 @@
#include "userwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QDateTime>
#include <QDebug>
UserWidget::UserWidget(UserManager* userManager, QWidget *parent)
: QWidget(parent)
, m_userManager(userManager)
, m_currentFilter(FilterAll)
, m_contextMenu(nullptr)
{
setupUI();
m_tableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_tableWidget, &QTableWidget::customContextMenuRequested,
this, &UserWidget::showContextMenu);
setupContextMenu(); // Добавляем инициализацию контекстного меню
// Подключаем сигналы если менеджер уже есть
if (m_userManager) {
connectSignals();
}
// Таймер для периодического обновления
m_refreshTimer = new QTimer(this);
connect(m_refreshTimer, &QTimer::timeout, this, &UserWidget::onRefreshTimer);
m_refreshTimer->start(30000); // 30 секунд
refreshData();
}
UserWidget::~UserWidget()
{
if (m_refreshTimer) {
m_refreshTimer->stop();
}
}
void UserWidget::setUserManager(UserManager* userManager)
{
// Отключаем старые соединения если были
if (m_userManager) {
disconnectSignals();
}
m_userManager = userManager;
if (m_userManager) {
connectSignals();
}
refreshData();
}
void UserWidget::connectSignals()
{
connect(m_userManager, &UserManager::userAdded,
this, &UserWidget::onUserAdded);
connect(m_userManager, &UserManager::userUpdated,
this, &UserWidget::onUserUpdated);
connect(m_userManager, &UserManager::userRemoved,
this, &UserWidget::onUserRemoved);
}
void UserWidget::disconnectSignals()
{
disconnect(m_userManager, &UserManager::userAdded,
this, &UserWidget::onUserAdded);
disconnect(m_userManager, &UserManager::userUpdated,
this, &UserWidget::onUserUpdated);
disconnect(m_userManager, &UserManager::userRemoved,
this, &UserWidget::onUserRemoved);
}
void UserWidget::setupUI()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(5, 5, 5, 5);
mainLayout->setSpacing(5);
// Панель инструментов
QHBoxLayout* toolbarLayout = new QHBoxLayout();
// Фильтр
QLabel* filterLabel = new QLabel("Фильтр:", this);
m_filterCombo = new QComboBox(this);
m_filterCombo->addItem("Все зрители", FilterAll);
m_filterCombo->addItem("Модераторы", FilterModerators);
m_filterCombo->addItem("VIP", FilterVIPs);
m_filterCombo->addItem("Подписчики", FilterSubscribers);
m_filterCombo->addItem("Активные (10 мин)", FilterActive);
connect(m_filterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &UserWidget::onFilterChanged);
// Поиск
QLabel* searchLabel = new QLabel("Поиск:", this);
m_searchEdit = new QLineEdit(this);
m_searchEdit->setPlaceholderText("Введите имя зрителя...");
m_searchEdit->setClearButtonEnabled(true);
connect(m_searchEdit, &QLineEdit::textChanged,
this, &UserWidget::onSearchTextChanged);
// Кнопка обновления
QPushButton* refreshBtn = new QPushButton("Обновить", this);
refreshBtn->setFixedWidth(100);
connect(refreshBtn, &QPushButton::clicked,
this, &UserWidget::onRefreshButtonClicked);
toolbarLayout->addWidget(filterLabel);
toolbarLayout->addWidget(m_filterCombo);
toolbarLayout->addWidget(searchLabel);
toolbarLayout->addWidget(m_searchEdit);
toolbarLayout->addStretch();
toolbarLayout->addWidget(refreshBtn);
// Таблица
m_tableWidget = new QTableWidget(this);
m_tableWidget->setColumnCount(6);
m_tableWidget->setHorizontalHeaderLabels(
QStringList() << "Имя" << "Сообщений" << "Статус"
<< "VIP" << "Подписчик" << "Последняя активность");
// Настройка таблицы
m_tableWidget->setSortingEnabled(true);
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
m_tableWidget->setAlternatingRowColors(true);
m_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
m_tableWidget->horizontalHeader()->setStretchLastSection(true);
m_tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
m_tableWidget->verticalHeader()->setVisible(false);
// Настройка ширины колонок
m_tableWidget->setColumnWidth(0, 250); // Имя
m_tableWidget->setColumnWidth(1, 80); // Сообщений
m_tableWidget->setColumnWidth(2, 100); // Статус
m_tableWidget->setColumnWidth(3, 50); // VIP
m_tableWidget->setColumnWidth(4, 80); // Подписчик
// Последняя колонка растягивается
connect(m_tableWidget, &QTableWidget::cellDoubleClicked,
this, &UserWidget::onUserDoubleClicked);
// Статистика внизу
QHBoxLayout* statsLayout = new QHBoxLayout();
m_statsLabel = new QLabel(this);
statsLayout->addWidget(m_statsLabel);
statsLayout->addStretch();
// Добавляем всё на форму
mainLayout->addLayout(toolbarLayout);
mainLayout->addWidget(m_tableWidget);
mainLayout->addLayout(statsLayout);
setLayout(mainLayout);
}
void UserWidget::refreshData()
{
if (!m_userManager) {
m_tableWidget->setRowCount(0);
m_statsLabel->setText("Менеджер пользователей не установлен");
return;
}
clearTable();
populateTable();
updateStatistics();
}
void UserWidget::populateTable()
{
if (!m_userManager) return;
QVector<User*> users;
// Получаем пользователей в зависимости от фильтра
switch (m_currentFilter) {
case FilterAll:
for (const User& user : m_userManager->getAllUsers()) {
users.append(const_cast<User*>(&user));
}
break;
case FilterModerators:
users = m_userManager->getModerators();
break;
case FilterVIPs:
users = m_userManager->getVIPs();
break;
case FilterSubscribers:
users = m_userManager->getSubscribers();
break;
case FilterActive:
users = m_userManager->getActiveUsers(10);
break;
}
// Применяем поисковый фильтр
if (!m_searchText.isEmpty()) {
QString searchLower = m_searchText.toLower();
QVector<User*> filteredUsers;
for (User* user : users) {
if (user->displayName.toLower().contains(searchLower) ||
user->login.toLower().contains(searchLower)) {
filteredUsers.append(user);
}
}
users = filteredUsers;
}
// Заполняем таблицу
m_tableWidget->setRowCount(users.size());
for (int i = 0; i < users.size(); ++i) {
User* user = users[i];
// Имя
QTableWidgetItem* nameItem = new QTableWidgetItem(user->displayName);
nameItem->setData(Qt::UserRole, user->id);
m_tableWidget->setItem(i, 0, nameItem);
// Количество сообщений
m_tableWidget->setItem(i, 1,
new QTableWidgetItem(QString::number(user->messageCount)));
// Статус
QString status;
if (user->isModerator) status = "👑 Модератор";
else if (user->isVIP) status = "⭐ VIP";
else if (user->isSubscriber) status = "💎 Подписчик";
else status = "👤 Зритель";
m_tableWidget->setItem(i, 2, new QTableWidgetItem(status));
// VIP
QTableWidgetItem* vipItem = new QTableWidgetItem(user->isVIP ? "Да" : "Нет");
if (user->isVIP) {
vipItem->setForeground(Qt::darkGreen);
}
m_tableWidget->setItem(i, 3, vipItem);
// Подписчик
QTableWidgetItem* subItem = new QTableWidgetItem(user->isSubscriber ? "Да" : "Нет");
if (user->isSubscriber) {
subItem->setForeground(Qt::darkBlue);
}
m_tableWidget->setItem(i, 4, subItem);
// Последняя активность
QString lastActive;
if (user->lastMessageTime.isValid()) {
QDateTime now = QDateTime::currentDateTime();
qint64 secs = user->lastMessageTime.secsTo(now);
if (secs < 60) {
lastActive = "Только что";
} else if (secs < 3600) {
lastActive = QString("%1 мин назад").arg(secs / 60);
} else if (secs < 86400) {
lastActive = QString("%1 ч назад").arg(secs / 3600);
} else {
lastActive = user->lastMessageTime.toString("dd.MM.yyyy hh:mm");
}
} else {
lastActive = "Никогда";
}
m_tableWidget->setItem(i, 5, new QTableWidgetItem(lastActive));
}
// Автоподгон ширины колонок после заполнения
m_tableWidget->resizeColumnsToContents();
}
void UserWidget::updateStatistics()
{
if (!m_userManager) return;
QString stats = QString("Зрителей: %1 | Сообщений: %2 | Модераторов: %3 | VIP: %4 | Подписчиков: %5")
.arg(m_userManager->getUserCount())
.arg(m_userManager->getMessageCount())
.arg(m_userManager->getModerators().size())
.arg(m_userManager->getVIPs().size())
.arg(m_userManager->getSubscribers().size());
m_statsLabel->setText(stats);
}
void UserWidget::addUserToTable(const User &user)
{
int row = m_tableWidget->rowCount();
m_tableWidget->insertRow(row);
// Имя
QTableWidgetItem* nameItem = new QTableWidgetItem(user.displayName);
nameItem->setData(Qt::UserRole, user.id);
m_tableWidget->setItem(row, 0, nameItem);
// Количество сообщений
m_tableWidget->setItem(row, 1,
new QTableWidgetItem(QString::number(user.messageCount)));
// Статус
QString status;
if (user.isModerator) status = "👑 Модератор";
else if (user.isVIP) status = "⭐ VIP";
else if (user.isSubscriber) status = "💎 Подписчик";
else status = "👤 Зритель";
m_tableWidget->setItem(row, 2, new QTableWidgetItem(status));
// VIP
QTableWidgetItem* vipItem = new QTableWidgetItem(user.isVIP ? "Да" : "Нет");
if (user.isVIP) {
vipItem->setForeground(Qt::darkGreen);
}
m_tableWidget->setItem(row, 3, vipItem);
// Подписчик
QTableWidgetItem* subItem = new QTableWidgetItem(user.isSubscriber ? "Да" : "Нет");
if (user.isSubscriber) {
subItem->setForeground(Qt::darkBlue);
}
m_tableWidget->setItem(row, 4, subItem);
// Последняя активность
QString lastActive = "Только что";
m_tableWidget->setItem(row, 5, new QTableWidgetItem(lastActive));
updateStatistics();
}
void UserWidget::updateUserInTable(const User &user)
{
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
QTableWidgetItem* item = m_tableWidget->item(row, 0);
if (item && item->data(Qt::UserRole).toString() == user.id) {
// Обновляем данные
item->setText(user.displayName);
m_tableWidget->item(row, 1)->setText(
QString::number(user.messageCount));
QString status;
if (user.isModerator) status = "👑 Модератор";
else if (user.isVIP) status = "⭐ VIP";
else if (user.isSubscriber) status = "💎 Подписчик";
else status = "👤 Зритель";
m_tableWidget->item(row, 2)->setText(status);
m_tableWidget->item(row, 3)->setText(user.isVIP ? "Да" : "Нет");
if (user.isVIP) {
m_tableWidget->item(row, 3)->setForeground(Qt::darkGreen);
} else {
m_tableWidget->item(row, 3)->setForeground(Qt::black);
}
m_tableWidget->item(row, 4)->setText(user.isSubscriber ? "Да" : "Нет");
if (user.isSubscriber) {
m_tableWidget->item(row, 4)->setForeground(Qt::darkBlue);
} else {
m_tableWidget->item(row, 4)->setForeground(Qt::black);
}
// Обновляем время активности
QString lastActive;
if (user.lastMessageTime.isValid()) {
QDateTime now = QDateTime::currentDateTime();
qint64 secs = user.lastMessageTime.secsTo(now);
if (secs < 60) {
lastActive = "Только что";
} else if (secs < 3600) {
lastActive = QString("%1 мин назад").arg(secs / 60);
} else if (secs < 86400) {
lastActive = QString("%1 ч назад").arg(secs / 3600);
} else {
lastActive = user.lastMessageTime.toString("dd.MM hh:mm");
}
}
m_tableWidget->item(row, 5)->setText(lastActive);
break;
}
}
updateStatistics();
}
void UserWidget::removeUserFromTable(const QString &displayName)
{
// Ищем пользователя по displayName
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
QTableWidgetItem* item = m_tableWidget->item(row, 0);
if (item && item->text() == displayName) {
m_tableWidget->removeRow(row);
updateStatistics();
break;
}
}
}
void UserWidget::clearTable()
{
m_tableWidget->setRowCount(0);
}
void UserWidget::onRefreshTimer()
{
if (!m_userManager) return;
QDateTime now = QDateTime::currentDateTime();
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
QTableWidgetItem* idItem = m_tableWidget->item(row, 0);
if (idItem) {
QString userId = idItem->data(Qt::UserRole).toString();
User* user = m_userManager->findUserById(userId);
if (user && user->lastMessageTime.isValid()) {
qint64 secs = user->lastMessageTime.secsTo(now);
QString lastActive;
if (secs < 60) {
lastActive = "Только что";
} else if (secs < 3600) {
lastActive = QString("%1 мин назад").arg(secs / 60);
} else if (secs < 86400) {
lastActive = QString("%1 ч назад").arg(secs / 3600);
} else {
lastActive = user->lastMessageTime.toString("dd.MM hh:mm");
}
// Проверяем, существует ли ячейка
if (m_tableWidget->item(row, 5)) {
m_tableWidget->item(row, 5)->setText(lastActive);
}
}
}
}
}
bool UserWidget::userMatchesFilter(const User* user) const
{
if (!user) return false;
// Проверка фильтра
switch (m_currentFilter) {
case FilterAll:
break;
case FilterModerators:
if (!user->isModerator) return false;
break;
case FilterVIPs:
if (!user->isVIP) return false;
break;
case FilterSubscribers:
if (!user->isSubscriber) return false;
break;
case FilterActive:
// Активные за последние 10 минут
if (user->lastMessageTime.secsTo(QDateTime::currentDateTime()) > 10*60)
return false;
break;
}
// Проверка поиска
if (!m_searchText.isEmpty()) {
QString searchLower = m_searchText.toLower();
if (!user->displayName.toLower().contains(searchLower) &&
!user->login.toLower().contains(searchLower)) {
return false;
}
}
return true;
}
void UserWidget::onUserAdded(const User &user)
{
// Проверяем, проходит ли пользователь текущий фильтр и поиск
if (userMatchesFilter(&user)) {
addUserToTable(user);
}
}
void UserWidget::onUserUpdated(const User &user)
{
// Ищем пользователя в таблице
int row = findRowByUserId(user.id);
if (row >= 0) {
// Пользователь уже в таблице
if (userMatchesFilter(&user)) {
// Обновляем существующую запись
updateUserInTable(user);
} else {
// Удаляем, так как пользователь больше не проходит фильтр
removeUserFromTableByUserId(user.id);
}
} else {
// Пользователя нет в таблице
if (userMatchesFilter(&user)) {
// Добавляем, так как пользователь теперь проходит фильтр
addUserToTable(user);
}
}
}
int UserWidget::findRowByUserId(const QString &userId) const
{
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
QTableWidgetItem* item = m_tableWidget->item(row, 0);
if (item && item->data(Qt::UserRole).toString() == userId) {
return row;
}
}
return -1;
}
void UserWidget::removeUserFromTableByUserId(const QString &userId)
{
int row = findRowByUserId(userId);
if (row >= 0) {
m_tableWidget->removeRow(row);
updateStatistics();
}
}
void UserWidget::onUserRemoved(const QString &userId, const QString &displayName)
{
Q_UNUSED(displayName);
removeUserFromTableByUserId(userId);
}
void UserWidget::onUserDoubleClicked(int row, int column)
{
Q_UNUSED(column);
if (!m_userManager) return;
QTableWidgetItem* item = m_tableWidget->item(row, 0);
if (item) {
QString userId = item->data(Qt::UserRole).toString();
User* user = m_userManager->findUserById(userId);
if (user) {
qDebug() << "Детали зрителя:"
<< "\nИмя:" << user->displayName
<< "\nID:" << user->id
<< "\nLogin:" << user->login
<< "\nСообщений:" << user->messageCount
<< "\nМодератор:" << user->isModerator
<< "\nVIP:" << user->isVIP
<< "\nПодписчик:" << user->isSubscriber
<< "\nПоследнее сообщение:" << user->lastMessageTime.toString();
// Здесь можно открыть диалог с детальной информацией
// или выполнить другие действия
}
}
}
void UserWidget::onFilterChanged(int index)
{
m_currentFilter = static_cast<FilterType>(m_filterCombo->itemData(index).toInt());
refreshData();
}
void UserWidget::onSearchTextChanged(const QString &text)
{
m_searchText = text;
refreshData();
}
void UserWidget::onRefreshButtonClicked()
{
refreshData();
}
void UserWidget::setupContextMenu()
{
m_contextMenu = new QMenu(this);
m_banAction = m_contextMenu->addAction("Забанить");
connect(m_banAction, &QAction::triggered, this, &UserWidget::onBanUser);
m_timeoutAction = m_contextMenu->addAction("Таймаут (5 мин)");
connect(m_timeoutAction, &QAction::triggered, this, &UserWidget::onTimeoutUser);
m_unbanAction = m_contextMenu->addAction("Разбанить");
connect(m_unbanAction, &QAction::triggered, this, &UserWidget::onUnbanUser);
m_contextMenu->addSeparator();
m_setModAction = m_contextMenu->addAction("Сделать модератором");
connect(m_setModAction, &QAction::triggered, this, &UserWidget::onSetModerator);
m_removeModAction = m_contextMenu->addAction("Забрать модератора");
connect(m_removeModAction, &QAction::triggered, this, &UserWidget::onRemoveModerator);
m_contextMenu->addSeparator();
m_setVIPAction = m_contextMenu->addAction("Сделать VIP");
connect(m_setVIPAction, &QAction::triggered, this, &UserWidget::onSetVIP);
m_removeVIPAction = m_contextMenu->addAction("Забрать VIP");
connect(m_removeVIPAction, &QAction::triggered, this, &UserWidget::onRemoveVIP);
m_contextMenu->addSeparator();
m_infoAction = m_contextMenu->addAction("Информация");
connect(m_infoAction, &QAction::triggered, this, &UserWidget::onShowUserInfo);
}
// Добавляем обработчик контекстного меню:
void UserWidget::showContextMenu(const QPoint &pos)
{
QTableWidgetItem* item = m_tableWidget->itemAt(pos);
if (!item) {
return; // Клик был не по ячейке (например, по заголовку или пустой области)
}
int row = item->row();
QTableWidgetItem* nameItem = m_tableWidget->item(row, 0);
if (nameItem) {
m_selectedUserId = nameItem->data(Qt::UserRole).toString();
m_selectedUserName = nameItem->text();
// Получаем данные пользователя
User* user = m_userManager->findUserById(m_selectedUserId);
if (user) {
// Обновляем состояние пунктов меню
m_setModAction->setEnabled(!user->isModerator);
m_removeModAction->setEnabled(user->isModerator);
m_setVIPAction->setEnabled(!user->isVIP);
m_removeVIPAction->setEnabled(user->isVIP);
// Показываем меню в позиции клика
m_contextMenu->exec(m_tableWidget->viewport()->mapToGlobal(pos));
}
}
}
// Добавляем вспомогательный метод:
User* UserWidget::getSelectedUser()
{
if (!m_selectedUserId.isEmpty()) {
return m_userManager->findUserById(m_selectedUserId);
}
return nullptr;
}
// Добавляем слоты для обработки действий меню:
void UserWidget::onBanUser()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit banUserRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onTimeoutUser()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit timeoutUserRequested(m_selectedUserId, m_selectedUserName, 5);
}
}
void UserWidget::onUnbanUser()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit unbanUserRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onSetModerator()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit setModeratorRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onRemoveModerator()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit removeModeratorRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onSetVIP()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit setVIPRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onRemoveVIP()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit removeVIPRequested(m_selectedUserId, m_selectedUserName);
}
}
void UserWidget::onShowUserInfo()
{
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
emit userInfoRequested(m_selectedUserId, m_selectedUserName);
}
}
+114
View File
@@ -0,0 +1,114 @@
#ifndef USERWIDGET_H
#define USERWIDGET_H
#include <QWidget>
#include <QTableWidget>
#include <QTimer>
#include <QComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QMenu>
#include <QAction>
#include <QContextMenuEvent>
#include "user_manager.h"
class UserWidget : public QWidget
{
Q_OBJECT
public:
explicit UserWidget(UserManager* userManager, QWidget *parent = nullptr);
~UserWidget();
void setUserManager(UserManager* userManager);
void refreshData();
void updateStatistics();
signals:
void banUserRequested(const QString &userId, const QString &userName);
void timeoutUserRequested(const QString &userId, const QString &userName, int minutes);
void unbanUserRequested(const QString &userId, const QString &userName);
void setModeratorRequested(const QString &userId, const QString &userName);
void removeModeratorRequested(const QString &userId, const QString &userName);
void setVIPRequested(const QString &userId, const QString &userName);
void removeVIPRequested(const QString &userId, const QString &userName);
void userInfoRequested(const QString &userId, const QString &userName);
private slots:
void onRefreshTimer();
void onUserAdded(const User &user);
void onUserUpdated(const User &user);
void onUserRemoved(const QString &userId, const QString &displayName);
void onUserDoubleClicked(int row, int column);
void onFilterChanged(int index);
void onSearchTextChanged(const QString &text);
void onRefreshButtonClicked();
// Слоты для контекстного меню
void onBanUser();
void onTimeoutUser();
void onUnbanUser();
void onSetModerator();
void onRemoveModerator();
void onSetVIP();
void onRemoveVIP();
void onShowUserInfo();
void showContextMenu(const QPoint &pos);
protected:
private:
void setupUI();
void populateTable();
void addUserToTable(const User &user);
void updateUserInTable(const User &user);
void removeUserFromTable(const QString &displayName);
void clearTable();
void disconnectSignals();
void connectSignals();
void setupContextMenu();
bool userMatchesFilter(const User* user) const;
int findRowByUserId(const QString &userId) const;
void removeUserFromTableByUserId(const QString &userId);
User* getSelectedUser();
UserManager* m_userManager;
// UI элементы
QTableWidget* m_tableWidget;
QComboBox* m_filterCombo;
QLineEdit* m_searchEdit;
QLabel* m_statsLabel;
QTimer* m_refreshTimer;
// Контекстное меню
QMenu* m_contextMenu;
QAction* m_banAction;
QAction* m_timeoutAction;
QAction* m_unbanAction;
QAction* m_setModAction;
QAction* m_removeModAction;
QAction* m_setVIPAction;
QAction* m_removeVIPAction;
QAction* m_infoAction;
enum FilterType {
FilterAll,
FilterModerators,
FilterVIPs,
FilterSubscribers,
FilterActive
};
FilterType m_currentFilter;
QString m_searchText;
// Выбранный пользователь для контекстного меню
QString m_selectedUserId;
QString m_selectedUserName;
};
#endif // USERWIDGET_H
+739
View File
@@ -0,0 +1,739 @@
#include "webserverchat.h"
#include "qcolor.h"
#include <QCoreApplication>
#include <QDir>
#include <QDebug>
#include <QJsonDocument>
#include <QFileInfo>
#include <QDateTime>
HttpServerChat::HttpServerChat(const QStringList &fontList, int port, const QString &backgroundColor, QObject *parent)
: QObject(parent)
, m_server(nullptr)
, m_fontList(fontList)
, m_backgroundColor(backgroundColor)
, m_deleteByTime(true) // По умолчанию удаление по времени
, m_maxMsgCount(100) // Значение по умолчанию
{
m_server = new QTcpServer(this);
m_server->listen(QHostAddress::Any, port);
connect(m_server, &QTcpServer::newConnection, this, &HttpServerChat::onNewConnection);
m_currentStyle.freez = m_isFreez;
}
HttpServerChat::~HttpServerChat()
{
stop();
}
bool HttpServerChat::start()
{
if (m_server->isListening()) {
emit serverStarted(true);
return true;
}
if (!m_server->listen(QHostAddress::Any, m_server->serverPort())) {
emit serverStarted(false);
return false;
}
emit serverStarted(true);
return true;
}
void HttpServerChat::stop()
{
if (m_server) {
m_server->close();
}
for (auto client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) {
client->close();
}
client->deleteLater();
}
m_clients.clear();
}
quint16 HttpServerChat::port() const
{
return m_server ? m_server->serverPort() : 0;
}
void HttpServerChat::addMessage(const StyleChat &style)
{
m_currentStyle = style;
ChatMessage msg;
msg.nickname = style.nick;
msg.content = style.context;
msg.timestamp = QDateTime::currentSecsSinceEpoch();
msg.timeMsg = style.timeMsg;
msg.freez = style.freez;
msg.transparency = style.transparency;
// Копируем стилевые параметры
msg.blockColor = style.blockColor;
msg.borderColor = style.borderColor;
msg.fontColor = style.fontColor;
msg.fontFamily = style.fontFamily;
msg.fontSize = style.fontSize;
msg.borderSize = style.borderSize;
msg.padding = style.padding;
if (!m_deleteByTime) {
// Режим удаления по количеству
// Проверяем, не превышен ли лимит
if (m_messages.size() >= m_maxMsgCount) {
// Ищем незамороженное сообщение для удаления
bool removed = false;
for (int i = 0; i < m_messages.size(); ++i) {
if (!m_messages[i].freez) {
m_messages.removeAt(i);
removed = true;
break;
}
}
// Если все сообщения заморожены и лимит превышен,
// не добавляем новое сообщение
if (!removed) {
return;
}
}
}
m_messages.append(msg);
// Обновляем всех подключенных клиентов
for (auto client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) {
// Отправляем JavaScript для обновления страницы
QString js = "<script>window.location.reload();</script>";
sendResponse(client, "text/html", js);
}
}
}
void HttpServerChat::activeServer(bool enabled)
{
if (enabled) {
if (!m_server->isListening()) {
m_server->listen(QHostAddress::Any, m_server->serverPort());
}
} else {
m_server->close();
}
}
void HttpServerChat::changeBackground(const QString &color)
{
m_backgroundColor = color;
}
void HttpServerChat::onNewConnection()
{
while (m_server->hasPendingConnections()) {
QTcpSocket *socket = m_server->nextPendingConnection();
m_clients.append(socket);
connect(socket, &QTcpSocket::readyRead, this, &HttpServerChat::readClient);
connect(socket, &QTcpSocket::disconnected, this, &HttpServerChat::discardClient);
}
}
void HttpServerChat::readClient()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray requestData = socket->readAll();
QString request = QString::fromUtf8(requestData);
if (!request.isEmpty()) {
processRequest(socket, request);
}
}
void HttpServerChat::processRequest(QTcpSocket *socket, const QString &request)
{
QStringList lines = request.split("\r\n");
if (lines.isEmpty()) return;
QString firstLine = lines[0];
QStringList parts = firstLine.split(" ");
if (parts.size() < 2) return;
QString method = parts[0];
QString path = parts[1];
cleanupOldMessages();
if (method == "GET") {
if (path == "/" || path == "/index.html") {
sendHtmlPage(socket);
} else if (path == "/messages") {
sendJSONMessages(socket);
} else if (path.startsWith("/fonts/")) {
QString filePath = path.mid(1); // Убираем первый слэш
serveStaticFile(socket, filePath);
} else {
// Игнорируем favicon и другие запросы
if (!path.contains("favicon.ico") && !path.contains(".well-known")) {
}
sendResponse(socket, "text/html", "", 404);
}
}
}
void HttpServerChat::sendHtmlPage(QTcpSocket *socket)
{
QString html = generateHTML();
sendResponse(socket, "text/html", html);
}
void HttpServerChat::sendJSONMessages(QTcpSocket *socket)
{
QString json = generateJSON();
sendResponse(socket, "application/json; charset=utf-8", json);
}
void HttpServerChat::serveStaticFile(QTcpSocket *socket, const QString &filePath)
{
QString fullPath = QCoreApplication::applicationDirPath() + "/" + filePath;
QFile file(fullPath);
if (file.exists() && file.open(QIODevice::ReadOnly)) {
QString mimeType = getMimeType(filePath);
QByteArray content = file.readAll();
file.close();
QString response = QString(
"HTTP/1.1 200 OK\r\n"
"Content-Type: %1\r\n"
"Content-Length: %2\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"\r\n")
.arg(mimeType)
.arg(content.size());
socket->write(response.toUtf8());
socket->write(content);
socket->flush();
} else {
sendResponse(socket, "text/html",
"<html><body><h1>404 Not Found</h1><p>" + filePath + "</p></body></html>",
404);
}
}
QString HttpServerChat::generateHTML()
{
// Генерация CSS для шрифтов
QString fontFaces;
for (const QString &fontFile : m_fontList) {
QString fontName = fontFile;
fontName.replace(".ttf", "").replace(".otf", "").replace(".ttc", "");
fontFaces += QString("@font-face { font-family: '%1'; src: url(fonts/%2); }\n")
.arg(fontName).arg(fontFile);
}
QString deleteByTimeJS = m_deleteByTime ? "true" : "false";
QString html =
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='UTF-8'>\n"
"<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\">\n"
"<meta http-equiv=\"Pragma\" content=\"no-cache\">\n"
"<meta http-equiv=\"Expires\" content=\"0\">\n"
"<title>Chat Messages</title>\n"
"<style>\n"
"body { background: %1; margin: 0; padding: 0; overflow: hidden; }\n"
"#messages { \n"
" position: fixed; \n"
" bottom: 20px; \n"
" right: 20px; \n"
" width: 400px; \n"
" display: flex; \n"
" flex-direction: column; \n"
" align-items: flex-end; \n"
"}\n"
".message { \n"
" margin: 3px 0; \n"
" border-radius: 5px; \n"
" transition: opacity 0.5s linear; \n"
" display: flex; \n"
" align-items: center; \n"
" opacity: 1;\n"
" max-width: 100%;\n"
" word-wrap: break-word;\n"
"}\n"
".message-icon { \n"
" width: 1.5em; \n"
" height: 1.5em; \n"
" margin-right: 0.5em; \n"
"}\n"
"%2\n" // font faces
"</style>\n"
"<script>\n"
"let existingMessages = new Map();\n"
"let fetching = false;\n"
"const deleteByTime = %3;\n"
"\n"
"function hexToRgb(hex) {\n"
" console.log('hexToRgb input:', hex);\n"
" \n"
" // Создаем временный canvas элемент для парсинга цвета\n"
" const ctx = document.createElement('canvas').getContext('2d');\n"
" ctx.fillStyle = hex;\n"
" const color = ctx.fillStyle;\n"
" \n"
" // Если цвет не распознан, используем значение по умолчанию\n"
" if (!color || color === 'rgba(0, 0, 0, 0)') {\n"
" console.log('Color not recognized, using default');\n"
" return '74, 175, 80'; // #4CAF50\n"
" }\n"
" \n"
" // Парсим цвет в формате rgb(r, g, b)\n"
" const match = color.match(/rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)/);\n"
" \n"
" if (match) {\n"
" console.log('Parsed RGB:', match[1], match[2], match[3]);\n"
" return `${match[1]}, ${match[2]}, ${match[3]}`;\n"
" }\n"
" \n"
" console.log('Failed to parse, using default');\n"
" return '74, 175, 80'; // #4CAF50\n"
"}\n"
"\n"
"function fetchMessages() {\n"
" if (fetching) return;\n"
" fetching = true;\n"
" \n"
" fetch('/messages')\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" const container = document.getElementById('messages');\n"
" \n"
" // 1. СОЗДАЕМ СЕТ ИЗ ВСЕХ ИМЕЮЩИХСЯ ID\n"
" const newIds = new Set();\n"
" data.forEach(msg => {\n"
" const msgId = 'msg-' + msg.timestamp;\n"
" newIds.add(msgId);\n"
" });\n"
" \n"
" // 2. УДАЛЯЕМ ТОЛЬКО ТЕ СООБЩЕНИЯ, КОТОРЫХ НЕТ В НОВЫХ ДАННЫХ\n"
" // И КОТОРЫЕ НЕ ЗАМОРОЖЕНЫ\n"
" existingMessages.forEach((div, msgId) => {\n"
" if (!newIds.has(msgId)) {\n"
" // Проверяем, заморожено ли сообщение\n"
" const isFreez = div.getAttribute('data-freez') === 'true';\n"
" if (!isFreez) {\n"
" div.style.opacity = '0';\n"
" setTimeout(() => {\n"
" if (div.parentNode) {\n"
" div.parentNode.removeChild(div);\n"
" }\n"
" existingMessages.delete(msgId);\n"
" }, 500);\n"
" }\n"
" }\n"
" });\n"
" \n"
" // 3. ДОБАВЛЯЕМ НОВЫЕ СООБЩЕНИЯ\n"
" data.forEach(msg => {\n"
" const msgId = 'msg-' + msg.timestamp;\n"
" \n"
" if (!existingMessages.has(msgId)) {\n"
" const div = document.createElement('div');\n"
" div.className = 'message';\n"
" div.id = msgId;\n"
" div.setAttribute('data-freez', msg.freez ? 'true' : 'false');\n"
" \n"
" // ПРИМЕНЯЕМ СТИЛИ\n"
" function hexToRgb(hex) {\n"
" console.log('hexToRgb input:', hex);\n"
" if (!hex || !hex.startsWith('#')) {\n"
" console.log('Not a hex color, using default');\n"
" return '74, 175, 80'; // #4CAF50\n"
" }\n"
" // Убираем # и проверяем длину\n"
" let cleanHex = hex.substring(1);\n"
" // Убираем альфа-канал если есть\n"
" if (cleanHex.length === 8) {\n"
" cleanHex = cleanHex.substring(0, 6);\n"
" }\n"
" // Расширяем короткую форму (#rgb → #rrggbb)\n"
" if (cleanHex.length === 3) {\n"
" cleanHex = cleanHex[0] + cleanHex[0] + \n"
" cleanHex[1] + cleanHex[1] + \n"
" cleanHex[2] + cleanHex[2];\n"
" }\n"
" // Проверяем, что строка состоит из шестнадцатеричных символов\n"
" if (cleanHex.length !== 6 || !/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {\n"
" console.log('Invalid hex format, using default');\n"
" return '74, 175, 80'; // #4CAF50\n"
" }\n"
" // Конвертируем HEX в RGB\n"
" const r = parseInt(cleanHex.substring(0, 2), 16);\n"
" const g = parseInt(cleanHex.substring(2, 4), 16);\n"
" const b = parseInt(cleanHex.substring(4, 6), 16);\n"
" console.log('Parsed RGB:', r, g, b);\n"
" return `${r}, ${g}, ${b}`;\n"
" }\n"
" \n"
" // ФОРМИРУЕМ СОДЕРЖИМОЕ\n"
" let content = '<span><b>' + msg.nickname + ':</b> ' + msg.content + '</span>';\n"
" div.innerHTML = content;\n"
" div.style.opacity = '1';\n"
" \n"
" container.appendChild(div);\n"
" existingMessages.set(msgId, div);\n"
" \n"
" // 4. УСТАНАВЛИВАЕМ ТАЙМЕР УДАЛЕНИЯ ТОЛЬКО ЕСЛИ:\n"
" // - включено удаление по времени (deleteByTime)\n"
" // - сообщение НЕ заморожено (freez = false)\n"
" if (deleteByTime && (!msg.freez || msg.freez === false)) {\n"
" setTimeout(() => {\n"
" if (existingMessages.has(msgId)) {\n"
" const messageDiv = existingMessages.get(msgId);\n"
" messageDiv.style.opacity = '0';\n"
" setTimeout(() => {\n"
" if (messageDiv.parentNode) {\n"
" messageDiv.parentNode.removeChild(messageDiv);\n"
" }\n"
" existingMessages.delete(msgId);\n"
" }, 500);\n"
" }\n"
" }, (parseInt(msg.timeMsg) || 10) * 1000);\n"
" }\n"
" }\n"
" });\n"
" \n"
" // 5. ОБРАБОТКА РЕЖИМА УДАЛЕНИЯ ПО КОЛИЧЕСТВУ\n"
" if (!deleteByTime) {\n"
" const maxMessages = 100;\n"
" if (existingMessages.size > maxMessages) {\n"
" const messagesArray = Array.from(existingMessages.entries());\n"
" messagesArray.sort((a, b) => {\n"
" const idA = parseInt(a[0].replace('msg-', ''));\n"
" const idB = parseInt(b[0].replace('msg-', ''));\n"
" return idA - idB;\n"
" });\n"
" \n"
" let removedCount = 0;\n"
" for (const [msgId, div] of messagesArray) {\n"
" if (existingMessages.size - removedCount <= maxMessages) break;\n"
" \n"
" const isFreez = div.getAttribute('data-freez') === 'true';\n"
" if (!isFreez) {\n"
" div.style.opacity = '0';\n"
" setTimeout(() => {\n"
" if (div.parentNode) {\n"
" div.parentNode.removeChild(div);\n"
" }\n"
" existingMessages.delete(msgId);\n"
" }, 500);\n"
" removedCount++;\n"
" }\n"
" }\n"
" }\n"
" }\n"
" })\n"
" .catch(error => console.error('Ошибка при загрузке сообщений:', error))\n"
" .finally(() => { fetching = false; });\n"
"}\n"
"\n"
"// Обновляем сообщения каждые 500 мс\n"
"setInterval(fetchMessages, 500);\n"
"\n"
"// Первоначальная загрузка\n"
"fetchMessages();\n"
"</script>\n"
"</head>\n"
"<body>\n"
"<div id='messages'></div>\n"
"</body>\n"
"</html>";
return html.arg(m_backgroundColor).arg(fontFaces).arg(deleteByTimeJS);
}
QString HttpServerChat::generateJSON()
{
QJsonArray jsonArray;
for (const ChatMessage &msg : m_messages) {
QJsonObject obj;
obj["nickname"] = msg.nickname;
obj["content"] = msg.content;
obj["timestamp"] = msg.timestamp;
obj["timeMsg"] = msg.timeMsg;
obj["freez"] = msg.freez;
obj["transparency"] = msg.transparency;
// Конвертируем цветовые названия в HEX формат
QString blockColor = msg.blockColor.isEmpty() ? m_currentStyle.blockColor : msg.blockColor;
QString borderColor = msg.borderColor.isEmpty() ? m_currentStyle.borderColor : msg.borderColor;
QString fontColor = msg.fontColor.isEmpty() ? m_currentStyle.fontColor : msg.fontColor;
// Конвертируем названия цветов в HEX формат
obj["color"] = colorNameToHex(blockColor);
obj["borderColor"] = colorNameToHex(borderColor);
obj["fontColor"] = colorNameToHex(fontColor);
obj["family"] = msg.fontFamily.isEmpty() ? m_currentStyle.fontFamily : msg.fontFamily;
obj["fontSize"] = msg.fontSize > 0 ? msg.fontSize : m_currentStyle.fontSize;
obj["borderSize"] = msg.borderSize > 0 ? msg.borderSize : m_currentStyle.borderSize;
obj["padding"] = msg.padding > 0 ? msg.padding : m_currentStyle.padding;
jsonArray.append(obj);
}
QJsonDocument doc(jsonArray);
return QString::fromUtf8(doc.toJson());
}
QString HttpServerChat::colorNameToHex(const QString &colorName) const
{
if (colorName.isEmpty()) {
return "#000000";
}
// Если строка уже начинается с #, это HEX формат
if (colorName.startsWith('#')) {
return colorName;
}
// Проверяем, является ли это названием цвета
QColor color;
if (color.isValidColor(colorName)) {
color.setNamedColor(colorName);
return color.name();
}
// Пробуем создать QColor из строки
color = QColor(colorName);
if (color.isValid()) {
return color.name();
}
// Если ничего не получилось, возвращаем черный по умолчанию
return "#000000";
}
void HttpServerChat::sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode)
{
QString statusText;
switch(statusCode) {
case 200: statusText = "OK"; break;
case 404: statusText = "Not Found"; break;
default: statusText = "OK"; break;
}
QByteArray responseData = QString(
"HTTP/1.1 %1 %2\r\n"
"Content-Type: %3\r\n"
"Content-Length: %4\r\n"
"Cache-Control: no-cache, no-store, must-revalidate\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Connection: keep-alive\r\n"
"\r\n"
"%5")
.arg(statusCode)
.arg(statusText)
.arg(contentType)
.arg(content.toUtf8().size())
.arg(content)
.toUtf8();
socket->write(responseData);
socket->flush();
}
QString HttpServerChat::getMimeType(const QString &filePath)
{
if (filePath.endsWith(".html")) return "text/html";
if (filePath.endsWith(".css")) return "text/css";
if (filePath.endsWith(".js")) return "application/javascript";
if (filePath.endsWith(".png")) return "image/png";
if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg";
if (filePath.endsWith(".gif")) return "image/gif";
if (filePath.endsWith(".svg")) return "image/svg+xml";
if (filePath.endsWith(".ico")) return "image/x-icon";
if (filePath.endsWith(".ttf")) return "font/ttf";
if (filePath.endsWith(".otf")) return "font/otf";
if (filePath.endsWith(".woff")) return "font/woff";
if (filePath.endsWith(".woff2")) return "font/woff2";
return "text/plain";
}
void HttpServerChat::discardClient()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (socket) {
m_clients.removeOne(socket);
socket->deleteLater();
}
}
// Реализация методов получения настроек
QString HttpServerChat::getFontFamily() const {
return m_currentStyle.fontFamily;
}
int HttpServerChat::getFontSize() const {
return m_currentStyle.fontSize;
}
QString HttpServerChat::getFontColor() const {
return m_currentStyle.fontColor;
}
QString HttpServerChat::getBlockColor() const {
return m_currentStyle.blockColor;
}
QString HttpServerChat::getBorderColor() const {
return m_currentStyle.borderColor;
}
int HttpServerChat::getBorderSize() const {
return m_currentStyle.borderSize;
}
int HttpServerChat::getPadding() const {
return m_currentStyle.padding;
}
int HttpServerChat::getMessageTimeout() const {
return m_currentStyle.timeMsg;
}
QString HttpServerChat::getBackgroundColor() const {
return m_backgroundColor;
}
int HttpServerChat::getTransparency() const {
return m_currentStyle.transparency;
}
void HttpServerChat::setFontFamily(const QString &fontFamily) {
m_currentStyle.fontFamily = fontFamily;
}
void HttpServerChat::setFontSize(int fontSize) {
m_currentStyle.fontSize = fontSize;
}
void HttpServerChat::setFontColor(const QString &fontColor) {
m_currentStyle.fontColor = fontColor;
}
void HttpServerChat::setBlockColor(const QString &blockColor) {
m_currentStyle.blockColor = blockColor;
}
void HttpServerChat::setBorderColor(const QString &borderColor) {
m_currentStyle.borderColor = borderColor;
}
void HttpServerChat::setBorderSize(int borderSize) {
m_currentStyle.borderSize = borderSize;
}
void HttpServerChat::setPadding(int padding) {
m_currentStyle.padding = padding;
}
void HttpServerChat::setMessageTimeout(int timeout) {
m_currentStyle.timeMsg = timeout;
}
void HttpServerChat::setTransparency(int transparency) {
m_currentStyle.transparency = qBound(0, transparency, 255);
}
void HttpServerChat::setDeleteMode(bool deleteByTime, int maxMsgCount)
{
m_deleteByTime = deleteByTime;
m_maxMsgCount = maxMsgCount;
if (!deleteByTime) {
// Режим удаления по количеству
// Удаляем самые старые сообщения, НО сохраняем замороженные
while (m_messages.size() > m_maxMsgCount) {
// Ищем незамороженное сообщение для удаления
bool removed = false;
for (int i = 0; i < m_messages.size(); ++i) {
if (!m_messages[i].freez) {
m_messages.removeAt(i);
removed = true;
break;
}
}
// Если все сообщения заморожены, выходим из цикла
if (!removed) {
break;
}
}
} else {
// Режим удаления по времени
// Замороженные сообщения сохраняются в любом случае
cleanupOldMessages();
}
}
void HttpServerChat::cleanupOldMessages()
{
if (!m_deleteByTime) {
return; // Если удаление по времени отключено, выходим
}
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
// Проходим сообщения в обратном порядке для безопасного удаления
for (int i = m_messages.size() - 1; i >= 0; --i) {
const ChatMessage &msg = m_messages[i];
// Пропускаем замороженные сообщения
if (msg.freez) {
continue;
}
qint64 messageAge = currentTime - msg.timestamp;
if (messageAge >= msg.timeMsg) {
m_messages.removeAt(i);
}
}
}
+152
View File
@@ -0,0 +1,152 @@
#ifndef WEBSERVERCHAT_H
#define WEBSERVERCHAT_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMap>
#include <QFile>
#include <QTimer>
#include <QList>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
// Структура для стиля чата
struct StyleChat {
QString nick;
QString context;
QString blockColor;
QString bColor; // Цвет фона страницы
int fontSize;
QString fontColor;
QString borderColor;
int borderSize;
int padding;
QString fontFamily;
int timeMsg; // Время отображения в секундах
bool freez;
int transparency;
StyleChat() :
fontSize(14),
borderSize(2),
padding(5),
timeMsg(10),
freez(false),
transparency(255)
{}
};
// Структура для сообщения чата
struct ChatMessage {
QString nickname;
QString content;
qint64 timestamp;
int timeMsg;
bool freez;
int transparency; // Исправлено с transporant на transparency
// Поля для стиля
QString blockColor;
QString borderColor;
QString fontColor;
QString fontFamily;
int fontSize;
int borderSize;
int padding;
ChatMessage() :
timestamp(0),
timeMsg(10),
freez(false),
transparency(255),
fontSize(14),
borderSize(2),
padding(5)
{}
};
class HttpServerChat : public QObject
{
Q_OBJECT
public:
explicit HttpServerChat(const QStringList &fontList, int port, const QString &backgroundColor, QObject *parent = nullptr);
~HttpServerChat();
bool start();
void stop();
void addMessage(const StyleChat &style);
void activeServer(bool enabled);
void setDeleteMode(bool deleteByTime, int maxMsgCount);
void changeBackground(const QString &color);
bool isFreez() const { return m_isFreez; }
void setFreez(bool freez) { m_isFreez = freez; m_currentStyle.freez = freez; }
int getMaxMsgCount() const { return m_maxMsgCount; }
void setMaxMsgCount(int count) { m_maxMsgCount = count; }
// block color
QString getBlockColor() const;
QString getBorderColor() const;
QString getBackgroundColor() const;
int getBorderSize() const;
int getPadding() const;
int getTransparency() const;
// block font
QString getFontFamily() const;
int getFontSize() const;
QString getFontColor() const;
// block settings
int getMessageTimeout() const;
quint16 port() const;
void setFontFamily(const QString &fontFamily);
void setFontSize(int fontSize);
void setFontColor(const QString &fontColor);
void setBlockColor(const QString &blockColor);
void setBorderColor(const QString &borderColor);
void setBorderSize(int borderSize);
void setPadding(int padding);
void setMessageTimeout(int timeout);
void setTransparency(int transparency);
signals:
void serverStarted(bool success);
private slots:
void onNewConnection();
void readClient();
void discardClient();
private:
QTcpServer *m_server;
QList<QTcpSocket*> m_clients;
QList<ChatMessage> m_messages;
// Настройки
QStringList m_fontList;
StyleChat m_currentStyle;
QString m_backgroundColor;
bool m_deleteByTime; // true - удаление по времени, false - по количеству
int m_maxMsgCount; // Максимальное количество сообщений
QString colorNameToHex(const QString &colorName) const;
void processRequest(QTcpSocket *socket, const QString &request);
void sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode = 200);
void sendHtmlPage(QTcpSocket *socket);
void sendJSONMessages(QTcpSocket *socket);
void serveStaticFile(QTcpSocket *socket, const QString &filePath);
QString getMimeType(const QString &filePath);
QString generateHTML();
QString generateJSON();
void cleanupOldMessages();
bool m_isFreez;
};
#endif // WEBSERVERCHAT_H
+507
View File
@@ -0,0 +1,507 @@
#include "webservernotify.h"
#include <QDateTime>
#include <QCoreApplication>
#include <QDir>
#include <QDebug>
#include <QJsonDocument>
#include <QFileInfo>
HttpServer::HttpServer(QObject *parent)
: QObject(parent)
, m_server(nullptr)
, m_pageBackgroundColor("transparent")
{
}
HttpServer::~HttpServer()
{
stop();
}
bool HttpServer::start(quint16 port)
{
if (m_server) {
stop();
}
m_server = new QTcpServer(this);
if (!m_server->listen(QHostAddress::Any, port)) {
delete m_server;
m_server = nullptr;
emit serverStarted(false);
return false;
}
connect(m_server, &QTcpServer::newConnection, this, &HttpServer::onNewConnection);
emit serverStarted(true);
return true;
}
void HttpServer::stop()
{
if (m_server) {
m_server->close();
m_server->deleteLater();
m_server = nullptr;
}
// Закрываем всех клиентов
for (auto client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) {
client->close();
}
client->deleteLater();
}
m_clients.clear();
}
quint16 HttpServer::port() const
{
return m_server ? m_server->serverPort() : 0;
}
void HttpServer::addNotification(const Notification &notification)
{
cleanOldMessages();
// Добавляем временную метку
Notification notif = notification;
notif.timestamp = QDateTime::currentSecsSinceEpoch();
// Обновляем цвет фона страницы из уведомления
if (!notification.pageBackgroundColor.isEmpty()) {
m_pageBackgroundColor = notification.pageBackgroundColor;
}
m_notifications.append(notif);
// Обновляем всех подключенных клиентов
for (auto client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) {
// Отправляем JavaScript для обновления страницы
QString js = "<script>window.location.reload();</script>";
sendResponse(client, "text/html", js);
}
}
}
void HttpServer::clearNotifications()
{
m_notifications.clear();
}
void HttpServer::cleanOldMessages()
{
qint64 currentTime = QDateTime::currentSecsSinceEpoch();
for (int i = m_notifications.size() - 1; i >= 0; --i) {
if (currentTime - m_notifications[i].timestamp >= m_notifications[i].duration) {
m_notifications.removeAt(i);
}
}
}
void HttpServer::onNewConnection()
{
while (m_server->hasPendingConnections()) {
QTcpSocket *socket = m_server->nextPendingConnection();
m_clients.append(socket);
// Устанавливаем keep-alive
connect(socket, &QTcpSocket::readyRead, this, &HttpServer::readClient);
connect(socket, &QTcpSocket::disconnected, this, &HttpServer::discardClient);
}
}
void HttpServer::readClient()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray requestData = socket->readAll();
QString request = QString::fromUtf8(requestData);
if (!request.isEmpty()) {
processRequest(socket, request);
}
}
void HttpServer::processRequest(QTcpSocket *socket, const QString &request)
{
QStringList lines = request.split("\r\n");
if (lines.isEmpty()) return;
QString firstLine = lines[0];
QStringList parts = firstLine.split(" ");
if (parts.size() < 2) return;
QString method = parts[0];
QString path = parts[1];
if (method == "GET") {
if (path == "/" || path == "/index.html") {
sendHtmlPage(socket);
} else if (path == "/messages") {
sendJSONMessages(socket);
} else if (path.startsWith("/sounds/")) {
QString filePath = path.mid(1); // Убираем первый слэш
serveStaticFile(socket, filePath);
} else if (path.startsWith("/imgs/")) {
QString filePath = path.mid(1);
serveStaticFile(socket, filePath);
} else if (path.startsWith("/fonts/")) {
QString filePath = path.mid(1);
serveStaticFile(socket, filePath);
} else if (path == "/clear") {
clearNotifications();
sendResponse(socket, "text/html", "<html><body>Notifications cleared</body></html>");
} else {
// Игнорируем favicon и другие запросы
if (!path.contains("favicon.ico") && !path.contains(".well-known")) {
}
sendResponse(socket, "text/html", "", 404);
}
}
}
void HttpServer::sendHtmlPage(QTcpSocket *socket)
{
QString html = generateHTML();
sendResponse(socket, "text/html", html);
}
void HttpServer::sendJSONMessages(QTcpSocket *socket)
{
QString json = generateJSON();
// Убедимся, что это валидный JSON
QJsonParseError parseError;
QJsonDocument::fromJson(json.toUtf8(), &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Ошибка парсинга JSON:" << parseError.errorString();
json = "[]"; // Отправляем пустой массив при ошибке
}
sendResponse(socket, "application/json; charset=utf-8", json);
}
void HttpServer::serveStaticFile(QTcpSocket *socket, const QString &filePath)
{
QString fullPath = QCoreApplication::applicationDirPath() + "/" + filePath;
QFile file(fullPath);
if (file.exists() && file.open(QIODevice::ReadOnly)) {
QString mimeType = getMimeType(filePath);
QByteArray content = file.readAll();
file.close();
QString response = QString(
"HTTP/1.1 200 OK\r\n"
"Content-Type: %1\r\n"
"Content-Length: %2\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"\r\n")
.arg(mimeType)
.arg(content.size());
socket->write(response.toUtf8());
socket->write(content);
socket->flush();
} else {
sendResponse(socket, "text/html",
"<html><body><h1>404 Not Found</h1><p>" + filePath + "</p></body></html>",
404);
}
}
QString HttpServer::generateHTML()
{
QString html =
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='UTF-8'>\n"
"<title>Web Notifications</title>\n"
"<style>\n"
"body { background: %1; margin: 0; padding: 0; }\n" // Используем цвет фона страницы
"#messages { position: fixed; top: 20px; right: 20px; width: 400px; }\n"
".notification { \n"
" margin: 10px 0; \n"
" border-radius: 10px; \n"
" padding: 15px; \n"
" max-width: 100%; \n"
" box-shadow: 0 4px 15px rgba(0,0,0,0.2);\n"
" animation: fadeIn 0.3s ease-in;\n"
" opacity: 1;\n"
" transition: opacity 1s ease-out;\n"
"}\n"
".notification.fade-out {\n"
" opacity: 0 !important;\n"
"}\n"
".nick { margin: 0 0 5px 0; padding: 0; }\n"
".text { margin: 0; padding: 0; }\n"
"@keyframes fadeIn {\n"
" from { opacity: 0; transform: translateY(-20px); }\n"
" to { opacity: 1; transform: translateY(0); }\n"
"}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"<div id='messages'></div>\n"
"<script>\n"
"let notifications = new Map(); // Храним активные уведомления по timestamp\n"
"let lastSoundTime = 0;\n"
"\n"
"function loadMessages() {\n"
" fetch('/messages')\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" updateNotifications(data);\n"
" })\n"
" .catch(error => console.error('Error:', error));\n"
"}\n"
"\n"
"function updateNotifications(messages) {\n"
" const container = document.getElementById('messages');\n"
" \n"
" // Проходим по всем полученным сообщениям\n"
" messages.forEach(msg => {\n"
" const msgId = 'msg-' + msg.timestamp;\n"
" \n"
" // Если уведомление уже есть, пропускаем\n"
" if (notifications.has(msgId)) {\n"
" return;\n"
" }\n"
" \n"
" // Создаем новое уведомление\n"
" const div = document.createElement('div');\n"
" div.className = 'notification';\n"
" div.id = msgId;\n"
" \n"
" // Применяем стили из настроек\n"
" div.style.backgroundColor = msg.color || '#4CAF50';\n"
" div.style.border = (msg.borderSize || 2) + 'px solid ' + (msg.borderColor || '#2E7D32');\n"
" div.style.padding = '15px';\n"
" \n"
" // Формируем содержимое\n"
" let content = '';\n"
" \n"
" // Изображение\n"
" if (msg.url && msg.url !== '') {\n"
" content += '<img src=\"' + msg.url + '\" style=\"max-width: 100%; height: auto; border-radius: 5px; margin-bottom: 10px;\">';\n"
" }\n"
" \n"
" // Заголовок\n"
" content += '<div class=\"nick\" style=\"'\n"
" + 'color: ' + (msg.titleColor || '#FFFFFF') + ';'\n"
" + 'font-family: \"' + (msg.titleFamily || 'Arial') + '\", sans-serif;'\n"
" + 'font-size: ' + (msg.titleSize || 20) + 'px;'\n"
" + 'font-weight: bold;'\n"
" + 'text-shadow: 1px 1px 2px rgba(0,0,0,0.5);'\n"
" + '\">'\n"
" + msg.nickname\n"
" + '</div>';\n"
" \n"
" // Текст\n"
" content += '<div class=\"text\" style=\"'\n"
" + 'color: ' + (msg.contentColor || '#F5F5F5') + ';'\n"
" + 'font-family: \"' + (msg.contentFamily || 'Arial') + '\", sans-serif;'\n"
" + 'font-size: ' + (msg.contentSize || 16) + 'px;'\n"
" + '\">'\n"
" + msg.content\n"
" + '</div>';\n"
" \n"
" div.innerHTML = content;\n"
" container.appendChild(div);\n"
" \n"
" // Добавляем в карту\n"
" notifications.set(msgId, {\n"
" element: div,\n"
" timestamp: msg.timestamp,\n"
" duration: msg.duration || 10\n"
" });\n"
" \n"
" // Проигрываем звук\n"
" if (msg.sound && msg.sound !== '') {\n"
" playSound(msg.sound);\n"
" }\n"
" \n"
" // Запускаем таймер удаления\n"
" setTimeout(() => {\n"
" removeNotification(msgId);\n"
" }, (msg.duration || 10) * 1000);\n"
" });\n"
"}\n"
"\n"
"function removeNotification(id) {\n"
" if (notifications.has(id)) {\n"
" const notification = notifications.get(id);\n"
" notification.element.classList.add('fade-out');\n"
" \n"
" // Удаляем после анимации\n"
" setTimeout(() => {\n"
" if (notification.element.parentNode) {\n"
" notification.element.parentNode.removeChild(notification.element);\n"
" }\n"
" notifications.delete(id);\n"
" }, 1000);\n"
" }\n"
"}\n"
"\n"
"function playSound(soundUrl) {\n"
" if (!soundUrl) return;\n"
" \n"
" const audio = new Audio(soundUrl);\n"
" audio.volume = 0.5;\n"
" audio.play().catch(e => console.log('Audio error:', e));\n"
"}\n"
"\n"
"// Загружаем при старте\n"
"loadMessages();\n"
"\n"
"// Обновляем каждые 2 секунды (не каждый раз)\n"
"setInterval(loadMessages, 2000);\n"
"\n"
"// Обновляем при возврате на вкладку\n"
"document.addEventListener('visibilitychange', () => {\n"
" if (!document.hidden) {\n"
" loadMessages();\n"
" }\n"
"});\n"
"</script>\n"
"</body>\n"
"</html>";
return html.arg(m_pageBackgroundColor); // Подставляем цвет фона
}
QString HttpServer::generateJSON()
{
cleanOldMessages();
QJsonArray jsonArray;
for (const Notification &notif : m_notifications) {
QJsonObject obj;
obj["nickname"] = notif.nickname;
// Исправляем пути - добавляем слеш в начале
if (!notif.url.isEmpty()) {
obj["url"] = notif.url.startsWith("/") ? notif.url : "/" + notif.url;
} else {
obj["url"] = "";
}
obj["content"] = notif.content;
obj["timestamp"] = notif.timestamp;
// Исправляем путь к звуку
if (!notif.soundURL.isEmpty()) {
obj["sound"] = notif.soundURL.startsWith("/") ? notif.soundURL : "/" + notif.soundURL;
} else {
obj["sound"] = "";
}
obj["duration"] = notif.duration;
obj["color"] = notif.blockColor;
obj["borderColor"] = notif.borderColor;
obj["borderSize"] = notif.borderSize;
obj["titleColor"] = notif.titleColor;
obj["titleFamily"] = notif.titleFamily;
obj["titleSize"] = notif.titleSize;
obj["contentColor"] = notif.contentColor;
obj["contentFamily"] = notif.contentFamily;
obj["contentSize"] = notif.contentSize;
jsonArray.append(obj);
}
QJsonDocument doc(jsonArray);
QString jsonStr = QString::fromUtf8(doc.toJson());
return jsonStr;
}
void HttpServer::sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode)
{
QString statusText;
switch(statusCode) {
case 200: statusText = "OK"; break;
case 404: statusText = "Not Found"; break;
default: statusText = "OK"; break;
}
QByteArray responseData = QString(
"HTTP/1.1 %1 %2\r\n"
"Content-Type: %3\r\n"
"Content-Length: %4\r\n"
"Cache-Control: no-cache, no-store, must-revalidate\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Connection: keep-alive\r\n"
"\r\n"
"%5")
.arg(statusCode)
.arg(statusText)
.arg(contentType)
.arg(content.toUtf8().size())
.arg(content)
.toUtf8();
socket->write(responseData);
// Не закрываем соединение сразу для keep-alive
if (contentType == "text/html" || contentType.startsWith("application/json")) {
// Для HTML и JSON оставляем соединение открытым
socket->flush();
} else {
// Для статических файлов закрываем после отправки
socket->flush();
socket->close();
}
}
QString HttpServer::getMimeType(const QString &filePath)
{
if (filePath.endsWith(".html")) return "text/html";
if (filePath.endsWith(".css")) return "text/css";
if (filePath.endsWith(".js")) return "application/javascript";
if (filePath.endsWith(".png")) return "image/png";
if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg";
if (filePath.endsWith(".gif")) return "image/gif";
if (filePath.endsWith(".svg")) return "image/svg+xml";
if (filePath.endsWith(".ico")) return "image/x-icon";
if (filePath.endsWith(".ttf")) return "font/ttf";
if (filePath.endsWith(".otf")) return "font/otf";
if (filePath.endsWith(".woff")) return "font/woff";
if (filePath.endsWith(".woff2")) return "font/woff2";
if (filePath.endsWith(".mp3")) return "audio/mpeg";
if (filePath.endsWith(".wav")) return "audio/wav";
if (filePath.endsWith(".ogg")) return "audio/ogg";
return "text/plain";
}
void HttpServer::discardClient()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (socket) {
m_clients.removeOne(socket);
socket->deleteLater();
}
}
+85
View File
@@ -0,0 +1,85 @@
#ifndef WEBSERVERNOTIFY_H
#define WEBSERVERNOTIFY_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMap>
#include <QFile>
#include <QTimer>
#include <QList>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
struct Notification {
QString nickname;
QString url;
QString content;
QString soundURL;
QString blockColor;
QString borderColor;
int borderSize;
int duration;
QString titleColor;
QString titleFamily;
int titleSize;
QString contentColor;
QString contentFamily;
int contentSize;
qint64 timestamp;
// Новое поле для цвета фона страницы
QString pageBackgroundColor;
Notification() :
borderSize(2),
duration(10),
titleSize(16),
contentSize(14),
timestamp(0),
pageBackgroundColor("transparent") // По умолчанию прозрачный
{}
};
class HttpServer : public QObject
{
Q_OBJECT
public:
explicit HttpServer(QObject *parent = nullptr);
~HttpServer();
bool start(quint16 port);
void stop();
quint16 port() const;
void addNotification(const Notification &notification);
void clearNotifications();
signals:
void serverStarted(bool success);
private slots:
void onNewConnection();
void readClient();
void discardClient();
private:
QTcpServer *m_server;
QList<QTcpSocket*> m_clients;
QList<Notification> m_notifications;
QString m_pageBackgroundColor; // Цвет фона страницы
void processRequest(QTcpSocket *socket, const QString &request);
void sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode = 200);
void sendHtmlPage(QTcpSocket *socket);
void sendJSONMessages(QTcpSocket *socket);
void serveStaticFile(QTcpSocket *socket, const QString &filePath);
QString getMimeType(const QString &filePath);
QString generateHTML();
QString generateJSON();
void cleanOldMessages();
};
#endif // WEBSERVERNOTIFY_H
+238
View File
@@ -0,0 +1,238 @@
#include "websocketclient.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QCoreApplication>
#include <synchapi.h>
WebSocketClient::WebSocketClient(QObject *parent) :
QObject(parent),
m_webSocket(nullptr),
m_isConnected(false)
{
m_pingTimer = new QTimer(this);
m_pingTimer->setInterval(60000); // Ping каждые 30 секунд
m_channelJoined = false;
// Используем новый синтаксис подключения сигналов для Qt5
connect(m_pingTimer, &QTimer::timeout, this, &WebSocketClient::onPingTimeout);
}
WebSocketClient::~WebSocketClient()
{
disconnectFromServer();
delete m_webSocket;
}
bool WebSocketClient::connectToServer(const QString &url)
{
if (m_webSocket) {
disconnectFromServer();
delete m_webSocket;
m_webSocket = nullptr;
}
m_webSocket = new QWebSocket();
// Подключаем сигналы с новым синтаксисом Qt5
connect(m_webSocket, &QWebSocket::connected,
this, &WebSocketClient::onConnectedInternal);
connect(m_webSocket, &QWebSocket::disconnected,
this, &WebSocketClient::onDisconnectedInternal);
connect(m_webSocket, &QWebSocket::textMessageReceived,
this, &WebSocketClient::onTextMessageReceived);
connect(m_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
this, &WebSocketClient::onErrorInternal);
connect(m_webSocket, &QWebSocket::sslErrors,
this, &WebSocketClient::onSslErrors);
m_webSocket->open(QUrl(url));
return true;
}
void WebSocketClient::connectToTwitchChat(const QString &oauthToken,
const QString &nickname,
const QString &channel)
{
m_nickname = nickname;
m_currentChannel = channel;
m_oauthToken = oauthToken;
// URL для подключения к Twitch IRC через WebSocket
QString url = "wss://irc-ws.chat.twitch.tv:443";
connectToServer(url);
}
void WebSocketClient::onConnectedInternal()
{
m_isConnected = true;
m_pingTimer->start();
// Аутентификация в Twitch
authenticate(m_oauthToken, m_nickname);
emit onConnected();
}
void WebSocketClient::onDisconnectedInternal()
{
m_isConnected = false;
m_pingTimer->stop();
m_channelJoined = false;
emit onDisconnected();
}
void WebSocketClient::send(const QString &data)
{
if (m_webSocket && m_isConnected) {
m_webSocket->sendTextMessage(data);
} else {
}
}
void WebSocketClient::sendMessage(const QString &message)
{
send(message);
}
void WebSocketClient::sendIrcCommand(const QString &command, const QString &parameters)
{
QString message = command;
if (!parameters.isEmpty()) {
message += " " + parameters;
}
message += "\r\n";
send(message);
}
void WebSocketClient::authenticate(const QString &oauthToken, const QString &nickname)
{
// PASS команда для аутентификации
if (!oauthToken.startsWith("oauth:")) {
sendIrcCommand("PASS", "oauth:" + oauthToken);
} else {
sendIrcCommand("PASS", oauthToken);
}
Sleep(1000);
// NICK команда
sendIrcCommand("NICK", nickname);
// CAP REQ для получения дополнительных возможностей
sendIrcCommand("CAP REQ", ":twitch.tv/membership twitch.tv/tags twitch.tv/commands");
}
void WebSocketClient::joinChannel(const QString &channel)
{
if (m_isConnected) {
m_channelJoined = true;
sendIrcCommand("JOIN", "#" + channel.toLower());
}
}
void WebSocketClient::sendChatMessage(const QString &channel, const QString &message)
{
if (m_isConnected) {
sendIrcCommand("PRIVMSG", "#" + channel.toLower() + " :" + message);
}
}
void WebSocketClient::pingServer()
{
if (m_isConnected) {
send("PING :tmi.twitch.tv\r\n");
}
}
void WebSocketClient::onPingTimeout()
{
pingServer();
}
void WebSocketClient::onTextMessageReceived(const QString &message)
{
// Обработка PING от сервера
if (message.startsWith("PING")) {
send("PONG :tmi.twitch.tv\r\n");
return;
}
// Обработка успешной аутентификации
if (message.contains("001") && !m_currentChannel.isEmpty()) {
joinChannel(m_currentChannel);
return; // Добавить return после первого успешного подключения
}
// Обработка сообщений чата
if (message.contains("PRIVMSG")) {
// Извлекаем информацию из сообщения
// Формат: :nick!nick@nick.tmi.twitch.tv PRIVMSG #channel :message
QString cleanMessage = message;
emit onNewMessage(cleanMessage);
// Удаляем служебную информацию для чистого отображения
int msgIndex = cleanMessage.indexOf("PRIVMSG");
if (msgIndex != -1) {
int channelIndex = cleanMessage.indexOf("#", msgIndex);
int messageIndex = cleanMessage.indexOf(" :", channelIndex);
if (messageIndex != -1) {
QString chatMessage = cleanMessage.mid(messageIndex + 2).trimmed();
QString sender = cleanMessage.mid(1, cleanMessage.indexOf("!") - 1);
QString channel = cleanMessage.mid(channelIndex + 1,
messageIndex - channelIndex - 1);
}
}
}
}
void WebSocketClient::onErrorInternal(QAbstractSocket::SocketError error)
{
QString errorMsg = QString("WebSocket error: %1 - %2")
.arg(error)
.arg(m_webSocket ? m_webSocket->errorString() : "Unknown error");
emit onError(errorMsg);
}
void WebSocketClient::onSslErrors(const QList<QSslError> &errors)
{
QStringList errorMessages;
foreach (const QSslError &error, errors) {
errorMessages.append(error.errorString());
}
// В разработке можно игнорировать SSL ошибки, но в продакшене это опасно
if (m_webSocket) {
m_webSocket->ignoreSslErrors();
}
}
void WebSocketClient::disconnectFromServer()
{
if (m_webSocket && m_isConnected) {
// Отправляем команду выхода из канала для Twitch
if (!m_currentChannel.isEmpty()) {
sendIrcCommand("PART", "#" + m_currentChannel.toLower());
}
m_webSocket->close();
m_isConnected = false;
m_pingTimer->stop();
}
}
bool WebSocketClient::isConnected() const
{
return m_isConnected;
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef WEBSOCKETCLIENT_H
#define WEBSOCKETCLIENT_H
#include <QObject>
#include <QWebSocket>
#include <QUrl>
#include <QTimer>
#include <QNetworkRequest>
#include <QNetworkReply>
class WebSocketClient : public QObject
{
Q_OBJECT
public:
explicit WebSocketClient(QObject *parent = nullptr);
~WebSocketClient();
// Внешние методы
Q_INVOKABLE bool connectToServer(const QString &url);
Q_INVOKABLE void disconnectFromServer();
Q_INVOKABLE void sendMessage(const QString &message);
Q_INVOKABLE bool isConnected() const;
// Методы для Twitch чата
Q_INVOKABLE void connectToTwitchChat(const QString &oauthToken, const QString &nickname, const QString &channel);
Q_INVOKABLE void joinChannel(const QString &channel);
Q_INVOKABLE void sendChatMessage(const QString &channel, const QString &message);
signals:
// Внешние сигналы
void onNewMessage(const QString &message);
void onConnected();
void onDisconnected();
void onError(const QString &errorMessage);
private slots:
// Внутренние слоты
void onConnectedInternal();
void onDisconnectedInternal();
void onTextMessageReceived(const QString &message);
void onErrorInternal(QAbstractSocket::SocketError error);
void onSslErrors(const QList<QSslError> &errors);
void onPingTimeout();
void pingServer();
private:
// Внутренние методы
void send(const QString &data);
void sendIrcCommand(const QString &command, const QString &parameters = "");
void authenticate(const QString &oauthToken, const QString &nickname);
private:
QWebSocket *m_webSocket;
QTimer *m_pingTimer;
bool m_isConnected;
QString m_nickname;
QString m_currentChannel;
QString m_oauthToken;
bool m_channelJoined;
};
#endif // WEBSOCKETCLIENT_H