summaryrefslogtreecommitdiffstats
path: root/libcommuni/src/model
diff options
context:
space:
mode:
Diffstat (limited to 'libcommuni/src/model')
-rw-r--r--libcommuni/src/model/ircbuffer.cpp593
-rw-r--r--libcommuni/src/model/ircbuffermodel.cpp1168
-rw-r--r--libcommuni/src/model/ircchannel.cpp623
-rw-r--r--libcommuni/src/model/ircmodel.cpp61
-rw-r--r--libcommuni/src/model/ircuser.cpp266
-rw-r--r--libcommuni/src/model/ircusermodel.cpp709
-rw-r--r--libcommuni/src/model/model.pri40
-rw-r--r--libcommuni/src/model/model.pro11
8 files changed, 3471 insertions, 0 deletions
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 <IrcBuffer>
+ \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<IrcBuffer*>();
+ qRegisterMetaType<QList<IrcBuffer*> >();
+}
+
+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<IrcJoinMessage*>(message));
+ break;
+ case IrcMessage::Kick:
+ processed = processKickMessage(static_cast<IrcKickMessage*>(message));
+ break;
+ case IrcMessage::Mode:
+ processed = processModeMessage(static_cast<IrcModeMessage*>(message));
+ break;
+ case IrcMessage::Names:
+ processed = processNamesMessage(static_cast<IrcNamesMessage*>(message));
+ break;
+ case IrcMessage::Nick:
+ processed = processNickMessage(static_cast<IrcNickMessage*>(message));
+ break;
+ case IrcMessage::Notice:
+ processed = processNoticeMessage(static_cast<IrcNoticeMessage*>(message));
+ break;
+ case IrcMessage::Numeric:
+ processed = processNumericMessage(static_cast<IrcNumericMessage*>(message));
+ break;
+ case IrcMessage::Part:
+ processed = processPartMessage(static_cast<IrcPartMessage*>(message));
+ break;
+ case IrcMessage::Private:
+ processed = processPrivateMessage(static_cast<IrcPrivateMessage*>(message));
+ break;
+ case IrcMessage::Quit:
+ processed = processQuitMessage(static_cast<IrcQuitMessage*>(message));
+ break;
+ case IrcMessage::Topic:
+ processed = processTopicMessage(static_cast<IrcTopicMessage*>(message));
+ break;
+ case IrcMessage::WhoReply:
+ processed = processWhoReplyMessage(static_cast<IrcWhoReplyMessage*>(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 <b>title</b>() const
+
+ \par Notifier signal:
+ \li void <b>titleChanged</b>(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 <b>name</b>() const
+ \li void <b>setName</b>(const QString& name) [slot]
+
+ \par Notifier signal:
+ \li void <b>nameChanged</b>(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 <b>prefix</b>() const
+ \li void <b>setPrefix</b>(const QString& prefix) [slot]
+
+ \par Notifier signal:
+ \li void <b>prefixChanged</b>(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 <b>isChannel</b>() const
+
+ \sa toChannel()
+ */
+bool IrcBuffer::isChannel() const
+{
+ return qobject_cast<const IrcChannel*>(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<IrcChannel*>(this);
+}
+
+/*!
+ This property holds the connection of the buffer.
+
+ \par Access function:
+ \li \ref IrcConnection* <b>connection</b>() 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* <b>network</b>() 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* <b>model</b>() 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 <b>isActive</b>() const
+
+ \par Notifier signal:
+ \li void <b>activeChanged</b>(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 <b>isSticky</b>() const
+ \li void <b>setSticky</b>(bool sticky)
+
+ \par Notifier signal:
+ \li void <b>stickyChanged</b>(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 <b>isPersistent</b>() const
+ \li void <b>setPersistent</b>(bool persistent)
+
+ \par Notifier signal:
+ \li void <b>persistentChanged</b>(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 <b>userData</b>() const
+ \li void <b>setUserData</b>(const QVariantMap& data)
+
+ \par Notifier signal:
+ \li void <b>userDataChanged</b>(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 <qmetatype.h>
+#include <qmetaobject.h>
+#include <qdatastream.h>
+#include <qvariant.h>
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file ircbuffermodel.h
+ \brief \#include &lt;IrcBufferModel&gt;
+ */
+
+/*!
+ \class IrcBufferModel ircbuffermodel.h <IrcBufferModel>
+ \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<IrcJoinMessage*>(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<IrcWhoReplyMessage*>(msg)->mask(), msg);
+ break;
+
+ case IrcMessage::Private:
+ if (IrcPrivateMessage* pm = static_cast<IrcPrivateMessage*>(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<IrcNoticeMessage*>(msg))
+ processed = !no->isReply() && (processMessage(no->target(), no, no->flags() & IrcMessage::Own) || processMessage(no->nick(), no));
+ break;
+
+ case IrcMessage::Mode:
+ processed = processMessage(static_cast<IrcModeMessage*>(msg)->target(), msg);
+ break;
+
+ case IrcMessage::Numeric:
+ // TODO: any other special cases besides RPL_NAMREPLY?
+ if (static_cast<IrcNumericMessage*>(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<IrcPartMessage*>(msg)->channel());
+ } else if (msg->type() == IrcMessage::Kick) {
+ const IrcKickMessage* kickMsg = static_cast<IrcKickMessage*>(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<IrcBuffer*>();
+ } 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<IrcChannel*>();
+ } 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<IrcBuffer*>::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<IrcBuffer*> 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<IrcConnection*>(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* <b>connection</b>() const
+ \li void <b>setConnection</b>(\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* <b>network</b>() 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 <b>count</b>() const
+
+ \par Notifier signal:
+ \li void <b>countChanged</b>(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 <b>isEmpty</b>() const
+
+ \par Notifier signal:
+ \li void <b>emptyChanged</b>(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 <b>channels</b>() const
+
+ \par Notifier signal:
+ \li void <b>channelsChanged</b>(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*> <b>buffers</b>() const
+
+ \par Notifier signal:
+ \li void <b>buffersChanged</b>(const QList<\ref IrcBuffer*>& buffers)
+ */
+QList<IrcBuffer*> 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 <b>displayRole</b>() const
+ \li void <b>setDisplayRole</b>(\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 <b>isPersistent</b>() const
+ \li void <b>setPersistent</b>(bool persistent)
+
+ \par Notifier signal:
+ \li void <b>persistentChanged</b>(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<IrcBuffer*>(index.internalPointer());
+}
+
+/*!
+ This property holds the model sort order.
+
+ The default value is \c Qt::AscendingOrder.
+
+ \par Access functions:
+ \li Qt::SortOrder <b>sortOrder</b>() const
+ \li void <b>setSortOrder</b>(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 <b>sortMethod</b>() const
+ \li void <b>setSortMethod</b>(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<IrcBuffer*> persistentBuffers;
+ QModelIndexList oldPersistentIndexes = persistentIndexList();
+ foreach (const QModelIndex& index, oldPersistentIndexes)
+ persistentBuffers += static_cast<IrcBuffer*>(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<IrcBuffer*>(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<IrcChannel*>(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* | &lt;object&gt;
+ Irc::ChannelRole | "channel" | IrcChannel* | &lt;object&gt;
+ Irc::NameRole | "name" | QString | "communi"
+ Irc::PrefixRole | "prefix" | QString | "#"
+ Irc::TitleRole | "title" | QString | "#communi"
+
+ 1) The type depends on \ref displayRole.
+ */
+QHash<int, QByteArray> IrcBufferModel::roleNames() const
+{
+ QHash<int, QByteArray> 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<IrcBuffer*>(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* <b>bufferPrototype</b>() const
+ \li void <b>setBufferPrototype</b>(\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* <b>channelPrototype</b>() const
+ \li void <b>setChannelPrototype</b>(\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<Qt::SortOrder>(args.value("sortOrder", sortOrder()).toInt()));
+ setSortMethod(static_cast<Irc::SortMethod>(args.value("sortMethod", sortMethod()).toInt()));
+ setDisplayRole(static_cast<Irc::DataRole>(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 &lt;IrcChannel&gt;
+ */
+
+/*!
+ \class IrcChannel ircchannel.h <IrcChannel>
+ \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<IrcChannel*>();
+ qRegisterMetaType<QList<IrcChannel*> >();
+}
+
+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<QString, QString> 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<QString, QString> 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 <b>key</b>() const
+
+ \par Notifier signal:
+ \li void <b>keyChanged</b>(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 <b>mode</b>() const
+
+ \par Notifier signal:
+ \li void <b>modeChanged</b>(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 <b>topic</b>() const
+
+ \par Notifier signal:
+ \li void <b>topicChanged</b>(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 &lt;IrcModel&gt;
+ */
+
+/*!
+ \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*>("IrcBuffer*");
+ qRegisterMetaType<IrcBufferModel*>("IrcBufferModel*");
+ qRegisterMetaType<IrcChannel*>("IrcChannel*");
+ qRegisterMetaType<IrcUser*>("IrcUser*");
+ qRegisterMetaType<IrcUserModel*>("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 <qdebug.h>
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file ircuser.h
+ \brief \#include &lt;IrcUser&gt;
+ */
+
+/*!
+ \class IrcUser ircuser.h <IrcUser>
+ \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 <b>title</b>() const
+
+ \par Notifier signal:
+ \li void <b>titleChanged</b>(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 <b>name</b>() const
+
+ \par Notifier signal:
+ \li void <b>nameChanged</b>(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 <b>prefix</b>() const
+
+ \par Notifier signal:
+ \li void <b>prefixChanged</b>(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 <b>mode</b>() const
+
+ \par Notifier signal:
+ \li void <b>modeChanged</b>(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 <b>isServOp</b>() const
+
+ \par Notifier signal:
+ \li void <b>servOpChanged</b>(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 <b>isAway</b>() const
+
+ \par Notifier signal:
+ \li void <b>awayChanged</b>(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* <b>channel</b>() 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 <qpointer.h>
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file ircusermodel.h
+ \brief \#include &lt;IrcUserModel&gt;
+ */
+
+/*!
+ \class IrcUserModel ircusermodel.h <IrcUserModel>
+ \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<IrcUser*>::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<IrcUser*>& 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<IrcUser*> 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<IrcChannel*>(parent));
+
+ qRegisterMetaType<IrcUser*>();
+ qRegisterMetaType<QList<IrcUser*> >();
+}
+
+/*!
+ 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* <b>channel</b>() const
+ \li void <b>setChannel</b>(\ref IrcChannel* channel)
+
+ \par Notifier signal:
+ \li void <b>channelChanged</b>(\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<IrcUser*> 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 <b>count</b>() const
+
+ \par Notifier signal:
+ \li void <b>countChanged</b>(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 <b>isEmpty</b>() const
+
+ \par Notifier signal:
+ \li void <b>emptyChanged</b>(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 <b>names</b>() const
+
+ \par Notifier signal:
+ \li void <b>namesChanged</b>(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*> <b>users</b>() const
+
+ \par Notifier signal:
+ \li void <b>usersChanged</b>(const QList<\ref IrcUser*>& users)
+ */
+QList<IrcUser*> 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 <b>sortMethod</b>() const
+ \li void <b>setSortMethod</b>(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 <b>sortOrder</b>() const
+ \li void <b>setSortOrder</b>(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 <b>displayRole</b>() const
+ \li void <b>setDisplayRole</b>(\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<IrcUser*>(index.internalPointer());
+}
+
+/*!
+ The following role names are provided by default:
+
+ Role | Name | Type | Example
+ --------------- | ----------|----------|--------
+ Qt::DisplayRole | "display" | 1) | -
+ Irc::UserRole | "user" | IrcUser* | &lt;object&gt;
+ 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<int, QByteArray> IrcUserModel::roleNames() const
+{
+ QHash<int, QByteArray> 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<IrcUser*>(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<IrcUser*>());
+ 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<IrcUser*> persistentUsers;
+ QModelIndexList oldPersistentIndexes = persistentIndexList();
+ foreach (const QModelIndex& index, oldPersistentIndexes)
+ persistentUsers += static_cast<IrcUser*>(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<IrcUser*> 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)