X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fclient%2Fnetworkmodel.cpp;h=5daec856559992fe4fbd9e7c0ea43afdbb17ee6c;hp=d6bd9f487ea15b20bc804ab8d0e1a7da4baa8bb8;hb=1a45f16a9734820fba42fe1db3f38dd1eee49df6;hpb=b60e07cf184dc374b135489c4d5ec7db1e5f3651 diff --git a/src/client/networkmodel.cpp b/src/client/networkmodel.cpp index d6bd9f48..5daec856 100644 --- a/src/client/networkmodel.cpp +++ b/src/client/networkmodel.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2016 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 * @@ -22,9 +22,6 @@ #include #include -#if QT_VERSION < 0x050000 -#include // for Qt::escape() -#endif #include "buffermodel.h" #include "buffersettings.h" @@ -34,12 +31,13 @@ #include "ircchannel.h" #include "network.h" #include "signalproxy.h" +#include "buffersyncer.h" /***************************************** * Network Items *****************************************/ NetworkItem::NetworkItem(const NetworkId &netid, AbstractTreeItem *parent) - : PropertyMapItem(QList() << "networkName" << "currentServer" << "nickCount", parent), + : PropertyMapItem(parent), _networkId(netid), _statusBufferItem(0) { @@ -52,6 +50,13 @@ NetworkItem::NetworkItem(const NetworkId &netid, AbstractTreeItem *parent) } +QStringList NetworkItem::propertyOrder() const +{ + static QStringList order{"networkName", "currentServer", "nickCount"}; + return order; +} + + QVariant NetworkItem::data(int column, int role) const { switch (role) { @@ -76,13 +81,9 @@ QVariant NetworkItem::data(int column, int role) const QString NetworkItem::escapeHTML(const QString &string, bool useNonbreakingSpaces) { - // QString.replace() doesn't guarentee the source string will remain constant. + // QString.replace() doesn't guarantee the source string will remain constant. // Use a local variable to avoid compiler errors. -#if QT_VERSION < 0x050000 - QString formattedString = Qt::escape(string); -#else QString formattedString = string.toHtmlEscaped(); -#endif return (useNonbreakingSpaces ? formattedString.replace(" ", " ") : formattedString); } @@ -145,6 +146,14 @@ BufferItem *NetworkItem::bufferItem(const BufferInfo &bufferInfo) break; } + BufferSyncer *bufferSyncer = Client::bufferSyncer(); + if (bufferSyncer) { + bufferItem->addActivity( + bufferSyncer->activity(bufferItem->bufferId()), + bufferSyncer->highlightCount(bufferItem->bufferId()) > 0 + ); + } + return bufferItem; } @@ -224,7 +233,7 @@ QString NetworkItem::toolTip(int column) const Q_UNUSED(column); QString strTooltip; QTextStream tooltip( &strTooltip, QIODevice::WriteOnly ); - tooltip << ""; + tooltip << ""; // Function to add a row to the tooltip table auto addRow = [&](const QString& key, const QString& value, bool condition) { @@ -234,15 +243,18 @@ QString NetworkItem::toolTip(int column) const }; tooltip << "

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

"; - tooltip << ""; - addRow(tr("Server"), NetworkItem::escapeHTML(currentServer(), true), true); - - addRow(tr("Users"), QString::number(nickCount()), true); - - if (_network) - addRow(tr("Lag"), NetworkItem::escapeHTML(tr("%1 msecs").arg(_network->latency()), true), 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); - tooltip << "
"; + tooltip << ""; + } else { + tooltip << "

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

"; + } + tooltip << "
"; return strTooltip; } @@ -271,7 +283,7 @@ void NetworkItem::onNetworkDestroyed() * Fancy Buffer Items *****************************************/ BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent) - : PropertyMapItem(QStringList() << "bufferName" << "topic" << "nickCount", parent), + : PropertyMapItem(parent), _bufferInfo(bufferInfo), _activity(BufferInfo::NoActivity) { @@ -279,6 +291,13 @@ BufferItem::BufferItem(const BufferInfo &bufferInfo, AbstractTreeItem *parent) } +QStringList BufferItem::propertyOrder() const +{ + static QStringList order{"bufferName", "topic", "nickCount"}; + return order; +} + + void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level) { if (_activity != level) { @@ -290,11 +309,16 @@ 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; } @@ -304,6 +328,11 @@ void BufferItem::clearActivityLevel() 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; } @@ -324,19 +353,43 @@ void BufferItem::updateActivityLevel(const Message &msg) _firstUnreadMsgId = msg.msgId(); } + Message::Types type; + // If the core handles activities, ignore types + if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) { + type = Message::Types(); + } else { + type = msg.type(); + } + + if (addActivity(type, msg.flags().testFlag(Message::Highlight)) || stateChanged) { + emit dataChanged(); + } +} + +void BufferItem::setActivity(Message::Types type, bool highlight) { BufferInfo::ActivityLevel oldLevel = activityLevel(); - _activity |= BufferInfo::OtherActivity; - if (msg.type() & (Message::Plain | Message::Notice | Message::Action)) + _activity &= BufferInfo::Highlight; + addActivity(type, highlight); + + 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 (msg.flags() & Message::Highlight) + if (highlight) _activity |= BufferInfo::Highlight; - stateChanged |= (oldLevel != _activity); - - if (stateChanged) - emit dataChanged(); + return oldActivity != _activity; } @@ -392,7 +445,7 @@ 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; } @@ -519,8 +572,7 @@ QString QueryBufferItem::toolTip(int column) const Q_UNUSED(column); QString strTooltip; QTextStream tooltip( &strTooltip, QIODevice::WriteOnly ); - tooltip << "" - << ""; + tooltip << ""; // Keep track of whether or not information has been added bool infoAdded = false; @@ -549,26 +601,66 @@ QString QueryBufferItem::toolTip(int column) const tooltip << ""; if (_ircUser->isAway()) { - QString awayMessage(tr("(unknown)")); - if(!_ircUser->awayMessage().isEmpty()) { - awayMessage = _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), NetworkItem::escapeHTML(awayMessage), true); + addRow(NetworkItem::escapeHTML(tr("Away message"), true), awayMessageHTML, true); } addRow(tr("Realname"), NetworkItem::escapeHTML(_ircUser->realName()), !_ircUser->realName().isEmpty()); - addRow(NetworkItem::escapeHTML(tr("Suser Host"), true), - NetworkItem::escapeHTML(_ircUser->suserHost()), - !_ircUser->suserHost().isEmpty()); - addRow(NetworkItem::escapeHTML(tr("Whois Service Reply"), true), - NetworkItem::escapeHTML(_ircUser->whoisServiceReply()), - !_ircUser->whoisServiceReply().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()); + } + + // 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()), + NetworkItem::escapeHTML(_ircUser->ircOperator().replace("is an ", "").replace("is a ", "")), !_ircUser->ircOperator().isEmpty()); if (_ircUser->idleTime().isValid()) { @@ -588,7 +680,7 @@ QString QueryBufferItem::toolTip(int column) const // If no further information found, offer an explanatory message if (!infoAdded) - tooltip << "

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

"; + tooltip << "

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

"; tooltip << ""; return strTooltip; @@ -618,8 +710,22 @@ void QueryBufferItem::setIrcUser(IrcUser *ircUser) 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, 0, this, 0); + + // Clear IrcUser (only set to 0 if not already 0) + _ircUser = 0; + + // 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(); + } } @@ -630,6 +736,7 @@ ChannelBufferItem::ChannelBufferItem(const BufferInfo &bufferInfo, AbstractTreeI : BufferItem(bufferInfo, parent), _ircChannel(0) { + setFlags(flags() | Qt::ItemIsDropEnabled); } @@ -649,8 +756,7 @@ QString ChannelBufferItem::toolTip(int column) const Q_UNUSED(column); QString strTooltip; QTextStream tooltip( &strTooltip, QIODevice::WriteOnly ); - tooltip << "" - << ""; + tooltip << ""; // Function to add a row to the tooltip table auto addRow = [&](const QString& key, const QString& value, bool condition) { @@ -685,7 +791,7 @@ QString ChannelBufferItem::toolTip(int column) const tooltip << "
"; } else { - tooltip << "

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

"; + tooltip << "

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

"; } tooltip << "
"; @@ -727,6 +833,16 @@ 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() { @@ -883,7 +999,7 @@ void ChannelBufferItem::userModeChanged(IrcUser *ircUser) const QList UserCategoryItem::categories = QList() << 'q' << 'a' << 'o' << 'h' << 'v'; UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent) - : PropertyMapItem(QStringList() << "categoryName", parent), + : PropertyMapItem(parent), _category(category) { setFlags(Qt::ItemIsEnabled); @@ -892,6 +1008,13 @@ UserCategoryItem::UserCategoryItem(int category, AbstractTreeItem *parent) } +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 { @@ -985,7 +1108,7 @@ QVariant UserCategoryItem::data(int column, int role) const * Irc User Items *****************************************/ IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent) - : PropertyMapItem(QStringList() << "nickName", parent), + : PropertyMapItem(parent), _ircUser(ircUser) { setObjectName(ircUser->nick()); @@ -995,6 +1118,13 @@ IrcUserItem::IrcUserItem(IrcUser *ircUser, AbstractTreeItem *parent) } +QStringList IrcUserItem::propertyOrder() const +{ + static QStringList order{"nickName"}; + return order; +} + + QVariant IrcUserItem::data(int column, int role) const { switch (role) { @@ -1025,8 +1155,7 @@ QString IrcUserItem::toolTip(int column) const Q_UNUSED(column); QString strTooltip; QTextStream tooltip( &strTooltip, QIODevice::WriteOnly ); - tooltip << "" - << ""; + tooltip << ""; // Keep track of whether or not information has been added bool infoAdded = false; @@ -1048,27 +1177,71 @@ QString IrcUserItem::toolTip(int column) const }; tooltip << ""; + addRow(tr("Modes"), + NetworkItem::escapeHTML(channelModes()), + !channelModes().isEmpty()); if (_ircUser->isAway()) { - QString awayMessage(tr("(unknown)")); - if(!_ircUser->awayMessage().isEmpty()) { - awayMessage = _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), NetworkItem::escapeHTML(awayMessage), true); + addRow(NetworkItem::escapeHTML(tr("Away message"), true), awayMessageHTML, true); } addRow(tr("Realname"), NetworkItem::escapeHTML(_ircUser->realName()), !_ircUser->realName().isEmpty()); - addRow(NetworkItem::escapeHTML(tr("Suser Host"), true), - NetworkItem::escapeHTML(_ircUser->suserHost()), - !_ircUser->suserHost().isEmpty()); - addRow(NetworkItem::escapeHTML(tr("Whois Service Reply"), true), - NetworkItem::escapeHTML(_ircUser->whoisServiceReply()), - !_ircUser->whoisServiceReply().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()); + } + + // 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()), + NetworkItem::escapeHTML(_ircUser->ircOperator().replace("is an ", "").replace("is a ", "")), !_ircUser->ircOperator().isEmpty()); if (_ircUser->idleTime().isValid()) { @@ -1087,12 +1260,27 @@ QString IrcUserItem::toolTip(int column) const // If no further information found, offer an explanatory message if (!infoAdded) - tooltip << "

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

"; + 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. + UserCategoryItem *category = qobject_cast(parent()); + if (!category) + return QString(); + + ChannelBufferItem *channel = qobject_cast(category->parent()); + if (!channel) + return QString(); + + return channel->nickChannelModes(nickName()); +} + /***************************************** * NetworkModel @@ -1582,3 +1770,24 @@ 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); +}