From ddd9528bbd33219dc65e5c238de91a6eab2c071c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:07:48 +0000 Subject: [PATCH 1/5] Initial plan From a5d9decd110a78ce21c93c844a598bc7038c4ac9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:16:49 +0000 Subject: [PATCH 2/5] Add complete Serial Chat Qt application boilerplate Co-authored-by: zhangmx <1308646+zhangmx@users.noreply.github.com> --- .gitignore | 20 ++++ SerialChat.pro | 29 ++++++ src/main.cpp | 16 +++ src/mainwindow.cpp | 199 ++++++++++++++++++++++++++++++++++++ src/mainwindow.h | 48 +++++++++ src/messagebubblewidget.cpp | 129 +++++++++++++++++++++++ src/messagebubblewidget.h | 40 ++++++++ src/models/message.cpp | 47 +++++++++ src/models/message.h | 50 +++++++++ src/models/serialport.cpp | 91 +++++++++++++++++ src/models/serialport.h | 47 +++++++++ src/models/session.cpp | 73 +++++++++++++ src/models/session.h | 48 +++++++++ 13 files changed, 837 insertions(+) create mode 100644 SerialChat.pro create mode 100644 src/main.cpp create mode 100644 src/mainwindow.cpp create mode 100644 src/mainwindow.h create mode 100644 src/messagebubblewidget.cpp create mode 100644 src/messagebubblewidget.h create mode 100644 src/models/message.cpp create mode 100644 src/models/message.h create mode 100644 src/models/serialport.cpp create mode 100644 src/models/serialport.h create mode 100644 src/models/session.cpp create mode 100644 src/models/session.h diff --git a/.gitignore b/.gitignore index 7f4826b..aa47056 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,14 @@ target_wrapper.* # QtCreator *.autosave +# Qt MOC files +moc_*.cpp +moc_*.h +moc_predefs.h + +# Object files +*.o + # QtCreator Qml *.qmlproject.user *.qmlproject.user.* @@ -52,3 +60,15 @@ compile_commands.json *creator.user* *_qmlcache.qrc + +# Build directories +build/ +build-*/ + +# Binaries +SerialChat +SerialChat.exe + +# Screenshots and temporary files +screenshot.png +*.xwd diff --git a/SerialChat.pro b/SerialChat.pro new file mode 100644 index 0000000..874a389 --- /dev/null +++ b/SerialChat.pro @@ -0,0 +1,29 @@ +QT += core gui widgets serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + src/main.cpp \ + src/mainwindow.cpp \ + src/messagebubblewidget.cpp \ + src/models/message.cpp \ + src/models/serialport.cpp \ + src/models/session.cpp + +HEADERS += \ + src/mainwindow.h \ + src/messagebubblewidget.h \ + src/models/message.h \ + src/models/serialport.h \ + src/models/session.h + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..92d0f8d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,16 @@ +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + app.setApplicationName("Serial Chat"); + app.setOrganizationName("SerialChat"); + app.setApplicationVersion("1.0.0"); + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..86fe5d1 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,199 @@ +#include "mainwindow.h" +#include "messagebubblewidget.h" +#include "models/session.h" +#include "models/message.h" +#include "models/serialport.h" +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , m_sessionListModel(new QStringListModel(this)) + , m_currentSession(nullptr) +{ + setupUI(); + createMenuBar(); + + // Create a default session for demonstration + m_currentSession = new Session("Default Session", this); + connect(m_currentSession, &Session::messageAdded, this, &MainWindow::handleMessageAdded); + + // Add a welcome system message + Message *welcomeMsg = new Message( + QByteArray("Welcome to Serial Chat! Connect a serial port to start chatting."), + Message::System, + nullptr, + m_currentSession + ); + m_currentSession->addMessage(welcomeMsg); + + // Update session list + QStringList sessions; + sessions << "Default Session"; + m_sessionListModel->setStringList(sessions); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::setupUI() +{ + setWindowTitle("Serial Chat"); + resize(1000, 700); + + // Create central widget with horizontal splitter + QSplitter *mainSplitter = new QSplitter(Qt::Horizontal, this); + setCentralWidget(mainSplitter); + + // Left sidebar - Session list + QWidget *leftPanel = new QWidget(this); + QVBoxLayout *leftLayout = new QVBoxLayout(leftPanel); + leftLayout->setContentsMargins(0, 0, 0, 0); + + QLabel *sessionLabel = new QLabel("Sessions", leftPanel); + sessionLabel->setStyleSheet("QLabel { font-weight: bold; padding: 10px; background-color: #f0f0f0; }"); + leftLayout->addWidget(sessionLabel); + + m_sessionListView = new QListView(leftPanel); + m_sessionListView->setModel(m_sessionListModel); + leftLayout->addWidget(m_sessionListView); + + mainSplitter->addWidget(leftPanel); + + // Central area - Chat view + QWidget *centralPanel = new QWidget(this); + QVBoxLayout *centralLayout = new QVBoxLayout(centralPanel); + centralLayout->setContentsMargins(0, 0, 0, 0); + + // Chat scroll area + m_chatScrollArea = new QScrollArea(centralPanel); + m_chatScrollArea->setWidgetResizable(true); + m_chatScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + m_chatContainer = new QWidget(); + m_chatLayout = new QVBoxLayout(m_chatContainer); + m_chatLayout->setAlignment(Qt::AlignTop); + m_chatLayout->setSpacing(5); + m_chatScrollArea->setWidget(m_chatContainer); + + centralLayout->addWidget(m_chatScrollArea); + + // Bottom bar - Input area + QWidget *inputPanel = new QWidget(centralPanel); + QHBoxLayout *inputLayout = new QHBoxLayout(inputPanel); + + m_messageInput = new QLineEdit(inputPanel); + m_messageInput->setPlaceholderText("Type a message or hex data..."); + m_sendButton = new QPushButton("Send", inputPanel); + m_sendButton->setFixedWidth(80); + + connect(m_sendButton, &QPushButton::clicked, this, &MainWindow::handleSendButtonClicked); + connect(m_messageInput, &QLineEdit::returnPressed, this, &MainWindow::handleSendButtonClicked); + + inputLayout->addWidget(m_messageInput); + inputLayout->addWidget(m_sendButton); + + centralLayout->addWidget(inputPanel); + + mainSplitter->addWidget(centralPanel); + + // Right dock - Serial port status (optional for MVP) + m_portDock = new QDockWidget("Serial Ports", this); + QWidget *dockWidget = new QWidget(m_portDock); + QVBoxLayout *dockLayout = new QVBoxLayout(dockWidget); + QLabel *portLabel = new QLabel("No ports connected", dockWidget); + portLabel->setAlignment(Qt::AlignCenter); + dockLayout->addWidget(portLabel); + m_portDock->setWidget(dockWidget); + addDockWidget(Qt::RightDockWidgetArea, m_portDock); + + // Set splitter proportions + mainSplitter->setStretchFactor(0, 1); + mainSplitter->setStretchFactor(1, 3); +} + +void MainWindow::createMenuBar() +{ + QMenuBar *menuBar = new QMenuBar(this); + setMenuBar(menuBar); + + // File menu + QMenu *fileMenu = menuBar->addMenu("&File"); + QAction *exitAction = fileMenu->addAction("E&xit"); + connect(exitAction, &QAction::triggered, qApp, &QApplication::quit); + + // Session menu + QMenu *sessionMenu = menuBar->addMenu("&Session"); + QAction *newSessionAction = sessionMenu->addAction("&New Session"); + QAction *clearAction = sessionMenu->addAction("&Clear Messages"); + connect(clearAction, &QAction::triggered, [this]() { + if (m_currentSession) { + // Clear layout + QLayoutItem *item; + while ((item = m_chatLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + m_currentSession->clearMessages(); + } + }); + + // Port menu + QMenu *portMenu = menuBar->addMenu("&Port"); + QAction *connectAction = portMenu->addAction("&Connect Port..."); + QAction *disconnectAction = portMenu->addAction("&Disconnect Port"); + + // View menu + QMenu *viewMenu = menuBar->addMenu("&View"); + QAction *togglePortDockAction = viewMenu->addAction("Toggle &Port Panel"); + togglePortDockAction->setCheckable(true); + togglePortDockAction->setChecked(true); + connect(togglePortDockAction, &QAction::toggled, m_portDock, &QDockWidget::setVisible); +} + +void MainWindow::handleSendButtonClicked() +{ + QString text = m_messageInput->text().trimmed(); + if (text.isEmpty() || !m_currentSession) { + return; + } + + // Create outgoing message + Message *message = new Message( + text.toUtf8(), + Message::Outgoing, + nullptr, + m_currentSession + ); + m_currentSession->addMessage(message); + + // Clear input + m_messageInput->clear(); + m_messageInput->setFocus(); +} + +void MainWindow::handleMessageAdded(Message *message) +{ + addMessageBubble(message); +} + +void MainWindow::addMessageBubble(Message *message) +{ + if (!message) { + return; + } + + MessageBubbleWidget *bubble = new MessageBubbleWidget(message, m_chatContainer); + m_chatLayout->addWidget(bubble); + + // Scroll to bottom + QScrollBar *scrollBar = m_chatScrollArea->verticalScrollBar(); + scrollBar->setValue(scrollBar->maximum()); +} diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..a6c5fcd --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,48 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations +class Session; +class Message; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private Q_SLOTS: + void handleSendButtonClicked(); + void handleMessageAdded(Message *message); + +private: + void setupUI(); + void createMenuBar(); + void addMessageBubble(Message *message); + + // UI Components + QListView *m_sessionListView; + QStringListModel *m_sessionListModel; + QScrollArea *m_chatScrollArea; + QWidget *m_chatContainer; + QVBoxLayout *m_chatLayout; + QLineEdit *m_messageInput; + QPushButton *m_sendButton; + QDockWidget *m_portDock; + + // Data + Session *m_currentSession; +}; + +#endif // MAINWINDOW_H diff --git a/src/messagebubblewidget.cpp b/src/messagebubblewidget.cpp new file mode 100644 index 0000000..6149b14 --- /dev/null +++ b/src/messagebubblewidget.cpp @@ -0,0 +1,129 @@ +#include "messagebubblewidget.h" +#include "models/message.h" +#include +#include +#include + +MessageBubbleWidget::MessageBubbleWidget(Message *message, QWidget *parent) + : QWidget(parent) + , m_message(message) + , m_displayMode(TextMode) +{ + setMinimumHeight(40); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); +} + +void MessageBubbleWidget::setMessage(Message *message) +{ + m_message = message; + update(); + updateGeometry(); +} + +void MessageBubbleWidget::setDisplayMode(DisplayMode mode) +{ + if (m_displayMode != mode) { + m_displayMode = mode; + update(); + updateGeometry(); + } +} + +QString MessageBubbleWidget::formatContent() const +{ + if (!m_message) { + return QString(); + } + + if (m_displayMode == HexMode) { + return m_message->content().toHex(' ').toUpper(); + } else { + return QString::fromUtf8(m_message->content()); + } +} + +QRect MessageBubbleWidget::calculateBubbleRect() const +{ + if (!m_message) { + return QRect(); + } + + QFontMetrics fm(font()); + QString text = formatContent(); + QRect textRect = fm.boundingRect(QRect(0, 0, width() - 80, 1000), + Qt::TextWordWrap, text); + + int bubbleWidth = qMin(textRect.width() + 20, width() - 40); + int bubbleHeight = textRect.height() + 20; + + int x = 10; + if (m_message->direction() == Message::Outgoing) { + x = width() - bubbleWidth - 10; + } + + return QRect(x, 5, bubbleWidth, bubbleHeight); +} + +void MessageBubbleWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + if (!m_message) { + return; + } + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRect bubbleRect = calculateBubbleRect(); + + // Choose color based on message direction + QColor bubbleColor; + QColor textColor = Qt::black; + + switch (m_message->direction()) { + case Message::Incoming: + bubbleColor = QColor(230, 230, 230); // Light gray + break; + case Message::Outgoing: + bubbleColor = QColor(100, 150, 255); // Blue + textColor = Qt::white; + break; + case Message::System: + bubbleColor = QColor(255, 250, 200); // Light yellow + break; + } + + // Draw bubble with rounded corners + QPainterPath path; + path.addRoundedRect(bubbleRect, 10, 10); + painter.fillPath(path, bubbleColor); + + // Draw text + painter.setPen(textColor); + QString text = formatContent(); + QRect textRect = bubbleRect.adjusted(10, 10, -10, -10); + painter.drawText(textRect, Qt::TextWordWrap, text); + + // Draw timestamp in smaller font + painter.setFont(QFont(font().family(), font().pointSize() - 2)); + painter.setPen(QColor(100, 100, 100)); + QString timestamp = m_message->timestamp().toString("hh:mm:ss"); + QRect timestampRect = bubbleRect.adjusted(0, bubbleRect.height() + 2, 0, bubbleRect.height() + 15); + if (m_message->direction() == Message::Outgoing) { + painter.drawText(timestampRect, Qt::AlignRight, timestamp); + } else { + painter.drawText(timestampRect, Qt::AlignLeft, timestamp); + } +} + +QSize MessageBubbleWidget::sizeHint() const +{ + QRect bubbleRect = calculateBubbleRect(); + return QSize(width(), bubbleRect.height() + 20); +} + +QSize MessageBubbleWidget::minimumSizeHint() const +{ + return QSize(200, 40); +} diff --git a/src/messagebubblewidget.h b/src/messagebubblewidget.h new file mode 100644 index 0000000..c9e14e5 --- /dev/null +++ b/src/messagebubblewidget.h @@ -0,0 +1,40 @@ +#ifndef MESSAGEBUBBLEWIDGET_H +#define MESSAGEBUBBLEWIDGET_H + +#include + +// Forward declaration +class Message; + +class MessageBubbleWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MessageBubbleWidget(Message *message, QWidget *parent = nullptr); + + Message* message() const { return m_message; } + void setMessage(Message *message); + + enum DisplayMode { + TextMode, + HexMode + }; + + DisplayMode displayMode() const { return m_displayMode; } + void setDisplayMode(DisplayMode mode); + +protected: + void paintEvent(QPaintEvent *event) override; + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +private: + QString formatContent() const; + QRect calculateBubbleRect() const; + + Message *m_message; + DisplayMode m_displayMode; +}; + +#endif // MESSAGEBUBBLEWIDGET_H diff --git a/src/models/message.cpp b/src/models/message.cpp new file mode 100644 index 0000000..1bd18d1 --- /dev/null +++ b/src/models/message.cpp @@ -0,0 +1,47 @@ +#include "message.h" + +Message::Message(QObject *parent) + : QObject(parent) + , m_timestamp(QDateTime::currentDateTime()) + , m_direction(System) + , m_sourcePort(nullptr) +{ +} + +Message::Message(const QByteArray &content, Direction direction, SerialPort *sourcePort, QObject *parent) + : QObject(parent) + , m_content(content) + , m_timestamp(QDateTime::currentDateTime()) + , m_direction(direction) + , m_sourcePort(sourcePort) +{ +} + +void Message::setContent(const QByteArray &content) +{ + if (m_content != content) { + m_content = content; + emit contentChanged(); + } +} + +void Message::setTimestamp(const QDateTime ×tamp) +{ + if (m_timestamp != timestamp) { + m_timestamp = timestamp; + emit timestampChanged(); + } +} + +void Message::setDirection(Direction direction) +{ + if (m_direction != direction) { + m_direction = direction; + emit directionChanged(); + } +} + +void Message::setSourcePort(SerialPort *port) +{ + m_sourcePort = port; +} diff --git a/src/models/message.h b/src/models/message.h new file mode 100644 index 0000000..5310447 --- /dev/null +++ b/src/models/message.h @@ -0,0 +1,50 @@ +#ifndef MESSAGE_H +#define MESSAGE_H + +#include +#include +#include + +// Forward declaration +class SerialPort; + +class Message : public QObject +{ + Q_OBJECT + +public: + enum Direction { + Incoming, + Outgoing, + System + }; + Q_ENUM(Direction) + + explicit Message(QObject *parent = nullptr); + Message(const QByteArray &content, Direction direction, SerialPort *sourcePort = nullptr, QObject *parent = nullptr); + + QByteArray content() const { return m_content; } + void setContent(const QByteArray &content); + + QDateTime timestamp() const { return m_timestamp; } + void setTimestamp(const QDateTime ×tamp); + + Direction direction() const { return m_direction; } + void setDirection(Direction direction); + + SerialPort* sourcePort() const { return m_sourcePort; } + void setSourcePort(SerialPort *port); + +Q_SIGNALS: + void contentChanged(); + void timestampChanged(); + void directionChanged(); + +private: + QByteArray m_content; + QDateTime m_timestamp; + Direction m_direction; + SerialPort *m_sourcePort; +}; + +#endif // MESSAGE_H diff --git a/src/models/serialport.cpp b/src/models/serialport.cpp new file mode 100644 index 0000000..48ac115 --- /dev/null +++ b/src/models/serialport.cpp @@ -0,0 +1,91 @@ +#include "serialport.h" + +SerialPort::SerialPort(QObject *parent) + : QObject(parent) + , m_baudRate(9600) + , m_serialPort(new QSerialPort(this)) +{ + connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPort::handleReadyRead); + connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPort::handleError); +} + +SerialPort::SerialPort(const QString &portName, QObject *parent) + : QObject(parent) + , m_portName(portName) + , m_baudRate(9600) + , m_serialPort(new QSerialPort(this)) +{ + m_serialPort->setPortName(portName); + connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPort::handleReadyRead); + connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPort::handleError); +} + +SerialPort::~SerialPort() +{ + if (m_serialPort->isOpen()) { + m_serialPort->close(); + } +} + +void SerialPort::setPortName(const QString &portName) +{ + if (m_portName != portName) { + m_portName = portName; + m_serialPort->setPortName(portName); + emit portNameChanged(); + } +} + +void SerialPort::setBaudRate(qint32 baudRate) +{ + if (m_baudRate != baudRate) { + m_baudRate = baudRate; + m_serialPort->setBaudRate(baudRate); + emit baudRateChanged(); + } +} + +bool SerialPort::isOpen() const +{ + return m_serialPort->isOpen(); +} + +bool SerialPort::open() +{ + if (m_serialPort->open(QIODevice::ReadWrite)) { + m_serialPort->setBaudRate(m_baudRate); + emit connected(); + return true; + } + return false; +} + +void SerialPort::close() +{ + if (m_serialPort->isOpen()) { + m_serialPort->close(); + emit disconnected(); + } +} + +void SerialPort::sendData(const QByteArray &data) +{ + if (m_serialPort->isOpen()) { + m_serialPort->write(data); + } +} + +void SerialPort::handleReadyRead() +{ + QByteArray data = m_serialPort->readAll(); + if (!data.isEmpty()) { + emit dataReceived(data); + } +} + +void SerialPort::handleError(QSerialPort::SerialPortError error) +{ + if (error != QSerialPort::NoError) { + emit errorOccurred(m_serialPort->errorString()); + } +} diff --git a/src/models/serialport.h b/src/models/serialport.h new file mode 100644 index 0000000..d2f121d --- /dev/null +++ b/src/models/serialport.h @@ -0,0 +1,47 @@ +#ifndef SERIALPORT_H +#define SERIALPORT_H + +#include +#include +#include + +class SerialPort : public QObject +{ + Q_OBJECT + +public: + explicit SerialPort(QObject *parent = nullptr); + explicit SerialPort(const QString &portName, QObject *parent = nullptr); + ~SerialPort(); + + QString portName() const { return m_portName; } + void setPortName(const QString &portName); + + qint32 baudRate() const { return m_baudRate; } + void setBaudRate(qint32 baudRate); + + bool isOpen() const; + bool open(); + void close(); + + void sendData(const QByteArray &data); + +Q_SIGNALS: + void dataReceived(const QByteArray &data); + void portNameChanged(); + void baudRateChanged(); + void errorOccurred(const QString &error); + void connected(); + void disconnected(); + +private Q_SLOTS: + void handleReadyRead(); + void handleError(QSerialPort::SerialPortError error); + +private: + QString m_portName; + qint32 m_baudRate; + QSerialPort *m_serialPort; +}; + +#endif // SERIALPORT_H diff --git a/src/models/session.cpp b/src/models/session.cpp new file mode 100644 index 0000000..6c60053 --- /dev/null +++ b/src/models/session.cpp @@ -0,0 +1,73 @@ +#include "session.h" +#include "serialport.h" +#include "message.h" + +Session::Session(QObject *parent) + : QObject(parent) + , m_name("Default Session") +{ +} + +Session::Session(const QString &name, QObject *parent) + : QObject(parent) + , m_name(name) +{ +} + +Session::~Session() +{ + // Clean up messages (they are owned by this session) + qDeleteAll(m_messages); + m_messages.clear(); +} + +void Session::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + emit nameChanged(); + } +} + +void Session::addSerialPort(SerialPort *port) +{ + if (port && !m_serialPorts.contains(port)) { + m_serialPorts.append(port); + // Connect to receive data from this port + connect(port, &SerialPort::dataReceived, this, &Session::handleDataReceived); + emit serialPortAdded(port); + } +} + +void Session::removeSerialPort(SerialPort *port) +{ + if (port && m_serialPorts.contains(port)) { + disconnect(port, &SerialPort::dataReceived, this, &Session::handleDataReceived); + m_serialPorts.removeOne(port); + emit serialPortRemoved(port); + } +} + +void Session::addMessage(Message *message) +{ + if (message) { + m_messages.append(message); + emit messageAdded(message); + } +} + +void Session::clearMessages() +{ + qDeleteAll(m_messages); + m_messages.clear(); + emit messagesCleared(); +} + +void Session::handleDataReceived(const QByteArray &data) +{ + SerialPort *port = qobject_cast(sender()); + if (port) { + Message *message = new Message(data, Message::Incoming, port, this); + addMessage(message); + } +} diff --git a/src/models/session.h b/src/models/session.h new file mode 100644 index 0000000..698065d --- /dev/null +++ b/src/models/session.h @@ -0,0 +1,48 @@ +#ifndef SESSION_H +#define SESSION_H + +#include +#include +#include + +// Forward declarations +class SerialPort; +class Message; + +class Session : public QObject +{ + Q_OBJECT + +public: + explicit Session(QObject *parent = nullptr); + explicit Session(const QString &name, QObject *parent = nullptr); + ~Session(); + + QString name() const { return m_name; } + void setName(const QString &name); + + void addSerialPort(SerialPort *port); + void removeSerialPort(SerialPort *port); + QList serialPorts() const { return m_serialPorts; } + + void addMessage(Message *message); + QList messages() const { return m_messages; } + void clearMessages(); + +Q_SIGNALS: + void nameChanged(); + void serialPortAdded(SerialPort *port); + void serialPortRemoved(SerialPort *port); + void messageAdded(Message *message); + void messagesCleared(); + +private Q_SLOTS: + void handleDataReceived(const QByteArray &data); + +private: + QString m_name; + QList m_serialPorts; + QList m_messages; +}; + +#endif // SESSION_H From 4f378aaaaf4f63fcc7e547ecee1beb48ac352da5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:18:46 +0000 Subject: [PATCH 3/5] Address code review feedback for Message::setSourcePort Co-authored-by: zhangmx <1308646+zhangmx@users.noreply.github.com> --- src/models/message.cpp | 5 ++++- src/models/message.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/models/message.cpp b/src/models/message.cpp index 1bd18d1..644b5ba 100644 --- a/src/models/message.cpp +++ b/src/models/message.cpp @@ -43,5 +43,8 @@ void Message::setDirection(Direction direction) void Message::setSourcePort(SerialPort *port) { - m_sourcePort = port; + if (m_sourcePort != port) { + m_sourcePort = port; + emit sourcePortChanged(); + } } diff --git a/src/models/message.h b/src/models/message.h index 5310447..e246fb9 100644 --- a/src/models/message.h +++ b/src/models/message.h @@ -39,6 +39,7 @@ class Message : public QObject void contentChanged(); void timestampChanged(); void directionChanged(); + void sourcePortChanged(); private: QByteArray m_content; From b0bbe39115e28a34b16bf29b6a3800defcf3fdd8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:19:51 +0000 Subject: [PATCH 4/5] Update README with build instructions and architecture documentation Co-authored-by: zhangmx <1308646+zhangmx@users.noreply.github.com> --- README.md | 74 +++++++++++++++++++++++++++++++++++- _codeql_detected_source_root | 1 + 2 files changed, 74 insertions(+), 1 deletion(-) create mode 120000 _codeql_detected_source_root diff --git a/README.md b/README.md index 0972a44..207cbee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,74 @@ # Serial-Chat -​​Serial Chat​​: Reimagining serial port debugging as an intuitive chat conversation. Visualize data flows between devices and applications in a familiar, collaborative interface. +**Serial Chat**: Reimagining serial port debugging as an intuitive chat conversation. Visualize data flows between devices and applications in a familiar, collaborative interface. + +## Project Status +This is the initial MVP boilerplate - a clean, well-architected Qt Widgets application skeleton that serves as the foundation for the Serial Chat vision. + +## Features +- **Chat-style interface** inspired by modern messaging apps (WeChat/Telegram) +- **Model-View architecture** with clear separation of concerns +- **Data models** for SerialPort, Message, and Session management +- **Custom message bubble widgets** with direction-based styling +- **Session management** with sidebar and message history +- **Serial port support** using Qt SerialPort module + +## Architecture + +### Data Models (Model Layer) +- **SerialPort**: Represents a physical/virtual serial port with properties like `portName`, `baudRate`, and signals like `dataReceived(QByteArray)` +- **Message**: Represents a single communication unit with `content`, `timestamp`, `direction` (Incoming/Outgoing/System), and `sourcePort` +- **Session**: Manages collections of SerialPort objects and message logs + +### UI Components (View Layer) +- **MainWindow**: Main window with left sidebar for session list, central scrollable message area, and optional right dock for port status +- **MessageBubbleWidget**: Custom widget that renders chat bubbles with different styles based on message direction + +## Building + +### Prerequisites +- Qt 5.x (Core, GUI, Widgets, SerialPort modules) +- C++17 compatible compiler +- qmake + +### Ubuntu/Debian +```bash +sudo apt-get install qtbase5-dev qt5-qmake libqt5serialport5-dev +``` + +### Build Instructions +```bash +qmake SerialChat.pro +make +``` + +### Run +```bash +./SerialChat +``` + +## Project Structure +``` +Serial-Chat/ +├── SerialChat.pro # Qt project file +├── src/ +│ ├── main.cpp # Application entry point +│ ├── mainwindow.h/cpp # Main window with chat interface +│ ├── messagebubblewidget.h/cpp # Message bubble rendering +│ └── models/ +│ ├── message.h/cpp # Message data model +│ ├── serialport.h/cpp # Serial port wrapper +│ └── session.h/cpp # Session management +``` + +## Future Development +This boilerplate provides the foundation for: +- Actual serial port connection and communication +- Multiple session support +- Message filtering and search +- Hex/text view switching +- Data export and logging +- Port configuration UI +- And more... + +## License +See LICENSE file for details. diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From 14fbfd74852c162275c786d04da00d8c85c3bc3e Mon Sep 17 00:00:00 2001 From: Flyman Date: Fri, 31 Oct 2025 16:28:53 +0800 Subject: [PATCH 5/5] Delete _codeql_detected_source_root --- _codeql_detected_source_root | 1 - 1 file changed, 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b4..0000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file