2025-12-04 19:46:54 -07:00
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QMainWindow>
|
|
|
|
|
#include <QWidget>
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QTextEdit>
|
|
|
|
|
#include <QLineEdit>
|
|
|
|
|
#include <QPushButton>
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QColorDialog>
|
|
|
|
|
#include <QSlider>
|
|
|
|
|
#include <QSpinBox>
|
|
|
|
|
#include <QSslSocket>
|
|
|
|
|
#include <QHostAddress>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <QThread>
|
2025-12-04 21:22:58 -07:00
|
|
|
#include <QTabWidget>
|
|
|
|
|
#include <QCamera>
|
|
|
|
|
#include <QCameraInfo>
|
|
|
|
|
#include <QCheckBox>
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
#include <QGridLayout>
|
|
|
|
|
#include <QScrollArea>
|
|
|
|
|
#include <QImage>
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
#include <QTimer>
|
2025-12-05 09:14:41 -07:00
|
|
|
#include <QDialog>
|
2025-12-04 19:46:54 -07:00
|
|
|
#include <iostream>
|
2025-12-04 21:22:58 -07:00
|
|
|
#include <mutex>
|
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
|
|
class VideoFeedWidget : public QWidget {
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
VideoFeedWidget(const QString &username, QWidget *parent = nullptr)
|
|
|
|
|
: QWidget(parent), m_username(username) {
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
|
|
|
layout->setContentsMargins(5, 5, 5, 5);
|
|
|
|
|
|
|
|
|
|
// Title
|
|
|
|
|
QLabel *title = new QLabel(m_username + " (Offline)", this);
|
|
|
|
|
title->setStyleSheet("font-weight: bold; font-size: 12px;");
|
|
|
|
|
layout->addWidget(title);
|
|
|
|
|
|
|
|
|
|
// Video placeholder
|
|
|
|
|
m_video_label = new QLabel(this);
|
|
|
|
|
m_video_label->setMinimumSize(320, 240);
|
|
|
|
|
m_video_label->setStyleSheet("border: 2px solid gray; background-color: black;");
|
|
|
|
|
m_video_label->setAlignment(Qt::AlignCenter);
|
|
|
|
|
m_video_label->setText("Waiting for video...");
|
|
|
|
|
layout->addWidget(m_video_label);
|
|
|
|
|
|
|
|
|
|
setStyleSheet("border: 1px solid gray; padding: 5px;");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void update_video_frame(const QPixmap &pixmap) {
|
|
|
|
|
m_video_label->setPixmap(pixmap.scaled(320, 240, Qt::KeepAspectRatio));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void set_status(bool online) {
|
|
|
|
|
QLabel *title = findChild<QLabel *>();
|
|
|
|
|
if (title) {
|
|
|
|
|
title->setText(m_username + (online ? " (Online)" : " (Offline)"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_username;
|
|
|
|
|
QLabel *m_video_label;
|
|
|
|
|
};
|
2025-12-04 19:46:54 -07:00
|
|
|
|
2025-12-05 09:14:41 -07:00
|
|
|
// Login dialog for authentication
|
|
|
|
|
class LoginDialog : public QDialog {
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
LoginDialog(QWidget *parent = nullptr) : QDialog(parent) {
|
|
|
|
|
setWindowTitle("SCAR Chat - Login");
|
|
|
|
|
setModal(true);
|
|
|
|
|
setGeometry(100, 100, 400, 300);
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
|
|
|
|
|
|
|
|
// Title
|
|
|
|
|
QLabel *title = new QLabel("SCAR Chat", this);
|
|
|
|
|
title->setStyleSheet("font-size: 24px; font-weight: bold;");
|
|
|
|
|
title->setAlignment(Qt::AlignCenter);
|
|
|
|
|
layout->addWidget(title);
|
|
|
|
|
|
|
|
|
|
layout->addSpacing(20);
|
|
|
|
|
|
|
|
|
|
// Server address
|
|
|
|
|
QHBoxLayout *server_layout = new QHBoxLayout();
|
|
|
|
|
QLabel *server_label = new QLabel("Server:", this);
|
|
|
|
|
server_input = new QLineEdit("localhost", this);
|
|
|
|
|
server_layout->addWidget(server_label);
|
|
|
|
|
server_layout->addWidget(server_input);
|
|
|
|
|
layout->addLayout(server_layout);
|
|
|
|
|
|
|
|
|
|
// Server port
|
|
|
|
|
QHBoxLayout *port_layout = new QHBoxLayout();
|
|
|
|
|
QLabel *port_label = new QLabel("Port:", this);
|
|
|
|
|
port_input = new QLineEdit("42317", this);
|
|
|
|
|
port_input->setMaximumWidth(100);
|
|
|
|
|
port_layout->addWidget(port_label);
|
|
|
|
|
port_layout->addWidget(port_input);
|
|
|
|
|
port_layout->addStretch();
|
|
|
|
|
layout->addLayout(port_layout);
|
|
|
|
|
|
|
|
|
|
layout->addSpacing(15);
|
|
|
|
|
|
|
|
|
|
// Username
|
|
|
|
|
QHBoxLayout *user_layout = new QHBoxLayout();
|
|
|
|
|
QLabel *user_label = new QLabel("Username:", this);
|
|
|
|
|
username_input = new QLineEdit(this);
|
|
|
|
|
user_layout->addWidget(user_label);
|
|
|
|
|
user_layout->addWidget(username_input);
|
|
|
|
|
layout->addLayout(user_layout);
|
|
|
|
|
|
|
|
|
|
// Password
|
|
|
|
|
QHBoxLayout *pass_layout = new QHBoxLayout();
|
|
|
|
|
QLabel *pass_label = new QLabel("Password:", this);
|
|
|
|
|
password_input = new QLineEdit(this);
|
|
|
|
|
password_input->setEchoMode(QLineEdit::Password);
|
|
|
|
|
pass_layout->addWidget(pass_label);
|
|
|
|
|
pass_layout->addWidget(password_input);
|
|
|
|
|
layout->addLayout(pass_layout);
|
|
|
|
|
|
|
|
|
|
layout->addSpacing(15);
|
|
|
|
|
|
|
|
|
|
// Status label
|
|
|
|
|
status_label = new QLabel("", this);
|
|
|
|
|
status_label->setAlignment(Qt::AlignCenter);
|
|
|
|
|
status_label->setStyleSheet("color: #666;");
|
|
|
|
|
layout->addWidget(status_label);
|
|
|
|
|
|
|
|
|
|
// Buttons
|
|
|
|
|
QHBoxLayout *btn_layout = new QHBoxLayout();
|
|
|
|
|
login_btn = new QPushButton("Login", this);
|
|
|
|
|
cancel_btn = new QPushButton("Cancel", this);
|
|
|
|
|
btn_layout->addStretch();
|
|
|
|
|
btn_layout->addWidget(login_btn);
|
|
|
|
|
btn_layout->addWidget(cancel_btn);
|
|
|
|
|
layout->addLayout(btn_layout);
|
|
|
|
|
|
|
|
|
|
connect(login_btn, &QPushButton::clicked, this, &LoginDialog::accept);
|
|
|
|
|
connect(cancel_btn, &QPushButton::clicked, this, &LoginDialog::reject);
|
|
|
|
|
connect(password_input, &QLineEdit::returnPressed, this, &LoginDialog::accept);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString get_server() const { return server_input->text(); }
|
|
|
|
|
int get_port() const { return port_input->text().toInt(); }
|
|
|
|
|
QString get_username() const { return username_input->text(); }
|
|
|
|
|
QString get_password() const { return password_input->text(); }
|
|
|
|
|
|
|
|
|
|
void set_status(const QString &msg) { status_label->setText(msg); }
|
|
|
|
|
void set_login_enabled(bool enabled) { login_btn->setEnabled(enabled); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QLineEdit *server_input;
|
|
|
|
|
QLineEdit *port_input;
|
|
|
|
|
QLineEdit *username_input;
|
|
|
|
|
QLineEdit *password_input;
|
|
|
|
|
QLabel *status_label;
|
|
|
|
|
QPushButton *login_btn;
|
|
|
|
|
QPushButton *cancel_btn;
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-04 19:46:54 -07:00
|
|
|
class ChatClient : public QMainWindow {
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
2025-12-05 09:14:41 -07:00
|
|
|
ChatClient(QWidget *parent = nullptr) : QMainWindow(parent), m_camera(nullptr), m_authenticated(false) {
|
|
|
|
|
setWindowTitle("SCAR Chat Client");
|
2025-12-04 21:22:58 -07:00
|
|
|
setGeometry(100, 100, 1000, 700);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
2025-12-04 21:22:58 -07:00
|
|
|
// Create central widget with tabs
|
2025-12-04 19:46:54 -07:00
|
|
|
QWidget *central = new QWidget(this);
|
|
|
|
|
setCentralWidget(central);
|
|
|
|
|
|
|
|
|
|
QVBoxLayout *main_layout = new QVBoxLayout(central);
|
|
|
|
|
|
2025-12-05 09:14:41 -07:00
|
|
|
// Status bar showing logged-in user
|
|
|
|
|
status_label = new QLabel("Not authenticated", this);
|
|
|
|
|
status_label->setStyleSheet("font-weight: bold;");
|
|
|
|
|
main_layout->addWidget(status_label);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
2025-12-04 21:22:58 -07:00
|
|
|
// Tab widget for chat and video
|
|
|
|
|
QTabWidget *tabs = new QTabWidget(this);
|
|
|
|
|
|
|
|
|
|
// ==================== CHAT TAB ====================
|
|
|
|
|
QWidget *chat_tab = new QWidget(this);
|
|
|
|
|
QVBoxLayout *chat_layout = new QVBoxLayout(chat_tab);
|
|
|
|
|
|
2025-12-04 19:46:54 -07:00
|
|
|
// Chat display
|
|
|
|
|
chat_display = new QTextEdit(this);
|
|
|
|
|
chat_display->setReadOnly(true);
|
2025-12-04 21:22:58 -07:00
|
|
|
chat_layout->addWidget(chat_display);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
|
|
|
|
// Message input
|
|
|
|
|
QHBoxLayout *msg_layout = new QHBoxLayout();
|
|
|
|
|
message_input = new QLineEdit(this);
|
|
|
|
|
send_btn = new QPushButton("Send", this);
|
|
|
|
|
msg_layout->addWidget(message_input);
|
|
|
|
|
msg_layout->addWidget(send_btn);
|
2025-12-04 21:22:58 -07:00
|
|
|
chat_layout->addLayout(msg_layout);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
2025-12-04 21:22:58 -07:00
|
|
|
// Chat settings
|
|
|
|
|
QHBoxLayout *chat_settings = new QHBoxLayout();
|
2025-12-04 19:46:54 -07:00
|
|
|
bg_color_btn = new QPushButton("BG Color", this);
|
|
|
|
|
text_color_btn = new QPushButton("Text Color", this);
|
|
|
|
|
QLabel *trans_label = new QLabel("Transparency:", this);
|
|
|
|
|
transparency_slider = new QSlider(Qt::Horizontal, this);
|
|
|
|
|
transparency_slider->setMinimum(0);
|
|
|
|
|
transparency_slider->setMaximum(100);
|
|
|
|
|
transparency_slider->setValue(100);
|
|
|
|
|
transparency_value = new QLabel("100%", this);
|
2025-12-04 21:22:58 -07:00
|
|
|
chat_settings->addWidget(bg_color_btn);
|
|
|
|
|
chat_settings->addWidget(text_color_btn);
|
|
|
|
|
chat_settings->addWidget(trans_label);
|
|
|
|
|
chat_settings->addWidget(transparency_slider);
|
|
|
|
|
chat_settings->addWidget(transparency_value);
|
|
|
|
|
chat_layout->addLayout(chat_settings);
|
|
|
|
|
|
|
|
|
|
tabs->addTab(chat_tab, "Chat");
|
|
|
|
|
|
|
|
|
|
// ==================== VIDEO TAB ====================
|
|
|
|
|
QWidget *video_tab = new QWidget(this);
|
|
|
|
|
QVBoxLayout *video_layout = new QVBoxLayout(video_tab);
|
|
|
|
|
|
|
|
|
|
// Camera controls
|
|
|
|
|
QHBoxLayout *camera_ctrl = new QHBoxLayout();
|
|
|
|
|
camera_combo = new QComboBox(this);
|
|
|
|
|
camera_toggle = new QCheckBox("Enable Camera", this);
|
|
|
|
|
camera_toggle->setChecked(false);
|
|
|
|
|
camera_info_label = new QLabel("No camera selected", this);
|
|
|
|
|
camera_ctrl->addWidget(new QLabel("Camera:", this));
|
|
|
|
|
camera_ctrl->addWidget(camera_combo);
|
|
|
|
|
camera_ctrl->addWidget(camera_toggle);
|
|
|
|
|
camera_ctrl->addWidget(camera_info_label);
|
|
|
|
|
camera_ctrl->addStretch();
|
|
|
|
|
video_layout->addLayout(camera_ctrl);
|
|
|
|
|
|
|
|
|
|
// Local camera view
|
|
|
|
|
QLabel *local_title = new QLabel("Your Camera Feed:", this);
|
|
|
|
|
local_title->setStyleSheet("font-weight: bold;");
|
|
|
|
|
video_layout->addWidget(local_title);
|
|
|
|
|
|
|
|
|
|
local_video_label = new QLabel(this);
|
|
|
|
|
local_video_label->setMinimumSize(640, 480);
|
|
|
|
|
local_video_label->setMaximumSize(640, 480);
|
|
|
|
|
local_video_label->setStyleSheet("border: 2px solid gray; background-color: black;");
|
|
|
|
|
local_video_label->setAlignment(Qt::AlignCenter);
|
|
|
|
|
local_video_label->setText("Camera Disabled\nClick 'Enable Camera' to start");
|
|
|
|
|
video_layout->addWidget(local_video_label, 0, Qt::AlignHCenter);
|
|
|
|
|
|
|
|
|
|
// Remote video feeds
|
|
|
|
|
QLabel *remote_title = new QLabel("Connected Users:", this);
|
|
|
|
|
remote_title->setStyleSheet("font-weight: bold; margin-top: 20px;");
|
|
|
|
|
video_layout->addWidget(remote_title);
|
|
|
|
|
|
|
|
|
|
// Scroll area for remote feeds
|
|
|
|
|
QScrollArea *scroll = new QScrollArea(this);
|
|
|
|
|
scroll->setWidgetResizable(true);
|
|
|
|
|
QWidget *scroll_content = new QWidget(this);
|
|
|
|
|
remote_feeds_layout = new QGridLayout(scroll_content);
|
|
|
|
|
scroll->setWidget(scroll_content);
|
|
|
|
|
video_layout->addWidget(scroll);
|
|
|
|
|
|
|
|
|
|
tabs->addTab(video_tab, "Video Feeds");
|
|
|
|
|
|
|
|
|
|
main_layout->addWidget(tabs);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
|
|
|
|
// Connect signals
|
|
|
|
|
connect(send_btn, &QPushButton::clicked, this, &ChatClient::on_send_message);
|
|
|
|
|
connect(message_input, &QLineEdit::returnPressed, this, &ChatClient::on_send_message);
|
|
|
|
|
connect(bg_color_btn, &QPushButton::clicked, this, &ChatClient::on_bg_color);
|
|
|
|
|
connect(text_color_btn, &QPushButton::clicked, this, &ChatClient::on_text_color);
|
|
|
|
|
connect(transparency_slider, &QSlider::valueChanged, this, &ChatClient::on_transparency_changed);
|
2025-12-04 21:22:58 -07:00
|
|
|
connect(camera_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ChatClient::on_camera_changed);
|
|
|
|
|
connect(camera_toggle, &QCheckBox::stateChanged, this, &ChatClient::on_camera_toggle);
|
2025-12-04 19:46:54 -07:00
|
|
|
|
|
|
|
|
// Initialize socket
|
|
|
|
|
socket = new QSslSocket(this);
|
|
|
|
|
connect(socket, &QSslSocket::connected, this, &ChatClient::on_socket_connected);
|
|
|
|
|
connect(socket, &QSslSocket::disconnected, this, &ChatClient::on_socket_disconnected);
|
|
|
|
|
connect(socket, &QSslSocket::readyRead, this, &ChatClient::on_socket_read);
|
2025-12-04 21:22:58 -07:00
|
|
|
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::errorOccurred),
|
2025-12-04 19:46:54 -07:00
|
|
|
this, &ChatClient::on_socket_error);
|
|
|
|
|
connect(socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
|
|
|
|
|
this, &ChatClient::on_ssl_errors);
|
|
|
|
|
|
|
|
|
|
// Initialize colors
|
|
|
|
|
bg_color = Qt::white;
|
|
|
|
|
text_color = Qt::black;
|
|
|
|
|
update_window_colors();
|
2025-12-04 21:22:58 -07:00
|
|
|
|
|
|
|
|
// Populate camera list
|
|
|
|
|
populate_cameras();
|
|
|
|
|
|
|
|
|
|
chat_display->append("[System] SCAR Chat Client started.");
|
|
|
|
|
chat_display->append("[Info] Camera detection active.");
|
|
|
|
|
chat_display->append("[Info] Select a camera and enable it to start video capture.");
|
2025-12-05 09:14:41 -07:00
|
|
|
|
|
|
|
|
// Show login dialog
|
|
|
|
|
show_login_dialog();
|
2025-12-04 21:22:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~ChatClient() {
|
|
|
|
|
if (m_camera) {
|
|
|
|
|
m_camera->stop();
|
|
|
|
|
delete m_camera;
|
|
|
|
|
}
|
2025-12-04 19:46:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private slots:
|
2025-12-05 09:14:41 -07:00
|
|
|
void show_login_dialog() {
|
|
|
|
|
LoginDialog dialog(this);
|
|
|
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
|
|
|
QString server = dialog.get_server();
|
|
|
|
|
int port = dialog.get_port();
|
|
|
|
|
QString username = dialog.get_username();
|
|
|
|
|
QString password = dialog.get_password();
|
|
|
|
|
|
|
|
|
|
if (server.isEmpty() || username.isEmpty() || password.isEmpty()) {
|
|
|
|
|
QMessageBox::warning(this, "Error", "Please fill in all fields");
|
|
|
|
|
show_login_dialog();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chat_display->append("[System] Connecting to " + server + ":" + QString::number(port) + "...");
|
|
|
|
|
m_current_username = username;
|
|
|
|
|
m_current_password = password;
|
|
|
|
|
m_login_dialog_server = server;
|
|
|
|
|
m_login_dialog_port = port;
|
|
|
|
|
|
|
|
|
|
socket->connectToHostEncrypted(server, port);
|
|
|
|
|
} else {
|
|
|
|
|
// User cancelled login
|
|
|
|
|
QApplication::quit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 21:22:58 -07:00
|
|
|
void populate_cameras() {
|
|
|
|
|
camera_combo->clear();
|
|
|
|
|
const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
|
|
|
|
if (cameras.isEmpty()) {
|
|
|
|
|
camera_combo->addItem("No camera detected");
|
|
|
|
|
camera_toggle->setEnabled(false);
|
|
|
|
|
camera_info_label->setText("No cameras found on this system");
|
|
|
|
|
chat_display->append("[Warning] No cameras detected on system");
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = 0; i < cameras.size(); ++i) {
|
|
|
|
|
const QCameraInfo &camera = cameras.at(i);
|
|
|
|
|
QString device_name = camera.deviceName();
|
|
|
|
|
QString description = camera.description();
|
|
|
|
|
|
|
|
|
|
QString label = description.isEmpty() ? ("Camera " + QString::number(i)) : description;
|
|
|
|
|
camera_combo->addItem(label, device_name);
|
|
|
|
|
}
|
|
|
|
|
camera_toggle->setEnabled(true);
|
|
|
|
|
if (!cameras.isEmpty()) {
|
|
|
|
|
camera_info_label->setText("Available cameras: " + QString::number(cameras.size()));
|
|
|
|
|
chat_display->append("[System] Found " + QString::number(cameras.size()) + " camera(s)");
|
|
|
|
|
for (int i = 0; i < cameras.size(); ++i) {
|
|
|
|
|
chat_display->append(" [" + QString::number(i + 1) + "] " + cameras.at(i).description());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_camera_changed(int index) {
|
|
|
|
|
if (index < 0) return;
|
|
|
|
|
|
|
|
|
|
if (m_camera && camera_toggle->isChecked()) {
|
|
|
|
|
m_camera->stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString device_name = camera_combo->currentData().toString();
|
|
|
|
|
if (device_name.isEmpty()) {
|
|
|
|
|
camera_info_label->setText("Invalid camera selection");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QCameraInfo camera_info(device_name.toLatin1());
|
|
|
|
|
|
|
|
|
|
if (m_camera) {
|
|
|
|
|
delete m_camera;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_camera = new QCamera(camera_info, this);
|
|
|
|
|
|
|
|
|
|
QString cam_desc = camera_info.description();
|
|
|
|
|
if (cam_desc.isEmpty()) {
|
|
|
|
|
cam_desc = camera_combo->currentText();
|
|
|
|
|
}
|
|
|
|
|
camera_info_label->setText("Selected: " + cam_desc);
|
|
|
|
|
chat_display->append("[System] Camera changed to: " + cam_desc);
|
|
|
|
|
|
|
|
|
|
if (camera_toggle->isChecked()) {
|
|
|
|
|
start_camera();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_camera_toggle(int state) {
|
|
|
|
|
if (state == Qt::Checked) {
|
|
|
|
|
start_camera();
|
|
|
|
|
} else {
|
|
|
|
|
stop_camera();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void start_camera() {
|
|
|
|
|
if (!m_camera) {
|
|
|
|
|
chat_display->append("[Error] No camera selected");
|
|
|
|
|
camera_toggle->setChecked(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
m_camera->start();
|
|
|
|
|
chat_display->append("[System] Camera enabled: " + camera_info_label->text());
|
|
|
|
|
local_video_label->setText("Camera Active\n(Video capture enabled)");
|
|
|
|
|
local_video_label->setStyleSheet("border: 2px solid green; background-color: #1a1a1a; color: white;");
|
|
|
|
|
|
|
|
|
|
if (socket->state() == QSslSocket::ConnectedState) {
|
|
|
|
|
socket->write("CAMERA_ENABLE\n");
|
|
|
|
|
}
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
chat_display->append("[Error] Failed to start camera: " + QString(e.what()));
|
|
|
|
|
camera_toggle->setChecked(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void stop_camera() {
|
|
|
|
|
if (m_camera) {
|
|
|
|
|
m_camera->stop();
|
|
|
|
|
}
|
|
|
|
|
chat_display->append("[System] Camera disabled");
|
|
|
|
|
local_video_label->setText("Camera Disabled\nClick 'Enable Camera' to start");
|
|
|
|
|
local_video_label->setStyleSheet("border: 2px solid gray; background-color: black; color: white;");
|
|
|
|
|
|
|
|
|
|
if (socket->state() == QSslSocket::ConnectedState) {
|
|
|
|
|
socket->write("CAMERA_DISABLE\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 19:46:54 -07:00
|
|
|
void on_socket_connected() {
|
|
|
|
|
chat_display->append("[System] Connected to server!");
|
2025-12-05 09:14:41 -07:00
|
|
|
// Send LOGIN command
|
|
|
|
|
QString login_cmd = "LOGIN:" + m_current_username + ":" + m_current_password;
|
|
|
|
|
socket->write(login_cmd.toUtf8() + "\n");
|
2025-12-04 19:46:54 -07:00
|
|
|
message_input->setFocus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_socket_disconnected() {
|
|
|
|
|
chat_display->append("[System] Disconnected from server");
|
2025-12-04 21:22:58 -07:00
|
|
|
if (camera_toggle->isChecked()) {
|
|
|
|
|
camera_toggle->setChecked(false);
|
|
|
|
|
}
|
|
|
|
|
stop_camera();
|
2025-12-05 09:14:41 -07:00
|
|
|
|
|
|
|
|
if (m_authenticated) {
|
|
|
|
|
QMessageBox::information(this, "Disconnected", "You have been disconnected from the server");
|
|
|
|
|
show_login_dialog();
|
|
|
|
|
}
|
2025-12-04 19:46:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_socket_read() {
|
2025-12-05 09:28:47 -07:00
|
|
|
// Read all available data
|
|
|
|
|
QByteArray data = socket->readAll();
|
|
|
|
|
if (data.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
// Append to buffer
|
|
|
|
|
m_read_buffer.append(data);
|
|
|
|
|
|
|
|
|
|
// Process complete lines
|
|
|
|
|
while (true) {
|
|
|
|
|
int newline_pos = m_read_buffer.indexOf('\n');
|
|
|
|
|
if (newline_pos == -1) break; // No complete line yet
|
|
|
|
|
|
|
|
|
|
QString message = QString::fromUtf8(m_read_buffer.left(newline_pos)).trimmed();
|
|
|
|
|
m_read_buffer.remove(0, newline_pos + 1);
|
|
|
|
|
|
|
|
|
|
if (message.isEmpty()) continue; // Skip empty lines
|
2025-12-04 21:22:58 -07:00
|
|
|
|
2025-12-05 09:14:41 -07:00
|
|
|
// Handle authentication response
|
|
|
|
|
if (message.startsWith("LOGIN_SUCCESS:")) {
|
|
|
|
|
QString username = message.mid(14);
|
|
|
|
|
m_authenticated = true;
|
|
|
|
|
status_label->setText("Logged in as: " + username);
|
|
|
|
|
chat_display->append("[System] Successfully logged in as " + username);
|
|
|
|
|
} else if (message.startsWith("LOGIN_FAILED:")) {
|
|
|
|
|
QString reason = message.mid(13);
|
|
|
|
|
m_authenticated = false;
|
|
|
|
|
chat_display->append("[System] Login failed: " + reason);
|
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
|
QMessageBox::critical(this, "Login Failed", "Login failed: " + reason);
|
|
|
|
|
show_login_dialog();
|
|
|
|
|
} else if (message.startsWith("ERROR:")) {
|
|
|
|
|
QString error = message.mid(6);
|
|
|
|
|
chat_display->append("[Error] " + error);
|
|
|
|
|
} else if (message.startsWith("USER_CAMERA_ON:")) {
|
2025-12-04 21:22:58 -07:00
|
|
|
QString username = message.mid(15);
|
|
|
|
|
add_remote_user(username, true);
|
|
|
|
|
chat_display->append("[System] " + username + " enabled their camera");
|
|
|
|
|
} else if (message.startsWith("USER_CAMERA_OFF:")) {
|
|
|
|
|
QString username = message.mid(16);
|
|
|
|
|
add_remote_user(username, false);
|
|
|
|
|
chat_display->append("[System] " + username + " disabled their camera");
|
|
|
|
|
} else if (message.startsWith("VIDEO_FRAME:")) {
|
|
|
|
|
// Placeholder for future video frame implementation
|
|
|
|
|
} else {
|
2025-12-05 09:14:41 -07:00
|
|
|
// Regular chat message
|
2025-12-05 09:28:47 -07:00
|
|
|
chat_display->append(message);
|
2025-12-04 21:22:58 -07:00
|
|
|
}
|
2025-12-04 19:46:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_socket_error(QAbstractSocket::SocketError error) {
|
|
|
|
|
Q_UNUSED(error);
|
|
|
|
|
chat_display->append("[Error] " + socket->errorString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_ssl_errors(const QList<QSslError> &errors) {
|
|
|
|
|
for (const QSslError &error : errors) {
|
|
|
|
|
std::cerr << "SSL Error: " << error.errorString().toStdString() << std::endl;
|
|
|
|
|
}
|
|
|
|
|
socket->ignoreSslErrors();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_send_message() {
|
|
|
|
|
if (socket->state() != QSslSocket::ConnectedState) {
|
|
|
|
|
chat_display->append("[System] Not connected!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString message = message_input->text();
|
|
|
|
|
if (message.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
socket->write(message.toUtf8() + "\n");
|
|
|
|
|
chat_display->append("[You] " + message);
|
|
|
|
|
message_input->clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_bg_color() {
|
|
|
|
|
QColor color = QColorDialog::getColor(bg_color, this, "Select Background Color");
|
|
|
|
|
if (color.isValid()) {
|
|
|
|
|
bg_color = color;
|
|
|
|
|
update_window_colors();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_text_color() {
|
|
|
|
|
QColor color = QColorDialog::getColor(text_color, this, "Select Text Color");
|
|
|
|
|
if (color.isValid()) {
|
|
|
|
|
text_color = color;
|
|
|
|
|
update_window_colors();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void on_transparency_changed(int value) {
|
|
|
|
|
transparency_value->setText(QString::number(value) + "%");
|
|
|
|
|
double opacity = value / 100.0;
|
|
|
|
|
setWindowOpacity(opacity);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 21:22:58 -07:00
|
|
|
void add_remote_user(const QString &username, bool camera_active) {
|
|
|
|
|
if (remote_users.find(username) == remote_users.end()) {
|
|
|
|
|
VideoFeedWidget *widget = new VideoFeedWidget(username, this);
|
|
|
|
|
remote_users[username] = widget;
|
|
|
|
|
remote_feeds_layout->addWidget(widget);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
remote_users[username]->set_status(camera_active);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-04 19:46:54 -07:00
|
|
|
private:
|
|
|
|
|
void update_window_colors() {
|
|
|
|
|
QString stylesheet = QString(
|
|
|
|
|
"QTextEdit { background-color: %1; color: %2; }"
|
|
|
|
|
"QLineEdit { background-color: %1; color: %2; }"
|
|
|
|
|
"QPushButton { background-color: %1; color: %2; border: 1px solid gray; padding: 4px; }"
|
|
|
|
|
"QLabel { color: %2; }"
|
|
|
|
|
"QSlider { color: %2; }"
|
2025-12-04 21:22:58 -07:00
|
|
|
"QComboBox { background-color: %1; color: %2; }"
|
|
|
|
|
"QCheckBox { color: %2; }"
|
2025-12-04 19:46:54 -07:00
|
|
|
).arg(bg_color.name()).arg(text_color.name());
|
|
|
|
|
|
|
|
|
|
setStyleSheet(stylesheet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2025-12-04 21:22:58 -07:00
|
|
|
// Chat UI
|
2025-12-05 09:14:41 -07:00
|
|
|
QLabel *status_label;
|
2025-12-04 19:46:54 -07:00
|
|
|
QTextEdit *chat_display;
|
|
|
|
|
QLineEdit *message_input;
|
|
|
|
|
QPushButton *send_btn;
|
|
|
|
|
QPushButton *bg_color_btn;
|
|
|
|
|
QPushButton *text_color_btn;
|
|
|
|
|
QSlider *transparency_slider;
|
|
|
|
|
QLabel *transparency_value;
|
2025-12-04 21:22:58 -07:00
|
|
|
|
|
|
|
|
// Video UI
|
|
|
|
|
QComboBox *camera_combo;
|
|
|
|
|
QCheckBox *camera_toggle;
|
|
|
|
|
QLabel *camera_info_label;
|
|
|
|
|
QLabel *local_video_label;
|
|
|
|
|
QGridLayout *remote_feeds_layout;
|
|
|
|
|
std::map<QString, VideoFeedWidget *> remote_users;
|
|
|
|
|
|
|
|
|
|
// Camera
|
|
|
|
|
QCamera *m_camera;
|
|
|
|
|
|
|
|
|
|
// Connection
|
2025-12-04 19:46:54 -07:00
|
|
|
QSslSocket *socket;
|
2025-12-05 09:28:47 -07:00
|
|
|
QByteArray m_read_buffer;
|
2025-12-04 19:46:54 -07:00
|
|
|
QColor bg_color;
|
|
|
|
|
QColor text_color;
|
2025-12-05 09:14:41 -07:00
|
|
|
|
|
|
|
|
// Authentication
|
|
|
|
|
bool m_authenticated;
|
|
|
|
|
QString m_current_username;
|
|
|
|
|
QString m_current_password;
|
|
|
|
|
QString m_login_dialog_server;
|
|
|
|
|
int m_login_dialog_port;
|
2025-12-04 19:46:54 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
|
QApplication app(argc, argv);
|
|
|
|
|
|
|
|
|
|
ChatClient client;
|
|
|
|
|
client.show();
|
|
|
|
|
|
|
|
|
|
return app.exec();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "main.moc"
|