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 <IrcBufferModel>
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* | <object>
1179 Irc::ChannelRole | "channel" | IrcChannel* | <object>
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