X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fclient%2Fnetworkmodel.cpp;h=47139325669bda7f68389467fe08e99ad43bc0f6;hp=c85f579f296d272a15624dd2690a1e70c1a5dc57;hb=2c4cc10cfb540da2b33c2a2dba8e012b2b594395;hpb=80f22eedcd34e1fc021f5d30fe67a56955dc9083 diff --git a/src/client/networkmodel.cpp b/src/client/networkmodel.cpp index c85f579f..47139325 100644 --- a/src/client/networkmodel.cpp +++ b/src/client/networkmodel.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2015 by the Quassel Project * + * Copyright (C) 2005-2019 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -20,14 +20,14 @@ #include "networkmodel.h" +#include + #include #include -#if QT_VERSION < 0x050000 -#include // for Qt::escape() -#endif #include "buffermodel.h" #include "buffersettings.h" +#include "buffersyncer.h" #include "client.h" #include "clientignorelistmanager.h" #include "clientsettings.h" @@ -36,21 +36,26 @@ #include "signalproxy.h" /***************************************** -* Network Items -*****************************************/ -NetworkItem::NetworkItem(const NetworkId &netid, AbstractTreeItem *parent) - : PropertyMapItem(QList() << "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 { @@ -74,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(" ", " ") : formattedString); +} // FIXME shouldn't we check the bufferItemCache here? -BufferItem *NetworkItem::findBufferItem(BufferId bufferId) +BufferItem* NetworkItem::findBufferItem(BufferId bufferId) { - BufferItem *bufferItem = 0; + BufferItem* bufferItem = nullptr; for (int i = 0; i < childCount(); i++) { - bufferItem = qobject_cast(child(i)); + bufferItem = qobject_cast(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); + BufferItem* bufferItem = findBufferItem(bufferInfo); if (bufferItem) return bufferItem; @@ -101,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); @@ -119,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(bufferItem); + case BufferInfo::ChannelBuffer: { + auto* channelBufferItem = static_cast(bufferItem); if (_network) { - IrcChannel *ircChannel = _network->ircChannel(bufferInfo.bufferName()); + IrcChannel* ircChannel = _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; + ChannelBufferItem* channelItem; for (int i = 0; i < childCount(); i++) { - channelItem = qobject_cast(child(i)); + channelItem = qobject_cast(child(i)); if (!channelItem) continue; @@ -176,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(child(i)); + queryItem = qobject_cast(child(i)); if (!queryItem) continue; @@ -192,73 +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 << ""; + + // Function to add a row to the tooltip table + auto addRow = [&](const QString& key, const QString& value, bool condition) { + if (condition) { + tooltip << "" << key << "" << value << ""; + } + }; -#if QT_VERSION < 0x050000 - QStringList toolTip(QString("%1").arg(Qt::escape(networkName()))); - toolTip.append(tr("Server: %1").arg(Qt::escape(currentServer()))); -#else - QStringList toolTip(QString("%1").arg(networkName().toHtmlEscaped())); - toolTip.append(tr("Server: %1").arg(currentServer().toHtmlEscaped())); -#endif - toolTip.append(tr("Users: %1").arg(nickCount())); + tooltip << "

" << NetworkItem::escapeHTML(networkName(), true) << "

"; + if (isActive()) { + tooltip << ""; + 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 << "
"; } - - return QString("

%1

").arg(toolTip.join("
")); + else { + tooltip << "

" << tr("Not connected") << "

"; + } + tooltip << "
"; + return strTooltip; } - void NetworkItem::onBeginRemoveChilds(int start, int end) { for (int i = start; i <= end; i++) { - StatusBufferItem *statusBufferItem = qobject_cast(child(i)); + auto* statusBufferItem = qobject_cast(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) { @@ -268,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(parent())->networkName())) + if (Client::ignoreListManager() && Client::ignoreListManager()->match(msg, qobject_cast(parent())->networkName())) return; if (msg.msgId() <= lastSeenMsgId()) @@ -305,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 { @@ -347,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: @@ -357,23 +405,20 @@ bool BufferItem::setData(int column, const QVariant &value, int role) default: return PropertyMapItem::setData(column, value, 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; } @@ -381,71 +426,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(); } - QString BufferItem::toolTip(int column) const { Q_UNUSED(column); return tr("

%1 - %2

").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(parent()); + auto* networkItem = qobject_cast(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 Network* net = Client::network(bufferInfo.networkId()); if (!net) return; - IrcUser *ircUser = net->ircUser(bufferInfo.bufferName()); + IrcUser* ircUser = 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(_ircUser); + return QVariant::fromValue(_ircUser); case NetworkModel::UserAwayRole: return (bool)_ircUser ? _ircUser->isAway() : false; default: @@ -453,16 +490,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; @@ -470,127 +511,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(); - const Network *net = Client::network(netId); + const Network* net = 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 << ""; - toolTip.append(tr("Query with %1").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 << "

"; + 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 << "

"; + } + else { + // Function to add a row to the tooltip table + auto addRow = [&](const QString& key, const QString& value, bool condition) { + if (condition) { + tooltip << "" << key << "" << value << ""; + 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 << "

"; + + tooltip << ""; if (_ircUser->isAway()) { - toolTip[0].append(QString(" (away%1)").arg(!_ircUser->awayMessage().isEmpty() ? (QString(" ") + _ircUser->awayMessage()) : QString())); + QString awayMessageHTML = QString("

%1

").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 " 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("

%1

").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 " 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 << "
"; } - return QString("

%1

").arg(toolTip.join("
")); -} + // If no further information found, offer an explanatory message + if (!infoAdded) + tooltip << "

" << tr("No information available") << "

"; + tooltip << "
"; + return strTooltip; +} -void QueryBufferItem::setIrcUser(IrcUser *ircUser) +void QueryBufferItem::setIrcUser(IrcUser* ircUser) { 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(_ircChannel); + return QVariant::fromValue(_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 << ""; + + // Function to add a row to the tooltip table + auto addRow = [&](const QString& key, const QString& value, bool condition) { + if (condition) { + tooltip << "" << key << "" << value << ""; + } + }; + + tooltip << "

"; + tooltip << NetworkItem::escapeHTML(tr("Channel %1").arg(bufferName()), true) << "

"; -#if QT_VERSION < 0x050000 - toolTip.append(tr("Channel %1").arg(Qt::escape(bufferName()))); -#else - toolTip.append(tr("Channel %1").arg(bufferName().toHtmlEscaped())); -#endif if (isActive()) { - //TODO: add channel modes - toolTip.append(tr("Users: %1").arg(nickCount())); + tooltip << ""; + 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("Mode: %1").arg(channelMode)); + addRow(tr("Mode"), channelMode, true); } ItemViewSettings s; @@ -599,51 +734,39 @@ QString ChannelBufferItem::toolTip(int column) const QString _topic = topic(); if (_topic != "") { _topic = stripFormatCodes(_topic); -#if QT_VERSION < 0x050000 - _topic = Qt::escape(_topic); -#else - _topic = _topic.toHtmlEscaped(); -#endif - toolTip.append(QString(" ")); - toolTip.append(tr("Topic: %1").arg(_topic)); + _topic = NetworkItem::escapeHTML(_topic); + addRow(tr("Topic"), _topic, true); } } + + tooltip << "
"; } else { - toolTip.append(tr("Not active
Double-click to join")); + tooltip << "

" << tr("Not active, double-click to join") << "

"; } - return tr("

%1

").arg(toolTip.join("
")); + tooltip << "
"; + 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 )), - this, SLOT(join(QList ))); - 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()); @@ -651,97 +774,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 &ircUsers) +void ChannelBufferItem::join(const QList& 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(child(i)); + categoryItem = qobject_cast(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); + addUsersToCategory(QList() << ircUser); } - -void ChannelBufferItem::addUsersToCategory(const QList &ircUsers) +void ChannelBufferItem::addUsersToCategory(const QList& ircUsers) { Q_ASSERT(_ircChannel); - QHash > categories; + QHash> 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(); + categories[categoryItem] = QList(); newChild(categoryItem); } categories[categoryItem] << ircUser; } - QHash >::const_iterator catIter = categories.constBegin(); + QHash>::const_iterator catIter = categories.constBegin(); while (catIter != categories.constEnd()) { catIter.key()->addUsers(catIter.value()); ++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. @@ -750,9 +875,9 @@ void ChannelBufferItem::removeUserFromCategory(IrcUser *ircUser) return; } - UserCategoryItem *categoryItem = 0; + UserCategoryItem* categoryItem = nullptr; for (int i = 0; i < childCount(); i++) { - categoryItem = qobject_cast(child(i)); + categoryItem = qobject_cast(child(i)); if (categoryItem->removeUser(ircUser)) { if (categoryItem->childCount() == 0) removeChild(i); @@ -761,17 +886,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); + UserCategoryItem* categoryItem = findCategoryItem(categoryId); if (categoryItem) { if (categoryItem->findIrcUser(ircUser)) { - return; // already in the right category; + return; // already in the right category; } } else { @@ -780,11 +904,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(child(i)); + auto* oldCategoryItem = qobject_cast(child(i)); Q_ASSERT(oldCategoryItem); - IrcUserItem *userItem = oldCategoryItem->findIrcUser(ircUser); + IrcUserItem* userItem = oldCategoryItem->findIrcUser(ircUser); if (userItem) { ircUserItem = userItem; break; @@ -798,23 +922,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 UserCategoryItem::categories = QList() << 'q' << 'a' << 'o' << 'h' << 'v'; -UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent) - : PropertyMapItem(QStringList() << "categoryName", parent), - _category(category) +UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem* parent) + : 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 @@ -822,50 +950,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(child(i)); + userItem = qobject_cast(child(i)); if (!userItem) continue; if (userItem->ircUser() == ircUser) return userItem; } - return 0; + return nullptr; } - -void UserCategoryItem::addUsers(const QList &ircUsers) +void UserCategoryItem::addUsers(const QList& ircUsers) { - QList userItems; - foreach(IrcUser *ircUser, ircUsers) - userItems << new IrcUserItem(ircUser, this); + QList 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; + IrcUserItem* userItem = findIrcUser(ircUser); + auto success = (bool)userItem; if (success) { removeChild(userItem); emit dataChanged(0); @@ -873,8 +998,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])) @@ -883,7 +1007,6 @@ int UserCategoryItem::categoryFromModes(const QString &modes) return categories.count(); } - QVariant UserCategoryItem::data(int column, int role) const { switch (role) { @@ -904,20 +1027,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 { @@ -935,7 +1062,7 @@ QVariant IrcUserItem::data(int column, int role) const case NetworkModel::IrcChannelRole: return parent()->data(column, role); case NetworkModel::IrcUserRole: - return QVariant::fromValue(_ircUser.data()); + return QVariant::fromValue(_ircUser.data()); case NetworkModel::UserAwayRole: return (bool)_ircUser ? _ircUser->isAway() : false; default: @@ -943,59 +1070,144 @@ QVariant IrcUserItem::data(int column, int role) const } } - QString IrcUserItem::toolTip(int column) const { Q_UNUSED(column); - QStringList toolTip(QString("%1").arg(nickName())); - if (_ircUser->userModes() != "") toolTip[0].append(QString(" (%1)").arg(_ircUser->userModes())); + QString strTooltip; + QTextStream tooltip(&strTooltip, QIODevice::WriteOnly); + tooltip << ""; + + // Keep track of whether or not information has been added + bool infoAdded = false; + + // Use bufferName() for QueryBufferItem, nickName() for IrcUserItem + tooltip << "

" << 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 << "

"; + + auto addRow = [&](const QString& key, const QString& value, bool condition) { + if (condition) { + tooltip << "" << key << "" << value << ""; + infoAdded = true; + } + }; + + tooltip << ""; + 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("

%1

").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 " 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("

%1

").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 " 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 << "
"; - return QString("

%1

").arg(toolTip.join("
")); + // If no further information found, offer an explanatory message + if (!infoAdded) + tooltip << "

" << tr("No information available") << "

"; + + tooltip << "
"; + 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(parent()); + if (!category) + return QString(); + + auto* channel = qobject_cast(category->parent()); + if (!channel) + return QString(); + + return channel->nickChannelModes(nickName()); +} /***************************************** * NetworkModel *****************************************/ -NetworkModel::NetworkModel(QObject *parent) +NetworkModel::NetworkModel(QObject* parent) : 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 NetworkModel::defaultHeader() { QList data; @@ -1003,18 +1215,16 @@ QList 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(rootItem->child(i)); + netItem = qobject_cast(rootItem->child(i)); if (!netItem) continue; if (netItem->networkId() == networkId) @@ -1023,40 +1233,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(rootItem->child(netRow))); + return indexByItem(qobject_cast(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(rootItem->child(netRow)); + return qobject_cast(rootItem->child(netRow)); } - -NetworkItem *NetworkModel::networkItem(NetworkId networkId) +NetworkItem* NetworkModel::networkItem(NetworkId networkId) { - NetworkItem *netItem = findNetworkItem(networkId); + NetworkItem* netItem = 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) { @@ -1064,35 +1270,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()); + NetworkItem* netItem = networkItem(bufferInfo.networkId()); return netItem->bufferItem(bufferInfo); } - QStringList NetworkModel::mimeTypes() const { // mimetypes we accept for drops @@ -1103,16 +1305,14 @@ 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 > NetworkModel::mimeDataToBufferList(const QMimeData *mimeData) +QList> NetworkModel::mimeDataToBufferList(const QMimeData* mimeData) { - QList > bufferList; + QList> bufferList; if (!mimeContainsBufferList(mimeData)) return bufferList; @@ -1120,7 +1320,7 @@ QList > NetworkModel::mimeDataToBufferList(const QMim 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(); @@ -1130,14 +1330,13 @@ QList > 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().toInt()); uid = QString::number(index.data(BufferIdRole).value().toInt()); bufferid = QString("%1:%2").arg(netid).arg(uid); @@ -1150,66 +1349,59 @@ QMimeData *NetworkModel::mimeData(const QModelIndexList &indexes) const return mimeData; } - -void NetworkModel::attachNetwork(Network *net) +void NetworkModel::attachNetwork(Network* net) { - NetworkItem *netItem = networkItem(net->networkId()); + NetworkItem* netItem = networkItem(net->networkId()); netItem->attachNetwork(net); } - void NetworkModel::bufferUpdated(BufferInfo bufferInfo) { - BufferItem *bufItem = bufferItem(bufferInfo); + BufferItem* bufItem = bufferItem(bufferInfo); QModelIndex itemindex = indexByItem(bufItem); emit dataChanged(itemindex, itemindex); } - void NetworkModel::removeBuffer(BufferId bufferId) { - BufferItem *buffItem = findBufferItem(bufferId); + BufferItem* buffItem = 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 BufferId& bufferId) { - BufferItem *bufferItem = findBufferItem(bufferId); + BufferItem* bufferItem = 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); + BufferItem* bufferItem = findBufferItem(bufferId); if (!bufferItem) { qDebug() << "NetworkModel::setLastSeenMsgId(): buffer is unknown:" << bufferId; Client::purgeKnownBufferIds(); @@ -1219,10 +1411,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); + BufferItem* bufferItem = findBufferItem(bufferId); if (!bufferItem) { qDebug() << "NetworkModel::setMarkerLineMsgId(): buffer is unknown:" << bufferId; Client::purgeKnownBufferIds(); @@ -1232,8 +1423,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()) { @@ -1257,8 +1447,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 Network* net = Client::network(msg.bufferInfo().networkId()); + IrcUser* user = net ? net->ircUser(nickFromMask(msg.sender())) : nullptr; if (user) user->setLastChannelActivity(msg.bufferId(), msg.timestamp()); } @@ -1272,7 +1462,7 @@ void NetworkModel::updateBufferActivity(Message &msg) updateBufferActivity(bufferItem(msg.bufferInfo()), msg); if (redirectionTarget & BufferSettings::StatusBuffer) { - const NetworkItem *netItem = findNetworkItem(msg.bufferInfo().networkId()); + const NetworkItem* netItem = findNetworkItem(msg.bufferInfo().networkId()); if (netItem) { updateBufferActivity(netItem->statusBufferItem(), msg); } @@ -1284,8 +1474,7 @@ void NetworkModel::updateBufferActivity(Message &msg) } } - -void NetworkModel::updateBufferActivity(BufferItem *bufferItem, const Message &msg) +void NetworkModel::updateBufferActivity(BufferItem* bufferItem, const Message& msg) { if (!bufferItem) return; @@ -1295,10 +1484,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); + BufferItem* bufferItem = findBufferItem(bufferId); if (!bufferItem) { qDebug() << "NetworkModel::setBufferActivity(): buffer is unknown:" << bufferId; return; @@ -1306,10 +1494,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); + BufferItem* bufferItem = findBufferItem(bufferId); if (!bufferItem) { qDebug() << "NetworkModel::clearBufferActivity(): buffer is unknown:" << bufferId; return; @@ -1317,19 +1504,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(); 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; @@ -1339,19 +1524,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(child.internalPointer()); + _bufferItemCache[child.data(BufferIdRole).value()] = static_cast(child.internalPointer()); } } - QString NetworkModel::bufferName(BufferId bufferId) const { if (!_bufferItemCache.contains(bufferId)) @@ -1360,7 +1543,6 @@ QString NetworkModel::bufferName(BufferId bufferId) const return _bufferItemCache[bufferId]->bufferName(); } - BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const { if (!_bufferItemCache.contains(bufferId)) @@ -1369,7 +1551,6 @@ BufferInfo::Type NetworkModel::bufferType(BufferId bufferId) const return _bufferItemCache[bufferId]->bufferType(); } - BufferInfo NetworkModel::bufferInfo(BufferId bufferId) const { if (!_bufferItemCache.contains(bufferId)) @@ -1378,65 +1559,60 @@ 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(_bufferItemCache[bufferId]->parent()); + auto* netItem = qobject_cast(_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(_bufferItemCache[bufferId]->parent()); + auto* netItem = qobject_cast(_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 NetworkItem* netItem = findNetworkItem(networkId); if (!netItem) - return BufferId(); + return {}; for (int i = 0; i < netItem->childCount(); i++) { - BufferItem *bufferItem = qobject_cast(netItem->child(i)); + auto* bufferItem = qobject_cast(netItem->child(i)); if (bufferItem && !bufferItem->bufferName().compare(bufferName, cs)) return bufferItem->bufferId(); } - return BufferId(); + return {}; } - -void NetworkModel::sortBufferIds(QList &bufferIds) const +void NetworkModel::sortBufferIds(QList& bufferIds) const { - QList bufferItems; - foreach(BufferId bufferId, bufferIds) { + QList bufferItems; + foreach (BufferId bufferId, bufferIds) { if (_bufferItemCache.contains(bufferId)) bufferItems << _bufferItemCache[bufferId]; } - qSort(bufferItems.begin(), bufferItems.end(), bufferItemLessThan); + std::sort(bufferItems.begin(), bufferItems.end(), bufferItemLessThan); bufferIds.clear(); - foreach(BufferItem *bufferItem, bufferItems) { + foreach (BufferItem* bufferItem, bufferItems) { bufferIds << bufferItem->bufferId(); } } - QList NetworkModel::allBufferIdsSorted() const { QList bufferIds = allBufferIds(); @@ -1444,8 +1620,7 @@ QList 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(); @@ -1456,7 +1631,6 @@ bool NetworkModel::bufferItemLessThan(const BufferItem *left, const BufferItem * return QString::compare(left->bufferName(), right->bufferName(), Qt::CaseInsensitive) < 0; } - void NetworkModel::messageRedirectionSettingsChanged() { BufferSettings bufferSettings; @@ -1465,3 +1639,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); +}