1 /*
2   Copyright (C) 2008-2020 The Communi Project
3 
4   You may use this file under the terms of BSD license as follows:
5 
6   Redistribution and use in source and binary forms, with or without
7   modification, are permitted provided that the following conditions are met:
8     * Redistributions of source code must retain the above copyright
9       notice, this list of conditions and the following disclaimer.
10     * Redistributions in binary form must reproduce the above copyright
11       notice, this list of conditions and the following disclaimer in the
12       documentation and/or other materials provided with the distribution.
13     * Neither the name of the copyright holder nor the names of its
14       contributors may be used to endorse or promote products derived
15       from this software without specific prior written permission.
16 
17   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
21   ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
29 #include "ircbuffermodel.h"
30 #include "ircbuffermodel_p.h"
31 #include "ircchannel_p.h"
32 #include "ircbuffer_p.h"
33 #include "ircnetwork.h"
34 #include "ircchannel.h"
35 #include "ircmessage.h"
36 #include "irccommand.h"
37 #include "ircconnection.h"
38 #include <qmetatype.h>
39 #include <qmetaobject.h>
40 #include <qdatastream.h>
41 #include <qvariant.h>
42 #include <qtimer.h>
43 #include <algorithm>
44 
45 IRC_BEGIN_NAMESPACE
46 
47 /*!
48     \file ircbuffermodel.h
49     \brief \#include &lt;IrcBufferModel&gt;
50  */
51 
52 /*!
53     \class IrcBufferModel ircbuffermodel.h <IrcBufferModel>
54     \ingroup models
55     \brief Keeps track of buffers.
56 
57     IrcBufferModel automatically keeps track of channel and query buffers
58     and manages IrcBuffer instances for them. It will notify via signals
59     when channel and query buffers are added and/or removed. IrcBufferModel
60     can be used directly as a data model for Qt's item views - both in C++
61     and QML.
62 
63     \code
64     IrcConnection* connection = new IrcConnection(this);
65     IrcBufferModel* model = new IrcBufferModel(connection);
66     connect(model, SIGNAL(added(IrcBuffer*)), this, SLOT(onBufferAdded(IrcBuffer*)));
67     connect(model, SIGNAL(removed(IrcBuffer*)), this, SLOT(onBufferRemoved(IrcBuffer*)));
68     listView->setModel(model);
69     \endcode
70  */
71 
72 /*!
73     \fn void IrcBufferModel::added(IrcBuffer* buffer)
74 
75     This signal is emitted when a \a buffer is added to the list of buffers.
76  */
77 
78 /*!
79     \fn void IrcBufferModel::removed(IrcBuffer* buffer)
80 
81     This signal is emitted when a \a buffer is removed from the list of buffers.
82  */
83 
84 /*!
85     \fn void IrcBufferModel::aboutToBeAdded(IrcBuffer* buffer)
86 
87     This signal is emitted just before a \a buffer is added to the list of buffers.
88  */
89 
90 /*!
91     \fn void IrcBufferModel::aboutToBeRemoved(IrcBuffer* buffer)
92 
93     This signal is emitted just before a \a buffer is removed from the list of buffers.
94  */
95 
96 /*!
97     \fn void IrcBufferModel::messageIgnored(IrcMessage* message)
98 
99     This signal is emitted when a message was ignored.
100 
101     IrcBufferModel handles only buffer specific messages and delivers
102     them to the appropriate IrcBuffer instances. When applications decide
103     to handle IrcBuffer::messageReceived(), this signal makes it easy to
104     implement handling for the rest, non-buffer specific messages.
105 
106     \sa IrcConnection::messageReceived(), IrcBuffer::messageReceived()
107  */
108 
109 #ifndef IRC_DOXYGEN
110 class IrcBufferLessThan
111 {
112 public:
IrcBufferLessThan(IrcBufferModel * model,Irc::SortMethod method)113     IrcBufferLessThan(IrcBufferModel* model, Irc::SortMethod method) : model(model), method(method) { }
operator ()(IrcBuffer * b1,IrcBuffer * b2) const114     bool operator()(IrcBuffer* b1, IrcBuffer* b2) const { return model->lessThan(b1, b2, method); }
115 private:
116     IrcBufferModel* model;
117     Irc::SortMethod method;
118 };
119 
120 class IrcBufferGreaterThan
121 {
122 public:
IrcBufferGreaterThan(IrcBufferModel * model,Irc::SortMethod method)123     IrcBufferGreaterThan(IrcBufferModel* model, Irc::SortMethod method) : model(model), method(method) { }
operator ()(IrcBuffer * b1,IrcBuffer * b2) const124     bool operator()(IrcBuffer* b1, IrcBuffer* b2) const { return model->lessThan(b2, b1, method); }
125 private:
126     IrcBufferModel* model;
127     Irc::SortMethod method;
128 };
129 
irc_buffer_model_roles()130 static QHash<int, QByteArray> irc_buffer_model_roles()
131 {
132     QHash<int, QByteArray> roles;
133     roles[Qt::DisplayRole] = "display";
134     roles[Irc::BufferRole] = "buffer";
135     roles[Irc::ChannelRole] = "channel";
136     roles[Irc::NameRole] = "name";
137     roles[Irc::PrefixRole] = "prefix";
138     roles[Irc::TitleRole] = "title";
139     return roles;
140 }
141 
IrcBufferModelPrivate()142 IrcBufferModelPrivate::IrcBufferModelPrivate()
143 {
144 }
145 
messageFilter(IrcMessage * msg)146 bool IrcBufferModelPrivate::messageFilter(IrcMessage* msg)
147 {
148     Q_Q(IrcBufferModel);
149     if (msg->type() == IrcMessage::Join && msg->isOwn())
150         createBuffer(static_cast<IrcJoinMessage*>(msg)->channel());
151 
152     bool processed = false;
153     switch (msg->type()) {
154         case IrcMessage::Away:
155         case IrcMessage::Nick:
156         case IrcMessage::Quit:
157             foreach (IrcBuffer* buffer, bufferList) {
158                 if (buffer->isActive())
159                     IrcBufferPrivate::get(buffer)->processMessage(msg);
160             }
161             if (msg->type() != IrcMessage::Away || !msg->isOwn())
162                 processed = true;
163             break;
164 
165         case IrcMessage::Join:
166         case IrcMessage::Part:
167         case IrcMessage::Kick:
168         case IrcMessage::Names:
169         case IrcMessage::Topic:
170             processed = processMessage(msg->property("channel").toString(), msg);
171             break;
172 
173         case IrcMessage::WhoReply:
174             processed = processMessage(static_cast<IrcWhoReplyMessage*>(msg)->mask(), msg);
175             break;
176 
177         case IrcMessage::Private:
178             if (IrcPrivateMessage* pm = static_cast<IrcPrivateMessage*>(msg))
179                 processed = !pm->isRequest() && processMessage(pm->isPrivate() ? pm->nick() : pm->target(), pm, true);
180             break;
181 
182         case IrcMessage::Notice:
183             if (IrcNoticeMessage* no = static_cast<IrcNoticeMessage*>(msg))
184                 processed = !no->isReply() && processMessage(no->isPrivate() ? no->nick() : no->target(), no, no->host() != QLatin1String("services."));
185             break;
186 
187         case IrcMessage::Mode:
188             processed = processMessage(static_cast<IrcModeMessage*>(msg)->target(), msg);
189             break;
190 
191         case IrcMessage::Numeric:
192             // TODO: any other special cases besides RPL_NAMREPLY?
193             if (static_cast<IrcNumericMessage*>(msg)->code() == Irc::RPL_NAMREPLY) {
194                 const int count = msg->parameters().count();
195                 const QString channel = msg->parameters().value(count - 2);
196                 processed = processMessage(channel, msg);
197             } else if (static_cast<IrcNumericMessage*>(msg)->code() == Irc::RPL_MONONLINE ||
198                        static_cast<IrcNumericMessage*>(msg)->code() == Irc::RPL_MONOFFLINE) {
199                 msg->setFlag(IrcMessage::Implicit);
200                 foreach (const QString& target, msg->parameters().value(1).split(QLatin1String(",")))
201                     processed |= processMessage(Irc::nickFromPrefix(target), msg);
202             } else {
203                 processed = processMessage(msg->parameters().value(1), msg);
204             }
205             break;
206 
207         default:
208             break;
209     }
210 
211     if (!processed)
212         emit q->messageIgnored(msg);
213 
214     if (!msg->testFlag(IrcMessage::Playback)) {
215         if (msg->type() == IrcMessage::Part && msg->isOwn()) {
216             destroyBuffer(static_cast<IrcPartMessage*>(msg)->channel());
217         } else if (msg->type() == IrcMessage::Kick) {
218             const IrcKickMessage* kickMsg = static_cast<IrcKickMessage*>(msg);
219             if (!kickMsg->user().compare(msg->connection()->nickName(), Qt::CaseInsensitive))
220                 destroyBuffer(kickMsg->channel());
221         }
222     }
223     return false;
224 }
225 
commandFilter(IrcCommand * cmd)226 bool IrcBufferModelPrivate::commandFilter(IrcCommand* cmd)
227 {
228     if (cmd->type() == IrcCommand::Join) {
229         const QString channel = cmd->parameters().value(0).toLower();
230         const QString key = cmd->parameters().value(1);
231         if (!key.isEmpty())
232             keys.insert(channel, key);
233         else
234             keys.remove(channel);
235     }
236     return false;
237 }
238 
createBufferHelper(const QString & title)239 IrcBuffer* IrcBufferModelPrivate::createBufferHelper(const QString& title)
240 {
241     Q_Q(IrcBufferModel);
242     IrcBuffer* buffer = nullptr;
243     const QMetaObject* metaObject = q->metaObject();
244     int idx = metaObject->indexOfMethod("createBuffer(QVariant)");
245     if (idx != -1) {
246         // QML: QVariant createBuffer(QVariant)
247         QVariant ret;
248         QMetaMethod method = metaObject->method(idx);
249         method.invoke(q, Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, title));
250         buffer = ret.value<IrcBuffer*>();
251     } else {
252         // C++: IrcBuffer* createBuffer(QString)
253         idx = metaObject->indexOfMethod("createBuffer(QString)");
254         QMetaMethod method = metaObject->method(idx);
255         method.invoke(q, Q_RETURN_ARG(IrcBuffer*, buffer), Q_ARG(QString, title));
256     }
257     return buffer;
258 }
259 
createChannelHelper(const QString & title)260 IrcChannel* IrcBufferModelPrivate::createChannelHelper(const QString& title)
261 {
262     Q_Q(IrcBufferModel);
263     IrcChannel* channel = nullptr;
264     const QMetaObject* metaObject = q->metaObject();
265     int idx = metaObject->indexOfMethod("createChannel(QVariant)");
266     if (idx != -1) {
267         // QML: QVariant createChannel(QVariant)
268         QVariant ret;
269         QMetaMethod method = metaObject->method(idx);
270         method.invoke(q, Q_RETURN_ARG(QVariant, ret), Q_ARG(QVariant, title));
271         channel = ret.value<IrcChannel*>();
272     } else {
273         // C++: IrcChannel* createChannel(QString)
274         idx = metaObject->indexOfMethod("createChannel(QString)");
275         QMetaMethod method = metaObject->method(idx);
276         method.invoke(q, Q_RETURN_ARG(IrcChannel*, channel), Q_ARG(QString, title));
277     }
278     return channel;
279 }
280 
createBuffer(const QString & title)281 IrcBuffer* IrcBufferModelPrivate::createBuffer(const QString& title)
282 {
283     Q_Q(IrcBufferModel);
284     IrcBuffer* buffer = bufferMap.value(title.toLower());
285     if (!buffer) {
286         if (connection && connection->network()->isChannel(title))
287             buffer = createChannelHelper(title);
288         else
289             buffer = createBufferHelper(title);
290         if (buffer) {
291             IrcBufferPrivate::get(buffer)->init(title, q);
292             addBuffer(buffer);
293         }
294     }
295     return buffer;
296 }
297 
destroyBuffer(const QString & title,bool force)298 void IrcBufferModelPrivate::destroyBuffer(const QString& title, bool force)
299 {
300     IrcBuffer* buffer = bufferMap.value(title.toLower());
301     if (buffer && (force || (!persistent && !buffer->isPersistent()))) {
302         removeBuffer(buffer);
303         buffer->deleteLater();
304     }
305 }
306 
addBuffer(IrcBuffer * buffer,bool notify)307 void IrcBufferModelPrivate::addBuffer(IrcBuffer* buffer, bool notify)
308 {
309     insertBuffer(-1, buffer, notify);
310 }
311 
insertBuffer(int index,IrcBuffer * buffer,bool notify)312 void IrcBufferModelPrivate::insertBuffer(int index, IrcBuffer* buffer, bool notify)
313 {
314     Q_Q(IrcBufferModel);
315     if (buffer && !bufferList.contains(buffer)) {
316         restoreBuffer(buffer);
317         const QString title = buffer->title();
318         const QString lower = title.toLower();
319         if (bufferMap.contains(lower)) {
320             qWarning() << "IrcBufferModel: ignored duplicate buffer" << title;
321             return;
322         }
323         IrcBufferPrivate::get(buffer)->setModel(q);
324         const bool isChannel = buffer->isChannel();
325         if (sortMethod != Irc::SortByHand) {
326             QList<IrcBuffer*>::iterator it;
327             if (sortOrder == Qt::AscendingOrder)
328                 it = std::upper_bound(bufferList.begin(), bufferList.end(), buffer, IrcBufferLessThan(q, sortMethod));
329             else
330                 it = std::upper_bound(bufferList.begin(), bufferList.end(), buffer, IrcBufferGreaterThan(q, sortMethod));
331             index = it - bufferList.begin();
332         } else if (index == -1) {
333             index = bufferList.count();
334         }
335         if (notify)
336             emit q->aboutToBeAdded(buffer);
337         q->beginInsertRows(QModelIndex(), index, index);
338         bufferList.insert(index, buffer);
339         bufferMap.insert(lower, buffer);
340         if (isChannel) {
341             channels += title;
342             IrcChannel* channel = buffer->toChannel();
343             if (keys.contains(lower) && channel->key().isEmpty())
344                 IrcChannelPrivate::get(channel)->setKey(keys.take(lower));
345         }
346         q->connect(buffer, SIGNAL(destroyed(IrcBuffer*)), SLOT(_irc_bufferDestroyed(IrcBuffer*)));
347         q->endInsertRows();
348         if (notify) {
349             emit q->added(buffer);
350             if (isChannel)
351                 emit q->channelsChanged(channels);
352             emit q->buffersChanged(bufferList);
353             emit q->countChanged(bufferList.count());
354             if (bufferList.count() == 1)
355                 emit q->emptyChanged(false);
356         }
357         if (monitorEnabled && IrcBufferPrivate::get(buffer)->isMonitorable()) {
358             connection->sendCommand(IrcCommand::createMonitor("+", buffer->title()));
359             if (!monitorPending) {
360                 monitorPending = true;
361                 QTimer::singleShot(1000, q, SLOT(_irc_monitorStatus()));
362             }
363         }
364     }
365 }
366 
removeBuffer(IrcBuffer * buffer,bool notify)367 void IrcBufferModelPrivate::removeBuffer(IrcBuffer* buffer, bool notify)
368 {
369     Q_Q(IrcBufferModel);
370     int idx = bufferList.indexOf(buffer);
371     if (idx != -1) {
372         const QString title = buffer->title();
373         const QString lower = title.toLower();
374         const bool isChannel = buffer->isChannel();
375         if (notify)
376             emit q->aboutToBeRemoved(buffer);
377         q->beginRemoveRows(QModelIndex(), idx, idx);
378         bufferList.removeAt(idx);
379         bufferMap.remove(lower);
380         bufferStates.remove(lower);
381         if (isChannel)
382             channels.removeOne(title);
383         q->endRemoveRows();
384         if (notify) {
385             emit q->removed(buffer);
386             if (isChannel)
387                 emit q->channelsChanged(channels);
388             emit q->buffersChanged(bufferList);
389             emit q->countChanged(bufferList.count());
390             if (bufferList.isEmpty())
391                 emit q->emptyChanged(true);
392         }
393         if (monitorEnabled && IrcBufferPrivate::get(buffer)->isMonitorable())
394             connection->sendCommand(IrcCommand::createMonitor("-", title));
395     }
396 }
397 
renameBuffer(const QString & from,const QString & to)398 bool IrcBufferModelPrivate::renameBuffer(const QString& from, const QString& to)
399 {
400     Q_Q(IrcBufferModel);
401     const QString fromLower = from.toLower();
402     const QString toLower = to.toLower();
403     if (bufferMap.contains(toLower))
404         destroyBuffer(toLower, true);
405     if (bufferMap.contains(fromLower)) {
406         IrcBuffer* buffer = bufferMap.take(fromLower);
407         bufferMap.insert(toLower, buffer);
408 
409         const int idx = bufferList.indexOf(buffer);
410         QModelIndex index = q->index(idx);
411         emit q->dataChanged(index, index);
412 
413         if (sortMethod != Irc::SortByHand) {
414             QList<IrcBuffer*> buffers = bufferList;
415             const bool notify = false;
416             removeBuffer(buffer, notify);
417             insertBuffer(-1, buffer, notify);
418             if (buffers != bufferList)
419                 emit q->buffersChanged(bufferList);
420         }
421         return true;
422     }
423     return false;
424 }
425 
promoteBuffer(IrcBuffer * buffer)426 void IrcBufferModelPrivate::promoteBuffer(IrcBuffer* buffer)
427 {
428     Q_Q(IrcBufferModel);
429     if (sortMethod == Irc::SortByActivity) {
430         const bool notify = false;
431         removeBuffer(buffer, notify);
432         insertBuffer(0, buffer, notify);
433         emit q->buffersChanged(bufferList);
434     }
435 }
436 
restoreBuffer(IrcBuffer * buffer)437 void IrcBufferModelPrivate::restoreBuffer(IrcBuffer* buffer)
438 {
439     const QVariantMap& b = bufferStates.value(buffer->title().toLower()).toMap();
440     if (!b.isEmpty()) {
441         buffer->setSticky(b.value("sticky").toBool());
442         buffer->setPersistent(b.value("persistent").toBool());
443         buffer->setUserData(b.value("userData").toMap());
444         IrcChannel* channel = buffer->toChannel();
445         if (channel && !channel->isActive()) {
446             IrcChannelPrivate* p = IrcChannelPrivate::get(channel);
447             const QStringList modes = b.value("modes").toStringList();
448             const QStringList args = b.value("args").toStringList();
449             for (int i = 0; i < modes.count(); ++i)
450                 p->modes.insert(modes.at(i), args.value(i));
451             p->enabled = b.value("enabled", true).toBool();
452         }
453     }
454 }
455 
saveBuffer(IrcBuffer * buffer) const456 QVariantMap IrcBufferModelPrivate::saveBuffer(IrcBuffer* buffer) const
457 {
458     QVariantMap b;
459     b.insert("title", buffer->title());
460     b.insert("name", buffer->name());
461     b.insert("prefix", buffer->prefix());
462     if (IrcChannel* channel = buffer->toChannel()) {
463         IrcChannelPrivate* p = IrcChannelPrivate::get(channel);
464         b.insert("modes", QStringList(p->modes.keys()));
465         b.insert("args", QStringList(p->modes.values()));
466         b.insert("topic", channel->topic());
467         b.insert("enabled", p->enabled);
468     }
469     b.insert("channel", buffer->isChannel());
470     b.insert("sticky", buffer->isSticky());
471     b.insert("persistent", buffer->isPersistent());
472     b.insert("userData", buffer->userData());
473     return b;
474 }
475 
processMessage(const QString & title,IrcMessage * message,bool create)476 bool IrcBufferModelPrivate::processMessage(const QString& title, IrcMessage* message, bool create)
477 {
478     IrcBuffer* buffer = bufferMap.value(title.toLower());
479     if (!buffer && create && title != QLatin1String("*"))
480         buffer = createBuffer(title);
481     if (buffer)
482         return IrcBufferPrivate::get(buffer)->processMessage(message);
483     return false;
484 }
485 
_irc_connected()486 void IrcBufferModelPrivate::_irc_connected()
487 {
488     foreach (IrcBuffer* buffer, bufferList)
489         IrcBufferPrivate::get(buffer)->connected();
490 }
491 
_irc_initialized()492 void IrcBufferModelPrivate::_irc_initialized()
493 {
494     Q_Q(IrcBufferModel);
495     if (joinDelay >= 0)
496         QTimer::singleShot(joinDelay * 1000, q, SLOT(_irc_restoreBuffers()));
497 
498     bool monitored = false;
499     foreach (IrcBuffer* buffer, bufferList) {
500         if (monitorEnabled && IrcBufferPrivate::get(buffer)->isMonitorable()) {
501             connection->sendCommand(IrcCommand::createMonitor("+", buffer->title()));
502             monitored = true;
503         }
504     }
505 
506     if (monitored && !monitorPending) {
507         monitorPending = true;
508         QTimer::singleShot(1000, q, SLOT(_irc_monitorStatus()));
509     }
510 }
511 
_irc_disconnected()512 void IrcBufferModelPrivate::_irc_disconnected()
513 {
514     foreach (IrcBuffer* buffer, bufferList)
515         IrcBufferPrivate::get(buffer)->disconnected();
516 }
517 
_irc_bufferDestroyed(IrcBuffer * buffer)518 void IrcBufferModelPrivate::_irc_bufferDestroyed(IrcBuffer* buffer)
519 {
520     removeBuffer(buffer);
521 }
522 
sortIrcChannels_withKeysFirst(IrcChannel * ch1,IrcChannel * ch2)523 static bool sortIrcChannels_withKeysFirst(IrcChannel *ch1, IrcChannel *ch2) {
524     return ch1->key().length() > ch2->key().length();
525 }
526 
_irc_restoreBuffers()527 void IrcBufferModelPrivate::_irc_restoreBuffers()
528 {
529     Q_Q(IrcBufferModel);
530     if (!connection || !connection->isConnected())
531         return;
532 
533     bool hasActiveChannels = false;
534     foreach (IrcBuffer* buffer, bufferList) {
535         if (buffer->isChannel() && buffer->isActive()) {
536             // this is probably a bouncer connection if there are already
537             // active channels. don't restore and re-join channels that were
538             // left in another client meanwhile this client was disconnected.
539             hasActiveChannels = true;
540         }
541     }
542 
543     if (!hasActiveChannels) {
544         foreach (const QVariant& v, bufferStates) {
545             QVariantMap b = v.toMap();
546             IrcBuffer* buffer = q->find(b.value("title").toString());
547             if (!buffer) {
548                 if (b.value("channel").toBool())
549                     buffer = createChannelHelper(b.value("title").toString());
550                 else
551                     buffer = createBufferHelper(b.value("title").toString());
552                 buffer->setName(b.value("name").toString());
553                 buffer->setPrefix(b.value("prefix").toString());
554                 q->add(buffer);
555             }
556         }
557 
558         // Join multiple channels:
559         //
560         // * A single IRC command may be 512 bytes long, including the <CR><LF> at the end.
561         //   Therefore, we must collect as many channels as many channels as possible into
562         //   a single command, but without exceeding the 512 bytes.
563         //   NOTES:
564         //       - Previously, communi tried to join all channels in a single command,
565         //         which didn't work because it exceeded the 512 bytes
566         //       - Then, it joined 3 channels at a time
567         //
568         // * We should also group channels with keys separately from channels without keys,
569         //   because the JOIN command doesn't work when channels without keys preceed the
570         //   channels with keys.
571         //   Works:
572         //       JOIN #ch1,#ch2             --- neither #ch1 nor #ch2 have keys
573         //       JOIN #ch1,#ch2 key1,key2   --- both #ch1 and #ch2 have keys
574         //       JOIN #ch1,#ch2 key1        --- #ch1 has key, #ch2 doesn't
575         //   Doesn't work:
576         //       JOIN #ch1,#ch2 ,key2       --- #ch1 doesn't have a key, #ch2 does
577         //                                      (NOTE: this is what communi used to do)
578         //
579 
580         // Get channels from buffers
581         QList<IrcChannel*> filteredChannels;
582 
583         foreach (IrcBuffer* buf, bufferList) {
584             IrcChannel *channel = buf->toChannel();
585             if (channel && !channel->isActive() && IrcChannelPrivate::get(channel)->enabled) {
586                 filteredChannels.append(channel);
587             }
588         }
589 
590         // Sort channels with keys first
591         std::sort(filteredChannels.begin(), filteredChannels.end(), sortIrcChannels_withKeysFirst);
592 
593         if (filteredChannels.size()) {
594 
595             // Length of "JOIN  \r\n"
596             const int joinCommandMinLength = 8;
597             const int maxIrcCommandBytes = 512;
598             int joinCommandLength = joinCommandMinLength;
599 
600             QStringList chans, keys;
601             foreach (IrcChannel *channel, filteredChannels) {
602                 int additonalLength = channel->title().length() + channel->key().length();
603                 // Command needs a comma between channels
604                 if (chans.length())
605                     additonalLength++;
606                 // Command needs a comma between keys
607                 if (keys.length() && channel->key().length())
608                     additonalLength++;
609 
610                 if ((joinCommandLength + additonalLength) > maxIrcCommandBytes) {
611                     // If the command size would exceed the maximum, send a command with the channels collected so far
612                     connection->sendCommand(IrcCommand::createJoin(chans, keys));
613 
614                     chans.clear();
615                     keys.clear();
616                     joinCommandLength = joinCommandMinLength;
617                 }
618 
619                 // Add channel to list
620                 chans += channel->title();
621 
622                 // Only add key to list if there is a key,
623                 // otherwise not needed, because channels with keys are sorted before channels without keys
624                 if (channel->key().length())
625                     keys += channel->key();
626 
627                 joinCommandLength = additonalLength;
628             }
629 
630             if (!chans.isEmpty()) {
631                 connection->sendCommand(IrcCommand::createJoin(chans, keys));
632             }
633 
634         }
635     }
636 }
637 
_irc_monitorStatus()638 void IrcBufferModelPrivate::_irc_monitorStatus()
639 {
640     if (monitorEnabled && connection)
641         connection->sendCommand(IrcCommand::createMonitor("S"));
642     monitorPending = false;
643 }
644 #endif // IRC_DOXYGEN
645 
646 /*!
647     Constructs a new model with \a parent.
648 
649     \note If \a parent is an instance of IrcConnection, it will be
650     automatically assigned to \ref IrcBufferModel::connection "connection".
651  */
IrcBufferModel(QObject * parent)652 IrcBufferModel::IrcBufferModel(QObject* parent)
653     : QAbstractListModel(parent), d_ptr(new IrcBufferModelPrivate)
654 {
655     Q_D(IrcBufferModel);
656     d->q_ptr = this;
657     setBufferPrototype(new IrcBuffer(this));
658     setChannelPrototype(new IrcChannel(this));
659     setConnection(qobject_cast<IrcConnection*>(parent));
660 
661 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
662     setRoleNames(irc_buffer_model_roles());
663 #endif
664 }
665 
666 /*!
667     Destructs the model.
668  */
~IrcBufferModel()669 IrcBufferModel::~IrcBufferModel()
670 {
671     Q_D(IrcBufferModel);
672     foreach (IrcBuffer* buffer, d->bufferList) {
673         buffer->disconnect(this);
674         delete buffer;
675     }
676     d->bufferList.clear();
677     d->bufferMap.clear();
678     d->channels.clear();
679     emit destroyed(this);
680 }
681 
682 /*!
683     This property holds the connection.
684 
685     \par Access functions:
686     \li \ref IrcConnection* <b>connection</b>() const
687     \li void <b>setConnection</b>(\ref IrcConnection* connection)
688 
689     \warning Changing the connection on the fly is not supported.
690  */
connection() const691 IrcConnection* IrcBufferModel::connection() const
692 {
693     Q_D(const IrcBufferModel);
694     return d->connection;
695 }
696 
setConnection(IrcConnection * connection)697 void IrcBufferModel::setConnection(IrcConnection* connection)
698 {
699     Q_D(IrcBufferModel);
700     if (d->connection != connection) {
701         if (d->connection) {
702             qCritical("IrcBufferModel::setConnection(): changing the connection on the fly is not supported.");
703             return;
704         }
705         d->connection = connection;
706         d->connection->installMessageFilter(d);
707         d->connection->installCommandFilter(d);
708         connect(d->connection, SIGNAL(connected()), this, SLOT(_irc_connected()));
709         connect(d->connection, SIGNAL(disconnected()), this, SLOT(_irc_disconnected()));
710         connect(d->connection->network(), SIGNAL(initialized()), this, SLOT(_irc_initialized()));
711         emit connectionChanged(connection);
712         emit networkChanged(network());
713     }
714 }
715 
716 /*!
717     This property holds the network.
718 
719     \par Access functions:
720     \li \ref IrcNetwork* <b>network</b>() const
721  */
network() const722 IrcNetwork* IrcBufferModel::network() const
723 {
724     Q_D(const IrcBufferModel);
725     return d->connection ? d->connection->network() : nullptr;
726 }
727 
728 /*!
729     This property holds the number of buffers.
730 
731     \par Access function:
732     \li int <b>count</b>() const
733 
734     \par Notifier signal:
735     \li void <b>countChanged</b>(int count)
736  */
count() const737 int IrcBufferModel::count() const
738 {
739     return rowCount();
740 }
741 
742 /*!
743     \since 3.1
744     \property bool IrcBufferModel::empty
745 
746     This property holds the whether the model is empty.
747 
748     \par Access function:
749     \li bool <b>isEmpty</b>() const
750 
751     \par Notifier signal:
752     \li void <b>emptyChanged</b>(bool empty)
753  */
isEmpty() const754 bool IrcBufferModel::isEmpty() const
755 {
756     Q_D(const IrcBufferModel);
757     return d->bufferList.isEmpty();
758 }
759 
760 /*!
761     This property holds the list of channel names.
762 
763     \par Access function:
764     \li QStringList <b>channels</b>() const
765 
766     \par Notifier signal:
767     \li void <b>channelsChanged</b>(const QStringList& channels)
768  */
channels() const769 QStringList IrcBufferModel::channels() const
770 {
771     Q_D(const IrcBufferModel);
772     return d->channels;
773 }
774 
775 /*!
776     This property holds the list of buffers.
777 
778     \par Access function:
779     \li QList<\ref IrcBuffer*> <b>buffers</b>() const
780 
781     \par Notifier signal:
782     \li void <b>buffersChanged</b>(const QList<\ref IrcBuffer*>& buffers)
783  */
buffers() const784 QList<IrcBuffer*> IrcBufferModel::buffers() const
785 {
786     Q_D(const IrcBufferModel);
787     return d->bufferList;
788 }
789 
790 /*!
791     Returns the buffer object at \a index.
792  */
get(int index) const793 IrcBuffer* IrcBufferModel::get(int index) const
794 {
795     Q_D(const IrcBufferModel);
796     return d->bufferList.value(index);
797 }
798 
799 /*!
800     Returns the buffer object for \a title or \c 0 if not found.
801  */
find(const QString & title) const802 IrcBuffer* IrcBufferModel::find(const QString& title) const
803 {
804     Q_D(const IrcBufferModel);
805     return d->bufferMap.value(title.toLower());
806 }
807 
808 /*!
809     Returns \c true if the model contains \a title.
810  */
contains(const QString & title) const811 bool IrcBufferModel::contains(const QString& title) const
812 {
813     Q_D(const IrcBufferModel);
814     return d->bufferMap.contains(title.toLower());
815 }
816 
817 /*!
818     Returns the index of the specified \a buffer,
819     or \c -1 if the model does not contain the \a buffer.
820  */
indexOf(IrcBuffer * buffer) const821 int IrcBufferModel::indexOf(IrcBuffer* buffer) const
822 {
823     Q_D(const IrcBufferModel);
824     return d->bufferList.indexOf(buffer);
825 }
826 
827 /*!
828     Adds a buffer with \a title to the model and returns it.
829  */
add(const QString & title)830 IrcBuffer* IrcBufferModel::add(const QString& title)
831 {
832     Q_D(IrcBufferModel);
833     return d->createBuffer(title);
834 }
835 
836 /*!
837     Adds the \a buffer to the model.
838  */
add(IrcBuffer * buffer)839 void IrcBufferModel::add(IrcBuffer* buffer)
840 {
841     Q_D(IrcBufferModel);
842     d->addBuffer(buffer);
843 }
844 
845 /*!
846     Removes and destroys a buffer with \a title from the model.
847  */
remove(const QString & title)848 void IrcBufferModel::remove(const QString& title)
849 {
850     Q_D(IrcBufferModel);
851     d->destroyBuffer(title, true);
852 }
853 
854 /*!
855     Removes and destroys a \a buffer from the model.
856  */
remove(IrcBuffer * buffer)857 void IrcBufferModel::remove(IrcBuffer* buffer)
858 {
859     delete buffer;
860 }
861 
862 /*!
863     This property holds the display role.
864 
865     The specified data role is returned for Qt::DisplayRole.
866 
867     The default value is \ref Irc::TitleRole.
868 
869     \par Access functions:
870     \li \ref Irc::DataRole <b>displayRole</b>() const
871     \li void <b>setDisplayRole</b>(\ref Irc::DataRole role)
872  */
displayRole() const873 Irc::DataRole IrcBufferModel::displayRole() const
874 {
875     Q_D(const IrcBufferModel);
876     return d->role;
877 }
878 
setDisplayRole(Irc::DataRole role)879 void IrcBufferModel::setDisplayRole(Irc::DataRole role)
880 {
881     Q_D(IrcBufferModel);
882     d->role = role;
883 }
884 
885 /*!
886     \since 3.1
887 
888     \property bool IrcBufferModel::persistent
889     This property holds whether the model is persistent.
890 
891     The default value is \c false.
892 
893     A persistent model does not remove and destruct channel buffers
894     automatically when leaving the corresponding channels. In order
895     to remove buffers from a persistent model, either call
896     IrcBufferModel::remove() or delete the buffer.
897 
898     \par Access functions:
899     \li bool <b>isPersistent</b>() const
900     \li void <b>setPersistent</b>(bool persistent)
901 
902     \par Notifier signal:
903     \li void <b>persistentChanged</b>(bool persistent)
904  */
isPersistent() const905 bool IrcBufferModel::isPersistent() const
906 {
907     Q_D(const IrcBufferModel);
908     return d->persistent;
909 }
910 
setPersistent(bool persistent)911 void IrcBufferModel::setPersistent(bool persistent)
912 {
913     Q_D(IrcBufferModel);
914     if (d->persistent != persistent) {
915         d->persistent = persistent;
916         emit persistentChanged(persistent);
917     }
918 }
919 
920 /*!
921     Returns the model index for \a buffer.
922  */
index(IrcBuffer * buffer) const923 QModelIndex IrcBufferModel::index(IrcBuffer* buffer) const
924 {
925     Q_D(const IrcBufferModel);
926     return index(d->bufferList.indexOf(buffer));
927 }
928 
929 /*!
930     Returns the buffer for model \a index.
931  */
buffer(const QModelIndex & index) const932 IrcBuffer* IrcBufferModel::buffer(const QModelIndex& index) const
933 {
934     if (!hasIndex(index.row(), index.column()))
935         return nullptr;
936 
937     return static_cast<IrcBuffer*>(index.internalPointer());
938 }
939 
940 /*!
941     This property holds the model sort order.
942 
943     The default value is \c Qt::AscendingOrder.
944 
945     \par Access functions:
946     \li Qt::SortOrder <b>sortOrder</b>() const
947     \li void <b>setSortOrder</b>(Qt::SortOrder order)
948 
949     \sa sort(), lessThan()
950  */
sortOrder() const951 Qt::SortOrder IrcBufferModel::sortOrder() const
952 {
953     Q_D(const IrcBufferModel);
954     return d->sortOrder;
955 }
956 
setSortOrder(Qt::SortOrder order)957 void IrcBufferModel::setSortOrder(Qt::SortOrder order)
958 {
959     Q_D(IrcBufferModel);
960     if (d->sortOrder != order) {
961         d->sortOrder = order;
962         if (d->sortMethod != Irc::SortByHand && !d->bufferList.isEmpty())
963             sort(d->sortMethod, d->sortOrder);
964     }
965 }
966 
967 /*!
968     This property holds the model sort method.
969 
970     The default value is \c Irc::SortByHand.
971 
972     Method              | Description                                                                      | Example
973     --------------------|----------------------------------------------------------------------------------|-------------------------------------------------
974     Irc::SortByHand     | Buffers are not sorted automatically, but only by calling sort().                | -
975     Irc::SortByName     | Buffers are sorted alphabetically, ignoring any channel prefix.                  | "bot", "#communi", "#freenode", "jpnurmi", "#qt"
976     Irc::SortByTitle    | Buffers are sorted alphabetically, and channels before queries.                  | "#communi", "#freenode", "#qt", "bot", "jpnurmi"
977     Irc::SortByActivity | Buffers are sorted based on their messaging activity, last active buffers first. | -
978 
979     \note Irc::SortByActivity support was added in version \b 3.4.
980 
981     \par Access functions:
982     \li Irc::SortMethod <b>sortMethod</b>() const
983     \li void <b>setSortMethod</b>(Irc::SortMethod method)
984 
985     \sa sort(), lessThan()
986  */
sortMethod() const987 Irc::SortMethod IrcBufferModel::sortMethod() const
988 {
989     Q_D(const IrcBufferModel);
990     return d->sortMethod;
991 }
992 
setSortMethod(Irc::SortMethod method)993 void IrcBufferModel::setSortMethod(Irc::SortMethod method)
994 {
995     Q_D(IrcBufferModel);
996     if (d->sortMethod != method) {
997         d->sortMethod = method;
998         if (d->sortMethod != Irc::SortByHand && !d->bufferList.isEmpty())
999             sort(d->sortMethod, d->sortOrder);
1000     }
1001 }
1002 
1003 /*!
1004     Clears the model.
1005 
1006     All buffers except \ref IrcBuffer::persistent "persistent" buffers are removed and destroyed.
1007 
1008     In order to remove a persistent buffer, either explicitly call remove() or delete the buffer.
1009  */
clear()1010 void IrcBufferModel::clear()
1011 {
1012     Q_D(IrcBufferModel);
1013     if (!d->bufferList.isEmpty()) {
1014         bool bufferRemoved = false;
1015         bool channelRemoved = false;
1016         foreach (IrcBuffer* buffer, d->bufferList) {
1017             if (!buffer->isPersistent()) {
1018                 if (!bufferRemoved) {
1019                     beginResetModel();
1020                     bufferRemoved = true;
1021                 }
1022                 channelRemoved |= buffer->isChannel();
1023                 buffer->disconnect(this);
1024                 d->bufferList.removeOne(buffer);
1025                 d->channels.removeOne(buffer->title());
1026                 d->bufferMap.remove(buffer->title().toLower());
1027                 delete buffer;
1028             }
1029         }
1030         if (bufferRemoved) {
1031             endResetModel();
1032             if (channelRemoved)
1033                 emit channelsChanged(d->channels);
1034             emit buffersChanged(d->bufferList);
1035             emit countChanged(d->bufferList.count());
1036             if (d->bufferList.isEmpty())
1037                 emit emptyChanged(true);
1038         }
1039     }
1040 }
1041 
1042 /*!
1043     Makes the model receive and handle \a message.
1044  */
receiveMessage(IrcMessage * message)1045 void IrcBufferModel::receiveMessage(IrcMessage* message)
1046 {
1047     Q_D(IrcBufferModel);
1048     d->messageFilter(message);
1049 }
1050 
1051 /*!
1052     Sorts the model using the given \a order.
1053  */
sort(int column,Qt::SortOrder order)1054 void IrcBufferModel::sort(int column, Qt::SortOrder order)
1055 {
1056     Q_D(IrcBufferModel);
1057     if (column == 0)
1058         sort(d->sortMethod, order);
1059 }
1060 
1061 /*!
1062     Sorts the model using the given \a method and \a order.
1063 
1064     \sa lessThan()
1065  */
sort(Irc::SortMethod method,Qt::SortOrder order)1066 void IrcBufferModel::sort(Irc::SortMethod method, Qt::SortOrder order)
1067 {
1068     Q_D(IrcBufferModel);
1069     if (method == Irc::SortByHand)
1070         return;
1071 
1072     emit layoutAboutToBeChanged();
1073 
1074     QList<IrcBuffer*> persistentBuffers;
1075     QModelIndexList oldPersistentIndexes = persistentIndexList();
1076     foreach (const QModelIndex& index, oldPersistentIndexes)
1077         persistentBuffers += static_cast<IrcBuffer*>(index.internalPointer());
1078 
1079     if (order == Qt::AscendingOrder)
1080         std::sort(d->bufferList.begin(), d->bufferList.end(), IrcBufferLessThan(this, method));
1081     else
1082         std::sort(d->bufferList.begin(), d->bufferList.end(), IrcBufferGreaterThan(this, method));
1083 
1084     QModelIndexList newPersistentIndexes;
1085     foreach (IrcBuffer* buffer, persistentBuffers)
1086         newPersistentIndexes += index(d->bufferList.indexOf(buffer));
1087     changePersistentIndexList(oldPersistentIndexes, newPersistentIndexes);
1088 
1089     emit layoutChanged();
1090 }
1091 
1092 /*!
1093     Creates a buffer object with \a title.
1094 
1095     IrcBufferModel will automatically call this factory method when a
1096     need for the buffer object occurs ie. a private message is received.
1097 
1098     The default implementation creates an instance of the buffer prototype.
1099     Reimplement this function in order to alter the default behavior.
1100 
1101     \sa bufferPrototype
1102  */
createBuffer(const QString & title)1103 IrcBuffer* IrcBufferModel::createBuffer(const QString& title)
1104 {
1105     Q_D(IrcBufferModel);
1106     Q_UNUSED(title);
1107     return d->bufferProto->clone(this);
1108 }
1109 
1110 /*!
1111     Creates a channel object with \a title.
1112 
1113     IrcBufferModel will automatically call this factory method when a
1114     need for the channel object occurs ie. a channel is being joined.
1115 
1116     The default implementation creates an instance of the channel prototype.
1117     Reimplement this function in order to alter the default behavior.
1118 
1119     \sa channelPrototype
1120  */
createChannel(const QString & title)1121 IrcChannel* IrcBufferModel::createChannel(const QString& title)
1122 {
1123     Q_D(IrcBufferModel);
1124     Q_UNUSED(title);
1125     return qobject_cast<IrcChannel*>(d->channelProto->clone(this));
1126 }
1127 
1128 /*!
1129     Returns \c true if \a one buffer is "less than" \a another,
1130     otherwise returns \c false.
1131 
1132     The default implementation sorts according to the specified sort method.
1133     Reimplement this function in order to customize the sort order.
1134 
1135     \sa sort(), sortMethod
1136  */
lessThan(IrcBuffer * one,IrcBuffer * another,Irc::SortMethod method) const1137 bool IrcBufferModel::lessThan(IrcBuffer* one, IrcBuffer* another, Irc::SortMethod method) const
1138 {
1139     if (one->isSticky() != another->isSticky())
1140         return one->isSticky();
1141 
1142     if (method == Irc::SortByActivity) {
1143         QDateTime ts1 = IrcBufferPrivate::get(one)->activity;
1144         QDateTime ts2 = IrcBufferPrivate::get(another)->activity;
1145         if (ts1.isValid() || ts2.isValid())
1146             return ts1.isValid() && ts1 > ts2;
1147     }
1148 
1149     if (method == Irc::SortByTitle) {
1150         const QStringList prefixes = one->network()->channelTypes();
1151 
1152         const QString p1 = one->prefix();
1153         const QString p2 = another->prefix();
1154 
1155         const int i1 = !p1.isEmpty() ? prefixes.indexOf(p1.at(0)) : -1;
1156         const int i2 = !p2.isEmpty() ? prefixes.indexOf(p2.at(0)) : -1;
1157 
1158         if (i1 >= 0 && i2 < 0)
1159             return true;
1160         if (i1 < 0 && i2 >= 0)
1161             return false;
1162         if (i1 >= 0 && i2 >= 0 && i1 != i2)
1163             return i1 < i2;
1164     }
1165 
1166     // Irc::SortByName
1167     const QString n1 = one->name();
1168     const QString n2 = another->name();
1169     return n1.compare(n2, Qt::CaseInsensitive) < 0;
1170 }
1171 
1172 /*!
1173     The following role names are provided by default:
1174 
1175     Role             | Name       | Type        | Example
1176     -----------------|------------|-------------|--------
1177     Qt::DisplayRole  | "display"  | 1)          | -
1178     Irc::BufferRole  | "buffer"   | IrcBuffer*  | &lt;object&gt;
1179     Irc::ChannelRole | "channel"  | IrcChannel* | &lt;object&gt;
1180     Irc::NameRole    | "name"     | QString     | "communi"
1181     Irc::PrefixRole  | "prefix"   | QString     | "#"
1182     Irc::TitleRole   | "title"    | QString     | "#communi"
1183 
1184     1) The type depends on \ref displayRole.
1185  */
1186 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
roleNames() const1187 QHash<int, QByteArray> IrcBufferModel::roleNames() const
1188 {
1189     return irc_buffer_model_roles();
1190 }
1191 #endif
1192 
1193 /*!
1194     Returns the number of buffers.
1195  */
rowCount(const QModelIndex & parent) const1196 int IrcBufferModel::rowCount(const QModelIndex& parent) const
1197 {
1198     if (parent.isValid())
1199         return 0;
1200 
1201     Q_D(const IrcBufferModel);
1202     return d->bufferList.count();
1203 }
1204 
1205 /*!
1206     Returns the data for specified \a role and user referred to by by the \a index.
1207  */
data(const QModelIndex & index,int role) const1208 QVariant IrcBufferModel::data(const QModelIndex& index, int role) const
1209 {
1210     Q_D(const IrcBufferModel);
1211     if (!hasIndex(index.row(), index.column(), index.parent()))
1212         return QVariant();
1213 
1214     IrcBuffer* buffer = static_cast<IrcBuffer*>(index.internalPointer());
1215     Q_ASSERT(buffer);
1216 
1217     switch (role) {
1218     case Qt::DisplayRole:
1219         return data(index, d->role);
1220     case Irc::BufferRole:
1221         return QVariant::fromValue(buffer);
1222     case Irc::ChannelRole:
1223         return QVariant::fromValue(buffer->toChannel());
1224     case Irc::NameRole:
1225         return buffer->name();
1226     case Irc::PrefixRole:
1227         return buffer->prefix();
1228     case Irc::TitleRole:
1229         return buffer->title();
1230     }
1231 
1232     return QVariant();
1233 }
1234 
1235 /*!
1236     Returns the index of the item in the model specified by the given \a row, \a column and \a parent index.
1237  */
index(int row,int column,const QModelIndex & parent) const1238 QModelIndex IrcBufferModel::index(int row, int column, const QModelIndex& parent) const
1239 {
1240     Q_D(const IrcBufferModel);
1241     if (!hasIndex(row, column, parent))
1242         return QModelIndex();
1243 
1244     return createIndex(row, column, d->bufferList.at(row));
1245 }
1246 
1247 /*!
1248     This property holds the buffer prototype.
1249 
1250     The prototype is used by the default implementation of createBuffer().
1251 
1252     \note A custom buffer prototype must reimplement \ref IrcBuffer::clone().
1253 
1254     \par Access functions:
1255     \li \ref IrcBuffer* <b>bufferPrototype</b>() const
1256     \li void <b>setBufferPrototype</b>(\ref IrcBuffer* prototype)
1257  */
bufferPrototype() const1258 IrcBuffer* IrcBufferModel::bufferPrototype() const
1259 {
1260     Q_D(const IrcBufferModel);
1261     return d->bufferProto;
1262 }
1263 
setBufferPrototype(IrcBuffer * prototype)1264 void IrcBufferModel::setBufferPrototype(IrcBuffer* prototype)
1265 {
1266     Q_D(IrcBufferModel);
1267     if (d->bufferProto != prototype) {
1268         if (d->bufferProto && d->bufferProto->parent() == this)
1269             delete d->bufferProto;
1270         d->bufferProto = prototype ? prototype : new IrcBuffer(this);
1271         emit bufferPrototypeChanged(d->bufferProto);
1272     }
1273 }
1274 
1275 /*!
1276     This property holds the channel prototype.
1277 
1278     The prototype is used by the default implementation of createChannel().
1279 
1280     \note A custom channel prototype must reimplement \ref IrcChannel::clone().
1281 
1282     \par Access functions:
1283     \li \ref IrcChannel* <b>channelPrototype</b>() const
1284     \li void <b>setChannelPrototype</b>(\ref IrcChannel* prototype)
1285  */
channelPrototype() const1286 IrcChannel* IrcBufferModel::channelPrototype() const
1287 {
1288     Q_D(const IrcBufferModel);
1289     return d->channelProto;
1290 }
1291 
setChannelPrototype(IrcChannel * prototype)1292 void IrcBufferModel::setChannelPrototype(IrcChannel* prototype)
1293 {
1294     Q_D(IrcBufferModel);
1295     if (d->channelProto != prototype) {
1296         if (d->channelProto && d->channelProto->parent() == this)
1297             delete d->channelProto;
1298         d->channelProto = prototype ? prototype : new IrcChannel(this);
1299         emit channelPrototypeChanged(d->channelProto);
1300     }
1301 }
1302 
1303 /*!
1304     \since 3.3
1305 
1306     This property holds the join delay in seconds.
1307 
1308     The default value is \c 0 - channels are joined immediately
1309     after getting connected. A negative value disables automatic
1310     joining of channels.
1311 
1312     \par Access function:
1313     \li int <b>joinDelay</b>() const
1314     \li void <b>setJoinDelay</b>(int delay)
1315 
1316     \par Notifier signal:
1317     \li void <b>joinDelayChanged</b>(int delay)
1318  */
joinDelay() const1319 int IrcBufferModel::joinDelay() const
1320 {
1321     Q_D(const IrcBufferModel);
1322     return d->joinDelay;
1323 }
1324 
setJoinDelay(int delay)1325 void IrcBufferModel::setJoinDelay(int delay)
1326 {
1327     Q_D(IrcBufferModel);
1328     if (d->joinDelay != delay) {
1329         d->joinDelay = delay;
1330         emit joinDelayChanged(delay);
1331     }
1332 }
1333 
1334 /*!
1335     \since 3.4
1336     \property bool IrcBufferModel::monitorEnabled
1337 
1338     This property holds whether automatic monitor is enabled.
1339 
1340     The default value is \c false.
1341 
1342     \par Access function:
1343     \li bool <b>isMonitorEnabled</b>() const
1344     \li void <b>setMonitorEnabled</b>(bool enabled)
1345 
1346     \par Notifier signal:
1347     \li void <b>monitorEnabledChanged</b>(bool enabled)
1348 
1349     \sa \ref ircv3
1350  */
isMonitorEnabled() const1351 bool IrcBufferModel::isMonitorEnabled() const
1352 {
1353     Q_D(const IrcBufferModel);
1354     return d->monitorEnabled;
1355 }
1356 
setMonitorEnabled(bool enabled)1357 void IrcBufferModel::setMonitorEnabled(bool enabled)
1358 {
1359     Q_D(IrcBufferModel);
1360     if (d->monitorEnabled != enabled) {
1361         d->monitorEnabled = enabled;
1362         emit monitorEnabledChanged(enabled);
1363     }
1364 }
1365 
1366 /*!
1367     \since 3.1
1368 
1369     Saves the state of the model. The \a version number is stored as part of the state data.
1370 
1371     To restore the saved state, pass the return value and \a version number to restoreState().
1372  */
saveState(int version) const1373 QByteArray IrcBufferModel::saveState(int version) const
1374 {
1375     Q_D(const IrcBufferModel);
1376     QVariantMap args;
1377     args.insert("version", version);
1378 
1379     QVariantMap states = d->bufferStates;
1380     foreach (IrcBuffer* buffer, d->bufferList)
1381         states.insert(buffer->title(), d->saveBuffer(buffer));
1382 
1383     QVariantList buffers;
1384     foreach (const QVariant& b, states)
1385         buffers += b;
1386     args.insert("buffers", buffers);
1387 
1388     QByteArray state;
1389     QDataStream out(&state, QIODevice::WriteOnly);
1390     out << args;
1391     return state;
1392 }
1393 
1394 /*!
1395     \since 3.1
1396 
1397     Restores the \a state of the model. The \a version number is compared with that stored in \a state.
1398     If they do not match, the model state is left unchanged, and this function returns \c false; otherwise,
1399     the state is restored, and \c true is returned.
1400 
1401     \sa saveState()
1402  */
restoreState(const QByteArray & state,int version)1403 bool IrcBufferModel::restoreState(const QByteArray& state, int version)
1404 {
1405     Q_D(IrcBufferModel);
1406     QVariantMap args;
1407     QDataStream in(state);
1408     in >> args;
1409     if (in.status() != QDataStream::Ok || args.value("version", -1).toInt() != version)
1410         return false;
1411 
1412     const QVariantList buffers = args.value("buffers").toList();
1413     foreach (const QVariant& v, buffers) {
1414         const QVariantMap b = v.toMap();
1415         d->bufferStates.insert(b.value("title").toString(), b);
1416     }
1417 
1418     if (d->joinDelay >= 0 && d->connection && d->connection->isConnected())
1419         QTimer::singleShot(d->joinDelay * 1000, this, SLOT(_irc_restoreBuffers()));
1420 
1421     return true;
1422 }
1423 
1424 #include "moc_ircbuffermodel.cpp"
1425 #include "moc_ircbuffermodel_p.cpp"
1426 
1427 IRC_END_NAMESPACE
1428