Difference between revisions of "Development/Tutorials/Decibel/Handling TextChannels"

Jump to: navigation, search
m (fix layout)
m (Text replace - "<syntaxhighlight lang="bash"" to "<syntaxhighlight lang="cmake"")
 
(8 intermediate revisions by 2 users not shown)
Line 5: Line 5:
 
name=Handling TextChannels|
 
name=Handling TextChannels|
  
pre=[http://mindview.net/Books/TICPP/ThinkingInCPP2e.html C++], [http://www.trolltech.com/products/qt/ Qt],  
+
pre=[http://mindview.net/Books/TICPP/ThinkingInCPP2e.html C++], [http://www.trolltech.com/products/qt/ Qt], [[Development/Tutorials/Decibel/GettingStarted|Getting Started with Decibel]]|  
[[Development/Tutorials/Decibel/GettingStarted|Getting Started with Decibel]]|  
+
 
+
 
next=|  
 
next=|  
  
Line 17: Line 15:
 
This tutorial is based on the simpleclient demo included with Decibel. An explanation of using the simpleclient demo can be [[Development/Tutorials/Decibel/GettingStarted#simpleclient|found here]].
 
This tutorial is based on the simpleclient demo included with Decibel. An explanation of using the simpleclient demo can be [[Development/Tutorials/Decibel/GettingStarted#simpleclient|found here]].
  
In this tutorial, we only pick out the important parts of the source code to discuss. The complete working source code for this example can be found [http://websvn.kde.org/trunk/kdereview/decibel/demos/simpleclient here in KDE's SVN Repository].
+
In this tutorial, we only pick out the important parts of the source code to discuss. The complete working source code for this example can be found [http://websvn.kde.org/trunk/kdesupport/decibel/demos/simpleclient here in KDE's SVN Repository].
  
 
==ChannelHandler class==
 
==ChannelHandler class==
Line 25: Line 23:
 
Here is the class definition for our implementation of the <tt>Decibel::ChannelHandler</tt> interface. It is explained below.
 
Here is the class definition for our implementation of the <tt>Decibel::ChannelHandler</tt> interface. It is explained below.
  
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
class MyTextChannelHandler : public Decibel::ChannelHandler
 
class MyTextChannelHandler : public Decibel::ChannelHandler
 
{
 
{
Line 44: Line 42:
 
     QtTapioca::TextChannel * m_channel;
 
     QtTapioca::TextChannel * m_channel;
 
};
 
};
</code>
+
</syntaxhighlight>
  
 
We must inherit from <tt>Decibel::ChannelHandler</tt> which is the interface definition for handling incoming Channels. We reimplement the constructor, Destructor and the <tt>handleChannel()</tt> method as well as adding two slots of our own. We also create the member variables <tt>m_connecion</tt> and <tt>m_channel</tt> which will hold the <tt>QtTapioca::Connection</tt> and <tt>QtTapioca::Channel</tt> objects that are received by the <tt>handleChannel</tt> method.
 
We must inherit from <tt>Decibel::ChannelHandler</tt> which is the interface definition for handling incoming Channels. We reimplement the constructor, Destructor and the <tt>handleChannel()</tt> method as well as adding two slots of our own. We also create the member variables <tt>m_connecion</tt> and <tt>m_channel</tt> which will hold the <tt>QtTapioca::Connection</tt> and <tt>QtTapioca::Channel</tt> objects that are received by the <tt>handleChannel</tt> method.
Line 53: Line 51:
  
 
====Constructor====
 
====Constructor====
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
MyTextChannelHandler::MyTextChannelHandler(QObject * parent) :
 
MyTextChannelHandler::MyTextChannelHandler(QObject * parent) :
 
     ChannelHandler(parent),
 
     ChannelHandler(parent),
Line 59: Line 57:
 
     m_channel(0)
 
     m_channel(0)
 
{ }
 
{ }
</code>
+
</syntaxhighlight>
 
The constructor is very simple. We just pass the <tt>parent</tt> object on to the parent class and initialise our member variables to <tt>0</tt>.
 
The constructor is very simple. We just pass the <tt>parent</tt> object on to the parent class and initialise our member variables to <tt>0</tt>.
  
 
====Destructor====
 
====Destructor====
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
MyTextChannelHandler::~MyTextChannelHandler()
 
MyTextChannelHandler::~MyTextChannelHandler()
 
{ }
 
{ }
</code>
+
</syntaxhighlight>
 
The destructor is even simpler. We don't need to do anything in it because Qt will handle the deletion of child objects automatically.
 
The destructor is even simpler. We don't need to do anything in it because Qt will handle the deletion of child objects automatically.
  
 
====handleChannel()====
 
====handleChannel()====
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
bool MyTextChannelHandler::handleChannel(QtTapioca::Connection * connection,
 
bool MyTextChannelHandler::handleChannel(QtTapioca::Connection * connection,
 
                                         QtTapioca::Channel * channel,
 
                                         QtTapioca::Channel * channel,
Line 95: Line 93:
 
     return true;
 
     return true;
 
}
 
}
</code>
+
</syntaxhighlight>
 
This method is reimplemented from the <tt>Decibel::ChannelHandler</tt> interface. It is called when ever a new incoming communication channel (<tt>QtTapioca::Channel</tt>) comes into existence. In this method, we must decide whether to accept the incoming channel, and if so, to deal with it appropriately.
 
This method is reimplemented from the <tt>Decibel::ChannelHandler</tt> interface. It is called when ever a new incoming communication channel (<tt>QtTapioca::Channel</tt>) comes into existence. In this method, we must decide whether to accept the incoming channel, and if so, to deal with it appropriately.
  
Line 111: Line 109:
  
 
====onCloseChannel()====
 
====onCloseChannel()====
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
void MyTextChannelHandler::onCloseChannel()
 
void MyTextChannelHandler::onCloseChannel()
 
{
 
{
Line 117: Line 115:
 
     m_channel = 0;
 
     m_channel = 0;
 
}
 
}
</code>
+
</syntaxhighlight>
 
This slot is called when the current TextChannel is closed. It resets the <tt>m_connection</tt> and <tt>m_channel</tt> member variables to <tt>0</tt> so that the <tt>handleChannel()</tt> method will accept the next incoming channel.
 
This slot is called when the current TextChannel is closed. It resets the <tt>m_connection</tt> and <tt>m_channel</tt> member variables to <tt>0</tt> so that the <tt>handleChannel()</tt> method will accept the next incoming channel.
  
 
====onMessageReceived()====
 
====onMessageReceived()====
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
void MyTextChannelHandler::onMessageReceived()
 
void MyTextChannelHandler::onMessageReceived()
 
{
 
{
Line 137: Line 135:
 
     }
 
     }
 
}
 
}
</code>
+
</syntaxhighlight>
 
This slot is called when a new message is received on the channel we are currently handling. It is where the processing of the message takes place.
 
This slot is called when a new message is received on the channel we are currently handling. It is where the processing of the message takes place.
  
Line 150: Line 148:
 
==The main() function==
 
==The main() function==
  
<code cppqt>
+
<syntaxhighlight lang="cpp-qt">
 
int main(int argc, char ** argv)
 
int main(int argc, char ** argv)
 
{
 
{
Line 166: Line 164:
 
     return app.exec();
 
     return app.exec();
 
}
 
}
</code>
+
</syntaxhighlight>
 
Since this is a console application, we set up a <tt>QCoreApplication</tt> first.
 
Since this is a console application, we set up a <tt>QCoreApplication</tt> first.
  
Line 181: Line 179:
  
 
The contents of our demo application's <tt>.component</tt> file are as follows.
 
The contents of our demo application's <tt>.component</tt> file are as follows.
<code ini>
+
<syntaxhighlight lang="ini">
 
[Component]
 
[Component]
 
Name=Simpleclient
 
Name=Simpleclient
Line 189: Line 187:
 
Service%20Name=org.kde.SimpleClient
 
Service%20Name=org.kde.SimpleClient
 
Object%20Path=/TextChannelHandler
 
Object%20Path=/TextChannelHandler
</code>
+
</syntaxhighlight>
  
The only group in this file is the <tt><nowiki>[Component]</nowiki></tt> group. It contains a series of keys idntifying our application to Decibel and detailing the funcionality it supports.
+
The only group in this file is the <tt><nowiki>[Component]</nowiki></tt> group. It contains a series of keys identifying our application to Decibel and detailing the funcionality it supports.
  
 
In this example, the important keys are the <tt>Service%20Name</tt> which tells Decibel the dbus name of our application, and the <tt>Object%20Path</tt> which tells Decibel the dbus object path that should be called on our application.
 
In this example, the important keys are the <tt>Service%20Name</tt> which tells Decibel the dbus name of our application, and the <tt>Object%20Path</tt> which tells Decibel the dbus object path that should be called on our application.
Line 198: Line 196:
 
In order to have dbus automatically start our application when Decibel tries to communicate with it, we must create a <tt>.service</tt> file.
 
In order to have dbus automatically start our application when Decibel tries to communicate with it, we must create a <tt>.service</tt> file.
  
<code ini>
+
<syntaxhighlight lang="ini">
 
[D-BUS Service]
 
[D-BUS Service]
 
Name=org.kde.SimpleClient
 
Name=org.kde.SimpleClient
 
Exec=@INSTALL_DIR@/decibel_simpleclient_demo
 
Exec=@INSTALL_DIR@/decibel_simpleclient_demo
</code>
+
</syntaxhighlight>
 
This is a standard dbus autostart file. It contains the dbus name of our application and the absolute path to it. <tt>@INSTALL_DIR</tt> will be replaced automatically by the installation directory using cmake in the next step.
 
This is a standard dbus autostart file. It contains the dbus name of our application and the absolute path to it. <tt>@INSTALL_DIR</tt> will be replaced automatically by the installation directory using cmake in the next step.
  
 
==CMakeLists.txt==
 
==CMakeLists.txt==
 
Since this example program is built as part of the main Decibel sources, I will not explain here how to create a standalone <tt>CMakeLists.txt</tt> file to build it, but will instead explain the important parts of the <tt>CMakeLists.txt</tt> file as found in the Decibel sources for this subdirectory. For more information on how <tt>CMakeLists.txt</tt> files work, please see the [[Development/Tutorials/CMake|Introduction to cmake]] tutorial.
 
Since this example program is built as part of the main Decibel sources, I will not explain here how to create a standalone <tt>CMakeLists.txt</tt> file to build it, but will instead explain the important parts of the <tt>CMakeLists.txt</tt> file as found in the Decibel sources for this subdirectory. For more information on how <tt>CMakeLists.txt</tt> files work, please see the [[Development/Tutorials/CMake|Introduction to cmake]] tutorial.
<code bash>
+
<syntaxhighlight lang="cmake">
 
SET(QT_DONT_USE_QTGUI "YES")
 
SET(QT_DONT_USE_QTGUI "YES")
 
INCLUDE(${QT_USE_FILE})
 
INCLUDE(${QT_USE_FILE})
Line 226: Line 224:
 
               "${CMAKE_CURRENT_BINARY_DIR}/${SERVICE_FILE}"
 
               "${CMAKE_CURRENT_BINARY_DIR}/${SERVICE_FILE}"
 
)
 
)
</code>
+
</syntaxhighlight>
  
 
The <tt>CONFIGURE_FILE</tt> line above tells cmake tp fill in the variable to the <tt>.service.cmake</tt> file we previously created.
 
The <tt>CONFIGURE_FILE</tt> line above tells cmake tp fill in the variable to the <tt>.service.cmake</tt> file we previously created.
  
<code bash>
+
<syntaxhighlight lang="cpp">
 
# ######### simpleclient demo #########
 
# ######### simpleclient demo #########
  
Line 260: Line 258:
 
         DESTINATION ${COMPONENT_SEARCH_DIR}
 
         DESTINATION ${COMPONENT_SEARCH_DIR}
 
)
 
)
</code>
+
</syntaxhighlight>
  
 
==Running our Application==
 
==Running our Application==

Latest revision as of 12:33, 30 June 2011

Handling TextChannels
Tutorial Series   Decibel Tutorial
Previous   C++, Qt, Getting Started with Decibel
What's Next  
Further Reading   CMake

Contents

[edit] Abstract

This tutorial will walk you through the process of creating a simple application that uses Decibel's TextChannels to communicate via arbitrary instant messaging networks using telepathy. From the result of this tutorial, only a few more lines of code are needed to produce a functioning text based instant messaging client.

This tutorial is based on the simpleclient demo included with Decibel. An explanation of using the simpleclient demo can be found here.

In this tutorial, we only pick out the important parts of the source code to discuss. The complete working source code for this example can be found here in KDE's SVN Repository.

[edit] ChannelHandler class

The first class we will need to create is an implementation of the Decibel::ChannelHandler interface.

[edit] The Class Definition

Here is the class definition for our implementation of the Decibel::ChannelHandler interface. It is explained below.

class MyTextChannelHandler : public Decibel::ChannelHandler
{
    Q_OBJECT
 
public:
    explicit MyTextChannelHandler(QObject * parent = 0);
    ~MyTextChannelHandler();
 
    bool handleChannel(QtTapioca::Connection *, QtTapioca::Channel *, const bool);
 
public slots:
    void onMessageReceived();
    void onCloseChannel();
 
private:
    QtTapioca::Connection *  m_connection;
    QtTapioca::TextChannel * m_channel;
};

We must inherit from Decibel::ChannelHandler which is the interface definition for handling incoming Channels. We reimplement the constructor, Destructor and the handleChannel() method as well as adding two slots of our own. We also create the member variables m_connecion and m_channel which will hold the QtTapioca::Connection and QtTapioca::Channel objects that are received by the handleChannel method.

[edit] The Class Implementation

Now that we have defined our Decibel::ChannelHandler subclass, we need to implement its methods.

[edit] Constructor

MyTextChannelHandler::MyTextChannelHandler(QObject * parent) :
    ChannelHandler(parent),
    m_connection(0),
    m_channel(0)
{ }

The constructor is very simple. We just pass the parent object on to the parent class and initialise our member variables to 0.

[edit] Destructor

MyTextChannelHandler::~MyTextChannelHandler()
{ }

The destructor is even simpler. We don't need to do anything in it because Qt will handle the deletion of child objects automatically.

[edit] handleChannel()

bool MyTextChannelHandler::handleChannel(QtTapioca::Connection * connection,
                                         QtTapioca::Channel * channel,
                                         const bool)
{
    Q_ASSERT(connection != 0);
    Q_ASSERT(channel != 0);
 
    if (m_connection != 0) { return false; }
 
    m_channel = dynamic_cast<QtTapioca::TextChannel*>(channel);
    if (m_channel == 0)
    {
        return false;
    }
    m_connection = connection;
 
    connect(m_channel, SIGNAL(messageReceived(const QtTapioca::TextChannel *, const QtTapioca::TextChannel::Message &)),
            this, SLOT(onMessageReceived()));
    connect(m_channel, SIGNAL(closed()), this, SLOT(onCloseChannel()));
 
    onMessageReceived();
 
    return true;
}

This method is reimplemented from the Decibel::ChannelHandler interface. It is called when ever a new incoming communication channel (QtTapioca::Channel) comes into existence. In this method, we must decide whether to accept the incoming channel, and if so, to deal with it appropriately.

First we check the incoming channel and connection objects are not invalid (=0) and we fail if this is the case. In a proper application, these error cases should be handled more gracefully, but for the purpose of this demo, we will just use Q_ASSERT() to handle them.

Next we check if the m_connection member variable is set to 0. If it is not, then we are already handling a channel. This demo can only handle one channel at a time, so we return false to reject the new incoming channel.

There are multiple types of channel we could possible receive. In this demo we are only interested in the TextChannel type. So the next step is to try and cast it to a QtTapioca::TextChannel object. We then evaluate if this cast was successful (not = 0) and if it was not, we again return false to reject the channel. If the cast is successful, we assign the QtTapioca::TextChannel object to the member variable m_channel to store it.

Now that we are sure the incoming channel is one we want to handle, we assign the incoming connection object to a member variable m_connection as well.

We are also ready to connect the channel's signals to the slots in our class. We connect the channel's messageReceived() signal to our onMessageReceived() slot, and the channel's closed() signal to our onCloseChannel() slot.

Finally, we call the onMessageReceived() slot to process any messages that have already arrived on the channel, before returning true to tell Decibel that we have accepted the channel and are handling it.

[edit] onCloseChannel()

void MyTextChannelHandler::onCloseChannel()
{
    m_connection = 0;
    m_channel = 0;
}

This slot is called when the current TextChannel is closed. It resets the m_connection and m_channel member variables to 0 so that the handleChannel() method will accept the next incoming channel.

[edit] onMessageReceived()

void MyTextChannelHandler::onMessageReceived()
{
    QList<QtTapioca::TextChannel::Message> message_list = m_channel->pendingMessages();
 
    for (QList<QtTapioca::TextChannel::Message>::const_iterator
             message = message_list.constBegin();
         message != message_list.constEnd(); ++message)
    {
        if (message->type() == QtTapioca::TextChannel::Message::Normal &&
            message->contents() == QString("ping?"))
        { m_channel->sendMessage(QString("pong!")); }
 
        m_channel->acknowledge(*message);
    }
}

This slot is called when a new message is received on the channel we are currently handling. It is where the processing of the message takes place.

The first thing we do is to call the pendingMessages() method of the channel to receive *all* the unprocessed received messages on that channel.

Next we iterate over the list of these messages. For each message in the list, we check that it is a message of type Normal, as oposed to another type like Avatar or Emoticon which we are not interested in for this tutorial.

If it is a normal type of message, we check its contents, and if they are the string ping?, we reply with the string pong!.

We then call the acknowledge() method on the channel to confirm to the sender that we received the message.

[edit] The main() function

int main(int argc, char ** argv)
{
    QCoreApplication app(argc, argv);
    app.setOrganizationName(Decibel::organisation_name);
    app.setApplicationName("SimpleClient");
 
    MyTextChannelHandler thandler(&app);
 
    Decibel::registerTypes();
 
    QDBusConnection::sessionBus().registerService(Decibel::organisation_name + '.' + "SimpleClient");
    QDBusConnection::sessionBus().registerObject("/TextChannelHandler", thandler);
 
    return app.exec();
}

Since this is a console application, we set up a QCoreApplication first.

Then we create an instance of the MyTextChannelHandler (which is our implementation of the Decibel::ChannelHandler interface).

Before registering any of the DBus interfaces for our app, it is necessary to call Decibe::registerTypes() to set up the types used by Decibel.

Next we register our application as a DBus service, and a TextChannelHandler so that decibel can communicate with it and can pass incoming TextChannels to it.

Finally, we start the application event-loop.

[edit] The .component File

Since we want Decibel to pass incoming channels to our application, we need to tell it that our program exists. We do this by creating a .component file. A component file is a simple .ini/.desktop style file.

The contents of our demo application's .component file are as follows.

[Component]
Name=Simpleclient
Protocols=
Types=0
Targets=
Service%20Name=org.kde.SimpleClient
Object%20Path=/TextChannelHandler

The only group in this file is the [Component] group. It contains a series of keys identifying our application to Decibel and detailing the funcionality it supports.

In this example, the important keys are the Service%20Name which tells Decibel the dbus name of our application, and the Object%20Path which tells Decibel the dbus object path that should be called on our application.

[edit] The .service File

In order to have dbus automatically start our application when Decibel tries to communicate with it, we must create a .service file.

[D-BUS Service]
Name=org.kde.SimpleClient
Exec=@INSTALL_DIR@/decibel_simpleclient_demo

This is a standard dbus autostart file. It contains the dbus name of our application and the absolute path to it. @INSTALL_DIR will be replaced automatically by the installation directory using cmake in the next step.

[edit] CMakeLists.txt

Since this example program is built as part of the main Decibel sources, I will not explain here how to create a standalone CMakeLists.txt file to build it, but will instead explain the important parts of the CMakeLists.txt file as found in the Decibel sources for this subdirectory. For more information on how CMakeLists.txt files work, please see the Introduction to cmake tutorial.

SET(QT_DONT_USE_QTGUI "YES")
INCLUDE(${QT_USE_FILE})
 
SET(SERVICE_FILE "org.kde.SimpleClient.service")
 
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include
                    ${CMAKE_BINARY_DIR}/src/client
                    ${CMAKE_SOURCE_DIR}/src/client
                    ${QT_QTCORE_INCLUDE_DIR}
                    ${TELEPATHY_QT_INCLUDE_DIR}
                    ${TAPIOCA_QT_INCLUDE_DIR}
                    ${CMAKE_CURRENT_BINARY_DIR}
                    ${CMAKE_CURRENT_SOURCE_DIR}
)
 
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/${SERVICE_FILE}.cmake"
               "${CMAKE_CURRENT_BINARY_DIR}/${SERVICE_FILE}"
)

The CONFIGURE_FILE line above tells cmake tp fill in the variable to the .service.cmake file we previously created.

# ######### simpleclient demo #########
 
SET(simpleclient_SRCS
    simpleclient.cpp
    mytextchannelhandler.cpp
)
 
SET(simpleclient_MOC_HDRS
    mytextchannelhandler.h
)
 
QT4_WRAP_CPP(simpleclient_MOC_SRCS ${simpleclient_MOC_HDRS})
 
ADD_EXECUTABLE(decibel_simpleclient_demo ${simpleclient_SRCS}
               ${simpleclient_MOC_SRCS}
)
TARGET_LINK_LIBRARIES(decibel_simpleclient_demo
                      ${QT_LIBRARIES}
                      ${TAPIOCA_QT_LIBRARIES}
                      decibel
)
 
INSTALL(TARGETS decibel_simpleclient_demo DESTINATION ${DEMO_INSTALL_DIR})
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SERVICE_FILE}
        DESTINATION ${DBUS_SERVICES_INSTALL_DIR}
)
INSTALL(FILES org.kde.SimpleClient.TextChannel.component
        DESTINATION ${COMPONENT_SEARCH_DIR}
)

[edit] Running our Application

In this tutorial, we have only looked at the important parts of the code of this application. The easiest way to build a working example from this tutorial is to check out the Decibel source code from the KDE SVN repository. The simpleclient demo which this tutorial explains will be built and installed automatically along with the rest of Decibel.

To see it working, you need to register an account with Decibel and bring it online. For information on how to do this, please see Getting Started with Decibel. Then you should use another instant messaging program to send the message ping? to that account. The simpleclient demo will reply with the message pong!.


This page was last modified on 30 June 2011, at 12:33. This page has been accessed 8,229 times. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal