From 529f38bd8878b6b1bea2b5457031ce936aab8d80 Mon Sep 17 00:00:00 2001 From: Markus Mittendrein Date: Mon, 6 Oct 2014 15:03:54 +0200 Subject: addedd communi --- libcommuni/src/model/ircbuffer.cpp | 593 ++++++++++++++++ libcommuni/src/model/ircbuffermodel.cpp | 1168 +++++++++++++++++++++++++++++++ libcommuni/src/model/ircchannel.cpp | 623 +++++++++++++++++ libcommuni/src/model/ircmodel.cpp | 61 ++ libcommuni/src/model/ircuser.cpp | 266 +++++++ libcommuni/src/model/ircusermodel.cpp | 709 +++++++++++++++++++ libcommuni/src/model/model.pri | 40 ++ libcommuni/src/model/model.pro | 11 + 8 files changed, 3471 insertions(+) create mode 100644 libcommuni/src/model/ircbuffer.cpp create mode 100644 libcommuni/src/model/ircbuffermodel.cpp create mode 100644 libcommuni/src/model/ircchannel.cpp create mode 100644 libcommuni/src/model/ircmodel.cpp create mode 100644 libcommuni/src/model/ircuser.cpp create mode 100644 libcommuni/src/model/ircusermodel.cpp create mode 100644 libcommuni/src/model/model.pri create mode 100644 libcommuni/src/model/model.pro (limited to 'libcommuni/src/model') diff --git a/libcommuni/src/model/ircbuffer.cpp b/libcommuni/src/model/ircbuffer.cpp new file mode 100644 index 0000000..e7aa6b9 --- /dev/null +++ b/libcommuni/src/model/ircbuffer.cpp @@ -0,0 +1,593 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircbuffer.h" +#include "ircbuffer_p.h" +#include "ircbuffermodel.h" +#include "ircbuffermodel_p.h" +#include "ircconnection.h" +#include "ircchannel.h" + +IRC_BEGIN_NAMESPACE + +/*! + \file ircbuffer.h + \brief \#include <IrcBuffer> + */ + +/*! + \class IrcBuffer ircbuffer.h + \ingroup models + \brief Keeps track of buffer status. + + \sa IrcBufferModel +*/ + +/*! + \fn void IrcBuffer::messageReceived(IrcMessage* message) + + This signal is emitted when a buffer specific message is received. + + The message may one of the following types: + - IrcMessage::Join + - IrcMessage::Kick + - IrcMessage::Mode + - IrcMessage::Names + - IrcMessage::Nick + - IrcMessage::Notice + - IrcMessage::Numeric + - IrcMessage::Part + - IrcMessage::Private + - IrcMessage::Quit + - IrcMessage::Topic + + \sa IrcConnection::messageReceived(), IrcBufferModel::messageIgnored() + */ + +#ifndef IRC_DOXYGEN +IrcBufferPrivate::IrcBufferPrivate() + : q_ptr(0), model(0), persistent(false), sticky(false) +{ + qRegisterMetaType(); + qRegisterMetaType >(); +} + +IrcBufferPrivate::~IrcBufferPrivate() +{ +} + +void IrcBufferPrivate::init(const QString& title, IrcBufferModel* m) +{ + name = title; + setModel(m); +} + +void IrcBufferPrivate::connected() +{ + Q_Q(IrcBuffer); + emit q->activeChanged(q->isActive()); +} + +void IrcBufferPrivate::disconnected() +{ + Q_Q(IrcBuffer); + emit q->activeChanged(q->isActive()); +} + +void IrcBufferPrivate::setName(const QString& value) +{ + Q_Q(IrcBuffer); + if (name != value) { + const QString oldTitle = q->title(); + name = value; + emit q->nameChanged(name); + emit q->titleChanged(q->title()); + if (model) + IrcBufferModelPrivate::get(model)->renameBuffer(oldTitle, q->title()); + } +} + +void IrcBufferPrivate::setPrefix(const QString& value) +{ + Q_Q(IrcBuffer); + if (prefix != value) { + const QString oldTitle = q->title(); + prefix = value; + emit q->prefixChanged(prefix); + emit q->titleChanged(q->title()); + if (model) + IrcBufferModelPrivate::get(model)->renameBuffer(oldTitle, q->title()); + } +} + +void IrcBufferPrivate::setModel(IrcBufferModel* value) +{ + model = value; +} + +bool IrcBufferPrivate::processMessage(IrcMessage* message) +{ + Q_Q(IrcBuffer); + bool processed = false; + switch (message->type()) { + case IrcMessage::Join: + processed = processJoinMessage(static_cast(message)); + break; + case IrcMessage::Kick: + processed = processKickMessage(static_cast(message)); + break; + case IrcMessage::Mode: + processed = processModeMessage(static_cast(message)); + break; + case IrcMessage::Names: + processed = processNamesMessage(static_cast(message)); + break; + case IrcMessage::Nick: + processed = processNickMessage(static_cast(message)); + break; + case IrcMessage::Notice: + processed = processNoticeMessage(static_cast(message)); + break; + case IrcMessage::Numeric: + processed = processNumericMessage(static_cast(message)); + break; + case IrcMessage::Part: + processed = processPartMessage(static_cast(message)); + break; + case IrcMessage::Private: + processed = processPrivateMessage(static_cast(message)); + break; + case IrcMessage::Quit: + processed = processQuitMessage(static_cast(message)); + break; + case IrcMessage::Topic: + processed = processTopicMessage(static_cast(message)); + break; + case IrcMessage::WhoReply: + processed = processWhoReplyMessage(static_cast(message)); + break; + default: + break; + } + if (processed) + emit q->messageReceived(message); + return processed; +} + +bool IrcBufferPrivate::processJoinMessage(IrcJoinMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processKickMessage(IrcKickMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processModeMessage(IrcModeMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processNamesMessage(IrcNamesMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processNickMessage(IrcNickMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback) && !message->nick().compare(name, Qt::CaseInsensitive)) { + setName(message->newNick()); + return true; + } + return !message->newNick().compare(name, Qt::CaseInsensitive); +} + +bool IrcBufferPrivate::processNoticeMessage(IrcNoticeMessage* message) +{ + Q_UNUSED(message); + return true; +} + +bool IrcBufferPrivate::processNumericMessage(IrcNumericMessage* message) +{ + Q_UNUSED(message); + return true; +} + +bool IrcBufferPrivate::processPartMessage(IrcPartMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processPrivateMessage(IrcPrivateMessage* message) +{ + Q_UNUSED(message); + return true; +} + +bool IrcBufferPrivate::processQuitMessage(IrcQuitMessage* message) +{ + return !message->nick().compare(name, Qt::CaseInsensitive); +} + +bool IrcBufferPrivate::processTopicMessage(IrcTopicMessage* message) +{ + Q_UNUSED(message); + return false; +} + +bool IrcBufferPrivate::processWhoReplyMessage(IrcWhoReplyMessage *message) +{ + Q_UNUSED(message); + return true; +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new buffer object with \a parent. + */ +IrcBuffer::IrcBuffer(QObject* parent) + : QObject(parent), d_ptr(new IrcBufferPrivate) +{ + Q_D(IrcBuffer); + d->q_ptr = this; +} + +/*! + \internal + */ +IrcBuffer::IrcBuffer(IrcBufferPrivate& dd, QObject* parent) + : QObject(parent), d_ptr(&dd) +{ + Q_D(IrcBuffer); + d->q_ptr = this; +} + +/*! + Destructs the buffer object. + */ +IrcBuffer::~IrcBuffer() +{ + emit destroyed(this); +} + +/*! + This property holds the whole buffer title. + + The title consists of \ref prefix and \ref name. + + \par Access function: + \li QString title() const + + \par Notifier signal: + \li void titleChanged(const QString& title) + */ +QString IrcBuffer::title() const +{ + Q_D(const IrcBuffer); + return d->prefix + d->name; +} + +/*! + This property holds the name part of the buffer \ref title. + + \par Access functions: + \li QString name() const + \li void setName(const QString& name) [slot] + + \par Notifier signal: + \li void nameChanged(const QString& name) + */ +QString IrcBuffer::name() const +{ + Q_D(const IrcBuffer); + return d->name; +} + +void IrcBuffer::setName(const QString& name) +{ + Q_D(IrcBuffer); + d->setName(name); +} + +/*! + This property holds the prefix part of the buffer \ref title. + + \par Access functions: + \li QString prefix() const + \li void setPrefix(const QString& prefix) [slot] + + \par Notifier signal: + \li void prefixChanged(const QString& prefix) + */ +QString IrcBuffer::prefix() const +{ + Q_D(const IrcBuffer); + return d->prefix; +} + +void IrcBuffer::setPrefix(const QString& prefix) +{ + Q_D(IrcBuffer); + return d->setPrefix(prefix); +} + +/*! + \property bool IrcBuffer::channel + This property holds whether the buffer is a channel. + + \par Access function: + \li bool isChannel() const + + \sa toChannel() + */ +bool IrcBuffer::isChannel() const +{ + return qobject_cast(this); +} + +/*! + Returns the buffer cast to a IrcChannel, + if the class is actually a channel, \c 0 otherwise. + + \sa \ref channel "isChannel()" +*/ +IrcChannel* IrcBuffer::toChannel() +{ + return qobject_cast(this); +} + +/*! + This property holds the connection of the buffer. + + \par Access function: + \li \ref IrcConnection* connection() const + */ +IrcConnection* IrcBuffer::connection() const +{ + Q_D(const IrcBuffer); + return d->model ? d->model->connection() : 0; +} + +/*! + This property holds the network of the buffer. + + \par Access function: + \li \ref IrcNetwork* network() const + */ +IrcNetwork* IrcBuffer::network() const +{ + Q_D(const IrcBuffer); + return d->model ? d->model->network() : 0; +} + +/*! + This property holds the model of the buffer. + + \par Access function: + \li \ref IrcBufferModel* model() const + */ +IrcBufferModel* IrcBuffer::model() const +{ + Q_D(const IrcBuffer); + return d->model; +} + +/*! + \property bool IrcBuffer::active + This property holds whether the buffer is active. + + A buffer is considered active when a %connection is established. Furthermore, + channel buffers are only considered active when the user is on the channel. + + \par Access function: + \li bool isActive() const + + \par Notifier signal: + \li void activeChanged(bool active) + + \sa IrcConnection::connected + */ +bool IrcBuffer::isActive() const +{ + if (IrcConnection* c = connection()) + return c->isConnected(); + return false; +} + +/*! + \property bool IrcBuffer::sticky + This property holds whether the buffer is sticky. + + A sticky buffer stays in the beginning (Qt::AscendingOrder) or + end (Qt::DescendingOrder) of the list of buffers in IrcBufferModel. + + The default value is \c false. + + \par Access functions: + \li bool isSticky() const + \li void setSticky(bool sticky) + + \par Notifier signal: + \li void stickyChanged(bool sticky) + */ + +bool IrcBuffer::isSticky() const +{ + Q_D(const IrcBuffer); + return d->sticky; +} + +void IrcBuffer::setSticky(bool sticky) +{ + Q_D(IrcBuffer); + if (d->sticky != sticky) { + d->sticky = sticky; + emit stickyChanged(sticky); + } +} + +/*! + \property bool IrcBuffer::persistent + This property holds whether the buffer is persistent. + + The default value is \c false. + + A persistent buffer does not get removed and destructed + when calling IrcBufferModel::clear(), or when when leaving + the corresponding channel. In order to remove a persistent + buffer, either explicitly call IrcBufferModel::remove() or + delete the buffer. + + \par Access functions: + \li bool isPersistent() const + \li void setPersistent(bool persistent) + + \par Notifier signal: + \li void persistentChanged(bool persistent) + */ + +bool IrcBuffer::isPersistent() const +{ + Q_D(const IrcBuffer); + return d->persistent; +} + +void IrcBuffer::setPersistent(bool persistent) +{ + Q_D(IrcBuffer); + if (d->persistent != persistent) { + d->persistent = persistent; + emit persistentChanged(persistent); + } +} + +/*! + \since 3.1 + + This property holds arbitrary user data. + + \par Access functions: + \li QVariantMap userData() const + \li void setUserData(const QVariantMap& data) + + \par Notifier signal: + \li void userDataChanged(const QVariantMap& data) + */ +QVariantMap IrcBuffer::userData() const +{ + Q_D(const IrcBuffer); + return d->userData; +} + +void IrcBuffer::setUserData(const QVariantMap& data) +{ + Q_D(IrcBuffer); + if (d->userData != data) { + d->userData = data; + emit userDataChanged(data); + } +} + +/*! + Sends a \a command to the server. + + This method is provided for convenience. It is equal to: + \code + IrcConnection* connection = buffer->connection(); + connection->sendCommand(command); + \endcode + + \sa IrcConnection::sendCommand() + */ +bool IrcBuffer::sendCommand(IrcCommand* command) +{ + if (IrcConnection* c = connection()) + return c->sendCommand(command); + return false; +} + +/*! + Emits messageReceived() with \a message. + + IrcBufferModel handles only buffer specific messages and delivers them + to the appropriate IrcBuffer instances. When applications decide to handle + IrcBuffer::messageReceived(), IrcBufferModel::messageIgnored() makes it + easy to implement handling for the rest, non-buffer specific messages. + This method can be used to forward such ignored messages to the desired + buffers (for instance the one that is currently active in the GUI). + */ +void IrcBuffer::receiveMessage(IrcMessage* message) +{ + if (message) + emit messageReceived(message); +} + +/*! + \since 3.1 + + Closes the buffer with an optional \a reason. + + The default implementation removes the buffer from its \ref model. + Furthermore, IrcChannel parts the channel with \a reason and custom + IrcBuffer subclasses might do some additional tasks. + + \sa IrcChannel::close() + */ +void IrcBuffer::close(const QString& reason) +{ + Q_UNUSED(reason); + Q_D(const IrcBuffer); + if (d->model) + d->model->remove(this); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const IrcBuffer* buffer) +{ + if (!buffer) + return debug << "IrcBuffer(0x0) "; + debug.nospace() << buffer->metaObject()->className() << '(' << (void*) buffer; + if (!buffer->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(buffer->objectName()); + if (!buffer->title().isEmpty()) + debug.nospace() << ", title=" << qPrintable(buffer->title()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircbuffer.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/ircbuffermodel.cpp b/libcommuni/src/model/ircbuffermodel.cpp new file mode 100644 index 0000000..c037a53 --- /dev/null +++ b/libcommuni/src/model/ircbuffermodel.cpp @@ -0,0 +1,1168 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircbuffermodel.h" +#include "ircbuffermodel_p.h" +#include "ircchannel_p.h" +#include "ircbuffer_p.h" +#include "ircnetwork.h" +#include "ircchannel.h" +#include "ircmessage.h" +#include "irccommand.h" +#include "ircconnection.h" +#include +#include +#include +#include + +IRC_BEGIN_NAMESPACE + +/*! + \file ircbuffermodel.h + \brief \#include <IrcBufferModel> + */ + +/*! + \class IrcBufferModel ircbuffermodel.h + \ingroup models + \brief Keeps track of buffers. + + IrcBufferModel automatically keeps track of channel and query buffers + and manages IrcBuffer instances for them. It will notify via signals + when channel and query buffers are added and/or removed. IrcBufferModel + can be used directly as a data model for Qt's item views - both in C++ + and QML. + + \code + IrcConnection* connection = new IrcConnection(this); + IrcBufferModel* model = new IrcBufferModel(connection); + connect(model, SIGNAL(added(IrcBuffer*)), this, SLOT(onBufferAdded(IrcBuffer*))); + connect(model, SIGNAL(removed(IrcBuffer*)), this, SLOT(onBufferRemoved(IrcBuffer*))); + listView->setModel(model); + \endcode + */ + +/*! + \fn void IrcBufferModel::added(IrcBuffer* buffer) + + This signal is emitted when a \a buffer is added to the list of buffers. + */ + +/*! + \fn void IrcBufferModel::removed(IrcBuffer* buffer) + + This signal is emitted when a \a buffer is removed from the list of buffers. + */ + +/*! + \fn void IrcBufferModel::aboutToBeAdded(IrcBuffer* buffer) + + This signal is emitted just before a \a buffer is added to the list of buffers. + */ + +/*! + \fn void IrcBufferModel::aboutToBeRemoved(IrcBuffer* buffer) + + This signal is emitted just before a \a buffer is removed from the list of buffers. + */ + +/*! + \fn void IrcBufferModel::messageIgnored(IrcMessage* message) + + This signal is emitted when a message was ignored. + + IrcBufferModel handles only buffer specific messages and delivers + them to the appropriate IrcBuffer instances. When applications decide + to handle IrcBuffer::messageReceived(), this signal makes it easy to + implement handling for the rest, non-buffer specific messages. + + \sa IrcConnection::messageReceived(), IrcBuffer::messageReceived() + */ + +#ifndef IRC_DOXYGEN +class IrcBufferLessThan +{ +public: + IrcBufferLessThan(IrcBufferModel* model, Irc::SortMethod method) : model(model), method(method) { } + bool operator()(IrcBuffer* b1, IrcBuffer* b2) const { return model->lessThan(b1, b2, method); } +private: + IrcBufferModel* model; + Irc::SortMethod method; +}; + +class IrcBufferGreaterThan +{ +public: + IrcBufferGreaterThan(IrcBufferModel* model, Irc::SortMethod method) : model(model), method(method) { } + bool operator()(IrcBuffer* b1, IrcBuffer* b2) const { return model->lessThan(b2, b1, method); } +private: + IrcBufferModel* model; + Irc::SortMethod method; +}; + +IrcBufferModelPrivate::IrcBufferModelPrivate() : q_ptr(0), role(Irc::TitleRole), + sortMethod(Irc::SortByHand), sortOrder(Qt::AscendingOrder), + bufferProto(0), channelProto(0), persistent(false) +{ +} + +bool IrcBufferModelPrivate::messageFilter(IrcMessage* msg) +{ + Q_Q(IrcBufferModel); + if (msg->type() == IrcMessage::Join && msg->flags() & IrcMessage::Own) + createBuffer(static_cast(msg)->channel()); + + bool processed = false; + switch (msg->type()) { + case IrcMessage::Nick: + case IrcMessage::Quit: + foreach (IrcBuffer* buffer, bufferList) { + if (buffer->isActive()) + IrcBufferPrivate::get(buffer)->processMessage(msg); + } + processed = true; + break; + + case IrcMessage::Join: + case IrcMessage::Part: + case IrcMessage::Kick: + case IrcMessage::Names: + case IrcMessage::Topic: + processed = processMessage(msg->property("channel").toString(), msg); + break; + + case IrcMessage::WhoReply: + processed = processMessage(static_cast(msg)->mask(), msg); + break; + + case IrcMessage::Private: + if (IrcPrivateMessage* pm = static_cast(msg)) + processed = !pm->isRequest() && (processMessage(pm->target(), pm, pm->flags() & IrcMessage::Own) || processMessage(pm->nick(), pm, true)); + break; + + case IrcMessage::Notice: + if (IrcNoticeMessage* no = static_cast(msg)) + processed = !no->isReply() && (processMessage(no->target(), no, no->flags() & IrcMessage::Own) || processMessage(no->nick(), no)); + break; + + case IrcMessage::Mode: + processed = processMessage(static_cast(msg)->target(), msg); + break; + + case IrcMessage::Numeric: + // TODO: any other special cases besides RPL_NAMREPLY? + if (static_cast(msg)->code() == Irc::RPL_NAMREPLY) { + const int count = msg->parameters().count(); + const QString channel = msg->parameters().value(count - 2); + processed = processMessage(channel, msg); + } else { + processed = processMessage(msg->parameters().value(1), msg); + } + break; + + default: + break; + } + + if (!processed) + emit q->messageIgnored(msg); + + if (!(msg->flags() & IrcMessage::Playback)) { + if (msg->type() == IrcMessage::Part && msg->flags() & IrcMessage::Own) { + destroyBuffer(static_cast(msg)->channel()); + } else if (msg->type() == IrcMessage::Kick) { + const IrcKickMessage* kickMsg = static_cast(msg); + if (!kickMsg->user().compare(msg->connection()->nickName(), Qt::CaseInsensitive)) + destroyBuffer(kickMsg->channel()); + } + } + return false; +} + +bool IrcBufferModelPrivate::commandFilter(IrcCommand* cmd) +{ + if (cmd->type() == IrcCommand::Join) { + const QString channel = cmd->parameters().value(0).toLower(); + const QString key = cmd->parameters().value(1); + if (!key.isEmpty()) + keys.insert(channel, key); + else + keys.remove(channel); + } + return false; +} + +IrcBuffer* IrcBufferModelPrivate::createBufferHelper(const QString& title) +{ + Q_Q(IrcBufferModel); + IrcBuffer* buffer = 0; + const QMetaObject* metaObject = q->metaObject(); + int idx = metaObject->indexOfMethod("createBuffer(QVariant)"); + if (idx != -1) { + // QML: QVariant createBuffer(QVariant) + QVariant ret; + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, title)); + buffer = ret.value(); + } else { + // C++: IrcBuffer* createBuffer(QString) + idx = metaObject->indexOfMethod("createBuffer(QString)"); + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(IrcBuffer*, buffer), Q_ARG(QString, title)); + } + return buffer; +} + +IrcChannel* IrcBufferModelPrivate::createChannelHelper(const QString& title) +{ + Q_Q(IrcBufferModel); + IrcChannel* channel = 0; + const QMetaObject* metaObject = q->metaObject(); + int idx = metaObject->indexOfMethod("createChannel(QVariant)"); + if (idx != -1) { + // QML: QVariant createChannel(QVariant) + QVariant ret; + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, title)); + channel = ret.value(); + } else { + // C++: IrcChannel* createChannel(QString) + idx = metaObject->indexOfMethod("createChannel(QString)"); + QMetaMethod method = metaObject->method(idx); + method.invoke(q, Q_RETURN_ARG(IrcChannel*, channel), Q_ARG(QString, title)); + } + return channel; +} + +IrcBuffer* IrcBufferModelPrivate::createBuffer(const QString& title) +{ + Q_Q(IrcBufferModel); + IrcBuffer* buffer = bufferMap.value(title.toLower()); + if (!buffer) { + if (connection && connection->network()->isChannel(title)) + buffer = createChannelHelper(title); + else + buffer = createBufferHelper(title); + if (buffer) { + IrcBufferPrivate::get(buffer)->init(title, q); + addBuffer(buffer); + } + } + return buffer; +} + +void IrcBufferModelPrivate::destroyBuffer(const QString& title, bool force) +{ + IrcBuffer* buffer = bufferMap.value(title.toLower()); + if (buffer && (force || (!persistent && !buffer->isPersistent()))) { + removeBuffer(buffer); + buffer->deleteLater(); + } +} + +void IrcBufferModelPrivate::addBuffer(IrcBuffer* buffer, bool notify) +{ + insertBuffer(-1, buffer, notify); +} + +void IrcBufferModelPrivate::insertBuffer(int index, IrcBuffer* buffer, bool notify) +{ + Q_Q(IrcBufferModel); + if (buffer && !bufferList.contains(buffer)) { + const QString title = buffer->title(); + const QString lower = title.toLower(); + if (bufferMap.contains(lower)) { + qWarning() << "IrcBufferModel: ignored duplicate buffer" << title; + return; + } + IrcBufferPrivate::get(buffer)->setModel(q); + const bool isChannel = buffer->isChannel(); + if (sortMethod != Irc::SortByHand) { + QList::iterator it; + if (sortOrder == Qt::AscendingOrder) + it = qUpperBound(bufferList.begin(), bufferList.end(), buffer, IrcBufferLessThan(q, sortMethod)); + else + it = qUpperBound(bufferList.begin(), bufferList.end(), buffer, IrcBufferGreaterThan(q, sortMethod)); + index = it - bufferList.begin(); + } else if (index == -1) { + index = bufferList.count(); + } + if (notify) + emit q->aboutToBeAdded(buffer); + q->beginInsertRows(QModelIndex(), index, index); + bufferList.insert(index, buffer); + bufferMap.insert(lower, buffer); + if (isChannel) { + channels += title; + if (keys.contains(lower)) + IrcChannelPrivate::get(buffer->toChannel())->setKey(keys.take(lower)); + } + q->connect(buffer, SIGNAL(destroyed(IrcBuffer*)), SLOT(_irc_bufferDestroyed(IrcBuffer*))); + q->endInsertRows(); + if (notify) { + emit q->added(buffer); + if (isChannel) + emit q->channelsChanged(channels); + emit q->buffersChanged(bufferList); + emit q->countChanged(bufferList.count()); + if (bufferList.count() == 1) + emit q->emptyChanged(false); + } + } +} + +void IrcBufferModelPrivate::removeBuffer(IrcBuffer* buffer, bool notify) +{ + Q_Q(IrcBufferModel); + int idx = bufferList.indexOf(buffer); + if (idx != -1) { + const bool isChannel = buffer->isChannel(); + if (notify) + emit q->aboutToBeRemoved(buffer); + q->beginRemoveRows(QModelIndex(), idx, idx); + bufferList.removeAt(idx); + bufferMap.remove(buffer->title().toLower()); + if (isChannel) + channels.removeOne(buffer->title()); + q->endRemoveRows(); + if (notify) { + emit q->removed(buffer); + if (isChannel) + emit q->channelsChanged(channels); + emit q->buffersChanged(bufferList); + emit q->countChanged(bufferList.count()); + if (bufferList.isEmpty()) + emit q->emptyChanged(true); + } + } +} + +bool IrcBufferModelPrivate::renameBuffer(const QString& from, const QString& to) +{ + Q_Q(IrcBufferModel); + const QString fromLower = from.toLower(); + const QString toLower = to.toLower(); + if (bufferMap.contains(toLower)) + destroyBuffer(toLower, true); + if (bufferMap.contains(fromLower)) { + IrcBuffer* buffer = bufferMap.take(fromLower); + bufferMap.insert(toLower, buffer); + + const int idx = bufferList.indexOf(buffer); + QModelIndex index = q->index(idx); + emit q->dataChanged(index, index); + + if (sortMethod != Irc::SortByHand) { + QList buffers = bufferList; + const bool notify = false; + removeBuffer(buffer, notify); + insertBuffer(-1, buffer, notify); + if (buffers != bufferList) + emit q->buffersChanged(bufferList); + } + return true; + } + return false; +} + +bool IrcBufferModelPrivate::processMessage(const QString& title, IrcMessage* message, bool create) +{ + IrcBuffer* buffer = bufferMap.value(title.toLower()); + if (!buffer && create && !title.contains(QLatin1Char('*'))) + buffer = createBuffer(title); + if (buffer) + return IrcBufferPrivate::get(buffer)->processMessage(message); + return false; +} + +void IrcBufferModelPrivate::_irc_connected() +{ + foreach (IrcBuffer* buffer, bufferList) + IrcBufferPrivate::get(buffer)->connected(); +} + +void IrcBufferModelPrivate::_irc_disconnected() +{ + foreach (IrcBuffer* buffer, bufferList) + IrcBufferPrivate::get(buffer)->disconnected(); +} + +void IrcBufferModelPrivate::_irc_bufferDestroyed(IrcBuffer* buffer) +{ + removeBuffer(buffer); +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new model with \a parent. + + \note If \a parent is an instance of IrcConnection, it will be + automatically assigned to \ref IrcBufferModel::connection "connection". + */ +IrcBufferModel::IrcBufferModel(QObject* parent) + : QAbstractListModel(parent), d_ptr(new IrcBufferModelPrivate) +{ + Q_D(IrcBufferModel); + d->q_ptr = this; + setBufferPrototype(new IrcBuffer(this)); + setChannelPrototype(new IrcChannel(this)); + setConnection(qobject_cast(parent)); +} + +/*! + Destructs the model. + */ +IrcBufferModel::~IrcBufferModel() +{ + Q_D(IrcBufferModel); + foreach (IrcBuffer* buffer, d->bufferList) { + buffer->disconnect(this); + delete buffer; + } + d->bufferList.clear(); + d->bufferMap.clear(); + d->channels.clear(); + emit destroyed(this); +} + +/*! + This property holds the connection. + + \par Access functions: + \li \ref IrcConnection* connection() const + \li void setConnection(\ref IrcConnection* connection) + + \warning Changing the connection on the fly is not supported. + */ +IrcConnection* IrcBufferModel::connection() const +{ + Q_D(const IrcBufferModel); + return d->connection; +} + +void IrcBufferModel::setConnection(IrcConnection* connection) +{ + Q_D(IrcBufferModel); + if (d->connection != connection) { + if (d->connection) { + qCritical("IrcBufferModel::setConnection(): changing the connection on the fly is not supported."); + return; + } + d->connection = connection; + d->connection->installMessageFilter(d); + d->connection->installCommandFilter(d); + connect(d->connection, SIGNAL(connected()), this, SLOT(_irc_connected())); + connect(d->connection, SIGNAL(disconnected()), this, SLOT(_irc_disconnected())); + emit connectionChanged(connection); + emit networkChanged(network()); + } +} + +/*! + This property holds the network. + + \par Access functions: + \li \ref IrcNetwork* network() const + */ +IrcNetwork* IrcBufferModel::network() const +{ + Q_D(const IrcBufferModel); + return d->connection ? d->connection->network() : 0; +} + +/*! + This property holds the number of buffers. + + \par Access function: + \li int count() const + + \par Notifier signal: + \li void countChanged(int count) + */ +int IrcBufferModel::count() const +{ + return rowCount(); +} + +/*! + \since 3.1 + \property bool IrcBufferModel::empty + + This property holds the whether the model is empty. + + \par Access function: + \li bool isEmpty() const + + \par Notifier signal: + \li void emptyChanged(bool empty) + */ +bool IrcBufferModel::isEmpty() const +{ + Q_D(const IrcBufferModel); + return d->bufferList.isEmpty(); +} + +/*! + This property holds the list of channel names. + + \par Access function: + \li QStringList channels() const + + \par Notifier signal: + \li void channelsChanged(const QStringList& channels) + */ +QStringList IrcBufferModel::channels() const +{ + Q_D(const IrcBufferModel); + return d->channels; +} + +/*! + This property holds the list of buffers. + + \par Access function: + \li QList<\ref IrcBuffer*> buffers() const + + \par Notifier signal: + \li void buffersChanged(const QList<\ref IrcBuffer*>& buffers) + */ +QList IrcBufferModel::buffers() const +{ + Q_D(const IrcBufferModel); + return d->bufferList; +} + +/*! + Returns the buffer object at \a index. + */ +IrcBuffer* IrcBufferModel::get(int index) const +{ + Q_D(const IrcBufferModel); + return d->bufferList.value(index); +} + +/*! + Returns the buffer object for \a title or \c 0 if not found. + */ +IrcBuffer* IrcBufferModel::find(const QString& title) const +{ + Q_D(const IrcBufferModel); + return d->bufferMap.value(title.toLower()); +} + +/*! + Returns \c true if the model contains \a title. + */ +bool IrcBufferModel::contains(const QString& title) const +{ + Q_D(const IrcBufferModel); + return d->bufferMap.contains(title.toLower()); +} + +/*! + Returns the index of the specified \a buffer, + or \c -1 if the model does not contain the \a buffer. + */ +int IrcBufferModel::indexOf(IrcBuffer* buffer) const +{ + Q_D(const IrcBufferModel); + return d->bufferList.indexOf(buffer); +} + +/*! + Adds a buffer with \a title to the model and returns it. + */ +IrcBuffer* IrcBufferModel::add(const QString& title) +{ + Q_D(IrcBufferModel); + return d->createBuffer(title); +} + +/*! + Adds the \a buffer to the model. + */ +void IrcBufferModel::add(IrcBuffer* buffer) +{ + Q_D(IrcBufferModel); + d->addBuffer(buffer); +} + +/*! + Removes and destroys a buffer with \a title from the model. + */ +void IrcBufferModel::remove(const QString& title) +{ + Q_D(IrcBufferModel); + d->destroyBuffer(title, true); +} + +/*! + Removes and destroys a \a buffer from the model. + */ +void IrcBufferModel::remove(IrcBuffer* buffer) +{ + delete buffer; +} + +/*! + This property holds the display role. + + The specified data role is returned for Qt::DisplayRole. + + The default value is \ref Irc::TitleRole. + + \par Access functions: + \li \ref Irc::DataRole displayRole() const + \li void setDisplayRole(\ref Irc::DataRole role) + */ +Irc::DataRole IrcBufferModel::displayRole() const +{ + Q_D(const IrcBufferModel); + return d->role; +} + +void IrcBufferModel::setDisplayRole(Irc::DataRole role) +{ + Q_D(IrcBufferModel); + d->role = role; +} + +/*! + \since 3.1 + + \property bool IrcBufferModel::persistent + This property holds whether the model is persistent. + + The default value is \c false. + + A persistent model does not remove and destruct channel buffers + automatically when leaving the corresponding channels. In order + to remove buffers from a persistent model, either call + IrcBufferModel::remove() or delete the buffer. + + \par Access functions: + \li bool isPersistent() const + \li void setPersistent(bool persistent) + + \par Notifier signal: + \li void persistentChanged(bool persistent) + */ +bool IrcBufferModel::isPersistent() const +{ + Q_D(const IrcBufferModel); + return d->persistent; +} + +void IrcBufferModel::setPersistent(bool persistent) +{ + Q_D(IrcBufferModel); + if (d->persistent != persistent) { + d->persistent = persistent; + emit persistentChanged(persistent); + } +} + +/*! + Returns the model index for \a buffer. + */ +QModelIndex IrcBufferModel::index(IrcBuffer* buffer) const +{ + Q_D(const IrcBufferModel); + return index(d->bufferList.indexOf(buffer)); +} + +/*! + Returns the buffer for model \a index. + */ +IrcBuffer* IrcBufferModel::buffer(const QModelIndex& index) const +{ + if (!hasIndex(index.row(), index.column())) + return 0; + + return static_cast(index.internalPointer()); +} + +/*! + This property holds the model sort order. + + The default value is \c Qt::AscendingOrder. + + \par Access functions: + \li Qt::SortOrder sortOrder() const + \li void setSortOrder(Qt::SortOrder order) + + \sa sort(), lessThan() + */ +Qt::SortOrder IrcBufferModel::sortOrder() const +{ + Q_D(const IrcBufferModel); + return d->sortOrder; +} + +void IrcBufferModel::setSortOrder(Qt::SortOrder order) +{ + Q_D(IrcBufferModel); + if (d->sortOrder != order) { + d->sortOrder = order; + if (d->sortMethod != Irc::SortByHand && !d->bufferList.isEmpty()) + sort(d->sortMethod, d->sortOrder); + } +} + +/*! + This property holds the model sort method. + + The default value is \c Irc::SortByHand. + + Method | Description | Example + -----------------|-------------------------------------------------------------------|------------------------------------------------- + Irc::SortByHand | Buffers are not sorted automatically, but only by calling sort(). | - + Irc::SortByName | Buffers are sorted alphabetically, ignoring any channel prefix. | "bot", "#communi", "#freenode", "jpnurmi", "#qt" + Irc::SortByTitle | Buffers are sorted alphabetically, and channels before queries. | "#communi", "#freenode", "#qt", "bot", "jpnurmi" + + \par Access functions: + \li Irc::SortMethod sortMethod() const + \li void setSortMethod(Irc::SortMethod method) + + \sa sort(), lessThan() + */ +Irc::SortMethod IrcBufferModel::sortMethod() const +{ + Q_D(const IrcBufferModel); + return d->sortMethod; +} + +void IrcBufferModel::setSortMethod(Irc::SortMethod method) +{ + Q_D(IrcBufferModel); + if (d->sortMethod != method) { + d->sortMethod = method; + if (d->sortMethod != Irc::SortByHand && !d->bufferList.isEmpty()) + sort(d->sortMethod, d->sortOrder); + } +} + +/*! + Clears the model. + + All buffers except \ref IrcBuffer::persistent "persistent" buffers are removed and destroyed. + + In order to remove a persistent buffer, either explicitly call remove() or delete the buffer. + */ +void IrcBufferModel::clear() +{ + Q_D(IrcBufferModel); + if (!d->bufferList.isEmpty()) { + bool bufferRemoved = false; + bool channelRemoved = false; + foreach (IrcBuffer* buffer, d->bufferList) { + if (!buffer->isPersistent()) { + if (!bufferRemoved) { + beginResetModel(); + bufferRemoved = true; + } + channelRemoved |= buffer->isChannel(); + buffer->disconnect(this); + d->bufferList.removeOne(buffer); + d->channels.removeOne(buffer->title()); + d->bufferMap.remove(buffer->title().toLower()); + delete buffer; + } + } + if (bufferRemoved) { + endResetModel(); + if (channelRemoved) + emit channelsChanged(d->channels); + emit buffersChanged(d->bufferList); + emit countChanged(d->bufferList.count()); + if (d->bufferList.isEmpty()) + emit emptyChanged(true); + } + } +} + +/*! + Makes the model receive and handle \a message. + */ +void IrcBufferModel::receiveMessage(IrcMessage* message) +{ + Q_D(IrcBufferModel); + d->messageFilter(message); +} + +/*! + Sorts the model using the given \a order. + */ +void IrcBufferModel::sort(int column, Qt::SortOrder order) +{ + Q_D(IrcBufferModel); + if (column == 0) + sort(d->sortMethod, order); +} + +/*! + Sorts the model using the given \a method and \a order. + + \sa lessThan() + */ +void IrcBufferModel::sort(Irc::SortMethod method, Qt::SortOrder order) +{ + Q_D(IrcBufferModel); + if (method == Irc::SortByHand) + return; + + emit layoutAboutToBeChanged(); + + QList persistentBuffers; + QModelIndexList oldPersistentIndexes = persistentIndexList(); + foreach (const QModelIndex& index, oldPersistentIndexes) + persistentBuffers += static_cast(index.internalPointer()); + + if (order == Qt::AscendingOrder) + qSort(d->bufferList.begin(), d->bufferList.end(), IrcBufferLessThan(this, method)); + else + qSort(d->bufferList.begin(), d->bufferList.end(), IrcBufferGreaterThan(this, method)); + + QModelIndexList newPersistentIndexes; + foreach (IrcBuffer* buffer, persistentBuffers) + newPersistentIndexes += index(d->bufferList.indexOf(buffer)); + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + + emit layoutChanged(); +} + +/*! + Creates a buffer object with \a title. + + IrcBufferModel will automatically call this factory method when a + need for the buffer object occurs ie. a private message is received. + + The default implementation creates an instance of the buffer prototype. + Reimplement this function in order to alter the default behavior. + + \sa bufferPrototype + */ +IrcBuffer* IrcBufferModel::createBuffer(const QString& title) +{ + Q_D(IrcBufferModel); + Q_UNUSED(title); + QObject* instance = d->bufferProto->metaObject()->newInstance(Q_ARG(QObject*, this)); + return qobject_cast(instance); +} + +/*! + Creates a channel object with \a title. + + IrcBufferModel will automatically call this factory method when a + need for the channel object occurs ie. a channel is being joined. + + The default implementation creates an instance of the channel prototype. + Reimplement this function in order to alter the default behavior. + + \sa channelPrototype + */ +IrcChannel* IrcBufferModel::createChannel(const QString& title) +{ + Q_D(IrcBufferModel); + Q_UNUSED(title); + QObject* instance = d->channelProto->metaObject()->newInstance(Q_ARG(QObject*, this)); + return qobject_cast(instance); +} + +/*! + Returns \c true if \a one buffer is "less than" \a another, + otherwise returns \c false. + + The default implementation sorts according to the specified sort method. + Reimplement this function in order to customize the sort order. + + \sa sort(), sortMethod + */ +bool IrcBufferModel::lessThan(IrcBuffer* one, IrcBuffer* another, Irc::SortMethod method) const +{ + if (one->isSticky() != another->isSticky()) + return one->isSticky(); + + if (method == Irc::SortByTitle) { + const QStringList prefixes = one->network()->channelTypes(); + + const QString p1 = one->prefix(); + const QString p2 = another->prefix(); + + const int i1 = !p1.isEmpty() ? prefixes.indexOf(p1.at(0)) : -1; + const int i2 = !p2.isEmpty() ? prefixes.indexOf(p2.at(0)) : -1; + + if (i1 >= 0 && i2 < 0) + return true; + if (i1 < 0 && i2 >= 0) + return false; + if (i1 >= 0 && i2 >= 0 && i1 != i2) + return i1 < i2; + } + + // Irc::SortByName + const QString n1 = one->name(); + const QString n2 = another->name(); + return n1.compare(n2, Qt::CaseInsensitive) < 0; +} + +/*! + The following role names are provided by default: + + Role | Name | Type | Example + -----------------|------------|-------------|-------- + Qt::DisplayRole | "display" | 1) | - + Irc::BufferRole | "buffer" | IrcBuffer* | <object> + Irc::ChannelRole | "channel" | IrcChannel* | <object> + Irc::NameRole | "name" | QString | "communi" + Irc::PrefixRole | "prefix" | QString | "#" + Irc::TitleRole | "title" | QString | "#communi" + + 1) The type depends on \ref displayRole. + */ +QHash IrcBufferModel::roleNames() const +{ + QHash roles; + roles[Qt::DisplayRole] = "display"; + roles[Irc::BufferRole] = "buffer"; + roles[Irc::ChannelRole] = "channel"; + roles[Irc::NameRole] = "name"; + roles[Irc::PrefixRole] = "prefix"; + roles[Irc::TitleRole] = "title"; + return roles; +} + +/*! + Returns the number of buffers. + */ +int IrcBufferModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + Q_D(const IrcBufferModel); + return d->bufferList.count(); +} + +/*! + Returns the data for specified \a role and user referred to by by the \a index. + */ +QVariant IrcBufferModel::data(const QModelIndex& index, int role) const +{ + Q_D(const IrcBufferModel); + if (!hasIndex(index.row(), index.column(), index.parent())) + return QVariant(); + + IrcBuffer* buffer = static_cast(index.internalPointer()); + Q_ASSERT(buffer); + + switch (role) { + case Qt::DisplayRole: + return data(index, d->role); + case Irc::BufferRole: + return QVariant::fromValue(buffer); + case Irc::ChannelRole: + return QVariant::fromValue(buffer->toChannel()); + case Irc::NameRole: + return buffer->name(); + case Irc::PrefixRole: + return buffer->prefix(); + case Irc::TitleRole: + return buffer->title(); + } + + return QVariant(); +} + +/*! + Returns the index of the item in the model specified by the given \a row, \a column and \a parent index. + */ +QModelIndex IrcBufferModel::index(int row, int column, const QModelIndex& parent) const +{ + Q_D(const IrcBufferModel); + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + return createIndex(row, column, d->bufferList.at(row)); +} + +/*! + This property holds the buffer prototype. + + The prototype is used by the default implementation of createBuffer(). + + \note The prototype must have an invokable constructor. + + \par Access functions: + \li \ref IrcBuffer* bufferPrototype() const + \li void setBufferPrototype(\ref IrcBuffer* prototype) + */ +IrcBuffer* IrcBufferModel::bufferPrototype() const +{ + Q_D(const IrcBufferModel); + return d->bufferProto; +} + +void IrcBufferModel::setBufferPrototype(IrcBuffer* prototype) +{ + Q_D(IrcBufferModel); + if (d->bufferProto != prototype) { + if (d->bufferProto && d->bufferProto->parent() == this) + delete d->bufferProto; + d->bufferProto = prototype ? prototype : new IrcBuffer(this); + emit bufferPrototypeChanged(d->bufferProto); + } +} + +/*! + This property holds the channel prototype. + + The prototype is used by the default implementation of createChannel(). + + \note The prototype must have an invokable constructor. + + \par Access functions: + \li \ref IrcChannel* channelPrototype() const + \li void setChannelPrototype(\ref IrcChannel* prototype) + */ +IrcChannel* IrcBufferModel::channelPrototype() const +{ + Q_D(const IrcBufferModel); + return d->channelProto; +} + +void IrcBufferModel::setChannelPrototype(IrcChannel* prototype) +{ + Q_D(IrcBufferModel); + if (d->channelProto != prototype) { + if (d->channelProto && d->channelProto->parent() == this) + delete d->channelProto; + d->channelProto = prototype ? prototype : new IrcChannel(this); + emit channelPrototypeChanged(d->channelProto); + } +} + +/*! + \since 3.1 + + Saves the state of the model. The \a version number is stored as part of the state data. + + To restore the saved state, pass the return value and \a version number to restoreState(). + */ +QByteArray IrcBufferModel::saveState(int version) const +{ + Q_D(const IrcBufferModel); + QVariantMap args; + args.insert("version", version); + args.insert("sortOrder", d->sortOrder); + args.insert("sortMethod", d->sortMethod); + args.insert("displayRole", d->role); + args.insert("persistent", d->persistent); + + QVariantList bufs; + foreach (IrcBuffer* buffer, d->bufferList) { + QVariantMap b; + b.insert("channel", buffer->isChannel()); + b.insert("name", buffer->name()); + b.insert("prefix", buffer->prefix()); + b.insert("title", buffer->title()); + if (IrcChannel* channel = buffer->toChannel()) { + IrcChannelPrivate* p = IrcChannelPrivate::get(channel); + b.insert("modes", QStringList(p->modes.keys())); + b.insert("args", QStringList(p->modes.values())); + b.insert("topic", channel->topic()); + } + b.insert("stick", buffer->isSticky()); + b.insert("persistent", buffer->isPersistent()); + b.insert("userData", buffer->userData()); + bufs += b; + } + args.insert("buffers", bufs); + + QByteArray state; + QDataStream out(&state, QIODevice::WriteOnly); + out << args; + return state; +} + +/*! + \since 3.1 + + Restores the \a state of the model. The \a version number is compared with that stored in \a state. + If they do not match, the model state is left unchanged, and this function returns \c false; otherwise, + the state is restored, and \c true is returned. + + \sa saveState() + */ +bool IrcBufferModel::restoreState(const QByteArray& state, int version) +{ + Q_D(IrcBufferModel); + QVariantMap args; + QDataStream in(state); + in >> args; + if (in.status() != QDataStream::Ok || args.value("version", -1).toInt() != version) + return false; + + setSortOrder(static_cast(args.value("sortOrder", sortOrder()).toInt())); + setSortMethod(static_cast(args.value("sortMethod", sortMethod()).toInt())); + setDisplayRole(static_cast(args.value("displayRole", displayRole()).toInt())); + setPersistent(args.value("persistent", isPersistent()).toBool()); + + QVariantList buffers = args.value("buffers").toList(); + foreach (const QVariant& v, buffers) { + QVariantMap b = v.toMap(); + IrcBuffer* buffer = find(b.value("title").toString()); + if (!buffer) { + if (b.value("channel").toBool()) + buffer = d->createChannelHelper(b.value("title").toString()); + else + buffer = d->createBufferHelper(b.value("title").toString()); + buffer->setName(b.value("name").toString()); + buffer->setPrefix(b.value("prefix").toString()); + buffer->setSticky(b.value("sticky").toBool()); + buffer->setPersistent(b.value("persistent").toBool()); + buffer->setUserData(b.value("userData").toMap()); + add(buffer); + } + IrcChannel* channel = buffer->toChannel(); + if (channel && !channel->isActive()) { + IrcChannelPrivate* p = IrcChannelPrivate::get(channel); + const QStringList modes = b.value("modes").toStringList(); + const QStringList args = b.value("args").toStringList(); + for (int i = 0; i < modes.count(); ++i) + p->modes.insert(modes.at(i), args.value(i)); + channel->join(); + } + } + return true; +} + +#include "moc_ircbuffermodel.cpp" +#include "moc_ircbuffermodel_p.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/ircchannel.cpp b/libcommuni/src/model/ircchannel.cpp new file mode 100644 index 0000000..15b4bc2 --- /dev/null +++ b/libcommuni/src/model/ircchannel.cpp @@ -0,0 +1,623 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircchannel.h" +#include "ircchannel_p.h" +#include "ircusermodel.h" +#include "ircusermodel_p.h" +#include "ircbuffermodel.h" +#include "ircbuffermodel_p.h" +#include "ircconnection.h" +#include "ircnetwork.h" +#include "irccommand.h" +#include "ircuser_p.h" +#include "irc.h" + +IRC_BEGIN_NAMESPACE + +/*! + \file ircchannel.h + \brief \#include <IrcChannel> + */ + +/*! + \class IrcChannel ircchannel.h + \ingroup models + \brief Keeps track of channel status. + + \sa IrcBufferModel +*/ + +#ifndef IRC_DOXYGEN +static QString getPrefix(const QString& name, const QStringList& prefixes) +{ + int i = 0; + while (i < name.length() && prefixes.contains(name.at(i))) + ++i; + return name.left(i); +} + +static QString channelName(const QString& title, const QStringList& prefixes) +{ + int i = 0; + while (i < title.length() && prefixes.contains(title.at(i))) + ++i; + return title.mid(i); +} + +static QString userName(const QString& name, const QStringList& prefixes) +{ + QString copy = name; + while (!copy.isEmpty() && prefixes.contains(copy.at(0))) + copy.remove(0, 1); + return Irc::nickFromPrefix(copy); +} + +IrcChannelPrivate::IrcChannelPrivate() : active(false) +{ + qRegisterMetaType(); + qRegisterMetaType >(); +} + +IrcChannelPrivate::~IrcChannelPrivate() +{ +} + +void IrcChannelPrivate::init(const QString& title, IrcBufferModel* m) +{ + IrcBufferPrivate::init(title, m); + + const QStringList chanTypes = m->network()->channelTypes(); + prefix = getPrefix(title, chanTypes); + name = channelName(title, chanTypes); +} + +void IrcChannelPrivate::connected() +{ + // not active until joined + setActive(false); +} + +void IrcChannelPrivate::disconnected() +{ + setActive(false); +} + +void IrcChannelPrivate::setActive(bool value) +{ + Q_Q(IrcChannel); + if (active != value) { + active = value; + emit q->activeChanged(active); + } +} + +void IrcChannelPrivate::changeModes(const QString& value, const QStringList& arguments) +{ + Q_Q(IrcChannel); + const IrcNetwork* network = q->network(); + + QMap ms = modes; + QStringList args = arguments; + + bool add = true; + for (int i = 0; i < value.size(); ++i) { + const QString m = value.at(i); + if (m == QLatin1String("+")) { + add = true; + } else if (m == QLatin1String("-")) { + add = false; + } else { + if (add) { + QString a; + if (!args.isEmpty() && network && network->channelModes(IrcNetwork::TypeB | IrcNetwork::TypeC).contains(m)) + a = args.takeFirst(); + ms.insert(m, a); + } else { + ms.remove(m); + } + } + } + + if (modes != ms) { + setKey(ms.value(QLatin1String("k"))); + modes = ms; + emit q->modeChanged(q->mode()); + } +} + +void IrcChannelPrivate::setModes(const QString& value, const QStringList& arguments) +{ + Q_Q(IrcChannel); + const IrcNetwork* network = q->network(); + + QMap ms; + QStringList args = arguments; + + for (int i = 0; i < value.size(); ++i) { + const QString m = value.at(i); + if (m != QLatin1String("+") && m != QLatin1String("-")) { + QString a; + if (!args.isEmpty() && network && network->channelModes(IrcNetwork::TypeB | IrcNetwork::TypeC).contains(m)) + a = args.takeFirst(); + ms.insert(m, a); + } + } + + if (modes != ms) { + setKey(ms.value(QLatin1String("k"))); + modes = ms; + emit q->modeChanged(q->mode()); + } +} + +void IrcChannelPrivate::setTopic(const QString& value) +{ + Q_Q(IrcChannel); + if (topic != value) { + topic = value; + emit q->topicChanged(topic); + } +} + +void IrcChannelPrivate::setKey(const QString& value) +{ + Q_Q(IrcChannel); + if (modes.value(QLatin1String("k")) != value) { + modes.insert(QLatin1String("k"), value); + emit q->keyChanged(value); + } +} + +void IrcChannelPrivate::addUser(const QString& name) +{ + Q_Q(IrcChannel); + const QStringList prefixes = q->network()->prefixes(); + + IrcUser* user = new IrcUser(q); + IrcUserPrivate* priv = IrcUserPrivate::get(user); + priv->channel = q; + priv->setName(userName(name, prefixes)); + priv->setPrefix(getPrefix(name, prefixes)); + priv->setMode(q->network()->prefixToMode(user->prefix())); + activeUsers.prepend(user); + userList.append(user); + userMap.insert(user->name(), user); + names = userMap.keys(); + + foreach (IrcUserModel* model, userModels) + IrcUserModelPrivate::get(model)->addUser(user); +} + +bool IrcChannelPrivate::removeUser(const QString& name) +{ + if (IrcUser* user = userMap.value(name)) { + userMap.remove(name); + names = userMap.keys(); + userList.removeOne(user); + activeUsers.removeOne(user); + foreach (IrcUserModel* model, userModels) + IrcUserModelPrivate::get(model)->removeUser(user); + user->deleteLater(); + return true; + } + return false; +} + +void IrcChannelPrivate::setUsers(const QStringList& users) +{ + Q_Q(IrcChannel); + const QStringList prefixes = q->network()->prefixes(); + + qDeleteAll(userList); + userMap.clear(); + userList.clear(); + activeUsers.clear(); + + foreach (const QString& name, users) { + IrcUser* user = new IrcUser(q); + IrcUserPrivate* priv = IrcUserPrivate::get(user); + priv->channel = q; + priv->setName(userName(name, prefixes)); + priv->setPrefix(getPrefix(name, prefixes)); + priv->setMode(q->network()->prefixToMode(user->prefix())); + activeUsers.append(user); + userList.append(user); + userMap.insert(user->name(), user); + } + names = userMap.keys(); + + foreach (IrcUserModel* model, userModels) + IrcUserModelPrivate::get(model)->setUsers(userList); +} + +bool IrcChannelPrivate::renameUser(const QString& from, const QString& to) +{ + if (IrcUser* user = userMap.take(from)) { + IrcUserPrivate::get(user)->setName(to); + userMap.insert(to, user); + names = userMap.keys(); + + foreach (IrcUserModel* model, userModels) { + IrcUserModelPrivate::get(model)->renameUser(user); + emit model->namesChanged(names); + } + return true; + } + return false; +} + +void IrcChannelPrivate::setUserMode(const QString& name, const QString& command) +{ + if (IrcUser* user = userMap.value(name)) { + bool add = true; + QString mode = user->mode(); + QString prefix = user->prefix(); + const IrcNetwork* network = model->network(); + for (int i = 0; i < command.size(); ++i) { + QChar c = command.at(i); + if (c == QLatin1Char('+')) { + add = true; + } else if (c == QLatin1Char('-')) { + add = false; + } else { + QString p = network->modeToPrefix(c); + if (add) { + if (!mode.contains(c)) + mode += c; + if (!prefix.contains(p)) + prefix += p; + } else { + mode.remove(c); + prefix.remove(p); + } + } + } + + QString sortedMode; + foreach (const QString& m, network->modes()) + if (mode.contains(m)) + sortedMode += m; + + QString sortedPrefix; + foreach (const QString& p, network->prefixes()) + if (prefix.contains(p)) + sortedPrefix += p; + + IrcUserPrivate* priv = IrcUserPrivate::get(user); + priv->setPrefix(sortedPrefix); + priv->setMode(sortedMode); + + foreach (IrcUserModel* model, userModels) + IrcUserModelPrivate::get(model)->setUserMode(user); + } +} + +void IrcChannelPrivate::promoteUser(const QString& name) +{ + if (IrcUser* user = userMap.value(name)) { + const int idx = activeUsers.indexOf(user); + Q_ASSERT(idx != -1); + activeUsers.move(idx, 0); + foreach (IrcUserModel* model, userModels) + IrcUserModelPrivate::get(model)->promoteUser(user); + } +} + +void IrcChannelPrivate::setUserAway(const QString& name, const bool &away) +{ + if (IrcUser* user = userMap.value(name)) { + IrcUserPrivate* priv = IrcUserPrivate::get(user); + + priv->setAway(away); + } +} + +void IrcChannelPrivate::setUserServOp(const QString& name, const bool &servOp) +{ + if (IrcUser* user = userMap.value(name)) { + IrcUserPrivate* priv = IrcUserPrivate::get(user); + + priv->setServOp(servOp); + } +} + +bool IrcChannelPrivate::processJoinMessage(IrcJoinMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) { + if (message->flags() & IrcMessage::Own) + setActive(true); + else + addUser(message->nick()); + } + return true; +} + +bool IrcChannelPrivate::processKickMessage(IrcKickMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) { + if (!message->user().compare(message->connection()->nickName(), Qt::CaseInsensitive)) { + setActive(false); + return true; + } + return removeUser(message->user()); + } + return userMap.contains(message->user()); +} + +bool IrcChannelPrivate::processModeMessage(IrcModeMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) { + if (message->kind() == IrcModeMessage::Channel) { + if (message->isReply()) + setModes(message->mode(), message->arguments()); + else + changeModes(message->mode(), message->arguments()); + return true; + } else if (!message->argument().isEmpty()) { + setUserMode(message->argument(), message->mode()); + } + } + return true; +} + +bool IrcChannelPrivate::processNamesMessage(IrcNamesMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) + setUsers(message->names()); + return true; +} + +bool IrcChannelPrivate::processNickMessage(IrcNickMessage* message) +{ + const bool renamed = renameUser(message->oldNick(), message->newNick()); + if (renamed) + promoteUser(message->newNick()); + return renamed; +} + +bool IrcChannelPrivate::processNoticeMessage(IrcNoticeMessage* message) +{ + promoteUser(message->nick()); + return true; +} + +bool IrcChannelPrivate::processNumericMessage(IrcNumericMessage* message) +{ + promoteUser(message->nick()); + return true; +} + +bool IrcChannelPrivate::processPartMessage(IrcPartMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) { + if (message->flags() & IrcMessage::Own) { + setActive(false); + return true; + } + return removeUser(message->nick()); + } + return true; +} + +bool IrcChannelPrivate::processPrivateMessage(IrcPrivateMessage* message) +{ + const QString content = message->content(); + const bool prefixed = !content.isEmpty() && message->network()->prefixes().contains(content.at(0)); + foreach (IrcUser* user, activeUsers) { + const QString str = prefixed ? user->title() : user->name(); + if (content.startsWith(str)) { + promoteUser(user->name()); + break; + } + } + promoteUser(message->nick()); + return true; +} + +bool IrcChannelPrivate::processQuitMessage(IrcQuitMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) { + if (message->flags() & IrcMessage::Own) { + setActive(false); + return true; + } + return removeUser(message->nick()) || IrcBufferPrivate::processQuitMessage(message); + } + return userMap.contains(message->nick()) || IrcBufferPrivate::processQuitMessage(message); +} + +bool IrcChannelPrivate::processTopicMessage(IrcTopicMessage* message) +{ + if (!(message->flags() & IrcMessage::Playback)) + setTopic(message->topic()); + return true; +} + +bool IrcChannelPrivate::processWhoReplyMessage(IrcWhoReplyMessage *message) +{ + if(message->isValid()) { + setUserAway(message->nick(), message->isAway()); + setUserServOp(message->nick(), message->isServOp()); + return true; + } + return false; +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new channel object with \a parent. + */ +IrcChannel::IrcChannel(QObject* parent) + : IrcBuffer(*new IrcChannelPrivate, parent) +{ +} + +/*! + Destructs the channel object. + */ +IrcChannel::~IrcChannel() +{ + Q_D(IrcChannel); + qDeleteAll(d->userList); + d->userList.clear(); + d->userMap.clear(); + d->names.clear(); + d->userModels.clear(); + emit destroyed(this); +} + +/*! + \since 3.1 + + This property holds the channel key. + + \par Access function: + \li QString key() const + + \par Notifier signal: + \li void keyChanged(const QString& key) + */ +QString IrcChannel::key() const +{ + Q_D(const IrcChannel); + return d->modes.value(QLatin1String("k")); +} + +/*! + This property holds the complete channel mode including possible arguments. + + \par Access function: + \li QString mode() const + + \par Notifier signal: + \li void modeChanged(const QString& mode) + */ +QString IrcChannel::mode() const +{ + Q_D(const IrcChannel); + QString m = QStringList(d->modes.keys()).join(QString()); + QStringList a = d->modes.values(); + a.removeAll(QString()); + if (!a.isEmpty()) + m += QLatin1String(" ") + a.join(QLatin1String(" ")); + if (!m.isEmpty()) + m.prepend(QLatin1String("+")); + return m; +} + +/*! + This property holds the channel topic. + + \par Access function: + \li QString topic() const + + \par Notifier signal: + \li void topicChanged(const QString& topic) + */ +QString IrcChannel::topic() const +{ + Q_D(const IrcChannel); + return d->topic; +} + +bool IrcChannel::isActive() const +{ + Q_D(const IrcChannel); + return IrcBuffer::isActive() && d->active; +} + +/*! + \since 3.1 + + Joins the channel with an optional \a key. + + This method is provided for convenience. It is equal to: + \code + IrcCommand* command = IrcCommand::createJoin(channel->title(), key); + channel->sendCommand(command); + \endcode + + \sa IrcBuffer::sendCommand(), IrcCommand::createJoin() + */ +void IrcChannel::join(const QString& key) +{ + Q_D(IrcChannel); + if (!key.isEmpty()) + d->setKey(key); + sendCommand(IrcCommand::createJoin(title(), IrcChannel::key())); +} + +/*! + Parts the channel with an optional \a reason. + + This method is provided for convenience. It is equal to: + \code + IrcCommand* command = IrcCommand::createPart(channel->title(), reason); + channel->sendCommand(command); + \endcode + + \sa IrcBuffer::sendCommand(), IrcCommand::createPart() + */ +void IrcChannel::part(const QString& reason) +{ + sendCommand(IrcCommand::createPart(title(), reason)); +} + +/*! + \since 3.1 + + Closes the channel with an optional \a reason. + + \sa IrcBuffer::close(), IrcChannel::part() + */ +void IrcChannel::close(const QString& reason) +{ + if (isActive()) + part(reason); + IrcBuffer::close(reason); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const IrcChannel* channel) +{ + if (!channel) + return debug << "IrcChannel(0x0) "; + debug.nospace() << channel->metaObject()->className() << '(' << (void*) channel; + if (!channel->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(channel->objectName()); + if (!channel->title().isEmpty()) + debug.nospace() << ", title=" << qPrintable(channel->title()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircchannel.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/ircmodel.cpp b/libcommuni/src/model/ircmodel.cpp new file mode 100644 index 0000000..7032db5 --- /dev/null +++ b/libcommuni/src/model/ircmodel.cpp @@ -0,0 +1,61 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircmodel.h" + +IRC_BEGIN_NAMESPACE + +/*! + \file ircmodel.h + \brief \#include <IrcModel> + */ + +/*! + \namespace IrcModel + \ingroup models + \brief Module meta-type registration. + */ + +namespace IrcModel { + + /*! + Registers IrcModel types to the %Qt meta-system. + + \sa IrcCore::registerMetaTypes(), IrcUtil::registerMetaTypes(), qRegisterMetaType() + */ + void registerMetaTypes() + { + qRegisterMetaType("IrcBuffer*"); + qRegisterMetaType("IrcBufferModel*"); + qRegisterMetaType("IrcChannel*"); + qRegisterMetaType("IrcUser*"); + qRegisterMetaType("IrcUserModel*"); + } +} + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/ircuser.cpp b/libcommuni/src/model/ircuser.cpp new file mode 100644 index 0000000..d46e7a3 --- /dev/null +++ b/libcommuni/src/model/ircuser.cpp @@ -0,0 +1,266 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircuser.h" +#include "ircuser_p.h" +#include + +IRC_BEGIN_NAMESPACE + +/*! + \file ircuser.h + \brief \#include <IrcUser> + */ + +/*! + \class IrcUser ircuser.h + \ingroup models + \brief Keeps track of user status on a channel. + + \sa IrcUserModel +*/ + +#ifndef IRC_DOXYGEN +void IrcUserPrivate::setName(const QString& n) +{ + Q_Q(IrcUser); + if (name != n) { + name = n; + emit q->nameChanged(name); + emit q->titleChanged(q->title()); + } +} + +void IrcUserPrivate::setPrefix(const QString& p) +{ + Q_Q(IrcUser); + if (prefix != p) { + prefix = p; + emit q->prefixChanged(prefix); + emit q->titleChanged(q->title()); + } +} + +void IrcUserPrivate::setMode(const QString& m) +{ + Q_Q(IrcUser); + if (mode != m) { + mode = m; + emit q->modeChanged(mode); + } +} + +void IrcUserPrivate::setServOp(const bool& o) +{ + Q_Q(IrcUser); + if (servOp != o) { + servOp = o; + emit q->servOpChanged(servOp); + } +} + +void IrcUserPrivate::setAway(const bool& a) +{ + Q_Q(IrcUser); + if (away != a) { + away = a; + emit q->awayChanged(away); + } +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new user with \a parent. + */ +IrcUser::IrcUser(QObject* parent) + : QObject(parent), d_ptr(new IrcUserPrivate) +{ + Q_D(IrcUser); + d->q_ptr = this; + d->channel = 0; + d->away = false; + d->servOp = false; +} + +/*! + Destructs the user object. + */ +IrcUser::~IrcUser() +{ +} + +/*! + This property holds the title. + + The title consists of \ref prefix and \ref name. + + \par Access function: + \li QString title() const + + \par Notifier signal: + \li void titleChanged(const QString& title) + */ +QString IrcUser::title() const +{ + Q_D(const IrcUser); + return d->prefix.left(1) + d->name; +} + +/*! + This property holds the name. + + \par Access function: + \li QString name() const + + \par Notifier signal: + \li void nameChanged(const QString& name) + */ +QString IrcUser::name() const +{ + Q_D(const IrcUser); + return d->name; +} + +/*! + This property holds the prefix character. + + Typical prefix characters are \c @ (op) and \c + (voice). + + \par Access function: + \li QString prefix() const + + \par Notifier signal: + \li void prefixChanged(const QString& prefix) + */ +QString IrcUser::prefix() const +{ + Q_D(const IrcUser); + return d->prefix; +} + +/*! + This property holds the mode letter. + + Typical mode letters are \c o (op) and \c v (voice). + + \par Access function: + \li QString mode() const + + \par Notifier signal: + \li void modeChanged(const QString& mode) + */ +QString IrcUser::mode() const +{ + Q_D(const IrcUser); + return d->mode; +} + +/*! + \since 3.1 + + \property bool IrcUser::servOp + This property holds whether the user is a server operator. + + \note IRC servers do not send this information by default. + In order to fetch the information for all users on a channel, + issue a WHO command on the channel: + \code + IrcChannel* channel = user->channel(); + IrcCommand* command = IrcCommand::createWho(channel->title()); + channel->sendCommand(command); + \endcode + + \par Access function: + \li bool isServOp() const + + \par Notifier signal: + \li void servOpChanged(bool servOp) + */ +bool IrcUser::isServOp() const +{ + Q_D(const IrcUser); + return d->servOp; +} + +/*! + \since 3.1 + + \property bool IrcUser::away + This property holds whether the user is marked as being away. + + \note IRC servers do not send this information by default. + In order to fetch the information for all users on a channel, + issue a WHO command on the channel: + \code + IrcChannel* channel = user->channel(); + IrcCommand* command = IrcCommand::createWho(channel->title()); + channel->sendCommand(command); + \endcode + + \par Access function: + \li bool isAway() const + + \par Notifier signal: + \li void awayChanged(bool away) + */ +bool IrcUser::isAway() const +{ + Q_D(const IrcUser); + return d->away; +} + +/*! + This property holds the channel of the user. + + \par Access function: + \li \ref IrcChannel* channel() const + */ +IrcChannel* IrcUser::channel() const +{ + Q_D(const IrcUser); + return d->channel; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const IrcUser* user) +{ + if (!user) + return debug << "IrcUser(0x0) "; + debug.nospace() << user->metaObject()->className() << '(' << (void*) user; + if (!user->objectName().isEmpty()) + debug.nospace() << ", name=" << qPrintable(user->objectName()); + if (!user->name().isEmpty()) + debug.nospace() << ", user=" << qPrintable(user->name()); + debug.nospace() << ')'; + return debug.space(); +} +#endif // QT_NO_DEBUG_STREAM + +#include "moc_ircuser.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/ircusermodel.cpp b/libcommuni/src/model/ircusermodel.cpp new file mode 100644 index 0000000..23f13fb --- /dev/null +++ b/libcommuni/src/model/ircusermodel.cpp @@ -0,0 +1,709 @@ +/* + Copyright (C) 2008-2014 The Communi Project + + You may use this file under the terms of BSD license as follows: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ircusermodel.h" +#include "ircusermodel_p.h" +#include "ircbuffermodel.h" +#include "ircconnection.h" +#include "ircchannel_p.h" +#include "ircuser.h" +#include + +IRC_BEGIN_NAMESPACE + +/*! + \file ircusermodel.h + \brief \#include <IrcUserModel> + */ + +/*! + \class IrcUserModel ircusermodel.h + \ingroup models + \brief Keeps track of channel users. + + In order to keep track of channel users, create an instance of IrcUserModel. + It will notify via signals when users are added and/or removed. IrcUserModel + can be used directly as a data model for Qt's item views - both in C++ and QML. + + \code + void ChatView::setChannel(IrcChannel* channel) + { + IrcUserModel* model = new IrcUserModel(channel); + connect(model, SIGNAL(added(IrcUser*)), this, SLOT(onUserAdded(IrcUser*))); + connect(model, SIGNAL(removed(IrcUser*)), this, SLOT(onUserRemoved(IrcUser*))); + nickCompleter->setModel(model); + userListView->setModel(model); + } + \endcode +*/ + +/*! + \fn void IrcUserModel::added(IrcUser* user) + + This signal is emitted when a \a user is added to the list of users. + */ + +/*! + \fn void IrcUserModel::removed(IrcUser* user) + + This signal is emitted when a \a user is removed from the list of users. + */ + +/*! + \fn void IrcUserModel::aboutToBeAdded(IrcUser* user) + + This signal is emitted just before a \a user is added to the list of users. + */ + +/*! + \fn void IrcUserModel::aboutToBeRemoved(IrcUser* user) + + This signal is emitted just before a \a user is removed from the list of users. + */ + +#ifndef IRC_DOXYGEN +class IrcUserLessThan +{ +public: + IrcUserLessThan(IrcUserModel* model, Irc::SortMethod method) : model(model), method(method) { } + bool operator()(IrcUser* u1, IrcUser* u2) const { return model->lessThan(u1, u2, method); } +private: + IrcUserModel* model; + Irc::SortMethod method; +}; + +class IrcUserGreaterThan +{ +public: + IrcUserGreaterThan(IrcUserModel* model, Irc::SortMethod method) : model(model), method(method) { } + bool operator()(IrcUser* u1, IrcUser* u2) const { return model->lessThan(u2, u1, method); } +private: + IrcUserModel* model; + Irc::SortMethod method; +}; + +IrcUserModelPrivate::IrcUserModelPrivate() : q_ptr(0), role(Irc::TitleRole), + sortMethod(Irc::SortByHand), sortOrder(Qt::AscendingOrder) +{ +} + +void IrcUserModelPrivate::addUser(IrcUser* user, bool notify) +{ + insertUser(-1, user, notify); +} + +void IrcUserModelPrivate::insertUser(int index, IrcUser* user, bool notify) +{ + Q_Q(IrcUserModel); + if (index == -1) + index = userList.count(); + if (sortMethod != Irc::SortByHand) { + QList::iterator it; + if (sortOrder == Qt::AscendingOrder) + it = qUpperBound(userList.begin(), userList.end(), user, IrcUserLessThan(q, sortMethod)); + else + it = qUpperBound(userList.begin(), userList.end(), user, IrcUserGreaterThan(q, sortMethod)); + index = it - userList.begin(); + } + if (notify) + emit q->aboutToBeAdded(user); + q->beginInsertRows(QModelIndex(), index, index); + userList.insert(index, user); + q->endInsertRows(); + if (notify) { + emit q->added(user); + emit q->namesChanged(IrcChannelPrivate::get(channel)->names); + emit q->usersChanged(userList); + emit q->countChanged(userList.count()); + if (userList.count() == 1) + emit q->emptyChanged(false); + } +} + +void IrcUserModelPrivate::removeUser(IrcUser* user, bool notify) +{ + Q_Q(IrcUserModel); + int idx = userList.indexOf(user); + if (idx != -1) { + if (notify) + emit q->aboutToBeRemoved(user); + q->beginRemoveRows(QModelIndex(), idx, idx); + userList.removeAt(idx); + q->endRemoveRows(); + if (notify) { + emit q->removed(user); + emit q->namesChanged(IrcChannelPrivate::get(channel)->names); + emit q->usersChanged(userList); + emit q->countChanged(userList.count()); + if (userList.isEmpty()) + emit q->emptyChanged(true); + } + } +} + +void IrcUserModelPrivate::setUsers(const QList& users, bool reset) +{ + Q_Q(IrcUserModel); + bool wasEmpty = userList.isEmpty(); + if (reset) + q->beginResetModel(); + userList = users; + if (sortMethod != Irc::SortByHand) { + if (sortOrder == Qt::AscendingOrder) + qSort(userList.begin(), userList.end(), IrcUserLessThan(q, sortMethod)); + else + qSort(userList.begin(), userList.end(), IrcUserGreaterThan(q, sortMethod)); + } + if (reset) + q->endResetModel(); + QStringList names; + if (channel) + names = IrcChannelPrivate::get(channel)->names; + emit q->namesChanged(names); + emit q->usersChanged(userList); + emit q->countChanged(userList.count()); + if (wasEmpty != userList.isEmpty()) + emit q->emptyChanged(userList.isEmpty()); +} + +void IrcUserModelPrivate::renameUser(IrcUser* user) +{ + Q_Q(IrcUserModel); + const int idx = userList.indexOf(user); + if (idx != -1) { + QModelIndex index = q->index(idx, 0); + emit q->dataChanged(index, index); + + if (sortMethod != Irc::SortByHand) { + QList users = userList; + const bool notify = false; + removeUser(user, notify); + insertUser(-1, user, notify); + if (users != userList) + emit q->usersChanged(userList); + } + } +} + +void IrcUserModelPrivate::setUserMode(IrcUser* user) +{ + Q_Q(IrcUserModel); + const int idx = userList.indexOf(user); + if (idx != -1) { + QModelIndex index = q->index(idx, 0); + emit q->dataChanged(index, index); + + if (sortMethod == Irc::SortByTitle) { + const bool notify = false; + removeUser(user, notify); + insertUser(0, user, notify); + emit q->usersChanged(userList); + } + } +} + +void IrcUserModelPrivate::promoteUser(IrcUser* user) +{ + Q_Q(IrcUserModel); + if (sortMethod == Irc::SortByActivity) { + const bool notify = false; + removeUser(user, notify); + insertUser(0, user, notify); + emit q->usersChanged(userList); + } +} +#endif // IRC_DOXYGEN + +/*! + Constructs a new model with \a parent. + + \note If \a parent is an instance of IrcChannel, it will be + automatically assigned to \ref IrcUserModel::channel "channel". + */ +IrcUserModel::IrcUserModel(QObject* parent) : QAbstractListModel(parent), d_ptr(new IrcUserModelPrivate) +{ + Q_D(IrcUserModel); + d->q_ptr = this; + setChannel(qobject_cast(parent)); + + qRegisterMetaType(); + qRegisterMetaType >(); +} + +/*! + Destructs the model. + */ +IrcUserModel::~IrcUserModel() +{ + Q_D(IrcUserModel); + if (d->channel) + IrcChannelPrivate::get(d->channel)->userModels.removeOne(this); +} + +/*! + This property holds the channel. + + \par Access functions: + \li \ref IrcChannel* channel() const + \li void setChannel(\ref IrcChannel* channel) + + \par Notifier signal: + \li void channelChanged(\ref IrcChannel* channel) + */ +IrcChannel* IrcUserModel::channel() const +{ + Q_D(const IrcUserModel); + return d->channel; +} + +void IrcUserModel::setChannel(IrcChannel* channel) +{ + Q_D(IrcUserModel); + if (d->channel != channel) { + beginResetModel(); + if (d->channel) + IrcChannelPrivate::get(d->channel)->userModels.removeOne(this); + + d->channel = channel; + + QList users; + if (d->channel) { + IrcChannelPrivate::get(d->channel)->userModels.append(this); + if (d->sortMethod == Irc::SortByActivity) + users = IrcChannelPrivate::get(d->channel)->activeUsers; + else + users = IrcChannelPrivate::get(d->channel)->userList; + } + const bool reset = false; + d->setUsers(users, reset); + endResetModel(); + + emit channelChanged(channel); + } +} + +/*! + This property holds the number of users on the channel. + + \par Access function: + \li int count() const + + \par Notifier signal: + \li void countChanged(int count) + */ +int IrcUserModel::count() const +{ + return rowCount(); +} + +/*! + \since 3.1 + \property bool IrcUserModel::empty + + This property holds the whether the model is empty. + + \par Access function: + \li bool isEmpty() const + + \par Notifier signal: + \li void emptyChanged(bool empty) + */ +bool IrcUserModel::isEmpty() const +{ + Q_D(const IrcUserModel); + return d->userList.isEmpty(); +} + +/*! + This property holds the list of names in alphabetical order. + + \par Access function: + \li QStringList names() const + + \par Notifier signal: + \li void namesChanged(const QStringList& names) + */ +QStringList IrcUserModel::names() const +{ + Q_D(const IrcUserModel); + if (d->channel && !d->userList.isEmpty()) + return IrcChannelPrivate::get(d->channel)->names; + return QStringList(); +} + +/*! + This property holds the list of users. + + The order of users is kept as sent from the server. + + \par Access function: + \li QList<\ref IrcUser*> users() const + + \par Notifier signal: + \li void usersChanged(const QList<\ref IrcUser*>& users) + */ +QList IrcUserModel::users() const +{ + Q_D(const IrcUserModel); + return d->userList; +} + +/*! + Returns the user object at \a index. + */ +IrcUser* IrcUserModel::get(int index) const +{ + Q_D(const IrcUserModel); + return d->userList.value(index); +} + +/*! + Returns the user object for \a name or \c 0 if not found. + */ +IrcUser* IrcUserModel::find(const QString& name) const +{ + Q_D(const IrcUserModel); + if (d->channel && !d->userList.isEmpty()) + return IrcChannelPrivate::get(d->channel)->userMap.value(name); + return 0; +} + +/*! + Returns \c true if the model contains \a name. + */ +bool IrcUserModel::contains(const QString& name) const +{ + Q_D(const IrcUserModel); + if (d->channel && !d->userList.isEmpty()) + return IrcChannelPrivate::get(d->channel)->userMap.contains(name); + return false; +} + +/*! + Returns the index of the specified \a user, + or \c -1 if the model does not contain the \a user. + */ +int IrcUserModel::indexOf(IrcUser* user) const +{ + Q_D(const IrcUserModel); + return d->userList.indexOf(user); +} + +/*! + This property holds the model sort method. + + The default value is \c Irc::SortByHand. + + Method | Description | Example + --------------------|---------------------------------------------------------------------------------------------------|---------------------------------------------- + Irc::SortByHand | Users are not sorted automatically, but only by calling sort(). | - + Irc::SortByName | Users are sorted alphabetically, ignoring any mode prefix. | "bot", "@ChanServ", "jpnurmi", "+qtassistant" + Irc::SortByTitle | Users are sorted alphabetically, and special users (operators, voiced users) before normal users. | "@ChanServ", "+qtassistant", "bot", "jpnurmi" + Irc::SortByActivity | Users are sorted based on their activity, last active and mentioned (1) users first. | - + + 1) For performance reasons, IrcUserModel does \b not scan the whole channel + messages to find out if a channel user was mentioned. IrcUserModel merely + checks if channel messages \b begin with the name of a user in the model. + + \par Access functions: + \li Irc::SortMethod sortMethod() const + \li void setSortMethod(Irc::SortMethod method) + + \sa sort(), lessThan() + */ +Irc::SortMethod IrcUserModel::sortMethod() const +{ + Q_D(const IrcUserModel); + return d->sortMethod; +} + +void IrcUserModel::setSortMethod(Irc::SortMethod method) +{ + Q_D(IrcUserModel); + if (d->sortMethod != method) { + d->sortMethod = method; + if (method == Irc::SortByActivity && d->channel) + d->userList = IrcChannelPrivate::get(d->channel)->activeUsers; + if (d->sortMethod != Irc::SortByHand && !d->userList.isEmpty()) + sort(d->sortMethod, d->sortOrder); + } +} + +/*! + This property holds the model sort order. + + The default value is \c Qt::AscendingOrder. + + \par Access functions: + \li Qt::SortOrder sortOrder() const + \li void setSortOrder(Qt::SortOrder order) + + \sa sort(), lessThan() + */ +Qt::SortOrder IrcUserModel::sortOrder() const +{ + Q_D(const IrcUserModel); + return d->sortOrder; +} + +void IrcUserModel::setSortOrder(Qt::SortOrder order) +{ + Q_D(IrcUserModel); + if (d->sortOrder != order) { + d->sortOrder = order; + if (d->sortMethod != Irc::SortByHand && !d->userList.isEmpty()) + sort(d->sortMethod, d->sortOrder); + } +} + +/*! + This property holds the display role. + + The specified data role is returned for Qt::DisplayRole. + + The default value is \ref Irc::TitleRole. + + \par Access functions: + \li \ref Irc::DataRole displayRole() const + \li void setDisplayRole(\ref Irc::DataRole role) + */ +Irc::DataRole IrcUserModel::displayRole() const +{ + Q_D(const IrcUserModel); + return d->role; +} + +void IrcUserModel::setDisplayRole(Irc::DataRole role) +{ + Q_D(IrcUserModel); + d->role = role; +} + +/*! + Returns the model index for \a user. + */ +QModelIndex IrcUserModel::index(IrcUser* user) const +{ + Q_D(const IrcUserModel); + return index(d->userList.indexOf(user)); +} + +/*! + Returns the user for model \a index. + */ +IrcUser* IrcUserModel::user(const QModelIndex& index) const +{ + if (!hasIndex(index.row(), index.column())) + return 0; + + return static_cast(index.internalPointer()); +} + +/*! + The following role names are provided by default: + + Role | Name | Type | Example + --------------- | ----------|----------|-------- + Qt::DisplayRole | "display" | 1) | - + Irc::UserRole | "user" | IrcUser* | <object> + Irc::NameRole | "name" | QString | "jpnurmi" + Irc::PrefixRole | "prefix" | QString | "@" + Irc::ModeRole | "mode" | QString | "o" + Irc::TitleRole | "title" | QString | "@jpnurmi" + + 1) The type depends on \ref displayRole. + */ +QHash IrcUserModel::roleNames() const +{ + QHash roles; + roles[Qt::DisplayRole] = "display"; + roles[Irc::UserRole] = "user"; + roles[Irc::NameRole] = "name"; + roles[Irc::PrefixRole] = "prefix"; + roles[Irc::ModeRole] = "mode"; + roles[Irc::TitleRole] = "title"; + return roles; +} + +/*! + Returns the number of users on the channel. + */ +int IrcUserModel::rowCount(const QModelIndex& parent) const +{ + Q_D(const IrcUserModel); + if (parent.isValid() || !d->channel) + return 0; + + return d->userList.count(); +} + +/*! + Returns the data for specified \a role referred to by the \a index. + + \sa Irc::DataRole, roleNames() + */ +QVariant IrcUserModel::data(const QModelIndex& index, int role) const +{ + Q_D(const IrcUserModel); + if (!d->channel || !hasIndex(index.row(), index.column(), index.parent())) + return QVariant(); + + IrcUser* user = static_cast(index.internalPointer()); + Q_ASSERT(user); + + switch (role) { + case Qt::DisplayRole: + return data(index, d->role); + case Irc::UserRole: + return QVariant::fromValue(user); + case Irc::NameRole: + return user->name(); + case Irc::PrefixRole: + return user->prefix().left(1); + case Irc::ModeRole: + return user->mode().left(1); + case Irc::TitleRole: + return user->title(); + } + + return QVariant(); +} + +/*! + Returns the index of the item in the model specified by the given \a row, \a column and \a parent index. + */ +QModelIndex IrcUserModel::index(int row, int column, const QModelIndex& parent) const +{ + Q_D(const IrcUserModel); + if (!d->channel || !hasIndex(row, column, parent)) + return QModelIndex(); + + return createIndex(row, column, d->userList.value(row)); +} + +/*! + Clears the model. + */ +void IrcUserModel::clear() +{ + Q_D(IrcUserModel); + if (!d->userList.isEmpty()) { + beginResetModel(); + d->userList.clear(); + endResetModel(); + emit namesChanged(QStringList()); + emit usersChanged(QList()); + emit countChanged(0); + emit emptyChanged(true); + } +} + +/*! + Sorts the model using the given \a order. + */ +void IrcUserModel::sort(int column, Qt::SortOrder order) +{ + Q_D(IrcUserModel); + if (column == 0) + sort(d->sortMethod, order); +} + +/*! + Sorts the model using the given \a method and \a order. + + \sa lessThan() + */ +void IrcUserModel::sort(Irc::SortMethod method, Qt::SortOrder order) +{ + Q_D(IrcUserModel); + if (method == Irc::SortByHand) + return; + + emit layoutAboutToBeChanged(); + + QList persistentUsers; + QModelIndexList oldPersistentIndexes = persistentIndexList(); + foreach (const QModelIndex& index, oldPersistentIndexes) + persistentUsers += static_cast(index.internalPointer()); + + if (order == Qt::AscendingOrder) + qSort(d->userList.begin(), d->userList.end(), IrcUserLessThan(this, method)); + else + qSort(d->userList.begin(), d->userList.end(), IrcUserGreaterThan(this, method)); + + QModelIndexList newPersistentIndexes; + foreach (IrcUser* user, persistentUsers) + newPersistentIndexes += index(d->userList.indexOf(user)); + changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes); + + emit layoutChanged(); +} + +/*! + Returns \c true if \a one buffer is "less than" \a another, + otherwise returns \c false. + + The default implementation sorts according to the specified sort method. + Reimplement this function in order to customize the sort order. + + \sa sort(), sortMethod + */ +bool IrcUserModel::lessThan(IrcUser* one, IrcUser* another, Irc::SortMethod method) const +{ + if (method == Irc::SortByActivity) { + QList activeUsers = IrcChannelPrivate::get(one->channel())->activeUsers; + const int i1 = activeUsers.indexOf(one); + const int i2 = activeUsers.indexOf(another); + return i1 < i2; + } else if (method == Irc::SortByTitle) { + const IrcNetwork* network = one->channel()->network(); + const QStringList prefixes = network->prefixes(); + + const QString p1 = one->prefix(); + const QString p2 = another->prefix(); + + const int i1 = !p1.isEmpty() ? prefixes.indexOf(p1.at(0)) : -1; + const int i2 = !p2.isEmpty() ? prefixes.indexOf(p2.at(0)) : -1; + + if (i1 >= 0 && i2 < 0) + return true; + if (i1 < 0 && i2 >= 0) + return false; + if (i1 >= 0 && i2 >= 0 && i1 != i2) + return i1 < i2; + } + + // Irc::SortByName + const QString n1 = one->name(); + const QString n2 = another->name(); + return n1.compare(n2, Qt::CaseInsensitive) < 0; +} + +#include "moc_ircusermodel.cpp" + +IRC_END_NAMESPACE diff --git a/libcommuni/src/model/model.pri b/libcommuni/src/model/model.pri new file mode 100644 index 0000000..38212a0 --- /dev/null +++ b/libcommuni/src/model/model.pri @@ -0,0 +1,40 @@ +###################################################################### +# Communi +###################################################################### + +DEFINES += BUILD_IRC_MODEL + +INCDIR = $$PWD/../../include/IrcModel + +DEPENDPATH += $$PWD $$INCDIR +INCLUDEPATH += $$PWD $$INCDIR + +CONV_HEADERS = $$INCDIR/IrcBuffer +CONV_HEADERS += $$INCDIR/IrcBufferModel +CONV_HEADERS += $$INCDIR/IrcChannel +CONV_HEADERS += $$INCDIR/IrcModel +CONV_HEADERS += $$INCDIR/IrcUser +CONV_HEADERS += $$INCDIR/IrcUserModel + +PUB_HEADERS = $$INCDIR/ircbuffer.h +PUB_HEADERS += $$INCDIR/ircbuffermodel.h +PUB_HEADERS += $$INCDIR/ircchannel.h +PUB_HEADERS += $$INCDIR/ircmodel.h +PUB_HEADERS += $$INCDIR/ircuser.h +PUB_HEADERS += $$INCDIR/ircusermodel.h + +PRIV_HEADERS = $$INCDIR/ircbuffer_p.h +PRIV_HEADERS += $$INCDIR/ircbuffermodel_p.h +PRIV_HEADERS += $$INCDIR/ircchannel_p.h +PRIV_HEADERS += $$INCDIR/ircuser_p.h +PRIV_HEADERS += $$INCDIR/ircusermodel_p.h + +HEADERS += $$PUB_HEADERS +HEADERS += $$PRIV_HEADERS + +SOURCES += $$PWD/ircbuffer.cpp +SOURCES += $$PWD/ircbuffermodel.cpp +SOURCES += $$PWD/ircchannel.cpp +SOURCES += $$PWD/ircmodel.cpp +SOURCES += $$PWD/ircuser.cpp +SOURCES += $$PWD/ircusermodel.cpp diff --git a/libcommuni/src/model/model.pro b/libcommuni/src/model/model.pro new file mode 100644 index 0000000..5a35306 --- /dev/null +++ b/libcommuni/src/model/model.pro @@ -0,0 +1,11 @@ +###################################################################### +# Communi +###################################################################### + +IRC_MODULE = IrcModel +include(model.pri) +include(../module_build.pri) +include(../module_install.pri) + +IRC_MODULES = IrcCore +include(../module_deps.pri) -- cgit v1.2.3-54-g00ecf