modernize: Reformat ALL the source... again!
[quassel.git] / src / client / networkmodel.cpp
index dc985bd..752eb1e 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2014 by the Quassel Project                        *
+ *   Copyright (C) 2005-2018 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include "networkmodel.h"
 
+#include <utility>
+
 #include <QAbstractItemView>
 #include <QMimeData>
-#include <QTextDocument>        // for Qt::escape()
 
 #include "buffermodel.h"
 #include "buffersettings.h"
+#include "buffersyncer.h"
 #include "client.h"
 #include "clientignorelistmanager.h"
 #include "clientsettings.h"
 #include "signalproxy.h"
 
 /*****************************************
-*  Network Items
-*****************************************/
-NetworkItem::NetworkItem(const NetworkId &netid, AbstractTreeItem *parent)
-    : PropertyMapItem(QList<QString>() << "networkName" << "currentServer" << "nickCount", parent),
-    _networkId(netid),
-    _statusBufferItem(0)
+ *  Network Items
+ *****************************************/
+NetworkItem::NetworkItem(const NetworkId& netid, AbstractTreeItem* parent)
+    : PropertyMapItem(parent)
+    , _networkId(netid)
+    , _statusBufferItem(nullptr)
 {
     // DO NOT EMIT dataChanged() DIRECTLY IN NetworkItem
     // use networkDataChanged() instead. Otherwise you will end up in a infinite loop
     // as we "sync" the dataChanged() signals of NetworkItem and StatusBufferItem
     setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
-    connect(this, SIGNAL(networkDataChanged(int)), this, SIGNAL(dataChanged(int)));
-    connect(this, SIGNAL(beginRemoveChilds(int, int)), this, SLOT(onBeginRemoveChilds(int, int)));
+    connect(this, &NetworkItem::networkDataChanged, this, &NetworkItem::dataChanged);
+    connect(this, &NetworkItem::beginRemoveChilds, this, &NetworkItem::onBeginRemoveChilds);
 }
 
+QStringList NetworkItem::propertyOrder() const
+{
+    static QStringList order{"networkName", "currentServer", "nickCount"};
+    return order;
+}
 
 QVariant NetworkItem::data(int column, int role) const
 {
@@ -72,26 +79,32 @@ QVariant NetworkItem::data(int column, int role) const
     }
 }
 
+QString NetworkItem::escapeHTML(const QString& string, bool useNonbreakingSpaces)
+{
+    // QString.replace() doesn't guarantee the source string will remain constant.
+    // Use a local variable to avoid compiler errors.
+    QString formattedString = string.toHtmlEscaped();
+    return (useNonbreakingSpaces ? formattedString.replace(" ", "&nbsp;") : formattedString);
+}
 
 // FIXME shouldn't we check the bufferItemCache here?
-BufferItem *NetworkItem::findBufferItem(BufferId bufferId)
+BufferItemNetworkItem::findBufferItem(BufferId bufferId)
 {
-    BufferItem *bufferItem = 0;
+    BufferItem* bufferItem = nullptr;
 
     for (int i = 0; i < childCount(); i++) {
-        bufferItem = qobject_cast<BufferItem *>(child(i));
+        bufferItem = qobject_cast<BufferItem*>(child(i));
         if (!bufferItem)
             continue;
         if (bufferItem->bufferId() == bufferId)
             return bufferItem;
     }
-    return 0;
+    return nullptr;
 }
 
-
-BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo)
+BufferItem* NetworkItem::bufferItem(const BufferInfo& bufferInfo)
 {
-    BufferItem *bufferItem = findBufferItem(bufferInfo);
+    BufferItembufferItem = findBufferItem(bufferInfo);
     if (bufferItem)
         return bufferItem;
 
@@ -99,9 +112,9 @@ BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo)
     case BufferInfo::StatusBuffer:
         _statusBufferItem = new StatusBufferItem(bufferInfo, this);
         bufferItem = _statusBufferItem;
-        disconnect(this, SIGNAL(networkDataChanged(int)), this, SIGNAL(dataChanged(int)));
-        connect(this, SIGNAL(networkDataChanged(int)), bufferItem, SIGNAL(dataChanged(int)));
-        connect(bufferItem, SIGNAL(dataChanged(int)), this, SIGNAL(dataChanged(int)));
+        disconnect(this, &NetworkItem::networkDataChanged, this, &NetworkItem::dataChanged);
+        connect(this, &NetworkItem::networkDataChanged, bufferItem, &BufferItem::dataChanged);
+        connect(bufferItem, &BufferItem::dataChanged, this, &NetworkItem::dataChanged);
         break;
     case BufferInfo::ChannelBuffer:
         bufferItem = new ChannelBufferItem(bufferInfo, this);
@@ -117,53 +130,48 @@ BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo)
 
     // postprocess... this is necessary because Qt doesn't seem to like adding children which already have children on their own
     switch (bufferInfo.type()) {
-    case BufferInfo::ChannelBuffer:
-    {
-        ChannelBufferItem *channelBufferItem = static_cast<ChannelBufferItem *>(bufferItem);
+    case BufferInfo::ChannelBuffer: {
+        auto* channelBufferItem = static_cast<ChannelBufferItem*>(bufferItem);
         if (_network) {
-            IrcChannel *ircChannel = _network->ircChannel(bufferInfo.bufferName());
+            IrcChannelircChannel = _network->ircChannel(bufferInfo.bufferName());
             if (ircChannel)
                 channelBufferItem->attachIrcChannel(ircChannel);
         }
-    }
-    break;
+    } break;
     default:
         break;
     }
 
+    BufferSyncer* bufferSyncer = Client::bufferSyncer();
+    if (bufferSyncer) {
+        bufferItem->addActivity(bufferSyncer->activity(bufferItem->bufferId()), bufferSyncer->highlightCount(bufferItem->bufferId()) > 0);
+    }
+
     return bufferItem;
 }
 
-
-void NetworkItem::attachNetwork(Network *network)
+void NetworkItem::attachNetwork(Network* network)
 {
     if (!network)
         return;
 
     _network = network;
 
-    connect(network, SIGNAL(networkNameSet(QString)),
-        this, SLOT(setNetworkName(QString)));
-    connect(network, SIGNAL(currentServerSet(QString)),
-        this, SLOT(setCurrentServer(QString)));
-    connect(network, SIGNAL(ircChannelAdded(IrcChannel *)),
-        this, SLOT(attachIrcChannel(IrcChannel *)));
-    connect(network, SIGNAL(ircUserAdded(IrcUser *)),
-        this, SLOT(attachIrcUser(IrcUser *)));
-    connect(network, SIGNAL(connectedSet(bool)),
-        this, SIGNAL(networkDataChanged()));
-    connect(network, SIGNAL(destroyed()),
-        this, SLOT(onNetworkDestroyed()));
+    connect(network, &Network::networkNameSet, this, &NetworkItem::setNetworkName);
+    connect(network, &Network::currentServerSet, this, &NetworkItem::setCurrentServer);
+    connect(network, &Network::ircChannelAdded, this, &NetworkItem::attachIrcChannel);
+    connect(network, &Network::ircUserAdded, this, &NetworkItem::attachIrcUser);
+    connect(network, &Network::connectedSet, this, [this]() { emit networkDataChanged(); });
+    connect(network, &QObject::destroyed, this, &NetworkItem::onNetworkDestroyed);
 
     emit networkDataChanged();
 }
 
-
-void NetworkItem::attachIrcChannel(IrcChannel *ircChannel)
+void NetworkItem::attachIrcChannel(IrcChannel* ircChannel)
 {
-    ChannelBufferItem *channelItem;
+    ChannelBufferItemchannelItem;
     for (int i = 0; i < childCount(); i++) {
-        channelItem = qobject_cast<ChannelBufferItem *>(child(i));
+        channelItem = qobject_cast<ChannelBufferItem*>(child(i));
         if (!channelItem)
             continue;
 
@@ -174,12 +182,11 @@ void NetworkItem::attachIrcChannel(IrcChannel *ircChannel)
     }
 }
 
-
-void NetworkItem::attachIrcUser(IrcUser *ircUser)
+void NetworkItem::attachIrcUser(IrcUser* ircUser)
 {
-    QueryBufferItem *queryItem = 0;
+    QueryBufferItem* queryItem = nullptr;
     for (int i = 0; i < childCount(); i++) {
-        queryItem = qobject_cast<QueryBufferItem *>(child(i));
+        queryItem = qobject_cast<QueryBufferItem*>(child(i));
         if (!queryItem)
             continue;
 
@@ -190,68 +197,83 @@ void NetworkItem::attachIrcUser(IrcUser *ircUser)
     }
 }
 
-
-void NetworkItem::setNetworkName(const QString &networkName)
+void NetworkItem::setNetworkName(const QString& networkName)
 {
     Q_UNUSED(networkName);
     emit networkDataChanged(0);
 }
 
-
-void NetworkItem::setCurrentServer(const QString &serverName)
+void NetworkItem::setCurrentServer(const QString& serverName)
 {
     Q_UNUSED(serverName);
     emit networkDataChanged(1);
 }
 
-
 QString NetworkItem::toolTip(int column) const
 {
     Q_UNUSED(column);
+    QString strTooltip;
+    QTextStream tooltip(&strTooltip, QIODevice::WriteOnly);
+    tooltip << "<qt><style>.bold { font-weight: bold; } .italic { font-style: italic; }</style>";
+
+    // Function to add a row to the tooltip table
+    auto addRow = [&](const QString& key, const QString& value, bool condition) {
+        if (condition) {
+            tooltip << "<tr><td class='bold' align='right'>" << key << "</td><td>" << value << "</td></tr>";
+        }
+    };
 
-    QStringList toolTip(QString("<b>%1</b>").arg(Qt::escape(networkName())));
-    toolTip.append(tr("Server: %1").arg(Qt::escape(currentServer())));
-    toolTip.append(tr("Users: %1").arg(nickCount()));
+    tooltip << "<p class='bold' align='center'>" << NetworkItem::escapeHTML(networkName(), true) << "</p>";
+    if (isActive()) {
+        tooltip << "<table cellspacing='5' cellpadding='0'>";
+        addRow(tr("Server"), NetworkItem::escapeHTML(currentServer(), true), !currentServer().isEmpty());
+        addRow(tr("Users"), QString::number(nickCount()), true);
+        if (_network)
+            addRow(tr("Lag"), NetworkItem::escapeHTML(tr("%1 msecs").arg(_network->latency()), true), true);
 
-    if (_network) {
-        toolTip.append(tr("Lag: %1 msecs").arg(_network->latency()));
+        tooltip << "</table>";
     }
-
-    return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
+    else {
+        tooltip << "<p class='italic' align='center'>" << tr("Not connected") << "</p>";
+    }
+    tooltip << "</qt>";
+    return strTooltip;
 }
 
-
 void NetworkItem::onBeginRemoveChilds(int start, int end)
 {
     for (int i = start; i <= end; i++) {
-        StatusBufferItem *statusBufferItem = qobject_cast<StatusBufferItem *>(child(i));
+        auto* statusBufferItem = qobject_cast<StatusBufferItem*>(child(i));
         if (statusBufferItem) {
-            _statusBufferItem = 0;
+            _statusBufferItem = nullptr;
             break;
         }
     }
 }
 
-
 void NetworkItem::onNetworkDestroyed()
 {
-    _network = 0;
+    _network = nullptr;
     emit networkDataChanged();
     removeAllChilds();
 }
 
-
 /*****************************************
-*  Fancy Buffer Items
-*****************************************/
-BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
-    : PropertyMapItem(QStringList() << "bufferName" << "topic" << "nickCount", parent),
-    _bufferInfo(bufferInfo),
-    _activity(BufferInfo::NoActivity)
+ *  Fancy Buffer Items
+ *****************************************/
+BufferItem::BufferItem(BufferInfo bufferInfo, AbstractTreeItem* parent)
+    : PropertyMapItem(parent)
+    , _bufferInfo(std::move(bufferInfo))
+    _activity(BufferInfo::NoActivity)
 {
     setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
 }
 
+QStringList BufferItem::propertyOrder() const
+{
+    static QStringList order{"bufferName", "topic", "nickCount"};
+    return order;
+}
 
 void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level)
 {
@@ -261,32 +283,40 @@ void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level)
     }
 }
 
-
 void BufferItem::clearActivityLevel()
 {
-    _activity = BufferInfo::NoActivity;
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) {
+        // If the core handles activity sync, clear only the highlight flag
+        _activity &= ~BufferInfo::Highlight;
+    }
+    else {
+        _activity = BufferInfo::NoActivity;
+    }
     _firstUnreadMsgId = MsgId();
 
     // FIXME remove with core proto v11
-    if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
         _markerLineMsgId = _lastSeenMsgId;
     }
 
     emit dataChanged();
 }
 
-
-void BufferItem::updateActivityLevel(const Message &msg)
+void BufferItem::updateActivityLevel(const Message& msg)
 {
+    // If the core handles activity, and this message is not a highlight, ignore this
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync) && !msg.flags().testFlag(Message::Highlight)) {
+        return;
+    }
+
     if (isCurrentBuffer()) {
         return;
     }
 
-    if (msg.flags() & Message::Self)    // don't update activity for our own messages
+    if (msg.flags() & Message::Self)  // don't update activity for our own messages
         return;
 
-    if (Client::ignoreListManager()
-        && Client::ignoreListManager()->match(msg, qobject_cast<NetworkItem *>(parent())->networkName()))
+    if (Client::ignoreListManager() && Client::ignoreListManager()->match(msg, qobject_cast<NetworkItem*>(parent())->networkName()))
         return;
 
     if (msg.msgId() <= lastSeenMsgId())
@@ -298,21 +328,47 @@ void BufferItem::updateActivityLevel(const Message &msg)
         _firstUnreadMsgId = msg.msgId();
     }
 
-    BufferInfo::ActivityLevel oldLevel = activityLevel();
+    Message::Types type;
+    // If the core handles activities, ignore types
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) {
+        type = Message::Types();
+    }
+    else {
+        type = msg.type();
+    }
 
-    _activity |= BufferInfo::OtherActivity;
-    if (msg.type() & (Message::Plain | Message::Notice | Message::Action))
-        _activity |= BufferInfo::NewMessage;
+    if (addActivity(type, msg.flags().testFlag(Message::Highlight)) || stateChanged) {
+        emit dataChanged();
+    }
+}
 
-    if (msg.flags() & Message::Highlight)
-        _activity |= BufferInfo::Highlight;
+void BufferItem::setActivity(Message::Types type, bool highlight)
+{
+    BufferInfo::ActivityLevel oldLevel = activityLevel();
 
-    stateChanged |= (oldLevel != _activity);
+    _activity &= BufferInfo::Highlight;
+    addActivity(type, highlight);
 
-    if (stateChanged)
+    if (_activity != oldLevel) {
         emit dataChanged();
+    }
 }
 
+bool BufferItem::addActivity(Message::Types type, bool highlight)
+{
+    auto oldActivity = activityLevel();
+
+    if (type != Message::Types())
+        _activity |= BufferInfo::OtherActivity;
+
+    if (type.testFlag(Message::Plain) || type.testFlag(Message::Notice) || type.testFlag(Message::Action))
+        _activity |= BufferInfo::NewMessage;
+
+    if (highlight)
+        _activity |= BufferInfo::Highlight;
+
+    return oldActivity != _activity;
+}
 
 QVariant BufferItem::data(int column, int role) const
 {
@@ -340,8 +396,7 @@ QVariant BufferItem::data(int column, int role) const
     }
 }
 
-
-bool BufferItem::setData(int column, const QVariant &value, int role)
+bool BufferItem::setData(int column, const QVariant& value, int role)
 {
     switch (role) {
     case NetworkModel::BufferActivityRole:
@@ -353,20 +408,18 @@ bool BufferItem::setData(int column, const QVariant &value, int role)
     return true;
 }
 
-
-void BufferItem::setBufferName(const QString &name)
+void BufferItem::setBufferName(const QString& name)
 {
     _bufferInfo = BufferInfo(_bufferInfo.bufferId(), _bufferInfo.networkId(), _bufferInfo.type(), _bufferInfo.groupId(), name);
     emit dataChanged(0);
 }
 
-
 void BufferItem::setLastSeenMsgId(MsgId msgId)
 {
     _lastSeenMsgId = msgId;
 
     // FIXME remove with core protocol v11
-    if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
         if (!isCurrentBuffer())
             _markerLineMsgId = msgId;
     }
@@ -374,71 +427,63 @@ void BufferItem::setLastSeenMsgId(MsgId msgId)
     setActivityLevel(BufferInfo::NoActivity);
 }
 
-
 void BufferItem::setMarkerLineMsgId(MsgId msgId)
 {
     _markerLineMsgId = msgId;
     emit dataChanged();
 }
 
-
 bool BufferItem::isCurrentBuffer() const
 {
     return _bufferInfo.bufferId() == Client::bufferModel()->currentIndex().data(NetworkModel::BufferIdRole).value<BufferId>();
 }
 
-
 QString BufferItem::toolTip(int column) const
 {
     Q_UNUSED(column);
     return tr("<p> %1 - %2 </p>").arg(bufferInfo().bufferId().toInt()).arg(bufferName());
 }
 
-
 /*****************************************
-*  StatusBufferItem
-*****************************************/
-StatusBufferItem::StatusBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
+ *  StatusBufferItem
+ *****************************************/
+StatusBufferItem::StatusBufferItem(const BufferInfo& bufferInfo, NetworkItem* parent)
     : BufferItem(bufferInfo, parent)
-{
-}
-
+{}
 
 QString StatusBufferItem::toolTip(int column) const
 {
-    NetworkItem *networkItem = qobject_cast<NetworkItem *>(parent());
+    auto* networkItem = qobject_cast<NetworkItem*>(parent());
     if (networkItem)
         return networkItem->toolTip(column);
     else
         return QString();
 }
 
-
 /*****************************************
-*  QueryBufferItem
-*****************************************/
-QueryBufferItem::QueryBufferItem(const BufferInfo &bufferInfo, NetworkItem *parent)
-    : BufferItem(bufferInfo, parent),
-    _ircUser(0)
+ *  QueryBufferItem
+ *****************************************/
+QueryBufferItem::QueryBufferItem(const BufferInfo& bufferInfo, NetworkItem* parent)
+    : BufferItem(bufferInfo, parent)
+    , _ircUser(nullptr)
 {
     setFlags(flags() | Qt::ItemIsDropEnabled | Qt::ItemIsEditable);
 
-    const Network *net = Client::network(bufferInfo.networkId());
+    const Networknet = Client::network(bufferInfo.networkId());
     if (!net)
         return;
 
-    IrcUser *ircUser = net->ircUser(bufferInfo.bufferName());
+    IrcUserircUser = net->ircUser(bufferInfo.bufferName());
     setIrcUser(ircUser);
 }
 
-
 QVariant QueryBufferItem::data(int column, int role) const
 {
     switch (role) {
     case Qt::EditRole:
         return BufferItem::data(column, Qt::DisplayRole);
     case NetworkModel::IrcUserRole:
-        return QVariant::fromValue<QObject *>(_ircUser);
+        return QVariant::fromValue<QObject*>(_ircUser);
     case NetworkModel::UserAwayRole:
         return (bool)_ircUser ? _ircUser->isAway() : false;
     default:
@@ -446,16 +491,20 @@ QVariant QueryBufferItem::data(int column, int role) const
     }
 }
 
-
-bool QueryBufferItem::setData(int column, const QVariant &value, int role)
+bool QueryBufferItem::setData(int column, const QVariant& value, int role)
 {
     if (column != 0)
         return BufferItem::setData(column, value, role);
 
     switch (role) {
-    case Qt::EditRole:
-    {
+    case Qt::EditRole: {
         QString newName = value.toString();
+
+        // Sanity check - buffer names must not contain newlines!
+        int nlpos = newName.indexOf('\n');
+        if (nlpos >= 0)
+            newName = newName.left(nlpos);
+
         if (!newName.isEmpty()) {
             Client::renameBuffer(bufferId(), newName);
             return true;
@@ -463,123 +512,221 @@ bool QueryBufferItem::setData(int column, const QVariant &value, int role)
         else {
             return false;
         }
-    }
-    break;
+    } break;
     default:
         return BufferItem::setData(column, value, role);
     }
 }
 
-
-void QueryBufferItem::setBufferName(const QString &name)
+void QueryBufferItem::setBufferName(const QString& name)
 {
     BufferItem::setBufferName(name);
     NetworkId netId = data(0, NetworkModel::NetworkIdRole).value<NetworkId>();
-    const Network *net = Client::network(netId);
+    const Networknet = Client::network(netId);
     if (net)
         setIrcUser(net->ircUser(name));
 }
 
-
 QString QueryBufferItem::toolTip(int column) const
 {
     // pretty much code duplication of IrcUserItem::toolTip() but inheritance won't solve this...
     Q_UNUSED(column);
-    QStringList toolTip;
+    QString strTooltip;
+    QTextStream tooltip(&strTooltip, QIODevice::WriteOnly);
+    tooltip << "<qt><style>.bold { font-weight: bold; } .italic { font-style: italic; }</style>";
 
-    toolTip.append(tr("<b>Query with %1</b>").arg(bufferName()));
+    // Keep track of whether or not information has been added
+    bool infoAdded = false;
 
-    if (_ircUser) {
-        if (_ircUser->userModes() != "") toolTip[0].append(QString(" (+%1)").arg(_ircUser->userModes()));
+    // Use bufferName() for QueryBufferItem, nickName() for IrcUserItem
+    tooltip << "<p class='bold' align='center'>";
+    tooltip << tr("Query with %1").arg(NetworkItem::escapeHTML(bufferName(), true));
+    if (!_ircUser) {
+        // User seems to be offline, let the no information message be added below
+        tooltip << "</p>";
+    }
+    else {
+        // Function to add a row to the tooltip table
+        auto addRow = [&](const QString& key, const QString& value, bool condition) {
+            if (condition) {
+                tooltip << "<tr><td class='bold' align='right'>" << key << "</td><td>" << value << "</td></tr>";
+                infoAdded = true;
+            }
+        };
+
+        // User information is available
+        if (_ircUser->userModes() != "") {
+            // TODO Translate user Modes and add them to the table below and in IrcUserItem::toolTip
+            tooltip << " (" << _ircUser->userModes() << ")";
+        }
+        tooltip << "</p>";
+
+        tooltip << "<table cellspacing='5' cellpadding='0'>";
         if (_ircUser->isAway()) {
-            toolTip[0].append(QString(" (away%1)").arg(!_ircUser->awayMessage().isEmpty() ? (QString(" ") + _ircUser->awayMessage()) : QString()));
+            QString awayMessageHTML = QString("<p class='italic'>%1</p>").arg(tr("Unknown"));
+
+            // If away message is known, replace with the escaped message.
+            if (!_ircUser->awayMessage().isEmpty()) {
+                awayMessageHTML = NetworkItem::escapeHTML(_ircUser->awayMessage());
+            }
+            addRow(NetworkItem::escapeHTML(tr("Away message"), true), awayMessageHTML, true);
+        }
+        addRow(tr("Realname"), NetworkItem::escapeHTML(_ircUser->realName()), !_ircUser->realName().isEmpty());
+        // suserHost may return "<nick> is available for help", which should be translated.
+        // See https://www.alien.net.au/irc/irc2numerics.html
+        if (_ircUser->suserHost().endsWith("available for help")) {
+            addRow(NetworkItem::escapeHTML(tr("Help status"), true), NetworkItem::escapeHTML(tr("Available for help")), true);
+        }
+        else {
+            addRow(NetworkItem::escapeHTML(tr("Service status"), true),
+                   NetworkItem::escapeHTML(_ircUser->suserHost()),
+                   !_ircUser->suserHost().isEmpty());
         }
-        if (!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
-        if (!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(_ircUser->nick()).arg(_ircUser->ircOperator()));
-        if (!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
-        if (!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
 
-        toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
+        // Keep track of whether or not the account information's been added.  Don't show it twice.
+        bool accountAdded = false;
+        if (!_ircUser->account().isEmpty()) {
+            // IRCv3 account-notify is supported by the core and IRC server.
+            // Assume logged out (seems to be more common)
+            QString accountHTML = QString("<p class='italic'>%1</p>").arg(tr("Not logged in"));
+
+            // If account is logged in, replace with the escaped account name.
+            if (_ircUser->account() != "*") {
+                accountHTML = NetworkItem::escapeHTML(_ircUser->account());
+            }
+            addRow(NetworkItem::escapeHTML(tr("Account"), true), accountHTML, true);
+            // Mark the row as added
+            accountAdded = true;
+        }
+        // whoisServiceReply may return "<nick> is identified for this nick", which should be translated.
+        // See https://www.alien.net.au/irc/irc2numerics.html
+        if (_ircUser->whoisServiceReply().endsWith("identified for this nick")) {
+            addRow(NetworkItem::escapeHTML(tr("Account"), true), NetworkItem::escapeHTML(tr("Identified for this nick")), !accountAdded);
+            // Don't add the account row again if information's already added via account-notify
+            // Not used further down...
+            // accountAdded = true;
+        }
+        else {
+            addRow(NetworkItem::escapeHTML(tr("Service Reply"), true),
+                   NetworkItem::escapeHTML(_ircUser->whoisServiceReply()),
+                   !_ircUser->whoisServiceReply().isEmpty());
+        }
+        addRow(tr("Hostmask"),
+               NetworkItem::escapeHTML(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!") + 1)),
+               !(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!") + 1) == "@"));
+        // ircOperator may contain "is an" or "is a", which should be removed.
+        addRow(tr("Operator"),
+               NetworkItem::escapeHTML(_ircUser->ircOperator().replace("is an ", "").replace("is a ", "")),
+               !_ircUser->ircOperator().isEmpty());
 
         if (_ircUser->idleTime().isValid()) {
             QDateTime now = QDateTime::currentDateTime();
             QDateTime idle = _ircUser->idleTime();
             int idleTime = idle.secsTo(now);
-            toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
+            addRow(NetworkItem::escapeHTML(tr("Idling since"), true), secondsToString(idleTime), true);
         }
+
         if (_ircUser->loginTime().isValid()) {
-            toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
+            addRow(NetworkItem::escapeHTML(tr("Login time"), true), _ircUser->loginTime().toString(), true);
         }
 
-        if (!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
+        addRow(tr("Server"), NetworkItem::escapeHTML(_ircUser->server()), !_ircUser->server().isEmpty());
+        tooltip << "</table>";
     }
 
-    return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
-}
+    // If no further information found, offer an explanatory message
+    if (!infoAdded)
+        tooltip << "<p class='italic' align='center'>" << tr("No information available") << "</p>";
 
+    tooltip << "</qt>";
+    return strTooltip;
+}
 
-void QueryBufferItem::setIrcUser(IrcUser *ircUser)
+void QueryBufferItem::setIrcUser(IrcUserircUser)
 {
     if (_ircUser == ircUser)
         return;
 
     if (_ircUser) {
-        disconnect(_ircUser, 0, this, 0);
+        disconnect(_ircUser, nullptr, this, nullptr);
     }
 
     if (ircUser) {
-        connect(ircUser, SIGNAL(destroyed(QObject*)), SLOT(removeIrcUser()));
-        connect(ircUser, SIGNAL(quited()), this, SLOT(removeIrcUser()));
-        connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
-        connect(ircUser, SIGNAL(encryptedSet(bool)), this, SLOT(setEncrypted(bool)));
+        connect(ircUser, &IrcUser::destroyed, this, &QueryBufferItem::removeIrcUser);
+        connect(ircUser, &IrcUser::quited, this, &QueryBufferItem::removeIrcUser);
+        connect(ircUser, &IrcUser::awaySet, this, [this]() { emit dataChanged(); });
+        connect(ircUser, &IrcUser::encryptedSet, this, &BufferItem::setEncrypted);
     }
 
     _ircUser = ircUser;
     emit dataChanged();
 }
 
-
 void QueryBufferItem::removeIrcUser()
 {
-    _ircUser = 0;
-    emit dataChanged();
+    if (_ircUser) {
+        // Disconnect the active IrcUser before removing it, otherwise it will fire removeIrcUser()
+        // a second time when the object's destroyed due to QueryBufferItem::setIrcUser() connecting
+        // SIGNAL destroyed(QObject*) to SLOT removeIrcUser().
+        // This fixes removing an active IrcUser if the user had quit then rejoined in a nonstandard
+        // manner (e.g. updateNickFromHost calling newIrcUser, triggered by an away-notify message).
+        disconnect(_ircUser, nullptr, this, nullptr);
+
+        // Clear IrcUser (only set to 0 if not already 0)
+        _ircUser = nullptr;
+
+        // Only emit dataChanged() if data actually changed.  This might serve as a small
+        // optimization, but it can be moved outside the if statement if other behavior depends on
+        // it always being called.
+        emit dataChanged();
+    }
 }
 
-
 /*****************************************
-*  ChannelBufferItem
-*****************************************/
-ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent)
-    : BufferItem(bufferInfo, parent),
-    _ircChannel(0)
+ *  ChannelBufferItem
+ *****************************************/
+ChannelBufferItem::ChannelBufferItem(const BufferInfo& bufferInfo, AbstractTreeItem* parent)
+    : BufferItem(bufferInfo, parent)
+    , _ircChannel(nullptr)
 {
+    setFlags(flags() | Qt::ItemIsDropEnabled);
 }
 
-
 QVariant ChannelBufferItem::data(int column, int role) const
 {
     switch (role) {
     case NetworkModel::IrcChannelRole:
-        return QVariant::fromValue<QObject *>(_ircChannel);
+        return QVariant::fromValue<QObject*>(_ircChannel);
     default:
         return BufferItem::data(column, role);
     }
 }
 
-
 QString ChannelBufferItem::toolTip(int column) const
 {
     Q_UNUSED(column);
-    QStringList toolTip;
+    QString strTooltip;
+    QTextStream tooltip(&strTooltip, QIODevice::WriteOnly);
+    tooltip << "<qt><style>.bold { font-weight: bold; } .italic { font-style: italic; }</style>";
+
+    // Function to add a row to the tooltip table
+    auto addRow = [&](const QString& key, const QString& value, bool condition) {
+        if (condition) {
+            tooltip << "<tr><td class='bold' align='right'>" << key << "</td><td>" << value << "</td></tr>";
+        }
+    };
+
+    tooltip << "<p class='bold' align='center'>";
+    tooltip << NetworkItem::escapeHTML(tr("Channel %1").arg(bufferName()), true) << "</p>";
 
-    toolTip.append(tr("<b>Channel %1</b>").arg(Qt::escape(bufferName())));
     if (isActive()) {
-        //TODO: add channel modes
-        toolTip.append(tr("<b>Users:</b> %1").arg(nickCount()));
+        tooltip << "<table cellspacing='5' cellpadding='0'>";
+        addRow(tr("Users"), QString::number(nickCount()), true);
+
         if (_ircChannel) {
-            QString channelMode = _ircChannel->channelModeString(); // channelModeString is compiled on the fly -> thus cache the result
+            QString channelMode = _ircChannel->channelModeString();  // channelModeString is compiled on the fly -> thus cache the result
             if (!channelMode.isEmpty())
-                toolTip.append(tr("<b>Mode:</b> %1").arg(channelMode));
+                addRow(tr("Mode"), channelMode, true);
         }
 
         ItemViewSettings s;
@@ -588,47 +735,39 @@ QString ChannelBufferItem::toolTip(int column) const
             QString _topic = topic();
             if (_topic != "") {
                 _topic = stripFormatCodes(_topic);
-                _topic = Qt::escape(_topic);
-                toolTip.append(QString("<font size='-2'>&nbsp;</font>"));
-                toolTip.append(tr("<b>Topic:</b> %1").arg(_topic));
+                _topic = NetworkItem::escapeHTML(_topic);
+                addRow(tr("Topic"), _topic, true);
             }
         }
+
+        tooltip << "</table>";
     }
     else {
-        toolTip.append(tr("Not active <br /> Double-click to join"));
+        tooltip << "<p class='italic' align='center'>" << tr("Not active, double-click to join") << "</p>";
     }
 
-    return tr("<p> %1 </p>").arg(toolTip.join("<br />"));
+    tooltip << "</qt>";
+    return strTooltip;
 }
 
-
-void ChannelBufferItem::attachIrcChannel(IrcChannel *ircChannel)
+void ChannelBufferItem::attachIrcChannel(IrcChannel* ircChannel)
 {
     if (_ircChannel) {
         qWarning() << Q_FUNC_INFO << "IrcChannel already set; cleanup failed!?";
-        disconnect(_ircChannel, 0, this, 0);
+        disconnect(_ircChannel, nullptr, this, nullptr);
     }
 
     _ircChannel = ircChannel;
 
-    connect(ircChannel, SIGNAL(destroyed(QObject*)),
-        this, SLOT(ircChannelDestroyed()));
-    connect(ircChannel, SIGNAL(topicSet(QString)),
-        this, SLOT(setTopic(QString)));
-    connect(ircChannel, SIGNAL(encryptedSet(bool)),
-        this, SLOT(setEncrypted(bool)));
-    connect(ircChannel, SIGNAL(ircUsersJoined(QList<IrcUser *> )),
-        this, SLOT(join(QList<IrcUser *> )));
-    connect(ircChannel, SIGNAL(ircUserParted(IrcUser *)),
-        this, SLOT(part(IrcUser *)));
-    connect(ircChannel, SIGNAL(parted()),
-        this, SLOT(ircChannelParted()));
-    connect(ircChannel, SIGNAL(ircUserModesSet(IrcUser *, QString)),
-        this, SLOT(userModeChanged(IrcUser *)));
-    connect(ircChannel, SIGNAL(ircUserModeAdded(IrcUser *, QString)),
-        this, SLOT(userModeChanged(IrcUser *)));
-    connect(ircChannel, SIGNAL(ircUserModeRemoved(IrcUser *, QString)),
-        this, SLOT(userModeChanged(IrcUser *)));
+    connect(ircChannel, &QObject::destroyed, this, &ChannelBufferItem::ircChannelDestroyed);
+    connect(ircChannel, &IrcChannel::topicSet, this, &ChannelBufferItem::setTopic);
+    connect(ircChannel, &IrcChannel::encryptedSet, this, &ChannelBufferItem::setEncrypted);
+    connect(ircChannel, &IrcChannel::ircUsersJoined, this, &ChannelBufferItem::join);
+    connect(ircChannel, &IrcChannel::ircUserParted, this, &ChannelBufferItem::part);
+    connect(ircChannel, &IrcChannel::parted, this, &ChannelBufferItem::ircChannelParted);
+    connect(ircChannel, &IrcChannel::ircUserModesSet, this, &ChannelBufferItem::userModeChanged);
+    connect(ircChannel, &IrcChannel::ircUserModeAdded, this, &ChannelBufferItem::userModeChanged);
+    connect(ircChannel, &IrcChannel::ircUserModeRemoved, this, &ChannelBufferItem::userModeChanged);
 
     if (!ircChannel->ircUsers().isEmpty())
         join(ircChannel->ircUsers());
@@ -636,97 +775,99 @@ void ChannelBufferItem::attachIrcChannel(IrcChannel *ircChannel)
     emit dataChanged();
 }
 
+QString ChannelBufferItem::nickChannelModes(const QString& nick) const
+{
+    if (!_ircChannel) {
+        qDebug() << Q_FUNC_INFO << "IrcChannel not set, can't get user modes";
+        return QString();
+    }
+
+    return _ircChannel->userModes(nick);
+}
 
 void ChannelBufferItem::ircChannelParted()
 {
     Q_CHECK_PTR(_ircChannel);
-    disconnect(_ircChannel, 0, this, 0);
-    _ircChannel = 0;
+    disconnect(_ircChannel, nullptr, this, nullptr);
+    _ircChannel = nullptr;
     emit dataChanged();
     removeAllChilds();
 }
 
-
 void ChannelBufferItem::ircChannelDestroyed()
 {
     if (_ircChannel) {
-        _ircChannel = 0;
+        _ircChannel = nullptr;
         emit dataChanged();
         removeAllChilds();
     }
 }
 
-
-void ChannelBufferItem::join(const QList<IrcUser *> &ircUsers)
+void ChannelBufferItem::join(const QList<IrcUser*>& ircUsers)
 {
     addUsersToCategory(ircUsers);
     emit dataChanged(2);
 }
 
-
-UserCategoryItem *ChannelBufferItem::findCategoryItem(int categoryId)
+UserCategoryItem* ChannelBufferItem::findCategoryItem(int categoryId)
 {
-    UserCategoryItem *categoryItem = 0;
+    UserCategoryItem* categoryItem = nullptr;
 
     for (int i = 0; i < childCount(); i++) {
-        categoryItem = qobject_cast<UserCategoryItem *>(child(i));
+        categoryItem = qobject_cast<UserCategoryItem*>(child(i));
         if (!categoryItem)
             continue;
         if (categoryItem->categoryId() == categoryId)
             return categoryItem;
     }
-    return 0;
+    return nullptr;
 }
 
-
-void ChannelBufferItem::addUserToCategory(IrcUser *ircUser)
+void ChannelBufferItem::addUserToCategory(IrcUser* ircUser)
 {
-    addUsersToCategory(QList<IrcUser *>() << ircUser);
+    addUsersToCategory(QList<IrcUser*>() << ircUser);
 }
 
-
-void ChannelBufferItem::addUsersToCategory(const QList<IrcUser *> &ircUsers)
+void ChannelBufferItem::addUsersToCategory(const QList<IrcUser*>& ircUsers)
 {
     Q_ASSERT(_ircChannel);
 
-    QHash<UserCategoryItem *, QList<IrcUser *> > categories;
+    QHash<UserCategoryItem*, QList<IrcUser*>> categories;
 
     int categoryId = -1;
-    UserCategoryItem *categoryItem = 0;
+    UserCategoryItem* categoryItem = nullptr;
 
-    foreach(IrcUser *ircUser, ircUsers) {
+    foreach (IrcUser* ircUser, ircUsers) {
         categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
         categoryItem = findCategoryItem(categoryId);
         if (!categoryItem) {
             categoryItem = new UserCategoryItem(categoryId, this);
-            categories[categoryItem] = QList<IrcUser *>();
+            categories[categoryItem] = QList<IrcUser*>();
             newChild(categoryItem);
         }
         categories[categoryItem] << ircUser;
     }
 
-    QHash<UserCategoryItem *, QList<IrcUser *> >::const_iterator catIter = categories.constBegin();
+    QHash<UserCategoryItem*, QList<IrcUser*>>::const_iterator catIter = categories.constBegin();
     while (catIter != categories.constEnd()) {
         catIter.key()->addUsers(catIter.value());
-        catIter++;
+        ++catIter;
     }
 }
 
-
-void ChannelBufferItem::part(IrcUser *ircUser)
+void ChannelBufferItem::part(IrcUser* ircUser)
 {
     if (!ircUser) {
         qWarning() << bufferName() << "ChannelBufferItem::part(): unknown User" << ircUser;
         return;
     }
 
-    disconnect(ircUser, 0, this, 0);
+    disconnect(ircUser, nullptr, this, nullptr);
     removeUserFromCategory(ircUser);
     emit dataChanged(2);
 }
 
-
-void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser)
+void ChannelBufferItem::removeUserFromCategory(IrcUser* ircUser)
 {
     if (!_ircChannel) {
         // If we parted the channel there might still be some ircUsers connected.
@@ -735,9 +876,9 @@ void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser)
         return;
     }
 
-    UserCategoryItem *categoryItem = 0;
+    UserCategoryItem* categoryItem = nullptr;
     for (int i = 0; i < childCount(); i++) {
-        categoryItem = qobject_cast<UserCategoryItem *>(child(i));
+        categoryItem = qobject_cast<UserCategoryItem*>(child(i));
         if (categoryItem->removeUser(ircUser)) {
             if (categoryItem->childCount() == 0)
                 removeChild(i);
@@ -746,17 +887,16 @@ void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser)
     }
 }
 
-
-void ChannelBufferItem::userModeChanged(IrcUser *ircUser)
+void ChannelBufferItem::userModeChanged(IrcUser* ircUser)
 {
     Q_ASSERT(_ircChannel);
 
     int categoryId = UserCategoryItem::categoryFromModes(_ircChannel->userModes(ircUser));
-    UserCategoryItem *categoryItem = findCategoryItem(categoryId);
+    UserCategoryItemcategoryItem = findCategoryItem(categoryId);
 
     if (categoryItem) {
         if (categoryItem->findIrcUser(ircUser)) {
-            return; // already in the right category;
+            return;  // already in the right category;
         }
     }
     else {
@@ -765,11 +905,11 @@ void ChannelBufferItem::userModeChanged(IrcUser *ircUser)
     }
 
     // find the item that needs reparenting
-    IrcUserItem *ircUserItem = 0;
+    IrcUserItem* ircUserItem = nullptr;
     for (int i = 0; i < childCount(); i++) {
-        UserCategoryItem *oldCategoryItem = qobject_cast<UserCategoryItem *>(child(i));
+        auto* oldCategoryItem = qobject_cast<UserCategoryItem*>(child(i));
         Q_ASSERT(oldCategoryItem);
-        IrcUserItem *userItem = oldCategoryItem->findIrcUser(ircUser);
+        IrcUserItemuserItem = oldCategoryItem->findIrcUser(ircUser);
         if (userItem) {
             ircUserItem = userItem;
             break;
@@ -783,23 +923,27 @@ void ChannelBufferItem::userModeChanged(IrcUser *ircUser)
     ircUserItem->reParent(categoryItem);
 }
 
-
 /*****************************************
-*  User Category Items (like @vh etc.)
-*****************************************/
+ *  User Category Items (like @vh etc.)
+ *****************************************/
 // we hardcode this even though we have PREFIX in network... but that wouldn't help with mapping modes to
 // category strings anyway.
 const QList<QChar> UserCategoryItem::categories = QList<QChar>() << 'q' << 'a' << 'o' << 'h' << 'v';
 
-UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent)
-    : PropertyMapItem(QStringList() << "categoryName", parent),
-    _category(category)
+UserCategoryItem::UserCategoryItem(int category, AbstractTreeItemparent)
+    : PropertyMapItem(parent)
+    _category(category)
 {
     setFlags(Qt::ItemIsEnabled);
     setTreeItemFlags(AbstractTreeItem::DeleteOnLastChildRemoved);
     setObjectName(parent->data(0, Qt::DisplayRole).toString() + "/" + QString::number(category));
 }
 
+QStringList UserCategoryItem::propertyOrder() const
+{
+    static QStringList order{"categoryName"};
+    return order;
+}
 
 // caching this makes no sense, since we display the user number dynamically
 QString UserCategoryItem::categoryName() const
@@ -807,50 +951,47 @@ QString UserCategoryItem::categoryName() const
     int n = childCount();
     switch (_category) {
     case 0:
-        return tr("%n Owner(s)", 0, n);
+        return tr("%n Owner(s)", "", n);
     case 1:
-        return tr("%n Admin(s)", 0, n);
+        return tr("%n Admin(s)", "", n);
     case 2:
-        return tr("%n Operator(s)", 0, n);
+        return tr("%n Operator(s)", "", n);
     case 3:
-        return tr("%n Half-Op(s)", 0, n);
+        return tr("%n Half-Op(s)", "", n);
     case 4:
-        return tr("%n Voiced", 0, n);
+        return tr("%n Voiced", "", n);
     default:
-        return tr("%n User(s)", 0, n);
+        return tr("%n User(s)", "", n);
     }
 }
 
-
-IrcUserItem *UserCategoryItem::findIrcUser(IrcUser *ircUser)
+IrcUserItem* UserCategoryItem::findIrcUser(IrcUser* ircUser)
 {
-    IrcUserItem *userItem = 0;
+    IrcUserItem* userItem = nullptr;
 
     for (int i = 0; i < childCount(); i++) {
-        userItem = qobject_cast<IrcUserItem *>(child(i));
+        userItem = qobject_cast<IrcUserItem*>(child(i));
         if (!userItem)
             continue;
         if (userItem->ircUser() == ircUser)
             return userItem;
     }
-    return 0;
+    return nullptr;
 }
 
-
-void UserCategoryItem::addUsers(const QList<IrcUser *> &ircUsers)
+void UserCategoryItem::addUsers(const QList<IrcUser*>& ircUsers)
 {
-    QList<AbstractTreeItem *> userItems;
-    foreach(IrcUser *ircUser, ircUsers)
-    userItems << new IrcUserItem(ircUser, this);
+    QList<AbstractTreeItem*> userItems;
+    foreach (IrcUser* ircUser, ircUsers)
+        userItems << new IrcUserItem(ircUser, this);
     newChilds(userItems);
     emit dataChanged(0);
 }
 
-
-bool UserCategoryItem::removeUser(IrcUser *ircUser)
+bool UserCategoryItem::removeUser(IrcUser* ircUser)
 {
-    IrcUserItem *userItem = findIrcUser(ircUser);
-    bool success = (bool)userItem;
+    IrcUserItemuserItem = findIrcUser(ircUser);
+    auto success = (bool)userItem;
     if (success) {
         removeChild(userItem);
         emit dataChanged(0);
@@ -858,8 +999,7 @@ bool UserCategoryItem::removeUser(IrcUser *ircUser)
     return success;
 }
 
-
-int UserCategoryItem::categoryFromModes(const QString &modes)
+int UserCategoryItem::categoryFromModes(const QString& modes)
 {
     for (int i = 0; i < categories.count(); i++) {
         if (modes.contains(categories[i]))
@@ -868,7 +1008,6 @@ int UserCategoryItem::categoryFromModes(const QString &modes)
     return categories.count();
 }
 
-
 QVariant UserCategoryItem::data(int column, int role) const
 {
     switch (role) {
@@ -889,20 +1028,24 @@ QVariant UserCategoryItem::data(int column, int role) const
     }
 }
 
-
 /*****************************************
-*  Irc User Items
-*****************************************/
-IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent)
-    : PropertyMapItem(QStringList() << "nickName", parent),
-    _ircUser(ircUser)
+ *  Irc User Items
+ *****************************************/
+IrcUserItem::IrcUserItem(IrcUser* ircUser, AbstractTreeItem* parent)
+    : PropertyMapItem(parent)
+    _ircUser(ircUser)
 {
     setObjectName(ircUser->nick());
-    connect(ircUser, SIGNAL(quited()), this, SLOT(ircUserQuited()));
-    connect(ircUser, SIGNAL(nickSet(QString)), this, SIGNAL(dataChanged()));
-    connect(ircUser, SIGNAL(awaySet(bool)), this, SIGNAL(dataChanged()));
+    connect(ircUser, &IrcUser::quited, this, &IrcUserItem::ircUserQuited);
+    connect(ircUser, &IrcUser::nickSet, this, [this]() { emit dataChanged(); });
+    connect(ircUser, &IrcUser::awaySet, this, [this]() { emit dataChanged(); });
 }
 
+QStringList IrcUserItem::propertyOrder() const
+{
+    static QStringList order{"nickName"};
+    return order;
+}
 
 QVariant IrcUserItem::data(int column, int role) const
 {
@@ -920,7 +1063,7 @@ QVariant IrcUserItem::data(int column, int role) const
     case NetworkModel::IrcChannelRole:
         return parent()->data(column, role);
     case NetworkModel::IrcUserRole:
-        return QVariant::fromValue<QObject *>(_ircUser.data());
+        return QVariant::fromValue<QObject*>(_ircUser.data());
     case NetworkModel::UserAwayRole:
         return (bool)_ircUser ? _ircUser->isAway() : false;
     default:
@@ -928,59 +1071,144 @@ QVariant IrcUserItem::data(int column, int role) const
     }
 }
 
-
 QString IrcUserItem::toolTip(int column) const
 {
     Q_UNUSED(column);
-    QStringList toolTip(QString("<b>%1</b>").arg(nickName()));
-    if (_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes()));
+    QString strTooltip;
+    QTextStream tooltip(&strTooltip, QIODevice::WriteOnly);
+    tooltip << "<qt><style>.bold { font-weight: bold; } .italic { font-style: italic; }</style>";
+
+    // Keep track of whether or not information has been added
+    bool infoAdded = false;
+
+    // Use bufferName() for QueryBufferItem, nickName() for IrcUserItem
+    tooltip << "<p class='bold' align='center'>" << NetworkItem::escapeHTML(nickName(), true);
+    if (_ircUser->userModes() != "") {
+        // TODO: Translate user Modes and add them to the table below and in QueryBufferItem::toolTip
+        tooltip << " (" << _ircUser->userModes() << ")";
+    }
+    tooltip << "</p>";
+
+    auto addRow = [&](const QString& key, const QString& value, bool condition) {
+        if (condition) {
+            tooltip << "<tr><td class='bold' align='right'>" << key << "</td><td>" << value << "</td></tr>";
+            infoAdded = true;
+        }
+    };
+
+    tooltip << "<table cellspacing='5' cellpadding='0'>";
+    addRow(tr("Modes"), NetworkItem::escapeHTML(channelModes()), !channelModes().isEmpty());
     if (_ircUser->isAway()) {
-        toolTip[0].append(tr(" is away"));
-        if (!_ircUser->awayMessage().isEmpty())
-            toolTip[0].append(QString(" (%1)").arg(_ircUser->awayMessage()));
+        QString awayMessageHTML = QString("<p class='italic'>%1</p>").arg(tr("Unknown"));
+
+        // If away message is known, replace with the escaped message.
+        if (!_ircUser->awayMessage().isEmpty()) {
+            awayMessageHTML = NetworkItem::escapeHTML(_ircUser->awayMessage());
+        }
+        addRow(NetworkItem::escapeHTML(tr("Away message"), true), awayMessageHTML, true);
     }
-    if (!_ircUser->realName().isEmpty()) toolTip.append(_ircUser->realName());
-    if (!_ircUser->ircOperator().isEmpty()) toolTip.append(QString("%1 %2").arg(nickName()).arg(_ircUser->ircOperator()));
-    if (!_ircUser->suserHost().isEmpty()) toolTip.append(_ircUser->suserHost());
-    if (!_ircUser->whoisServiceReply().isEmpty()) toolTip.append(_ircUser->whoisServiceReply());
+    addRow(tr("Realname"), NetworkItem::escapeHTML(_ircUser->realName()), !_ircUser->realName().isEmpty());
 
-    toolTip.append(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!")+1));
+    // suserHost may return "<nick> is available for help", which should be translated.
+    // See https://www.alien.net.au/irc/irc2numerics.html
+    if (_ircUser->suserHost().endsWith("available for help")) {
+        addRow(NetworkItem::escapeHTML(tr("Help status"), true), NetworkItem::escapeHTML(tr("Available for help")), true);
+    }
+    else {
+        addRow(NetworkItem::escapeHTML(tr("Service status"), true),
+               NetworkItem::escapeHTML(_ircUser->suserHost()),
+               !_ircUser->suserHost().isEmpty());
+    }
+
+    // Keep track of whether or not the account information's been added.  Don't show it twice.
+    bool accountAdded = false;
+    if (!_ircUser->account().isEmpty()) {
+        // IRCv3 account-notify is supported by the core and IRC server.
+        // Assume logged out (seems to be more common)
+        QString accountHTML = QString("<p class='italic'>%1</p>").arg(tr("Not logged in"));
+
+        // If account is logged in, replace with the escaped account name.
+        if (_ircUser->account() != "*") {
+            accountHTML = NetworkItem::escapeHTML(_ircUser->account());
+        }
+        addRow(NetworkItem::escapeHTML(tr("Account"), true), accountHTML, true);
+        // Mark the row as added
+        accountAdded = true;
+    }
+    // whoisServiceReply may return "<nick> is identified for this nick", which should be translated.
+    // See https://www.alien.net.au/irc/irc2numerics.html
+    if (_ircUser->whoisServiceReply().endsWith("identified for this nick")) {
+        addRow(NetworkItem::escapeHTML(tr("Account"), true), NetworkItem::escapeHTML(tr("Identified for this nick")), !accountAdded);
+        // Don't add the account row again if information's already added via account-notify
+        // Not used further down...
+        // accountAdded = true;
+    }
+    else {
+        addRow(NetworkItem::escapeHTML(tr("Service Reply"), true),
+               NetworkItem::escapeHTML(_ircUser->whoisServiceReply()),
+               !_ircUser->whoisServiceReply().isEmpty());
+    }
+    addRow(tr("Hostmask"),
+           NetworkItem::escapeHTML(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!") + 1)),
+           !(_ircUser->hostmask().remove(0, _ircUser->hostmask().indexOf("!") + 1) == "@"));
+    // ircOperator may contain "is an" or "is a", which should be removed.
+    addRow(tr("Operator"),
+           NetworkItem::escapeHTML(_ircUser->ircOperator().replace("is an ", "").replace("is a ", "")),
+           !_ircUser->ircOperator().isEmpty());
 
     if (_ircUser->idleTime().isValid()) {
         QDateTime now = QDateTime::currentDateTime();
         QDateTime idle = _ircUser->idleTime();
         int idleTime = idle.secsTo(now);
-        toolTip.append(tr("idling since %1").arg(secondsToString(idleTime)));
+        addRow(NetworkItem::escapeHTML(tr("Idling since"), true), secondsToString(idleTime), true);
     }
+
     if (_ircUser->loginTime().isValid()) {
-        toolTip.append(tr("login time: %1").arg(_ircUser->loginTime().toString()));
+        addRow(NetworkItem::escapeHTML(tr("Login time"), true), _ircUser->loginTime().toString(), true);
     }
 
-    if (!_ircUser->server().isEmpty()) toolTip.append(tr("server: %1").arg(_ircUser->server()));
+    addRow(tr("Server"), NetworkItem::escapeHTML(_ircUser->server()), !_ircUser->server().isEmpty());
+    tooltip << "</table>";
 
-    return QString("<p> %1 </p>").arg(toolTip.join("<br />"));
+    // If no further information found, offer an explanatory message
+    if (!infoAdded)
+        tooltip << "<p class='italic' align='center'>" << tr("No information available") << "</p>";
+
+    tooltip << "</qt>";
+    return strTooltip;
 }
 
+QString IrcUserItem::channelModes() const
+{
+    // IrcUserItems are parented to UserCategoryItem, which are parented to ChannelBufferItem.
+    // We want the channel buffer item in order to get the channel-specific user modes.
+    auto* category = qobject_cast<UserCategoryItem*>(parent());
+    if (!category)
+        return QString();
+
+    auto* channel = qobject_cast<ChannelBufferItem*>(category->parent());
+    if (!channel)
+        return QString();
+
+    return channel->nickChannelModes(nickName());
+}
 
 /*****************************************
  * NetworkModel
  *****************************************/
-NetworkModel::NetworkModel(QObject *parent)
+NetworkModel::NetworkModel(QObjectparent)
     : TreeModel(NetworkModel::defaultHeader(), parent)
 {
-    connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
-        this, SLOT(checkForNewBuffers(const QModelIndex &, int, int)));
-    connect(this, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
-        this, SLOT(checkForRemovedBuffers(const QModelIndex &, int, int)));
+    connect(this, &NetworkModel::rowsInserted, this, &NetworkModel::checkForNewBuffers);
+    connect(this, &NetworkModel::rowsAboutToBeRemoved, this, &NetworkModel::checkForRemovedBuffers);
 
     BufferSettings defaultSettings;
-    defaultSettings.notify("UserNoticesTarget", this, SLOT(messageRedirectionSettingsChanged()));
-    defaultSettings.notify("ServerNoticesTarget", this, SLOT(messageRedirectionSettingsChanged()));
-    defaultSettings.notify("ErrorMsgsTarget", this, SLOT(messageRedirectionSettingsChanged()));
+    defaultSettings.notify("UserNoticesTarget", this, &NetworkModel::messageRedirectionSettingsChanged);
+    defaultSettings.notify("ServerNoticesTarget", this, &NetworkModel::messageRedirectionSettingsChanged);
+    defaultSettings.notify("ErrorMsgsTarget", this, &NetworkModel::messageRedirectionSettingsChanged);
     messageRedirectionSettingsChanged();
 }
 
-
 QList<QVariant> NetworkModel::defaultHeader()
 {
     QList<QVariant> data;
@@ -988,18 +1216,16 @@ QList<QVariant> NetworkModel::defaultHeader()
     return data;
 }
 
-
-bool NetworkModel::isBufferIndex(const QModelIndex &index) const
+bool NetworkModel::isBufferIndex(const QModelIndex& index) const
 {
     return index.data(NetworkModel::ItemTypeRole) == NetworkModel::BufferItemType;
 }
 
-
 int NetworkModel::networkRow(NetworkId networkId) const
 {
-    NetworkItem *netItem = 0;
+    NetworkItem* netItem = nullptr;
     for (int i = 0; i < rootItem->childCount(); i++) {
-        netItem = qobject_cast<NetworkItem *>(rootItem->child(i));
+        netItem = qobject_cast<NetworkItem*>(rootItem->child(i));
         if (!netItem)
             continue;
         if (netItem->networkId() == networkId)
@@ -1008,40 +1234,36 @@ int NetworkModel::networkRow(NetworkId networkId) const
     return -1;
 }
 
-
 QModelIndex NetworkModel::networkIndex(NetworkId networkId)
 {
     int netRow = networkRow(networkId);
     if (netRow == -1)
-        return QModelIndex();
+        return {};
     else
-        return indexByItem(qobject_cast<NetworkItem *>(rootItem->child(netRow)));
+        return indexByItem(qobject_cast<NetworkItem*>(rootItem->child(netRow)));
 }
 
-
-NetworkItem *NetworkModel::findNetworkItem(NetworkId networkId) const
+NetworkItem* NetworkModel::findNetworkItem(NetworkId networkId) const
 {
     int netRow = networkRow(networkId);
     if (netRow == -1)
-        return 0;
+        return nullptr;
     else
-        return qobject_cast<NetworkItem *>(rootItem->child(netRow));
+        return qobject_cast<NetworkItem*>(rootItem->child(netRow));
 }
 
-
-NetworkItem *NetworkModel::networkItem(NetworkId networkId)
+NetworkItem* NetworkModel::networkItem(NetworkId networkId)
 {
-    NetworkItem *netItem = findNetworkItem(networkId);
+    NetworkItemnetItem = findNetworkItem(networkId);
 
-    if (netItem == 0) {
+    if (netItem == nullptr) {
         netItem = new NetworkItem(networkId, rootItem);
         rootItem->newChild(netItem);
     }
     return netItem;
 }
 
-
-void NetworkModel::networkRemoved(const NetworkId &networkId)
+void NetworkModel::networkRemoved(const NetworkId& networkId)
 {
     int netRow = networkRow(networkId);
     if (netRow != -1) {
@@ -1049,35 +1271,31 @@ void NetworkModel::networkRemoved(const NetworkId &networkId)
     }
 }
 
-
 QModelIndex NetworkModel::bufferIndex(BufferId bufferId)
 {
     if (!_bufferItemCache.contains(bufferId))
-        return QModelIndex();
+        return {};
 
     return indexByItem(_bufferItemCache[bufferId]);
 }
 
-
-BufferItem *NetworkModel::findBufferItem(BufferId bufferId) const
+BufferItem* NetworkModel::findBufferItem(BufferId bufferId) const
 {
     if (_bufferItemCache.contains(bufferId))
         return _bufferItemCache[bufferId];
     else
-        return 0;
+        return nullptr;
 }
 
-
-BufferItem *NetworkModel::bufferItem(const BufferInfo &bufferInfo)
+BufferItem* NetworkModel::bufferItem(const BufferInfo& bufferInfo)
 {
     if (_bufferItemCache.contains(bufferInfo.bufferId()))
         return _bufferItemCache[bufferInfo.bufferId()];
 
-    NetworkItem *netItem = networkItem(bufferInfo.networkId());
+    NetworkItemnetItem = networkItem(bufferInfo.networkId());
     return netItem->bufferItem(bufferInfo);
 }
 
-
 QStringList NetworkModel::mimeTypes() const
 {
     // mimetypes we accept for drops
@@ -1088,24 +1306,22 @@ QStringList NetworkModel::mimeTypes() const
     return types;
 }
 
-
-bool NetworkModel::mimeContainsBufferList(const QMimeData *mimeData)
+bool NetworkModel::mimeContainsBufferList(const QMimeData* mimeData)
 {
     return mimeData->hasFormat("application/Quassel/BufferItemList");
 }
 
-
-QList<QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData)
+QList<QPair<NetworkId, BufferId>> NetworkModel::mimeDataToBufferList(const QMimeData* mimeData)
 {
-    QList<QPair<NetworkId, BufferId> > bufferList;
+    QList<QPair<NetworkId, BufferId>> bufferList;
 
     if (!mimeContainsBufferList(mimeData))
         return bufferList;
 
-    QStringList rawBufferList = QString::fromAscii(mimeData->data("application/Quassel/BufferItemList")).split(",");
+    QStringList rawBufferList = QString::fromLatin1(mimeData->data("application/Quassel/BufferItemList")).split(",");
     NetworkId networkId;
     BufferId bufferUid;
-    foreach(QString rawBuffer, rawBufferList) {
+    foreach (QString rawBuffer, rawBufferList) {
         if (!rawBuffer.contains(":"))
             continue;
         networkId = rawBuffer.section(":", 0, 0).toInt();
@@ -1115,14 +1331,13 @@ QList<QPair<NetworkId, BufferId> > NetworkModel::mimeDataToBufferList(const QMim
     return bufferList;
 }
 
-
-QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const
+QMimeData* NetworkModel::mimeData(const QModelIndexList& indexes) const
 {
-    QMimeData *mimeData = new QMimeData();
+    auto* mimeData = new QMimeData();
 
     QStringList bufferlist;
     QString netid, uid, bufferid;
-    foreach(QModelIndex index, indexes) {
+    foreach (QModelIndex index, indexes) {
         netid = QString::number(index.data(NetworkIdRole).value<NetworkId>().toInt());
         uid = QString::number(index.data(BufferIdRole).value<BufferId>().toInt());
         bufferid = QString("%1:%2").arg(netid).arg(uid);
@@ -1130,71 +1345,64 @@ QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const
             bufferlist << bufferid;
     }
 
-    mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toAscii());
+    mimeData->setData("application/Quassel/BufferItemList", bufferlist.join(",").toLatin1());
 
     return mimeData;
 }
 
-
-void NetworkModel::attachNetwork(Network *net)
+void NetworkModel::attachNetwork(Network* net)
 {
-    NetworkItem *netItem = networkItem(net->networkId());
+    NetworkItemnetItem = networkItem(net->networkId());
     netItem->attachNetwork(net);
 }
 
-
 void NetworkModel::bufferUpdated(BufferInfo bufferInfo)
 {
-    BufferItem *bufItem = bufferItem(bufferInfo);
+    BufferItembufItem = bufferItem(bufferInfo);
     QModelIndex itemindex = indexByItem(bufItem);
     emit dataChanged(itemindex, itemindex);
 }
 
-
 void NetworkModel::removeBuffer(BufferId bufferId)
 {
-    BufferItem *buffItem = findBufferItem(bufferId);
+    BufferItembuffItem = findBufferItem(bufferId);
     if (!buffItem)
         return;
 
     buffItem->parent()->removeChild(buffItem);
 }
 
-
 MsgId NetworkModel::lastSeenMsgId(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
-        return MsgId();
+        return {};
 
     return _bufferItemCache[bufferId]->lastSeenMsgId();
 }
 
-
 MsgId NetworkModel::markerLineMsgId(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
-        return MsgId();
+        return {};
 
     return _bufferItemCache[bufferId]->markerLineMsgId();
 }
 
-
 // FIXME we always seem to use this (expensive) non-const version
-MsgId NetworkModel::lastSeenMsgId(const BufferId &bufferId)
+MsgId NetworkModel::lastSeenMsgId(const BufferIdbufferId)
 {
-    BufferItem *bufferItem = findBufferItem(bufferId);
+    BufferItembufferItem = findBufferItem(bufferId);
     if (!bufferItem) {
         qDebug() << "NetworkModel::lastSeenMsgId(): buffer is unknown:" << bufferId;
         Client::purgeKnownBufferIds();
-        return MsgId();
+        return {};
     }
     return bufferItem->lastSeenMsgId();
 }
 
-
-void NetworkModel::setLastSeenMsgId(const BufferId &bufferId, const MsgId &msgId)
+void NetworkModel::setLastSeenMsgId(const BufferId& bufferId, const MsgId& msgId)
 {
-    BufferItem *bufferItem = findBufferItem(bufferId);
+    BufferItembufferItem = findBufferItem(bufferId);
     if (!bufferItem) {
         qDebug() << "NetworkModel::setLastSeenMsgId(): buffer is unknown:" << bufferId;
         Client::purgeKnownBufferIds();
@@ -1204,10 +1412,9 @@ void NetworkModel::setLastSeenMsgId(const BufferId &bufferId, const MsgId &msgId
     emit lastSeenMsgSet(bufferId, msgId);
 }
 
-
-void NetworkModel::setMarkerLineMsgId(const BufferId &bufferId, const MsgId &msgId)
+void NetworkModel::setMarkerLineMsgId(const BufferId& bufferId, const MsgId& msgId)
 {
-    BufferItem *bufferItem = findBufferItem(bufferId);
+    BufferItembufferItem = findBufferItem(bufferId);
     if (!bufferItem) {
         qDebug() << "NetworkModel::setMarkerLineMsgId(): buffer is unknown:" << bufferId;
         Client::purgeKnownBufferIds();
@@ -1217,8 +1424,7 @@ void NetworkModel::setMarkerLineMsgId(const BufferId &bufferId, const MsgId &msg
     emit markerLineSet(bufferId, msgId);
 }
 
-
-void NetworkModel::updateBufferActivity(Message &msg)
+void NetworkModel::updateBufferActivity(Message& msg)
 {
     int redirectionTarget = 0;
     switch (msg.type()) {
@@ -1242,8 +1448,8 @@ void NetworkModel::updateBufferActivity(Message &msg)
     case Message::Plain:
     case Message::Action:
         if (bufferType(msg.bufferId()) == BufferInfo::ChannelBuffer) {
-            const Network *net = Client::network(msg.bufferInfo().networkId());
-            IrcUser *user = net ? net->ircUser(nickFromMask(msg.sender())) : 0;
+            const Networknet = Client::network(msg.bufferInfo().networkId());
+            IrcUser* user = net ? net->ircUser(nickFromMask(msg.sender())) : nullptr;
             if (user)
                 user->setLastChannelActivity(msg.bufferId(), msg.timestamp());
         }
@@ -1257,19 +1463,19 @@ void NetworkModel::updateBufferActivity(Message &msg)
             updateBufferActivity(bufferItem(msg.bufferInfo()), msg);
 
         if (redirectionTarget & BufferSettings::StatusBuffer) {
-            const NetworkItem *netItem = findNetworkItem(msg.bufferInfo().networkId());
+            const NetworkItemnetItem = findNetworkItem(msg.bufferInfo().networkId());
             if (netItem) {
                 updateBufferActivity(netItem->statusBufferItem(), msg);
             }
         }
     }
     else {
-        updateBufferActivity(bufferItem(msg.bufferInfo()), msg);
+        if ((BufferSettings(msg.bufferId()).messageFilter() & msg.type()) != msg.type())
+            updateBufferActivity(bufferItem(msg.bufferInfo()), msg);
     }
 }
 
-
-void NetworkModel::updateBufferActivity(BufferItem *bufferItem, const Message &msg)
+void NetworkModel::updateBufferActivity(BufferItem* bufferItem, const Message& msg)
 {
     if (!bufferItem)
         return;
@@ -1279,10 +1485,9 @@ void NetworkModel::updateBufferActivity(BufferItem *bufferItem, const Message &m
         emit requestSetLastSeenMsg(bufferItem->bufferId(), msg.msgId());
 }
 
-
-void NetworkModel::setBufferActivity(const BufferId &bufferId, BufferInfo::ActivityLevel level)
+void NetworkModel::setBufferActivity(const BufferId& bufferId, BufferInfo::ActivityLevel level)
 {
-    BufferItem *bufferItem = findBufferItem(bufferId);
+    BufferItembufferItem = findBufferItem(bufferId);
     if (!bufferItem) {
         qDebug() << "NetworkModel::setBufferActivity(): buffer is unknown:" << bufferId;
         return;
@@ -1290,10 +1495,9 @@ void NetworkModel::setBufferActivity(const BufferId &bufferId, BufferInfo::Activ
     bufferItem->setActivityLevel(level);
 }
 
-
-void NetworkModel::clearBufferActivity(const BufferId &bufferId)
+void NetworkModel::clearBufferActivity(const BufferId& bufferId)
 {
-    BufferItem *bufferItem = findBufferItem(bufferId);
+    BufferItembufferItem = findBufferItem(bufferId);
     if (!bufferItem) {
         qDebug() << "NetworkModel::clearBufferActivity(): buffer is unknown:" << bufferId;
         return;
@@ -1301,19 +1505,17 @@ void NetworkModel::clearBufferActivity(const BufferId &bufferId)
     bufferItem->clearActivityLevel();
 }
 
-
-const Network *NetworkModel::networkByIndex(const QModelIndex &index) const
+const Network* NetworkModel::networkByIndex(const QModelIndex& index) const
 {
     QVariant netVariant = index.data(NetworkIdRole);
     if (!netVariant.isValid())
-        return 0;
+        return nullptr;
 
     NetworkId networkId = netVariant.value<NetworkId>();
     return Client::network(networkId);
 }
 
-
-void NetworkModel::checkForRemovedBuffers(const QModelIndex &parent, int start, int end)
+void NetworkModel::checkForRemovedBuffers(const QModelIndex& parent, int start, int end)
 {
     if (parent.data(ItemTypeRole) != NetworkItemType)
         return;
@@ -1323,19 +1525,17 @@ void NetworkModel::checkForRemovedBuffers(const QModelIndex &parent, int start,
     }
 }
 
-
-void NetworkModel::checkForNewBuffers(const QModelIndex &parent, int start, int end)
+void NetworkModel::checkForNewBuffers(const QModelIndex& parent, int start, int end)
 {
     if (parent.data(ItemTypeRole) != NetworkItemType)
         return;
 
     for (int row = start; row <= end; row++) {
         QModelIndex child = parent.child(row, 0);
-        _bufferItemCache[child.data(BufferIdRole).value < BufferId > ()] = static_cast<BufferItem *>(child.internalPointer());
+        _bufferItemCache[child.data(BufferIdRole).value<BufferId>()] = static_cast<BufferItem*>(child.internalPointer());
     }
 }
 
-
 QString NetworkModel::bufferName(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
@@ -1344,7 +1544,6 @@ QString NetworkModel::bufferName(BufferId bufferId) const
     return _bufferItemCache[bufferId]->bufferName();
 }
 
-
 BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
@@ -1353,7 +1552,6 @@ BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const
     return _bufferItemCache[bufferId]->bufferType();
 }
 
-
 BufferInfo NetworkModel::bufferInfo(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
@@ -1362,52 +1560,48 @@ BufferInfo NetworkModel::bufferInfo(BufferId bufferId) const
     return _bufferItemCache[bufferId]->bufferInfo();
 }
 
-
 NetworkId NetworkModel::networkId(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
-        return NetworkId();
+        return {};
 
-    NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
+    auto* netItem = qobject_cast<NetworkItem*>(_bufferItemCache[bufferId]->parent());
     if (netItem)
         return netItem->networkId();
     else
-        return NetworkId();
+        return {};
 }
 
-
 QString NetworkModel::networkName(BufferId bufferId) const
 {
     if (!_bufferItemCache.contains(bufferId))
         return QString();
 
-    NetworkItem *netItem = qobject_cast<NetworkItem *>(_bufferItemCache[bufferId]->parent());
+    auto* netItem = qobject_cast<NetworkItem*>(_bufferItemCache[bufferId]->parent());
     if (netItem)
         return netItem->networkName();
     else
         return QString();
 }
 
-
-BufferId NetworkModel::bufferId(NetworkId networkId, const QString &bufferName, Qt::CaseSensitivity cs) const
+BufferId NetworkModel::bufferId(NetworkId networkId, const QString& bufferName, Qt::CaseSensitivity cs) const
 {
-    const NetworkItem *netItem = findNetworkItem(networkId);
+    const NetworkItemnetItem = findNetworkItem(networkId);
     if (!netItem)
-        return BufferId();
+        return {};
 
     for (int i = 0; i < netItem->childCount(); i++) {
-        BufferItem *bufferItem = qobject_cast<BufferItem *>(netItem->child(i));
+        auto* bufferItem = qobject_cast<BufferItem*>(netItem->child(i));
         if (bufferItem && !bufferItem->bufferName().compare(bufferName, cs))
             return bufferItem->bufferId();
     }
-    return BufferId();
+    return {};
 }
 
-
-void NetworkModel::sortBufferIds(QList<BufferId> &bufferIds) const
+void NetworkModel::sortBufferIds(QList<BufferId>& bufferIds) const
 {
-    QList<BufferItem *> bufferItems;
-    foreach(BufferId bufferId, bufferIds) {
+    QList<BufferItem*> bufferItems;
+    foreach (BufferId bufferId, bufferIds) {
         if (_bufferItemCache.contains(bufferId))
             bufferItems << _bufferItemCache[bufferId];
     }
@@ -1415,12 +1609,11 @@ void NetworkModel::sortBufferIds(QList<BufferId> &bufferIds) const
     qSort(bufferItems.begin(), bufferItems.end(), bufferItemLessThan);
 
     bufferIds.clear();
-    foreach(BufferItem *bufferItem, bufferItems) {
+    foreach (BufferItem* bufferItem, bufferItems) {
         bufferIds << bufferItem->bufferId();
     }
 }
 
-
 QList<BufferId> NetworkModel::allBufferIdsSorted() const
 {
     QList<BufferId> bufferIds = allBufferIds();
@@ -1428,8 +1621,7 @@ QList<BufferId> NetworkModel::allBufferIdsSorted() const
     return bufferIds;
 }
 
-
-bool NetworkModel::bufferItemLessThan(const BufferItem *left, const BufferItem *right)
+bool NetworkModel::bufferItemLessThan(const BufferItem* left, const BufferItem* right)
 {
     int leftType = left->bufferType();
     int rightType = right->bufferType();
@@ -1440,7 +1632,6 @@ bool NetworkModel::bufferItemLessThan(const BufferItem *left, const BufferItem *
         return QString::compare(left->bufferName(), right->bufferName(), Qt::CaseInsensitive) < 0;
 }
 
-
 void NetworkModel::messageRedirectionSettingsChanged()
 {
     BufferSettings bufferSettings;
@@ -1449,3 +1640,26 @@ void NetworkModel::messageRedirectionSettingsChanged()
     _serverNoticesTarget = bufferSettings.serverNoticesTarget();
     _errorMsgsTarget = bufferSettings.errorMsgsTarget();
 }
+
+void NetworkModel::bufferActivityChanged(BufferId bufferId, const Message::Types activity)
+{
+    auto _bufferItem = findBufferItem(bufferId);
+    if (!_bufferItem) {
+        qDebug() << "NetworkModel::bufferActivityChanged(): buffer is unknown:" << bufferId;
+        return;
+    }
+    auto hiddenTypes = BufferSettings(bufferId).messageFilter();
+    auto visibleTypes = ~hiddenTypes;
+    auto activityVisibleTypesIntersection = activity & visibleTypes;
+    _bufferItem->setActivity(activityVisibleTypesIntersection, false);
+}
+
+void NetworkModel::highlightCountChanged(BufferId bufferId, int count)
+{
+    auto _bufferItem = findBufferItem(bufferId);
+    if (!_bufferItem) {
+        qDebug() << "NetworkModel::highlightCountChanged(): buffer is unknown:" << bufferId;
+        return;
+    }
+    _bufferItem->addActivity(Message::Types{}, count > 0);
+}