Files
snoop_device/third_party/socket.io-client-cpp/examples/QT/README.md
2025-09-08 14:36:31 +03:00

291 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
![Demo shot](https://cldup.com/98tHyoJJE7.gif)
In this tutorial well learn how to create a QT chat application that communicates with a [Socket.IO Node.JS chat server](https://github.com/Automattic/socket.io/tree/master/examples/chat).
### Introduction
To follow along, start by cloning the repository: [socket.io-client-cpp](https://github.com/socketio/socket.io-client-cpp).
Using:
```bash
git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git
```
The app has the following features:
* Sending a message to all users joining to the room.
* Notifies when each user joins or leaves.
* Notifies when an user start typing a message.
###Install QT community
Visit [QT community download link](http://www.qt.io/download-open-source/#section-2) to get the install package.
Just install it with default installation option.
###Create a QT GUI application.
Launch QT Creator.
In welcome page, select `New Project`, create a `QT Widget Application`, named it `SioChatDemo`
The project structure is like:
```
SioChatDemo
|__ SioChatDemo.pro
|__Headers
| |__mainwindow.h
|__Sources
| |__main.cpp
| |__mainwindow.cpp
|__Forms
|__mainwindow.ui
```
### Import SioClient and config compile options.
Let's copy the SioClient into the QT project as a subfolder `sioclient`.
Edit `SioChatDemo.pro` to config paths and compile options, simply add:
```bash
SOURCES += ./sioclient/src/sio_client.cpp \
./sioclient/src/sio_packet.cpp
HEADERS += ./sioclient/src/sio_client.h \
./sioclient/src/sio_message.h
INCLUDEPATH += $$PWD/sioclient/lib/rapidjson/include
INCLUDEPATH += $$PWD/sioclient/lib/websocketpp
```
Also add two additional compile option
```bash
CONFIG+=no_keywords
CONFIG+=c++11
```
`no_keywords` is for preventing qmake treat some function's name `emit` as the keyword of signal-slot mechanism.
`c++11` ask for C++11 support.
### Make up mainwindow ui.
Make up a simple ui by drag and drop widget from `Widget box` in left side.
We finally end up with this:
![QT mainwindow](https://cldup.com/RI98CYpYL5.png)
It contains:
* a `QLineEdit` at the top for nickname inputing, named `nickNameEdit`
* a `QPushButton` at the topright for login, named `loginBtn`
* a `QListWidget` at the center for showing messages, named `listView`
* a `QLineEdit` at the bottom for typing message, named `messageEdit`
* a `QPushButton` at the bottomright for sending message, named `sendBtn`
### Add Slots in mainwindow
Slots need to be added in `mainwindow` class to handle UI events.They are
* click login button
* click send message button
* text change in messageEdit(for typing status)
* message editing is returned (for sending message by return)
Insert following code into `MainWindow` class in `mainwindow.h`
```C++
public Q_SLOTS:
void SendBtnClicked();
void TypingChanged();
void LoginClicked();
void OnMessageReturn();
```
### Connect UI event signal and slots together
Open `mainwindow.ui` in Design mode. switch to `signals/slots` mode by check `Menu->Edit->Edit Signals/Slots`
By press left mouse on widget and drag on to the window (cursor will become a sign of electrical ground), to open the connection editor.
In the connection editor, edit the slots of MainWindow at the right side, add Those slots function name added in `mainwindow.h` before.
Then we'll be able to connect the event signal from widget with our own slots.
We finally end up with this:
![QT signals&slots](https://cldup.com/Vsb-UXG3FC.jpg)
### Adding UI refresh Signals/Slots
`sio::client`'s callbacks are not in UI thread. However, UI is required to be updated by those callbacks, so we need some `Signal` for non-UI thread to "request" `Slots` functions been called in UI thread. Say if we want to signal `QListWidgetItem` being added, add:
```C++
//In mainwindow.h
Q_SIGNALS:
void RequestAddListItem(QListWidgetItem *item);
private Q_SLOTS:
void AddListItem(QListWidgetItem *item);
```
```C++
//In mainwindow.cpp
void MainWindow::AddListItem(QListWidgetItem* item)
{
this->findChild<QListWidget*>("listView")->addItem(item);
}
```
Then connect them in `MainWindow` constructor.
```C++
connect(this,SIGNAL(RequestAddListItem(QListWidgetItem*)),this,SLOT(AddListItem(QListWidgetItem*)));
```
### Init sio::client in MainWindow
For single window applications, simply let `MainWindow` class holding the `sio::client` object:
declare a `unique_ptr` member of `sio::client` and Several event handling functions in `mainwindow.h`
```C++
private:
void OnNewMessage(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnUserJoined(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnUserLeft(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnTyping(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnStopTyping(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnLogin(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp);
void OnConnected();
void OnClosed(client::close_reason const& reason);
void OnFailed();
std::unique_ptr<client> _io;
```
Init `sio::client` and setup event bindings for default `socket` in `MainWindow` constructor.
And we also need to handle the connectivity events, handle the connect and disconnect events.
Now the `MainWindow` constructor:
```C++
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
_io(new client())
{
ui->setupUi(this);
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
socket::ptr sock = _io->socket();
sock->on("new message",std::bind(&MainWindow::OnNewMessage,this,_1,_2,_3,_4));
sock->on("user joined",std::bind(&MainWindow::OnUserJoined,this,_1,_2,_3,_4));
sock->on("user left",std::bind(&MainWindow::OnUserLeft,this,_1,_2,_3,_4));
sock->on("typing",std::bind(&MainWindow::OnTyping,this,_1,_2,_3,_4));
sock->on("stop typing",std::bind(&MainWindow::OnStopTyping,this,_1,_2,_3,_4));
sock->on("login",std::bind(&MainWindow::OnLogin,this,_1,_2,_3,_4));
//default socket opened, also we have "set_open_listener" for monitoring physical connection opened.
_io->set_socket_open_listener(std::bind(&MainWindow::OnConnected,this,std::placeholders::_1));
//physical connection closed or drop.
_io->set_close_listener(std::bind(&MainWindow::OnClosed,this,_1));
//physical connection fail to establish.
_io->set_fail_listener(std::bind(&MainWindow::OnFailed,this));
connect(this,SIGNAL(RequestAddListItem(QListWidgetItem*)),this,SLOT(AddListItem(QListWidgetItem*)));
}
```
### Managing connection state
We have several connection listeners for connection events.
First we want to send login message once we're connected, get the default `socket` from `client` to do that.
```C++
void MainWindow::OnConnected()
{
QByteArray bytes = m_name.toUtf8();
std::string nickName(bytes.data(),bytes.length());
_io->socket()->emit("add user", nickName);
}
```
Then if connection is closed or failed, we need to restore to the UI before connect.
```C++
void MainWindow::OnClosed(client::close_reason const& reason)
{
//restore UI to pre-login state
}
void MainWindow::OnFailed()
{
//restore UI to pre-login state
}
```
If `MainWindow` is exit, we need to clear event bindings and listeners.
the `sio::client` object will be destruct by `unique_ptr`
```C++
MainWindow::~MainWindow()
{
_io->socket()->off_all();
_io->socket()->off_error();
delete ui;
}
```
### Handle socket.io events
We'll need to handle socket.io events in our functions bind to socket.io events.
For example, we need to show received messages to the `listView`
```C++
void MainWindow::OnNewMessage(std::string const& name,message::ptr const& data,bool hasAck,message::ptr &ack_resp)
{
if(data->get_flag() == message::flag_object)
{
std::string msg = data->get_map()["message"]->get_string();
std::string name = data->get_map()["username"]->get_string();
QString label = QString::fromUtf8(name.data(),name.length());
label.append(':');
label.append(QString::fromUtf8(msg.data(),msg.length()));
QListWidgetItem *item= new QListWidgetItem(label);
//emit RequestAddListItem signal
//so that 'AddListItem' will be executed in UI thread.
Q_EMIT RequestAddListItem(item);
}
}
```
### Sending chat message
When `sendBtn` is clicked, we need to send the text in `messageEdit` to chatroom.
Add code to `SendBtnClicked()`:
```C++
void MainWindow::SendBtnClicked()
{
QLineEdit* messageEdit = this->findChild<QLineEdit*>("messageEdit");
QString text = messageEdit->text();
if(text.length()>0)
{
QByteArray bytes = text.toUtf8();
std::string msg(bytes.data(),bytes.length());
_io->socket()->emit("new message",msg);//emit new message
text.append(":You");
QListWidgetItem *item = new QListWidgetItem(text);
item->setTextAlignment(Qt::AlignRight);
Q_EMIT RequestAddListItem(item);
messageEdit->clear();
}
}
```
### Further reading
You can run [Demo project](https://github.com/socketio/socket.io-client-cpp/tree/master/examples/QT/SioChatDemo) to have a closer look.
Before running, please follow the [instructions](../../README.md#with_cmake) to make the sioclient library.