From be551d2d72b19f885b057b2209094a7ba635b968 Mon Sep 17 00:00:00 2001 From: Rutuja Nemane Date: Mon, 5 May 2025 00:55:26 -0700 Subject: [PATCH 1/3] fixed user1 issue --- src/LoginDialog.cpp | 6 ++++-- src/MainWindow.cpp | 8 +++++++- src/main.cpp | 22 ++-------------------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/LoginDialog.cpp b/src/LoginDialog.cpp index 9290182..bfd7eb9 100644 --- a/src/LoginDialog.cpp +++ b/src/LoginDialog.cpp @@ -134,7 +134,8 @@ bool LoginDialog::login(const QString& username, const QString& password) QJsonObject userInfo = users[username].toObject(); if (userInfo["password"].toString() == password) { QString email = userInfo["email"].toString(); - user = std::make_shared(username, username, email); + QString userId = "user_" + QString::number(QDateTime::currentMSecsSinceEpoch()); + user = std::make_shared(userId, username, email); return user->manageSession(); // Simulated session } } @@ -162,7 +163,8 @@ bool LoginDialog::registerUser(const QString& username, const QString& email, co } // Set the user so MainWindow gets the new user - user = std::make_shared(username, username, email); + QString userId = "user_" + QString::number(QDateTime::currentMSecsSinceEpoch()); + user = std::make_shared(userId, username, email); return true; } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3ce4099..085c28b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -290,7 +290,13 @@ void MainWindow::showLoginDialog() statusLabel->setText("Connecting to server..."); // Try to connect - if (!collaborationClient->connect("ws://localhost:8080")) { + if (collaborationClient->connect("ws://localhost:8080")) { + // Set the document in the collaboration client + collaborationClient->setDocument(currentDocument); + // Join the document + collaborationClient->joinDocument(currentDocument->getId()); + statusLabel->setText("Connected to server"); + } else { QMessageBox::warning(this, "Connection Error", "Failed to connect to the collaboration server.\n" "Please make sure the server is running (start with --server flag).\n" diff --git a/src/main.cpp b/src/main.cpp index b1028f3..2f8dfff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,27 +58,9 @@ int main(int argc, char *argv[]) } } - // Create a test user - auto user = std::make_shared("user1", "user1", "user1@example.com"); - - // Create a test document - auto document = std::make_shared("test_doc_1", "Test Document", user); - QString documentId = document->getId(); - - // Add the document to the main window + // Create a test document and add it to the main window + auto document = std::make_shared("test_doc_1", "Test Document", nullptr); mainWindow.addDocument(document); - // Initialize collaboration client - CollaborationClient client; - client.setUser(user); - client.setDocument(document); - - // Connect to server and join document - if (client.connect("ws://localhost:8080")) { - client.joinDocument(documentId); - } else { - qDebug() << "Failed to connect to collaboration server"; - } - return app.exec(); } \ No newline at end of file From 635344d14edd0a7054cb0f28136f8e5cb0192ac2 Mon Sep 17 00:00:00 2001 From: Rutuja Nemane Date: Mon, 5 May 2025 16:25:08 -0700 Subject: [PATCH 2/3] some color fixes --- src/CodeEditorWidget.cpp | 49 ++++++++++++++++++----------- src/MainWindow.cpp | 66 ++++++++++++++++++++++++---------------- src/User.cpp | 26 ++++++++++------ src/main.cpp | 1 + 4 files changed, 88 insertions(+), 54 deletions(-) diff --git a/src/CodeEditorWidget.cpp b/src/CodeEditorWidget.cpp index f678381..59da8be 100644 --- a/src/CodeEditorWidget.cpp +++ b/src/CodeEditorWidget.cpp @@ -93,21 +93,29 @@ void CodeEditorWidget::updateRemoteCursor(const QString& userId, const QString& cursor.username = username; cursor.position = position; - // Assign a color if it doesn't have one - if (!remoteCursors.contains(userId)) { - // Generate a color based on userId hash - int hash = 0; - for (QChar c : userId) { - hash = (hash * 31) + c.unicode(); + // Use a fixed set of distinct colors for different users + static const QColor colors[] = { + QColor(255, 0, 0), // Red + QColor(0, 255, 0), // Green + QColor(0, 0, 255), // Blue + QColor(255, 255, 0), // Yellow + QColor(255, 0, 255), // Magenta + QColor(0, 255, 255), // Cyan + QColor(255, 128, 0), // Orange + QColor(128, 0, 255) // Purple + }; + + // Calculate a more deterministic color index based on the entire user ID + int colorIndex = 0; + if (!userId.isEmpty()) { + // Sum up all characters in the user ID + int sum = 0; + for (const QChar& c : userId) { + sum += c.unicode(); } - - // Create a hue between 0 and 359 (exclude red which is for errors) - int hue = (hash % 300) + 30; - QColor color = QColor::fromHsv(hue, 255, 255); - cursor.color = color; - } else { - cursor.color = remoteCursors[userId].color; + colorIndex = sum % 8; } + cursor.color = colors[colorIndex]; // Store the cursor remoteCursors[userId] = cursor; @@ -156,11 +164,15 @@ void CodeEditorWidget::paintEvent(QPaintEvent* event) nameRect.setLeft(cursorRectangle.left()); nameRect.setWidth(fontMetrics().horizontalAdvance(cursor.username) + 10); - // Draw a background rectangle - painter.fillRect(nameRect, cursor.color); + // Draw a semi-transparent background rectangle + QColor bgColor = cursor.color; + bgColor.setAlpha(180); // Make it semi-transparent + painter.fillRect(nameRect, bgColor); - // Draw the text - painter.setPen(Qt::white); + // Draw the text with contrasting color + // If the background is light, use dark text; if dark, use light text + QColor textColor = (bgColor.lightness() > 128) ? Qt::black : Qt::white; + painter.setPen(textColor); painter.drawText(nameRect, Qt::AlignCenter, cursor.username); } } @@ -234,7 +246,8 @@ void CodeEditorWidget::highlightCurrentLine() if (!isReadOnly()) { QTextEdit::ExtraSelection selection; - QColor lineColor = QColor(Qt::yellow).lighter(180); + // Use a light grey color for the current line highlight + QColor lineColor = QColor(0, 0, 0); // Light grey selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 085c28b..99476e0 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -20,6 +20,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -275,6 +276,7 @@ void MainWindow::showLoginDialog() codeEditor->setDocument(currentDocument); codeEditor->setPlainText(initialContent); codeEditor->setLanguage("C++"); + codeEditor->highlightSyntax(); // Force syntax highlighting update // Update UI updateTitle(); @@ -285,32 +287,11 @@ void MainWindow::showLoginDialog() chatInput->clear(); // Try to connect to the collaboration server - if (collaborationClient) { - // Show connecting status - statusLabel->setText("Connecting to server..."); - - // Try to connect - if (collaborationClient->connect("ws://localhost:8080")) { - // Set the document in the collaboration client - collaborationClient->setDocument(currentDocument); - // Join the document - collaborationClient->joinDocument(currentDocument->getId()); - statusLabel->setText("Connected to server"); - } else { - QMessageBox::warning(this, "Connection Error", - "Failed to connect to the collaboration server.\n" - "Please make sure the server is running (start with --server flag).\n" - "You can continue working offline."); - statusLabel->setText("Working offline"); - } + if (collaborationClient->connect("ws://localhost:8080")) { + statusLabel->setText("Connected to server"); + } else { + statusLabel->setText("Not connected to server"); } - } else { - QMessageBox::critical(this, "Login Failed", "Could not authenticate user."); - } - } else { - // User cancelled login, close application if not already logged in - if (!currentUser) { - close(); } } } @@ -638,11 +619,40 @@ void MainWindow::onTextChanged() void MainWindow::onChatMessageReceived(const QString& userId, const QString& username, const QString& message) { + // Use the same color scheme as the cursors + static const QColor colors[] = { + QColor(255, 0, 0), // Red + QColor(0, 255, 0), // Green + QColor(0, 0, 255), // Blue + QColor(255, 255, 0), // Yellow + QColor(255, 0, 255), // Magenta + QColor(0, 255, 255), // Cyan + QColor(255, 128, 0), // Orange + QColor(128, 0, 255) // Purple + }; + + // Calculate a more deterministic color index based on the entire user ID + int colorIndex = 0; + if (!userId.isEmpty()) { + // Sum up all characters in the user ID + int sum = 0; + for (const QChar& c : userId) { + sum += c.unicode(); + } + colorIndex = sum % 8; + } + QColor userColor = colors[colorIndex]; + QString formattedMessage; if (currentUser && userId == currentUser->getUserId()) { - formattedMessage = "You: " + message + ""; + formattedMessage = QString("You: %2") + .arg(userColor.name()) + .arg(message); } else { - formattedMessage = "" + username + ": " + message + ""; + formattedMessage = QString("%2: %3") + .arg(userColor.name()) + .arg(username) + .arg(message); } chatBox->append(formattedMessage); } @@ -670,6 +680,8 @@ void MainWindow::addDocument(std::shared_ptr document) currentDocument = document; codeEditor->setDocument(document); codeEditor->setPlainText(document->getContent()); + codeEditor->setLanguage(document->getLanguage()); + codeEditor->highlightSyntax(); // Force syntax highlighting update // Set up collaboration if (collaborationClient) { diff --git a/src/User.cpp b/src/User.cpp index c6cf37f..e059721 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -13,16 +13,24 @@ User::User(const QString& id, const QString& name, const QString& mail) , passwordHash("") , sessionToken("") { - // Generate a random color for this user's cursor - // Use the userId to make it deterministic - int hash = 0; - for (QChar c : userId) { - hash = (hash * 31) + c.unicode(); + // Use a fixed set of colors for different users + static const QColor colors[] = { + QColor(0, 128, 255), // Blue + QColor(0, 200, 83), // Green + QColor(255, 128, 0), // Orange + QColor(128, 0, 255), // Purple + QColor(255, 0, 128), // Pink + QColor(0, 200, 200), // Cyan + QColor(200, 0, 200), // Magenta + QColor(200, 200, 0) // Yellow + }; + + // Use the first character of the user ID to determine the color + int colorIndex = 0; + if (!id.isEmpty()) { + colorIndex = id.at(0).unicode() % 8; } - - // Create a hue between 0 and 359 (exclude red which is for errors) - int hue = (hash % 300) + 30; - cursorColor = QColor::fromHsv(hue, 255, 255); + cursorColor = colors[colorIndex]; } User::~User() = default; diff --git a/src/main.cpp b/src/main.cpp index 2f8dfff..3761f88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,6 +60,7 @@ int main(int argc, char *argv[]) // Create a test document and add it to the main window auto document = std::make_shared("test_doc_1", "Test Document", nullptr); + document->setLanguage("C++"); // Set the language explicitly mainWindow.addDocument(document); return app.exec(); From d94216026df72ec4fafdfce94eeb6131c0f3dc6b Mon Sep 17 00:00:00 2001 From: patilkalpesh Date: Mon, 5 May 2025 17:54:53 -0700 Subject: [PATCH 3/3] Without cursor --- CMakeLists.txt | 74 +++--- codecolab.pro | 8 +- include/CodeEditorWidget.h | 5 +- include/MainWindow.h | 65 ++--- include/NetworkClient.h | 25 ++ include/NetworkServer.h | 32 +++ src/CodeEditorWidget.cpp | 137 +++++++--- src/MainWindow.cpp | 509 ++++++++++++++++++++----------------- src/NetworkClient.cpp | 22 ++ src/NetworkServer.cpp | 37 +++ users.json | 14 + 11 files changed, 586 insertions(+), 342 deletions(-) create mode 100644 include/NetworkClient.h create mode 100644 include/NetworkServer.h create mode 100644 src/NetworkClient.cpp create mode 100644 src/NetworkServer.cpp create mode 100644 users.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bf2da6..58512bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Include Qt -find_package(Qt6 COMPONENTS Core Widgets WebSockets REQUIRED) +find_package(Qt6 COMPONENTS Core Widgets WebSockets Network REQUIRED) # Set automoc for Qt set(CMAKE_AUTOMOC ON) @@ -15,7 +15,6 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/forms) - # Include directories include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) @@ -23,42 +22,48 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) # Source files set(SOURCES - src/main.cpp - src/MainWindow.cpp - src/CodeEditorWidget.cpp - src/SyntaxHighlighter.cpp - src/LoginDialog.cpp - src/Document.cpp - src/User.cpp - src/CollaborationClient.cpp - src/CollaborationManager.cpp - src/CollaborationServer.cpp - src/EditOperation.cpp + + src/main.cpp + src/MainWindow.cpp + src/CodeEditorWidget.cpp + src/SyntaxHighlighter.cpp + src/LoginDialog.cpp + src/Document.cpp + src/User.cpp + src/CollaborationClient.cpp + src/CollaborationManager.cpp + src/CollaborationServer.cpp + src/EditOperation.cpp + src/NetworkServer.cpp # ✅ New + src/NetworkClient.cpp # ✅ New ) # Header files set(HEADERS - include/MainWindow.h - include/CodeEditorWidget.h - include/SyntaxHighlighter.h - include/LoginDialog.h - include/Document.h - include/User.h - include/CollaborationClient.h - include/CollaborationManager.h - include/CollaborationServer.h - include/EditOperation.h + include/MainWindow.h + include/CodeEditorWidget.h + include/SyntaxHighlighter.h + include/LoginDialog.h + include/Document.h + include/User.h + include/CollaborationClient.h + include/CollaborationManager.h + include/CollaborationServer.h + include/EditOperation.h + include/NetworkServer.h # ✅ New + include/NetworkClient.h # ✅ New + ) # UI files set(UI_FILES - forms/MainWindow.ui - forms/LoginDialog.ui + forms/MainWindow.ui + forms/LoginDialog.ui ) -# Add resources +# Resource files set(RESOURCE_FILES - resources/CodeColab.qrc + resources/CodeColab.qrc ) # Create executable @@ -69,17 +74,18 @@ add_executable(codecolab ${SOURCES} ${HEADERS} ${UI_FILES} ${RESOURCE_FILES} # Link Qt libraries target_link_libraries(codecolab PRIVATE - Qt6::Core - Qt6::Widgets - Qt6::WebSockets + Qt6::Core + Qt6::Widgets + Qt6::WebSockets + Qt6::Network # ✅ Added ) -# Install +# Install step install(TARGETS codecolab - RUNTIME DESTINATION bin + RUNTIME DESTINATION bin ) -# Create a resource file if it doesn't exist yet +# Resource setup if not present if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/CodeColab.qrc") file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/resources/CodeColab.qrc" " @@ -101,6 +107,6 @@ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/CodeColab.qrc") ") endif() -# Create directories for resources +# Ensure directories exist file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons") file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/resources/styles") diff --git a/codecolab.pro b/codecolab.pro index 63ed3b7..5d39d91 100644 --- a/codecolab.pro +++ b/codecolab.pro @@ -24,7 +24,9 @@ SOURCES += \ src/CollaborationManager.cpp \ src/CollaborationServer.cpp \ src/EditOperation.cpp \ - src/UserStorage.cpp + src/UserStorage.cpp \ + src/NetworkClient.cpp \ + src/NetworkServer.cpp HEADERS += \ include/MainWindow.h \ @@ -37,7 +39,9 @@ HEADERS += \ include/CollaborationManager.h \ include/CollaborationServer.h \ include/EditOperation.h \ - include/UserStorage.h + include/UserStorage.h \ + include/NetworkClient.h \ + include/NetworkServer.h FORMS += \ forms/MainWindow.ui \ diff --git a/include/CodeEditorWidget.h b/include/CodeEditorWidget.h index c00e7d5..14f7dd1 100644 --- a/include/CodeEditorWidget.h +++ b/include/CodeEditorWidget.h @@ -38,10 +38,12 @@ class CodeEditorWidget : public QPlainTextEdit void applyRemoteEdit(const EditOperation& operation); void removeRemoteCursor(const QString& userId); + bool ignoreChanges; signals: void editorContentChanged(const QString& content); void cursorPositionChanged(int position); + void textChangedAt(int pos, int charsRemoved, const QString& insertedText); protected: void paintEvent(QPaintEvent *event) override; @@ -51,6 +53,7 @@ class CodeEditorWidget : public QPlainTextEdit void resizeEvent(QResizeEvent *event) override; private slots: +void onDocumentContentsChange(int position, int charsRemoved, int charsAdded); void onTextChanged(); void onCursorPositionChanged(); void updateLineNumberAreaWidth(int newBlockCount); @@ -89,7 +92,7 @@ private slots: QMap remoteCursors; // Track local changes to avoid loops - bool ignoreChanges; + }; #endif // CODEEDITORWIDGET_H \ No newline at end of file diff --git a/include/MainWindow.h b/include/MainWindow.h index 5f06e56..7020ae0 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -1,4 +1,3 @@ -// MainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -6,23 +5,25 @@ #include #include #include -#include // Add for Qt 6 -#include // Add for Qt 6 - -#include "User.h" +#include // Qt 6 +#include // Qt 6 +#include +#include +#include +#include "ui_MainWindow.h" +#include "CodeEditorWidget.h" #include "Document.h" -#include "CollaborationManager.h" +#include "User.h" #include "CollaborationClient.h" +#include "CollaborationManager.h" +#include "NetworkServer.h" +#include "NetworkClient.h" -class CodeEditorWidget; +// Forward declarations class LoginDialog; class QTextEdit; class QSplitter; -namespace Ui { - class MainWindow; -} - class MainWindow : public QMainWindow { Q_OBJECT @@ -34,19 +35,20 @@ class MainWindow : public QMainWindow // Add document to the editor void addDocument(std::shared_ptr document); - private slots: - void onLogin(); +private slots: + void showLoginDialog(); + void onLogin(); void onLogout(); void onNewDocument(); void onOpenDocument(); void onSaveDocument(); void onShareDocument(); + void onUserConnected(const QString &userId, const QString &username); + void onUserDisconnected(const QString &userId); void onTextChanged(); - void onCursorPositionChanged(); + void onChatMessageReceived(const QString &userId, const QString &username, const QString &message); void onSendChatMessage(); - void onUserConnected(const QString& userId, const QString& username); - void onUserDisconnected(const QString& userId); - void onChatMessageReceived(const QString& userId, const QString& username, const QString& message); + void onRemoteTextChanged(const QString &message); private: void setupUI(); @@ -54,26 +56,25 @@ class MainWindow : public QMainWindow void updateTitle(); void updateStatusBar(); void updateUserList(); - void showLoginDialog(); bool eventFilter(QObject *obj, QEvent *event) override; - Ui::MainWindow *ui; + std::unique_ptr ui; + std::shared_ptr collaborationManager; + std::unique_ptr collaborationClient; + std::shared_ptr currentDocument; + std::shared_ptr currentUser; std::unique_ptr codeEditor; + std::unique_ptr mainSplitter; + std::unique_ptr rightSplitter; std::unique_ptr chatBox; std::unique_ptr chatInput; std::unique_ptr statusLabel; - std::unique_ptr mainSplitter; - std::unique_ptr rightSplitter; - - // Core objects - std::shared_ptr currentUser; - std::shared_ptr currentDocument; - std::shared_ptr collaborationManager; - std::unique_ptr collaborationClient; - - // Track collaboration state - QMap connectedUsers; // userId -> username - bool isCollaborating = false; + QMap connectedUsers; + bool isCollaborating; + NetworkServer *server; + NetworkClient *client; + QString lastSentText; + QTimer *typingTimer; }; -#endif // MAINWINDOW_H \ No newline at end of file +#endif // MAINWINDOW_H diff --git a/include/NetworkClient.h b/include/NetworkClient.h new file mode 100644 index 0000000..f2815c2 --- /dev/null +++ b/include/NetworkClient.h @@ -0,0 +1,25 @@ +#ifndef NETWORKCLIENT_H +#define NETWORKCLIENT_H + +#include +#include + +class NetworkClient : public QObject { + Q_OBJECT + +public: + explicit NetworkClient(QObject *parent = nullptr); + void connectToHost(const QString &host, quint16 port); + void sendMessage(const QString &message); + +signals: + void messageReceived(const QString &message); + +private slots: + void readData(); + +private: + QTcpSocket *socket; +}; + +#endif // NETWORKCLIENT_H diff --git a/include/NetworkServer.h b/include/NetworkServer.h new file mode 100644 index 0000000..b0e11a3 --- /dev/null +++ b/include/NetworkServer.h @@ -0,0 +1,32 @@ +#ifndef NETWORKSERVER_H +#define NETWORKSERVER_H + +#include +#include +#include +#include + +class NetworkServer : public QObject { + Q_OBJECT + +public: + explicit NetworkServer(QObject *parent = nullptr); + bool startServer(quint16 port); + bool isListening() const { return server->isListening(); } + +signals: + void dataReceived(const QString &data); + +public slots: + void sendToClients(const QString &message); + +private slots: + void onNewConnection(); + void readClientData(); + +private: + QTcpServer *server; + QList clients; +}; + +#endif // NETWORKSERVER_H diff --git a/src/CodeEditorWidget.cpp b/src/CodeEditorWidget.cpp index 59da8be..0ec3588 100644 --- a/src/CodeEditorWidget.cpp +++ b/src/CodeEditorWidget.cpp @@ -1,4 +1,3 @@ -// CodeEditorWidget.cpp #include "CodeEditorWidget.h" #include "SyntaxHighlighter.h" #include "Document.h" @@ -7,26 +6,24 @@ #include #include #include +#include #include #include #include #include CodeEditorWidget::CodeEditorWidget(QWidget *parent) - : QPlainTextEdit(parent) - , lineNumberArea(new LineNumberArea(this)) - , syntaxHighlighter(nullptr) - , currentLanguage("Plain") - , ignoreChanges(false) -{ - setLineWrapMode(QPlainTextEdit::NoWrap); + : QPlainTextEdit(parent), ignoreChanges(false) , lineNumberArea(new LineNumberArea(this)), syntaxHighlighter(nullptr) { + setLineWrapMode(QPlainTextEdit::NoWrap); - // Enable line numbers + connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditorWidget::updateLineNumberAreaWidth); connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditorWidget::updateLineNumberArea); connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditorWidget::highlightCurrentLine); connect(this, &QPlainTextEdit::textChanged, this, &CodeEditorWidget::onTextChanged); connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditorWidget::onCursorPositionChanged); + connect(this->document(), &QTextDocument::contentsChange, + this, &CodeEditorWidget::onDocumentContentsChange); updateLineNumberAreaWidth(0); highlightCurrentLine(); @@ -39,12 +36,13 @@ CodeEditorWidget::CodeEditorWidget(QWidget *parent) // Set tab width to 4 spaces (updated for Qt 6) QFontMetricsF metrics(font); setTabStopDistance(4 * metrics.horizontalAdvance(' ')); + } -CodeEditorWidget::~CodeEditorWidget() -{ +CodeEditorWidget::~CodeEditorWidget(){ delete syntaxHighlighter; // lineNumberArea is automatically deleted as a child widget + } void CodeEditorWidget::setDocument(std::shared_ptr doc) @@ -64,8 +62,10 @@ void CodeEditorWidget::setDocument(std::shared_ptr doc) } } -void CodeEditorWidget::setCollaborationManager(std::shared_ptr manager) -{ + + + +void CodeEditorWidget::setCollaborationManager(std::shared_ptr manager) { collaborationManager = manager; } @@ -127,7 +127,7 @@ void CodeEditorWidget::updateRemoteCursor(const QString& userId, const QString& void CodeEditorWidget::removeRemoteCursor(const QString& userId) { if (remoteCursors.contains(userId)) { - remoteCursors.remove(userId); + remoteCursors.remove(userId); viewport()->update(); } } @@ -289,6 +289,7 @@ void CodeEditorWidget::keyPressEvent(QKeyEvent *event) // Insert newline with indent QPlainTextEdit::keyPressEvent(event); textCursor().insertText(indent); + event->accept(); } else { QPlainTextEdit::keyPressEvent(event); } @@ -298,33 +299,65 @@ void CodeEditorWidget::onTextChanged() { if (ignoreChanges || !currentDocument) return; - // Track local changes - ignoreChanges = true; - QString content = toPlainText(); - - // Notify about content changes - emit editorContentChanged(content); + // Get the current cursor position and text + QTextCursor cursor = textCursor(); + QString currentText = toPlainText(); + QString oldText = currentDocument->getContent(); + + // Find the difference between old and new text + int pos = 0; + while (pos < oldText.length() && pos < currentText.length() && + oldText[pos] == currentText[pos]) { + pos++; + } + + // Calculate the actual changes + int charsRemoved = 0; + int charsAdded = 0; + QString insertedText; + + if (pos < oldText.length() && pos < currentText.length()) { + // Find the end of the change + int oldEnd = oldText.length() - 1; + int newEnd = currentText.length() - 1; + + while (oldEnd >= pos && newEnd >= pos && + oldText[oldEnd] == currentText[newEnd]) { + oldEnd--; + newEnd--; + } + + charsRemoved = oldEnd - pos + 1; + charsAdded = newEnd - pos + 1; + insertedText = currentText.mid(pos, charsAdded); + } else if (pos < currentText.length()) { + // Only additions + charsAdded = currentText.length() - pos; + insertedText = currentText.mid(pos, charsAdded); + } else if (pos < oldText.length()) { + // Only deletions + charsRemoved = oldText.length() - pos; + } + + // Create an edit operation + EditOperation op; + op.userId = "local"; + op.documentId = currentDocument->getId(); + op.position = pos; + op.deletionLength = charsRemoved; + op.insertion = insertedText; // Update document content - currentDocument->setContent(content); + currentDocument->setContent(currentText); - // Create an edit operation + // Send the operation to the collaboration manager if (collaborationManager) { - EditOperation op; - op.userId = "local"; - op.documentId = currentDocument->getId(); - op.position = textCursor().position(); - - // For the prototype, we'll just send the entire content as an insertion - // In a real implementation, we would calculate the actual changes - op.insertion = content; - op.deletionLength = 0; - - // Send the operation to the collaboration manager collaborationManager->synchronizeChanges(op); } - ignoreChanges = false; + // Notify about content changes + emit editorContentChanged(currentText); + emit textChangedAt(pos, charsRemoved, insertedText); } void CodeEditorWidget::onCursorPositionChanged() @@ -370,8 +403,42 @@ void CodeEditorWidget::applyRemoteEdit(const EditOperation& operation) // Update the editor setPlainText(content); + + // Update document content + currentDocument->setContent(content); + + // Preserve the local cursor position if it's not affected by the change + QTextCursor cursor = textCursor(); + int localPos = cursor.position(); + + if (localPos <= operation.position) { + // Cursor is before the change, keep it where it is + cursor.setPosition(localPos); + } else if (localPos <= operation.position + operation.deletionLength) { + // Cursor is in the deleted region, move it to the start of the deletion + cursor.setPosition(operation.position); + } else { + // Cursor is after the change, adjust its position + cursor.setPosition(localPos - operation.deletionLength + operation.insertion.length()); + } + + setTextCursor(cursor); } // Reset flag ignoreChanges = false; -} \ No newline at end of file +} + +void CodeEditorWidget::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { + if (ignoreChanges) return; + + QString insertedText; + if (charsAdded > 0) { + insertedText = this->toPlainText().mid(position, charsAdded); + } + + emit textChangedAt(position, charsRemoved, insertedText); +} + + + diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 99476e0..3e41d63 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -3,6 +3,8 @@ #include "CodeEditorWidget.h" #include "LoginDialog.h" #include "CollaborationClient.h" +#include "NetworkServer.h" +#include "NetworkClient.h" #include #include @@ -21,225 +23,56 @@ #include #include #include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , collaborationManager(std::make_shared()) , collaborationClient(std::make_unique()) + , isCollaborating(false) { ui->setupUi(this); setupUI(); setupConnections(); - // Show login dialog on startup - QTimer::singleShot(500, this, &MainWindow::showLoginDialog); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::setupConnections() -{ - // Connect code editor signals - connect(codeEditor.get(), &CodeEditorWidget::editorContentChanged, - this, &MainWindow::onTextChanged); - connect(codeEditor.get(), &CodeEditorWidget::cursorPositionChanged, - this, &MainWindow::onCursorPositionChanged); - - // Connect collaboration client signals - connect(collaborationClient.get(), &CollaborationClient::connected, - this, [this]() { - statusLabel->setText("Connected to server"); - if (currentDocument) { - collaborationClient->joinDocument(currentDocument->getId()); + server = new NetworkServer(this); + client = new NetworkClient(this); + + // Connect server signals + connect(server, &NetworkServer::dataReceived, this, &MainWindow::onRemoteTextChanged); + connect(client, &NetworkClient::messageReceived, this, &MainWindow::onRemoteTextChanged); + + // Create typing timer for receiving updates + typingTimer = new QTimer(this); + typingTimer->setSingleShot(true); + typingTimer->setInterval(500); // 500ms delay before receiving updates + + // Connect editor signals + connect(codeEditor.get(), &CodeEditorWidget::editorContentChanged, this, [this](const QString &) { + if (isCollaborating) { + // Send updates immediately + QString currentText = codeEditor->toPlainText(); + if (currentText != lastSentText) { + if (server->isListening()) { + server->sendToClients(currentText); + } else { + client->sendMessage(currentText); } - }); - - connect(collaborationClient.get(), &CollaborationClient::disconnected, - this, [this]() { - statusLabel->setText("Disconnected from server"); - connectedUsers.clear(); - updateUserList(); - }); - - connect(collaborationClient.get(), &CollaborationClient::error, - this, [this](const QString& error) { - QMessageBox::warning(this, "Collaboration Error", error); - }); - - connect(collaborationClient.get(), &CollaborationClient::editReceived, - this, [this](const EditOperation& op) { - if (currentDocument && codeEditor) { - codeEditor->applyRemoteEdit(op); - } - }); - - connect(collaborationClient.get(), &CollaborationClient::cursorPositionReceived, - this, [this](const QString& userId, const QString& username, int position) { - if (codeEditor) { - codeEditor->updateRemoteCursor(userId, username, position); - } - }); - - connect(collaborationClient.get(), &CollaborationClient::userConnected, - this, &MainWindow::onUserConnected); - - connect(collaborationClient.get(), &CollaborationClient::userDisconnected, - this, &MainWindow::onUserDisconnected); - - connect(collaborationClient.get(), &CollaborationClient::chatMessageReceived, - this, &MainWindow::onChatMessageReceived); - - // Connect chat input - connect(chatInput.get(), &QTextEdit::textChanged, [this]() { - // Enable/disable send button based on whether there's text - QPushButton* sendButton = qobject_cast( - chatInput->parentWidget()->layout()->itemAt( - chatInput->parentWidget()->layout()->count() - 1)->widget()); - if (sendButton) { - sendButton->setEnabled(!chatInput->toPlainText().trimmed().isEmpty()); + lastSentText = currentText; + } + // Start the timer to delay receiving updates + typingTimer->start(); } }); - // Install event filter for chat input to handle Enter key - chatInput->installEventFilter(this); + QTimer::singleShot(500, this, &MainWindow::showLoginDialog); } -void MainWindow::setupUI() -{ - // Create central widget and layout - QWidget* centralWidget = new QWidget(this); - QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget); - mainLayout->setContentsMargins(5, 5, 5, 5); - setCentralWidget(centralWidget); - - // Create main splitter (editor | right panel) - mainSplitter = std::make_unique(Qt::Horizontal); - mainLayout->addWidget(mainSplitter.get()); - - // Create code editor - codeEditor = std::make_unique(); - codeEditor->setCollaborationManager(collaborationManager); - mainSplitter->addWidget(codeEditor.get()); - - // Create right panel splitter (user list | chat) - rightSplitter = std::make_unique(Qt::Vertical); - mainSplitter->addWidget(rightSplitter.get()); - - // Create user list panel - QWidget* userListPanel = new QWidget(rightSplitter.get()); - QVBoxLayout* userListLayout = new QVBoxLayout(userListPanel); - userListLayout->setContentsMargins(0, 0, 0, 0); - - QLabel* collaboratorsLabel = new QLabel("Collaborators:"); - userListLayout->addWidget(collaboratorsLabel); - - QListWidget* userList = new QListWidget(); - userListLayout->addWidget(userList); - - rightSplitter->addWidget(userListPanel); - - // Create chat panel - QWidget* chatPanel = new QWidget(rightSplitter.get()); - QVBoxLayout* chatLayout = new QVBoxLayout(chatPanel); - chatLayout->setContentsMargins(0, 0, 0, 0); - - QLabel* chatLabel = new QLabel("Chat:"); - chatLayout->addWidget(chatLabel); - - chatBox = std::make_unique(); - chatBox->setReadOnly(true); - chatLayout->addWidget(chatBox.get()); - - chatInput = std::make_unique(); - chatInput->setMaximumHeight(60); - chatLayout->addWidget(chatInput.get()); - - QPushButton* sendButton = new QPushButton("Send"); - connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendChatMessage); - chatLayout->addWidget(sendButton); - - rightSplitter->addWidget(chatPanel); - - // Set up splitter proportions - mainSplitter->setSizes({600, 200}); - rightSplitter->setSizes({200, 400}); - - // Set up status bar - statusLabel = std::make_unique("Not logged in"); - statusBar()->addWidget(statusLabel.get()); - - // Setup menus with newer Qt 6 syntax - QMenu* fileMenu = menuBar()->addMenu("&File"); - QAction* newAction = new QAction("New", this); - newAction->setShortcut(QKeySequence::New); - connect(newAction, &QAction::triggered, this, &MainWindow::onNewDocument); - fileMenu->addAction(newAction); - - QAction* openAction = new QAction("Open", this); - openAction->setShortcut(QKeySequence::Open); - connect(openAction, &QAction::triggered, this, &MainWindow::onOpenDocument); - fileMenu->addAction(openAction); - - QAction* saveAction = new QAction("Save", this); - saveAction->setShortcut(QKeySequence::Save); - connect(saveAction, &QAction::triggered, this, &MainWindow::onSaveDocument); - fileMenu->addAction(saveAction); - - fileMenu->addSeparator(); - - QAction* exitAction = new QAction("Exit", this); - exitAction->setShortcut(QKeySequence::Quit); - connect(exitAction, &QAction::triggered, this, &QWidget::close); - fileMenu->addAction(exitAction); - - QMenu* editMenu = menuBar()->addMenu("&Edit"); - QAction* cutAction = new QAction("Cut", this); - cutAction->setShortcut(QKeySequence::Cut); - connect(cutAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::cut); - editMenu->addAction(cutAction); - - QAction* copyAction = new QAction("Copy", this); - copyAction->setShortcut(QKeySequence::Copy); - connect(copyAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::copy); - editMenu->addAction(copyAction); - - QAction* pasteAction = new QAction("Paste", this); - pasteAction->setShortcut(QKeySequence::Paste); - connect(pasteAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::paste); - editMenu->addAction(pasteAction); - - QMenu* viewMenu = menuBar()->addMenu("&View"); - QAction* zoomInAction = new QAction("Zoom In", this); - zoomInAction->setShortcut(QKeySequence::ZoomIn); - connect(zoomInAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::zoomIn); - viewMenu->addAction(zoomInAction); - - QAction* zoomOutAction = new QAction("Zoom Out", this); - zoomOutAction->setShortcut(QKeySequence::ZoomOut); - connect(zoomOutAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::zoomOut); - viewMenu->addAction(zoomOutAction); - - QMenu* collaborationMenu = menuBar()->addMenu("&Collaboration"); - QAction* shareAction = new QAction("Share Document", this); - connect(shareAction, &QAction::triggered, this, &MainWindow::onShareDocument); - collaborationMenu->addAction(shareAction); - - QMenu* userMenu = menuBar()->addMenu("&User"); - QAction* loginAction = new QAction("Login", this); - connect(loginAction, &QAction::triggered, this, &MainWindow::onLogin); - userMenu->addAction(loginAction); - - QAction* logoutAction = new QAction("Logout", this); - connect(logoutAction, &QAction::triggered, this, &MainWindow::onLogout); - userMenu->addAction(logoutAction); - - // Set window properties - resize(1024, 768); - setWindowTitle("CodeColab - Collaborative Code Editor"); +MainWindow::~MainWindow() { + // No need to delete ui as it's a unique_ptr + // The unique_ptr will automatically delete the object when it goes out of scope } void MainWindow::showLoginDialog() @@ -276,7 +109,7 @@ void MainWindow::showLoginDialog() codeEditor->setDocument(currentDocument); codeEditor->setPlainText(initialContent); codeEditor->setLanguage("C++"); - codeEditor->highlightSyntax(); // Force syntax highlighting update + codeEditor->highlightSyntax(); // Update UI updateTitle(); @@ -286,11 +119,18 @@ void MainWindow::showLoginDialog() chatBox->clear(); chatInput->clear(); - // Try to connect to the collaboration server - if (collaborationClient->connect("ws://localhost:8080")) { - statusLabel->setText("Connected to server"); + // Try to start or join session + if (server->startServer(1234)) { + // First user - started server + statusLabel->setText("Started collaboration session"); + isCollaborating = true; + lastSentText = initialContent; + server->sendToClients(initialContent); } else { - statusLabel->setText("Not connected to server"); + // Subsequent user - try to join + client->connectToHost("127.0.0.1", 1234); + isCollaborating = true; + statusLabel->setText("Joined collaboration session"); } } } @@ -348,9 +188,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) return QMainWindow::eventFilter(obj, event); } -// Slot implementations -void MainWindow::onLogin() -{ +void MainWindow::onLogin() { // If already logged in, ask if user wants to logout first if (currentUser) { QMessageBox::StandardButton reply; @@ -457,12 +295,14 @@ void MainWindow::onNewDocument() } } -void MainWindow::onOpenDocument() -{ - if (!currentUser) { - QMessageBox::warning(this, "Not Logged In", "You must be logged in to open a document."); - return; - } +void MainWindow::onOpenDocument() { + QString file = QFileDialog::getOpenFileName(this, "Open File"); + if (!file.isEmpty()) { + QFile f(file); + if (f.open(QIODevice::ReadOnly)) { + codeEditor->setPlainText(f.readAll()); + f.close(); + } QString fileName = QFileDialog::getOpenFileName(this, "Open Document", "", "C++ Files (*.cpp *.h);;Python Files (*.py);;JavaScript Files (*.js);;Java Files (*.java);;All Files (*)"); @@ -493,9 +333,9 @@ void MainWindow::onOpenDocument() currentDocument->setLanguage("JavaScript"); } else if (extension == "java") { currentDocument->setLanguage("Java"); - } else { + } else { currentDocument->setLanguage("Plain"); - } + } // Set up editor codeEditor->setDocument(currentDocument); @@ -508,12 +348,13 @@ void MainWindow::onOpenDocument() // Clear chat chatBox->clear(); - chatInput->clear(); + chatInput->clear(); } else { QMessageBox::critical(this, "Error", "Could not open file: " + fileName); } } } +} void MainWindow::onSaveDocument() { @@ -584,24 +425,6 @@ void MainWindow::onUserDisconnected(const QString& userId) } } -void MainWindow::onCursorPositionChanged() -{ - if (!currentUser || !currentDocument) return; - - // Get current position - int position = codeEditor->textCursor().position(); - - // Update status bar with position information - int line = codeEditor->textCursor().blockNumber() + 1; - int column = codeEditor->textCursor().columnNumber() + 1; - statusLabel->setText(QString("Line: %1 Column: %2 | %3").arg(line).arg(column).arg(currentUser->getUsername())); - - // Send cursor position to other users - if (collaborationClient && collaborationClient->isConnected()) { - collaborationClient->sendCursorPosition(position); - } -} - void MainWindow::onTextChanged() { if (!currentUser || !currentDocument) return; @@ -669,6 +492,8 @@ void MainWindow::onSendChatMessage() // Send message through collaboration client if (collaborationClient && collaborationClient->isConnected()) { collaborationClient->sendChatMessage(message); + // Display message locally + onChatMessageReceived(currentUser->getUserId(), currentUser->getUsername(), message); } } } @@ -699,4 +524,212 @@ void MainWindow::addDocument(std::shared_ptr document) updateTitle(); updateStatusBar(); -} \ No newline at end of file +} + +void MainWindow::onRemoteTextChanged(const QString &message) { + if (!codeEditor || typingTimer->isActive()) return; // Don't process updates while typing + + // Store current cursor position + QTextCursor cursor = codeEditor->textCursor(); + int cursorPosition = cursor.position(); + + // Block signals to prevent feedback loop + codeEditor->blockSignals(true); + codeEditor->ignoreChanges = true; + + // Apply the remote changes + codeEditor->setPlainText(message); + + // Update document content + if (currentDocument) { + currentDocument->setContent(message); + } + + // Restore cursor position if it's still valid + if (cursorPosition <= message.length()) { + cursor.setPosition(cursorPosition); + codeEditor->setTextCursor(cursor); + } + + // Restore signal handling + codeEditor->ignoreChanges = false; + codeEditor->blockSignals(false); +} + +void MainWindow::setupUI() { + QWidget* centralWidget = new QWidget(this); + QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget); + mainLayout->setContentsMargins(5, 5, 5, 5); + setCentralWidget(centralWidget); + + // Create main splitter (editor | right panel) + mainSplitter = std::make_unique(Qt::Horizontal); + mainLayout->addWidget(mainSplitter.get()); + + // Create code editor + codeEditor = std::make_unique(); + codeEditor->setCollaborationManager(collaborationManager); + mainSplitter->addWidget(codeEditor.get()); + + // Create right panel splitter (user list | chat) + rightSplitter = std::make_unique(Qt::Vertical); + mainSplitter->addWidget(rightSplitter.get()); + + // Create user list panel + QWidget* userListPanel = new QWidget(rightSplitter.get()); + QVBoxLayout* userListLayout = new QVBoxLayout(userListPanel); + userListLayout->setContentsMargins(0, 0, 0, 0); + + QLabel* collaboratorsLabel = new QLabel("Collaborators:"); + userListLayout->addWidget(collaboratorsLabel); + + QListWidget* userList = new QListWidget(); + userListLayout->addWidget(userList); + + rightSplitter->addWidget(userListPanel); + + // Create chat panel + QWidget* chatPanel = new QWidget(rightSplitter.get()); + QVBoxLayout* chatLayout = new QVBoxLayout(chatPanel); + chatLayout->setContentsMargins(0, 0, 0, 0); + + QLabel* chatLabel = new QLabel("Chat:"); + chatLayout->addWidget(chatLabel); + + chatBox = std::make_unique(); + chatBox->setReadOnly(true); + chatLayout->addWidget(chatBox.get()); + + chatInput = std::make_unique(); + chatInput->setMaximumHeight(60); + chatLayout->addWidget(chatInput.get()); + + QPushButton* sendButton = new QPushButton("Send"); + connect(sendButton, &QPushButton::clicked, this, &MainWindow::onSendChatMessage); + chatLayout->addWidget(sendButton); + + rightSplitter->addWidget(chatPanel); + mainSplitter->setSizes({600, 200}); + rightSplitter->setSizes({200, 400}); + + statusLabel = std::make_unique("Not logged in"); + statusBar()->addWidget(statusLabel.get()); + + QMenu* fileMenu = menuBar()->addMenu("&File"); + QAction* newAction = new QAction("New", this); + newAction->setShortcut(QKeySequence::New); + connect(newAction, &QAction::triggered, this, &MainWindow::onNewDocument); + fileMenu->addAction(newAction); + + QAction* openAction = new QAction("Open", this); + openAction->setShortcut(QKeySequence::Open); + connect(openAction, &QAction::triggered, this, &MainWindow::onOpenDocument); + fileMenu->addAction(openAction); + + QAction* saveAction = new QAction("Save", this); + saveAction->setShortcut(QKeySequence::Save); + connect(saveAction, &QAction::triggered, this, &MainWindow::onSaveDocument); + fileMenu->addAction(saveAction); + + fileMenu->addSeparator(); + + QAction* exitAction = new QAction("Exit", this); + exitAction->setShortcut(QKeySequence::Quit); + connect(exitAction, &QAction::triggered, this, &QWidget::close); + fileMenu->addAction(exitAction); + + QMenu* editMenu = menuBar()->addMenu("&Edit"); + QAction* cutAction = new QAction("Cut", this); + cutAction->setShortcut(QKeySequence::Cut); + connect(cutAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::cut); + editMenu->addAction(cutAction); + + QAction* copyAction = new QAction("Copy", this); + copyAction->setShortcut(QKeySequence::Copy); + connect(copyAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::copy); + editMenu->addAction(copyAction); + + QAction* pasteAction = new QAction("Paste", this); + pasteAction->setShortcut(QKeySequence::Paste); + connect(pasteAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::paste); + editMenu->addAction(pasteAction); + + QMenu* viewMenu = menuBar()->addMenu("&View"); + QAction* zoomInAction = new QAction("Zoom In", this); + zoomInAction->setShortcut(QKeySequence::ZoomIn); + connect(zoomInAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::zoomIn); + viewMenu->addAction(zoomInAction); + + QAction* zoomOutAction = new QAction("Zoom Out", this); + zoomOutAction->setShortcut(QKeySequence::ZoomOut); + connect(zoomOutAction, &QAction::triggered, codeEditor.get(), &QPlainTextEdit::zoomOut); + viewMenu->addAction(zoomOutAction); + + QMenu* userMenu = menuBar()->addMenu("&User"); + QAction* loginAction = new QAction("Login", this); + connect(loginAction, &QAction::triggered, this, &MainWindow::onLogin); + userMenu->addAction(loginAction); + + QAction* logoutAction = new QAction("Logout", this); + connect(logoutAction, &QAction::triggered, this, &MainWindow::onLogout); + userMenu->addAction(logoutAction); + + // Set window properties + resize(1024, 768); + setWindowTitle("CodeColab - Collaborative Code Editor"); +} + +void MainWindow::setupConnections() { + connect(codeEditor.get(), &CodeEditorWidget::editorContentChanged, this, &MainWindow::onTextChanged); + + // Connect collaboration client signals + connect(collaborationClient.get(), &CollaborationClient::connected, + this, [this]() { + statusLabel->setText("Connected to server"); + if (currentDocument) { + collaborationClient->joinDocument(currentDocument->getId()); + } + }); + + connect(collaborationClient.get(), &CollaborationClient::disconnected, + this, [this]() { + statusLabel->setText("Disconnected from server"); + connectedUsers.clear(); + updateUserList(); + }); + + connect(collaborationClient.get(), &CollaborationClient::error, + this, [this](const QString& error) { + QMessageBox::warning(this, "Collaboration Error", error); + }); + + connect(collaborationClient.get(), &CollaborationClient::editReceived, + this, [this](const EditOperation& op) { + if (currentDocument && codeEditor) { + codeEditor->applyRemoteEdit(op); + } + }); + + connect(collaborationClient.get(), &CollaborationClient::userConnected, + this, &MainWindow::onUserConnected); + + connect(collaborationClient.get(), &CollaborationClient::userDisconnected, + this, &MainWindow::onUserDisconnected); + + connect(collaborationClient.get(), &CollaborationClient::chatMessageReceived, + this, &MainWindow::onChatMessageReceived); + + // Connect chat input + connect(chatInput.get(), &QTextEdit::textChanged, [this]() { + QPushButton* sendButton = qobject_cast( + chatInput->parentWidget()->layout()->itemAt( + chatInput->parentWidget()->layout()->count() - 1)->widget()); + if (sendButton) { + sendButton->setEnabled(!chatInput->toPlainText().trimmed().isEmpty()); + } + }); + + chatInput->installEventFilter(this); +} + + diff --git a/src/NetworkClient.cpp b/src/NetworkClient.cpp new file mode 100644 index 0000000..09e5670 --- /dev/null +++ b/src/NetworkClient.cpp @@ -0,0 +1,22 @@ +#include "NetworkClient.h" + +NetworkClient::NetworkClient(QObject *parent) : QObject(parent) { + socket = new QTcpSocket(this); + connect(socket, &QTcpSocket::readyRead, this, &NetworkClient::readData); +} + +void NetworkClient::connectToHost(const QString &host, quint16 port) { + socket->connectToHost(host, port); +} + +void NetworkClient::sendMessage(const QString &message) { + socket->write(message.toUtf8()); +} + +void NetworkClient::readData() { + QString message = socket->readAll(); + emit messageReceived(message); +} + + + diff --git a/src/NetworkServer.cpp b/src/NetworkServer.cpp new file mode 100644 index 0000000..8aeaf7c --- /dev/null +++ b/src/NetworkServer.cpp @@ -0,0 +1,37 @@ +#include "NetworkServer.h" +#include + +NetworkServer::NetworkServer(QObject *parent) : QObject(parent) { + server = new QTcpServer(this); + connect(server, &QTcpServer::newConnection, this, &NetworkServer::onNewConnection); +} + +bool NetworkServer::startServer(quint16 port) { + if (!server->listen(QHostAddress::Any, port)) { + qDebug() << "Server could not start!"; + return false; + } else { + qDebug() << "Server started on port:" << port; + return true; + } +} + +void NetworkServer::onNewConnection() { + QTcpSocket *client = server->nextPendingConnection(); + clients.append(client); + connect(client, &QTcpSocket::readyRead, this, &NetworkServer::readClientData); +} + +void NetworkServer::readClientData() { + QTcpSocket *client = qobject_cast(sender()); + if (!client) return; + QString data = client->readAll(); + emit dataReceived(data); + sendToClients(data); +} + +void NetworkServer::sendToClients(const QString &message) { + for (QTcpSocket *client : std::as_const(clients)) { + client->write(message.toUtf8()); + } +} diff --git a/users.json b/users.json new file mode 100644 index 0000000..4d6e14a --- /dev/null +++ b/users.json @@ -0,0 +1,14 @@ +{ + "c": { + "email": "c@g.com", + "password": "c" + }, + "k": { + "email": "k@g.com", + "password": "k" + }, + "r": { + "email": "r@g.com", + "password": "r" + } +}