493 lines
17 KiB
C++
Raw Normal View History

#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>
#include <QTabWidget>
#include <QCamera>
#include <QCameraInfo>
#include <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
#include <QScrollArea>
#include <QImage>
#include <QPixmap>
#include <QTimer>
#include <iostream>
#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;
};
class ChatClient : public QMainWindow {
Q_OBJECT
public:
ChatClient(QWidget *parent = nullptr) : QMainWindow(parent), m_camera(nullptr) {
setWindowTitle("SCAR Chat Client - Video Edition");
setGeometry(100, 100, 1000, 700);
// Create central widget with tabs
QWidget *central = new QWidget(this);
setCentralWidget(central);
QVBoxLayout *main_layout = new QVBoxLayout(central);
// Connection section
QHBoxLayout *conn_layout = new QHBoxLayout();
QLabel *host_label = new QLabel("Host:", this);
host_input = new QLineEdit("localhost", this);
QLabel *port_label = new QLabel("Port:", this);
port_input = new QLineEdit("42317", this);
connect_btn = new QPushButton("Connect", this);
conn_layout->addWidget(host_label);
conn_layout->addWidget(host_input);
conn_layout->addWidget(port_label);
conn_layout->addWidget(port_input);
conn_layout->addWidget(connect_btn);
main_layout->addLayout(conn_layout);
// 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);
// Chat display
chat_display = new QTextEdit(this);
chat_display->setReadOnly(true);
chat_layout->addWidget(chat_display);
// 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);
chat_layout->addLayout(msg_layout);
// Chat settings
QHBoxLayout *chat_settings = new QHBoxLayout();
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);
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);
// Connect signals
connect(connect_btn, &QPushButton::clicked, this, &ChatClient::on_connect);
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);
connect(camera_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ChatClient::on_camera_changed);
connect(camera_toggle, &QCheckBox::stateChanged, this, &ChatClient::on_camera_toggle);
// 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);
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::errorOccurred),
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();
// 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.");
}
~ChatClient() {
if (m_camera) {
m_camera->stop();
delete m_camera;
}
}
private slots:
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");
}
}
void on_connect() {
if (socket->state() == QSslSocket::ConnectedState) {
socket->disconnectFromHost();
connect_btn->setText("Connect");
return;
}
QString host = host_input->text();
int port = port_input->text().toInt();
chat_display->append("[System] Connecting to " + host + ":" + QString::number(port) + "...");
socket->connectToHostEncrypted(host, port);
}
void on_socket_connected() {
chat_display->append("[System] Connected to server!");
connect_btn->setText("Disconnect");
message_input->setFocus();
}
void on_socket_disconnected() {
chat_display->append("[System] Disconnected from server");
connect_btn->setText("Connect");
if (camera_toggle->isChecked()) {
camera_toggle->setChecked(false);
}
stop_camera();
}
void on_socket_read() {
while (socket->canReadLine()) {
QString message = QString::fromUtf8(socket->readLine()).trimmed();
if (message.startsWith("USER_CAMERA_ON:")) {
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 {
chat_display->append("[Server] " + message);
}
}
}
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);
}
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);
}
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; }"
"QComboBox { background-color: %1; color: %2; }"
"QCheckBox { color: %2; }"
).arg(bg_color.name()).arg(text_color.name());
setStyleSheet(stylesheet);
}
private:
// Chat UI
QLineEdit *host_input;
QLineEdit *port_input;
QPushButton *connect_btn;
QTextEdit *chat_display;
QLineEdit *message_input;
QPushButton *send_btn;
QPushButton *bg_color_btn;
QPushButton *text_color_btn;
QSlider *transparency_slider;
QLabel *transparency_value;
// 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
QSslSocket *socket;
QColor bg_color;
QColor text_color;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ChatClient client;
client.show();
return app.exec();
}
#include "main.moc"