Actor programming is nice and makes our lives easier, but at some point, we have to display the output of our program to the user. Not everything is a command line tool. Hence, we have to use some sort of GUI library, which raises the question how we pass messages from actors - safely! - to the GUI at runtime. Well, what if we could send an ordinary message to the GUI as if it's an actor? What if the GUI can send us ordinary messages whenever the user pushes some buttons? Briefly speaking, can we treat GUI elements as actors? Luckily, the answer is yes! We have to write some gluecode, but in fact, you can treat
everything as an actor with
libcppa.
Getting Started
In this article, we will focus on the Qt library, as it is the open source GUI library of choice for C++ developers. In the next article, we will have a look at the general concept of
actor companions. An actor companion is an actor that co-exists with another object. In our case, this object is a QWidget-base class. The companion is used to receive and send messages, but it does not have any behavior itself. All it takes to enable a class to have such a companion is to use the
actor_widget_mixin.
The following class implements a simple group-based chat widget with a
QTextEdit for text output and a
QLineEdit for user input (chat messages). The full source code can be found
in the examples folder in the Git repository.
#include <QWidget>
#include "cppa/qtsupport/actor_widget_mixin.hpp"
class ChatWidget : public cppa::actor_widget_mixin<QWidget> {
Q_OBJECT
typedef cppa::actor_widget_mixin<QWidget> super;
// ...
public:
ChatWidget(QWidget* parent = nullptr, Qt::WindowFlags f = 0);
// ...
private:
std::string m_name;
cppa::group_ptr m_chatroom;
};
The mixin adds the following member functions to
ChatWidget:
- actor_ptr as_actor()
returns the companion of this widget
- set_message_handler
sets the partial function for message processing
Handle Messages to the Widget
In our example, the widget should handle
'join',
'setName', and
'quit' messages as well as display chat messages (received as
std::string). We encode our message handling directly into the constructor of
ChatWidget:
ChatWidget::ChatWidget(QWidget* parent, Qt::WindowFlags f) : super(parent, f) {
set_message_handler (
on(atom("join"), arg_match) >> [=](const group_ptr& what) {
if (m_chatroom) {
send(m_chatroom, m_name + " has left the chatroom");
self->leave(m_chatroom);
}
self->join(what);
print(("*** joined " + to_string(what)).c_str());
m_chatroom = what;
send(what, m_name + " has entered the chatroom");
},
on(atom("setName"), arg_match) >> [=](string& name) {
send(m_chatroom, m_name + " is now known as " + name);
m_name = std::move(name);
print("*** changed name to "
+ QString::fromUtf8(m_name.c_str()));
},
on(atom("quit")) >> [=] {
close(); // close widget
},
on<string>() >> [=](const string& txt) {
// don't print own messages
if (self != self->last_sender()) {
print(QString::fromUtf8(txt.c_str()));
}
}
);
}
Pitfalls & Things to Know
The code is pretty straight forward if you are already familiar with
libcppa, but there is one important thing to note:
Unhandled messages are lost. Although the widget is able to handle
libcppa messages now, it does
not have a mailbox. Messages are delivered to the widget as
QEvent objects that are disposed afterwards.
The second thing to note is that you should
never use
self outside the message handler. This includes using functions such as
send, because
self is internally used to determine the sender of a message. The reason to this limitation is that
libcppa's
self "pointer" is thread-local. Using
self would therefore convert the Qt thread your widget runs in to an actor, but it wouldn't address your widget's companion.
To send a message from outside of your handler, you have to tell
libcppa who is the sender of the message by hand:
send_as(as_actor(), m_chatroom, "hello world!");
Have fun!