Communi  3.7.0
A cross-platform IRC framework written with Qt
Loading...
Searching...
No Matches
ircclient.cpp File Reference

Client example More...

Detailed Description

/*
* Copyright (C) 2008-2020 The Communi Project
*
* This example is free, and not covered by the BSD license. There is no
* restriction applied to their modification, redistribution, using and so on.
* You can study them, modify them, use them in your own program - either
* completely or partially.
*/
#include "ircclient.h"
#include <QLineEdit>
#include <QListView>
#if (QT_VERSION) >= (QT_VERSION_CHECK(5, 10, 0))
#include <QRandomGenerator>
#endif
#include <QScrollBar>
#include <QShortcut>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextEdit>
#include <QTime>
#include <QVBoxLayout>
#include <Irc>
#include <IrcUser>
#include <IrcBuffer>
#include <IrcCommand>
#include <IrcMessage>
#include <IrcUserModel>
#include <IrcCompleter>
#include <IrcConnection>
#include <IrcBufferModel>
#include <IrcCommandParser>
static const char* CHANNEL = "#libera";
static const char* SERVER = "irc.libera.chat";
IrcClient::IrcClient(QWidget* parent) : QSplitter(parent)
{
createParser();
createConnection();
createCompleter();
createUserList();
createLayout();
createBufferList();
// queue a command to automatically join the channel when connected
connection->sendCommand(IrcCommand::createJoin(CHANNEL));
connection->open();
textEdit->append(IrcMessageFormatter::formatMessage(tr("! Welcome to the Communi %1 example client.").arg(IRC_VERSION_STR)));
textEdit->append(IrcMessageFormatter::formatMessage(tr("! This example connects %1 and joins %2.").arg(SERVER, CHANNEL)));
textEdit->append(IrcMessageFormatter::formatMessage(tr("! PS. Available commands: JOIN, ME, NICK, PART")));
}
IrcClient::~IrcClient()
{
if (connection->isActive()) {
connection->quit(connection->realName());
connection->close();
}
}
void IrcClient::onConnected()
{
textEdit->append(IrcMessageFormatter::formatMessage(QStringLiteral("! Connected to %1.")).arg(SERVER));
textEdit->append(IrcMessageFormatter::formatMessage(QStringLiteral("! Joining %1...")).arg(CHANNEL));
}
void IrcClient::onConnecting()
{
textEdit->append(IrcMessageFormatter::formatMessage(QStringLiteral("! Connecting %1...")).arg(SERVER));
}
void IrcClient::onDisconnected()
{
textEdit->append(IrcMessageFormatter::formatMessage(QStringLiteral("! Disconnected from %1.")).arg(SERVER));
}
void IrcClient::onTextEdited()
{
// clear the possible error indication
lineEdit->setStyleSheet(QString());
}
void IrcClient::onTextEntered()
{
QString input = lineEdit->text();
IrcCommand* command = parser->parse(input);
if (command) {
connection->sendCommand(command);
// echo own messages (servers do not send our own messages back)
if (command->type() == IrcCommand::Message || command->type() == IrcCommand::CtcpAction) {
IrcMessage* msg = command->toMessage(connection->nickName(), connection);
receiveMessage(msg);
delete msg;
}
lineEdit->clear();
} else if (input.length() > 1) {
QString error;
#if (QT_VERSION) >= (QT_VERSION_CHECK(5, 14, 0))
QString command = lineEdit->text().mid(1).split(QStringLiteral(" "), Qt::SkipEmptyParts).value(0).toUpper();
#else
QString command = lineEdit->text().mid(1).split(" ", QString::SkipEmptyParts).value(0).toUpper();
#endif
if (parser->commands().contains(command))
error = tr("[ERROR] Syntax: %1").arg(parser->syntax(command).replace(QLatin1String("<"), QLatin1String("&lt;")).replace(QLatin1String(">"), QLatin1String("&gt;")));
else
error = tr("[ERROR] Unknown command: %1").arg(command);
textEdit->append(IrcMessageFormatter::formatMessage(error));
lineEdit->setStyleSheet(QStringLiteral("background: salmon"));
}
}
void IrcClient::onCompletion()
{
completer->complete(lineEdit->text(), lineEdit->cursorPosition());
}
void IrcClient::onCompleted(const QString& text, int cursor)
{
lineEdit->setText(text);
lineEdit->setCursorPosition(cursor);
}
void IrcClient::onBufferAdded(IrcBuffer* buffer)
{
// joined a buffer - start listening to buffer specific messages
connect(buffer, SIGNAL(messageReceived(IrcMessage*)), this, SLOT(receiveMessage(IrcMessage*)));
// create a document for storing the buffer specific messages
QTextDocument* document = new QTextDocument(buffer);
documents.insert(buffer, document);
// create a sorted model for buffer users
IrcUserModel* userModel = new IrcUserModel(buffer);
userModel->setSortMethod(Irc::SortByTitle);
userModels.insert(buffer, userModel);
// activate the new buffer
int idx = bufferModel->buffers().indexOf(buffer);
if (idx != -1)
bufferList->setCurrentIndex(bufferModel->index(idx));
}
void IrcClient::onBufferRemoved(IrcBuffer* buffer)
{
// the buffer specific models and documents are no longer needed
delete userModels.take(buffer);
delete documents.take(buffer);
}
void IrcClient::onBufferActivated(const QModelIndex& index)
{
IrcBuffer* buffer = index.data(Irc::BufferRole).value<IrcBuffer*>();
// document, user list and nick completion for the current buffer
textEdit->setDocument(documents.value(buffer));
textEdit->verticalScrollBar()->triggerAction(QScrollBar::SliderToMaximum);
userList->setModel(userModels.value(buffer));
completer->setBuffer(buffer);
// keep the command parser aware of the context
if (buffer)
parser->setTarget(buffer->title());
}
void IrcClient::onUserActivated(const QModelIndex& index)
{
IrcUser* user = index.data(Irc::UserRole).value<IrcUser*>();
if (user) {
IrcBuffer* buffer = bufferModel->add(user->name());
// activate the new query
int idx = bufferModel->buffers().indexOf(buffer);
if (idx != -1)
bufferList->setCurrentIndex(bufferModel->index(idx));
}
}
static void appendHtml(QTextDocument* document, const QString& html)
{
QTextCursor cursor(document);
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::End);
if (!document->isEmpty())
cursor.insertBlock();
cursor.insertHtml(html);
cursor.endEditBlock();
}
void IrcClient::receiveMessage(IrcMessage* message)
{
IrcBuffer* buffer = qobject_cast<IrcBuffer*>(sender());
if (!buffer)
buffer = bufferList->currentIndex().data(Irc::BufferRole).value<IrcBuffer*>();
QTextDocument* document = documents.value(buffer);
if (document) {
QString html = IrcMessageFormatter::formatMessage(message);
if (!html.isEmpty()) {
if (document == textEdit->document())
textEdit->append(html);
else
appendHtml(document, html);
}
}
}
void IrcClient::createLayout()
{
setWindowTitle(tr("Communi %1 example client").arg(IRC_VERSION_STR));
// a read-only text editor for showing the messages
textEdit = new QTextEdit(this);
textEdit->setReadOnly(true);
// a line editor for entering commands
lineEdit = new QLineEdit(this);
lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
textEdit->setFocusProxy(lineEdit);
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(onTextEntered()));
connect(lineEdit, SIGNAL(textEdited(QString)), this, SLOT(onTextEdited()));
// the rest is just setting up the UI layout...
QSplitter* splitter = new QSplitter(this);
splitter->setHandleWidth(1);
splitter->addWidget(textEdit);
splitter->addWidget(userList);
splitter->setStretchFactor(0, 5);
splitter->setStretchFactor(1, 1);
QWidget* container = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(container);
layout->setSpacing(0);
layout->setMargin(0);
layout->addWidget(splitter);
layout->addWidget(lineEdit);
addWidget(container);
setHandleWidth(1);
}
void IrcClient::createCompleter()
{
// nick name completion
completer = new IrcCompleter(this);
completer->setParser(parser);
connect(completer, SIGNAL(completed(QString,int)), this, SLOT(onCompleted(QString,int)));
QShortcut* shortcut = new QShortcut(Qt::Key_Tab, this);
connect(shortcut, SIGNAL(activated()), this, SLOT(onCompletion()));
}
void IrcClient::createParser()
{
// create a command parser and teach it some commands. notice also
// that we must keep the command parser aware of the context in
// createUi() and onBufferActivated()
parser = new IrcCommandParser(this);
parser->setTolerant(true);
parser->setTriggers(QStringList("/"));
parser->addCommand(IrcCommand::Join, QStringLiteral("JOIN <#channel> (<key>)"));
parser->addCommand(IrcCommand::CtcpAction, QStringLiteral("ME [target] <message...>"));
parser->addCommand(IrcCommand::Mode, QStringLiteral("MODE (<channel/user>) (<mode>) (<arg>)"));
parser->addCommand(IrcCommand::Nick, QStringLiteral("NICK <nick>"));
parser->addCommand(IrcCommand::Part, QStringLiteral("PART (<#channel>) (<message...>)"));
}
void IrcClient::createUserList()
{
// a list of channel users
userList = new QListView(this);
userList->setFocusPolicy(Qt::NoFocus);
// open a private query when double clicking a user
connect(userList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onUserActivated(QModelIndex)));
}
void IrcClient::createBufferList()
{
bufferModel = new IrcBufferModel(connection);
connect(bufferModel, SIGNAL(added(IrcBuffer*)), this, SLOT(onBufferAdded(IrcBuffer*)));
connect(bufferModel, SIGNAL(removed(IrcBuffer*)), this, SLOT(onBufferRemoved(IrcBuffer*)));
bufferList = new QListView(this);
bufferList->setFocusPolicy(Qt::NoFocus);
bufferList->setModel(bufferModel);
// keep the command parser aware of the context
connect(bufferModel, SIGNAL(channelsChanged(QStringList)), parser, SLOT(setChannels(QStringList)));
// keep track of the current buffer, see also onBufferActivated()
connect(bufferList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(onBufferActivated(QModelIndex)));
// create a server buffer for non-targeted messages...
IrcBuffer* serverBuffer = bufferModel->add(connection->host());
// ...and connect it to IrcBufferModel::messageIgnored()
connect(bufferModel, SIGNAL(messageIgnored(IrcMessage*)), serverBuffer, SLOT(receiveMessage(IrcMessage*)));
insertWidget(0, bufferList);
setStretchFactor(0, 1);
setStretchFactor(1, 3);
}
void IrcClient::createConnection()
{
connection = new IrcConnection(this);
connect(connection, SIGNAL(connected()), this, SLOT(onConnected()));
connect(connection, SIGNAL(connecting()), this, SLOT(onConnecting()));
connect(connection, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connection->setHost(SERVER);
connection->setUserName(QStringLiteral("communi"));
#if (QT_VERSION) >= (QT_VERSION_CHECK(5, 10, 0))
connection->setNickName(tr("Client%1").arg(QRandomGenerator::global()->bounded(1, 10000)));
#else
qsrand(QTime::currentTime().msec());
connection->setNickName(tr("Client%1").arg(qrand() % 9999));
#endif
connection->setRealName(tr("Communi %1 example client").arg(IRC_VERSION_STR));
}
Keeps track of buffers.
Definition ircbuffermodel.h:48
QModelIndex index(IrcBuffer *buffer) const
Definition ircbuffermodel.cpp:923
Q_INVOKABLE IrcBuffer * add(const QString &title)
Definition ircbuffermodel.cpp:830
Keeps track of buffer status.
Definition ircbuffer.h:50
Parses commands from user input.
Definition irccommandparser.h:43
Q_INVOKABLE void addCommand(IrcCommand::Type type, const QString &syntax)
Definition irccommandparser.cpp:398
Q_INVOKABLE QString syntax(const QString &command, Details details=Visual) const
Definition irccommandparser.cpp:370
Q_INVOKABLE IrcCommand * parse(const QString &input) const
Definition irccommandparser.cpp:550
Provides the most common commands.
Definition irccommand.h:45
static Q_INVOKABLE IrcCommand * createJoin(const QString &channel, const QString &key=QString())
Definition irccommand.cpp:572
@ Join
A join command (JOIN) is used to start listening a specific channel.
Definition irccommand.h:65
@ CtcpAction
A CTCP action command is used to send an action message to channels and users.
Definition irccommand.h:59
@ Nick
A nick command (NICK) is used to give user a nickname or change the previous one.
Definition irccommand.h:73
@ Part
A part command (PART) causes the client to be removed from the channel.
Definition irccommand.h:75
@ Message
A message command (PRIVMSG) is used to send private messages to channels and users.
Definition irccommand.h:69
@ Mode
A mode command (MODE) is used to change the mode of users and channels.
Definition irccommand.h:70
Q_INVOKABLE IrcMessage * toMessage(const QString &prefix, IrcConnection *connection) const
Definition irccommand.cpp:468
Provides command and name completion.
Definition irccompleter.h:44
void complete(const QString &text, int cursor, Direction direction=Forward)
Definition irccompleter.cpp:378
Provides means to establish a connection to an IRC server.
Definition ircconnection.h:49
bool sendCommand(IrcCommand *command)
Definition ircconnection.cpp:1442
void close()
Definition ircconnection.cpp:1389
void quit(const QString &reason=QString())
Definition ircconnection.cpp:1415
The base class of all messages.
Definition ircmessage.h:48
Keeps track of channel users.
Definition ircusermodel.h:46
Keeps track of user status on a channel.
Definition ircuser.h:43
@ SortByTitle
Sort by title (Irc::TitleRole)
Definition irc.h:91
@ UserRole
User object (IrcUser*)
Definition irc.h:79
@ BufferRole
Buffer object (IrcBuffer*)
Definition irc.h:80
Client example
#define IRC_VERSION_STR
Definition ircglobal.h:95
Client example