349 lines
9.7 KiB
C++
349 lines
9.7 KiB
C++
|
|
#include "mainwindow.h"
|
||
|
|
#include "ui/login_dialog.h"
|
||
|
|
#include <QSplitter>
|
||
|
|
#include <QVBoxLayout>
|
||
|
|
#include <QHBoxLayout>
|
||
|
|
#include <QMenuBar>
|
||
|
|
#include <QMenu>
|
||
|
|
#include <QMessageBox>
|
||
|
|
#include <QDateTime>
|
||
|
|
|
||
|
|
namespace scar {
|
||
|
|
|
||
|
|
MainWindow::MainWindow(QWidget* parent)
|
||
|
|
: QMainWindow(parent) {
|
||
|
|
|
||
|
|
setWindowTitle("SCAR Chat");
|
||
|
|
resize(1200, 800);
|
||
|
|
|
||
|
|
config_ = std::make_unique<ClientConfig>();
|
||
|
|
config_->load();
|
||
|
|
|
||
|
|
connection_ = std::make_unique<ClientConnection>(this);
|
||
|
|
|
||
|
|
// Connect signals
|
||
|
|
connect(connection_.get(), &ClientConnection::connected, this, &MainWindow::onConnected);
|
||
|
|
connect(connection_.get(), &ClientConnection::disconnected, this, &MainWindow::onDisconnected);
|
||
|
|
connect(connection_.get(), &ClientConnection::loginSuccess, this, &MainWindow::onLoginSuccess);
|
||
|
|
connect(connection_.get(), &ClientConnection::loginFailed, this, &MainWindow::onLoginFailed);
|
||
|
|
connect(connection_.get(), &ClientConnection::messageReceived, this, &MainWindow::onMessageReceived);
|
||
|
|
|
||
|
|
setupUI();
|
||
|
|
applyDarkTheme();
|
||
|
|
createMenuBar();
|
||
|
|
setupStatusBar();
|
||
|
|
|
||
|
|
// Clock timer
|
||
|
|
clockTimer_ = new QTimer(this);
|
||
|
|
connect(clockTimer_, &QTimer::timeout, this, &MainWindow::updateClock);
|
||
|
|
clockTimer_->start(1000);
|
||
|
|
updateClock();
|
||
|
|
|
||
|
|
// Show login dialog
|
||
|
|
QTimer::singleShot(100, this, &MainWindow::showLoginDialog);
|
||
|
|
}
|
||
|
|
|
||
|
|
MainWindow::~MainWindow() {
|
||
|
|
if (config_) {
|
||
|
|
config_->save();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::setupUI() {
|
||
|
|
auto* centralWidget = new QWidget(this);
|
||
|
|
setCentralWidget(centralWidget);
|
||
|
|
|
||
|
|
auto* mainLayout = new QHBoxLayout(centralWidget);
|
||
|
|
mainLayout->setSpacing(0);
|
||
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||
|
|
|
||
|
|
// Left splitter: User list and chat/video
|
||
|
|
auto* leftSplitter = new QSplitter(Qt::Horizontal, this);
|
||
|
|
|
||
|
|
// User list (left sidebar)
|
||
|
|
userListWidget_ = new UserListWidget(this);
|
||
|
|
userListWidget_->setMinimumWidth(200);
|
||
|
|
userListWidget_->setMaximumWidth(300);
|
||
|
|
leftSplitter->addWidget(userListWidget_);
|
||
|
|
|
||
|
|
// Right splitter: Chat and video grid
|
||
|
|
auto* rightSplitter = new QSplitter(Qt::Vertical, this);
|
||
|
|
|
||
|
|
// Chat widget
|
||
|
|
chatWidget_ = new ChatWidget(this);
|
||
|
|
connect(chatWidget_, &ChatWidget::messageSent, this, &MainWindow::onSendMessage);
|
||
|
|
rightSplitter->addWidget(chatWidget_);
|
||
|
|
|
||
|
|
// Video grid widget
|
||
|
|
videoGridWidget_ = new VideoGridWidget(this);
|
||
|
|
videoGridWidget_->setMinimumHeight(200);
|
||
|
|
rightSplitter->addWidget(videoGridWidget_);
|
||
|
|
|
||
|
|
// Set initial sizes (60% chat, 40% video)
|
||
|
|
rightSplitter->setStretchFactor(0, 60);
|
||
|
|
rightSplitter->setStretchFactor(1, 40);
|
||
|
|
|
||
|
|
leftSplitter->addWidget(rightSplitter);
|
||
|
|
|
||
|
|
// Set initial sizes (20% users, 80% content)
|
||
|
|
leftSplitter->setStretchFactor(0, 20);
|
||
|
|
leftSplitter->setStretchFactor(1, 80);
|
||
|
|
|
||
|
|
mainLayout->addWidget(leftSplitter);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::setupStatusBar() {
|
||
|
|
// Connection status (left)
|
||
|
|
connectionStatusLabel_ = new QLabel("Disconnected", this);
|
||
|
|
connectionStatusLabel_->setStyleSheet("color: #ED4245; padding: 2px 10px;"); // Red
|
||
|
|
statusBar()->addWidget(connectionStatusLabel_);
|
||
|
|
|
||
|
|
// User count (left)
|
||
|
|
userCountLabel_ = new QLabel("Users: 0", this);
|
||
|
|
userCountLabel_->setStyleSheet("padding: 2px 10px;");
|
||
|
|
statusBar()->addWidget(userCountLabel_);
|
||
|
|
|
||
|
|
// Spacer
|
||
|
|
statusBar()->addWidget(new QWidget(this), 1);
|
||
|
|
|
||
|
|
// Clock (center)
|
||
|
|
clockLabel_ = new QLabel(this);
|
||
|
|
clockLabel_->setStyleSheet("padding: 2px 10px;");
|
||
|
|
statusBar()->addPermanentWidget(clockLabel_);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::applyDarkTheme() {
|
||
|
|
// Discord-inspired dark theme
|
||
|
|
QString styleSheet = R"(
|
||
|
|
QMainWindow {
|
||
|
|
background-color: #36393F;
|
||
|
|
}
|
||
|
|
|
||
|
|
QWidget {
|
||
|
|
background-color: #36393F;
|
||
|
|
color: #DCDDDE;
|
||
|
|
font-family: "Segoe UI", Arial, sans-serif;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
QListWidget {
|
||
|
|
background-color: #2F3136;
|
||
|
|
border: none;
|
||
|
|
outline: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
QListWidget::item {
|
||
|
|
padding: 5px;
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
QListWidget::item:hover {
|
||
|
|
background-color: #3A3C42;
|
||
|
|
}
|
||
|
|
|
||
|
|
QListWidget::item:selected {
|
||
|
|
background-color: #5865F2;
|
||
|
|
}
|
||
|
|
|
||
|
|
QTextEdit {
|
||
|
|
background-color: #40444B;
|
||
|
|
border: none;
|
||
|
|
color: #DCDDDE;
|
||
|
|
}
|
||
|
|
|
||
|
|
QLineEdit {
|
||
|
|
background-color: #40444B;
|
||
|
|
border: none;
|
||
|
|
border-radius: 3px;
|
||
|
|
padding: 8px;
|
||
|
|
color: #DCDDDE;
|
||
|
|
}
|
||
|
|
|
||
|
|
QLineEdit:focus {
|
||
|
|
background-color: #383A40;
|
||
|
|
}
|
||
|
|
|
||
|
|
QPushButton {
|
||
|
|
background-color: #5865F2;
|
||
|
|
border: none;
|
||
|
|
border-radius: 3px;
|
||
|
|
padding: 8px 16px;
|
||
|
|
color: white;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
QPushButton:hover {
|
||
|
|
background-color: #4752C4;
|
||
|
|
}
|
||
|
|
|
||
|
|
QPushButton:pressed {
|
||
|
|
background-color: #3C45A5;
|
||
|
|
}
|
||
|
|
|
||
|
|
QStatusBar {
|
||
|
|
background-color: #202225;
|
||
|
|
color: #B9BBBE;
|
||
|
|
}
|
||
|
|
|
||
|
|
QMenuBar {
|
||
|
|
background-color: #202225;
|
||
|
|
color: #DCDDDE;
|
||
|
|
border-bottom: 1px solid #000;
|
||
|
|
}
|
||
|
|
|
||
|
|
QMenuBar::item:selected {
|
||
|
|
background-color: #5865F2;
|
||
|
|
}
|
||
|
|
|
||
|
|
QMenu {
|
||
|
|
background-color: #2F3136;
|
||
|
|
color: #DCDDDE;
|
||
|
|
border: 1px solid #202225;
|
||
|
|
}
|
||
|
|
|
||
|
|
QMenu::item:selected {
|
||
|
|
background-color: #5865F2;
|
||
|
|
}
|
||
|
|
|
||
|
|
QSplitter::handle {
|
||
|
|
background-color: #202225;
|
||
|
|
}
|
||
|
|
|
||
|
|
QDialog {
|
||
|
|
background-color: #36393F;
|
||
|
|
}
|
||
|
|
|
||
|
|
QLabel {
|
||
|
|
background-color: transparent;
|
||
|
|
}
|
||
|
|
|
||
|
|
QSpinBox {
|
||
|
|
background-color: #40444B;
|
||
|
|
border: none;
|
||
|
|
border-radius: 3px;
|
||
|
|
padding: 8px;
|
||
|
|
color: #DCDDDE;
|
||
|
|
}
|
||
|
|
)";
|
||
|
|
|
||
|
|
setStyleSheet(styleSheet);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::createMenuBar() {
|
||
|
|
auto* fileMenu = menuBar()->addMenu("&File");
|
||
|
|
|
||
|
|
auto* connectAction = fileMenu->addAction("&Connect");
|
||
|
|
connect(connectAction, &QAction::triggered, this, &MainWindow::showLoginDialog);
|
||
|
|
|
||
|
|
auto* disconnectAction = fileMenu->addAction("&Disconnect");
|
||
|
|
connect(disconnectAction, &QAction::triggered, [this]() {
|
||
|
|
connection_->disconnect();
|
||
|
|
});
|
||
|
|
|
||
|
|
fileMenu->addSeparator();
|
||
|
|
|
||
|
|
auto* exitAction = fileMenu->addAction("E&xit");
|
||
|
|
connect(exitAction, &QAction::triggered, this, &QMainWindow::close);
|
||
|
|
|
||
|
|
auto* helpMenu = menuBar()->addMenu("&Help");
|
||
|
|
|
||
|
|
auto* aboutAction = helpMenu->addAction("&About");
|
||
|
|
connect(aboutAction, &QAction::triggered, [this]() {
|
||
|
|
QMessageBox::about(this, "About SCAR Chat",
|
||
|
|
"SCAR Chat v1.0.0\n\n"
|
||
|
|
"A secure cross-platform chat application with video streaming.\n\n"
|
||
|
|
"Built with C++20, Qt6, Boost.ASIO, and SQLite3.");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::showLoginDialog() {
|
||
|
|
LoginDialog dialog(
|
||
|
|
QString::fromStdString(config_->lastUsername()),
|
||
|
|
QString::fromStdString(config_->lastServer()),
|
||
|
|
config_->lastPort(),
|
||
|
|
this
|
||
|
|
);
|
||
|
|
|
||
|
|
if (dialog.exec() == QDialog::Accepted) {
|
||
|
|
currentUsername_ = dialog.username();
|
||
|
|
|
||
|
|
config_->setLastUsername(dialog.username().toStdString());
|
||
|
|
config_->setLastServer(dialog.server().toStdString());
|
||
|
|
config_->setLastPort(dialog.port());
|
||
|
|
config_->save();
|
||
|
|
|
||
|
|
updateConnectionStatus("Connecting...");
|
||
|
|
|
||
|
|
connection_->connectToServer(
|
||
|
|
dialog.server().toStdString(),
|
||
|
|
dialog.port(),
|
||
|
|
dialog.username().toStdString(),
|
||
|
|
dialog.password().toStdString()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onConnected() {
|
||
|
|
updateConnectionStatus("Connected");
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onDisconnected() {
|
||
|
|
updateConnectionStatus("Disconnected");
|
||
|
|
userListWidget_->clear();
|
||
|
|
updateUserCount(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onLoginSuccess(const QString& token) {
|
||
|
|
config_->setJwtToken(token.toStdString());
|
||
|
|
config_->save();
|
||
|
|
|
||
|
|
updateConnectionStatus("Authenticated");
|
||
|
|
|
||
|
|
// Add self to user list
|
||
|
|
UserInfo self;
|
||
|
|
self.username = currentUsername_;
|
||
|
|
self.status = "Online";
|
||
|
|
userListWidget_->addUser(self);
|
||
|
|
updateUserCount(userListWidget_->userCount());
|
||
|
|
|
||
|
|
// Load chat history
|
||
|
|
chatWidget_->loadHistory();
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onLoginFailed(const QString& error) {
|
||
|
|
QMessageBox::critical(this, "Login Failed", error);
|
||
|
|
updateConnectionStatus("Login failed");
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onMessageReceived(const QString& sender, const QString& content) {
|
||
|
|
chatWidget_->addMessage(sender, content, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::onSendMessage(const QString& content) {
|
||
|
|
connection_->sendTextMessage(content.toStdString());
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::updateClock() {
|
||
|
|
QString currentTime = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||
|
|
clockLabel_->setText(currentTime);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::updateConnectionStatus(const QString& status) {
|
||
|
|
connectionStatusLabel_->setText(status);
|
||
|
|
|
||
|
|
// Color coding
|
||
|
|
if (status.contains("Connected") || status.contains("Authenticated")) {
|
||
|
|
connectionStatusLabel_->setStyleSheet("color: #43B581; padding: 2px 10px;"); // Green
|
||
|
|
} else if (status.contains("Connecting")) {
|
||
|
|
connectionStatusLabel_->setStyleSheet("color: #FAA61A; padding: 2px 10px;"); // Yellow
|
||
|
|
} else {
|
||
|
|
connectionStatusLabel_->setStyleSheet("color: #ED4245; padding: 2px 10px;"); // Red
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MainWindow::updateUserCount(int count) {
|
||
|
|
userCountLabel_->setText(QString("Users: %1").arg(count));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace scar
|