scar-chat7/client/mainwindow.cpp

357 lines
10 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);
videoGridWidget_->setConnection(connection_.get());
// Connect screen share signal with QueuedConnection to avoid re-entrancy issues
// This ensures frames are processed sequentially in the event queue
connect(connection_.get(), &ClientConnection::screenShareFrameReceived,
videoGridWidget_, &VideoGridWidget::onScreenShareFrameReceived,
Qt::QueuedConnection);
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