Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -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
74 changes: 73 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 29 additions & 0 deletions SerialChat.pro
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "mainwindow.h"
#include <QApplication>

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();
}
199 changes: 199 additions & 0 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#include "mainwindow.h"
#include "messagebubblewidget.h"
#include "models/session.h"
#include "models/message.h"
#include "models/serialport.h"
#include <QSplitter>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QApplication>
#include <QScrollBar>

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());
}
Loading