diff --git a/src/common.h b/src/common.h index 694a5a09..bd55ab69 100644 --- a/src/common.h +++ b/src/common.h @@ -1,98 +1,102 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2004 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #ifndef COMMON_H #define COMMON_H +#include #include #include #include class QString; class QPixmap; namespace Konversation { + Q_NAMESPACE + static QRegExp colorRegExp(QStringLiteral("((\003([0-9]|0[0-9]|1[0-5])(,([0-9]|0[0-9]|1[0-5])|)|\017)|\x02|\x03|\x09|\x13|\x15|\x16|\x1d|\x1f)")); static QRegExp urlPattern(QString(QStringLiteral("\\b((?:(?:([a-z][\\w\\.-]+:/{1,3})|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|\\}\\]|[^\\s`!()\\[\\]{};:'\".,<>?%1%2%3%4%5%6])|[a-z0-9.\\-+_]+@[a-z0-9.\\-]+[.][a-z]{1,5}[^\\s/`!()\\[\\]{};:'\".,<>?%1%2%3%4%5%6]))")).arg(QChar(0x00AB)).arg(QChar(0x00BB)).arg(QChar(0x201C)).arg(QChar(0x201D)).arg(QChar(0x2018)).arg(QChar(0x2019))); static QRegExp chanExp(QString(QStringLiteral("(^|\\s|^\"|\\s\"|,|'|\\(|\\:|!|@|%|\\+)(#[^,\\s;\\)\\:\\/\\(\\<\\>]*[^.,\\s;\\)\\:\\/\\(\"\''\\<\\>?%1%2%3%4%5%6])")).arg(QChar(0x00AB)).arg(QChar(0x00BB)).arg(QChar(0x201C)).arg(QChar(0x201D)).arg(QChar(0x2018)).arg(QChar(0x2019))); enum TabNotifyType { tnfNick, tnfHighlight, tnfPrivate, tnfNormal, tnfSystem, tnfControl, tnfNone }; enum ConnectionState { - SSNeverConnected, - SSDeliberatelyDisconnected, - SSInvoluntarilyDisconnected, - SSScheduledToConnect, - SSConnecting, - SSConnected + NeverConnected, + DeliberatelyDisconnected, + InvoluntarilyDisconnected, + ScheduledToConnect, + Connecting, + Connected }; + Q_ENUM_NS(ConnectionState) enum ConnectionFlag { SilentlyReuseConnection, PromptToReuseConnection, CreateNewConnection }; struct TextUrlData { QList > urlRanges; QStringList fixedUrls; }; struct TextChannelData { QList > channelRanges; QStringList fixedChannels; }; QString removeIrcMarkup(const QString& text); QString doVarExpansion(const QString& text); QString replaceFormattingCodes(const QString& text); QString replaceIRCMarkups(const QString& text); inline bool hasIRCMarkups(const QString& text) { static QRegExp ircMarkupsRegExp(QStringLiteral("[\\0000-\\0037]")); return text.contains(ircMarkupsRegExp); } QList > getUrlRanges(const QString& text); QList > getChannelRanges(const QString& text); TextUrlData extractUrlData(const QString& string, bool doUrlFixup = true); TextChannelData extractChannelData(const QString& text, bool doChannelFixup = true); bool isUrl(const QString& text); QString extractColorCodes(const QString& text); QPixmap overlayPixmaps(const QPixmap &under, const QPixmap &over); bool isUtf8(const QByteArray& text); uint colorForNick(const QString& nickname); static QHash m_modesHash; QHash getChannelModesHash(); QString sterilizeUnicode(const QString& s); QString& sterilizeUnicode(QString& s); QStringList& sterilizeUnicode(QStringList& list); QStringList sterilizeUnicode(const QStringList& inVal); } #endif diff --git a/src/connectionmanager.cpp b/src/connectionmanager.cpp index 9f672323..3fc5d952 100644 --- a/src/connectionmanager.cpp +++ b/src/connectionmanager.cpp @@ -1,707 +1,707 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2008 Eike Hein */ #include "connectionmanager.h" #include "connectionsettings.h" #include "serversettings.h" #include "servergroupsettings.h" #include "config/preferences.h" #include "application.h" #include "mainwindow.h" #include "statuspanel.h" #include #include #include #include ConnectionManager::ConnectionManager(QObject* parent) : QObject(parent), m_overrideAutoReconnect (false) { // Reenable the check again when it works reliably for all backends // if (Solid::Networking::status() != Solid::Networking::Connected) // m_overrideAutoReconnect = true; connect(this, &ConnectionManager::requestReconnect, this, &ConnectionManager::handleReconnect); } ConnectionManager::~ConnectionManager() = default; void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, const QString& target, const QString& port, const QString& password, const QString& nick, const QString& channel, bool useSSL) { ConnectionSettings settings; if (target.startsWith(QLatin1String("irc://")) || target.startsWith(QLatin1String("ircs://"))) decodeIrcUrl(target, settings); else { decodeAddress(target, settings); Konversation::ServerSettings server = settings.server(); if (!port.isEmpty()) server.setPort(port.toInt()); if (!password.isEmpty()) server.setPassword(password); if (useSSL) server.setSSLEnabled(true); settings.setServer(server); if (!nick.isEmpty()) settings.setInitialNick(nick); if (!channel.isEmpty()) { Konversation::ChannelSettings channelSettings(channel); Konversation::ChannelList cl; cl << channelSettings; settings.setOneShotChannelList(cl); } } connectTo(flag, settings); } void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, int serverGroupId) { ConnectionSettings settings; Konversation::ServerGroupSettingsPtr serverGroup; serverGroup = Preferences::serverGroupById(serverGroupId); if (serverGroup) { settings.setServerGroup(serverGroup); if (serverGroup->serverList().size() > 0) settings.setServer(serverGroup->serverList()[0]); } connectTo(flag, settings); } void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, const QList& list) { QMap serverChannels; QMap serverConnections; QList::ConstIterator it = list.constBegin(); QList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { ConnectionSettings settings; decodeIrcUrl(it->url(), settings); qDebug() << settings.name() << " - " << settings.server().host() << settings.server().port() << settings.server().password() << " - " << (settings.serverGroup()?settings.serverGroup()->name():QString()); QString sname = (settings.serverGroup() ? settings.serverGroup()->name() : (settings.server().host() + QLatin1Char(':') + QString::number(settings.server().port()))); if (!serverChannels.contains(sname)) serverConnections[sname] = settings; serverChannels[sname] += settings.oneShotChannelList(); } // Perform the connection. QMap::ConstIterator s_i = serverChannels.constBegin(); for (; s_i != serverChannels.constEnd(); ++s_i) { serverConnections[s_i.key()].setOneShotChannelList(s_i.value()); connectTo(flag, serverConnections[s_i.key()]); } } void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, ConnectionSettings settings) { if (!settings.isValid()) return; emit closeServerList(); if (flag != Konversation::CreateNewConnection && reuseExistingConnection(settings, (flag == Konversation::PromptToReuseConnection))) { return; } IdentityPtr identity = settings.identity(); if (!identity || !validateIdentity(identity)) return; Application* konvApp = Application::instance(); MainWindow* mainWindow = konvApp->getMainWindow(); Server* server = new Server(this, settings); enlistConnection(server->connectionId(), server); connect(server, &Server::destroyed, this, &ConnectionManager::delistConnection); - connect(server, SIGNAL(connectionStateChanged(Server*,Konversation::ConnectionState)), - this, SLOT(handleConnectionStateChange(Server*,Konversation::ConnectionState))); + connect(server, &Server::connectionStateChanged, this, &ConnectionManager::handleConnectionStateChange); connect(server, &Server::awayState, this, &ConnectionManager::connectionChangedAwayState); connect(server, SIGNAL(nicksNowOnline(Server*,QStringList,bool)), mainWindow, SLOT(setOnlineList(Server*,QStringList,bool))); connect(server, SIGNAL(awayInsertRememberLine(Server*)), mainWindow, SIGNAL(triggerRememberLines(Server*))); connect(server, SIGNAL(multiServerCommand(QString,QString)), konvApp, SLOT(sendMultiServerCommand(QString,QString))); } void ConnectionManager::enlistConnection(int connectionId, Server* server) { m_connectionList.insert(connectionId, server); emit connectionListChanged(); } void ConnectionManager::delistConnection(int connectionId) { m_connectionList.remove(connectionId); emit connectionListChanged(); } -void ConnectionManager::handleConnectionStateChange(Server* server, Konversation::ConnectionState state) +void ConnectionManager::handleConnectionStateChange(Konversation::ConnectionState state) { + auto server = static_cast(sender()); emit connectionChangedState(server, state); int identityId = server->getIdentity()->id(); - if (state == Konversation::SSConnected) + if (state == Konversation::Connected) { m_overrideAutoReconnect = false; if (!m_activeIdentities.contains(identityId)) { m_activeIdentities.insert(identityId); emit identityOnline(identityId); } } - else if (state != Konversation::SSConnecting) + else if (state != Konversation::Connecting) { if (m_activeIdentities.contains(identityId)) { m_activeIdentities.remove(identityId); emit identityOffline(identityId); } } - if (state == Konversation::SSInvoluntarilyDisconnected && !m_overrideAutoReconnect) + if (state == Konversation::InvoluntarilyDisconnected && !m_overrideAutoReconnect) { // The asynchronous invocation of handleReconnect() makes sure that // connectionChangedState() is emitted and delivered before it runs // (and causes the next connection state change to occur). emit requestReconnect(server); } - else if (state == Konversation::SSInvoluntarilyDisconnected && m_overrideAutoReconnect) + else if (state == Konversation::InvoluntarilyDisconnected && m_overrideAutoReconnect) { server->getStatusView()->appendServerMessage(i18n("Info"), i18n ("Network is down, will reconnect automatically when it is back up.")); } } void ConnectionManager::handleReconnect(Server* server) { if (!Preferences::self()->autoReconnect() || m_overrideAutoReconnect) return; ConnectionSettings settings = server->getConnectionSettings(); uint reconnectCount = Preferences::self()->reconnectCount(); // For server groups, one iteration over their server list shall count as one // connection attempt. if (settings.serverGroup()) reconnectCount = reconnectCount * settings.serverGroup()->serverList().size(); if (reconnectCount == 0 || settings.reconnectCount() < reconnectCount) { if (settings.serverGroup() && settings.serverGroup()->serverList().size() > 1) { Konversation::ServerList serverList = settings.serverGroup()->serverList(); int index = serverList.indexOf(settings.server()); int size = serverList.size(); if (index == size - 1 || index == -1) settings.setServer(serverList[0]); else if (index < size - 1) settings.setServer(serverList[index+1]); server->setConnectionSettings(settings); server->getStatusView()->appendServerMessage(i18n("Info"), i18np( "Trying to connect to %2 (port %3) in 1 second.", "Trying to connect to %2 (port %3) in %1 seconds.", Preferences::self()->reconnectDelay(), settings.server().host(), QString::number(settings.server().port()))); } else { server->getStatusView()->appendServerMessage(i18n("Info"), i18np( "Trying to reconnect to %2 (port %3) in 1 second.", "Trying to reconnect to %2 (port %3) in %1 seconds.", Preferences::self()->reconnectDelay(), settings.server().host(), QString::number(settings.server().port()))); } server->getConnectionSettings().incrementReconnectCount(); server->connectToIRCServerIn(Preferences::self()->reconnectDelay()); } else { server->getConnectionSettings().setReconnectCount(0); server->getStatusView()->appendServerMessage(i18n("Error"), i18n("Reconnection attempts exceeded.")); } } void ConnectionManager::quitServers() { QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) it.value()->quitServer(); } void ConnectionManager::reconnectServers() { QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) it.value()->reconnectServer(); } void ConnectionManager::decodeIrcUrl(const QString& url, ConnectionSettings& settings) { if (!url.startsWith(QLatin1String("irc://")) && !url.startsWith(QLatin1String("ircs://"))) return; QString mangledUrl = url; mangledUrl.remove(QRegExp(QStringLiteral("^ircs?:/+"))); if (mangledUrl.isEmpty()) return; // Parsing address and channel. QStringList mangledUrlSegments; mangledUrlSegments = mangledUrl.split(QLatin1Char('/'), QString::KeepEmptyParts); // Check for ",isserver". if (mangledUrlSegments[0].contains(QLatin1Char(','))) { QStringList addressSegments; bool checkIfServerGroup = true; addressSegments = mangledUrlSegments[0].split(QLatin1Char(','), QString::KeepEmptyParts); if (addressSegments.filter(QStringLiteral("isserver")).size() > 0) checkIfServerGroup = false; decodeAddress(addressSegments[0], settings, checkIfServerGroup); } else decodeAddress(mangledUrlSegments[0], settings); QString channel; Konversation::ChannelSettings channelSettings; // Grabbing channel from in front of potential ?key=value parameters. if (mangledUrlSegments.size() > 1) channel = mangledUrlSegments[1].section(QLatin1Char('?'), 0, 0); if (!channel.isEmpty()) { // Add default prefix to channel if necessary. if (!channel.contains(QRegExp(QStringLiteral("^[#+&]{1}")))) channel = QLatin1Char('#') + channel; channelSettings.setName(channel); } // Parsing ?key=value parameters. QString parameterString; if (mangledUrlSegments.size() > 1) parameterString = mangledUrlSegments[1].section(QLatin1Char('?'), 1); if (parameterString.isEmpty() && mangledUrlSegments.size() > 2) parameterString = mangledUrlSegments[2]; if (!parameterString.isEmpty()) { QRegExp parameterCatcher; parameterCatcher.setPattern(QStringLiteral("pass=([^&]+)")); if (parameterCatcher.indexIn(parameterString) != -1) { Konversation::ServerSettings server = settings.server(); server.setPassword(parameterCatcher.cap(1)); settings.setServer(server); } parameterCatcher.setPattern(QStringLiteral("key=([^&]+)")); if (parameterCatcher.indexIn(parameterString) != -1) channelSettings.setPassword(parameterCatcher.cap(1)); } // Assigning channel. if (!channelSettings.name().isEmpty()) { Konversation::ChannelList cl; cl << channelSettings; settings.setOneShotChannelList(cl); } // Override SSL setting state with directive from URL. if (url.startsWith(QLatin1String("ircs://"))) { Konversation::ServerSettings server = settings.server(); server.setSSLEnabled(true); settings.setServer(server); } } void ConnectionManager::decodeAddress(const QString& address, ConnectionSettings& settings, bool checkIfServerGroup) { QString host; QString port = QStringLiteral("6667"); // Full-length IPv6 address with port // Example: RFC 2732 notation: [2001:0DB8:0000:0000:0000:0000:1428:57ab]:6666 // Example: Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab:6666 if (address.count(QLatin1Char(':'))==8) { host = address.section(QLatin1Char(':'),0,-2).remove(QLatin1Char('[')).remove(QLatin1Char(']')); port = address.section(QLatin1Char(':'),-1); } // Full-length IPv6 address without port or not-full-length IPv6 address with port // Example: Without port, RFC 2732 notation: [2001:0DB8:0000:0000:0000:0000:1428:57ab] // Example: Without port, Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab // Example: With port, RFC 2732 notation: [2001:0DB8::1428:57ab]:6666 else if (address.count(QLatin1Char(':'))>=4) { // Last segment does not end with ], but the next to last does; // Assume not-full-length IPv6 address with port // Example: [2001:0DB8::1428:57ab]:6666 if (address.section(QLatin1Char(':'),0,-2).endsWith(QLatin1Char(']')) && !address.section(QLatin1Char(':'),-1).endsWith(QLatin1Char(']'))) { host = address.section(QLatin1Char(':'),0,-2).remove(QLatin1Char('[')).remove(QLatin1Char(']')); port = address.section(QLatin1Char(':'),-1); } else { QString addressCopy = address; host = addressCopy.remove(QLatin1Char('[')).remove(QLatin1Char(']')); } } // IPv4 address or ordinary hostname with port // Example: IPv4 address with port: 123.123.123.123:6666 // Example: Hostname with port: irc.bla.org:6666 else if (address.count(QLatin1Char(':'))==1) { host = address.section(QLatin1Char(':'),0,-2); port = address.section(QLatin1Char(':'),-1); } else host = address; // Try to assign server group. if (checkIfServerGroup && Preferences::isServerGroup(host)) { // If host is found to be the name of a server group. int serverGroupId = Preferences::serverGroupIdsByName(host).first(); Konversation::ServerGroupSettingsPtr serverGroup; serverGroup = Preferences::serverGroupById(serverGroupId); settings.setServerGroup(serverGroup); if (serverGroup->serverList().size() > 0) settings.setServer(serverGroup->serverList()[0]); } else { QList groups = Preferences::serverGroupsByServer(host); if (!groups.isEmpty()) { // If the host is found to be part of a server group's server list. Konversation::ServerGroupSettingsPtr serverGroup = groups.first(); settings.setServerGroup(serverGroup); } Konversation::ServerSettings server; server.setHost(host); server.setPort(port.toInt()); settings.setServer(server); } } bool ConnectionManager::reuseExistingConnection(ConnectionSettings& settings, bool interactive) { Server* dupe = nullptr; ConnectionDupe dupeType; bool doReuse = true; Application* konvApp = Application::instance(); MainWindow* mainWindow = konvApp->getMainWindow(); QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) { if (it.value()->getServerGroup() && settings.serverGroup() && it.value()->getServerGroup() == settings.serverGroup()) { dupe = it.value(); dupeType = SameServerGroup; break; } } if (!dupe) { for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) { if (it.value()->getConnectionSettings().server() == settings.server()) { dupe = it.value(); dupeType = SameServer; break; } } } if (dupe && interactive) { int result = KMessageBox::warningContinueCancel( mainWindow, i18n("You are already connected to %1. Do you want to open another connection?", dupe->getDisplayName()), i18n("Already connected to %1", dupe->getDisplayName()), KGuiItem(i18n("Create connection")), KStandardGuiItem::cancel(), QStringLiteral("ReuseExistingConnection")); if (result == KMessageBox::Continue) doReuse = false; } if (dupe && doReuse) { if (interactive && dupeType == SameServerGroup && !(dupe->getConnectionSettings().server() == settings.server())) { int result = KMessageBox::warningContinueCancel( mainWindow, i18n("You are presently connected to %1 via '%2' (port %3). Do you want to switch to '%4' (port %5) instead?", dupe->getDisplayName(), dupe->getServerName(), QString::number(dupe->getPort()), settings.server().host(), QString::number(settings.server().port())), i18n("Already connected to %1", dupe->getDisplayName()), KGuiItem(i18n("Switch Server")), KStandardGuiItem::cancel(), QStringLiteral("ReconnectWithDifferentServer")); if (result == KMessageBox::Continue) { dupe->disconnectServer(); dupe->setConnectionSettings(settings); } } if (!dupe->isConnected()) { if (!settings.oneShotChannelList().isEmpty()) dupe->updateAutoJoin(settings.oneShotChannelList()); if (!dupe->isConnecting()) dupe->reconnectServer(); } else { if (!settings.oneShotChannelList().isEmpty()) { Konversation::ChannelList::ConstIterator it = settings.oneShotChannelList().constBegin(); Konversation::ChannelList::ConstIterator itend = settings.oneShotChannelList().constEnd(); for ( ; it != itend; ++it ) { dupe->sendJoinCommand((*it).name(), (*it).password()); } settings.clearOneShotChannelList(); } } } return (dupe && doReuse); } bool ConnectionManager::validateIdentity(IdentityPtr identity, bool interactive) { Application* konvApp = Application::instance(); MainWindow* mainWindow = konvApp->getMainWindow(); QString errors; if (identity->getIdent().isEmpty()) errors+=i18n("Please fill in your Ident.
"); if (identity->getRealName().isEmpty()) errors+=i18n("Please fill in your Real name.
"); if (identity->getNickname(0).isEmpty()) errors+=i18n("Please provide at least one Nickname.
"); if (!errors.isEmpty()) { if (interactive) { int result = KMessageBox::warningContinueCancel( mainWindow, i18n("Your identity \"%1\" is not set up correctly:
%2
", identity->getName(), errors), i18n("Identity Settings"), KGuiItem(i18n("Edit Identity...")), KStandardGuiItem::cancel()); if (result == KMessageBox::Continue) { identity = mainWindow->editIdentity(identity); if (identity && validateIdentity(identity, false)) return true; else return false; } else return false; } return false; } return true; } QList ConnectionManager::getServerList() const { QList serverList; QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) serverList.append(it.value()); return serverList; } Server* ConnectionManager::getServerByConnectionId(int connectionId) { if (m_connectionList.contains(connectionId)) return m_connectionList[connectionId]; else return nullptr; } Server* ConnectionManager::getServerByName(const QString& name, NameMatchFlags flags) { if (flags == MatchByIdThenName) { bool conversion = false; const int connectionId = name.toInt(&conversion); if (conversion) { Server* const server = this->getServerByConnectionId(connectionId); if (server) return server; } } QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) { if (it.value()->getDisplayName() == name || it.value()->getServerName() == name) return it.value(); } return nullptr; } void ConnectionManager::involuntaryQuitServers() { m_overrideAutoReconnect = true; QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) it.value()->involuntaryQuit(); } void ConnectionManager::reconnectInvoluntary() { m_overrideAutoReconnect = false; QMap::ConstIterator it; for (it = m_connectionList.constBegin(); it != m_connectionList.constEnd(); ++it) it.value()->reconnectInvoluntary(); } void ConnectionManager::onOnlineStateChanged(bool isOnline) { if (isOnline) { reconnectInvoluntary(); } else { involuntaryQuitServers(); } } diff --git a/src/connectionmanager.h b/src/connectionmanager.h index ef41aa88..e7260f87 100644 --- a/src/connectionmanager.h +++ b/src/connectionmanager.h @@ -1,109 +1,109 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2008 Eike Hein */ #ifndef CONNECTIONMANAGER_H #define CONNECTIONMANAGER_H #include "server.h" #include "identity.h" #include #include class ConnectionSettings; class ConnectionManager : public QObject { Q_OBJECT public: explicit ConnectionManager(QObject* parent = nullptr); ~ConnectionManager() override; uint connectionCount() const { return m_connectionList.count(); } QList getServerList() const; enum NameMatchFlags { MatchByName, MatchByIdThenName }; Server* getServerByConnectionId(int connectionId); Server* getServerByName(const QString& name, NameMatchFlags flags = MatchByName); public Q_SLOTS: void connectTo(Konversation::ConnectionFlag flag, const QString& target, const QString& port = QString(), const QString& password = QString(), const QString& nick = QString(), const QString& channel = QString(), bool useSSL = false); void connectTo(Konversation::ConnectionFlag flag, int serverGroupId); void connectTo(Konversation::ConnectionFlag flag, const QList& list); void connectTo(Konversation::ConnectionFlag flag, ConnectionSettings settings); void quitServers(); void reconnectServers(); void involuntaryQuitServers(); void reconnectInvoluntary(); Q_SIGNALS: void connectionListChanged(); void connectionChangedState(Server* server, Konversation::ConnectionState state); void connectionChangedAwayState(bool away); void requestReconnect(Server* server); void identityOnline(int identityId); void identityOffline(int identityId); void closeServerList(); private Q_SLOTS: void delistConnection(int connectionId); - void handleConnectionStateChange(Server* server, Konversation::ConnectionState state); + void handleConnectionStateChange(Konversation::ConnectionState state); void handleReconnect(Server* server); void onOnlineStateChanged(bool isOnline); private: void enlistConnection(int connectionId, Server* server); void decodeIrcUrl(const QString& url, ConnectionSettings& settings); void decodeAddress(const QString& address, ConnectionSettings& settings, bool checkIfServerGroup = true); bool reuseExistingConnection(ConnectionSettings& settings, bool interactive); bool validateIdentity(IdentityPtr identity, bool interactive = true); QMap m_connectionList; QSet m_activeIdentities; bool m_overrideAutoReconnect; enum ConnectionDupe { SameServer, SameServerGroup }; }; #endif diff --git a/src/irc/channel.cpp b/src/irc/channel.cpp index 85454e67..6ce6ce9c 100644 --- a/src/irc/channel.cpp +++ b/src/irc/channel.cpp @@ -1,2903 +1,2903 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2004-2016 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #include "channel.h" #include "channeloptionsdialog.h" #include "application.h" #include "server.h" #include "nick.h" #include "nicklistview.h" #include "quickbutton.h" #include "modebutton.h" #include "ircinput.h" #include "ircviewbox.h" #include "ircview.h" #include "awaylabel.h" #include "topiclabel.h" #include "topichistorymodel.h" #include "notificationhandler.h" #include "viewcontainer.h" #include // WIPQTQUICK #include #include #include #include #include #include #include #include #include #include #include #define DELAYED_SORT_TRIGGER 10 using namespace Konversation; bool nickTimestampLessThan(const Nick* nick1, const Nick* nick2) { int returnValue = nick2->getChannelNick()->timeStamp() - nick1->getChannelNick()->timeStamp(); if( returnValue == 0) { returnValue = QString::compare(nick1->getChannelNick()->loweredNickname(), nick2->getChannelNick()->loweredNickname()); } return (returnValue < 0); } static bool nickLessThan(const Nick* nick1, const Nick* nick2) { return nick1->getChannelNick()->loweredNickname() < nick2->getChannelNick()->loweredNickname(); } using Konversation::ChannelOptionsDialog; Channel::Channel(QWidget* parent, const QString& _name) : ChatWindow(parent) { // init variables //HACK I needed the channel name at time of setServer, but setName needs m_server.. // This effectively assigns the name twice, but none of the other logic has been moved or updated. name=_name; m_ownChannelNick = nullptr; m_optionsDialog = nullptr; m_delayedSortTimer = nullptr; m_delayedSortTrigger = 0; m_processedNicksCount = 0; m_processedOpsCount = 0; m_initialNamesReceived = false; nicks = 0; ops = 0; completionPosition = 0; m_nicknameListViewTextChanged = 0; m_joined = false; quickButtonsChanged = false; quickButtonsState = false; modeButtonsChanged = false; modeButtonsState = false; awayChanged = false; awayState = false; splittersInitialized = false; topicSplitterHidden = false; channelSplitterHidden = false; setType(ChatWindow::Channel); m_isTopLevelView = false; setChannelEncodingSupported(true); // Build some size policies for the widgets QSizePolicy hfixed = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); QSizePolicy hmodest = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QSizePolicy vfixed = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); QSizePolicy greedy = QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_vertSplitter = new QSplitter(Qt::Vertical, this); QWidget* topicWidget = new QWidget(m_vertSplitter); m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(topicWidget), 0); QGridLayout* topicLayout = new QGridLayout(topicWidget); topicLayout->setMargin(0); topicLayout->setSpacing(0); m_topicButton = new QToolButton(topicWidget); m_topicButton->setIcon(QIcon::fromTheme(QStringLiteral("document-edit"))); m_topicButton->setToolTip(i18n("Edit Channel Settings")); m_topicButton->setAutoRaise(true); connect(m_topicButton, SIGNAL(clicked()), this, SLOT(showOptionsDialog())); topicLine = new Konversation::TopicLabel(topicWidget); topicLine->setContextMenuOptions(IrcContextMenus::ShowChannelActions | IrcContextMenus::ShowLogAction, true); topicLine->setChannelName(getName()); topicLine->setWordWrap(true); topicLine->setWhatsThis(i18n("

Every channel on IRC has a topic associated with it. This is simply a message that everybody can see.

If you are an operator, or the channel mode 'T' has not been set, then you can change the topic by clicking the Edit Channel Properties button to the left of the topic. You can also view the history of topics there.

")); connect(topicLine, SIGNAL(setStatusBarTempText(QString)), this, SIGNAL(setStatusBarTempText(QString))); connect(topicLine, SIGNAL(clearStatusBarTempText()), this, SIGNAL(clearStatusBarTempText())); m_topicHistory = new TopicHistoryModel(this); connect(m_topicHistory, SIGNAL(currentTopicChanged(QString)), topicLine, SLOT(setText(QString))); topicLayout->addWidget(m_topicButton, 0, 0); topicLayout->addWidget(topicLine, 0, 1, -1, 1); // The box holding the channel modes modeBox = new QFrame(topicWidget); QHBoxLayout* modeBoxLayout = new QHBoxLayout(modeBox); modeBoxLayout->setMargin(0); modeBox->hide(); modeBox->setSizePolicy(hfixed); modeT = new ModeButton(QStringLiteral("T"),modeBox,0); modeBoxLayout->addWidget(modeT); modeN = new ModeButton(QStringLiteral("N"),modeBox,1); modeBoxLayout->addWidget(modeN); modeS = new ModeButton(QStringLiteral("S"),modeBox,2); modeBoxLayout->addWidget(modeS); modeI = new ModeButton(QStringLiteral("I"),modeBox,3); modeBoxLayout->addWidget(modeI); modeP = new ModeButton(QStringLiteral("P"),modeBox,4); modeBoxLayout->addWidget(modeP); modeM = new ModeButton(QStringLiteral("M"),modeBox,5); modeBoxLayout->addWidget(modeM); modeK = new ModeButton(QStringLiteral("K"),modeBox,6); modeBoxLayout->addWidget(modeK); modeL = new ModeButton(QStringLiteral("L"),modeBox,7); modeBoxLayout->addWidget(modeL); modeT->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('T')); modeN->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('N')); modeS->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('S')); modeI->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('I')); modeP->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('P')); modeM->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('M')); modeK->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('K')); modeL->setWhatsThis(ChannelOptionsDialog::whatsThisForMode('L')); connect(modeT,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeN,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeS,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeI,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeP,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeM,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeK,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); connect(modeL,SIGNAL(clicked(int,bool)),this,SLOT(modeButtonClicked(int,bool))); limit=new KLineEdit(modeBox); modeBoxLayout->addWidget(limit); limit->setToolTip(i18n("Maximum users allowed in channel")); limit->setWhatsThis(i18n("

This is the channel user limit - the maximum number of users that can be in the channel at a time. If you are an operator, you can set this. The channel mode Topic (button to left) will automatically be set if set this.

")); connect(limit,SIGNAL (returnPressed()),this,SLOT (channelLimitChanged()) ); connect(limit,SIGNAL (editingFinished()), this, SLOT(channelLimitChanged()) ); topicLayout->addWidget(modeBox, 0, 2); topicLayout->setRowStretch(1, 10); topicLayout->setColumnStretch(1, 10); showTopic(Preferences::self()->showTopic()); showModeButtons(Preferences::self()->showModeButtons()); // (this) The main Box, holding the channel view/topic and the input line m_horizSplitter = new QSplitter(m_vertSplitter); m_vertSplitter->setStretchFactor(m_vertSplitter->indexOf(m_horizSplitter), 1); // Server will be set later in setServer() IRCViewBox* ircViewBox = new IRCViewBox(m_horizSplitter); m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(ircViewBox), 1); setTextView(ircViewBox->ircView()); ircViewBox->ircView()->setContextMenuOptions(IrcContextMenus::ShowChannelActions, true); // The box that holds the Nick List and the quick action buttons nickListButtons = new QFrame(m_horizSplitter); m_horizSplitter->setStretchFactor(m_horizSplitter->indexOf(nickListButtons), 0); QVBoxLayout* nickListButtonsLayout = new QVBoxLayout(nickListButtons); nickListButtonsLayout->setSpacing(0); nickListButtonsLayout->setMargin(0); nicknameListView=new NickListView(nickListButtons, this); nickListButtons->layout()->addWidget(nicknameListView); nicknameListView->installEventFilter(this); // initialize buttons grid, will be set up in updateQuickButtons m_buttonsGrid = nullptr; // The box holding the Nickname button and Channel input commandLineBox = new QFrame(this); QHBoxLayout* commandLineLayout = new QHBoxLayout(commandLineBox); commandLineBox->setLayout(commandLineLayout); commandLineLayout->setMargin(0); commandLineLayout->setSpacing(spacing()); nicknameCombobox = new KComboBox(commandLineBox); nicknameCombobox->setEditable(true); nicknameCombobox->setSizeAdjustPolicy(KComboBox::AdjustToContents); KLineEdit* nicknameComboboxLineEdit = qobject_cast(nicknameCombobox->lineEdit()); if (nicknameComboboxLineEdit) nicknameComboboxLineEdit->setClearButtonShown(false); nicknameCombobox->setWhatsThis(i18n("

This shows your current nick, and any alternatives you have set up. If you select or type in a different nickname, then a request will be sent to the IRC server to change your nick. If the server allows it, the new nickname will be selected. If you type in a new nickname, you need to press 'Enter' at the end.

You can edit the alternative nicknames from the Identities option in the Settings menu.

")); awayLabel = new AwayLabel(commandLineBox); awayLabel->hide(); cipherLabel = new QLabel(commandLineBox); cipherLabel->hide(); cipherLabel->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("document-encrypt"), KIconLoader::Toolbar)); m_inputBar = new IRCInput(commandLineBox); commandLineLayout->addWidget(nicknameCombobox); commandLineLayout->addWidget(awayLabel); commandLineLayout->addWidget(cipherLabel); commandLineLayout->addWidget(m_inputBar); getTextView()->installEventFilter(m_inputBar); topicLine->installEventFilter(m_inputBar); m_inputBar->installEventFilter(this); // Set the widgets size policies m_topicButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); topicLine->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum)); commandLineBox->setSizePolicy(vfixed); limit->setMaximumSize(40,100); limit->setSizePolicy(hfixed); modeT->setMaximumSize(20,100); modeT->setSizePolicy(hfixed); modeN->setMaximumSize(20,100); modeN->setSizePolicy(hfixed); modeS->setMaximumSize(20,100); modeS->setSizePolicy(hfixed); modeI->setMaximumSize(20,100); modeI->setSizePolicy(hfixed); modeP->setMaximumSize(20,100); modeP->setSizePolicy(hfixed); modeM->setMaximumSize(20,100); modeM->setSizePolicy(hfixed); modeK->setMaximumSize(20,100); modeK->setSizePolicy(hfixed); modeL->setMaximumSize(20,100); modeL->setSizePolicy(hfixed); getTextView()->setSizePolicy(greedy); nicknameListView->setSizePolicy(hmodest); connect(m_inputBar,SIGNAL (submit()),this,SLOT (channelTextEntered()) ); connect(m_inputBar,SIGNAL (envelopeCommand()),this,SLOT (channelPassthroughCommand()) ); connect(m_inputBar,SIGNAL (nickCompletion()),this,SLOT (completeNick()) ); connect(m_inputBar,SIGNAL (endCompletion()),this,SLOT (endCompleteNick()) ); connect(m_inputBar,SIGNAL (textPasted(QString)),this,SLOT (textPasted(QString)) ); connect(getTextView(), SIGNAL(textPasted(bool)), m_inputBar, SLOT(paste(bool))); connect(getTextView(),SIGNAL (gotFocus()),m_inputBar,SLOT (setFocus()) ); connect(getTextView(),SIGNAL (sendFile()),this,SLOT (sendFileMenu()) ); connect(getTextView(),SIGNAL (autoText(QString)),this,SLOT (sendText(QString)) ); connect(nicknameListView,SIGNAL (itemDoubleClicked(QTreeWidgetItem*,int)),this,SLOT (doubleClickCommand(QTreeWidgetItem*,int)) ); connect(nicknameCombobox,SIGNAL (activated(int)),this,SLOT(nicknameComboboxChanged())); if(nicknameCombobox->lineEdit()) connect(nicknameCombobox->lineEdit(), SIGNAL (returnPressed()),this,SLOT(nicknameComboboxChanged())); connect(&userhostTimer,SIGNAL (timeout()),this,SLOT (autoUserhost())); m_whoTimer.setSingleShot(true); connect(&m_whoTimer, SIGNAL(timeout()), this, SLOT(autoWho())); connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(updateAutoWho())); // every 5 minutes decrease everyone's activity by 1 unit m_fadeActivityTimer.start(5*60*1000); connect(&m_fadeActivityTimer, SIGNAL(timeout()), this, SLOT(fadeActivity())); updateAppearance(); #ifdef HAVE_QCA2 m_cipher = nullptr; #endif // Setup delayed sort timer m_delayedSortTimer = new QTimer(this); m_delayedSortTimer->setSingleShot(true); connect(m_delayedSortTimer, SIGNAL(timeout()), this, SLOT(delayedSortNickList())); } //FIXME there is some logic in setLogfileName that needs to be split out and called here if the server display name gets changed void Channel::setServer(Server* server) { if (m_server != server) { - connect(server, SIGNAL(connectionStateChanged(Server*,Konversation::ConnectionState)), - SLOT(connectionStateChanged(Server*,Konversation::ConnectionState))); + connect(server, &Server::connectionStateChanged, this, &Channel::connectionStateChanged); connect(server, SIGNAL(nickInfoChanged()), this, SLOT(updateNickInfos())); connect(server, SIGNAL(channelNickChanged(QString)), this, SLOT(updateChannelNicks(QString))); } ChatWindow::setServer(server); if (!server->getKeyForRecipient(getName()).isEmpty()) cipherLabel->show(); topicLine->setServer(server); refreshModeButtons(); nicknameCombobox->setModel(m_server->nickListModel()); connect(awayLabel, SIGNAL(unaway()), m_server, SLOT(requestUnaway())); connect(awayLabel, SIGNAL(awayMessageChanged(QString)), m_server, SLOT(requestAway(QString))); } -void Channel::connectionStateChanged(Server* server, Konversation::ConnectionState state) +void Channel::connectionStateChanged(Konversation::ConnectionState state) { + Server *server = static_cast(sender()); if (server == m_server) { - if (state != Konversation::SSConnected) + if (state != Konversation::Connected) { m_joined = false; ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) { viewContainer->unsetViewNotification(this); } } } } void Channel::setEncryptedOutput(bool e) { #ifdef HAVE_QCA2 if (e) { cipherLabel->show(); if (!getCipher()->setKey(m_server->getKeyForRecipient(getName()))) return; m_topicHistory->setCipher(getCipher()); topicLine->setText(m_topicHistory->currentTopic()); } else { cipherLabel->hide(); m_topicHistory->clearCipher(); topicLine->setText(m_topicHistory->currentTopic()); } #else Q_UNUSED(e) #endif } Channel::~Channel() { qDebug() << "(" << getName() << ")"; // Purge nickname list purgeNicks(); qDebug() << "Nicks purged."; // Unlink this channel from channel list m_server->removeChannel(this); qDebug() << "Channel removed."; if (m_recreationScheduled) { QMetaObject::invokeMethod(m_server, "sendJoinCommand", Qt::QueuedConnection, Q_ARG(QString, getName()), Q_ARG(QString, getPassword())); } } bool Channel::rejoinable() { if (getServer() && getServer()->isConnected()) return !m_joined; return false; } void Channel::rejoin() { if (rejoinable()) m_server->sendJoinCommand(getName(), getPassword()); } bool Channel::log() { return ChatWindow::log() && !Preferences::self()->privateOnly(); } ChannelNickPtr Channel::getOwnChannelNick() const { return m_ownChannelNick; } ChannelNickPtr Channel::getChannelNick(const QString &ircnick) const { return m_server->getChannelNick(getName(), ircnick); } void Channel::purgeNicks() { m_ownChannelNick = nullptr; // Purge nickname list qDeleteAll(nicknameList); nicknameList.clear(); m_nicknameNickHash.clear(); // Execute this otherwise it may crash trying to access // deleted nicks nicknameListView->executeDelayedItemsLayout(); // clear stats counter nicks=0; ops=0; } void Channel::showOptionsDialog() { if (!m_optionsDialog) m_optionsDialog = new Konversation::ChannelOptionsDialog(this); m_optionsDialog->show(); } // Will be connected to NickListView::doubleClicked() void Channel::doubleClickCommand(QTreeWidgetItem *item, int column) { Q_UNUSED(column) if(item) { nicknameListView->clearSelection(); item->setSelected(true); // TODO: put the quick button code in another function to make reusal more legitimate quickButtonClicked(Preferences::self()->channelDoubleClickAction()); } } void Channel::completeNick() { int pos, oldPos; QTextCursor cursor = m_inputBar->textCursor(); pos = cursor.position(); oldPos = m_inputBar->getOldCursorPosition(); QString line=m_inputBar->toPlainText(); QString newLine; // Check if completion position is out of range if(completionPosition >= nicknameList.count()) completionPosition = 0; // Check, which completion mode is active char mode = m_inputBar->getCompletionMode(); if(mode == 'c') { line.remove(oldPos, pos - oldPos); pos = oldPos; } // If the cursor is at beginning of line, insert last completion if the nick is still around if(pos == 0 && !m_inputBar->lastCompletion().isEmpty() && nicknameList.containsNick(m_inputBar->lastCompletion())) { QString addStart(Preferences::self()->nickCompleteSuffixStart()); newLine = m_inputBar->lastCompletion() + addStart; // New cursor position is behind nickname pos = newLine.length(); // Add rest of the line newLine += line; } else { // remember old cursor position in input field m_inputBar->setOldCursorPosition(pos); // remember old cursor position locally oldPos = pos; // step back to []{}-_^`\| or start of line QString regexpStr("[^A-Z0-9a-z\\_\\[\\]\\{\\}\\-\\^\\`\\\\\\|"); if(!Preferences::self()->prefixCharacter().isEmpty()) regexpStr += "\\" + Preferences::self()->prefixCharacter(); regexpStr += QLatin1Char(']'); QRegExp tmp(regexpStr); pos = tmp.lastIndexIn(line, pos - 1); if (pos < 0) pos = 0; else pos++; // copy search pattern (lowercase) QString pattern = line.mid(pos, oldPos - pos); // copy line to newLine-buffer newLine = line; // did we find any pattern? if(!pattern.isEmpty()) { bool complete = false; QString foundNick; // try to find matching nickname in list of names if(Preferences::self()->nickCompletionMode() == 1 || Preferences::self()->nickCompletionMode() == 2) { // Shell like completion QStringList found; foundNick = nicknameList.completeNick(pattern, complete, found, (Preferences::self()->nickCompletionMode() == 2), Preferences::self()->nickCompletionCaseSensitive()); if(!complete && !found.isEmpty()) { if(Preferences::self()->nickCompletionMode() == 1) { QString nicksFound = found.join(QStringLiteral(" ")); appendServerMessage(i18n("Completion"), i18n("Possible completions: %1.", nicksFound)); } else { m_inputBar->showCompletionList(found); } } } // Cycle completion else if(Preferences::self()->nickCompletionMode() == 0 && !nicknameList.isEmpty()) { if(mode == '\0') { uint timeStamp = 0; int listPosition = 0; foreach (Nick* nick, nicknameList) { if(nick->getChannelNick()->getNickname().startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive) && (nick->getChannelNick()->timeStamp() > timeStamp)) { timeStamp = nick->getChannelNick()->timeStamp(); completionPosition = listPosition; } ++listPosition; } } // remember old nick completion position int oldCompletionPosition = completionPosition; complete = true; QString prefixCharacter = Preferences::self()->prefixCharacter(); do { QString lookNick = nicknameList.at(completionPosition)->getChannelNick()->getNickname(); if(!prefixCharacter.isEmpty() && lookNick.contains(prefixCharacter)) { lookNick = lookNick.section( prefixCharacter,1 ); } if(lookNick.startsWith(pattern, Preferences::self()->nickCompletionCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive)) { foundNick = lookNick; } // increment search position completionPosition++; // wrap around if(completionPosition == nicknameList.count()) { completionPosition = 0; } // the search ends when we either find a suitable nick or we end up at the // first search position } while((completionPosition != oldCompletionPosition) && foundNick.isEmpty()); } // did we find a suitable nick? if(!foundNick.isEmpty()) { // set channel nicks completion mode m_inputBar->setCompletionMode('c'); // remove pattern from line newLine.remove(pos, pattern.length()); // did we find the nick in the middle of the line? if(pos && complete) { m_inputBar->setLastCompletion(foundNick); QString addMiddle = Preferences::self()->nickCompleteSuffixMiddle(); newLine.insert(pos, foundNick + addMiddle); pos = pos + foundNick.length() + addMiddle.length(); } // no, it was at the beginning else if(complete) { m_inputBar->setLastCompletion(foundNick); QString addStart = Preferences::self()->nickCompleteSuffixStart(); newLine.insert(pos, foundNick + addStart); pos = pos + foundNick.length() + addStart.length(); } // the nick wasn't complete else { newLine.insert(pos, foundNick); pos = pos + foundNick.length(); } } // no pattern found, so restore old cursor position else pos = oldPos; } } // Set new text and cursor position m_inputBar->setText(newLine); cursor.setPosition(pos); m_inputBar->setTextCursor(cursor); } // make sure to step back one position when completion ends so the user starts // with the last complete they made void Channel::endCompleteNick() { if(completionPosition) completionPosition--; else completionPosition=nicknameList.count()-1; } QString Channel::getDescription() const { // WIPQTQUICK TODO Clean up when/how formatting is done. return TopicLabel::tagUrls(getTopic().replace('<', "\x0blt;").replace('>', "\x0bgt;"), getName()); } void Channel::setName(const QString& newName) { ChatWindow::setName(newName); setLogfileName(newName.toLower()); } bool Channel::autoJoin() { if (!m_server->getServerGroup()) return false; Konversation::ChannelList channelList = m_server->getServerGroup()->channelList(); return channelList.contains(channelSettings()); } void Channel::setAutoJoin(bool autojoin) { if (autojoin && !(autoJoin())) { Konversation::ChannelSettings before; QList channelList = m_server->getChannelList(); if (channelList.count() > 1) { QMap channelMap; int index = -1; int ownIndex = m_server->getViewContainer()->getViewIndex(this); foreach (Channel* channel, channelList) { index = m_server->getViewContainer()->getViewIndex(channel); if (index && index > ownIndex) channelMap.insert(index, channel); } if (channelMap.count()) { QMap::Iterator it2; Channel* channel; for (it2 = channelMap.begin(); it2 != channelMap.end(); ++it2) { channel = it2.value(); if (channel->autoJoin()) { before = channel->channelSettings(); break; } } } } if (m_server->getServerGroup()) m_server->getServerGroup()->addChannel(channelSettings(), before); } else { if (m_server->getServerGroup()) m_server->getServerGroup()->removeChannel(channelSettings()); } } QString Channel::getPassword() { QString password; for (QStringList::const_iterator it = m_modeList.constBegin(); it != m_modeList.constEnd(); ++it) { if ((*it)[0] == QLatin1Char('k')) password = (*it).mid(1); } if (password.isEmpty() && m_server->getServerGroup()) { Konversation::ChannelList channelSettingsList = m_server->getServerGroup()->channelList(); Konversation::ChannelSettings channelSettings(getName()); int index = channelSettingsList.indexOf(channelSettings); if(index >= 0) password = channelSettingsList.at(index).password(); } return password; } IrcContextMenus::MenuOptions Channel::contextMenuOptions() const { return IrcContextMenus::ShowChannelActions; } const Konversation::ChannelSettings Channel::channelSettings() { Konversation::ChannelSettings channel; channel.setName(getName()); channel.setPassword(getPassword()); channel.setNotificationsEnabled(notificationsEnabled()); return channel; } void Channel::sendFileMenu() { emit sendFile(); } void Channel::channelTextEntered() { QString line = m_inputBar->toPlainText(); m_inputBar->clear(); if (!line.isEmpty()) sendText(sterilizeUnicode(line)); } void Channel::channelPassthroughCommand() { QString commandChar = Preferences::self()->commandChar(); QString line = m_inputBar->toPlainText(); m_inputBar->clear(); if(!line.isEmpty()) { // Prepend commandChar on Ctrl+Enter to bypass outputfilter command recognition if (line.startsWith(commandChar)) { line = commandChar + line; } sendText(sterilizeUnicode(line)); } } void Channel::sendText(const QString& sendLine) { // create a work copy QString outputAll(sendLine); // replace aliases and wildcards m_server->getOutputFilter()->replaceAliases(outputAll, this); // Send all strings, one after another QStringList outList = outputAll.split(QRegExp(QStringLiteral("[\r\n]+")), QString::SkipEmptyParts); for(int index=0;indexgetOutputFilter()->parse(m_server->getNickname(), output, getName(), this); // Is there something we need to display for ourselves? if(!result.output.isEmpty()) { if(result.type == Konversation::Action) appendAction(m_server->getNickname(), result.output); else if(result.type == Konversation::Command) appendCommandMessage(result.typeString, result.output); else if(result.type == Konversation::Program) appendServerMessage(result.typeString, result.output); else if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output); else append(m_server->getNickname(), result.output); } else if (result.outputList.count()) { if (result.type == Konversation::Message) { QStringListIterator it(result.outputList); while (it.hasNext()) append(m_server->getNickname(), it.next()); } else if (result.type == Konversation::Action) { for (int i = 0; i < result.outputList.count(); ++i) { if (i == 0) appendAction(m_server->getNickname(), result.outputList.at(i)); else append(m_server->getNickname(), result.outputList.at(i)); } } } // Send anything else to the server if (!result.toServerList.empty()) m_server->queueList(result.toServerList); else m_server->queue(result.toServer); } } void Channel::setNickname(const QString& newNickname) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(newNickname)); } QStringList Channel::getSelectedNickList() { QStringList selectedNicks; foreach (Nick* nick, nicknameList) { if (nick->isSelected()) selectedNicks << nick->getChannelNick()->getNickname(); } return selectedNicks; } void Channel::channelLimitChanged() { unsigned int lim=limit->text().toUInt(); modeButtonClicked(7,lim>0); } void Channel::modeButtonClicked(int id, bool on) { char mode[]={'t','n','s','i','p','m','k','l'}; QString command(QStringLiteral("MODE %1 %2%3 %4")); QString args = getPassword(); if (mode[id] == 'k') { if (args.isEmpty()) { QPointer dlg = new KPasswordDialog(this); dlg->setPrompt(i18n("Channel Password")); if (dlg->exec() && !dlg->password().isEmpty()) { args = dlg->password(); } delete dlg; } } else if(mode[id]=='l') { if(limit->text().isEmpty() && on) { bool ok=false; // ask user how many nicks should be the limit args=QInputDialog::getText(this, i18n("Channel User Limit"), i18n("Enter the new user limit for the channel:"), QLineEdit::Normal, limit->text(), // will be always "" but what the hell ;) &ok); // leave this function if user cancels if(!ok) return; } else if(on) args=limit->text(); } // put together the mode command and send it to the server queue m_server->queue(command.arg(getName()).arg((on) ? QStringLiteral("+") : QStringLiteral("-")).arg(mode[id]).arg(args)); } void Channel::quickButtonClicked(const QString &buttonText) { // parse wildcards (toParse,nickname,channelName,nickList,queryName,parameter) QString out=m_server->parseWildcards(buttonText,m_server->getNickname(),getName(),getPassword(),getSelectedNickList(), m_inputBar->toPlainText()); // are there any newlines in the definition? if (out.contains(QLatin1Char('\n'))) sendText(out); // single line without newline needs to be copied into input line else m_inputBar->setText(out, true); } void Channel::addNickname(ChannelNickPtr channelnick) { QString nickname = channelnick->loweredNickname(); Nick* nick = nullptr; foreach (Nick* lookNick, nicknameList) { if(lookNick->getChannelNick()->loweredNickname() == nickname) { nick = lookNick; break; } } if (!nick) { fastAddNickname(channelnick); if(channelnick->isAnyTypeOfOp()) { adjustOps(1); } adjustNicks(1); requestNickListSort(); } else qWarning() << "Nickname " << channelnick->getNickname() << " has not been added as it is already in the nickname list."<< endl; } // Use with caution! Does not check for duplicates or may not // sort if delayed sorting is in effect. void Channel::fastAddNickname(ChannelNickPtr channelnick, Nick *nick) { Q_ASSERT(channelnick); if(!channelnick) return; if (!nick || !nick->treeWidget()) { // Deal with nicknameListView now (creating nick if necessary) NickListView::NoSorting noSorting(nicknameListView); int index = nicknameListView->topLevelItemCount(); // Append nick to the lists if (nick) { nicknameListView->addTopLevelItem(nick); } else { nick = new Nick(nicknameListView, this, channelnick); m_nicknameListViewTextChanged |= 0xFF; // new nick, text changed. } if (!m_delayedSortTimer->isActive()) { // Find its right place and insert where it belongs int newindex = nicknameListView->findLowerBound(*nick); if (newindex != index) { if (newindex >= index) newindex--; nicknameListView->takeTopLevelItem(index); nicknameListView->insertTopLevelItem(newindex, nick); } } // Otherwise it will be sorted by delayed sort. } // Now deal with nicknameList if (m_delayedSortTimer->isActive()) { // nicks get sorted later nicknameList.append(nick); } else { NickList::iterator it = qLowerBound(nicknameList.begin(), nicknameList.end(), nick, nickLessThan); nicknameList.insert(it, nick); } m_nicknameNickHash.insert (channelnick->loweredNickname(), nick); } /* Determines whether Nick/Part/Join event should be shown or skipped based on user settings. */ bool Channel::shouldShowEvent(ChannelNickPtr channelNick) { if (Preferences::self()->hideUnimportantEvents()) { if (channelNick && Preferences::self()->hideUnimportantEventsExcludeActive()) { uint activityThreshold = 3600; if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 0) // last 10 minutes activityThreshold = 600; if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 1) // last hour activityThreshold = 3600; else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 2) // last day activityThreshold = 86400; else if (Preferences::self()->hideUnimportantEventsExcludeActiveThreshold() == 3) // last week activityThreshold = 604800; if (m_server->isWatchedNick(channelNick->getNickname())) return true; // nick is on our watched list, so we probably want to see the event else if (channelNick->timeStamp()+activityThreshold > QDateTime::currentDateTime().toTime_t()) return true; // the nick has spoken within activity threshold else return false; } else return false; // if hideUnimportantEventsExcludeActive is off, we hide all events } else return true; // if hideUnimportantEvents is off we don't care and just show the event } void Channel::nickRenamed(const QString &oldNick, const NickInfo& nickInfo, const QHash &messageTags) { QString newNick = nickInfo.getNickname(); Nick *nick = getNickByName(oldNick); bool displayCommandMessage; if (Preferences::self()->hideUnimportantEventsExcludeActive() && m_server->isWatchedNick(oldNick)) displayCommandMessage = true; // this is for displaying watched people NICK events both ways (watched->unwatched and unwatched->watched) else if (nick) displayCommandMessage = shouldShowEvent(nick->getChannelNick()); else displayCommandMessage = shouldShowEvent(ChannelNickPtr()); // passing null pointer /* Did we change our nick name? */ if(newNick == m_server->getNickname()) /* Check newNick because m_server->getNickname() is already updated to new nick */ { setNickname(newNick); if (displayCommandMessage) appendCommandMessage(i18n("Nick"),i18n("You are now known as %1.", newNick), messageTags, true, true); } else if (displayCommandMessage) { /* No, must've been someone else */ appendCommandMessage(i18n("Nick"),i18n("%1 is now known as %2.", oldNick, newNick), messageTags); } if (nick) { m_nicknameNickHash.remove(oldNick.toLower()); m_nicknameNickHash.insert(newNick.toLower(), nick); repositionNick(nick); } } void Channel::joinNickname(ChannelNickPtr channelNick, const QHash &messageTags) { bool displayCommandMessage = shouldShowEvent(channelNick); if(channelNick->getNickname() == m_server->getNickname()) { m_joined = true; emit joined(this); if (displayCommandMessage) appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 = our hostmask, %2 = channel", "You (%1) have joined the channel %2.", channelNick->getHostmask(), getName()), messageTags, false, true); // Prepare for impending NAMES. purgeNicks(); nicknameListView->setUpdatesEnabled(false); m_ownChannelNick = channelNick; refreshModeButtons(); setActive(true); ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); //HACK the way the notification priorities work sucks, this forces the tab text color to ungray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) { Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this); } Application::instance()->notificationHandler()->channelJoin(this,getName()); } else { QString nick = channelNick->getNickname(); QString hostname = channelNick->getHostmask(); if (displayCommandMessage) appendCommandMessage(i18nc("Message type", "Join"), i18nc("%1 is the nick joining and %2 the hostmask of that nick", "%1 (%2) has joined this channel.", nick, hostname), messageTags, false); addNickname(channelNick); } } void Channel::removeNick(ChannelNickPtr channelNick, const QString &reason, bool quit, const QHash &messageTags) { bool displayCommandMessage = shouldShowEvent(channelNick); QString displayReason = reason; if(!displayReason.isEmpty()) { // if the reason contains text markup characters, play it safe and reset all if (hasIRCMarkups(displayReason)) displayReason += QStringLiteral("\017"); } if(channelNick->getNickname() == m_server->getNickname()) { if (displayCommandMessage) { //If in the future we can leave a channel, but not close the window, refreshModeButtons() has to be called. if (quit) { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Quit"), i18n("You (%1) have left this server.", channelNick->getHostmask()), messageTags); else appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = our hostmask, %2 = reason", "You (%1) have left this server (%2).", channelNick->getHostmask(), displayReason), messageTags, false); } else { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Part"), i18n("You have left channel %1.", getName()), messageTags); else appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = our hostmask, %2 = channel, %3 = reason", "You (%1) have left channel %2 (%3).", channelNick->getHostmask(), getName(), displayReason), messageTags, false); } } delete this; } else { if (displayCommandMessage) { if (quit) { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Quit"), i18n("%1 (%2) has left this server.", channelNick->getNickname(), channelNick->getHostmask()), messageTags, false); else appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostname, %3 = reason", "%1 (%2) has left this server (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false); } else { if (displayReason.isEmpty()) appendCommandMessage(i18nc("Message type", "Part"), i18n("%1 (%2) has left this channel.", channelNick->getNickname(), channelNick->getHostmask()), messageTags, false); else appendCommandMessage(i18nc("Message type", "Part"), i18nc("%1 = nick, %2 = hostmask, %3 = reason", "%1 (%2) has left this channel (%3).", channelNick->getNickname(), channelNick->getHostmask(), displayReason), messageTags, false); } } if(channelNick->isAnyTypeOfOp()) { adjustOps(-1); } adjustNicks(-1); Nick* nick = getNickByName(channelNick->loweredNickname()); if(nick) { nicknameList.removeOne(nick); m_nicknameNickHash.remove(channelNick->loweredNickname()); delete nick; // Execute this otherwise it may crash trying to access deleted nick nicknameListView->executeDelayedItemsLayout(); } else { qWarning() << "Nickname " << channelNick->getNickname() << " not found!"<< endl; } } } void Channel::flushNickQueue() { processQueuedNicks(true); } void Channel::kickNick(ChannelNickPtr channelNick, const QString &kicker, const QString &reason, const QHash &messageTags) { QString displayReason = reason; if(!displayReason.isEmpty()) { // if the reason contains text markup characters, play it safe and reset all if (hasIRCMarkups(displayReason)) displayReason += QStringLiteral("\017"); } if(channelNick->getNickname() == m_server->getNickname()) { if(kicker == m_server->getNickname()) { if (displayReason.isEmpty()) appendCommandMessage(i18n("Kick"), i18n("You have kicked yourself from channel %1.", getName()), messageTags); else appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel and %2 the reason", "You have kicked yourself from channel %1 (%2).", getName(), displayReason), messageTags); } else { if (displayReason.isEmpty()) { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 adds the kicker", "You have been kicked from channel %1 by %2.", getName(), kicker), messageTags); } else { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the channel, %2 the kicker and %3 the reason", "You have been kicked from channel %1 by %2 (%3).", getName(), kicker, displayReason), messageTags); } Application::instance()->notificationHandler()->kick(this,getName(), kicker); } m_joined=false; setActive(false); //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now. if (m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) Application::instance()->getMainWindow()->getViewContainer()->unsetViewNotification(this); return; } else { if(kicker == m_server->getNickname()) { if (displayReason.isEmpty()) appendCommandMessage(i18n("Kick"), i18n("You have kicked %1 from the channel.", channelNick->getNickname()), messageTags); else appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick and %2 the reason", "You have kicked %1 from the channel (%2).", channelNick->getNickname(), displayReason), messageTags); } else { if (displayReason.isEmpty()) { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 adds the kicker", "%1 has been kicked from the channel by %2.", channelNick->getNickname(), kicker), messageTags); } else { appendCommandMessage(i18n("Kick"), i18nc("%1 adds the kicked nick, %2 the kicker and %3 the reason", "%1 has been kicked from the channel by %2 (%3).", channelNick->getNickname(), kicker, displayReason), messageTags); } } if(channelNick->isAnyTypeOfOp()) adjustOps(-1); adjustNicks(-1); Nick* nick = getNickByName(channelNick->loweredNickname()); if(!nick) { qWarning() << "Nickname " << channelNick->getNickname() << " not found!"<< endl; } else { nicknameList.removeOne(nick); m_nicknameNickHash.remove(channelNick->loweredNickname()); delete nick; } } } Nick* Channel::getNickByName(const QString &lookname) const { QString lcLookname(lookname.toLower()); return m_nicknameNickHash.value(lcLookname); } void Channel::adjustNicks(int value) { if((nicks == 0) && (value <= 0)) { return; } nicks += value; if(nicks < 0) { nicks = 0; } emitUpdateInfo(); } void Channel::adjustOps(int value) { if((ops == 0) && (value <= 0)) { return; } ops += value; if(ops < 0) { ops = 0; } emitUpdateInfo(); } void Channel::emitUpdateInfo() { QString info = getName() + QStringLiteral(" - "); info += i18np("%1 nick", "%1 nicks", numberOfNicks()); info += i18np(" (%1 op)", " (%1 ops)", numberOfOps()); emit updateInfo(info); } QString Channel::getTopic() const { return m_topicHistory->currentTopic(); } void Channel::setTopic(const QString& text, const QHash &messageTags) { QString cleanTopic = text; // If the reason contains text markup characters, play it safe and reset all. if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic)) cleanTopic += QStringLiteral("\017"); appendCommandMessage(i18n("Topic"), i18n("The channel topic is \"%1\".", cleanTopic), messageTags); m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text))); emit descriptionChanged(); } void Channel::setTopic(const QString& nickname, const QString& text, const QHash &messageTags) { QString cleanTopic = text; // If the reason contains text markup characters, play it safe and reset all. if (!cleanTopic.isEmpty() && hasIRCMarkups(cleanTopic)) cleanTopic += QStringLiteral("\017"); if (nickname == m_server->getNickname()) appendCommandMessage(i18n("Topic"), i18n("You set the channel topic to \"%1\".", cleanTopic), messageTags); else appendCommandMessage(i18n("Topic"), i18n("%1 sets the channel topic to \"%2\".", nickname, cleanTopic), messageTags); m_topicHistory->appendTopic(replaceIRCMarkups(Konversation::removeIrcMarkup(text)), nickname); } void Channel::setTopicAuthor(const QString& author, QDateTime time) { if (time.isNull() || !time.isValid()) time = QDateTime::currentDateTime(); m_topicHistory->setCurrentTopicMetadata(author, time); } void Channel::updateMode(const QString& sourceNick, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { // Note for future expansion: // m_server->getChannelNick(getName(), sourceNick); // may not return a valid channelNickPtr if the mode is updated by // the server. // --johnflux, 9 September 2004 // Note: nick repositioning in the nicknameListView should be // triggered by nickinfo / channelnick signals QString message; ChannelNickPtr parameterChannelNick = m_server->getChannelNick(getName(), parameter); bool fromMe = false; bool toMe = false; // HACK right now Server only keeps type A modes bool banTypeThang = m_server->banAddressListModes().contains(QLatin1Char(mode)); // remember if this nick had any type of op. bool wasAnyOp = false; if (parameterChannelNick) { addNickname(parameterChannelNick); wasAnyOp = parameterChannelNick->isAnyTypeOfOp(); } if (sourceNick.toLower() == m_server->loweredNickname()) fromMe = true; if (parameter.toLower() == m_server->loweredNickname()) toMe = true; switch (mode) { case 'q': if (banTypeThang) { if (plus) { if (fromMe) message = i18n("You set a quiet on %1.", parameter); else message = i18n("%1 sets a quiet on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the quiet on %1.", parameter); else message = i18n("%1 removes the quiet on %2.", sourceNick, parameter); } } else { if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel owner privileges to yourself."); else message = i18n("You give channel owner privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel owner privileges to you.", sourceNick); else message = i18n("%1 gives channel owner privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel owner privileges from yourself."); else message = i18n("You take channel owner privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel owner privileges from you.", sourceNick); else message = i18n("%1 takes channel owner privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setOwner(plus); emitUpdateInfo(); } } break; case 'a': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel admin privileges to yourself."); else message = i18n("You give channel admin privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel admin privileges to you.", sourceNick); else message = i18n("%1 gives channel admin privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel admin privileges from yourself."); else message = i18n("You take channel admin privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel admin privileges from you.", sourceNick); else message = i18n("%1 takes channel admin privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setAdmin(plus); emitUpdateInfo(); } break; case 'o': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel operator privileges to yourself."); else message = i18n("You give channel operator privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel operator privileges to you.", sourceNick); else message = i18n("%1 gives channel operator privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel operator privileges from yourself."); else message = i18n("You take channel operator privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel operator privileges from you.", sourceNick); else message = i18n("%1 takes channel operator privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setOp(plus); emitUpdateInfo(); } break; case 'h': if (plus) { if (fromMe) { if (toMe) message = i18n("You give channel halfop privileges to yourself."); else message = i18n("You give channel halfop privileges to %1.", parameter); } else { if (toMe) message = i18n("%1 gives channel halfop privileges to you.", sourceNick); else message = i18n("%1 gives channel halfop privileges to %2.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take channel halfop privileges from yourself."); else message = i18n("You take channel halfop privileges from %1.", parameter); } else { if (toMe) message = i18n("%1 takes channel halfop privileges from you.", sourceNick); else message = i18n("%1 takes channel halfop privileges from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setHalfOp(plus); emitUpdateInfo(); } break; //case 'O': break; case 'v': if (plus) { if (fromMe) { if (toMe) message = i18n("You give yourself permission to talk."); else message = i18n("You give %1 permission to talk.", parameter); } else { if (toMe) message = i18n("%1 gives you permission to talk.", sourceNick); else message = i18n("%1 gives %2 permission to talk.", sourceNick, parameter); } } else { if (fromMe) { if (toMe) message = i18n("You take the permission to talk from yourself."); else message = i18n("You take the permission to talk from %1.", parameter); } else { if (toMe) message = i18n("%1 takes the permission to talk from you.", sourceNick); else message = i18n("%1 takes the permission to talk from %2.", sourceNick, parameter); } } if (parameterChannelNick) { parameterChannelNick->setVoice(plus); } break; case 'c': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'no colors allowed'."); else message = i18n("%1 sets the channel mode to 'no colors allowed'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'allow color codes'."); else message = i18n("%1 sets the channel mode to 'allow color codes'.", sourceNick); } break; case 'i': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'invite only'."); else message = i18n("%1 sets the channel mode to 'invite only'.", sourceNick); } else { if (fromMe) message = i18n("You remove the 'invite only' mode from the channel."); else message = i18n("%1 removes the 'invite only' mode from the channel.", sourceNick); } modeI->setDown(plus); break; case 'm': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'moderated'."); else message = i18n("%1 sets the channel mode to 'moderated'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'unmoderated'."); else message = i18n("%1 sets the channel mode to 'unmoderated'.", sourceNick); } modeM->setDown(plus); break; case 'n': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'no messages from outside'."); else message = i18n("%1 sets the channel mode to 'no messages from outside'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'allow messages from outside'."); else message = i18n("%1 sets the channel mode to 'allow messages from outside'.", sourceNick); } modeN->setDown(plus); break; case 'p': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'private'."); else message = i18n("%1 sets the channel mode to 'private'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'public'."); else message = i18n("%1 sets the channel mode to 'public'.", sourceNick); } modeP->setDown(plus); if (plus) modeS->setDown(false); break; case 's': if (plus) { if (fromMe) message = i18n("You set the channel mode to 'secret'."); else message = i18n("%1 sets the channel mode to 'secret'.", sourceNick); } else { if (fromMe) message = i18n("You set the channel mode to 'visible'."); else message = i18n("%1 sets the channel mode to 'visible'.", sourceNick); } modeS->setDown(plus); if (plus) modeP->setDown(false); break; //case 'r': break; case 't': if (plus) { if (fromMe) message = i18n("You switch on 'topic protection'."); else message = i18n("%1 switches on 'topic protection'.", sourceNick); } else { if (fromMe) message = i18n("You switch off 'topic protection'."); else message = i18n("%1 switches off 'topic protection'.", sourceNick); } modeT->setDown(plus); break; case 'k': if (plus) { if (fromMe) message = i18n("You set the channel key to '%1'.", parameter); else message = i18n("%1 sets the channel key to '%2'.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the channel key."); else message = i18n("%1 removes the channel key.", sourceNick); } modeK->setDown(plus); break; case 'l': if (plus) { if (fromMe) message = i18np("You set the channel limit to 1 nick.", "You set the channel limit to %1 nicks.", parameter); else message = i18np("%2 sets the channel limit to 1 nick.", "%2 sets the channel limit to %1 nicks.", parameter, sourceNick); } else { if (fromMe) message = i18n("You remove the channel limit."); else message = i18n("%1 removes the channel limit.", sourceNick); } modeL->setDown(plus); if (plus) limit->setText(parameter); else limit->clear(); break; case 'b': if (plus) { if (fromMe) message = i18n("You set a ban on %1.", parameter); else message = i18n("%1 sets a ban on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the ban on %1.", parameter); else message = i18n("%1 removes the ban on %2.", sourceNick, parameter); } break; case 'e': if (plus) { if (fromMe) message = i18n("You set a ban exception on %1.", parameter); else message = i18n("%1 sets a ban exception on %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the ban exception on %1.", parameter); else message = i18n("%1 removes the ban exception on %2.", sourceNick, parameter); } break; case 'I': if (plus) { if (fromMe) message = i18n("You set invitation mask %1.", parameter); else message = i18n("%1 sets invitation mask %2.", sourceNick, parameter); } else { if (fromMe) message = i18n("You remove the invitation mask %1.", parameter); else message = i18n("%1 removes the invitation mask %2.", sourceNick, parameter); } break; default: if (plus) { if (Konversation::getChannelModesHash().contains(QLatin1Char(mode))) { if (fromMe) message = i18n("You set the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode))); else message= i18n("%1 sets the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode))); } else { if (fromMe) message = i18n("You set channel mode +%1", QLatin1Char(mode)); else message = i18n("%1 sets channel mode +%2", sourceNick, QLatin1Char(mode)); } } else { if (Konversation::getChannelModesHash().contains(QLatin1Char(mode))) { if (fromMe) message = i18n("You remove the channel mode '%1'.", Konversation::getChannelModesHash().value(QLatin1Char(mode))); else message= i18n("%1 removes the channel mode '%2'.", sourceNick, Konversation::getChannelModesHash().value(QLatin1Char(mode))); } else { if (fromMe) message = i18n("You set channel mode -%1", QLatin1Char(mode)); else message = i18n("%1 sets channel mode -%2", sourceNick, QLatin1Char(mode)); } } } // check if this nick's anyOp-status has changed and adjust ops accordingly if (parameterChannelNick) { if (wasAnyOp && (!parameterChannelNick->isAnyTypeOfOp())) adjustOps(-1); else if (!wasAnyOp && parameterChannelNick->isAnyTypeOfOp()) adjustOps(1); } if (!message.isEmpty() && !Preferences::self()->useLiteralModes()) { appendCommandMessage(i18n("Mode"), message, messageTags); } updateModeWidgets(mode, plus, parameter); } void Channel::clearModeList() { QString k; // Keep channel password in the backing store, for rejoins. for (QStringList::const_iterator it = m_modeList.constBegin(); it != m_modeList.constEnd(); ++it) { if ((*it)[0] == QLatin1Char('k')) k = (*it); } m_modeList.clear(); if (!k.isEmpty()) m_modeList << k; modeT->setOn(false); modeT->setDown(false); modeN->setOn(false); modeN->setDown(false); modeS->setOn(false); modeS->setDown(false); modeI->setOn(false); modeI->setDown(false); modeP->setOn(false); modeP->setDown(false); modeM->setOn(false); modeM->setDown(false); modeK->setOn(false); modeK->setDown(false); modeL->setOn(false); modeL->setDown(false); limit->clear(); emit modesChanged(); } void Channel::updateModeWidgets(char mode, bool plus, const QString ¶meter) { ModeButton* widget = nullptr; if(mode=='t') widget=modeT; else if(mode=='n') widget=modeN; else if(mode=='s') widget=modeS; else if(mode=='i') widget=modeI; else if(mode=='p') widget=modeP; else if(mode=='m') widget=modeM; else if(mode=='k') widget=modeK; else if(mode=='l') { widget=modeL; if(plus) limit->setText(parameter); else limit->clear(); } if(widget) widget->setOn(plus); if(plus) { m_modeList.append(QString(QLatin1Char(mode) + parameter)); } else { QStringList removable = m_modeList.filter(QRegExp(QString(QStringLiteral("^%1.*")).arg(mode))); foreach(const QString &mode, removable) { m_modeList.removeOne(mode); } } emit modesChanged(); } void Channel::updateQuickButtons() { delete m_buttonsGrid; m_buttonsGrid = nullptr; // the grid that holds the quick action buttons m_buttonsGrid = new QWidget (nickListButtons); //Q3Grid(2, nickListButtons); nickListButtons->layout()->addWidget(m_buttonsGrid); m_buttonsGrid->hide(); QGridLayout* layout = new QGridLayout (m_buttonsGrid); layout->setMargin(0); int col = 0; int row = 0; const QStringList &newButtonList = Preferences::quickButtonList(); // add new quick buttons for(int index=0;indexaddWidget (quickButton, row, col); row += col; connect(quickButton, SIGNAL(clicked(QString)), this, SLOT(quickButtonClicked(QString))); // Get the button definition QString buttonText=newButtonList[index]; // Extract button label QString buttonLabel=buttonText.section(QLatin1Char(','),0,0); // Extract button definition buttonText=buttonText.section(QLatin1Char(','),1); quickButton->setText(buttonLabel); quickButton->setDefinition(buttonText); // Add tool tips QString toolTip=buttonText.replace(QLatin1Char('&'),QStringLiteral("&")). replace(QLatin1Char('<'),QStringLiteral("<")). replace(QLatin1Char('>'),QStringLiteral(">")); quickButton->setToolTip(toolTip); quickButton->show(); } // for // set hide() or show() on grid showQuickButtons(Preferences::self()->showQuickButtons()); } void Channel::showQuickButtons(bool show) { // Qt does not redraw the buttons properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden() || !m_buttonsGrid) { quickButtonsChanged=true; quickButtonsState=show; } else { if(show) m_buttonsGrid->show(); else m_buttonsGrid->hide(); } } void Channel::showModeButtons(bool show) { // Qt does not redraw the buttons properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden()) { modeButtonsChanged=true; modeButtonsState=show; } else { if(show) { topicSplitterHidden = false; modeBox->show(); modeBox->parentWidget()->show(); } else { modeBox->hide(); if(topicLine->isHidden()) { topicSplitterHidden = true; modeBox->parentWidget()->hide(); } } } } void Channel::indicateAway(bool show) { // Qt does not redraw the label properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden()) { awayChanged=true; awayState=show; } else { if(show) awayLabel->show(); else awayLabel->hide(); } } void Channel::showEvent(QShowEvent*) { // If the show quick/mode button settings have changed, apply the changes now if(quickButtonsChanged) { quickButtonsChanged=false; showQuickButtons(quickButtonsState); } if(modeButtonsChanged) { modeButtonsChanged=false; showModeButtons(modeButtonsState); } if(awayChanged) { awayChanged=false; indicateAway(awayState); } syncSplitters(); } void Channel::syncSplitters() { QList vertSizes = Preferences::self()->topicSplitterSizes(); QList horizSizes = Preferences::self()->channelSplitterSizes(); if (vertSizes.isEmpty()) { vertSizes << m_topicButton->height() << (height() - m_topicButton->height()); Preferences::self()->setTopicSplitterSizes(vertSizes); } if (horizSizes.isEmpty()) { // An approximation of a common NICKLEN plus the width of the icon, // tested with 8pt and 10pt DejaVu Sans and Droid Sans. int listWidth = fontMetrics().averageCharWidth() * 17 + 20; horizSizes << (width() - listWidth) << listWidth; Preferences::self()->setChannelSplitterSizes(horizSizes); } m_vertSplitter->setSizes(vertSizes); m_horizSplitter->setSizes(horizSizes); splittersInitialized = true; } void Channel::updateAppearance() { QPalette palette; if (Preferences::self()->inputFieldsBackgroundColor()) { palette.setColor(QPalette::Text, Preferences::self()->color(Preferences::ChannelMessage)); palette.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground)); palette.setColor(QPalette::AlternateBase, Preferences::self()->color(Preferences::AlternateBackground)); } limit->setPalette(palette); topicLine->setPalette(QPalette()); if (Preferences::self()->customTextFont()) { topicLine->setFont(Preferences::self()->textFont()); m_inputBar->setFont(Preferences::self()->textFont()); nicknameCombobox->setFont(Preferences::self()->textFont()); limit->setFont(Preferences::self()->textFont()); } else { topicLine->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_inputBar->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); nicknameCombobox->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); limit->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } nicknameListView->resort(); nicknameListView->setPalette(palette); nicknameListView->setAlternatingRowColors(Preferences::self()->inputFieldsBackgroundColor()); if (Preferences::self()->customListFont()) nicknameListView->setFont(Preferences::self()->listFont()); else nicknameListView->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); nicknameListView->refresh(); showModeButtons(Preferences::self()->showModeButtons()); showNicknameList(Preferences::self()->showNickList()); showNicknameBox(Preferences::self()->showNicknameBox()); showTopic(Preferences::self()->showTopic()); setAutoUserhost(Preferences::self()->autoUserhost()); QMetaObject::invokeMethod(this, "updateQuickButtons", Qt::QueuedConnection); // Nick sorting settings might have changed. Trigger timer if (m_delayedSortTimer) { m_delayedSortTrigger = DELAYED_SORT_TRIGGER + 1; m_delayedSortTimer->start(500 + qrand()/2000); } ChatWindow::updateAppearance(); } void Channel::nicknameComboboxChanged() { QString newNick=nicknameCombobox->currentText(); oldNick=m_server->getNickname(); if (oldNick != newNick) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(oldNick)); changeNickname(newNick); // return focus to input line m_inputBar->setFocus(); } } void Channel::changeNickname(const QString& newNickname) { if (!newNickname.isEmpty()) m_server->queue(QStringLiteral("NICK ")+newNickname); } void Channel::queueNicks(const QStringList& nicknameList) { if (nicknameList.isEmpty()) return; m_nickQueue.append(nicknameList); processQueuedNicks(); } void Channel::endOfNames() { if (!m_initialNamesReceived) { m_initialNamesReceived = true; if (m_server->hasAwayNotify() && !Preferences::self()->autoWhoContinuousEnabled()) { // Do one who request to get the initial away state for the channel QMetaObject::invokeMethod(m_server, "requestWho", Qt::QueuedConnection, Q_ARG(QString, getName())); } scheduleAutoWho(); } } void Channel::childAdjustFocus() { m_inputBar->setFocus(); refreshModeButtons(); } void Channel::refreshModeButtons() { bool enable = true; if(getOwnChannelNick()) { enable=getOwnChannelNick()->isAnyTypeOfOp(); } // if not channel nick, then enable is true - fall back to assuming they are op //don't disable the mode buttons since you can't then tell if they are enabled or not. //needs to be fixed somehow /* modeT->setEnabled(enable); modeN->setEnabled(enable); modeS->setEnabled(enable); modeI->setEnabled(enable); modeP->setEnabled(enable); modeM->setEnabled(enable); modeK->setEnabled(enable); modeL->setEnabled(enable);*/ limit->setEnabled(enable); // Tooltips for the ModeButtons QString opOnly; if(!enable) opOnly = i18n("You have to be an operator to change this."); modeT->setToolTip(i18n("Topic can be changed by channel operator only. %1", opOnly)); modeN->setToolTip(i18n("No messages to channel from clients on the outside. %1", opOnly)); modeS->setToolTip(i18n("Secret channel. %1", opOnly)); modeI->setToolTip(i18n("Invite only channel. %1", opOnly)); modeP->setToolTip(i18n("Private channel. %1", opOnly)); modeM->setToolTip(i18n("Moderated channel. %1", opOnly)); modeK->setToolTip(i18n("Protect channel with a password.")); modeL->setToolTip(i18n("Set user limit to channel.")); } void Channel::nicknameListViewTextChanged(int textChangedFlags) { m_nicknameListViewTextChanged |= textChangedFlags; } void Channel::autoUserhost() { if(Preferences::self()->autoUserhost() && !Preferences::self()->autoWhoContinuousEnabled()) { int limit = 5; QString nickString; foreach (Nick* nick, getNickList()) { if(nick->getChannelNick()->getHostmask().isEmpty()) { if(limit--) nickString = nickString + nick->getChannelNick()->getNickname() + QLatin1Char(' '); else break; } } if(!nickString.isEmpty()) m_server->requestUserhost(nickString); } if(!nicknameList.isEmpty()) { resizeNicknameListViewColumns(); } } void Channel::setAutoUserhost(bool state) { nicknameListView->setColumnHidden(Nick::HostmaskColumn, !state); if (state) { nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // Cannot use QHeaderView::ResizeToContents here because it is slow // and it gets triggered by setSortingEnabled(). Using timed resize // instead, see Channel::autoUserhost() above. nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Fixed); nicknameListView->header()->setSectionResizeMode(Nick::HostmaskColumn, QHeaderView::Fixed); userhostTimer.start(10000); m_nicknameListViewTextChanged |= 0xFF; // ResizeColumnsToContents QTimer::singleShot(0, this, SLOT(autoUserhost())); // resize columns ASAP } else { nicknameListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); nicknameListView->header()->setSectionResizeMode(Nick::NicknameColumn, QHeaderView::Stretch); userhostTimer.stop(); } } void Channel::scheduleAutoWho(int msec) { // The first auto-who is scheduled by ENDOFNAMES in InputFilter, which means // the first auto-who occurs one interval after it. This has two desirable // consequences specifically related to the startup phase: auto-who dispatch // doesn't occur at the same time for all channels that are auto-joined, and // it gives some breathing room to process the NAMES replies for all channels // first before getting started on WHO. // Subsequent auto-whos are scheduled by ENDOFWHO in InputFilter. However, // autoWho() might refuse to actually do the request if the number of nicks // in the channel exceeds the threshold, and will instead schedule another // attempt later. Thus scheduling an auto-who does not guarantee it will be // performed. // If this is called mid-interval (e.g. due to the ENDOFWHO from a manual WHO) // it will reset the interval to avoid cutting it short. if (m_whoTimer.isActive()) m_whoTimer.stop(); if (Preferences::self()->autoWhoContinuousEnabled()) { if (msec > 0) m_whoTimer.start(msec); else m_whoTimer.start(Preferences::self()->autoWhoContinuousInterval() * 1000); } } void Channel::autoWho() { // Try again later if there are too many nicks or we're already processing a WHO request. if ((nicks > Preferences::self()->autoWhoNicksLimit()) || m_server->getInputFilter()->isWhoRequestUnderProcess(getName())) { scheduleAutoWho(); return; } m_server->requestWho(getName()); } void Channel::updateAutoWho() { if (!Preferences::self()->autoWhoContinuousEnabled()) m_whoTimer.stop(); else if (Preferences::self()->autoWhoContinuousEnabled() && !m_whoTimer.isActive()) autoWho(); else if (m_whoTimer.isActive()) { // The below tries to meet user expectations on an interval settings change, // making two assumptions: // - If the new interval is lower than the old one, the user may be impatient // and desires an information update. // - If the new interval is longer than the old one, the user may be trying to // avoid Konversation producing too much traffic in a given timeframe, and // wants it to stop doing so sooner rather than later. // Both require rescheduling the next auto-who request. int interval = Preferences::self()->autoWhoContinuousInterval() * 1000; if (interval != m_whoTimer.interval()) { if (m_whoTimerStarted.elapsed() >= interval) { // If the time since the last auto-who request is longer than (or // equal to) the new interval setting, it follows that the new // setting is lower than the old setting. In this case issue a new // request immediately, which is the closest we can come to acting // as if the new setting had been active all along, short of tra- // velling back in time to change history. This handles the impa- // tient user. // FIXME: Adjust algorithm when time machine becomes available. m_whoTimer.stop(); autoWho(); } else { // If on the other hand the elapsed time is shorter than the new // interval setting, the new setting could be either shorter or // _longer_ than the old setting. Happily, this time we can actually // behave as if the new setting had been active all along, by sched- // uling the next request to happen in the new interval time minus // the already elapsed time, meeting user expecations for both cases // originally laid out. scheduleAutoWho(interval - m_whoTimerStarted.elapsed()); } } } } void Channel::fadeActivity() { foreach (Nick *nick, nicknameList) { nick->getChannelNick()->lessActive(); } } bool Channel::canBeFrontView() { return true; } bool Channel::searchView() { return true; } bool Channel::closeYourself(bool confirm) { int result=KMessageBox::Continue; if (confirm) result = KMessageBox::warningContinueCancel(this, i18n("Do you want to leave %1?", getName()), i18n("Leave Channel"), KGuiItem(i18n("Leave")), KStandardGuiItem::cancel(), QStringLiteral("QuitChannelTab")); if (result==KMessageBox::Continue) { m_server->closeChannel(getName()); m_server->removeChannel(this); deleteLater(); return true; } else m_recreationScheduled = false; return false; } void Channel::serverOnline(bool online) { setActive(online); } //Used to disable functions when not connected, does not necessarily mean the server is offline void Channel::setActive(bool active) { if (active) nicknameCombobox->setEnabled(true); else { m_initialNamesReceived = false; purgeNicks(); nicknameCombobox->setEnabled(false); topicLine->clear(); clearModeList(); clearBanList(); m_whoTimer.stop(); } } void Channel::showTopic(bool show) { if(show) { topicSplitterHidden = false; topicLine->show(); m_topicButton->show(); topicLine->parentWidget()->show(); } else { topicLine->hide(); m_topicButton->hide(); if(modeBox->isHidden()) { topicSplitterHidden = true; topicLine->parentWidget()->hide(); } } } void Channel::processQueuedNicks(bool flush) { // This pops nicks from the front of a queue added to by incoming NAMES // messages and adds them to the channel nicklist, calling itself via // the event loop until the last invocation finds the queue empty and // adjusts the nicks/ops counters and requests a nicklist sort, but only // if previous invocations actually processed any nicks. The latter is // an optimization for the common case of processing being kicked off by // flushNickQueue(), which is done e.g. before a nick rename or part to // make sure the channel is up to date and will usually find an empty // queue. This is also the use case for the 'flush' parameter, which if // true causes the recursion to block in a tight loop instead of queueing // via the event loop. if (m_nickQueue.isEmpty()) { if (m_processedNicksCount) { adjustNicks(m_processedNicksCount); adjustOps(m_processedOpsCount); m_processedNicksCount = 0; m_processedOpsCount = 0; sortNickList(); nicknameListView->setUpdatesEnabled(true); if (Preferences::self()->autoUserhost()) resizeNicknameListViewColumns(); } } else { QString nickname; while (nickname.isEmpty() && !m_nickQueue.isEmpty()) nickname = m_nickQueue.takeFirst(); QString userHost; if(m_server->hasUserHostInNames()) { int index = nickname.indexOf(QLatin1Char('!')); if(index >= 0) { userHost = nickname.mid(index + 1); nickname = nickname.left(index); } } bool admin = false; bool owner = false; bool op = false; bool halfop = false; bool voice = false; // Remove possible mode characters from nickname and store the resulting mode. m_server->mangleNicknameWithModes(nickname, admin, owner, op, halfop, voice); // TODO: Make these an enumeration in KApplication or somewhere, we can use them as well. unsigned int mode = (admin ? 16 : 0) + (owner ? 8 : 0) + (op ? 4 : 0) + (halfop ? 2 : 0) + (voice ? 1 : 0); // Check if nick is already in the nicklist. if (!nickname.isEmpty() && !getNickByName(nickname)) { ChannelNickPtr nick = m_server->addNickToJoinedChannelsList(getName(), nickname); Q_ASSERT(nick); nick->setMode(mode); if(!userHost.isEmpty()) { nick->getNickInfo()->setHostmask(userHost); } fastAddNickname(nick); ++m_processedNicksCount; if (nick->isAdmin() || nick->isOwner() || nick->isOp() || nick->isHalfOp()) ++m_processedOpsCount; } QMetaObject::invokeMethod(this, "processQueuedNicks", flush ? Qt::DirectConnection : Qt::QueuedConnection, Q_ARG(bool, flush)); } } void Channel::setChannelEncoding(const QString& encoding) // virtual { if(m_server->getServerGroup()) Preferences::setChannelEncoding(m_server->getServerGroup()->id(), getName(), encoding); else Preferences::setChannelEncoding(m_server->getDisplayName(), getName(), encoding); } QString Channel::getChannelEncoding() // virtual { if(m_server->getServerGroup()) return Preferences::channelEncoding(m_server->getServerGroup()->id(), getName()); return Preferences::channelEncoding(m_server->getDisplayName(), getName()); } QString Channel::getChannelEncodingDefaultDesc() // virtual { return i18n("Identity Default ( %1 )", getServer()->getIdentity()->getCodecName()); } void Channel::showNicknameBox(bool show) { if(show) { nicknameCombobox->show(); } else { nicknameCombobox->hide(); } } void Channel::showNicknameList(bool show) { if (show) { channelSplitterHidden = false; nickListButtons->show(); } else { channelSplitterHidden = true; nickListButtons->hide(); } } void Channel::requestNickListSort() { m_delayedSortTrigger++; if (m_delayedSortTrigger == DELAYED_SORT_TRIGGER && !m_delayedSortTimer->isActive()) { nicknameListView->fastSetSortingEnabled(false); m_delayedSortTimer->start(1000); } } void Channel::delayedSortNickList() { sortNickList(true); } void Channel::sortNickList(bool delayed) { if (!delayed || m_delayedSortTrigger > DELAYED_SORT_TRIGGER) { qSort(nicknameList.begin(), nicknameList.end(), nickLessThan); nicknameListView->resort(); } if (!nicknameListView->isSortingEnabled()) nicknameListView->fastSetSortingEnabled(true); m_delayedSortTrigger = 0; m_delayedSortTimer->stop(); } void Channel::repositionNick(Nick *nick) { int index = nicknameList.indexOf(nick); if (index > -1) { // Trigger nick reposition in the nicklist including // field updates nick->refresh(); // Readd nick to the nicknameList nicknameList.removeAt(index); fastAddNickname(nick->getChannelNick(), nick); } else { qWarning() << "Nickname " << nick->getChannelNick()->getNickname() << " not found!"<< endl; } } bool Channel::eventFilter(QObject* watched, QEvent* e) { if((watched == nicknameListView) && (e->type() == QEvent::Resize) && splittersInitialized && isVisible()) { if (!topicSplitterHidden && !channelSplitterHidden) { Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes()); Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes()); } if (!topicSplitterHidden && channelSplitterHidden) { Preferences::self()->setTopicSplitterSizes(m_vertSplitter->sizes()); } if (!channelSplitterHidden && topicSplitterHidden) { Preferences::self()->setChannelSplitterSizes(m_horizSplitter->sizes()); } } return ChatWindow::eventFilter(watched, e); } void Channel::addBan(const QString& ban) { for ( QStringList::iterator it = m_BanList.begin(); it != m_BanList.end(); ++it ) { if ((*it).section(QLatin1Char(' '), 0, 0) == ban.section(QLatin1Char(' '), 0, 0)) { // Ban is already in list. it = m_BanList.erase(it); emit banRemoved(ban.section(QLatin1Char(' '), 0, 0)); if (it == m_BanList.end()) break; } } m_BanList.prepend(ban); emit banAdded(ban); } void Channel::removeBan(const QString& ban) { foreach(const QString &string, m_BanList) { if (string.section(QLatin1Char(' '), 0, 0) == ban) { m_BanList.removeOne(string); emit banRemoved(ban); } } } void Channel::clearBanList() { m_BanList.clear(); emit banListCleared(); } void Channel::append(const QString& nickname, const QString& message, const QHash &messageTags, const QString& label) { if(nickname != getServer()->getNickname()) { Nick* nick = getNickByName(nickname); if(nick) { nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toTime_t()); } } ChatWindow::append(nickname, message, messageTags, label); nickActive(nickname); } void Channel::appendAction(const QString& nickname, const QString& message, const QHash &messageTags) { if(nickname != getServer()->getNickname()) { Nick* nick = getNickByName(nickname); if(nick) { nick->getChannelNick()->setTimeStamp(QDateTime::currentDateTime().toTime_t()); } } ChatWindow::appendAction(nickname, message, messageTags); nickActive(nickname); } void Channel::nickActive(const QString& nickname) //FIXME reported to crash, can't reproduce { ChannelNickPtr channelnick=getChannelNick(nickname); //XXX Would be nice to know why it can be null here... if (channelnick) { channelnick->moreActive(); if (Preferences::self()->sortByActivity()) { Nick* nick = getNickByName(nickname); if (nick) { nick->repositionMe(); } } } } #ifdef HAVE_QCA2 Konversation::Cipher* Channel::getCipher() { if(!m_cipher) m_cipher = new Konversation::Cipher(); return m_cipher; } #endif void Channel::updateNickInfos() { foreach(Nick* nick, nicknameList) { if(nick->getChannelNick()->getNickInfo()->isChanged()) { nick->refresh(); } } } void Channel::updateChannelNicks(const QString& channel) { if(channel != name.toLower()) return; foreach(Nick* nick, nicknameList) { if(nick->getChannelNick()->isChanged()) { nick->refresh(); if(nick->getChannelNick() == m_ownChannelNick) { refreshModeButtons(); } } } } void Channel::resizeNicknameListViewColumns() { // Resize columns if needed (on regular basis) if (m_nicknameListViewTextChanged & (1 << Nick::NicknameColumn)) nicknameListView->resizeColumnToContents(Nick::NicknameColumn); if (m_nicknameListViewTextChanged & (1 << Nick::HostmaskColumn)) nicknameListView->resizeColumnToContents(Nick::HostmaskColumn); m_nicknameListViewTextChanged = 0; } // // NickList // NickList::NickList() : QList() { } QString NickList::completeNick(const QString& pattern, bool& complete, QStringList& found, bool skipNonAlfaNum, bool caseSensitive) { found.clear(); QString prefix(QLatin1Char('^')); QString newNick; QString prefixCharacter = Preferences::self()->prefixCharacter(); NickList foundNicks; if((pattern.contains(QRegExp(QStringLiteral("^(\\d|\\w)")))) && skipNonAlfaNum) { prefix = QStringLiteral("^([^\\d\\w]|[\\_]){0,}"); } QRegExp regexp(prefix + QRegExp::escape(pattern)); regexp.setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); foreach (Nick* nick, *this) { newNick = nick->getChannelNick()->getNickname(); if(!prefix.isEmpty() && newNick.contains(prefixCharacter)) { newNick = newNick.section( prefixCharacter,1 ); } if(newNick.contains(regexp)) { foundNicks.append(nick); } } qSort(foundNicks.begin(), foundNicks.end(), nickTimestampLessThan); foreach (Nick *nick, foundNicks) { found.append(nick->getChannelNick()->getNickname()); } if(found.count() > 1) { bool ok = true; int patternLength = pattern.length(); QString firstNick = found[0]; int firstNickLength = firstNick.length(); int foundCount = found.count(); while(ok && ((patternLength) < firstNickLength)) { ++patternLength; QStringList tmp = found.filter(firstNick.left(patternLength), caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(tmp.count() != foundCount) { ok = false; --patternLength; } } complete = false; return firstNick.left(patternLength); } else if(found.count() == 1) { complete = true; return found[0]; } return QString(); } bool NickList::containsNick(const QString& nickname) { foreach (Nick* nick, *this) { if (nick->getChannelNick()->getNickname()==nickname) return true; } return false; } // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/irc/channel.h b/src/irc/channel.h index 9a8094ac..18b7e951 100644 --- a/src/irc/channel.h +++ b/src/irc/channel.h @@ -1,358 +1,358 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2004-2006, 2009 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #ifndef CHANNEL_H #define CHANNEL_H #include #include "server.h" #include "chatwindow.h" #include "channelnick.h" #ifdef HAVE_QCA2 #include "cipher.h" #endif #include #include class QAbstractItemModel; // WIPQTQUICK class QLabel; class QTimer; class QTreeWidgetItem; class QStringList; class QSplitter; class QToolButton; class KLineEdit; class KComboBox; class AwayLabel; class NickListView; class Nick; class QuickButton; class ModeButton; class IRCInput; class TopicHistoryModel; namespace Konversation { class TopicLabel; class ChannelOptionsDialog; } class NickList : public QList { public: NickList(); QString completeNick(const QString& pattern, bool& complete, QStringList& found, bool skipNonAlfaNum, bool caseSensitive); bool containsNick(const QString& nickname); }; class Channel : public ChatWindow { Q_OBJECT friend class Nick; public: explicit Channel(QWidget* parent, const QString& name); ~Channel() override; //META bool canBeFrontView() Q_DECL_OVERRIDE; bool searchView() Q_DECL_OVERRIDE; void append(const QString& nickname, const QString& message, const QHash &messageTags = QHash(), const QString& label = QString()) Q_DECL_OVERRIDE; void appendAction(const QString& nickname, const QString& message, const QHash &messageTags = QHash()) Q_DECL_OVERRIDE; void nickActive(const QString& nickname); #ifdef HAVE_QCA2 Konversation::Cipher* getCipher(); #endif //General administrative stuff public: QString getDescription() const Q_DECL_OVERRIDE; void setName(const QString& newName) Q_DECL_OVERRIDE; QString getPassword(); IrcContextMenus::MenuOptions contextMenuOptions() const override; const Konversation::ChannelSettings channelSettings(); QString getPassword() const; void setServer(Server* newServer) Q_DECL_OVERRIDE; void setEncryptedOutput(bool); bool joined() const { return m_joined; } bool rejoinable(); //Unsure of future placement and/or continued existence of these members int numberOfNicks() const { return nicks; } int numberOfOps() const { return ops; } void setChannelEncoding(const QString& encoding) Q_DECL_OVERRIDE; QString getChannelEncoding() Q_DECL_OVERRIDE; QString getChannelEncodingDefaultDesc() Q_DECL_OVERRIDE; bool log() Q_DECL_OVERRIDE; protected: // use with caution! does not check for duplicates void fastAddNickname(ChannelNickPtr channelnick, Nick *nick = nullptr); void setActive(bool active); void repositionNick(Nick *nick); bool shouldShowEvent(ChannelNickPtr channelNick); public Q_SLOTS: void setNickname(const QString& newNickname); void scheduleAutoWho(int msec = -1); void setAutoUserhost(bool state); void rejoin(); protected Q_SLOTS: void autoUserhost(); void autoWho(); void updateAutoWho(); void fadeActivity(); void serverOnline(bool online) Q_DECL_OVERRIDE; void delayedSortNickList(); //Nicklist public: void flushNickQueue(); ChannelNickPtr getOwnChannelNick() const; ChannelNickPtr getChannelNick(const QString &ircnick) const; void joinNickname(ChannelNickPtr channelNick, const QHash &messageTags); void removeNick(ChannelNickPtr channelNick, const QString &reason, bool quit, const QHash &messageTags); void kickNick(ChannelNickPtr channelNick, const QString &kicker, const QString &reason, const QHash &messageTags); void addNickname(ChannelNickPtr channelNick); void nickRenamed(const QString &oldNick, const NickInfo& channelnick, const QHash &messageTags); void queueNicks(const QStringList& nicknameList); void endOfNames(); Nick *getNickByName(const QString& lookname) const; NickList getNickList() const { return nicknameList; } void adjustNicks(int value); void adjustOps(int value); void emitUpdateInfo() Q_DECL_OVERRIDE; void resizeNicknameListViewColumns(); protected Q_SLOTS: void purgeNicks(); void processQueuedNicks(bool flush = false); void updateNickInfos(); void updateChannelNicks(const QString& channel); //Topic public: QString getTopic() const; TopicHistoryModel* getTopicHistory() { return m_topicHistory; } void setTopic(const QString& text, const QHash &messageTags); void setTopic(const QString& nickname, const QString& text, const QHash &messageTags); void setTopicAuthor(const QString& author, QDateTime timestamp); Q_SIGNALS: void joined(Channel* channel); //Modes //TODO: the only representation of the channel limit is held in the GUI public: /// Internal - Empty the modelist void clearModeList(); /// Get the list of modes that this channel has - e.g. {+l,+s,-m} //TODO: does this method return a list of all modes, all modes that have been changed, or all modes that are +? QStringList getModeList() const { return m_modeList; } /** Outputs a message on the channel, and modifies the mode for a ChannelNick. * @param sourceNick The server or the nick of the person that made the mode change. * @param mode The mode that is changing one of v,h,o,a for voice halfop op admin * @param plus True if the mode is being granted, false if it's being taken away. * @param parameter This depends on what the mode change is. In most cases it is the nickname of the person that is being given voice/op/admin etc. See the code. */ void updateMode(const QString& sourceNick, char mode, bool plus, const QString ¶meter, const QHash &messageTags); Q_SIGNALS: void modesChanged(); //Bans public: void addBan(const QString& ban); void removeBan(const QString& ban); void clearBanList(); QStringList getBanList() const { return m_BanList; } Q_SIGNALS: void banAdded(const QString& newban); void banRemoved(const QString& newban); void banListCleared(); //Generic GUI public: bool eventFilter(QObject* watched, QEvent* e) Q_DECL_OVERRIDE; //Specific GUI public: void updateModeWidgets(char mode, bool plus, const QString ¶meter); /// Sounds suspiciously like a destructor.. bool closeYourself(bool askForConfirmation=true) Q_DECL_OVERRIDE; bool autoJoin(); QStringList getSelectedNickList(); NickListView* getNickListView() const { return nicknameListView; } Konversation::ChannelSettings channelSettings() const; Q_SIGNALS: void sendFile(); public Q_SLOTS: void updateAppearance(); void updateQuickButtons(); void channelTextEntered(); void channelPassthroughCommand(); void sendText(const QString& line) Q_DECL_OVERRIDE; void showOptionsDialog(); void showQuickButtons(bool show); void showModeButtons(bool show); void indicateAway(bool show) Q_DECL_OVERRIDE; void showTopic(bool show); void showNicknameBox(bool show); void showNicknameList(bool show); void setAutoJoin(bool autojoin); - void connectionStateChanged(Server*, Konversation::ConnectionState); + void connectionStateChanged(Konversation::ConnectionState state); protected Q_SLOTS: void completeNick(); ///< I guess this is a GUI function, might be nice to have at DCOP level though --argonel void endCompleteNick(); void quickButtonClicked(const QString& definition); void modeButtonClicked(int id,bool on); void channelLimitChanged(); void doubleClickCommand(QTreeWidgetItem *item,int column); ///< Connected to NickListView::itemDoubleClicked() // Dialogs void changeNickname(const QString& newNickname); void sendFileMenu(); ///< connected to IRCInput::sendFile() void nicknameComboboxChanged(); /// Enable/disable the mode buttons depending on whether you are op or not. void refreshModeButtons(); //only the GUI cares about sorted nicklists ///Request a delayed nicklist sorting void requestNickListSort(); ///Sort the nicklist void sortNickList(bool delayed=false); void nicknameListViewTextChanged(int textChangedFlags); protected: void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; void syncSplitters(); /// Called from ChatWindow adjustFocus void childAdjustFocus() Q_DECL_OVERRIDE; // to take care of redraw problem if hidden bool quickButtonsChanged; bool quickButtonsState; bool modeButtonsChanged; bool modeButtonsState; bool awayChanged; bool awayState; bool splittersInitialized; bool topicSplitterHidden; bool channelSplitterHidden; int completionPosition; QSplitter* m_horizSplitter; QSplitter* m_vertSplitter; QWidget* topicWidget; QToolButton* m_topicButton; Konversation::TopicLabel* topicLine; //TODO: abstract these QFrame* modeBox; ModeButton* modeT; ModeButton* modeN; ModeButton* modeS; ModeButton* modeI; ModeButton* modeP; ModeButton* modeM; ModeButton* modeK; ModeButton* modeL; KLineEdit* limit; //TODO: this GUI element is the only storage for the mode NickListView* nicknameListView; QFrame* commandLineBox; QFrame* nickListButtons; QWidget* m_buttonsGrid; KComboBox* nicknameCombobox; QString oldNick; ///< GUI AwayLabel* awayLabel; QLabel* cipherLabel; //Members from here to end are not GUI bool m_joined; NickList nicknameList; QTimer userhostTimer; int m_nicknameListViewTextChanged; QHash m_nicknameNickHash; TopicHistoryModel* m_topicHistory; QStringList m_BanList; QTimer m_whoTimer; ///< For continuous auto /WHO QTime m_whoTimerStarted; QTimer m_fadeActivityTimer; ///< For the smoothing function used in activity sorting QStringList m_nickQueue; int m_processedNicksCount; int m_processedOpsCount; bool m_initialNamesReceived; QTimer* m_delayedSortTimer; int m_delayedSortTrigger; QStringList m_modeList; ChannelNickPtr m_ownChannelNick; bool pendingNicks; ///< are there still nicks to be added by /names reply? int nicks; ///< How many nicks on the channel int ops; ///< How many ops on the channel Konversation::ChannelOptionsDialog *m_optionsDialog; #ifdef HAVE_QCA2 Konversation::Cipher *m_cipher; #endif }; #endif diff --git a/src/irc/query.cpp b/src/irc/query.cpp index 6fbe83fc..55da3f88 100644 --- a/src/irc/query.cpp +++ b/src/irc/query.cpp @@ -1,473 +1,474 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005-2008 Eike Hein */ #include "query.h" #include "channel.h" #include "server.h" #include "application.h" #include "mainwindow.h" #include "viewcontainer.h" #include "ircinput.h" #include "ircview.h" #include "ircviewbox.h" #include "awaylabel.h" #include "common.h" #include #include #include #include using namespace Konversation; Query::Query(QWidget* parent, const QString& _name) : ChatWindow(parent) { name=_name; // need the name a little bit earlier for setServer // don't setName here! It will break logfiles! // setName("QueryWidget"); setType(ChatWindow::Query); m_isTopLevelView = false; setChannelEncodingSupported(true); m_headerSplitter = new QSplitter(Qt::Vertical, this); m_initialShow = true; awayChanged=false; awayState=false; queryHostmask=new KSqueezedTextLabel(m_headerSplitter); m_headerSplitter->setStretchFactor(m_headerSplitter->indexOf(queryHostmask), 0); queryHostmask->setTextElideMode(Qt::ElideRight); queryHostmask->setObjectName(QStringLiteral("query_hostmask")); QString whatsthis = i18n("

Some details of the person you are talking to in this query is shown in this bar. The full name and hostmask is shown.

See the Konversation Handbook for an explanation of what the hostmask is.

"); queryHostmask->setWhatsThis(whatsthis); IRCViewBox* ircViewBox = new IRCViewBox(m_headerSplitter); m_headerSplitter->setStretchFactor(m_headerSplitter->indexOf(ircViewBox), 1); setTextView(ircViewBox->ircView()); // Server will be set later in setServer(); ircViewBox->ircView()->setContextMenuOptions(IrcContextMenus::ShowNickActions, true); textView->setAcceptDrops(true); connect(textView,SIGNAL(urlsDropped(QList)),this,SLOT(urlsDropped(QList))); // This box holds the input line QWidget* inputBox=new QWidget(this); QHBoxLayout* inputBoxLayout = new QHBoxLayout(inputBox); inputBox->setObjectName(QStringLiteral("input_log_box")); inputBoxLayout->setSpacing(spacing()); inputBoxLayout->setMargin(0); awayLabel=new AwayLabel(inputBox); inputBoxLayout->addWidget(awayLabel); awayLabel->hide(); blowfishLabel = new QLabel(inputBox); inputBoxLayout->addWidget(blowfishLabel); blowfishLabel->hide(); blowfishLabel->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("document-encrypt"), KIconLoader::Toolbar)); m_inputBar=new IRCInput(inputBox); inputBoxLayout->addWidget(m_inputBar); getTextView()->installEventFilter(m_inputBar); m_inputBar->installEventFilter(this); // connect the signals and slots connect(m_inputBar, &IRCInput::submit, this, &Query::queryTextEntered); connect(m_inputBar, &IRCInput::envelopeCommand, this, &Query::queryPassthroughCommand); connect(m_inputBar, &IRCInput::textPasted, this, &ChatWindow::textPasted); connect(getTextView(), SIGNAL(textPasted(bool)), m_inputBar, SLOT(paste(bool))); connect(getTextView(),SIGNAL (gotFocus()),m_inputBar,SLOT (setFocus()) ); connect(textView,SIGNAL (sendFile()),this,SLOT (sendFileMenu()) ); connect(textView,SIGNAL (autoText(QString)),this,SLOT (sendText(QString)) ); updateAppearance(); #ifdef HAVE_QCA2 m_cipher = nullptr; #endif } Query::~Query() { if (m_recreationScheduled) { qRegisterMetaType("NickInfoPtr"); QMetaObject::invokeMethod(m_server, "addQuery", Qt::QueuedConnection, Q_ARG(NickInfoPtr, m_nickInfo), Q_ARG(bool, true)); } } void Query::setServer(Server* newServer) { if (m_server != newServer) { - connect(newServer, SIGNAL(connectionStateChanged(Server*,Konversation::ConnectionState)), - SLOT(connectionStateChanged(Server*,Konversation::ConnectionState))); + connect(newServer, &Server::connectionStateChanged, + this, &Query::connectionStateChanged); connect(newServer, SIGNAL(nickInfoChanged(Server*,NickInfoPtr)), this, SLOT(updateNickInfo(Server*,NickInfoPtr))); } ChatWindow::setServer(newServer); if (!(newServer->getKeyForRecipient(getName()).isEmpty())) blowfishLabel->show(); connect(awayLabel, SIGNAL(unaway()), m_server, SLOT(requestUnaway())); connect(awayLabel, SIGNAL(awayMessageChanged(QString)), m_server, SLOT(requestAway(QString))); } -void Query::connectionStateChanged(Server* server, Konversation::ConnectionState state) +void Query::connectionStateChanged(Konversation::ConnectionState state) { + auto server = static_cast(sender()); if (server == m_server) { ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); - if (state == Konversation::SSConnected) + if (state == Konversation::Connected) { //HACK the way the notification priorities work sucks, this forces the tab text color to ungray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || !Preferences::self()->tabNotificationsEvents()) { viewContainer->unsetViewNotification(this); } } else { //HACK the way the notification priorities work sucks, this forces the tab text color to gray right now. if (viewContainer->getFrontView() == this || m_currentTabNotify == Konversation::tnfNone || (!Preferences::self()->tabNotificationsEvents() && m_currentTabNotify == Konversation::tnfControl)) { viewContainer->unsetViewNotification(this); } } } } void Query::setName(const QString& newName) { //if(ChatWindow::getName() == newName) return; // no change, so return if(ChatWindow::getName() != newName) { appendCommandMessage(i18n("Nick"),i18n("%1 is now known as %2.", getName(), newName)); } ChatWindow::setName(newName); // don't change logfile name if query name changes // This will prevent Nick-Changers to create more than one log file, if (logName.isEmpty()) { QString logName = (Preferences::self()->lowerLog()) ? getName().toLower() : getName() ; if(Preferences::self()->addHostnameToLog()) { if(m_nickInfo) logName += m_nickInfo->getHostmask(); } setLogfileName(logName); } } IrcContextMenus::MenuOptions Query::contextMenuOptions() const { return IrcContextMenus::ShowNickActions; } void Query::setEncryptedOutput(bool e) { if (e) blowfishLabel->show(); else blowfishLabel->hide(); } void Query::queryTextEntered() { QString line=m_inputBar->toPlainText(); m_inputBar->clear(); if (!line.isEmpty()) sendText(sterilizeUnicode(line)); } void Query::queryPassthroughCommand() { QString commandChar = Preferences::self()->commandChar(); QString line = m_inputBar->toPlainText(); m_inputBar->clear(); if(!line.isEmpty()) { // Prepend commandChar on Ctrl+Enter to bypass outputfilter command recognition if (line.startsWith(commandChar)) { line = commandChar + line; } sendText(sterilizeUnicode(line)); } } void Query::sendText(const QString& sendLine) { // create a work copy QString outputAll(sendLine); // replace aliases and wildcards m_server->getOutputFilter()->replaceAliases(outputAll, this); // Send all strings, one after another QStringList outList = outputAll.split(QLatin1Char('\n'), QString::SkipEmptyParts); for(int index=0;indexgetOutputFilter()->parse(m_server->getNickname(), output, getName(), this); if(!result.output.isEmpty()) { if(result.type == Konversation::Action) appendAction(m_server->getNickname(), result.output); else if(result.type == Konversation::Command) appendCommandMessage(result.typeString, result.output); else if(result.type == Konversation::Program) appendServerMessage(result.typeString, result.output); else if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output); else if(!result.typeString.isEmpty()) appendQuery(result.typeString, result.output); else appendQuery(m_server->getNickname(), result.output); } else if (result.outputList.count()) { if (result.type == Konversation::Message) { QStringListIterator it(result.outputList); while (it.hasNext()) appendQuery(m_server->getNickname(), it.next()); } else if (result.type == Konversation::Action) { for (int i = 0; i < result.outputList.count(); ++i) { if (i == 0) appendAction(m_server->getNickname(), result.outputList.at(i)); else appendQuery(m_server->getNickname(), result.outputList.at(i)); } } } // Send anything else to the server if (!result.toServerList.empty()) m_server->queueList(result.toServerList); else m_server->queue(result.toServer); } // for } void Query::indicateAway(bool show) { // QT does not redraw the label properly when they are not on screen // while getting hidden, so we remember the "soon to be" state here. if(isHidden()) { awayChanged=true; awayState=show; } else { if(show) awayLabel->show(); else awayLabel->hide(); } } // fix QTs broken behavior on hidden QListView pages void Query::showEvent(QShowEvent*) { if(awayChanged) { awayChanged=false; indicateAway(awayState); } if(m_initialShow) { m_initialShow = false; QList sizes; sizes << queryHostmask->sizeHint().height() << (height() - queryHostmask->sizeHint().height()); m_headerSplitter->setSizes(sizes); } } void Query::sendFileMenu() { emit sendFile(getName()); } void Query::childAdjustFocus() { m_inputBar->setFocus(); } void Query::setNickInfo(const NickInfoPtr & nickInfo) { m_nickInfo = nickInfo; Q_ASSERT(m_nickInfo); if(!m_nickInfo) return; nickInfoChanged(); } void Query::updateNickInfo(Server* server, NickInfoPtr nickInfo) { if (!m_nickInfo || server != m_server || nickInfo != m_nickInfo) return; nickInfoChanged(); } void Query::nickInfoChanged() { if (m_nickInfo) { setName(m_nickInfo->getNickname()); QString text = m_nickInfo->getBestAddresseeName(); if(!m_nickInfo->getHostmask().isEmpty() && !text.isEmpty()) text += QStringLiteral(" - "); text += m_nickInfo->getHostmask(); if(m_nickInfo->isAway() && !m_nickInfo->getAwayMessage().isEmpty()) text += QStringLiteral(" (") + m_nickInfo->getAwayMessage() + QStringLiteral(") "); queryHostmask->setText(Konversation::removeIrcMarkup(text)); QString strTooltip; QTextStream tooltip( &strTooltip, QIODevice::WriteOnly ); tooltip << ""; tooltip << ""; m_nickInfo->tooltipTableData(tooltip); tooltip << "
"; queryHostmask->setToolTip(strTooltip); } emit updateQueryChrome(this,getName()); emitUpdateInfo(); } NickInfoPtr Query::getNickInfo() { return m_nickInfo; } bool Query::canBeFrontView() { return true; } bool Query::searchView() { return true; } // virtual void Query::setChannelEncoding(const QString& encoding) { if(m_server->getServerGroup()) Preferences::setChannelEncoding(m_server->getServerGroup()->id(), getName(), encoding); else Preferences::setChannelEncoding(m_server->getDisplayName(), getName(), encoding); } QString Query::getChannelEncoding() // virtual { if(m_server->getServerGroup()) return Preferences::channelEncoding(m_server->getServerGroup()->id(), getName()); return Preferences::channelEncoding(m_server->getDisplayName(), getName()); } QString Query::getChannelEncodingDefaultDesc() // virtual { return i18n("Identity Default ( %1 )",getServer()->getIdentity()->getCodecName()); } bool Query::closeYourself(bool confirm) { int result = KMessageBox::Continue; if (confirm) result=KMessageBox::warningContinueCancel( this, i18n("Do you want to close your query with %1?", getName()), i18n("Close Query"), KStandardGuiItem::close(), KStandardGuiItem::cancel(), QStringLiteral("QuitQueryTab")); if (result == KMessageBox::Continue) { m_server->removeQuery(this); return true; } else m_recreationScheduled = false; return false; } void Query::urlsDropped(const QList& urls) { m_server->sendURIs(urls, getName()); } void Query::emitUpdateInfo() { QString info; if(m_nickInfo->loweredNickname() == m_server->loweredNickname()) info = i18n("Talking to yourself"); else if(m_nickInfo) info = m_nickInfo->getBestAddresseeName(); else info = getName(); emit updateInfo(info); } // show quit message of nick if we see it void Query::quitNick(const QString& reason, const QHash &messageTags) { QString displayReason = reason; if (displayReason.isEmpty()) { appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostmask", "%1 (%2) has left this server.", getName(), getNickInfo()->getHostmask()), messageTags, false); } else { if (hasIRCMarkups(displayReason)) displayReason+=QStringLiteral("\017"); appendCommandMessage(i18nc("Message type", "Quit"), i18nc("%1 = nick, %2 = hostmask, %3 = reason", "%1 (%2) has left this server (%3).", getName(), getNickInfo()->getHostmask(), displayReason), messageTags, false); } } #ifdef HAVE_QCA2 Konversation::Cipher* Query::getCipher() { if(!m_cipher) m_cipher = new Konversation::Cipher(); return m_cipher; } #endif diff --git a/src/irc/query.h b/src/irc/query.h index 01cb0bfd..902ee0f8 100644 --- a/src/irc/query.h +++ b/src/irc/query.h @@ -1,122 +1,122 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005-2008 Eike Hein */ #ifndef QUERY_H #define QUERY_H #include "chatwindow.h" #include "nickinfo.h" #include #ifdef HAVE_QCA2 #include "cipher.h" #endif #include /* TODO: Idle counter to close query after XXX minutes of inactivity */ /* TODO: Use /USERHOST to check if queries are still valid */ class AwayLabel; class IRCInput; class QLabel; class QSplitter; class KSqueezedTextLabel; class Query : public ChatWindow { Q_OBJECT public: explicit Query(QWidget* parent, const QString& _name); void setServer(Server* newServer) Q_DECL_OVERRIDE; ~Query() override; IrcContextMenus::MenuOptions contextMenuOptions() const override; /** This will always be called soon after this object is created. * @param nickInfo A nickinfo that must exist. */ void setNickInfo(const NickInfoPtr & nickInfo); /** It seems that this does _not_ guaranttee to return non null. * The problem is when you open a query to someone, then the go offline. * This should be fixed maybe? I don't know. */ NickInfoPtr getNickInfo(); bool closeYourself(bool askForConfirmation=true) Q_DECL_OVERRIDE; bool canBeFrontView() Q_DECL_OVERRIDE; bool searchView() Q_DECL_OVERRIDE; void setChannelEncoding(const QString& encoding) Q_DECL_OVERRIDE; QString getChannelEncoding() Q_DECL_OVERRIDE; QString getChannelEncodingDefaultDesc() Q_DECL_OVERRIDE; void emitUpdateInfo() Q_DECL_OVERRIDE; /** call this when you see a nick quit from the server. * @param reason The quit reason given by that user. */ void quitNick(const QString& reason, const QHash &messageTags); #ifdef HAVE_QCA2 Konversation::Cipher* getCipher(); #endif Q_SIGNALS: void sendFile(const QString& recipient); void updateQueryChrome(ChatWindow*, const QString&); public Q_SLOTS: void sendText(const QString& text) Q_DECL_OVERRIDE; void indicateAway(bool show) Q_DECL_OVERRIDE; void setEncryptedOutput(bool); - void connectionStateChanged(Server*, Konversation::ConnectionState); + void connectionStateChanged(Konversation::ConnectionState); protected Q_SLOTS: void queryTextEntered(); void queryPassthroughCommand(); void sendFileMenu(); void urlsDropped(const QList& urls); void nickInfoChanged(); void updateNickInfo(Server* server, NickInfoPtr nickInfo); protected: void setName(const QString& newName) Q_DECL_OVERRIDE; void showEvent(QShowEvent* event) Q_DECL_OVERRIDE; /** Called from ChatWindow adjustFocus */ void childAdjustFocus() Q_DECL_OVERRIDE; private: bool awayChanged; bool awayState; QString queryName; QString buffer; QSplitter* m_headerSplitter; KSqueezedTextLabel* queryHostmask; QLabel* blowfishLabel; AwayLabel* awayLabel; NickInfoPtr m_nickInfo; bool m_initialShow; #ifdef HAVE_QCA2 //FIXME: We might want to put this into the attendee object (i.e. NickInfo). Konversation::Cipher *m_cipher; #endif }; #endif diff --git a/src/irc/server.cpp b/src/irc/server.cpp index 7be43dcf..8500bbf0 100644 --- a/src/irc/server.cpp +++ b/src/irc/server.cpp @@ -1,4455 +1,4455 @@ // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*- /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #include "server.h" #include "ircqueue.h" #include "query.h" #include "channel.h" #include "application.h" #include "connectionmanager.h" #include "dcccommon.h" #include "dccfiledialog.h" #include "transfermanager.h" #include "transfersend.h" #include "transferrecv.h" #include "chat.h" #include "recipientdialog.h" #include "nick.h" #include "irccharsets.h" #include "viewcontainer.h" #include "rawlog.h" #include "channellistpanel.h" #include "scriptlauncher.h" #include "serverison.h" #include "notificationhandler.h" #include "awaymanager.h" #include "ircinput.h" #include #include #include #include #include #include #include #include #include #include using namespace Konversation; int Server::m_availableConnectionId = 0; Server::Server(QObject* parent, ConnectionSettings& settings) : QObject(parent) { m_connectionId = m_availableConnectionId; m_availableConnectionId++; setConnectionSettings(settings); - m_connectionState = Konversation::SSNeverConnected; + m_connectionState = Konversation::NeverConnected; m_recreationScheduled = false; m_delayedConnectTimer = new QTimer(this); m_delayedConnectTimer->setSingleShot(true); connect(m_delayedConnectTimer, SIGNAL(timeout()), this, SLOT(connectToIRCServer())); m_reconnectImmediately = false; m_userModel = new UserModel(this); for (int i=0; i <= Application::instance()->countOfQueues(); i++) { //QList r=Preferences::queueRate(i); IRCQueue *q=new IRCQueue(this, Application::instance()->staticrates[i]); //FIXME these are supposed to be in the rc m_queues.append(q); } m_processingIncoming = false; m_identifyMsg = false; m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_autoJoin = false; m_hasAwayNotify = false; m_hasExtendedJoin = false; m_hasWHOX = false; m_hasServerTime = false; m_hasUserHostInNames = false; m_nickIndices.clear(); m_nickIndices.append(0); m_nickListModel = new QStringListModel(this); m_currentLag = -1; m_rawLog = nullptr; m_channelListPanel = nullptr; m_serverISON = nullptr; m_away = false; m_socket = nullptr; m_prevISONList = QStringList(); m_bytesReceived = 0; m_encodedBytesSent = 0; m_bytesSent = 0; m_linesSent = 0; // TODO fold these into a QMAP, and these need to be reset to RFC values if this server object is reused. m_serverNickPrefixModes = QStringLiteral("ovh"); m_serverNickPrefixes = QStringLiteral("@+%"); m_banAddressListModes = QLatin1Char('b'); // {RFC-1459, draft-brocklesby-irc-isupport} -> pick one m_channelPrefixes = QStringLiteral("#&"); m_modesCount = 3; m_sslErrorLock = false; m_topicLength = -1; setObjectName(QString::fromLatin1("server_") + m_connectionSettings.name()); setNickname(m_connectionSettings.initialNick()); obtainNickInfo(getNickname()); m_statusView = getViewContainer()->addStatusView(this); if (Preferences::self()->rawLog()) addRawLog(false); m_inputFilter.setServer(this); m_outputFilter = new Konversation::OutputFilter(this); // For /msg query completion m_completeQueryPosition = 0; updateAutoJoin(m_connectionSettings.oneShotChannelList()); if (!getIdentity()->getShellCommand().isEmpty()) QTimer::singleShot(0, this, SLOT(doPreShellCommand())); else QTimer::singleShot(0, this, SLOT(connectToIRCServer())); initTimers(); if (getIdentity()->getShellCommand().isEmpty()) connectSignals(); // TODO FIXME this disappeared in a merge, ensure it should have - updateConnectionState(Konversation::SSNeverConnected); + updateConnectionState(Konversation::NeverConnected); m_nickInfoChangedTimer = new QTimer(this); m_nickInfoChangedTimer->setSingleShot(true); m_nickInfoChangedTimer->setInterval(3000); connect(m_nickInfoChangedTimer, SIGNAL(timeout()), this, SLOT(sendNickInfoChangedSignals())); m_channelNickChangedTimer = new QTimer(this); m_channelNickChangedTimer->setSingleShot(true); m_channelNickChangedTimer->setInterval(1000); connect(m_channelNickChangedTimer, SIGNAL(timeout()), this, SLOT(sendChannelNickChangedSignals())); } Server::~Server() { //send queued messages qDebug() << "Server::~Server(" << getServerName() << ")"; // Delete helper object. delete m_serverISON; m_serverISON = nullptr; // clear nicks online emit nicksNowOnline(this,QStringList(),true); // Make sure no signals get sent to a soon to be dying Server Window if (m_socket) { m_socket->blockSignals(true); m_socket->deleteLater(); } delete m_statusView; closeRawLog(); closeChannelListPanel(); if (m_recreationScheduled) { Konversation::ChannelList channelList; foreach (Channel* channel, m_channelList) { channelList << channel->channelSettings(); } m_connectionSettings.setOneShotChannelList(channelList); } qDeleteAll(m_channelList); m_channelList.clear(); m_loweredChannelNameHash.clear(); qDeleteAll(m_queryList); m_queryList.clear(); purgeData(); //Delete the queues qDeleteAll(m_queues); emit destroyed(m_connectionId); if (m_recreationScheduled) { qRegisterMetaType("ConnectionSettings"); qRegisterMetaType("Konversation::ConnectionFlag"); Application* konvApp = Application::instance(); QMetaObject::invokeMethod(konvApp->getConnectionManager(), "connectTo", Qt::QueuedConnection, Q_ARG(Konversation::ConnectionFlag, Konversation::CreateNewConnection), Q_ARG(ConnectionSettings, m_connectionSettings)); } qDebug() << "~Server done"; } void Server::purgeData() { // Delete all the NickInfos and ChannelNick structures. m_allNicks.clear(); m_userModel->clear(); // WIPQTQUICK ChannelMembershipMap::ConstIterator it; for ( it = m_joinedChannels.constBegin(); it != m_joinedChannels.constEnd(); ++it ) delete it.value(); m_joinedChannels.clear(); for ( it = m_unjoinedChannels.constBegin(); it != m_unjoinedChannels.constEnd(); ++it ) delete it.value(); m_unjoinedChannels.clear(); m_queryNicks.clear(); delete m_serverISON; m_serverISON = nullptr; } //... so called to match the ChatWindow derivatives. bool Server::closeYourself(bool askForConfirmation) { m_statusView->closeYourself(askForConfirmation); return true; } void Server::cycle() { m_recreationScheduled = true; m_statusView->closeYourself(); } void Server::doPreShellCommand() { KShell::Errors e; QStringList command = KShell::splitArgs(getIdentity()->getShellCommand(), KShell::TildeExpand, &e); if (e != KShell::NoError) { //FIXME The flow needs to be refactored, add a finally-like method that does the ready-to-connect stuff // "The pre-connect shell command could not be understood!"); preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } else { // FIXME add i18n, and in preShellCommandExited and preShellCommandError getStatusView()->appendServerMessage(i18n("Info"), i18nc("The command mentioned is executed in a shell prior to connecting.", "Running pre-connect shell command...")); connect(&m_preShellCommand, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(preShellCommandExited(int,QProcess::ExitStatus))); connect(&m_preShellCommand, SIGNAL(error(QProcess::ProcessError)), SLOT(preShellCommandError(QProcess::ProcessError))); m_preShellCommand.setProgram(command); m_preShellCommand.start(); // NOTE: isConnecting is tested in connectToIRCServer so there's no guard here if (m_preShellCommand.state() == QProcess::NotRunning) preShellCommandExited(m_preShellCommand.exitCode(), m_preShellCommand.exitStatus()); } } void Server::initTimers() { m_notifyTimer.setObjectName(QStringLiteral("notify_timer")); m_notifyTimer.setSingleShot(true); m_incomingTimer.setObjectName(QStringLiteral("incoming_timer")); m_pingSendTimer.setSingleShot(true); } void Server::connectSignals() { // Timers connect(&m_incomingTimer, SIGNAL(timeout()), this, SLOT(processIncomingData())); connect(&m_notifyTimer, SIGNAL(timeout()), this, SLOT(notifyTimeout())); connect(&m_pingResponseTimer, SIGNAL(timeout()), this, SLOT(updateLongPongLag())); connect(&m_pingSendTimer, SIGNAL(timeout()), this, SLOT(sendPing())); // OutputFilter connect(getOutputFilter(), SIGNAL(requestDccSend()), this,SLOT(requestDccSend()), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(requestDccSend(QString)), this, SLOT(requestDccSend(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(multiServerCommand(QString,QString)), this, SLOT(sendMultiServerCommand(QString,QString))); connect(getOutputFilter(), SIGNAL(reconnectServer(QString)), this, SLOT(reconnectServer(QString))); connect(getOutputFilter(), SIGNAL(disconnectServer(QString)), this, SLOT(disconnectServer(QString))); connect(getOutputFilter(), SIGNAL(quitServer(QString)), this, SLOT(quitServer(QString))); connect(getOutputFilter(), SIGNAL(openDccSend(QString,QUrl)), this, SLOT(addDccSend(QString,QUrl)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccChat(QString)), this, SLOT(openDccChat(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(openDccWBoard(QString)), this, SLOT(openDccWBoard(QString)), Qt::QueuedConnection); connect(getOutputFilter(), SIGNAL(acceptDccGet(QString,QString)), this, SLOT(acceptDccGet(QString,QString))); connect(getOutputFilter(), SIGNAL(sendToAllChannels(QString)), this, SLOT(sendToAllChannels(QString))); connect(getOutputFilter(), SIGNAL(banUsers(QStringList,QString,QString)), this, SLOT(requestBan(QStringList,QString,QString))); connect(getOutputFilter(), SIGNAL(unbanUsers(QString,QString)), this, SLOT(requestUnban(QString,QString))); connect(getOutputFilter(), SIGNAL(openRawLog(bool)), this, SLOT(addRawLog(bool))); connect(getOutputFilter(), SIGNAL(closeRawLog()), this, SLOT(closeRawLog())); connect(getOutputFilter(), SIGNAL(encodingChanged()), this, SLOT(updateEncoding())); Application* konvApp = Application::instance(); connect(getOutputFilter(), SIGNAL(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); connect(konvApp->getDccTransferManager(), SIGNAL(newDccTransferQueued(Konversation::DCC::Transfer*)), this, SLOT(slotNewDccTransferItemQueued(Konversation::DCC::Transfer*))); // ViewContainer connect(this, SIGNAL(showView(QObject*)), getViewContainer(), SLOT(showView(QObject*))); // WIPQTQUICK connect(this, SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); connect(this, SIGNAL(addDccChat(Konversation::DCC::Chat*)), getViewContainer(), SLOT(addDccChat(Konversation::DCC::Chat*)), Qt::QueuedConnection); connect(this, SIGNAL(serverLag(Server*,int)), getViewContainer(), SIGNAL(updateStatusBarLagLabel(Server*,int))); connect(this, SIGNAL(tooLongLag(Server*,int)), getViewContainer(), SIGNAL(setStatusBarLagLabelTooLongLag(Server*,int))); connect(this, SIGNAL(resetLag(Server*)), getViewContainer(), SIGNAL(resetStatusBarLagLabel(Server*))); connect(getOutputFilter(), SIGNAL(showView(QObject*)), getViewContainer(), SLOT(showView(QObject*))); // WIPQTQUICK connect(getOutputFilter(), SIGNAL(openKonsolePanel()), getViewContainer(), SLOT(addKonsolePanel())); connect(getOutputFilter(), SIGNAL(openChannelList(QString)), this, SLOT(requestOpenChannelListPanel(QString))); connect(getOutputFilter(), SIGNAL(closeDccPanel()), getViewContainer(), SLOT(closeDccPanel())); connect(getOutputFilter(), SIGNAL(addDccPanel()), getViewContainer(), SLOT(addDccPanel())); // Inputfilter - queued connections should be used for slots that have blocking UI connect(&m_inputFilter, SIGNAL(addDccChat(QString,QStringList)), this, SLOT(addDccChat(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(rejectDccChat(QString)), this, SLOT(rejectDccChat(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccChat(QString,QStringList)), this, SLOT(startReverseDccChat(QString,QStringList))); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(capCheckIgnored())); connect(&m_inputFilter, SIGNAL(welcome(QString)), this, SLOT(connectionEstablished(QString))); connect(&m_inputFilter, SIGNAL(notifyResponse(QString)), this, SLOT(notifyResponse(QString))); connect(&m_inputFilter, SIGNAL(startReverseDccSendTransfer(QString,QStringList)), this, SLOT(startReverseDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(addDccGet(QString,QStringList)), this, SLOT(addDccGet(QString,QStringList)), Qt::QueuedConnection); connect(&m_inputFilter, SIGNAL(resumeDccGetTransfer(QString,QStringList)), this, SLOT(resumeDccGetTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(resumeDccSendTransfer(QString,QStringList)), this, SLOT(resumeDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(rejectDccSendTransfer(QString,QStringList)), this, SLOT(rejectDccSendTransfer(QString,QStringList))); connect(&m_inputFilter, SIGNAL(userhost(QString,QString,bool,bool)), this, SLOT(userhost(QString,QString,bool,bool)) ); connect(&m_inputFilter, SIGNAL(topicAuthor(QString,QString,QDateTime)), this, SLOT(setTopicAuthor(QString,QString,QDateTime)) ); connect(&m_inputFilter, SIGNAL(endOfWho(QString)), this, SLOT(endOfWho(QString)) ); connect(&m_inputFilter, SIGNAL(endOfNames(QString)), this, SLOT(endOfNames(QString)) ); connect(&m_inputFilter, SIGNAL(invitation(QString,QString)), this,SLOT(invitation(QString,QString)) ); connect(&m_inputFilter, SIGNAL(addToChannelList(QString,int,QString)), this, SLOT(addToChannelList(QString,int,QString))); // Status View connect(this, SIGNAL(serverOnline(bool)), getStatusView(), SLOT(serverOnline(bool))); // Scripts connect(getOutputFilter(), SIGNAL(launchScript(int,QString,QString)), konvApp->getScriptLauncher(), SLOT(launchScript(int,QString,QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptNotFound(QString)), this, SLOT(scriptNotFound(QString))); connect(konvApp->getScriptLauncher(), SIGNAL(scriptExecutionError(QString)), this, SLOT(scriptExecutionError(QString))); connect(Preferences::self(), SIGNAL(notifyListStarted(int)), this, SLOT(notifyListStarted(int)), Qt::QueuedConnection); } int Server::getPort() { return getConnectionSettings().server().port(); } int Server::getLag() const { return m_currentLag; } bool Server::getAutoJoin() const { return m_autoJoin; } void Server::setAutoJoin(bool on) { m_autoJoin = on; } void Server::preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if (exitStatus == QProcess::NormalExit) getStatusView()->appendServerMessage(i18n("Info"), i18n("Pre-shell command executed successfully!")); else { QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); } connectToIRCServer(); connectSignals(); } void Server::preShellCommandError(QProcess::ProcessError error) { Q_UNUSED(error); QString errorText = i18nc("An error message from KDE or Qt is appended.", "There was a problem while executing the command: ") % m_preShellCommand.errorString(); getStatusView()->appendServerMessage(i18n("Warning"), errorText); connectToIRCServer(); connectSignals(); } void Server::connectToIRCServer() { if (!isConnected() && !isConnecting()) { if (m_sslErrorLock) { qDebug() << "Refusing to connect while SSL lock from previous connection attempt is being held."; return; } // Reenable check when it works reliably for all backends // if(Solid::Networking::status() != Solid::Networking::Connected) // { -// updateConnectionState(Konversation::SSInvoluntarilyDisconnected); +// updateConnectionState(Konversation::InvoluntarilyDisconnected); // return; // } - updateConnectionState(Konversation::SSConnecting); + updateConnectionState(Konversation::Connecting); m_ownIpByUserhost.clear(); resetQueues(); // This is needed to support server groups with mixed SSL and nonSSL servers delete m_socket; m_socket = nullptr; if (m_referenceNicklist != getIdentity()->getNicknameList()) m_nickListModel->setStringList(getIdentity()->getNicknameList()); resetNickSelection(); m_socket = new KTcpSocket(); m_socket->setObjectName(QStringLiteral("serverSocket")); connect(m_socket, SIGNAL(error(KTcpSocket::Error)), SLOT(broken(KTcpSocket::Error)) ); connect(m_socket, SIGNAL(readyRead()), SLOT(incoming())); connect(m_socket, SIGNAL(disconnected()), SLOT(closed())); connect(m_socket, SIGNAL(hostFound()), SLOT (hostFound())); getStatusView()->appendServerMessage(i18n("Info"),i18n("Looking for server %1 (port %2)...", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); KTcpSocket::ProxyPolicy proxyPolicy = KTcpSocket::AutoProxy; if(getConnectionSettings().server().bypassProxy()) { proxyPolicy = KTcpSocket::ManualProxy; m_socket->setProxy(QNetworkProxy::NoProxy); } // connect() will do a async lookup too if (getConnectionSettings().server().SSLEnabled() || getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { connect(m_socket, SIGNAL(encrypted()), SLOT (socketConnected())); connect(m_socket, SIGNAL(sslErrors(QList)), SLOT(sslError(QList))); if (getIdentity()->getAuthType() == QStringLiteral("saslexternal") || getIdentity()->getAuthType() == QStringLiteral("pemclientcert")) { m_socket->setLocalCertificate(getIdentity()->getPemClientCertFile().toLocalFile()); m_socket->setPrivateKey(getIdentity()->getPemClientCertFile().toLocalFile()); } m_socket->setAdvertisedSslVersion(KTcpSocket::SecureProtocols); m_socket->connectToHostEncrypted(getConnectionSettings().server().host(), getConnectionSettings().server().port()); } else { connect(m_socket, SIGNAL(connected()), SLOT (socketConnected())); m_socket->connectToHost(getConnectionSettings().server().host(), getConnectionSettings().server().port(), proxyPolicy); } // set up the connection details setPrefixes(m_serverNickPrefixModes, m_serverNickPrefixes); // reset InputFilter (auto request info, /WHO request info) m_inputFilter.reset(); } else qDebug() << "connectToIRCServer() called while already connected: This should never happen. (" << (isConnecting() << 1) + isConnected() << ')'; } void Server::connectToIRCServerIn(uint delay) { m_delayedConnectTimer->setInterval(delay * 1000); m_delayedConnectTimer->start(); - updateConnectionState(Konversation::SSScheduledToConnect); + updateConnectionState(Konversation::ScheduledToConnect); } void Server::showSSLDialog() { //TODO /* SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); */ } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const QString &pre) { m_channelPrefixes = pre; if (getConnectionSettings().reconnectCount() == 0) { updateAutoJoin(m_connectionSettings.oneShotChannelList()); } else { updateAutoJoin(); } } QString Server::getChannelTypes() const { return m_channelPrefixes; } // set max number of channel modes with parameter according to 005 RPL_ISUPPORT void Server::setModesCount(int count) { m_modesCount = count; } int Server::getModesCount() { return m_modesCount; } // set user mode prefixes according to non-standard 005-Reply (see inputfilter.cpp) void Server::setPrefixes(const QString &modes, const QString& prefixes) { // NOTE: serverModes is QString::null, if server did not supply the // modes which relates to the network's nick-prefixes m_serverNickPrefixModes = modes; m_serverNickPrefixes = prefixes; } QString Server::getServerNickPrefixes() const { return m_serverNickPrefixes; } void Server::setChanModes(const QString& modes) { QStringList abcd = modes.split(QLatin1Char(',')); m_banAddressListModes = abcd.value(0); } // return a nickname without possible mode character at the beginning void Server::mangleNicknameWithModes(QString& nickname,bool& isAdmin,bool& isOwner, bool& isOp,bool& isHalfop,bool& hasVoice) { isAdmin = false; isOwner = false; isOp = false; isHalfop = false; hasVoice = false; int modeIndex; if (nickname.isEmpty()) return; while ((modeIndex = m_serverNickPrefixes.indexOf(nickname[0])) != -1) { if(nickname.isEmpty()) return; nickname = nickname.mid(1); // cut off the prefix bool recognisedMode = false; // determine, whether status is like op or like voice while(modeIndex < m_serverNickPrefixes.length() && !recognisedMode) { switch(m_serverNickPrefixes[modeIndex].toLatin1()) { case '*': // admin (EUIRC) { isAdmin = true; recognisedMode = true; break; } case '&': // admin (unrealircd) { isAdmin = true; recognisedMode = true; break; } case '!': // channel owner (RFC2811) { isOwner = true; recognisedMode = true; break; } case '~': // channel owner (unrealircd) { isOwner = true; recognisedMode = true; break; } case '@': // channel operator (RFC1459) { isOp = true; recognisedMode = true; break; } case '%': // halfop { isHalfop = true; recognisedMode = true; break; } case '+': // voiced (RFC1459) { hasVoice = true; recognisedMode = true; break; } default: { ++modeIndex; break; } } //switch to recognise the mode. } // loop through the modes to find one recognised } // loop through the name } void Server::hostFound() { getStatusView()->appendServerMessage(i18n("Info"),i18n("Server found, connecting...")); } void Server::socketConnected() { emit sslConnected(this); getConnectionSettings().setReconnectCount(0); requestAvailableCapabilies(); QStringList ql; if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("serverpw") && !getIdentity()->getAuthPassword().isEmpty()) { ql << QStringLiteral("PASS ") + getIdentity()->getAuthPassword(); } else if (!getConnectionSettings().server().password().isEmpty()) ql << QStringLiteral("PASS ") + getConnectionSettings().server().password(); ql << QStringLiteral("NICK ") + getNickname(); ql << QStringLiteral("USER ") + getIdentity()->getIdent() + QStringLiteral(" 8 * :") /* 8 = +i; 4 = +w */ + getIdentity()->getRealName(); queueList(ql, HighPriority); connect(this, SIGNAL(nicknameChanged(QString)), getStatusView(), SLOT(setNickname(QString))); setNickname(getNickname()); } void Server::requestAvailableCapabilies () { getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server...")); m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true); queue(QStringLiteral("CAP LS"), HighPriority); } void Server::capInitiateNegotiation(const QString &availableCaps) { bool useSASL = false; if (getIdentity()) { // A username is optional SASL EXTERNAL and a client cert substitutes // for the password. if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { useSASL = true; // PLAIN on the other hand requires both. } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain") && !getIdentity()->getSaslAccount().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { useSASL = true; } } m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_hasAwayNotify = false; m_hasExtendedJoin = false; m_hasServerTime = false; m_hasUserHostInNames = false; QStringList requestCaps; QStringList capsList = availableCaps.split (QChar(' '), QString::SkipEmptyParts); foreach(const QString &cap, capsList) { if(useSASL && cap == QStringLiteral("sasl")) { requestCaps.append ("sasl"); } else if(cap == QStringLiteral("multi-prefix")) { requestCaps.append ("multi-prefix"); } else if(cap == QStringLiteral("away-notify")) { requestCaps.append ("away-notify"); } else if(cap == QStringLiteral("account-notify")) { requestCaps.append ("account-notify"); } else if(cap == QStringLiteral("extended-join")) { requestCaps.append ("extended-join"); } else if(cap == QStringLiteral("server-time")) { requestCaps.append ("server-time"); } else if(cap == QStringLiteral("znc.in/server-time-iso")) { requestCaps.append ("znc.in/server-time-iso"); } else if(cap == QStringLiteral("userhost-in-names")) { requestCaps.append ("userhost-in-names"); } } QString capsString = requestCaps.join (QStringLiteral(" ")); getStatusView()->appendServerMessage(i18n("Info"),i18n("Requesting capabilities: %1", capsString)); queue(QStringLiteral("CAP REQ :") + capsString, HighPriority); m_capRequested++; } void Server::capReply() { m_capAnswered++; } void Server::capEndNegotiation() { if(m_capRequested == m_capAnswered) { getStatusView()->appendServerMessage(i18n("Info"),i18n("Closing capabilities negotiation.")); queue(QStringLiteral("CAP END"), HighPriority); } } void Server::capCheckIgnored() { if (m_capRequested && !m_capAnswered) getStatusView()->appendServerMessage(i18n("Error"), i18n("Capabilities negotiation failed: Appears not supported by server.")); } void Server::capAcknowledged(const QString& name, Server::CapModifiers modifiers) { if (name == QStringLiteral("sasl") && modifiers == Server::NoModifiers) { const QString &authCommand = (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) ? QStringLiteral("EXTERNAL") : QStringLiteral("PLAIN"); getStatusView()->appendServerMessage(i18n("Info"), i18n("SASL capability acknowledged by server, attempting SASL %1 authentication...", authCommand)); sendAuthenticate(authCommand); m_capEndDelayed = true; } else if (name == QStringLiteral("away-notify")) { m_hasAwayNotify = true; } else if (name == QStringLiteral("extended-join")) { m_hasExtendedJoin = true; } else if (name == QStringLiteral("server-time") || name == QStringLiteral("znc.in/server-time-iso")) { m_hasServerTime = true; } else if (name == QStringLiteral("userhost-in-names")) { m_hasUserHostInNames = true; } } void Server::capDenied(const QString& name) { if (name == QStringLiteral("sasl")) getStatusView()->appendServerMessage(i18n("Error"), i18n("SASL capability denied or not supported by server.")); } void Server::registerWithServices() { if (!getIdentity()) return; NickInfoPtr nickInfo = getNickInfo(getNickname()); if (nickInfo && nickInfo->isIdentified()) return; if (getIdentity()->getAuthType() == QStringLiteral("nickserv")) { if (!getIdentity()->getNickservNickname().isEmpty() && !getIdentity()->getNickservCommand().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { queue(QStringLiteral("PRIVMSG ")+getIdentity()->getNickservNickname()+QStringLiteral(" :")+getIdentity()->getNickservCommand()+QLatin1Char(' ')+getIdentity()->getAuthPassword(), HighPriority); } } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain")) { QString authString = getIdentity()->getSaslAccount(); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getAuthPassword()); sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } else if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { QString authString = getIdentity()->getSaslAccount(); // An account name is optional with SASL EXTERNAL. if (!authString.isEmpty()) { authString.append(QChar(QChar::Null)); authString.append(getIdentity()->getSaslAccount()); } sendAuthenticate(QLatin1String(authString.toLatin1().toBase64())); } } void Server::sendAuthenticate(const QString& message) { m_lastAuthenticateCommand = message; if (message.isEmpty()) { queue(QStringLiteral("AUTHENTICATE +"), HighPriority); } else { queue(QStringLiteral("AUTHENTICATE ") + message, HighPriority); } } void Server::broken(KTcpSocket::Error error) { Q_UNUSED(error); - qDebug() << "Connection broken with state" << m_connectionState << "and error:" << m_socket->errorString(); + qDebug() << "Connection broken with state" << static_cast(m_connectionState) << "and error:" << m_socket->errorString(); m_socket->blockSignals(true); resetQueues(); m_notifyTimer.stop(); m_pingSendTimer.stop(); m_pingResponseTimer.stop(); m_inputFilter.setLagMeasuring(false); m_currentLag = -1; purgeData(); emit resetLag(this); emit nicksNowOnline(this, QStringList(), true); m_prevISONList.clear(); updateAutoJoin(); if (m_sslErrorLock) { // We got disconnected while dealing with an SSL error, e.g. due to the // user taking their time on dealing with the error dialog. Since auto- // reconnecting makes no sense in this situation, let's pass it off as a // deliberate disconnect. sslError() will either kick off a reconnect, or // reaffirm this. getStatusView()->appendServerMessage(i18n("SSL Connection Error"), i18n("Connection to server %1 (port %2) lost while waiting for user response to an SSL error. " "Will automatically reconnect if error is ignored.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); - updateConnectionState(SSDeliberatelyDisconnected); + updateConnectionState(Konversation::DeliberatelyDisconnected); } - else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected) + else if (getConnectionState() == Konversation::DeliberatelyDisconnected) { if (m_reconnectImmediately) { m_reconnectImmediately = false; QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { Application::instance()->notificationHandler()->connectionFailure(getStatusView(), getServerName()); QString error = i18n("Connection to server %1 (port %2) lost: %3.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), m_socket->errorString()); getStatusView()->appendServerMessage(i18n("Error"), error); - updateConnectionState(Konversation::SSInvoluntarilyDisconnected); + updateConnectionState(Konversation::InvoluntarilyDisconnected); } // HACK Only show one nick change dialog at connection time. // This hack is a bit nasty as it assumes that the only QInputDialog // child of the statusview will be the nick change dialog. if (getStatusView()) { QInputDialog* nickChangeDialog = getStatusView()->findChild(); if (nickChangeDialog) nickChangeDialog->reject(); } } void Server::sslError( const QList& errors ) { // We have to explicitly grab the socket we got the error from, // lest we might end up calling ignoreSslErrors() on a different // socket later if m_socket has started pointing at something // else. QPointer socket = qobject_cast(QObject::sender()); m_sslErrorLock = true; bool ignoreSslErrors = KIO::SslUi::askIgnoreSslErrors(socket, KIO::SslUi::RecallAndStoreRules); m_sslErrorLock = false; // The dialog-based user interaction above may take an undefined amount // of time, and anything could happen to the socket in that span of time. // If it was destroyed, let's not do anything and bail out. if (!socket) { qDebug() << "Socket was destroyed while waiting for user interaction."; return; } // Ask the user if he wants to ignore the errors. if (ignoreSslErrors) { // Show a warning in the chat window that the SSL certificate failed the authenticity check. QString error = i18n("The SSL certificate for the server %1 (port %2) failed the authenticity check.", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port())); getStatusView()->appendServerMessage(i18n("SSL Connection Warning"), error); // We may have gotten disconnected while waiting for user response and have // to reconnect. if (isConnecting()) { // The user has chosen to ignore SSL errors. socket->ignoreSslErrors(); } else { // QueuedConnection is vital here, otherwise we're deleting the socket // in a slot connected to one of its signals (connectToIRCServer deletes // any old socket) and crash. QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } } else { // Don't auto-reconnect if the user chose to ignore the SSL errors -- // treat it as a deliberate disconnect. - updateConnectionState(Konversation::SSDeliberatelyDisconnected); + updateConnectionState(Konversation::DeliberatelyDisconnected); QString errorReason; for (const auto & error : errors) { errorReason += error.errorString() + QLatin1Char(' '); } QString error = i18n("Could not connect to %1 (port %2) using SSL encryption. Either the server does not support SSL (did you use the correct port?) or you rejected the certificate. %3", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()), errorReason); getStatusView()->appendServerMessage(i18n("SSL Connection Error"), error); emit sslInitFailure(); } } // Will be called from InputFilter as soon as the Welcome message was received void Server::connectionEstablished(const QString& ownHost) { // Some servers don't include the userhost in RPL_WELCOME, so we // need to use RPL_USERHOST to get ahold of our IP later on if (!ownHost.isEmpty()) QHostInfo::lookupHost(ownHost, this, SLOT(gotOwnResolvedHostByWelcome(QHostInfo))); - updateConnectionState(Konversation::SSConnected); + updateConnectionState(Konversation::Connected); // Make a helper object to build ISON (notify) list. // TODO: Give the object a kick to get it started? delete m_serverISON; m_serverISON = new ServerISON(this); // get first notify very early startNotifyTimer(1000); // Register with services if (getIdentity() && getIdentity()->getAuthType() == QStringLiteral("nickserv")) registerWithServices(); // get own ip by userhost requestUserhost(getNickname()); // Start the PINGPONG match m_pingSendTimer.start(1000 /*1 sec*/); // Recreate away state if we were set away prior to a reconnect. if (m_away) { // Correct server's beliefs about its away state. m_away = false; requestAway(m_awayReason); } } //FIXME operator[] inserts an empty T& so each destination might just as well have its own key storage QByteArray Server::getKeyForRecipient(const QString& recipient) const { return m_keyHash[recipient.toLower()]; } void Server::setKeyForRecipient(const QString& recipient, const QByteArray& key) { m_keyHash[recipient.toLower()] = key; } void Server::gotOwnResolvedHostByWelcome(const QHostInfo& res) { if (res.error() == QHostInfo::NoError && !res.addresses().isEmpty()) m_ownIpByWelcome = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } bool Server::isSocketConnected() const { if (!m_socket) return false; return (m_socket->state() == KTcpSocket::ConnectedState); } void Server::updateConnectionState(Konversation::ConnectionState state) { if (state != m_connectionState) { m_connectionState = state; - if (m_connectionState == Konversation::SSConnected) + if (m_connectionState == Konversation::Connected) emit serverOnline(true); - else if (m_connectionState != Konversation::SSConnecting) + else if (m_connectionState != Konversation::Connecting) emit serverOnline(false); - emit connectionStateChanged(this, state); + emit connectionStateChanged(state); } } void Server::reconnectServer(const QString& quitMessage) { if (isConnecting() || isSocketConnected()) { m_reconnectImmediately = true; quitServer(quitMessage); } else QMetaObject::invokeMethod(this, "connectToIRCServer", Qt::QueuedConnection); } void Server::disconnectServer(const QString& quitMessage) { getConnectionSettings().setReconnectCount(0); if (isScheduledToConnect()) { m_delayedConnectTimer->stop(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Delayed connect aborted.")); } if (isSocketConnected()) quitServer(quitMessage); } void Server::quitServer(const QString& quitMessage) { // Make clear this is deliberate even if the QUIT never actually goes through the queue // (i.e. this is not redundant with _send_internal()'s updateConnectionState() call for // a QUIT). - updateConnectionState(Konversation::SSDeliberatelyDisconnected); + updateConnectionState(Konversation::DeliberatelyDisconnected); if (!m_socket) return; QString toServer = QStringLiteral("QUIT :"); if (quitMessage.isEmpty()) toServer += getIdentity()->getQuitReason(); else toServer += quitMessage; queue(toServer, HighPriority); flushQueues(); m_socket->flush(); // Close the socket to allow a dead connection to be reconnected before the socket timeout. m_socket->close(); getStatusView()->appendServerMessage(i18n("Info"), i18n("Disconnected from %1 (port %2).", getConnectionSettings().server().host(), QString::number(getConnectionSettings().server().port()))); } void Server::notifyAction(const QString& nick) { QString out(Preferences::self()->notifyDoubleClickAction()); getOutputFilter()->replaceAliases(out); // parse wildcards (toParse,nickname,channelName,nickList,parameter) out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString()); // Send all strings, one after another QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (int index=0; indexparse(getNickname(),outList[index],QString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const QString& nicksOnline) { bool nicksOnlineChanged = false; QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts); QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' '); QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QStringLiteral(" "))) + QLatin1Char(' '); QStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (!lcActual.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (!lcPrevISON.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setWatchedNickOnline(*it); nicksOnlineChanged = true; } } // Note: The list emitted in this signal *does* include nicks in joined channels. emit nicksNowOnline(this, actualList, nicksOnlineChanged); m_prevISONList = actualList; // Next round startNotifyTimer(); } void Server::notifyListStarted(int serverGroupId) { if (getServerGroup()) if (getServerGroup()->id() == serverGroupId) startNotifyTimer(1000); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (Preferences::self()->useNotify()) { if (msec == 0) msec = Preferences::self()->notifyDelay() * 1000; m_notifyTimer.start(msec); } } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::self()->useNotify()) { // But only if there actually are nicks in the notify list QString list = getISONListString(); if (!list.isEmpty()) queue(QStringLiteral("ISON ") + list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { QString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace(QStringLiteral("%nick"), getNickname()); QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts); QStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { QString output(*iter); output = output.simplified(); getOutputFilter()->replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for (auto & m_autoJoinCommand : m_autoJoinCommands) queue(m_autoJoinCommand); } if (!m_connectionSettings.oneShotChannelList().isEmpty()) { QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList()); for (auto & it : oneShotJoin) { queue(it); } m_connectionSettings.clearOneShotChannelList(); } } /** Create a set of indices into the nickname list of the current identity based on the current nickname. * * The index list is only used if the current nickname is not available. If the nickname is in the identity, * we do not want to retry it. If the nickname is not in the identity, it is considered to be at position -1. */ void Server::resetNickSelection() { m_nickIndices.clear(); // For equivalence testing in case the identity gets changed underneath us. m_referenceNicklist = getIdentity()->getNicknameList(); for (int i = 0; i < m_referenceNicklist.length(); ++i) { // Pointless to include the nick we're already going to use. if (m_referenceNicklist.at(i) != getNickname()) { m_nickIndices.append(i); } } } QString Server::getNextNickname() { //if the identity changed underneath us (likely impossible), start over if (m_referenceNicklist != getIdentity()->getNicknameList()) resetNickSelection(); QString newNick; if (!m_nickIndices.isEmpty()) { newNick = getIdentity()->getNickname(m_nickIndices.takeFirst()); } else { QString inputText = i18n("No nicknames from the \"%1\" identity were accepted by the connection \"%2\".\nPlease enter a new one or press Cancel to disconnect:", getIdentity()->getName(), getDisplayName()); newNick = QInputDialog::getText(getStatusView(), i18n("Nickname error"), inputText); } return newNick; } void Server::processIncomingData() { m_incomingTimer.stop(); if (!m_inputBuffer.isEmpty() && !m_processingIncoming) { m_processingIncoming = true; QString front(m_inputBuffer.front()); m_inputBuffer.pop_front(); m_inputFilter.parseLine(front); m_processingIncoming = false; if (!m_inputBuffer.isEmpty()) m_incomingTimer.start(0); } } void Server::incoming() { //if (getConnectionSettings().server().SSLEnabled()) // emit sslConnected(this); //if (len <= 0 && getConnectionSettings().server().SSLEnabled()) // return; // split buffer to lines QList bufferLines; while (m_socket->canReadLine()) { QByteArray line(m_socket->readLine()); //remove \n blowfish doesn't like it int i = line.size()-1; while (i >= 0 && (line[i]=='\n' || line[i]=='\r')) // since euIRC gets away with sending just \r, bet someone sends \n\r? { i--; } line.truncate(i+1); if (line.size() > 0) bufferLines.append(line); } while(!bufferLines.isEmpty()) { // Pre parsing is needed in case encryption/decryption is needed // BEGIN set channel encoding if specified QString senderNick; bool isServerMessage = false; QString channelKey; QTextCodec* codec = getIdentity()->getCodec(); QByteArray first = bufferLines.first(); bufferLines.removeFirst(); QStringList lineSplit = codec->toUnicode(first).split(QLatin1Char(' '), QString::SkipEmptyParts); if (lineSplit.count() >= 1) { if (lineSplit[0][0] == QLatin1Char(':')) // does this message have a prefix? { if(!lineSplit[0].contains(QLatin1Char('!'))) // is this a server(global) message? isServerMessage = true; else senderNick = lineSplit[0].mid(1, lineSplit[0].indexOf(QLatin1Char('!'))-1); lineSplit.removeFirst(); // remove prefix } } if (lineSplit.isEmpty()) continue; // BEGIN pre-parse to know where the message belongs to QString command = lineSplit[0].toLower(); if( isServerMessage ) { if( lineSplit.count() >= 3 ) { if( command == QStringLiteral("332") ) // RPL_TOPIC channelKey = lineSplit[2]; if( command == QStringLiteral("372") ) // RPL_MOTD channelKey = QStringLiteral(":server"); } } else // NOT a global message { if( lineSplit.count() >= 2 ) { // query if( ( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") ) && lineSplit[1] == getNickname() ) { channelKey = senderNick; } // channel message else if( command == QStringLiteral("privmsg") || command == QStringLiteral("notice") || command == QStringLiteral("join") || command == QStringLiteral("kick") || command == QStringLiteral("part") || command == QStringLiteral("topic") ) { channelKey = lineSplit[1]; } } } // END pre-parse to know where the message belongs to // Decrypt if necessary //send to raw log before decryption if (m_rawLog) m_rawLog->appendRaw(RawLog::Inbound, first); #ifdef HAVE_QCA2 QByteArray cKey = getKeyForRecipient(channelKey); if(!cKey.isEmpty()) { if(command == "privmsg") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); if(this->identifyMsgEnabled()) // Workaround braindead Freenode prefixing messages with + ++index; QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decrypt(first.mid(index+1)); first.prepend(backup); } else if(command == "332" || command == "topic") { //only send encrypted text to decrypter int index = first.indexOf(":",first.indexOf(":")+1); QByteArray backup = first.mid(0,index+1); if(getChannelByName(channelKey) && getChannelByName(channelKey)->getCipher()->setKey(cKey)) first = getChannelByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); else if(getQueryByName(channelKey) && getQueryByName(channelKey)->getCipher()->setKey(cKey)) first = getQueryByName(channelKey)->getCipher()->decryptTopic(first.mid(index+1)); first.prepend(backup); } } #endif bool isUtf8 = Konversation::isUtf8(first); QString encoded; if (isUtf8) encoded = QString::fromUtf8(first); else { // check setting QString channelEncoding; if( !channelKey.isEmpty() ) { if(getServerGroup()) channelEncoding = Preferences::channelEncoding(getServerGroup()->id(), channelKey); else channelEncoding = Preferences::channelEncoding(getDisplayName(), channelKey); } // END set channel encoding if specified if( !channelEncoding.isEmpty() ) codec = Konversation::IRCCharsets::self()->codecForName(channelEncoding); // if channel encoding is utf-8 and the string is definitely not utf-8 // then try latin-1 if (codec->mibEnum() == 106) codec = QTextCodec::codecForMib( 4 /* iso-8859-1 */ ); encoded = codec->toUnicode(first); } // Qt uses 0xFDD0 and 0xFDD1 to mark the beginning and end of text frames. Remove // these here to avoid fatal errors encountered in QText* and the event loop pro- // cessing. sterilizeUnicode(encoded); if (!encoded.isEmpty()) m_inputBuffer << encoded; //FIXME: This has nothing to do with bytes, and it's not raw received bytes either. Bogus number. //m_bytesReceived+=m_inputBuffer.back().length(); } if( !m_incomingTimer.isActive() && !m_processingIncoming ) m_incomingTimer.start(0); } /** Calculate how long this message premable will be. This is necessary because the irc server will clip messages so that the client receives a maximum of 512 bytes at once. */ int Server::getPreLength(const QString& command, const QString& dest) { NickInfoPtr info = getNickInfo(getNickname()); int hostMaskLength = 0; if(info) hostMaskLength = info->getHostmask().length(); //:Sho_!i=ehs1@konversation/developer/hein PRIVMSG #konversation :and then back to it //$nickname$hostmask$command$destination$message int x= 512 - 8 - (m_nickname.length() + hostMaskLength + command.length() + dest.length()); return x; } //Commands greater than 1 have localizeable text: 0 1 2 3 4 5 6 static QStringList outcmds = QString(QStringLiteral("WHO QUIT PRIVMSG NOTICE KICK PART TOPIC")).split(QChar(QLatin1Char(' '))); int Server::_send_internal(QString outputLine) { QStringList outputLineSplit = outputLine.split(QLatin1Char(' '), QString::SkipEmptyParts); int outboundCommand = -1; if (!outputLineSplit.isEmpty()) { //Lets cache the uppercase command so we don't miss or reiterate too much outboundCommand = outcmds.indexOf(outputLineSplit[0].toUpper()); } if (outputLine.at(outputLine.length()-1) == QLatin1Char('\n')) { qDebug() << "found \\n on " << outboundCommand; outputLine.resize(outputLine.length()-1); } // remember the first arg of /WHO to identify responses if (outboundCommand == 0 && outputLineSplit.count() >= 2) //"WHO" m_inputFilter.addWhoRequest(outputLineSplit[1]); else if (outboundCommand == 1) //"QUIT" - updateConnectionState(Konversation::SSDeliberatelyDisconnected); + updateConnectionState(Konversation::DeliberatelyDisconnected); // set channel encoding if specified QString channelCodecName; //[ PRIVMSG | NOTICE | KICK | PART | TOPIC ] target :message if (outputLineSplit.count() > 2 && outboundCommand > 1) { if(getServerGroup()) // if we're connecting via a servergroup channelCodecName=Preferences::channelEncoding(getServerGroup()->id(), outputLineSplit[1]); else //if we're connecting to a server manually channelCodecName=Preferences::channelEncoding(getDisplayName(), outputLineSplit[1]); } QTextCodec* codec = nullptr; if (channelCodecName.isEmpty()) codec = getIdentity()->getCodec(); else codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); // Some codecs don't work with a negative value. This is a bug in Qt 3. // ex.: JIS7, eucJP, SJIS //int outlen=-1; //leaving this done twice for now, I'm uncertain of the implications of not encoding other commands QByteArray encoded = outputLine.toUtf8(); if(codec) encoded = codec->fromUnicode(outputLine); #ifdef HAVE_QCA2 QString cipherKey; if (outputLineSplit.count() > 2 && outboundCommand > 1) cipherKey = getKeyForRecipient(outputLineSplit.at(1)); if (!cipherKey.isEmpty()) { int colon = outputLine.indexOf(':'); if (colon > -1) { colon++; QString pay(outputLine.mid(colon)); //only encode the actual user text, IRCD *should* desire only ASCII 31 < x < 127 for protocol elements QByteArray payload = pay.toUtf8(); QByteArray dest; if (codec) { payload=codec->fromUnicode(pay); //apparently channel name isn't a protocol element... dest = codec->fromUnicode(outputLineSplit.at(1)); } else { dest = outputLineSplit.at(1).toLatin1(); } if (outboundCommand == 2 || outboundCommand == 6) // outboundCommand == 3 { bool doit = true; if (outboundCommand == 2) { //if its a privmsg and a ctcp but not an action, don't encrypt //not interpreting `payload` in case encoding bollixed it if (outputLineSplit.at(2).startsWith(QLatin1String(":\x01")) && outputLineSplit.at(2) != ":\x01""ACTION") doit = false; } if (doit) { QString target = outputLineSplit.at(1); if(getChannelByName(target) && getChannelByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getChannelByName(target)->getCipher()->encrypt(payload); else if(getQueryByName(target) && getQueryByName(target)->getCipher()->setKey(cipherKey.toLocal8Bit())) getQueryByName(target)->getCipher()->encrypt(payload); encoded = outputLineSplit.at(0).toLatin1(); qDebug() << payload << "\n" << payload.data(); //two lines because the compiler insists on using the wrong operator+ encoded += ' ' + dest + " :" + payload; } } } } #endif if (m_rawLog) m_rawLog->appendRaw(RawLog::Outbound, encoded); encoded += '\n'; qint64 sout = m_socket->write(encoded); return sout; } void Server::toServer(QString&s, IRCQueue* q) { int sizesent = _send_internal(s); emit sentStat(s.length(), sizesent, q); //tell the queues what we sent collectStats(s.length(), sizesent); } void Server::collectStats(int bytes, int encodedBytes) { m_bytesSent += bytes; m_encodedBytesSent += encodedBytes; m_linesSent++; } bool Server::validQueue(QueuePriority priority) { if (priority >=0 && priority <= Application::instance()->countOfQueues()) return true; return false; } bool Server::queue(const QString& line, QueuePriority priority) { if (!line.isEmpty() && validQueue(priority)) { IRCQueue& out=*m_queues[priority]; out.enqueue(line); return true; } return false; } bool Server::queueList(const QStringList& buffer, QueuePriority priority) { if (buffer.isEmpty() || !validQueue(priority)) return false; IRCQueue& out=*(m_queues[priority]); for(int i=0;icountOfQueues(); i++) m_queues[i]->reset(); } //this could flood you off, but you're leaving anyway... void Server::flushQueues() { int cue; do { cue=-1; int wait=0; for (int i=1; i <= Application::instance()->countOfQueues(); i++) //slow queue can rot { IRCQueue *queue=m_queues[i]; //higher queue indices have higher priorty, higher queue priority wins tie if (!queue->isEmpty() && queue->currentWait()>=wait) { cue=i; wait=queue->currentWait(); } } if (cue>-1) m_queues[cue]->sendNow(); } while (cue>-1); } void Server::closed() { broken(m_socket->error()); } void Server::dbusRaw(const QString& command) { if(command.startsWith(Preferences::self()->commandChar())) { queue(command.section(Preferences::self()->commandChar(), 1)); } else queue(command); } void Server::dbusSay(const QString& target,const QString& command) { if(isAChannel(target)) { Channel* channel=getChannelByName(target); if(channel) channel->sendText(command); } else { Query* query = getQueryByName(target); if(query == nullptr) { NickInfoPtr nickinfo = obtainNickInfo(target); query=addQuery(nickinfo, true); } if(query) { if(!command.isEmpty()) query->sendText(command); else { query->adjustFocus(); getViewContainer()->getWindow()->show(); KWindowSystem::demandAttention(getViewContainer()->getWindow()->winId()); KWindowSystem::activateWindow(getViewContainer()->getWindow()->winId()); } } } } void Server::dbusInfo(const QString& string) { appendMessageToFrontmost(i18n("D-Bus"),string); } void Server::ctcpReply(const QString &receiver,const QString &text) { queue(QStringLiteral("NOTICE ")+receiver+QStringLiteral(" :")+QLatin1Char('\x01')+text+QLatin1Char('\x01')); } // Given a nickname, returns NickInfo object. 0 if not found. NickInfoPtr Server::getNickInfo(const QString& nickname) { QString lcNickname(nickname.toLower()); if (m_allNicks.contains(lcNickname)) { NickInfoPtr nickinfo = m_allNicks[lcNickname]; Q_ASSERT(nickinfo); return nickinfo; } else return NickInfoPtr(); //! TODO FIXME null null null } // Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. // Returns pointer to the found or created NickInfo object. NickInfoPtr Server::obtainNickInfo(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(QString(nickname.toLower()), nickInfo); m_userModel->add(nickInfo); // WIPQTQUICK } return nickInfo; } const NickInfoMap* Server::getAllNicks() { return &m_allNicks; } // Returns the list of members for a channel in the joinedChannels list. // 0 if channel is not in the joinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getJoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_joinedChannels.contains(lcChannelName)) return m_joinedChannels[lcChannelName]; else return nullptr; } // Returns the list of members for a channel in the unjoinedChannels list. // 0 if channel is not in the unjoinedChannels list. // Using code must not alter the list. const ChannelNickMap *Server::getUnjoinedChannelMembers(const QString& channelName) const { QString lcChannelName = channelName.toLower(); if (m_unjoinedChannels.contains(lcChannelName)) return m_unjoinedChannels[lcChannelName]; else return nullptr; } // Searches the Joined and Unjoined lists for the given channel and returns the member list. // 0 if channel is not in either list. // Using code must not alter the list. const ChannelNickMap *Server::getChannelMembers(const QString& channelName) const { const ChannelNickMap *members = getJoinedChannelMembers(channelName); if (members) return members; else return getUnjoinedChannelMembers(channelName); } // Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a given channel and nickname. // 0 if not found. ChannelNickPtr Server::getChannelNick(const QString& channelName, const QString& nickname) { QString lcNickname = nickname.toLower(); const ChannelNickMap *channelNickMap = getChannelMembers(channelName); if (channelNickMap) { if (channelNickMap->contains(lcNickname)) return (*channelNickMap)[lcNickname]; else return ChannelNickPtr(); //! TODO FIXME null null null } else { return ChannelNickPtr(); //! TODO FIXME null null null } } // Updates a nickname in a channel. If not on the joined or unjoined lists, and nick // is in the watch list, adds the channel and nick to the unjoinedChannels list. // If mode != 99, sets the mode for the nick in the channel. // Returns the NickInfo object if nick is on any lists, otherwise 0. ChannelNickPtr Server::setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode) { QString lcNickname = nickname.toLower(); // If already on a list, update mode. ChannelNickPtr channelNick = getChannelNick(channelName, lcNickname); if (!channelNick) { // If the nick is on the watch list, add channel and nick to unjoinedChannels list. if (getWatchList().contains(lcNickname, Qt::CaseInsensitive)) { channelNick = addNickToUnjoinedChannelsList(channelName, nickname); channelNick->setMode(mode); } else return ChannelNickPtr(); //! TODO FIXME null null null } if (mode != 99) channelNick->setMode(mode); return channelNick; } // Returns a list of all the joined channels that a nick is in. QStringList Server::getNickJoinedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } // Returns a list of all the channels (joined or unjoined) that a nick is in. QStringList Server::getNickChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } for( channel = m_unjoinedChannels.constBegin(); channel != m_unjoinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } QStringList Server::getSharedChannels(const QString& nickname) { QString lcNickname = nickname.toLower(); QStringList channellist; ChannelMembershipMap::ConstIterator channel; for( channel = m_joinedChannels.constBegin(); channel != m_joinedChannels.constEnd(); ++channel ) { if (channel.value()->contains(lcNickname)) channellist.append(channel.key()); } return channellist; } bool Server::isNickOnline(const QString &nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); return (nickInfo != nullptr); } QString Server::getOwnIpByNetworkInterface() { return m_socket->localAddress().toString(); } QString Server::getOwnIpByServerMessage() { if(!m_ownIpByWelcome.isEmpty()) return m_ownIpByWelcome; else if(!m_ownIpByUserhost.isEmpty()) return m_ownIpByUserhost; else return QString(); } void Server::addQuery(const QString &nickname) { addQuery(obtainNickInfo(nickname), true); } Query* Server::addQuery(const NickInfoPtr & nickInfo, bool weinitiated) { QString nickname = nickInfo->getNickname(); // Only create new query object if there isn't already one with the same name Query* query = getQueryByName(nickname); if (!query) { QString lcNickname = nickname.toLower(); query = getViewContainer()->addQuery(this, nickInfo, weinitiated); query->indicateAway(m_away); connect(query, SIGNAL(sendFile(QString)),this, SLOT(requestDccSend(QString))); connect(this, SIGNAL(serverOnline(bool)), query, SLOT(serverOnline(bool))); // Append query to internal list m_queryList.append(query); m_queryNicks.insert(lcNickname, nickInfo); if (!weinitiated) Application::instance()->notificationHandler()->query(query, nickname); } else if (weinitiated) { emit showView(query); } // try to get hostmask if there's none yet if (query->getNickInfo()->getHostmask().isEmpty()) requestUserhost(nickname); Q_ASSERT(query); return query; } void Server::closeQuery(const QString &name) { Query* query = getQueryByName(name); removeQuery(query); // Update NickInfo. If no longer on any lists, delete it altogether, but // only if not on the watch list. ISON replies will determine whether the NickInfo // is deleted altogether in that case. QString lcNickname = name.toLower(); m_queryNicks.remove(lcNickname); if (!isWatchedNick(name)) deleteNickIfUnlisted(name); } void Server::closeChannel(const QString& name) { qDebug() << "Server::closeChannel(" << name << ")"; Channel* channelToClose = getChannelByName(name); if (channelToClose && channelToClose->joined()) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("PART"), name); queue(result.toServer); } } void Server::requestChannelList() { m_inputFilter.setAutomaticRequest(QStringLiteral("LIST"), QString(), true); queue(QStringLiteral("LIST")); } void Server::requestWhois(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); } void Server::requestWho(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHO"), channel, true); QString command(QStringLiteral("WHO ") + channel); if (hasWHOX() && hasExtendedJoin()) { // Request the account as well as the usual info. // See http://faerion.sourceforge.net/doc/irc/whox.var // for more info. command += QStringLiteral(" nuhsa%cuhsnfdra"); } queue(command, LowPriority); } void Server::requestUserhost(const QString& nicks) { const QStringList nicksList = nicks.split(QLatin1Char(' '), QString::SkipEmptyParts); for(QStringList::ConstIterator it=nicksList.constBegin() ; it!=nicksList.constEnd() ; ++it) m_inputFilter.setAutomaticRequest(QStringLiteral("USERHOST"), *it, true); queue(QStringLiteral("USERHOST ")+nicks, LowPriority); } void Server::requestTopic(const QString& channel) { m_inputFilter.setAutomaticRequest(QStringLiteral("TOPIC"), channel, true); queue(QStringLiteral("TOPIC ")+channel, LowPriority); } void Server::resolveUserhost(const QString& nickname) { m_inputFilter.setAutomaticRequest(QStringLiteral("WHOIS"), nickname, true); m_inputFilter.setAutomaticRequest(QStringLiteral("DNS"), nickname, true); queue(QStringLiteral("WHOIS ")+nickname, LowPriority); //FIXME when is this really used? } void Server::requestBan(const QStringList& users,const QString& channel,const QString& a_option) { QString hostmask; QString option=a_option.toLower(); Channel* targetChannel=getChannelByName(channel); for(int index=0;indexgetNickByName(mask); // if we found the nick try to find their hostmask if(targetNick) { QString hostmask=targetNick->getChannelNick()->getHostmask(); // if we found the hostmask, add it to the ban mask if(!hostmask.isEmpty()) { mask=targetNick->getChannelNick()->getNickname()+QLatin1Char('!')+hostmask; // adapt ban mask to the option given if(option==QStringLiteral("host")) mask=QStringLiteral("*!*@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("domain")) mask=QStringLiteral("*!*@")+hostmask.section(QLatin1Char('@'),1); else if(option==QStringLiteral("userhost")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QStringLiteral("@*.")+hostmask.section(QLatin1Char('.'),1); else if(option==QStringLiteral("userdomain")) mask=QStringLiteral("*!")+hostmask.section(QLatin1Char('@'),0,0)+QLatin1Char('@')+hostmask.section(QLatin1Char('@'),1); } } } Konversation::OutputFilterResult result = getOutputFilter()->execBan(mask,channel); queue(result.toServer); } } void Server::requestUnban(const QString& mask,const QString& channel) { Konversation::OutputFilterResult result = getOutputFilter()->execUnban(mask,channel); queue(result.toServer); } void Server::requestDccSend() { requestDccSend(QString()); } void Server::sendURIs(const QList& uris, const QString& nick) { foreach(const QUrl &uri, uris) addDccSend(nick, uri); } void Server::requestDccSend(const QString &a_recipient) { QString recipient(a_recipient); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if(!recipient.isEmpty()) { QPointer dlg = new DccFileDialog (getViewContainer()->getWindow()); //DccFileDialog fileDialog(KUrl(), QString(), getViewContainer()->getWindow()); QList fileURLs = dlg->getOpenUrls( QUrl(), QString(), i18n("Select File(s) to Send to %1", recipient) ); QList::const_iterator it; for ( it = fileURLs.constBegin() ; it != fileURLs.constEnd() ; ++it ) { addDccSend( recipient, *it, dlg->passiveSend()); } delete dlg; } } void Server::slotNewDccTransferItemQueued(DCC::Transfer* transfer) { if (transfer->getConnectionId() == connectionId() ) { qDebug() << "connecting slots for " << transfer->getFileName() << " [" << transfer->getType() << "]"; if ( transfer->getType() == DCC::Transfer::Receive ) { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccGetDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } else { connect( transfer, SIGNAL(done(Konversation::DCC::Transfer*)), this, SLOT(dccSendDone(Konversation::DCC::Transfer*)) ); connect( transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(dccStatusChanged(Konversation::DCC::Transfer*,int,int)) ); } } } void Server::addDccSend(const QString &recipient, QUrl fileURL, bool passive, const QString &altFileName, quint64 fileSize) { if (!fileURL.isValid()) { return; } // We already checked that the file exists in output filter / requestDccSend() resp. DCC::TransferSend* newDcc = Application::instance()->getDccTransferManager()->newUpload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(recipient); newDcc->setFileURL(fileURL); newDcc->setReverse(passive); if (!altFileName.isEmpty()) newDcc->setFileName(altFileName); if (fileSize != 0) newDcc->setFileSize(fileSize); emit addDccPanel(); if (newDcc->queue()) newDcc->start(); } QString Server::recoverDccFileName(const QStringList & dccArguments, int offset) const { QString fileName; if(dccArguments.count() > offset + 1) { qDebug() << "recover filename"; const int argumentOffsetSize = dccArguments.size() - offset; for (int i = 0; i < argumentOffsetSize; ++i) { fileName += dccArguments.at(i); //if not last element, append a space if (i < (argumentOffsetSize - 1)) { fileName += QLatin1Char(' '); } } } else { fileName = dccArguments.at(0); } return cleanDccFileName(fileName); } QString Server::cleanDccFileName(const QString& filename) const { QString cleanFileName = filename; //we want a clean filename to get rid of the mass """filename""" //NOTE: if a filename starts really with a ", it is escaped -> \" (2 chars) // but most clients don't support that and just replace it with a _ while (cleanFileName.startsWith(QLatin1Char('\"')) && cleanFileName.endsWith(QLatin1Char('\"'))) { cleanFileName = cleanFileName.mid(1, cleanFileName.length() - 2); } return cleanFileName; } quint16 Server::stringToPort(const QString &port, bool *ok) { bool toUintOk = false; uint uPort32 = port.toUInt(&toUintOk); // ports over 65535 are invalid, someone sends us bad data if (!toUintOk || uPort32 > USHRT_MAX) { if (ok) { *ok = false; } } else { if (ok) { *ok = true; } } return static_cast(uPort32); } QString Server::recipientNick() const { QStringList nickList; // fill nickList with all nicks we know about foreach (Channel* lookChannel, m_channelList) { foreach (Nick* lookNick, lookChannel->getNickList()) { if (!nickList.contains(lookNick->getChannelNick()->getNickname())) nickList.append(lookNick->getChannelNick()->getNickname()); } } // add Queries as well, but don't insert duplicates foreach (Query* lookQuery, m_queryList) { if(!nickList.contains(lookQuery->getName())) nickList.append(lookQuery->getName()); } QStringListModel model; model.setStringList(nickList); return DCC::RecipientDialog::getNickname(getViewContainer()->getWindow(), &model); } void Server::addDccGet(const QString &sourceNick, const QStringList &dccArguments) { //filename ip port filesize [token] QString ip; quint16 port; QString fileName; quint64 fileSize; QString token; const int argumentSize = dccArguments.count(); bool ok = true; if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //port==0, for passive send, ip can't be 0 { //filename ip port(0) filesize token fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //-1 index, -1 token, -1 port, -1 filesize port = 0; fileSize = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token token = dccArguments.at(argumentSize - 1); //-1 index } else { //filename ip port filesize ip = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 3) ); //-1 index, -1 filesize fileName = recoverDccFileName(dccArguments, 3); //ip port filesize fileSize = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index port = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC SEND request from %1.", sourceNick)); return; } DCC::TransferRecv* newDcc = Application::instance()->getDccTransferManager()->newDownload(); newDcc->setConnectionId(connectionId()); newDcc->setPartnerNick(sourceNick); newDcc->setPartnerIp(ip); newDcc->setPartnerPort(port); newDcc->setFileName(fileName); newDcc->setFileSize(fileSize); // Reverse DCC if (!token.isEmpty()) { newDcc->setReverse(true, token); } qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; //emit after data was set emit addDccPanel(); if ( newDcc->queue() ) { appendMessageToFrontmost( i18n( "DCC" ), i18n( "%1 offers to send you \"%2\" (%3)...", newDcc->getPartnerNick(), fileName, ( newDcc->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( newDcc->getFileSize() ) ) ); if (Preferences::self()->dccAutoGet()) newDcc->start(); } } void Server::addDccChat(const QString& sourceNick, const QStringList& dccArguments) { //chat ip port [token] QString ip; quint16 port = 0; QString token; bool reverse = false; const int argumentSize = dccArguments.count(); bool ok = true; QString extension; extension = dccArguments.at(0); ip = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); if (argumentSize == 3) { //extension ip port port = stringToPort(dccArguments.at(2), &ok); } else if (argumentSize == 4) { //extension ip port(0) token token = dccArguments.at(3); reverse = true; } if (!ok) { appendMessageToFrontmost(i18n("Error"), i18nc("%1=nickname","Received invalid DCC CHAT request from %1.", sourceNick)); return; } DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(sourceNick); newChat->setOwnNick(getNickname()); qDebug() << "ip: " << ip; qDebug() << "port: " << port; qDebug() << "token: " << token; qDebug() << "extension: " << extension; newChat->setPartnerIp(ip); newChat->setPartnerPort(port); newChat->setReverse(reverse, token); newChat->setSelfOpened(false); newChat->setExtension(extension); emit addDccChat(newChat); newChat->start(); } void Server::openDccChat(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::openDccWBoard(const QString& nickname) { qDebug(); QString recipient(nickname); // if we don't have a recipient yet, let the user select one if (recipient.isEmpty()) { recipient = recipientNick(); } // do we have a recipient *now*? if (!recipient.isEmpty()) { DCC::Chat* newChat = Application::instance()->getDccTransferManager()->newChat(); newChat->setConnectionId(connectionId()); newChat->setPartnerNick(recipient); newChat->setOwnNick(getNickname()); // Set extension before emiting addDccChat newChat->setExtension(DCC::Chat::Whiteboard); newChat->setSelfOpened(true); emit addDccChat(newChat); newChat->start(); } } void Server::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) { Konversation::OutputFilterResult result = getOutputFilter()->requestDccChat(partnerNick, extension, numericalOwnIp,ownPort); queue(result.toServer); } void Server::acceptDccGet(const QString& nick, const QString& file) { Application::instance()->getDccTransferManager()->acceptDccGet(m_connectionId, nick, file); } void Server::dccSendRequest(const QString &partner, const QString &fileName, const QString &address, quint16 port, quint64 size) { Konversation::OutputFilterResult result = getOutputFilter()->sendRequest(partner,fileName,address,port,size); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept upload of \"%2\" (%3)...", partner, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveSendRequest(recipient,fileName,address,size,token); queue(result.toServer); appendMessageToFrontmost( i18n( "DCC" ), i18n( "Asking %1 to accept passive upload of \"%2\" (%3)...", recipient, cleanDccFileName(fileName), ( size == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( size ) ) ); } void Server::dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) { Konversation::OutputFilterResult result = getOutputFilter()->passiveChatRequest(recipient, extension, address, token); queue(result.toServer); appendMessageToFrontmost(i18n("DCC"), i18nc("%1=name, %2=dcc extension, chat or wboard for example","Asking %1 to accept %2...", recipient, extension)); } void Server::dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token) { Konversation::OutputFilterResult result = getOutputFilter()->resumePassiveRequest(sender,fileName,port,startAt,token);; queue(result.toServer); } void Server::dccResumeGetRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt) { Konversation::OutputFilterResult result = getOutputFilter()->resumeRequest(sender,fileName,port,startAt);; queue(result.toServer); } void Server::dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveSendRequest(partnerNick,fileName,ownAddress,ownPort,size,reverseToken); queue(result.toServer); } void Server::dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken) { Konversation::OutputFilterResult result = getOutputFilter()->acceptPassiveChatRequest(partnerNick, extension, ownAddress, ownPort, reverseToken); queue(result.toServer); } void Server::dccRejectSend(const QString& partnerNick, const QString& fileName) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccSend(partnerNick,fileName); queue(result.toServer); } void Server::dccRejectChat(const QString& partnerNick, const QString& extension) { Konversation::OutputFilterResult result = getOutputFilter()->rejectDccChat(partnerNick, extension); queue(result.toServer); } void Server::startReverseDccChat(const QString &sourceNick, const QStringList &dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; QString partnerIP = DCC::DccCommon::numericalIpToTextIp(dccArguments.at(1)); quint16 port = stringToPort(dccArguments.at(2), &ok); QString token = dccArguments.at(3); qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "token: " << token; if (!ok || dtm->startReverseChat(connectionId(), sourceNick, partnerIP, port, token) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid passive DCC chat acceptance message from %1.", sourceNick)); } } void Server::startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments) { qDebug(); DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool ok = true; const int argumentSize = dccArguments.size(); QString partnerIP = DCC::DccCommon::numericalIpToTextIp( dccArguments.at(argumentSize - 4) ); //dccArguments[1] ) ); quint16 port = stringToPort(dccArguments.at(argumentSize - 3), &ok); QString token = dccArguments.at(argumentSize - 1); quint64 fileSize = dccArguments.at(argumentSize - 2).toULongLong(); QString fileName = recoverDccFileName(dccArguments, 4); //ip port filesize token qDebug() << "ip: " << partnerIP; qDebug() << "port: " << port; qDebug() << "filename: " << fileName; qDebug() << "filesize: " << fileSize; qDebug() << "token: " << token; if (!ok || dtm->startReverseSending(connectionId(), sourceNick, fileName, // filename partnerIP, // partner IP port, // partner port fileSize, // filesize token // Reverse DCC token ) == nullptr) { // DTM could not find a matched item appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid passive DCC send acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccGetTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename port position [token] QString fileName; quint64 position; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); if (dccArguments.at(argumentSize - 3) == QStringLiteral("0")) //-1 index, -1 token, -1 pos { fileName = recoverDccFileName(dccArguments, 3); //port position token ownPort = 0; position = dccArguments.at(argumentSize - 2).toULongLong(); //-1 index, -1 token } else { fileName = recoverDccFileName(dccArguments, 2); //port position ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 pos position = dccArguments.at(argumentSize - 1).toULongLong(); //-1 index } //do we need the token here? DCC::TransferRecv* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeDownload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender, %3 = percentage of file size, %4 = file size", "Resuming download of \"%1\" from %2 starting at %3% of %4...", fileName, sourceNick, QString::number( dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume acceptance message for \"%1\" from %2.", fileName, sourceNick)); } } void Server::resumeDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); bool passiv = false; QString fileName; quint64 position; QString token; quint16 ownPort; bool ok = true; const int argumentSize = dccArguments.count(); //filename port filepos [token] if (dccArguments.at( argumentSize - 3) == QStringLiteral("0")) { //filename port filepos token passiv = true; ownPort = 0; token = dccArguments.at( argumentSize - 1); // -1 index position = dccArguments.at( argumentSize - 2).toULongLong(); // -1 index, -1 token fileName = recoverDccFileName(dccArguments, 3); //port filepos token } else { //filename port filepos ownPort = stringToPort(dccArguments.at(argumentSize - 2), &ok); //-1 index, -1 filesize position = dccArguments.at( argumentSize - 1).toULongLong(); // -1 index fileName = recoverDccFileName(dccArguments, 2); //port filepos } DCC::TransferSend* dccTransfer = nullptr; if (ok) { dccTransfer = dtm->resumeUpload(connectionId(), sourceNick, fileName, ownPort, position); } if (dccTransfer) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient, %3 = percentage of file size, %4 = file size", "Resuming upload of \"%1\" to %2 starting at %3% of %4...", fileName, sourceNick, QString::number(dccTransfer->getProgress()), (dccTransfer->getFileSize() == 0) ? i18n("unknown size") : KIO::convertSize(dccTransfer->getFileSize()))); // fileName can't have " here if (fileName.contains(QLatin1Char(' '))) fileName = QLatin1Char('\"')+fileName+QLatin1Char('\"'); // FIXME: this operation should be done by TransferManager Konversation::OutputFilterResult result; if (passiv) result = getOutputFilter()->acceptPassiveResumeRequest( sourceNick, fileName, ownPort, position, token ); else result = getOutputFilter()->acceptResumeRequest( sourceNick, fileName, ownPort, position ); queue( result.toServer ); } else { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid resume request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccSendTransfer(const QString &sourceNick, const QStringList &dccArguments) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); //filename QString fileName = recoverDccFileName(dccArguments, 0); DCC::TransferSend* dccTransfer = dtm->rejectSend(connectionId(), sourceNick, fileName); if (!dccTransfer) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = file name, %2 = nickname", "Received invalid reject request for \"%1\" from %2.", fileName, sourceNick)); } } void Server::rejectDccChat(const QString& sourceNick) { DCC::TransferManager* dtm = Application::instance()->getDccTransferManager(); DCC::Chat* dccChat = dtm->rejectChat(connectionId(), sourceNick); if (!dccChat) { appendMessageToFrontmost(i18n("Error"), i18nc("%1 = nickname", "Received invalid reject request from %1.", sourceNick)); } } void Server::dccGetDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 finished.", item->getFileName(), item->getPartnerNick())); } else if(item->getStatus() == DCC::Transfer::Failed) { appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of sender", "Download of \"%1\" from %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } } void Server::dccSendDone(DCC::Transfer* item) { if (!item) return; if(item->getStatus() == DCC::Transfer::Done) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 finished.", item->getFileName(), item->getPartnerNick())); else if(item->getStatus() == DCC::Transfer::Failed) appendMessageToFrontmost(i18n("DCC"), i18nc("%1 = file name, %2 = nickname of recipient", "Upload of \"%1\" to %2 failed. Reason: %3.", item->getFileName(), item->getPartnerNick(), item->getStatusDetail())); } void Server::dccStatusChanged(DCC::Transfer *item, int newStatus, int oldStatus) { if(!item) return; if ( item->getType() == DCC::Transfer::Send ) { // when resuming, a message about the receiver's acceptance has been shown already, so suppress this message if ( newStatus == DCC::Transfer::Transferring && oldStatus == DCC::Transfer::WaitingRemote && !item->isResumed() ) appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 nickname of recipient", "Sending \"%1\" to %2...", item->getFileName(), item->getPartnerNick() ) ); } else // type == Receive { if ( newStatus == DCC::Transfer::Transferring && !item->isResumed() ) { appendMessageToFrontmost( i18n( "DCC" ), i18nc( "%1 = file name, %2 = file size, %3 = nickname of sender", "Downloading \"%1\" (%2) from %3...", item->getFileName(), ( item->getFileSize() == 0 ) ? i18n( "unknown size" ) : KIO::convertSize( item->getFileSize() ), item->getPartnerNick() ) ); } } } void Server::removeQuery(Query* query) { m_queryList.removeOne(query); query->deleteLater(); } void Server::sendJoinCommand(const QString& name, const QString& password) { Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(), Preferences::self()->commandChar() + QStringLiteral("JOIN ") + name + QLatin1Char(' ') + password, QString()); queue(result.toServer); } Channel* Server::joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags) { // (re-)join channel, open a new panel if needed Channel* channel = getChannelByName(name); if (!channel) { channel=getViewContainer()->addChannel(this,name); Q_ASSERT(channel); channel->setNickname(getNickname()); channel->indicateAway(m_away); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(name); channel->setNotificationsEnabled(channelSettings.enableNotifications()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.append(channel); m_loweredChannelNameHash.insert(channel->getName().toLower(), channel); connect(channel,SIGNAL (sendFile()),this,SLOT (requestDccSend()) ); connect(this, SIGNAL(nicknameChanged(QString)), channel, SLOT(setNickname(QString))); } // Move channel from unjoined (if present) to joined list and add our own nickname to the joined list. ChannelNickPtr channelNick = addNickToJoinedChannelsList(name, getNickname()); if ((channelNick->getHostmask() != hostmask ) && !hostmask.isEmpty()) { NickInfoPtr nickInfo = channelNick->getNickInfo(); nickInfo->setHostmask(hostmask); } channel->joinNickname(channelNick, messageTags); return channel; } void Server::removeChannel(Channel* channel) { // Update NickInfo. removeJoinedChannel(channel->getName()); if (getServerGroup()) { Konversation::ChannelSettings channelSettings = getServerGroup()->channelByNameFromHistory(channel->getName()); channelSettings.setNotificationsEnabled(channel->notificationsEnabled()); getServerGroup()->appendChannelHistory(channelSettings); } m_channelList.removeOne(channel); m_loweredChannelNameHash.remove(channel->getName().toLower()); if (!isConnected()) updateAutoJoin(); } void Server::updateChannelMode(const QString &updater, const QString &channelName, char mode, bool plus, const QString ¶meter, const QHash &messageTags) { Channel* channel=getChannelByName(channelName); if(channel) //Let the channel be verbose to the screen about the change, and update channelNick channel->updateMode(updater, mode, plus, parameter, messageTags); // TODO: What is mode character for owner? // Answer from JOHNFLUX - I think that admin is the same as owner. Channel.h has owner as "a" // "q" is the likely answer.. UnrealIRCd and euIRCd use it. // TODO these need to become dynamic QString userModes=QStringLiteral("vhoqa"); // voice halfop op owner admin int modePos = userModes.indexOf(QLatin1Char(mode)); if (modePos > 0) { ChannelNickPtr updateeNick = getChannelNick(channelName, parameter); if(!updateeNick) { /* if(parameter.isEmpty()) { qDebug() << "in updateChannelMode, a nick with no-name has had their mode '" << mode << "' changed to (" <setMode(mode, plus); // Note that channel will be moved to joined list if necessary. addNickToJoinedChannelsList(channelName, parameter); } // Update channel ban list. if (mode == 'b') { if (plus) { addBan(channelName, QString(QStringLiteral("%1 %2 %3")).arg(parameter).arg(updater).arg(QDateTime::currentDateTime().toTime_t())); } else { removeBan(channelName, parameter); } } } void Server::updateChannelModeWidgets(const QString &channelName, char mode, const QString ¶meter) { Channel* channel=getChannelByName(channelName); if(channel) channel->updateModeWidgets(mode,true,parameter); } Channel* Server::getChannelByName(const QString& name) { if (name.isEmpty()) { return nullptr; } // Convert wanted channel name to lowercase QString wanted = name.toLower(); if (m_serverNickPrefixes.contains(wanted.at(0))) { wanted.remove(0, 1); } if (name.isEmpty()) { return nullptr; } if (m_loweredChannelNameHash.contains(wanted)) return m_loweredChannelNameHash.value(wanted); return nullptr; } Query* Server::getQueryByName(const QString& name) { // Convert wanted query name to lowercase QString wanted = name.toLower(); // Traverse through list to find the query with "name" foreach (Query* lookQuery, m_queryList) { if(lookQuery->getName().toLower()==wanted) return lookQuery; } // No query by that name found? Must be a new query request. Return 0 return nullptr; } ChatWindow* Server::getChannelOrQueryByName(const QString& name) { ChatWindow* window = getChannelByName(name); if (!window) window = getQueryByName(name); return window; } void Server::queueNicks(const QString& channelName, const QStringList& nicknameList) { Channel* channel = getChannelByName(channelName); if (channel) channel->queueNicks(nicknameList); } // Adds a nickname to the joinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the unjoined list to the joined list. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToJoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelJoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); m_userModel->add(nickInfo); // WIPQTQUICK doWatchedNickChangedSignal = isWatchedNick(nickname); } // if nickinfo already exists update nickname, in case we created the nickinfo based // on e.g. an incorrectly capitalized ISON request else nickInfo->setNickname(nickname); // Move the channel from unjoined list (if present) to joined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else { // Create a new list in the joined channels if not already present. if (!m_joinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_joinedChannels.insert(lcChannelName, channel); doChannelJoinedSignal = true; } else channel = m_joinedChannels[lcChannelName]; } // Add NickInfo to channel list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); Q_ASSERT(channelNick); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; } channelNick = (*channel)[lcNickname]; Q_ASSERT(channelNick); //Since we just added it if it didn't exist, it should be guaranteed to exist now if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelJoinedSignal) emit channelJoinedOrUnjoined(this, channelName, true); if (doChannelMembersChangedSignal) { m_userModel->changed(nickInfo); // WIPQTQUICK emit channelMembersChanged(this, channelName, true, false, nickname); } return channelNick; } // Adds a nickname to the unjoinedChannels list. // Creates new NickInfo if necessary. // If needed, moves the channel from the joined list to the unjoined list. // If mode != 99 sets the mode for this nick in this channel. // Returns the NickInfo for the nickname. ChannelNickPtr Server::addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname) { bool doChannelUnjoinedSignal = false; bool doWatchedNickChangedSignal = false; bool doChannelMembersChangedSignal = false; QString lcNickname(nickname.toLower()); // Create NickInfo if not already created. NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); m_userModel->add(nickInfo); // WIPQTQUICK doWatchedNickChangedSignal = isWatchedNick(nickname); } // Move the channel from joined list (if present) to unjoined list. QString lcChannelName = channelName.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else { // Create a new list in the unjoined channels if not already present. if (!m_unjoinedChannels.contains(lcChannelName)) { channel = new ChannelNickMap; m_unjoinedChannels.insert(lcChannelName, channel); doChannelUnjoinedSignal = true; } else channel = m_unjoinedChannels[lcChannelName]; } // Add NickInfo to unjoinedChannels list if not already in the list. ChannelNickPtr channelNick; if (!channel->contains(lcNickname)) { channelNick = new ChannelNick(nickInfo, lcChannelName); channel->insert(lcNickname, channelNick); doChannelMembersChangedSignal = true; m_userModel->changed(nickInfo); // WIPQTQUICK } channelNick = (*channel)[lcNickname]; // Set the mode for the nick in this channel. if (doWatchedNickChangedSignal) emit watchedNickChanged(this, nickname, true); if (doChannelUnjoinedSignal) emit channelJoinedOrUnjoined(this, channelName, false); if (doChannelMembersChangedSignal) emit channelMembersChanged(this, channelName, false, false, nickname); return channelNick; } /** * If not already online, changes a nick to the online state by creating * a NickInfo for it and emits various signals and messages for it. * This method should only be called for nicks on the watch list. * @param nickname The nickname that is online. * @return Pointer to NickInfo for nick. */ NickInfoPtr Server::setWatchedNickOnline(const QString& nickname) { NickInfoPtr nickInfo = getNickInfo(nickname); if (!nickInfo) { QString lcNickname(nickname.toLower()); nickInfo = new NickInfo(nickname, this); m_allNicks.insert(lcNickname, nickInfo); m_userModel->add(nickInfo); // WIPQTQUICK } emit watchedNickChanged(this, nickname, true); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 is online (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOnline(getStatusView(), nickname); nickInfo->setPrintedOnline(true); return nickInfo; } void Server::setWatchedNickOffline(const QString& nickname, const NickInfoPtr nickInfo) { Q_UNUSED(nickInfo) emit watchedNickChanged(this, nickname, false); appendMessageToFrontmost(i18nc("Message type", "Notify"), i18n("%1 went offline (%2).", nickname, getServerName()), QHash(), getStatusView()); Application::instance()->notificationHandler()->nickOffline(getStatusView(), nickname); } bool Server::setNickOffline(const QString& nickname) { QString lcNickname(nickname.toLower()); NickInfoPtr nickInfo = getNickInfo(lcNickname); bool wasOnline = nickInfo ? nickInfo->getPrintedOnline() : false; if (wasOnline) { // Delete from query list, if present. if (m_queryNicks.contains(lcNickname)) m_queryNicks.remove(lcNickname); // Delete the nickname from all channels (joined or unjoined). QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { QString channel = (*it); removeChannelNick(channel, lcNickname); } // Delete NickInfo. if (m_allNicks.contains(lcNickname)) { m_allNicks.remove(lcNickname); m_userModel->remove(nickInfo); // WIPQTQUICK } // If the nick was in the watch list, emit various signals and messages. if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, nickInfo); nickInfo->setPrintedOnline(false); } return (nickInfo != nullptr); } /** * If nickname is no longer on any channel list, or the query list, delete it altogether. * Call this routine only if the nick is not on the notify list or is on the notify * list but is known to be offline. * @param nickname The nickname to be deleted. Case insensitive. * @return True if the nickname is deleted. */ bool Server::deleteNickIfUnlisted(const QString &nickname) { QString lcNickname(nickname.toLower()); // Don't delete our own nickinfo. if (lcNickname == loweredNickname()) return false; if (!m_queryNicks.contains(lcNickname)) { QStringList nickChannels = getNickChannels(nickname); if (nickChannels.isEmpty()) { m_userModel->remove(m_allNicks[lcNickname]); // WIPQTQUICK m_allNicks.remove(lcNickname); return true; } } return false; } /** * Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void Server::removeChannelNick(const QString& channelName, const QString& nickname) { bool doSignal = false; bool joined = false; QString lcChannelName = channelName.toLower(); QString lcNickname = nickname.toLower(); ChannelNickMap *channel; if (m_joinedChannels.contains(lcChannelName)) { channel = m_joinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = true; // Note: Channel should not be empty because user's own nick should still be // in it, so do not need to delete empty channel here. } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from joined channel=" << channelName; } } else { if (m_unjoinedChannels.contains(lcChannelName)) { channel = m_unjoinedChannels[lcChannelName]; if (channel->contains(lcNickname)) { channel->remove(lcNickname); doSignal = true; joined = false; // If channel is now empty, delete it. // Caution: Any iterators across unjoinedChannels will be come invalid here. if (channel->isEmpty()) m_unjoinedChannels.remove(lcChannelName); } else { qDebug() << "Error: Tried to remove nickname=" << nickname << " from unjoined channel=" << channelName; } } } if (doSignal) { m_userModel->changed(m_allNicks[lcNickname]); // WIPQTQUICK emit channelMembersChanged(this, channelName, joined, true, nickname); } } QStringList Server::getWatchList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); // unreachable code, commented out /* if (m_serverISON) return m_serverISON->getWatchList(); else return QStringList(); */ } QStringList Server::getISONList() { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::notifyListByGroupId(getServerGroup()->id()); else return QStringList(); // unreachable code, commented out /* if (m_serverISON) return m_serverISON->getISONList(); else return QStringList(); */ } QString Server::getISONListString() { return getISONList().join(QStringLiteral(" ")); } /** * Return true if the given nickname is on the watch list. */ bool Server::isWatchedNick(const QString& nickname) { // no nickinfo ISON for the time being if (getServerGroup()) return Preferences::isNotify(getServerGroup()->id(), nickname); else return false; // unreachable code, commented out //return getWatchList().contains(nickname, Qt::CaseInsensitive); } /** * Remove channel from the joined list, placing it in the unjoined list. * All the unwatched nicks are removed from the channel. If the channel becomes * empty, it is deleted. * @param channelName Name of the channel. Case sensitive. */ void Server::removeJoinedChannel(const QString& channelName) { bool doSignal = false; QStringList watchListLower = getWatchList(); QString lcChannelName = channelName.toLower(); // Move the channel nick list from the joined to unjoined lists. if (m_joinedChannels.contains(lcChannelName)) { doSignal = true; ChannelNickMap* channel = m_joinedChannels[lcChannelName]; m_joinedChannels.remove(lcChannelName); m_unjoinedChannels.insert(lcChannelName, channel); // Remove nicks not on the watch list. bool allDeleted = true; Q_ASSERT(channel); if(!channel) return; //already removed.. hmm ChannelNickMap::Iterator member; for ( member = channel->begin(); member != channel->end() ;) { QString lcNickname = member.key(); if (!watchListLower.contains(lcNickname)) { // Remove the unwatched nickname from the unjoined channel. channel->erase(member); // If the nick is no longer listed in any channels or query list, delete it altogether. deleteNickIfUnlisted(lcNickname); member = channel->begin(); } else { allDeleted = false; ++member; } } // If all were deleted, remove the channel from the unjoined list. if (allDeleted) { channel = m_unjoinedChannels[lcChannelName]; m_unjoinedChannels.remove(lcChannelName); delete channel; // recover memory! } } if (doSignal) emit channelJoinedOrUnjoined(this, channelName, false); } // Renames a nickname in all NickInfo lists. // Returns pointer to the NickInfo object or 0 if nick not found. void Server::renameNickInfo(NickInfoPtr nickInfo, const QString& newname) { if (nickInfo) { // Get existing lowercase nickname and rename nickname in the NickInfo object. QString lcNickname(nickInfo->loweredNickname()); nickInfo->setNickname(newname); QString lcNewname(newname.toLower()); // Rename the key in m_allNicks list. m_allNicks.remove(lcNickname); m_userModel->changed(nickInfo); // WIPQTQUICK Necessary? Probably not ... m_allNicks.insert(lcNewname, nickInfo); // Rename key in the joined and unjoined lists. QStringList nickChannels = getNickChannels(lcNickname); QStringList::iterator itEnd = nickChannels.end(); for(QStringList::iterator it = nickChannels.begin(); it != itEnd; ++it) { const ChannelNickMap *channel = getChannelMembers(*it); Q_ASSERT(channel); ChannelNickPtr member = (*channel)[lcNickname]; Q_ASSERT(member); const_cast(channel)->remove(lcNickname); const_cast(channel)->insert(lcNewname, member); } // Rename key in Query list. if (m_queryNicks.contains(lcNickname)) { m_queryNicks.remove(lcNickname); m_queryNicks.insert(lcNewname, nickInfo); } } else { qDebug() << "was called for newname='" << newname << "' but nickInfo is null"; } } Channel* Server::nickJoinsChannel(const QString &channelName, const QString &nickname, const QString &hostmask, const QString &account, const QString &realName, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { // Update NickInfo. ChannelNickPtr channelNick = addNickToJoinedChannelsList(channelName, nickname); NickInfoPtr nickInfo = channelNick->getNickInfo(); if ((nickInfo->getHostmask() != hostmask) && !hostmask.isEmpty()) { nickInfo->setHostmask(hostmask); } if (!account.isEmpty()) { nickInfo->setAccount(account); } if (!realName.isEmpty()) { nickInfo->setRealName(realName); } outChannel->joinNickname(channelNick, messageTags); } return outChannel; } void Server::addHostmaskToNick(const QString& sourceNick, const QString& sourceHostmask) { // Update NickInfo. NickInfoPtr nickInfo = getNickInfo(sourceNick); if (nickInfo) { if ((nickInfo->getHostmask() != sourceHostmask) && !sourceHostmask.isEmpty()) { nickInfo->setHostmask(sourceHostmask); } } } Channel* Server::removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->removeNick(channelNick,reason,quit, messageTags); } } // Remove the nick from the channel. removeChannelNick(channelName, nickname); // If not listed in any channel, and not on query list, delete the NickInfo, // but only if not on the notify list. ISON replies will take care of deleting // the NickInfo, if on the notify list. if (!isWatchedNick(nickname)) { QString nicky = nickname; deleteNickIfUnlisted(nicky); } return outChannel; } void Server::nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags) { Channel* outChannel = getChannelByName(channelName); if(outChannel) { outChannel->flushNickQueue(); ChannelNickPtr channelNick = getChannelNick(channelName, nickname); if(channelNick) { outChannel->kickNick(channelNick, kicker, reason, messageTags); // Tell Nickinfo removeChannelNick(channelName,nickname); } } } void Server::removeNickFromServer(const QString &nickname,const QString &reason, const QHash &messageTags) { foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // Check if nick is in this channel or not. if(channel->getNickByName(nickname)) removeNickFromChannel(channel->getName(), nickname, reason, messageTags, true); } Query* query = getQueryByName(nickname); if (query) query->quitNick(reason, messageTags); // Delete the nick from all channels and then delete the nickinfo, // emitting signal if on the watch list. setNickOffline(nickname); } void Server::renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags) { if(nickname.isEmpty() || newNick.isEmpty()) { qDebug() << "called with empty strings! Trying to rename '" << nickname << "' to '" << newNick << "'"; return; } // If this was our own nickchange, tell our server object about it if (nickname == getNickname()) setNickname(newNick); //Actually do the rename. NickInfoPtr nickInfo = getNickInfo(nickname); if(!nickInfo) { qDebug() << "called for nickname '" << nickname << "' to '" << newNick << "' but getNickInfo('" << nickname << "') returned no results."; } else { renameNickInfo(nickInfo, newNick); //The rest of the code below allows the channels to echo to the user to tell them that the nick has changed. // Rename the nick in every channel they are in foreach (Channel* channel, m_channelList) { channel->flushNickQueue(); // All we do is notify that the nick has been renamed.. we haven't actually renamed it yet if (channel->getNickByName(nickname)) channel->nickRenamed(nickname, *nickInfo, messageTags); } //Watched nicknames stuff if (isWatchedNick(nickname)) setWatchedNickOffline(nickname, NickInfoPtr()); } // We had an encrypt conversation with the user that changed his nick, lets copy the key to the new nick and remove the old nick #ifdef HAVE_QCA2 QByteArray userKey = getKeyForRecipient(nickname); if (!userKey.isEmpty()) { setKeyForRecipient(newNick, userKey); m_keyHash.remove(nickname.toLower()); } #endif } void Server::userhost(const QString& nick,const QString& hostmask,bool away,bool /* ircOp */) { addHostmaskToNick(nick, hostmask); // remember my IP for DCC things // myself if (m_ownIpByUserhost.isEmpty() && nick == getNickname()) { QString myhost = hostmask.section(QLatin1Char('@'), 1); // Use async lookup else you will be blocking GUI badly QHostInfo::lookupHost(myhost, this, SLOT(gotOwnResolvedHostByUserhost(QHostInfo))); } NickInfoPtr nickInfo = getNickInfo(nick); if (nickInfo) { if (nickInfo->isAway() != away) { nickInfo->setAway(away); } } } void Server::gotOwnResolvedHostByUserhost(const QHostInfo& res) { if ( res.error() == QHostInfo::NoError && !res.addresses().isEmpty() ) m_ownIpByUserhost = res.addresses().first().toString(); else qDebug() << "Got error: " << res.errorString(); } void Server::appendServerMessageToChannel(const QString& channel,const QString& type,const QString& message, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if (outChannel) outChannel->appendServerMessage(type, message, messageTags); } void Server::appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight, bool parseURL) { Channel* outChannel = getChannelByName(channel); if (outChannel) { outChannel->appendCommandMessage(command, message, messageTags, parseURL, !highlight); } else { appendStatusMessage(command, QString(QStringLiteral("%1 %2")).arg(channel).arg(message), messageTags); } } void Server::appendStatusMessage(const QString& type, const QString& message, const QHash &messageTags) { getStatusView()->appendServerMessage(type, message, messageTags); } void Server::appendMessageToFrontmost(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { getViewContainer()->appendToFrontmost(type, message, getStatusView(), messageTags, parseURL); } void Server::setNickname(const QString &newNickname) { m_nickname = newNickname; m_loweredNickname = newNickname.toLower(); if (!m_nickListModel->stringList().contains(newNickname)) { m_nickListModel->insertRows(m_nickListModel->rowCount(), 1); m_nickListModel->setData(m_nickListModel->index(m_nickListModel->rowCount() -1 , 0), newNickname, Qt::DisplayRole); } emit nicknameChanged(newNickname); } void Server::setChannelTopic(const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(newTopic, messageTags); } } // Overloaded void Server::setChannelTopic(const QString& nickname, const QString &channel, const QString &newTopic, const QHash &messageTags) { Channel* outChannel = getChannelByName(channel); if(outChannel) { // encoding stuff is done in send() outChannel->setTopic(nickname, newTopic, messageTags); } } void Server::setTopicAuthor(const QString& channel, const QString& author, QDateTime time) { Channel* outChannel = getChannelByName(channel); if(outChannel) outChannel->setTopicAuthor(author, time); } void Server::endOfWho(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->scheduleAutoWho(); } void Server::endOfNames(const QString& target) { Channel* channel = getChannelByName(target); if(channel) channel->endOfNames(); } bool Server::isNickname(const QString &compare) const { return (m_nickname == compare); } QString Server::getNickname() const { return m_nickname; } QString Server::loweredNickname() const { return m_loweredNickname; } QString Server::parseWildcards(const QString& toParse, ChatWindow* context, QStringList nicks) { QString inputLineText; if (context && context->getInputBar()) inputLineText = context->getInputBar()->toPlainText(); if (!context) return parseWildcards(toParse, getNickname(), QString(), QString(), QString(), QString()); else if (context->getType() == ChatWindow::Channel) { Channel* channel = static_cast(context); return parseWildcards(toParse, getNickname(), context->getName(), channel->getPassword(), nicks.count() ? nicks : channel->getSelectedNickList(), inputLineText); } else if (context->getType() == ChatWindow::Query) return parseWildcards(toParse, getNickname(), context->getName(), QString(), context->getName(), inputLineText); return parseWildcards(toParse, getNickname(), context->getName(), QString(), QString(), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QString& nick, const QString& inputLineText) { return parseWildcards(toParse, sender, channelName, channelKey, nick.split(QLatin1Char(' '), QString::SkipEmptyParts), inputLineText); } QString Server::parseWildcards(const QString& toParse, const QString& sender, const QString& channelName, const QString& channelKey, const QStringList& nickList, const QString& inputLineText ) { // store the parsed version QString out; // default separator QString separator(QStringLiteral(" ")); int index = 0, found = 0; QChar toExpand; while ((found = toParse.indexOf(QLatin1Char('%'), index)) != -1) { // append part before the % out.append(toParse.mid(index,found-index)); index = found + 1; // skip the part before, including % if (index >= static_cast(toParse.length())) break; // % was the last char (not valid) toExpand = toParse.at(index++); if (toExpand == QLatin1Char('s')) { found = toParse.indexOf(QLatin1Char('%'), index); if (found == -1) // no other % (not valid) break; separator = toParse.mid(index,found-index); index = found + 1; // skip separator, including % } else if (toExpand == QLatin1Char('u')) { out.append(nickList.join(separator)); } else if (toExpand == QLatin1Char('c')) { if(!channelName.isEmpty()) out.append(channelName); } else if (toExpand == QLatin1Char('o')) { out.append(sender); } else if (toExpand == QLatin1Char('k')) { if(!channelKey.isEmpty()) out.append(channelKey); } else if (toExpand == QLatin1Char('K')) { if(getConnectionSettings().server().password().isEmpty()) out.append(getConnectionSettings().server().password()); } else if (toExpand == QLatin1Char('n')) { out.append(QStringLiteral("\n")); } else if (toExpand == QLatin1Char('p')) { out.append(QStringLiteral("%")); } else if (toExpand == QLatin1Char('i')) { out.append(inputLineText); } } // append last part out.append(toParse.mid(index,toParse.length()-index)); return out; } void Server::sendToAllChannels(const QString &text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } } void Server::invitation(const QString& nick,const QString& channel) { if(!m_inviteDialog) { QDialogButtonBox::StandardButton buttonCode = QDialogButtonBox::Cancel; if(!InviteDialog::shouldBeShown(buttonCode)) { if (buttonCode == QDialogButtonBox::Ok) sendJoinCommand(channel); return; } m_inviteDialog = new InviteDialog (getViewContainer()->getWindow()); connect(m_inviteDialog, SIGNAL(joinChannelsRequested(QString)), this, SLOT(sendJoinCommand(QString))); } m_inviteDialog->show(); m_inviteDialog->raise(); m_inviteDialog->addInvite(nick, channel); } void Server::scriptNotFound(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not find script \"%1\".", name)); } void Server::scriptExecutionError(const QString& name) { appendMessageToFrontmost(i18n("D-Bus"),i18n("Error: Could not execute script \"%1\". Check file permissions.", name)); } bool Server::isAChannel(const QString &channel) const { if (channel.isEmpty()) return false; uint index = 0; if (m_serverNickPrefixes.contains(channel.at(0)) && channel.length() >= 2) { ++index; } return (getChannelTypes().contains(channel.at(index)) > 0); } void Server::addRawLog(bool show) { if (!m_rawLog) m_rawLog = getViewContainer()->addRawLog(this); connect(this, SIGNAL(serverOnline(bool)), m_rawLog, SLOT(serverOnline(bool))); // bring raw log to front since the main window does not do this for us if (show) emit showView(m_rawLog); } void Server::closeRawLog() { delete m_rawLog; } void Server::requestOpenChannelListPanel(const QString& filter) { getViewContainer()->openChannelList(this, filter, true); } ChannelListPanel* Server::addChannelListPanel() { if(!m_channelListPanel) { m_channelListPanel = getViewContainer()->addChannelListPanel(this); connect(&m_inputFilter, SIGNAL(endOfChannelList()), m_channelListPanel, SLOT(endOfChannelList())); connect(m_channelListPanel, SIGNAL(refreshChannelList()), this, SLOT(requestChannelList())); connect(m_channelListPanel, SIGNAL(joinChannel(QString)), this, SLOT(sendJoinCommand(QString))); connect(this, SIGNAL(serverOnline(bool)), m_channelListPanel, SLOT(serverOnline(bool))); } return m_channelListPanel; } void Server::addToChannelList(const QString& channel, int users, const QString& topic) { addChannelListPanel(); m_channelListPanel->addToChannelList(channel, users, topic); } ChannelListPanel* Server::getChannelListPanel() const { return m_channelListPanel; } void Server::closeChannelListPanel() { delete m_channelListPanel; } void Server::updateAutoJoin(Konversation::ChannelList channels) { Konversation::ChannelList tmpList; if (!channels.isEmpty()) { foreach (const ChannelSettings& cs, channels) { tmpList << cs; } } else if (m_channelList.isEmpty() && getServerGroup()) tmpList = getServerGroup()->channelList(); else { foreach (Channel* channel, m_channelList) { tmpList << channel->channelSettings(); } } if (!tmpList.isEmpty()) { setAutoJoinCommands(generateJoinCommand(tmpList)); setAutoJoin(!m_autoJoinCommands.isEmpty()); } else { m_autoJoinCommands.clear(); setAutoJoin(false); } } QStringList Server::generateJoinCommand(const Konversation::ChannelList &tmpList) { QStringList channels; QStringList passwords; QStringList joinCommands; uint length = 0; Konversation::ChannelList::const_iterator it; for (it = tmpList.constBegin(); it != tmpList.constEnd(); ++it) { QString channel = (*it).name(); // Only add the channel to the JOIN command if it has a valid channel name. if (isAChannel(channel)) { QString password = ((*it).password().isEmpty() ? QStringLiteral(".") : (*it).password()); uint currentLength = getIdentity()->getCodec()->fromUnicode(channel).length(); currentLength += getIdentity()->getCodec()->fromUnicode(password).length(); //channels.count() and passwords.count() account for the commas if (length + currentLength + 6 + channels.count() + passwords.count() >= 512) // 6: "JOIN " plus separating space between chans and pws. { while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); channels.clear(); passwords.clear(); length = 0; } length += currentLength; channels << channel; passwords << password; } } while (!passwords.isEmpty() && passwords.last() == QStringLiteral(".")) passwords.pop_back(); // Even if the given tmpList contained entries they might have been filtered // out by the isAChannel() check. if (!channels.isEmpty()) { joinCommands << QStringLiteral("JOIN ") + channels.join(QStringLiteral(",")) + QLatin1Char(' ') + passwords.join(QStringLiteral(",")); } return joinCommands; } ViewContainer* Server::getViewContainer() const { Application* konvApp = Application::instance(); return konvApp->getMainWindow()->getViewContainer(); } bool Server::getUseSSL() const { if ( m_socket ) return ( m_socket->encryptionMode() != KTcpSocket::UnencryptedMode ); else return false; } QString Server::getSSLInfo() const { // SSLSocket* sslsocket = dynamic_cast(m_socket); // if(sslsocket) // return sslsocket->details(); return QString(); } void Server::sendMultiServerCommand(const QString& command, const QString& parameter) { emit multiServerCommand(command, parameter); } void Server::executeMultiServerCommand(const QString& command, const QString& parameter) { if (command == QStringLiteral("msg")) sendToAllChannelsAndQueries(parameter); else sendToAllChannelsAndQueries(Preferences::self()->commandChar() + command + QLatin1Char(' ') + parameter); } void Server::sendToAllChannelsAndQueries(const QString& text) { // Send a message to all channels we are in foreach (Channel* channel, m_channelList) { channel->sendText(text); } // Send a message to all queries we are in foreach (Query* query, m_queryList) { query->sendText(text); } } void Server::requestAway(const QString& reason) { QString awayReason = reason; IdentityPtr identity = getIdentity(); if (awayReason.isEmpty() && identity) awayReason = identity->getAwayMessage(); // Fallback in case the identity has no away message set. if (awayReason.isEmpty()) awayReason = i18n("Gone away for now"); setAwayReason(awayReason); queue(QStringLiteral("AWAY :") + awayReason); } void Server::requestUnaway() { queue(QStringLiteral("AWAY")); } void Server::setAway(bool away, const QHash &messageTags) { IdentityPtr identity = getIdentity(); if (away) { if (!m_away) startAwayTimer(); m_away = true; emit awayState(true); if (identity && !identity->getAwayNickname().isEmpty() && identity->getAwayNickname() != getNickname()) { m_nonAwayNick = getNickname(); queue(QStringLiteral("NICK ") + getIdentity()->getAwayNickname()); } if (!m_awayReason.isEmpty()) appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away (reason: %1).",m_awayReason), messageTags); else appendMessageToFrontmost(i18n("Away"), i18n("You are now marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getAwayCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%s"), Qt::CaseInsensitive), m_awayReason)); } if (identity && identity->getInsertRememberLineOnAway()) emit awayInsertRememberLine(this); } else { m_awayReason.clear(); emit awayState(false); if (!identity->getAwayNickname().isEmpty() && !m_nonAwayNick.isEmpty()) { queue(QStringLiteral("NICK ") + m_nonAwayNick); m_nonAwayNick.clear(); } if (m_away) { appendMessageToFrontmost(i18n("Away"), i18n("You are no longer marked as being away."), messageTags); if (identity && identity->getRunAwayCommands()) { QString message = identity->getReturnCommand(); sendToAllChannels(message.replace(QRegExp(QStringLiteral("%t"), Qt::CaseInsensitive), awayTime())); } } else appendMessageToFrontmost(i18n("Away"), i18n("You are not marked as being away."), messageTags); m_away = false; } } QString Server::awayTime() const { QString retVal; if (m_away) { int diff = QDateTime::currentDateTime().toTime_t() - m_awayTime; int num = diff / 3600; if (num < 10) retVal = QLatin1Char('0') + QString::number(num) + QLatin1Char(':'); else retVal = QString::number(num) + QLatin1Char(':'); num = (diff % 3600) / 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num) + QLatin1Char(':'); num = (diff % 3600) % 60; if (num < 10) retVal += QLatin1Char('0'); retVal += QString::number(num); } else retVal = QStringLiteral("00:00:00"); return retVal; } void Server::startAwayTimer() { m_awayTime = QDateTime::currentDateTime().toTime_t(); } void Server::enableIdentifyMsg(bool enabled) { m_identifyMsg = enabled; } bool Server::identifyMsgEnabled() { return m_identifyMsg; } void Server::addBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->addBan(ban); } } void Server::removeBan(const QString &channel, const QString &ban) { Channel* outChannel = getChannelByName(channel); if(outChannel) { outChannel->removeBan(ban); } } void Server::sendPing() { //WHO ourselves once a minute in case the irc server has changed our //hostmask, such as what happens when a Freenode cloak is activated. //It might be more intelligent to only do this when there is text //in the inputbox. Kinda changes this into a "do minutely" //queue :-) QStringList ql; ql << QStringLiteral("PING LAG") + QTime::currentTime().toString(QStringLiteral("hhmmss")); getInputFilter()->setAutomaticRequest(QStringLiteral("WHO"), getNickname(), true); ql << QStringLiteral("WHO ") + getNickname(); queueList(ql, HighPriority); m_lagTime.start(); m_inputFilter.setLagMeasuring(true); m_pingResponseTimer.start(1000 /*1 sec*/); } void Server::pongReceived() { // ignore unrequested PONGs if (m_pingSendTimer.isActive()) return; m_currentLag = m_lagTime.elapsed(); m_inputFilter.setLagMeasuring(false); m_pingResponseTimer.stop(); emit serverLag(this, m_currentLag); // Send another PING in 60 seconds m_pingSendTimer.start(60000 /*60 sec*/); } void Server::updateLongPongLag() { if (isSocketConnected()) { m_currentLag = m_lagTime.elapsed(); emit tooLongLag(this, m_currentLag); // qDebug() << "Current lag: " << currentLag; if (m_currentLag > (Preferences::self()->maximumLagTime() * 1000)) m_socket->close(); } } void Server::updateEncoding() { if(getViewContainer() && getViewContainer()->getFrontView()) getViewContainer()->updateViewEncoding(getViewContainer()->getFrontView()); } #ifdef HAVE_QCA2 void Server::initKeyExchange(const QString &receiver) { Query* query; if (getQueryByName(receiver)) { query = getQueryByName(receiver); } else { NickInfoPtr nickinfo = obtainNickInfo(receiver); query = addQuery(nickinfo, true); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->initKeyExchange(); if(pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to initiate key exchange with %1.", receiver)); } else { queue("NOTICE "+receiver+" :DH1080_INIT "+pubKey); } } void Server::parseInitKeyX(const QString &sender, const QString &remoteKey) { if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to perform key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } //TODO ask the user to accept without blocking Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else { NickInfoPtr nickinfo = obtainNickInfo(sender); query = addQuery(nickinfo, false); } Konversation::Cipher* cipher = query->getCipher(); QByteArray pubKey = cipher->parseInitKeyX(remoteKey.toLocal8Bit()); if (pubKey.isEmpty()) { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse the DH1080_INIT of %1. Key exchange failed.", sender)); } else { setKeyForRecipient(sender, cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Your key is set and your messages will now be encrypted, sending DH1080_FINISH to %1.", sender)); queue("NOTICE "+sender+" :DH1080_FINISH "+pubKey); } } void Server::parseFinishKeyX(const QString &sender, const QString &remoteKey) { Query* query; if (getQueryByName(sender)) { query = getQueryByName(sender); } else return; if (!Konversation::Cipher::isFeatureAvailable(Konversation::Cipher::DH)) { appendMessageToFrontmost(i18n("Error"), i18n("Unable to complete key exchange with %1.", sender) + ' ' + Konversation::Cipher::runtimeError()); return; } Konversation::Cipher* cipher = query->getCipher(); if (cipher->parseFinishKeyX(remoteKey.toLocal8Bit())) { setKeyForRecipient(sender,cipher->key()); query->setEncryptedOutput(true); appendMessageToFrontmost(i18n("Notice"), i18n("Successfully parsed DH1080_FINISH sent by %1. Your key is set and your messages will now be encrypted.", sender)); } else { appendMessageToFrontmost(i18n("Error"), i18n("Failed to parse DH1080_FINISH sent by %1. Key exchange failed.", sender)); } } #endif QAbstractItemModel* Server::nickListModel() const { return m_nickListModel; } void Server::startNickInfoChangedTimer() { if(!m_nickInfoChangedTimer->isActive()) m_nickInfoChangedTimer->start(); } void Server::sendNickInfoChangedSignals() { emit nickInfoChanged(); foreach(NickInfoPtr nickInfo, m_allNicks) { if(nickInfo->isChanged()) { m_userModel->changed(nickInfo); // WIPQTQUICK emit nickInfoChanged(this, nickInfo); nickInfo->setChanged(false); } } } void Server::startChannelNickChangedTimer(const QString& channel) { if(!m_channelNickChangedTimer->isActive()) m_channelNickChangedTimer->start(); m_changedChannels.append(channel); } void Server::sendChannelNickChangedSignals() { foreach(const QString& channel, m_changedChannels) { if (m_joinedChannels.contains (channel)) { emit channelNickChanged(channel); foreach(ChannelNickPtr nick, (*m_joinedChannels[channel])) { if(nick->isChanged()) { nick->setChanged(false); } } } } m_changedChannels.clear(); } void Server::involuntaryQuit() { - if((m_connectionState == Konversation::SSConnected || m_connectionState == Konversation::SSConnecting) && + if((m_connectionState == Konversation::Connected || m_connectionState == Konversation::Connecting) && (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6))) { quitServer(); - updateConnectionState(Konversation::SSInvoluntarilyDisconnected); + updateConnectionState(Konversation::InvoluntarilyDisconnected); } } void Server::reconnectInvoluntary() { - if(m_connectionState == Konversation::SSInvoluntarilyDisconnected) + if(m_connectionState == Konversation::InvoluntarilyDisconnected) reconnectServer(); } // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/irc/server.h b/src/irc/server.h index c98aacf3..7522c89a 100644 --- a/src/irc/server.h +++ b/src/irc/server.h @@ -1,880 +1,881 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2008 Eli J. MacKenzie Copyright (C) 2005-2008 Eike Hein */ #ifndef SERVER_H #define SERVER_H #include "common.h" #include "channelnick.h" #include "inputfilter.h" #include "outputfilter.h" #include "nickinfo.h" #include "serversettings.h" #include "servergroupsettings.h" #include "connectionsettings.h" #include "statuspanel.h" #include "invitedialog.h" #include "usermodel.h" // WIPQTQUICK #include #ifdef HAVE_QCA2 #include "cipher.h" #endif #include #include #include #include #include #include #include class QAbstractItemModel; class QStringListModel; class Channel; class Query; class Identity; class RawLog; class ChannelListPanel; class ServerISON; class ChatWindow; class ViewContainer; class UserModel; // WIPQTQUICK class IRCQueue; namespace Konversation { namespace DCC { class Transfer; class Chat; } } class Server : public QObject { Q_OBJECT friend class IRCQueue; friend class QueueTuner; Q_PROPERTY(QString nickname READ getNickname WRITE setNickname NOTIFY nicknameChanged) Q_PROPERTY(UserModel* userModel READ getUserModel CONSTANT) // WIPQTQUICK + Q_PROPERTY(Konversation::ConnectionState connectionState READ getConnectionState NOTIFY connectionStateChanged) public: enum QueuePriority { LowPriority, /// &messageTags); void renameNick(const QString &nickname, const QString &newNick, const QHash &messageTags); Channel* removeNickFromChannel(const QString &channelName, const QString &nickname, const QString &reason, const QHash &messageTags, bool quit=false); void nickWasKickedFromChannel(const QString &channelName, const QString &nickname, const QString &kicker, const QString &reason, const QHash &messageTags); void removeNickFromServer(const QString &nickname, const QString &reason, const QHash &messageTags); void setChannelTypes(const QString &types); QString getChannelTypes() const; void setModesCount(int count); int getModesCount(); // extended user modes support void setChanModes(const QString&); //grab modes types from RPL_ISUPPORT CHANMODES QString banAddressListModes() { return m_banAddressListModes; } // aka "TYPE A" modes http://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#section-3.3 void setPrefixes(const QString &modes, const QString& prefixes); QString getServerNickPrefixes() const; void mangleNicknameWithModes(QString &nickname,bool& isAdmin,bool& isOwner,bool &isOp, bool& isHalfop,bool &hasVoice); bool isAChannel(const QString &channel) const; bool isNickname(const QString& compare) const; QString getNickname() const; QString loweredNickname() const; QString getNextNickname(); InputFilter* getInputFilter() { return &m_inputFilter; } Konversation::OutputFilter* getOutputFilter() { return m_outputFilter; } Channel* joinChannel(const QString& name, const QString& hostmask, const QHash &messageTags); void removeChannel(Channel* channel); void appendServerMessageToChannel(const QString& channel, const QString& type, const QString& message, const QHash &messageTags); void appendCommandMessageToChannel(const QString& channel, const QString& command, const QString& message, const QHash &messageTags, bool highlight = true, bool parseURL = true); void appendStatusMessage(const QString& type,const QString& message, const QHash &messageTags); void appendMessageToFrontmost(const QString& type,const QString& message, const QHash &messageTags = QHash(), bool parseURL = true); int getPreLength(const QString& command, const QString& dest); void dbusRaw(const QString& command); void dbusSay(const QString& target,const QString& command); void dbusInfo(const QString& string); void ctcpReply(const QString& receiver, const QString& text); void setChannelTopic(const QString& channel, const QString& topic, const QHash &messageTags); // Overloaded void setChannelTopic(const QString& nickname, const QString& channel, const QString& topic, const QHash &messageTags); void updateChannelMode(const QString& nick, const QString& channel, char mode, bool plus, const QString& parameter, const QHash &messageTags); void updateChannelModeWidgets(const QString& channel, char mode, const QString& parameter); Channel* getChannelByName(const QString& name); Query* getQueryByName(const QString& name); ChatWindow* getChannelOrQueryByName(const QString& name); QString parseWildcards(const QString& toParse, ChatWindow* context = nullptr, const QStringList nicks = QStringList()); QString parseWildcards(const QString& toParse, const QString& nickname, const QString& channelName, const QString &channelKey, const QStringList &nickList, const QString& inputLineText); QString parseWildcards(const QString& toParse, const QString& nickname, const QString& channelName, const QString &channelKey, const QString& nick, const QString& inputLineText); void autoCommandsAndChannels(); void sendURIs(const QList& uris, const QString& nick); void notifyAction(const QString& nick); ChannelListPanel* getChannelListPanel() const; StatusPanel* getStatusView() const { return m_statusView; } virtual bool closeYourself(bool askForConfirmation=true); QString getOwnIpByNetworkInterface(); QString getOwnIpByServerMessage(); bool isAway() { return m_away; } void setAway(bool away, const QHash &messageTags); QString awayTime() const; void setAwayReason(const QString& reason) { m_awayReason = reason; } /** * Returns true if the given nickname is known to be online. * @param nickname The nickname. Case insensitive. * @return True if the nickname is known to be online by the server. * Note that a nick that is not in any of the joined channels and is not on the * notify list, and has not initiated a query with you, may well be online, * but server doesn't know if it is or not, in which case False is returned. */ bool isNickOnline(const QString &nickname); /** Given a nickname, returns NickInfo object. * @param nickname The desired nickname. Case insensitive. * @return Pointer to the nickinfo for this nickname if one exists. * 0 if not known to be online. * * A NickInfo pointer will only be returned if the nickname is known to the Konvi * Server object. A nick will be known if: * - It is in one of the server's channels user has joined. * - It is on the notify list and is known to be online. * - The nick initiated a query with the user. * A NickInfo is destroyed when it is offline. */ NickInfoPtr getNickInfo(const QString& nickname); /** Given a nickname, returns an existing NickInfo object, or creates a new NickInfo object. * Guaranteed to return a nickinfo. * @param nickname The desired nickname. Case sensitive. * @return Pointer to the found or created NickInfo object. */ NickInfoPtr obtainNickInfo(const QString& nickname); /** Returns a list of all the NickInfos that are online and known to the server. * Caller should not modify the list. * A nick will be known if: * - It is in one of the server's channels user has joined. * - It is on the notify list and is known to be online. * - The nick initiated a query with the user. * * @return A QMap of NickInfoPtrs indexed by lowercase nickname. */ const NickInfoMap* getAllNicks(); /** Returns the list of members for a channel in the joinedChannels list. * A joinedChannel is one that you are in, as opposed to a channel that you aren't in, * but one of your watched nicks is in. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of all the nicks in the channel. * 0 if channel is not in the joinedChannels list. */ const ChannelNickMap *getJoinedChannelMembers(const QString& channelName) const; /** Returns the list of members for a channel in the unjoinedChannels list. * An unjoinedChannel is a channel you aren't in. As such, this is only going to return * nicks that you know are in that channel because a /whois has been done against them. * This could be done automatically if they are on the watch list. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of only the nicks that we know that are in the channel. * 0 if channel is not in the unjoinedChannels list. */ const ChannelNickMap *getUnjoinedChannelMembers(const QString& channelName) const; /** Searches the Joined and Unjoined lists for the given channel and returns the member list. * Code that calls this must not modify the list. * @param channelName Name of desired channel. Case insensitive. * @return A map of nicks in that channel. 0 if channel is not in either list. * * @see getJoinedChannelMembers(const QString& channelName) * @see getUnjoinedChannelMembers(const QString& channelName) */ const ChannelNickMap *getChannelMembers(const QString& channelName) const; /** Returns a list of all the joined channels that a nick is in. * @param nickname The desired nickname. Case insensitive. * @return A list of joined channels the nick is in. Empty if none. */ QStringList getNickJoinedChannels(const QString& nickname); /** Returns a list of all the channels (joined or unjoined) that a nick is in. * @param nickname The desired nickname. Case insensitive. * @return A list of channels the nick is in. Empty if none. * * A nick will not appear in the Unjoined channels list unless a WHOIS * has been performed on it. */ QStringList getNickChannels(const QString& nickname); /** Returns a list of all the channels we're in that nickname is also in. * @param nickname The desired nickname. Case insensitive. * @return A list of channels the nick is in that we're also in. Empty if none. */ QStringList getSharedChannels(const QString& nickname); /** Returns pointer to the ChannelNick (mode and pointer to NickInfo) for a * given channel and nickname. * @param channelName The desired channel name. Case insensitive. * @param nickname The desired nickname. Case insensitive. * @return Pointer to ChannelNick structure containing a pointer * to the NickInfo and the mode of the nick in the channel. * 0 if not found. */ ChannelNickPtr getChannelNick(const QString& channelName, const QString& nickname); /** Updates a nickname in a channel. If not on the joined or unjoined lists, and nick * is in the watch list, adds the channel and nick to the unjoinedChannels list. * If mode != 99, sets the mode for the nick in the channel. * Returns the NickInfo object if nick is on any lists, otherwise 0. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @param mode Bit mask containing the modes the nick has in the channel, * or 99 if not known. See channelnick.cpp for bit definitions. */ ChannelNickPtr setChannelNick(const QString& channelName, const QString& nickname, unsigned int mode = 99); /** * Returns a QList of all channels */ const QList& getChannelList() const { return m_channelList; } /** * Returns a lower case list of all the nicks on the user watch list. */ QStringList getWatchList(); /** * Return true if the given nickname is on the watch list. */ bool isWatchedNick(const QString& nickname); /** * Returns a list of all the nicks on the watch list that are not in joined * channels. ISON command is sent for these nicks. */ QStringList getISONList(); QString getISONListString(); ViewContainer* getViewContainer() const; /** Adds a nickname to the joinedChannels list. * Creates new NickInfo if necessary. * If needed, moves the channel from the unjoined list to the joined list. * If needed, moves the nickname from the Offline to Online lists. * If mode != 99 sets the mode for this nick in this channel. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @return The NickInfo for the nickname. */ ChannelNickPtr addNickToJoinedChannelsList(const QString& channelName, const QString& nickname); void setAllowedChannelModes(const QString& modes) { m_allowedChannelModes = modes; } QString allowedChannelModes() const { return m_allowedChannelModes; } void setTopicLength(int topicLength) { m_topicLength = topicLength; } int topicLength() const { return m_topicLength; } void registerWithServices(); // Blowfish stuff QByteArray getKeyForRecipient(const QString& recipient) const; void setKeyForRecipient(const QString& recipient, const QByteArray& key); bool identifyMsg() const { return m_identifyMsg; } QString getLastAuthenticateCommand() const { return m_lastAuthenticateCommand; } ChannelListPanel* addChannelListPanel(); // invoked by DCC::TransferSend void dccSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint16 port,quint64 size); void dccPassiveSendRequest(const QString& recipient,const QString& fileName,const QString& address,quint64 size,const QString& token); // invoked by DCC::TransferRecv void dccPassiveResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt,const QString &token); void dccResumeGetRequest(const QString& sender,const QString& fileName,quint16 port,KIO::filesize_t startAt); void dccReverseSendAck(const QString& partnerNick,const QString& fileName,const QString& ownAddress,quint16 ownPort,quint64 size,const QString& reverseToken); void dccRejectSend(const QString& partnerNick, const QString& fileName); // invoked by DCC::Chat void dccRejectChat(const QString& partnerNick, const QString& extension); void dccPassiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token); void dccReverseChatAck(const QString& partnerNick, const QString& extension, const QString& ownAddress, quint16 ownPort, const QString& reverseToken); bool capEndDelayed() const { return m_capEndDelayed; } bool hasAwayNotify() const { return m_hasAwayNotify; } bool hasExtendedJoin() const { return m_hasExtendedJoin; } void setHasWHOX(bool state) { m_hasWHOX = state; } bool hasWHOX() const { return m_hasWHOX; } bool hasServerTime() const { return m_hasServerTime; } bool hasUserHostInNames() const { return m_hasUserHostInNames; } // IRCQueueManager bool validQueue(QueuePriority priority); ///< is this queue index valid? void resetQueues(); ///< Tell all of the queues to reset /** Forces the queued data to be sent in sequence of age, without pause. This could flood you off but since you're quitting, we probably don't care. This is done here instead of in the queues themselves so we can interleave the queues without having to zip the queues together. If you want to quit the server normally without sending, reset the queues first. */ void flushQueues(); //These are really only here to limit where ircqueue.h is included Q_SIGNALS: void destroyed(int connectionId); void nicknameChanged(const QString&); void serverLag(Server* server,int msec); /// will be connected to KonversationMainWindow::updateLag() void tooLongLag(Server* server, int msec);/// will be connected to KonversationMainWindow::updateLag() void resetLag(Server* server); ///< will be emitted when new 303 came in void nicksNowOnline(Server* server,const QStringList& list,bool changed); void awayState(bool away); /// will be connected to any user input panel; void multiServerCommand(const QString& command, const QString& parameter); /** * Emitted when the server gains/loses connection. * Will be connected to all server dependant tabs. */ void serverOnline(bool state); /** * Emitted every time something gets sent. * * @param bytes The count of bytes sent to the server, before re-encoding. * @param encodedBytes The count of bytes sent to the server after re-encoding. */ void sentStat(int bytes, int encodedBytes, IRCQueue *whichQueue); //Note that these signals haven't been implemented yet. /// Fires when the information in a NickInfo object changes. void nickInfoChanged(Server* server, const NickInfoPtr nickInfo); /// Emitted once if one or more NickInfo has been changed. void nickInfoChanged(); /// Emitted once if one or more ChannelNick has been changed in @p channel. void channelNickChanged(const QString& channel); /// Fires when a nick leaves or joins a channel. Based on joined flag, receiver could /// call getJoinedChannelMembers or getUnjoinedChannelMembers, or just /// getChannelMembers to get a list of all the nicks now in the channel. /// parted indicates whether the nick joined or left the channel. void channelMembersChanged(Server* server, const QString& channelName, bool joined, bool parted, const QString& nickname); /// Fires when a channel is moved to/from the Joinied/Unjoined lists. /// joined indicates which list it is now on. Note that if joined is False, it is /// possible the channel does not exist in any list anymore. void channelJoinedOrUnjoined(Server* server, const QString& channelName, bool joined); /// Fires when a nick on the watch list goes online or offline. void watchedNickChanged(Server* server, const QString& nickname, bool online); ///Fires when the user switches his state to away and has enabled "Insert Remember Line on away" in his identity. void awayInsertRememberLine(Server* server); void sslInitFailure(); void sslConnected(Server* server); - void connectionStateChanged(Server* server, Konversation::ConnectionState state); + void connectionStateChanged(Konversation::ConnectionState state); void showView(QObject* view); // WIPQTQUICK void addDccPanel(); void addDccChat(Konversation::DCC::Chat *chat); public Q_SLOTS: void connectToIRCServer(); void connectToIRCServerIn(uint delay); /** Adds line to queue if non-empty. */ bool queue(const QString& line, QueuePriority priority=StandardPriority); //TODO this should be an overload, not a separate name. ambiguous cases need QString() around the cstring bool queueList(const QStringList& buffer, QueuePriority priority=StandardPriority); void setNickname(const QString &newNickname); /** This is called when we want to open a new query, or focus an existing one. * @param nickInfo The nickinfo we want to open the query to. Must exist. * @param weinitiated This is whether we initiated this - did we do /query, or somebody else sending us a message. * @return A pointer to a new or already-existing query. Guaranteed to be non-null */ Q_INVOKABLE void addQuery(const QString& nick); // WIPQTQUICK Query *addQuery(const NickInfoPtr & nickInfo, bool weinitiated); void closeQuery(const QString &name); void closeChannel(const QString &name); void reconnectServer(const QString& quitMessage = QString()); void disconnectServer(const QString& quitMessage = QString()); void quitServer(const QString& quitMessage = QString()); void openDccChat(const QString& nickname); void openDccWBoard(const QString& nickname); void requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort); void acceptDccGet(const QString& nick, const QString& file); void requestBan(const QStringList& users,const QString& channel,const QString& option); void requestUnban(const QString& mask,const QString& channel); void addDccSend(const QString &recipient, QUrl fileURL, bool passive = Preferences::self()->dccPassiveSend(), const QString &altFileName = QString(), quint64 fileSize = 0); void removeQuery(Query *query); void notifyListStarted(int serverGroupId); void startNotifyTimer(int msec=0); void notifyTimeout(); Q_INVOKABLE void sendJoinCommand(const QString& channelName, const QString& password = QString()); // WIPQTQUICK void requestAway(const QString& reason = QString()); void requestUnaway(); void requestChannelList(); void requestWhois(const QString& nickname); void requestWho(const QString& channel); void requestUserhost(const QString& nicks); void requestTopic(const QString& channel); void resolveUserhost(const QString& nickname); void addRawLog(bool show); void closeRawLog(); void addToChannelList(const QString& channel, int users, const QString& topic); void closeChannelListPanel(); void sendMultiServerCommand(const QString& command, const QString& parameter); void executeMultiServerCommand(const QString& command, const QString& parameter); void showSSLDialog(); void sendToAllChannels(const QString& text); void sendToAllChannelsAndQueries(const QString& text); void enableIdentifyMsg(bool enabled); bool identifyMsgEnabled(); void addBan(const QString &channel, const QString &ban); void removeBan(const QString &channel, const QString &ban); /// Called when we received a PONG from the server void pongReceived(); #ifdef HAVE_QCA2 void initKeyExchange(const QString &receiver); void parseInitKeyX(const QString &sender, const QString &pubKey); void parseFinishKeyX(const QString &sender, const QString &pubKey); #endif /// Start the NickInfo changed timer if it isn't started already void startNickInfoChangedTimer(); /// Start the ChannelNick changed timer if it isn't started already void startChannelNickChangedTimer(const QString& channel); /// Called when the system wants to close the connection due to network going down etc. void involuntaryQuit(); /// Will only reconnect if the connection state is involuntary disconnected. void reconnectInvoluntary(); void requestAvailableCapabilies (); void capInitiateNegotiation(const QString &availableCaps); void capReply(); void capEndNegotiation(); void capCheckIgnored(); void capAcknowledged(const QString& name, CapModifiers modifiers); void capDenied(const QString& name); void sendAuthenticate(const QString& message); protected Q_SLOTS: void hostFound(); void preShellCommandExited(int exitCode, QProcess::ExitStatus exitStatus); void preShellCommandError(QProcess::ProcessError eror); void socketConnected(); void startAwayTimer(); void incoming(); void processIncomingData(); /// Sends the QString to the socket. No longer has any internal concept of queueing void toServer(QString&, IRCQueue *); /// Because KBufferedSocket has no closed(int) signal we use this slot to call broken(0) void closed(); void broken(KTcpSocket::Error error); /** This is connected to the SSLSocket failed. * @param reason The reason why this failed. This is already translated, ready to show the user. */ void sslError(const QList&); void connectionEstablished(const QString& ownHost); void notifyResponse(const QString& nicksOnline); void slotNewDccTransferItemQueued(Konversation::DCC::Transfer* transfer); void startReverseDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void startReverseDccChat(const QString &sourceNick, const QStringList &dccArgument); void addDccGet(const QString& sourceNick,const QStringList& dccArguments); void requestDccSend(); // -> to outputFilter, dccPanel // -> to outputFilter void requestDccSend(const QString& recipient); // -> to inputFilter void resumeDccGetTransfer(const QString& sourceNick,const QStringList& dccArguments); // -> to inputFilter void resumeDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void rejectDccSendTransfer(const QString& sourceNick,const QStringList& dccArguments); void dccGetDone(Konversation::DCC::Transfer* item); void dccSendDone(Konversation::DCC::Transfer* item); void dccStatusChanged(Konversation::DCC::Transfer* item, int newStatus, int oldStatus); void addDccChat(const QString& sourceNick,const QStringList& arguments); void rejectDccChat(const QString& sourceNick); void scriptNotFound(const QString& name); void scriptExecutionError(const QString& name); void userhost(const QString& nick,const QString& hostmask,bool away,bool ircOp); void setTopicAuthor(const QString& channel,const QString& author, QDateTime t); void endOfWho(const QString& target); void endOfNames(const QString& target); void invitation(const QString& nick,const QString& channel); void gotOwnResolvedHostByWelcome(const QHostInfo& res); void gotOwnResolvedHostByUserhost(const QHostInfo& res); /// Send a PING to the server so we can meassure the lag void sendPing(); /// Updates GUI when the lag gets high void updateLongPongLag(); /// Update the encoding shown in the mainwindow's actions void updateEncoding(); /** Called when the NickInfo changed timer times out. * Emits the nickInfoChanged() signal for all changed NickInfos */ void sendNickInfoChangedSignals(); /** Called when the ChannelNick changed timer times out. * Emits the channelNickChanged() signal for each channel with changed nicks. */ void sendChannelNickChangedSignals(); void requestOpenChannelListPanel(const QString& filter); private Q_SLOTS: /** Called in the server constructor if the preferences are set to run a command on a new server instance. * This sets up the kprocess, runs it, and connects the signals to call preShellCommandExited when done. */ void doPreShellCommand(); protected: // constants static const int BUFFER_LEN=513; /// Initialize the timers void initTimers(); /// Connect to the signals used in this class. void connectSignals(); int _send_internal(QString outputline); ///< Guts of old send, isn't a slot. /** Adds a nickname to the unjoinedChannels list. * Creates new NickInfo if necessary. * If needed, moves the channel from the joined list to the unjoined list. * If needed, moves the nickname from the Offline to the Online list. * If mode != 99 sets the mode for this nick in this channel. * @param channelName The channel name. Case sensitive. * @param nickname The nickname. Case sensitive. * @return The NickInfo for the nickname. */ ChannelNickPtr addNickToUnjoinedChannelsList(const QString& channelName, const QString& nickname); /** * If not already online, changes a nick to the online state by creating * a NickInfo for it and emits various signals and messages for it. * This method should only be called for nicks on the watch list. * @param nickname The nickname that is online. * @return Pointer to NickInfo for nick. */ NickInfoPtr setWatchedNickOnline(const QString& nickname); /** * Display offline notification for a certain nickname. The function doesn't change NickInfo objects. * @param nickname The nickname that is offline * @param nickInfo Pointer to NickInfo for nick */ void setWatchedNickOffline(const QString& nickname, const NickInfoPtr nickInfo); /** * If nickname is no longer on any channel list, or the query list, delete it altogether. * Call this routine only if the nick is not on the notify list or is on the notify * list but is known to be offline. * @param nickname The nickname to be deleted. Case insensitive. * @return True if the nickname is deleted. */ bool deleteNickIfUnlisted(const QString &nickname); /** * If not already offline, changes a nick to the offline state. * Removes it from all channels on the joined and unjoined lists. * If the nick is in the watch list, and went offline, emits a signal, * posts a Notify message, and posts a KNotify. * If the nick goes offline, the NickInfo is deleted. * * @param nickname The nickname. Case sensitive. * @return True if the nick was online. */ bool setNickOffline(const QString& nickname); /** Remove nickname from a channel (on joined or unjoined lists). * @param channelName The channel name. Case insensitive. * @param nickname The nickname. Case insensitive. */ void removeChannelNick(const QString& channelName, const QString& nickname); /** Remove channel from the joined list. * Nicknames in the channel are added to the unjoined list if they are in the watch list. * @param channelName The channel name. Case insensitive. */ void removeJoinedChannel(const QString& channelName); /** Renames a nickname in all NickInfo lists. * @param nickInfo Pointer to existing NickInfo object. * @param newname New nickname for the nick. Case sensitive. */ void renameNickInfo(NickInfoPtr nickInfo, const QString& newname); bool getAutoJoin() const; void setAutoJoin(bool on); QStringList getAutoJoinCommands() const { return m_autoJoinCommands; } void setAutoJoinCommands(const QStringList& commands) { m_autoJoinCommands = commands; } unsigned int m_completeQueryPosition; QList m_nickIndices; QStringList m_referenceNicklist; QStringListModel* m_nickListModel; // TODO roll these into a QMap. QString m_serverNickPrefixes; // Prefixes used by the server to indicate a mode QString m_serverNickPrefixModes; // if supplied: modes related to those prefixes QString m_banAddressListModes; // "TYPE A" modes from RPL_ISUPPORT CHANMODES=A,B,C,D QString m_channelPrefixes; // prefixes that indicate channel names. defaults to RFC1459 "#&" int m_modesCount; // Maximum number of channel modes with parameter allowed per MODE command. bool m_autoJoin; QStringList m_autoJoinCommands; KTcpSocket* m_socket; QTimer m_incomingTimer; QTimer m_notifyTimer; QStringList m_notifyCache; // List of users found with ISON int m_checkTime; // Time elapsed while waiting for server 303 response int m_currentLag; QStringList m_inputBuffer; QList m_queues; // Stats used in QueueTuner int m_bytesSent, m_encodedBytesSent, m_linesSent, m_bytesReceived; QString m_nickname; QString m_loweredNickname; QString m_ownIpByUserhost; // RPL_USERHOST QString m_ownIpByWelcome; // RPL_WELCOME QList m_channelList; QHash m_loweredChannelNameHash; QList m_queryList; InputFilter m_inputFilter; Konversation::OutputFilter* m_outputFilter; QPointer m_statusView; QPointer m_rawLog; QPointer m_channelListPanel; bool m_away; QString m_awayReason; QString m_nonAwayNick; int m_awayTime; Konversation::ConnectionState m_connectionState; void updateConnectionState(Konversation::ConnectionState state); bool isSocketConnected() const; KProcess m_preShellCommand; private: void purgeData(); /// Recovers the filename from the dccArguments list from pos 0 to size-offset-1 /// joining with a space and cleans the filename using cleanDccFileName. /// The filename only needs to be recovered if it contains a space, in case /// it does not, the cleaned string at pos 0 is returned. /// "offset" states how many fixed arguments the dcc command has, where the /// filename is variable. For example "filename ip port filesize", offset is 3. inline QString recoverDccFileName(const QStringList& dccArguments, int offset) const; /// Cleans the filename from extra '"'. We just remove '"' if it is the first /// and last char, if the filename really contains a '"' it comes as two chars, /// escaped "\"", and is not affected. /// Some clients return the filename with multiple '"' around the filename /// but all we want is the plain filename. inline QString cleanDccFileName(const QString& filename) const; /// Checks if the port is in a valid range inline quint16 stringToPort(const QString &port, bool *ok = nullptr); /// Creates a list of known users and returns the one chosen by the user inline QString recipientNick() const; void collectStats(int bytes, int encodedBytes); /// Helper object to construct ISON (notify) list. ServerISON* m_serverISON; /// All nicks known to this server. Note this is NOT a list of all nicks on the server. /// Any nick appearing in this list is online, but may not necessarily appear in /// any of the joined or unjoined channel lists because a WHOIS has not yet been /// performed on the nick. NickInfoMap m_allNicks; /// List of membership lists for joined channels. A "joined" channel is a channel /// that user has joined, i.e., a tab appears for the channel in the main window. ChannelMembershipMap m_joinedChannels; /// List of membership lists for unjoined channels. These come from WHOIS responses. /// Note that this is NOT a list of all channels on the server, just those we are /// interested in because of nicks in the Nick Watch List. ChannelMembershipMap m_unjoinedChannels; /// List of nicks in Queries. NickInfoMap m_queryNicks; QString m_allowedChannelModes; int m_topicLength; // Blowfish key map QHash m_keyHash; bool m_identifyMsg; bool m_sslErrorLock; /// Used to lock incomingTimer while processing message. bool m_processingIncoming; /// Measures the lag between PING and PONG QTime m_lagTime; /// Updates the gui when the lag gets too high QTimer m_pingResponseTimer; /// Wait before sending the next PING QTimer m_pingSendTimer; /// Previous ISON reply of the server, needed for comparison with the next reply QStringList m_prevISONList; int m_capRequested; int m_capAnswered; bool m_capEndDelayed; QString m_lastAuthenticateCommand; ConnectionSettings m_connectionSettings; /// Used by ConnectionManager to schedule a reconnect; stopped by /disconnect /// and /quit. QTimer* m_delayedConnectTimer; bool m_reconnectImmediately; static int m_availableConnectionId; int m_connectionId; QPointer m_inviteDialog; QTimer* m_nickInfoChangedTimer; QTimer* m_channelNickChangedTimer; QStringList m_changedChannels; bool m_recreationScheduled; bool m_hasAwayNotify; bool m_hasExtendedJoin; bool m_hasWHOX; bool m_hasServerTime; bool m_hasUserHostInNames; UserModel *m_userModel; // WIPQTQUICK }; Q_DECLARE_OPERATORS_FOR_FLAGS(Server::CapModifiers) #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 97102987..4de7e93c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,1135 +1,1140 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005 Peter Simonsson Copyright (C) 2005 John Tapsell Copyright (C) 2005-2008 Eike Hein */ #include "mainwindow.h" #include "application.h" #include "settingsdialog.h" #include "viewcontainer.h" #include "statusbar.h" #include "bookmarkhandler.h" #include "trayicon.h" #include "serverlistdialog.h" #include "identitydialog.h" #include "notificationhandler.h" #include "irccharsets.h" #include "connectionmanager.h" #include "awaymanager.h" #include "transfermanager.h" #include "messagemodel.h" // WIPQTQUICK #include "usermodel.h" // WIPQTQUICK #include "identitymodel.h" // WIPQTQUICK #include "completer.h" // WIPQTQUICK #include "inputhistorymodel.h" // WIPQTQUICK #include "irccontextmenus.h" // WIPQTQUICK #include "qclipboardwrapper.h" // WIPQTQUICK #include #include #include #include #include #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // WIPQTQUICK #include // WIPQTQUICK MainWindow::MainWindow(bool raiseQtQuickUi, const QString& uiPackage) : KXmlGuiWindow(nullptr) // WIPQTQUICK { m_hasDirtySettings = false; m_closeApp = false; m_serverListDialog = nullptr; m_trayIcon = nullptr; m_settingsDialog = nullptr; // BEGIN: WIPQTQUICK m_uiStack = new QStackedWidget(this); setCentralWidget(m_uiStack); m_viewContainer = new ViewContainer(this); m_uiStack->addWidget(m_viewContainer->getWidget()); m_identityModel = new IdentityModel(this); m_messageModel = new MessageModel(this); m_filteredMessageModel = new FilteredMessageModel(this); m_filteredMessageModel->setSourceModel(m_messageModel); m_filteredUserModel = new FilteredUserModel(this); m_completer = new Completer(this); m_completer->setSourceModel(m_filteredUserModel); m_inputHistoryModel = new InputHistoryModel(this); m_filteredInputHistoryModel = new FilteredInputHistoryModel(this); m_filteredInputHistoryModel->setSourceModel(m_inputHistoryModel); // Filter on the new view. connect(m_viewContainer, &ViewContainer::viewChanged, this, [this](const QModelIndex &idx) { if (m_closeApp) { return; } m_filteredMessageModel->setFilterView(static_cast(idx.internalPointer())); m_filteredUserModel->setFilterView(static_cast(idx.internalPointer())); m_completer->setContextView(static_cast(idx.internalPointer())); } ); // Update filter when Viewcontainer resets. QObject::connect(m_viewContainer, &QAbstractItemModel::modelAboutToBeReset, this, [this]() { m_filteredMessageModel->setFilterView(nullptr); } ); KDescendantsProxyModel *viewListModel = new KDescendantsProxyModel(this); viewListModel->setSourceModel(m_viewContainer); qputenv("QT_QUICK_CONTROLS_STYLE", "org.kde.desktop"); m_qmlEngine = new QQmlApplicationEngine(this); + + // register common enums needed in QML + qRegisterMetaType("Konversation::ConnectionState"); // C++ -> QML signal + qmlRegisterUncreatableMetaObject(Konversation::staticMetaObject, "org.kde.konversation", 1, 0, "Konversation", "Enums only"); qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "MessageModel", ""); qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "InputHistoryModel", ""); qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "IrcContextMenus", ""); + + // setup qml context m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("konvApp"), Application::instance()); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("viewModel"), m_viewContainer); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("viewListModel"), viewListModel); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("messageModel"), m_filteredMessageModel); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("userModel"), m_filteredUserModel); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("identityModel"), m_identityModel); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("completer"), m_completer); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("inputHistoryModel"), m_filteredInputHistoryModel); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("contextMenus"), IrcContextMenus::self()); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("clipboard"), new QClipboardWrapper(this)); loadUiPackage(uiPackage, raiseQtQuickUi); // END: WIPQTQUICK //used for event compression. See header file for resetHasDirtySettings() connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(resetHasDirtySettings())); connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(updateTrayIcon())); // Set up view container connect(Application::instance(), SIGNAL(appearanceChanged()), m_viewContainer, SLOT(updateAppearance())); connect(Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), m_viewContainer, SLOT(updateViews(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(autoJoinToggled(Konversation::ServerGroupSettingsPtr)), Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(autoConnectOnStartupToggled(Konversation::ServerGroupSettingsPtr)), Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(setWindowCaption(QString)), this, SLOT(setCaption(QString))); - connect(Application::instance()->getConnectionManager(), - SIGNAL(connectionChangedState(Server*,Konversation::ConnectionState)), - m_viewContainer, SLOT(connectionStateChanged(Server*,Konversation::ConnectionState))); + connect(Application::instance()->getConnectionManager(), &ConnectionManager::connectionChangedState, + m_viewContainer, &ViewContainer::connectionStateChanged); connect(this, SIGNAL(triggerRememberLine()), m_viewContainer, SLOT(insertRememberLine())); connect(this, SIGNAL(triggerRememberLines(Server*)), m_viewContainer, SLOT(insertRememberLines(Server*))); connect(this, SIGNAL(cancelRememberLine()), m_viewContainer, SLOT(cancelRememberLine())); connect(this, SIGNAL(insertMarkerLine()), m_viewContainer, SLOT(insertMarkerLine())); // Set up status bar m_statusBar = new Konversation::StatusBar(this); connect(Application::instance(), SIGNAL(appearanceChanged()), m_statusBar, SLOT(updateAppearance())); createStandardStatusBarAction(); connect(m_viewContainer, SIGNAL(resetStatusBar()), m_statusBar, SLOT(resetStatusBar())); connect(m_viewContainer, SIGNAL(setStatusBarTempText(QString)), m_statusBar, SLOT(setMainLabelTempText(QString))); connect(m_viewContainer, SIGNAL(clearStatusBarTempText()), m_statusBar, SLOT(clearMainLabelTempText())); connect(m_viewContainer, SIGNAL(setStatusBarInfoLabel(QString)), m_statusBar, SLOT(updateInfoLabel(QString))); connect(m_viewContainer, SIGNAL(clearStatusBarInfoLabel()), m_statusBar, SLOT(clearInfoLabel())); connect(m_viewContainer, SIGNAL(setStatusBarLagLabelShown(bool)), m_statusBar, SLOT(setLagLabelShown(bool))); connect(m_viewContainer, SIGNAL(updateStatusBarLagLabel(Server*,int)), m_statusBar, SLOT(updateLagLabel(Server*,int))); connect(m_viewContainer, SIGNAL(resetStatusBarLagLabel(Server*)), m_statusBar, SLOT(resetLagLabel(Server*))); connect(m_viewContainer, SIGNAL(setStatusBarLagLabelTooLongLag(Server*,int)), m_statusBar, SLOT(setTooLongLag(Server*,int))); connect(m_viewContainer, SIGNAL(updateStatusBarSSLLabel(Server*)), m_statusBar, SLOT(updateSSLLabel(Server*))); connect(m_viewContainer, SIGNAL(removeStatusBarSSLLabel()), m_statusBar, SLOT(removeSSLLabel())); // Actions KStandardAction::quit(this,SLOT(quitProgram()),actionCollection()); m_showMenuBarAction = KStandardAction::showMenubar(this, SLOT(toggleMenubar()), actionCollection()); setStandardToolBarMenuEnabled(true); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); KStandardAction::keyBindings(this, SLOT(openKeyBindings()), actionCollection()); KStandardAction::preferences(this, SLOT(openPrefsDialog()), actionCollection()); KStandardAction::configureNotifications(this, SLOT(openNotifications()), actionCollection()); QAction* action; // WIPQTQUICK action=new QAction(this); action->setText(i18n("Toggle UIs")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F10"))); connect(action, &QAction::triggered, this, [this]() { m_uiStack->setCurrentIndex(m_uiStack->currentIndex() ? 0 : 1); setFocusProxy(m_uiStack->currentIndex() == 1 ? m_uiStack->currentWidget() : nullptr); } ); actionCollection()->addAction(QStringLiteral("toggle_ui"), action); action=new QAction(this); action->setText(i18n("Restart")); action->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"))); action->setStatusTip(i18n("Quit and restart the application")); connect(action, SIGNAL(triggered()), Application::instance(), SLOT(restart())); actionCollection()->addAction(QStringLiteral("restart"), action); action=new QAction(this); action->setText(i18n("&Server List...")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-server"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("F2"))); action->setStatusTip(i18n("Manage networks and servers")); connect(action, SIGNAL(triggered()), SLOT(openServerList())); actionCollection()->addAction(QStringLiteral("open_server_list"), action); action=new QAction(this); action->setText(i18n("Quick &Connect...")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-connect"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F7"))); action->setStatusTip(i18n("Type in the address of a new IRC server to connect to")); connect(action, SIGNAL(triggered()), SLOT(openQuickConnectDialog())); actionCollection()->addAction(QStringLiteral("quick_connect_dialog"), action); action=new QAction(this); action->setText(i18n("&Reconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); action->setEnabled(false); action->setStatusTip(i18n("Reconnect to the current server.")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(reconnectFrontServer())); actionCollection()->addAction(QStringLiteral("reconnect_server"), action); action=new QAction(this); action->setText(i18n("&Disconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-disconnect"))); action->setEnabled(false); action->setStatusTip(i18n("Disconnect from the current server.")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(disconnectFrontServer())); actionCollection()->addAction(QStringLiteral("disconnect_server"), action); action=new QAction(this); action->setText(i18n("&Identities...")); action->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F8"))); action->setStatusTip(i18n("Manage your nick, away and other identity settings")); connect(action, SIGNAL(triggered()), SLOT(openIdentitiesDialog())); actionCollection()->addAction(QStringLiteral("identities_dialog"), action); action=new KToggleAction(this); action->setText(i18n("&Watched Nicks")); action->setIcon(QIcon::fromTheme(QStringLiteral("im-user"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F4"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openNicksOnlinePanel())); actionCollection()->addAction(QStringLiteral("open_nicksonline_window"), action); action=new KToggleAction(this); action->setText(i18n("&DCC Status")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right-double"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F9"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleDccPanel())); actionCollection()->addAction(QStringLiteral("open_dccstatus_window"), action); action=new QAction(this); action->setText(i18n("&Open Logfile")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+O"))); action->setEnabled(false); action->setStatusTip(i18n("Open the known history for this channel in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openLogFile())); actionCollection()->addAction(QStringLiteral("open_logfile"), action); action=new QAction(this); action->setText(i18n("&Channel Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); action->setEnabled(false); action->setStatusTip(i18n("Open the channel settings dialog for this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openChannelSettings())); actionCollection()->addAction(QStringLiteral("channel_settings"), action); action=new KToggleAction(this); action->setText(i18n("Channel &List")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F5"))); action->setEnabled(false); action->setStatusTip(i18n("Show a list of all the known channels on this server")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openChannelList())); actionCollection()->addAction(QStringLiteral("open_channel_list"), action); action=new KToggleAction(this); action->setText(i18n("&URL Catcher")); action->setIcon(QIcon::fromTheme(QStringLiteral("text-html"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("F6"))); action->setStatusTip(i18n("List all URLs that have been mentioned recently in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(addUrlCatcher())); actionCollection()->addAction(QStringLiteral("open_url_catcher"), action); if (KAuthorized::authorize(QStringLiteral("shell_access"))) { action=new QAction(this); action->setText(i18n("New &Konsole")); action->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); action->setStatusTip(i18n("Open a terminal in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(addKonsolePanel())); actionCollection()->addAction(QStringLiteral("open_konsole"), action); } // Actions to navigate through the different pages QList nextShortcut = KStandardShortcut::tabNext(); QList prevShortcut = KStandardShortcut::tabPrev(); QString nextIcon, prevIcon; if (QApplication::isRightToLeft()) { prevShortcut.append(QKeySequence(QStringLiteral("Alt+Right"))); nextShortcut.append(QKeySequence(QStringLiteral("Alt+Left"))); nextIcon=QStringLiteral("go-previous-view"); prevIcon=QStringLiteral("go-next-view"); } else { nextShortcut.append(QKeySequence(QStringLiteral("Alt+Right"))); prevShortcut.append(QKeySequence(QStringLiteral("Alt+Left"))); nextIcon=QStringLiteral("go-next-view"); prevIcon=QStringLiteral("go-previous-view"); } action=new QAction(this); action->setText(i18n("&Next Tab")); action->setIcon(QIcon::fromTheme(nextIcon)); actionCollection()->setDefaultShortcuts(action,nextShortcut); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showNextView())); actionCollection()->addAction(QStringLiteral("next_tab"), action); action=new QAction(this); action->setText(i18n("&Previous Tab")); action->setIcon(QIcon::fromTheme(prevIcon)); actionCollection()->setDefaultShortcuts(action, prevShortcut); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showPreviousView())); actionCollection()->addAction(QStringLiteral("previous_tab"), action); action=new QAction(this); action->setText(i18n("Close &Tab")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+w"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(closeCurrentView())); actionCollection()->addAction(QStringLiteral("close_tab"), action); action=new QAction(this); action->setText(i18n("Last Focused Tab")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Space"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showLastFocusedView())); actionCollection()->addAction(QStringLiteral("last_focused_tab"), action); action=new QAction(this); action->setText(i18n("Next Active Tab")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Alt+Space"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showNextActiveView())); actionCollection()->addAction(QStringLiteral("next_active_tab"), action); KGlobalAccel::setGlobalShortcut(action, QList()); if (Preferences::self()->tabPlacement()==Preferences::Left) { action=new QAction(this); action->setText(i18n("Move Tab Up")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); action=new QAction(this); action->setText(i18n("Move Tab Down")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } else { if (QApplication::isRightToLeft()) { action=new QAction(this); action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action=new QAction(this); action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } else { action=new QAction(this); action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action=new QAction(this); action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } } action->setEnabled(false); action=new QAction(this); action->setText(i18n("Rejoin Channel")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(rejoinChannel())); actionCollection()->addAction(QStringLiteral("rejoin_channel"), action); action->setEnabled(false); action=new KToggleAction(this); action->setText(i18n("Enable Notifications")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleViewNotifications())); actionCollection()->addAction(QStringLiteral("tab_notifications"), action); action->setEnabled(false); action=new KToggleAction(this); action->setText(i18n("Join on Connect")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleAutoJoin())); actionCollection()->addAction(QStringLiteral("tab_autojoin"), action); action=new KToggleAction(this); action->setText(i18n("Connect at Startup")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleConnectOnStartup())); actionCollection()->addAction(QStringLiteral("tab_autoconnect"), action); QStringList encodingDescs = Konversation::IRCCharsets::self()->availableEncodingDescriptiveNames(); encodingDescs.prepend(i18n("Default")); KSelectAction* selectAction = new KSelectAction(this); selectAction->setEditable(false); selectAction->setItems(encodingDescs); selectAction->setEnabled(false); selectAction->setText(i18n("Set Encoding")); selectAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); connect(selectAction, SIGNAL(triggered(int)), m_viewContainer, SLOT(changeViewCharset(int))); actionCollection()->addAction(QStringLiteral("tab_encoding"), selectAction); QSignalMapper* tabSelectionMapper = new QSignalMapper(this); connect(tabSelectionMapper, SIGNAL(mapped(int)), m_viewContainer, SLOT(goToView(int))); for (uint i = 1; i <= 10; ++i) { action=new QAction(this); action->setText(i18n("Go to Tab %1",i)); actionCollection()->setDefaultShortcut(action,QKeySequence(QString(QStringLiteral("Alt+%1")).arg(i%10))); connect(action, SIGNAL(triggered()), tabSelectionMapper, SLOT(map())); actionCollection()->addAction(QString(QStringLiteral("go_to_tab_%1")).arg(i), action); tabSelectionMapper->setMapping(action, i-1); } action=new QAction(this); action->setText(i18n("Clear &Marker Lines")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Shift+R"))); action->setEnabled(false); action->setStatusTip(i18n("Clear marker lines in the current tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearViewLines())); actionCollection()->addAction(QStringLiteral("clear_lines"), action); action=new QAction(this); action->setText(i18n("Enlarge Font Size")); actionCollection()->setDefaultShortcuts(action, KStandardShortcut::zoomIn()); action->setEnabled(false); action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); action->setStatusTip(i18n("Increase the current font size")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(zoomIn())); actionCollection()->addAction(QStringLiteral("increase_font"), action); action=new QAction(this); action->setText(i18n("Reset Font Size")); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Ctrl+0"))); action->setEnabled(false); action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); action->setStatusTip(i18n("Reset the current font size to settings values")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(resetFont())); actionCollection()->addAction(QStringLiteral("reset_font"), action); action=new QAction(this); action->setText(i18n("Decrease Font Size")); actionCollection()->setDefaultShortcuts(action, KStandardShortcut::zoomOut()); action->setEnabled(false); action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); action->setStatusTip(i18n("Decrease the current font size")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(zoomOut())); actionCollection()->addAction(QStringLiteral("shrink_font"), action); action=new QAction(this); action->setText(i18n("&Clear Window")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+L"))); action->setEnabled(false); action->setStatusTip(i18n("Clear the contents of the current tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearView())); actionCollection()->addAction(QStringLiteral("clear_window"), action); action=new QAction(this); action->setText(i18n("Clear &All Windows")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Shift+L"))); action->setEnabled(false); action->setStatusTip(i18n("Clear the contents of all open tabs")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearAllViews())); actionCollection()->addAction(QStringLiteral("clear_tabs"), action); KToggleAction* awayAction = new KToggleAction(this); awayAction->setText(i18n("Global Away")); actionCollection()->setDefaultShortcut(awayAction,QKeySequence(QStringLiteral("Ctrl+Shift+A"))); awayAction->setEnabled(false); awayAction->setIcon(QIcon::fromTheme(QStringLiteral("im-user-away"))); connect(awayAction, SIGNAL(triggered(bool)), Application::instance()->getAwayManager(), SLOT(setGlobalAway(bool))); actionCollection()->addAction(QStringLiteral("toggle_away"), awayAction); action=new QAction(this); action->setText(i18n("&Join Channel...")); action->setIcon(QIcon::fromTheme(QStringLiteral("irc-join-channel"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+J"))); action->setEnabled(false); action->setStatusTip(i18n("Join a new channel on this server")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showJoinChannelDialog())); actionCollection()->addAction(QStringLiteral("join_channel"), action); action = KStandardAction::find(m_viewContainer, SLOT(findText()), actionCollection()); action->setEnabled(false); action = KStandardAction::findNext(m_viewContainer, SLOT(findNextText()), actionCollection()); action->setEnabled(false); action = KStandardAction::findPrev(m_viewContainer, SLOT(findPrevText()), actionCollection()); action->setEnabled(false); action=new QAction(this); action->setText(i18n("&IRC Color...")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-text-color"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+K"))); action->setEnabled(false); action->setStatusTip(i18n("Set the color of your current IRC message")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertIRCColor())); actionCollection()->addAction(QStringLiteral("irc_colors"), action); action=new QAction(this); action->setText(i18n("&Marker Line")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+R"))); action->setEnabled(false); action->setStatusTip(i18n("Insert a horizontal line into the current tab that only you can see")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertMarkerLine())); actionCollection()->addAction(QStringLiteral("insert_marker_line"), action); action=new QAction(this); action->setText(i18n("Special &Character...")); action->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Shift+C"))); action->setEnabled(false); action->setStatusTip(i18n("Insert any character into your current IRC message")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertCharacter())); actionCollection()->addAction(QStringLiteral("insert_character"), action); action=new QAction(this); action->setText(i18n("Auto Replace")); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(doAutoReplace())); actionCollection()->addAction(QStringLiteral("auto_replace"), action); action=new QAction(this); action->setText(i18n("Focus Input Box")); actionCollection()->setDefaultShortcut(action,QKeySequence(Qt::Key_Escape)); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(focusInputBox())); actionCollection()->addAction(QStringLiteral("focus_input_box"), action); action=new QAction(this); action->setText(i18n("Close &All Open Queries")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F11"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(closeQueries())); actionCollection()->addAction(QStringLiteral("close_queries"), action); KToggleAction* toggleChannelNickListsAction = new KToggleAction(this); if (Preferences::self()->showNickList()) toggleChannelNickListsAction->setChecked(true); toggleChannelNickListsAction->setText(i18n("Show Nicklist")); actionCollection()->setDefaultShortcut(toggleChannelNickListsAction, QKeySequence(QStringLiteral("Ctrl+H"))); connect(toggleChannelNickListsAction, SIGNAL(triggered()), m_viewContainer, SLOT(toggleChannelNicklists())); actionCollection()->addAction(QStringLiteral("hide_nicknamelist"), toggleChannelNickListsAction); action=new QAction(this); action->setText(i18n("Show/Hide Konversation")); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); actionCollection()->addAction(QStringLiteral("toggle_mainwindow_visibility"), action); KGlobalAccel::setGlobalShortcut(action, QList()); action=new KToggleAction(this); action->setEnabled(true); action->setChecked(Preferences::self()->useOSD()); action->setText(i18n("Enable On Screen Display")); action->setIcon(QIcon::fromTheme(QStringLiteral("video-display"))); connect(action, SIGNAL(triggered(bool)), Preferences::self(), SLOT(slotSetUseOSD(bool))); actionCollection()->addAction(QStringLiteral("toggle_osd"), action); // Bookmarks action=new QAction(this); action->setText(i18n("Bookmarks")); QMenu *menu = new QMenu(this); action->setMenu(menu); new KonviBookmarkHandler(menu, this); actionCollection()->addAction(QStringLiteral("bookmarks") , action); // decide whether to show the tray icon or not updateTrayIcon(); createGUI(); setAutoSaveSettings(); // BEGIN WIPQTQUICK if (m_qmlEngine) { // Hide menubar by default. m_showMenuBarAction->setChecked(false); } else { // Apply menubar show/hide pref m_showMenuBarAction->setChecked(Preferences::self()->showMenuBar()); } toggleMenubar(true); statusBar()->hide(); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(showMenuBar(bool)), this, SLOT(showMenubar(bool))); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(openLegacyConfigDialog()), this, SLOT(openPrefsDialog())); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(setStatusBarTempText(QString)), m_statusBar, SLOT(setMainLabelTempText(QString))); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(clearStatusBarTempText()), m_statusBar, SLOT(clearMainLabelTempText())); // END WIPQTQUICK if (Preferences::self()->useNotify() && Preferences::self()->openWatchedNicksAtStartup()) m_viewContainer->openNicksOnlinePanel(); } MainWindow::~MainWindow() = default; bool MainWindow::loadUiPackage(const QString &packageName, bool raise) { if (packageName.isEmpty()) { qDebug() << "Error loading UI package: Package name is empty. Doing nothing."; return false; } QLatin1Literal packageNamePrefix("org.kde.konversation.uipackages."); QString fixedName(packageName.startsWith(packageNamePrefix) ? packageName : packageNamePrefix + packageName); KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Konversation/UiPackage"), fixedName); if (!p.isValid()) { qDebug() << "Error loading UI package: Package" << packageName << "is invalid."; qDebug() << "Available Qt Quick UI packages for Konversation (name / id):"; auto plist = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Konversation/UiPackage")); for (const auto &pkg : plist) { qDebug() << " " << pkg.name() << "/" << pkg.pluginId(); } return false; } if (m_qmlEngine->rootObjects().count()) { qDebug() << "Unloading current UI package:" << m_currentUiPackage; // Keep track of the current UI. if (m_uiStack->currentIndex() == 1) { raise = true; } // Close the window. static_cast(m_qmlEngine->rootObjects().first())->close(); QWidget *quickWindowContainer = m_uiStack->widget(1); // Remove the window container widget from the stack. m_uiStack->removeWidget(quickWindowContainer); // Reparent and hide the container. quickWindowContainer->setParent(nullptr); quickWindowContainer->close(); // Delete window container. delete quickWindowContainer; // Delete the root object. qDeleteAll(m_qmlEngine->rootObjects()); // Clear the component cache. m_qmlEngine->clearComponentCache(); } m_qmlEngine->load(QUrl::fromLocalFile(p.filePath("window"))); QWidget *container = QWidget::createWindowContainer(static_cast(m_qmlEngine->rootObjects().first()), this); container->setFocusPolicy(Qt::NoFocus); m_uiStack->addWidget(container); m_currentUiPackage = fixedName; if (raise) { qDebug() << "Raising Qt Quick UI ..."; m_uiStack->setCurrentIndex(1); setFocusProxy(m_uiStack->currentWidget()); } return true; } bool MainWindow::reloadUiPackage() { if (m_currentUiPackage.isEmpty()) { qDebug() << "Error reloading UI package: Currently no UI package loaded."; return false; } return loadUiPackage(m_currentUiPackage); } QSize MainWindow::sizeHint() const { return QSize(700, 500); // Give the app a sane default size } int MainWindow::confirmQuit() { Application* konvApp = Application::instance(); if (konvApp->getConnectionManager()->connectionCount() == 0) return KMessageBox::Continue; int result = KMessageBox::Cancel; if (!KMessageBox::shouldBeShownContinue(QStringLiteral("systemtrayquitKonversation")) && konvApp->getDccTransferManager()->hasActiveTransfers()) { result = KMessageBox::warningContinueCancel( this, i18n("You have active DCC file transfers. Are you sure you want to quit Konversation?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QStringLiteral("QuitWithActiveDccTransfers")); } else { result = KMessageBox::warningContinueCancel( this, i18n("Are you sure you want to quit Konversation?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QStringLiteral("systemtrayquitKonversation")); } if (result != KMessageBox::Continue) konvApp->abortScheduledRestart(); return result; } void MainWindow::activateAndRaiseWindow() { if (isMinimized()) KWindowSystem::unminimizeWindow(winId()); else if (Preferences::self()->showTrayIcon() && !isVisible()) m_trayIcon->restore(); KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(winId()); } void MainWindow::quitProgram() { if (Preferences::self()->showTrayIcon() && sender() != m_trayIcon && confirmQuit() == KMessageBox::Cancel) return; // will call queryClose() m_closeApp = true; m_messageModel->clear(); // WIPQTQUICK close(); } bool MainWindow::queryClose() { Application* konvApp = Application::instance(); if (!konvApp->isSavingSession()) { if (sender() == m_trayIcon) m_closeApp = true; if (Preferences::self()->showTrayIcon() && !m_closeApp) { bool doit = KMessageBox::warningContinueCancel(this, i18n("

Closing the main window will keep Konversation running in the system tray. " "Use Quit from the Konversation menu to quit the application.

"), i18n("Docking in System Tray"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String("HideOnCloseInfo")) == KMessageBox::Continue; if (doit) hide(); return false; } if (!Preferences::self()->showTrayIcon() && confirmQuit() == KMessageBox::Cancel) return false; } konvApp->prepareShutdown(); return true; } void MainWindow::hideEvent(QHideEvent *e) { emit triggerRememberLine(); m_statusBar->clearMainLabelTempText(); KXmlGuiWindow::hideEvent(e); } void MainWindow::showEvent(QShowEvent *e) { emit cancelRememberLine(); KXmlGuiWindow::showEvent(e); } void MainWindow::leaveEvent(QEvent* e) { m_statusBar->clearMainLabelTempText(); KXmlGuiWindow::leaveEvent(e); } bool MainWindow::event(QEvent* e) { if (e->type() == QEvent::StyleChange) { QMetaObject::invokeMethod(Application::instance(), "appearanceChanged"); } else if (e->type() == QEvent::WindowActivate) { emit endNotification(); emit cancelRememberLine(); if (m_uiStack->currentIndex() == 1) { m_uiStack->currentWidget()->setFocus(); QMetaObject::invokeMethod(m_qmlEngine->rootObjects().first(), "requestActivate", Qt::QueuedConnection); } } else if(e->type() == QEvent::WindowDeactivate) { m_statusBar->clearMainLabelTempText(); if (qApp->activeModalWidget() == nullptr) emit triggerRememberLine(); } return KXmlGuiWindow::event(e); } void MainWindow::settingsChangedSlot() { // This is for compressing the events. m_hasDirtySettings is set to true // when the settings have changed, then set to false when the app reacts to it // via the appearanceChanged signal. This prevents a series of settingsChanged signals // causing the app expensively rereading its settings many times. // The appearanceChanged signal is connected to resetHasDirtySettings to reset this bool if (!m_hasDirtySettings) { QTimer::singleShot(0, Application::instance(), SIGNAL(appearanceChanged())); m_hasDirtySettings = true; } } void MainWindow::resetHasDirtySettings() { m_hasDirtySettings = false; } void MainWindow::updateTrayIcon() { if (Preferences::self()->showTrayIcon()) { if (!m_trayIcon) { // set up system tray m_trayIcon = new Konversation::TrayIcon(this); connect(this, SIGNAL(endNotification()), m_trayIcon, SLOT(endNotification())); connect(KIconLoader::global(), SIGNAL(iconChanged(int)), m_trayIcon, SLOT(updateAppearance())); QMenu *trayMenu = qobject_cast(m_trayIcon->contextMenu()); trayMenu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Preferences)))); trayMenu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::ConfigureNotifications)))); trayMenu->addAction(actionCollection()->action(QStringLiteral("toggle_away"))); } m_trayIcon->setNotificationEnabled(Preferences::self()->trayNotify()); } else { delete m_trayIcon; m_trayIcon = nullptr; } } void MainWindow::toggleMenubar(bool dontShowWarning) { if (m_showMenuBarAction->isChecked()) menuBar()->show(); else { bool doit = true; if (!dontShowWarning) { QString accel = m_showMenuBarAction->shortcut().toString(); doit = KMessageBox::warningContinueCancel(this, i18n("This will hide the menu bar completely. You can show it again by typing %1.", accel), i18n("Hide menu bar"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String("HideMenuBarWarning")) == KMessageBox::Continue; } if (doit) menuBar()->hide(); else m_showMenuBarAction->setChecked (true); } Preferences::self()->setShowMenuBar(m_showMenuBarAction->isChecked()); } void MainWindow::showMenubar(bool show) { Q_UNUSED(show) m_showMenuBarAction->setChecked(show); toggleMenubar(true /* dontShowWarning */); } void MainWindow::focusAndShowErrorMessage(const QString &errorMsg) { show(); KWindowSystem::demandAttention(winId()); KWindowSystem::activateWindow(winId()); KMessageBox::error(this, errorMsg); } void MainWindow::openPrefsDialog() { //An instance of your dialog could be already created and could be cached, //in which case you want to display the cached dialog instead of creating //another one if (!m_settingsDialog) { m_settingsDialog = new KonviSettingsDialog(this); //User edited the configuration - update your local copies of the //configuration data connect(m_settingsDialog, SIGNAL(settingsChanged(QString)), this, SLOT(settingsChangedSlot())); } m_settingsDialog->show(); } void MainWindow::openKeyBindings() { // Change a number of action names to make them friendlier for the shortcut list. actionCollection()->action(QStringLiteral("tab_notifications"))->setText(i18n("Toggle Notifications")); actionCollection()->action(QStringLiteral("toggle_away"))->setText(i18n("Toggle Away Globally")); actionCollection()->action(QStringLiteral("irc_colors"))->setText(i18n("Insert &IRC Color...")); actionCollection()->action(QStringLiteral("insert_character"))->setText(i18n("Insert Special &Character...")); actionCollection()->action(QStringLiteral("insert_marker_line"))->setText(i18n("Insert &Marker Line")); QString openChannelListString = actionCollection()->action(QStringLiteral("open_channel_list"))->text(); actionCollection()->action(QStringLiteral("open_channel_list"))->setText(i18n("&Channel List")); QString openLogFileString = actionCollection()->action(QStringLiteral("open_logfile"))->text(); actionCollection()->action(QStringLiteral("open_logfile"))->setText(i18n("&Open Logfile")); // Open shortcut configuration dialog. KShortcutsDialog::configure(actionCollection()); // Reset action names. actionCollection()->action(QStringLiteral("tab_notifications"))->setText(i18n("Enable Notifications")); actionCollection()->action(QStringLiteral("toggle_away"))->setText(i18n("Set &Away Globally")); actionCollection()->action(QStringLiteral("irc_colors"))->setText(i18n("&IRC Color...")); actionCollection()->action(QStringLiteral("insert_character"))->setText(i18n("Special &Character...")); actionCollection()->action(QStringLiteral("insert_marker_line"))->setText(i18n("&Marker Line")); actionCollection()->action(QStringLiteral("open_channel_list"))->setText(openChannelListString); actionCollection()->action(QStringLiteral("open_logfile"))->setText(openLogFileString); } void MainWindow::openServerList() { if (!m_serverListDialog) { m_serverListDialog = new Konversation::ServerListDialog(i18n("Server List"), this); Application* konvApp = Application::instance(); connect(m_serverListDialog, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), konvApp, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(konvApp, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), m_serverListDialog, SLOT(updateServerList())); connect(m_serverListDialog, SIGNAL(connectTo(Konversation::ConnectionFlag,int)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,int))); connect(m_serverListDialog, SIGNAL(connectTo(Konversation::ConnectionFlag,ConnectionSettings)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,ConnectionSettings))); connect(konvApp->getConnectionManager(), SIGNAL(closeServerList()), m_serverListDialog, SLOT(reject())); } m_serverListDialog->show(); } void MainWindow::openQuickConnectDialog() { emit showQuickConnectDialog(); } void MainWindow::openIdentitiesDialog() { QPointer dlg = new Konversation::IdentityDialog(this); if (dlg->exec() == QDialog::Accepted) { if (m_serverListDialog) m_serverListDialog->updateServerList(); m_viewContainer->updateViewEncoding(m_viewContainer->getFrontView()); } delete dlg; } IdentityPtr MainWindow::editIdentity(IdentityPtr identity) { IdentityPtr newIdentity; QPointer dlg = new Konversation::IdentityDialog(this); newIdentity = dlg->setCurrentIdentity(identity); if ((dlg->exec() == QDialog::Accepted) && m_serverListDialog) { m_serverListDialog->updateServerList(); delete dlg; return newIdentity; } else { delete dlg; return IdentityPtr(); } } void MainWindow::openNotifications() { (void) KNotifyConfigWidget::configure(this); } void MainWindow::notifyAction(int connectionId, const QString& nick) { Application* konvApp = Application::instance(); Server* server = konvApp->getConnectionManager()->getServerByConnectionId(connectionId); if (server) server->notifyAction(nick); } // TODO: Let an own class handle notify things void MainWindow::setOnlineList(Server* notifyServer,const QStringList& /*list*/, bool /*changed*/) { emit nicksNowOnline(notifyServer); // FIXME if (changed && nicksOnlinePanel) newText(nicksOnlinePanel, QString::null, true); } void MainWindow::toggleVisibility() { if (isActiveWindow()) { if (Preferences::self()->showTrayIcon()) hide(); else KWindowSystem::minimizeWindow(winId()); } else { activateAndRaiseWindow(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index ec1c18c5..3d1e8897 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,164 +1,165 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005 Peter Simonsson Copyright (C) 2005 John Tapsell Copyright (C) 2005-2008 Eike Hein */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include "common.h" #include "preferences.h" #include "ssllabel.h" +#include "server.h" #include #include class QQmlApplicationEngine; // WIPQTQUICK class QStackedWidget; // WIPQTQUICK class KToggleAction; class Server; class KonviSettingsDialog; class ViewContainer; class MessageModel; // WIPQTQUICK class FilteredMessageModel; // WIPQTQUICK class FilteredUserModel; // WIPQTQUICK class IdentityModel; // WIPQTQUICK class Completer; // WIPQTQUICK class InputHistoryModel; // WIPQTQUICK class FilteredInputHistoryModel; // WIPQTQUICK namespace Konversation { class ServerListDialog; class TrayIcon; class StatusBar; } class MainWindow : public KXmlGuiWindow { Q_OBJECT public: MainWindow(bool raiseQtQuickUi, const QString& uiPackage = QStringLiteral("default")); // WIPQTQUICK ~MainWindow() override; ViewContainer* getViewContainer() { return m_viewContainer; } Konversation::TrayIcon* systemTrayIcon() const { return m_trayIcon; } MessageModel *getMessageModel() { return m_messageModel; } // WIPQTQUICK FilteredMessageModel *getFilteredMessageModel() { return m_filteredMessageModel; } // WIPQTQUICK FilteredUserModel *getFilteredUserModel() { return m_filteredUserModel; } // WIPQTQUICK InputHistoryModel *getInputHistoryModel() { return m_inputHistoryModel; } // WIPQTQUICK FilteredInputHistoryModel *getFilteredInputHistoryModel() { return m_filteredInputHistoryModel; } // WIPQTQUICK } bool loadUiPackage(const QString &packageName, bool raise = false); // WIPQTQUICK bool reloadUiPackage(); // WIPQTQUICK /** Some errors need to be shown, even when konversation is minimized. */ void focusAndShowErrorMessage(const QString &errorMsg); Q_SIGNALS: void showQuickConnectDialog(); void nicksNowOnline(Server*); void endNotification(); void serverStateChanged(Server* server, Konversation::ConnectionState state); void triggerRememberLine(); void triggerRememberLines(Server*); void cancelRememberLine(); void insertMarkerLine(); public Q_SLOTS: void activateAndRaiseWindow(); void quitProgram(); void updateTrayIcon(); void openServerList(); void openIdentitiesDialog(); IdentityPtr editIdentity(IdentityPtr identity); void setOnlineList(Server* notifyServer,const QStringList& list, bool changed); protected Q_SLOTS: /** This is connected to the preferences settingsChanged signal and acts to compress * multiple successively settingsChanged() signals into a single output * appearanceChanged() signal. * * Do not connect to the settingsChanged signal elsewhere. If you want to know when * the settings have changed, connect to: * KonversationApplication::instance(), SIGNAL(appearanceChanged()) */ void settingsChangedSlot(); /** This is connected to the appearanceChanged signal. * @see settingsChangedSlot() */ void resetHasDirtySettings(); void toggleMenubar(bool dontShowWarning = false); void showMenubar(bool show); // WIPQTQUICK void openPrefsDialog(); void openKeyBindings(); void openQuickConnectDialog(); // it seems that moc does not honor #ifs in compile so we create an // empty slot in our .cpp file rather than #if this slot out void openNotifications(); void notifyAction(int connectionId,const QString& nick); void toggleVisibility(); void showEvent(QShowEvent* e) Q_DECL_OVERRIDE; void hideEvent(QHideEvent* e) Q_DECL_OVERRIDE; void leaveEvent(QEvent* e) Q_DECL_OVERRIDE; protected: QSize sizeHint() const Q_DECL_OVERRIDE; int confirmQuit(); bool queryClose() Q_DECL_OVERRIDE; bool event(QEvent* e) Q_DECL_OVERRIDE; ViewContainer* m_viewContainer; Konversation::StatusBar* m_statusBar; Konversation::TrayIcon* m_trayIcon; KToggleAction* m_showMenuBarAction; KonviSettingsDialog *m_settingsDialog; Konversation::ServerListDialog* m_serverListDialog; MessageModel *m_messageModel; // WIPQTQUICK FilteredMessageModel *m_filteredMessageModel; // WIPQTQUICK FilteredUserModel *m_filteredUserModel; // WIPQTQUICK IdentityModel *m_identityModel; Completer *m_completer; // WIPQTQUICK InputHistoryModel *m_inputHistoryModel; FilteredInputHistoryModel *m_filteredInputHistoryModel; // WIPQTQUICK QQmlApplicationEngine *m_qmlEngine; // WIPQTQUICK QString m_currentUiPackage; // WIPQTQUICK QStackedWidget *m_uiStack; // WIPQTQUICK /** @see settingsChangedSlot() */ bool m_hasDirtySettings; bool m_closeApp; }; #endif /* MAINWINDOW_H */ diff --git a/src/qtquick/uipackages/default/contents/ViewSwitcher.qml b/src/qtquick/uipackages/default/contents/ViewSwitcher.qml index 9b502693..8cf1d8c4 100644 --- a/src/qtquick/uipackages/default/contents/ViewSwitcher.qml +++ b/src/qtquick/uipackages/default/contents/ViewSwitcher.qml @@ -1,251 +1,280 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2017 Eike Hein */ import QtQuick 2.7 import QtQuick.Controls 2.2 as QQC2 import org.kde.kirigami 2.2 as Kirigami +import org.kde.konversation 1.0 + Item { id: viewSwitcher readonly property string activityColor: "#008000" // WIPQTQUICK TODO Hard-coded green because Breeze' positiveTextColor is ugly. readonly property string mentionColor: "red" // WIPQTQUICK TODO Hard-coded red because Breeze' negativeTextColor is ugly. QQC2.ScrollView { anchors.fill: parent ListView { id: viewListView QQC2.ScrollBar.vertical: QQC2.ScrollBar { id: verticalScrollbar } clip: true model: viewListModel cacheBuffer: Math.max(1000 * Kirigami.Units.devicePixelRatio, viewSwitcher.height) function showView(view) { viewModel.showView(view); if (!konvUi.pageStack.wideMode) { if (konvUi.inputField) { konvUi.inputField.forceActiveFocus(); } konvUi.pageStack.currentIndex = 1; } } onContentYChanged: Qt.callLater(updateOffViewportOverlay); function updateOffViewportOverlay() { var topActivity = false; var topMention = false; var bottomActivity = false; var bottomMention = false; if (visibleArea.heightRatio == 1.0) { return; } var delegates = new Array(); // WIPQTQUICK HACK Skip over non-delegate children. for (var i = 0; i < contentItem.children.length; ++i) { var item = contentItem.children[i]; if (item.row != undefined) { delegates.push(item); } } delegates.sort(function(a, b) { return ((a.row < b.row) ? -1 : ((a.row > b.row) ? 1 : 0)); }); var mapped = mapToItem(contentItem, 0, 0); var firstIndex = Math.max(0, indexAt(0, mapped.y)); if (firstIndex) { for (var i = 0; i < firstIndex; ++i) { var item = delegates[i]; if (item.unreadMentions) { topMention = true; break; } else if (item.hasActivity) { topActivity = true; } } } mapped = mapToItem(contentItem, 0, height - 1); var lastIndex = indexAt(0, mapped.y); if (lastIndex != -1 && lastIndex < (count - 1)) { for (var i = lastIndex + 1; i < count; ++i) { var item = delegates[i]; if (item.unreadMentions) { bottomMention = true; break; } else if (item.hasActivity) { bottomActivity = true; } } } if (topMention) { topOverlay.mention = true; topOverlay.visible = true; } else if (topActivity) { topOverlay.mention = false; topOverlay.visible = true; } else { topOverlay.visible = false; } if (bottomMention) { bottomOverlay.mention = true; bottomOverlay.visible = true; } else if (bottomActivity) { bottomOverlay.mention = false; bottomOverlay.visible = true; } else { bottomOverlay.visible = false; } } delegate: ListItem { id: viewListItem width: viewListView.width readonly property int row: index readonly property bool hasActivity: model.HasActivity readonly property int unreadMentions: model.ViewRole.unreadMentions + property int connectionState: model.ViewRole ? model.ViewRole.server.connectionState : Konversation.NeverConnected property Item unreadMentionsCounter: null + property Item loadingBar: null + text: model.display textMarginLeft: model.IsChild ? Kirigami.Units.gridUnit * 2 : Kirigami.Units.gridUnit textMarginRight: unreadMentionsCounter ? (width - unreadMentionsCounter.x) + Kirigami.Units.smallSpacing : 0 textColor: model.HasActivity ? viewSwitcher.activityColor : Kirigami.Theme.textColor onTextColorChanged: Qt.callLater(ListView.view.updateOffViewportOverlay) onUnreadMentionsChanged: { if (!unreadMentions && unreadMentionsCounter) { unreadMentionsCounter.destroy(); unreadMentionsCounter = null; } else if (!unreadMentionsCounter) { unreadMentionsCounter = unreadMentionsCounterComponent.createObject(viewListItem); } Qt.callLater(ListView.view.updateOffViewportOverlay); } + onConnectionStateChanged: { + if (!loadingBar && connectionState == Konversation.Connecting) { + loadingBar = loadingBarComponent.createObject(viewListItem); + } else if (loadingBar) { + loadingBar.destroy(); + loadingBar = null; + } + } + onClicked: { viewListView.forceActiveFocus(); if (mouse.button == Qt.RightButton) { viewModel.showViewContextMenu(viewModel.indexForView(model.ViewRole), mapToGlobal(mouse.x, mouse.y)); } else { viewListView.showView(value); } } + Component { + id: loadingBarComponent + + QQC2.BusyIndicator { + id: loadingAnimator + running: true + anchors.right: parent.right + // we assume there won't be intersection with showing busy indicator and unread count + anchors.rightMargin: verticalScrollbar.visible ? verticalScrollbar.width : Kirigami.Units.smallSpacing + anchors.verticalCenter: parent.verticalCenter + height: Kirigami.Units.iconSizes.smallMedium + width: Kirigami.Units.iconSizes.smallMedium + } + } + Component { id: unreadMentionsCounterComponent Rectangle { id: unreadMentionsCounter anchors.right: parent.right anchors.rightMargin: verticalScrollbar.visible ? verticalScrollbar.width : Kirigami.Units.smallSpacing anchors.verticalCenter: parent.verticalCenter height: Math.floor(unreadMentionsCounterText.paintedHeight) width: Math.floor(unreadMentionsCounterText.paintedWidth) + Kirigami.Units.smallSpacing color: viewSwitcher.mentionColor radius: (Kirigami.Units.devicePixelRatio * 2) Text { id: unreadMentionsCounterText anchors.centerIn: parent renderType: Text.NativeRendering textFormat: Text.PlainText color: "white" font.pixelSize: konvUi.largerFontSize font.weight: Font.Bold font.hintingPreference: Font.PreferFullHinting horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: viewListItem.unreadMentions } } } } Keys.onUpPressed: { event.accept = true; viewModel.showPreviousView(); } Keys.onDownPressed: { event.accept = true; viewModel.showNextView(); } Component.onCompleted: sidebar.viewListView = viewListView } } Rectangle { id: topOverlay visible: false anchors.top: parent.top width: parent.width height: Math.ceil(Kirigami.Units.devicePixelRatio) property bool mention: false color: mention ? viewSwitcher.mentionColor : viewSwitcher.activityColor } Rectangle { id: bottomOverlay visible: false anchors.bottom: parent.bottom width: parent.width height: Math.ceil(Kirigami.Units.devicePixelRatio) property bool mention: false color: mention ? viewSwitcher.mentionColor : viewSwitcher.activityColor } } diff --git a/src/viewer/chatwindow.cpp b/src/viewer/chatwindow.cpp index f8de59b1..2102b808 100644 --- a/src/viewer/chatwindow.cpp +++ b/src/viewer/chatwindow.cpp @@ -1,797 +1,798 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2006-2008 Eike Hein */ #include "chatwindow.h" #include "channel.h" #include "query.h" #include "ircview.h" #include "ircinput.h" #include "server.h" #include "application.h" #include "logfilereader.h" #include "viewcontainer.h" #include #include #include #include #include #include #include ChatWindow::ChatWindow(QWidget* parent) : QWidget(parent) { QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setMargin(margin()); mainLayout->setSpacing(spacing()); setName("ChatWindowObject"); setTextView(nullptr); setInputBar(nullptr); firstLog = true; m_server = nullptr; m_recreationScheduled = false; m_isTopLevelView = true; m_notificationsEnabled = true; m_channelEncodingSupported = false; m_currentTabNotify = Konversation::tnfNone; m_unreadMentions = 0; } ChatWindow::~ChatWindow() { if (getInputBar() && getServer()) { const QString& language = getInputBar()->spellCheckingLanguage(); if (!language.isEmpty()) { Konversation::ServerGroupSettingsPtr serverGroup = getServer()->getConnectionSettings().serverGroup(); if (serverGroup) Preferences::setSpellCheckingLanguage(serverGroup, getName(), language); else Preferences::setSpellCheckingLanguage(getServer()->getDisplayName(), getName(), language); } } emit closing(this); m_server=nullptr; } void ChatWindow::childEvent(QChildEvent* event) { if(event->type() == QChildEvent::ChildAdded) { if(event->child()->isWidgetType()) { layout()->addWidget(qobject_cast< QWidget* >(event->child())); } } else if(event->type() == QChildEvent::ChildRemoved) { if(event->child()->isWidgetType()) { layout()->removeWidget(qobject_cast(event->child())); } } } // reimplement this if your window needs special close treatment bool ChatWindow::closeYourself(bool /* askForConfirmation */) { deleteLater(); return true; } void ChatWindow::cycle() { m_recreationScheduled = true; closeYourself(false); } void ChatWindow::updateAppearance() { if (getTextView()) getTextView()->updateAppearance(); // The font size of the KTabWidget container may be inappropriately // small due to the "Tab bar" font size setting. setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } void ChatWindow::setName(const QString& newName) { if (name != newName) { name=newName; emit nameChanged(this,newName); emit titleChanged(); } } QString ChatWindow::getName() const { return name; } QString ChatWindow::getTitle() const { QString title; if (getType() == Channel) { title = QString("%1 (%2)") .arg(getName()) .arg(getServer()->getDisplayName()); } else { title = getName(); } return title; } QString ChatWindow::getDescription() const { return QString(); } QString ChatWindow::getURI(bool passNetwork) { QString protocol; QString url; QString port; QString server; QString channel; if (getServer()->getUseSSL()) protocol = "ircs://"; else protocol = "irc://"; if (getType() == Channel) channel = getName().replace(QRegExp("^#"), QString()); if (passNetwork) { server = getServer()->getDisplayName(); QUrl test(protocol+server); // QUrl (ultimately used by the bookmark system, which is the // primary consumer here) doesn't like spaces in hostnames as // well as other things which are possible in user-chosen net- // work names, so let's fall back to the hostname if we can't // get the network name by it. if (!test.isValid()) passNetwork = false; } if (!passNetwork) { server = getServer()->getServerName(); port = ':'+QString::number(getServer()->getPort()); } if (server.contains(':')) // IPv6 server = '['+server+']'; url = protocol+server+port+'/'+channel; return url; } IrcContextMenus::MenuOptions ChatWindow::contextMenuOptions() const { return IrcContextMenus::NoOptions; } void ChatWindow::setType(WindowType newType) { type=newType; } ChatWindow::WindowType ChatWindow::getType() const { return type; } bool ChatWindow::isTopLevelView() const { return m_isTopLevelView; } void ChatWindow::setServer(Server* newServer) { if (!newServer) { - qDebug() << "ChatWindow::setServer(0)!"; + qDebug() << "ChatWindow::setServer(nullptr)!"; } else if (m_server != newServer) { - m_server=newServer; + m_server = newServer; + emit serverChanged(); connect(m_server, &Server::serverOnline, this, &ChatWindow::serverOnline); // check if we need to set up the signals if(getType() != ChannelList) { if(textView) textView->setServer(newServer); - else qDebug() << "textView==0!"; + else qDebug() << "textView == nullptr!"; } serverOnline(m_server->isConnected()); emit titleChanged(); } if (getInputBar()) { QString language; Konversation::ServerGroupSettingsPtr serverGroup = newServer->getConnectionSettings().serverGroup(); if (serverGroup) language = Preferences::spellCheckingLanguage(serverGroup, getName()); else language = Preferences::spellCheckingLanguage(newServer->getDisplayName(), getName()); if (!language.isEmpty()) getInputBar()->setSpellCheckingLanguage(language); } } Server* ChatWindow::getServer() const { return m_server; } void ChatWindow::serverOnline(bool /* state */) { //emit online(this,state); } void ChatWindow::textPasted(const QString& text) { if (getServer()) { QStringList multiline = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); for(int index=0;indexcommandChar()); // make sure that lines starting with command char get escaped if(line.startsWith(cChar)) line=cChar+line; sendText(line); } } } void ChatWindow::setTextView(IRCView* newView) { textView = newView; if(!textView) { return; } textView->setVerticalScrollBarPolicy(Preferences::self()->showIRCViewScrollBar() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); textView->setChatWin(this); connect(textView, &IRCView::textToLog, this, &ChatWindow::logText); connect(textView, &IRCView::setStatusBarTempText, this, &ChatWindow::setStatusBarTempText); connect(textView, &IRCView::clearStatusBarTempText, this, &ChatWindow::clearStatusBarTempText); } void ChatWindow::appendRaw(const QString& message, bool self) { if(!textView) return; textView->appendRaw(message, self); } void ChatWindow::appendLog(const QString& message) { if(!textView) return; textView->appendLog(message); } void ChatWindow::append(const QString& nickname, const QString& message, const QHash &messageTags, const QString& label) { if(!textView) return; textView->append(nickname, message, messageTags, label); } void ChatWindow::appendQuery(const QString& nickname, const QString& message, const QHash &messageTags, bool inChannel) { if(!textView) return ; textView->appendQuery(nickname, message, messageTags, inChannel); } void ChatWindow::appendAction(const QString& nickname, const QString& message, const QHash &messageTags) { if(!textView) return; if (getType() == Query || getType() == DccChat) textView->appendQueryAction(nickname, message, messageTags); else textView->appendChannelAction(nickname, message, messageTags); } void ChatWindow::appendServerMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { if(!textView) return ; textView->appendServerMessage(type, message, messageTags, parseURL); } void ChatWindow::appendCommandMessage(const QString& command, const QString& message, const QHash &messageTags, bool parseURL, bool self) { if(!textView) return ; textView->appendCommandMessage(command, message, messageTags, parseURL, self); } void ChatWindow::appendBacklogMessage(const QString& firstColumn,const QString& message) { if(!textView) return ; textView->appendBacklogMessage(firstColumn,Konversation::sterilizeUnicode(message)); } void ChatWindow::clear() { if (!textView) return; textView->clear(); resetTabNotification(); if (m_server) m_server->getViewContainer()->unsetViewNotification(this); } void ChatWindow::cdIntoLogPath() { QString home = KUser(KUser::UseRealUserID).homeDir(); QUrl logUrl = Preferences::self()->logfilePath(); if(!logUrl.isLocalFile()) { return; } QString logPath = logUrl.toLocalFile(); QDir logDir(home); // Try to "cd" into the logfile path. if (!logDir.cd(logPath)) { // Only create log path if logging is enabled. if (log()) { // Try to create the logfile path and "cd" into it again. logDir.mkpath(logPath); logDir.cd(logPath); } } // Add the logfile name to the path. logfile.setFileName(logDir.path() + '/' + logName); } void ChatWindow::setLogfileName(const QString& name) { // Only change name of logfile if the window was new. if(firstLog) { if (getTextView()) getTextView()->setContextMenuOptions(IrcContextMenus::ShowLogAction, true); // status panels get special treatment here, since they have no server at the beginning if (getType() == Status || getType() == DccChat) { logName = name + ".log"; } else if (m_server) { // make sure that no path delimiters are in the name logName = QString(m_server->getDisplayName().toLower()).append('_').append(name).append(".log").replace('/','_'); } // load backlog to show if(Preferences::self()->showBacklog()) { // "cd" into log path or create path, if it's not there cdIntoLogPath(); // Show last log lines. This idea was stole ... um ... inspired by PMP :) // Don't do this for the server status windows, though if((getType() != Status) && logfile.open(QIODevice::ReadOnly)) { qint64 filePosition; QString backlogLine; QTextStream backlog(&logfile); backlog.setCodec(QTextCodec::codecForName("UTF-8")); backlog.setAutoDetectUnicode(true); QStringList firstColumns; QStringList messages; int offset = 0; qint64 lastPacketHeadPosition = backlog.device()->size(); const unsigned int packetSize = 4096; while(messages.count() < Preferences::self()->backlogLines() && backlog.device()->size() > packetSize * offset) { QStringList firstColumnsInPacket; QStringList messagesInPacket; // packetSize * offset < size <= packetSize * ( offset + 1 ) // Check if the log is bigger than packetSize * ( offset + 1 ) if(backlog.device()->size() > packetSize * ( offset + 1 )) { // Set file pointer to the packet size above the offset backlog.seek(backlog.device()->size() - packetSize * ( offset + 1 )); // Skip first line, since it may be incomplete backlog.readLine(); } else { // Set file pointer to the head // Qt 4.5 Doc: Note that when using a QTextStream on a // QFile, calling reset() on the QFile will not have the // expected result because QTextStream buffers the file. // Use the QTextStream::seek() function instead. // backlog.device()->reset(); backlog.seek( 0 ); } // remember actual file position to check for deadlocks filePosition = backlog.pos(); qint64 currentPacketHeadPosition = filePosition; // Loop until end of file reached while(!backlog.atEnd() && filePosition < lastPacketHeadPosition) { backlogLine = backlog.readLine(); // check for deadlocks if(backlog.pos() == filePosition) { backlog.seek(filePosition + 1); } // if a tab character is present in the line, meaning it is a valid chatline if (backlogLine.contains('\t')) { // extract first column from log QString backlogFirst = backlogLine.left(backlogLine.indexOf('\t')); // cut first column from line backlogLine = backlogLine.mid(backlogLine.indexOf('\t') + 1); // Logfile is in utf8 so we don't need to do encoding stuff here // append backlog with time and first column to text view firstColumnsInPacket << backlogFirst; messagesInPacket << backlogLine; } // remember actual file position to check for deadlocks filePosition = backlog.pos(); } // while // remember the position not to read the same lines again lastPacketHeadPosition = currentPacketHeadPosition; ++offset; firstColumns = firstColumnsInPacket + firstColumns; messages = messagesInPacket + messages; } backlog.setDevice(nullptr); logfile.close(); // trim int surplus = messages.count() - Preferences::self()->backlogLines(); // "surplus" can be a minus value. (when the backlog is too short) if(surplus > 0) { for(int i = 0 ; i < surplus ; ++i) { firstColumns.pop_front(); messages.pop_front(); } } QStringList::Iterator itFirstColumn = firstColumns.begin(); QStringList::Iterator itMessage = messages.begin(); for( ; itFirstColumn != firstColumns.end() ; ++itFirstColumn, ++itMessage ) appendBacklogMessage(*itFirstColumn, *itMessage); } } // if(Preferences::showBacklog()) } } void ChatWindow::logText(const QString& text) { if(log()) { // "cd" into log path or create path, if it's not there cdIntoLogPath(); if(logfile.open(QIODevice::WriteOnly | QIODevice::Append)) { // wrap the file into a stream QTextStream logStream(&logfile); // write log in utf8 to help i18n logStream.setCodec(QTextCodec::codecForName("UTF-8")); logStream.setAutoDetectUnicode(true); if(firstLog) { QString intro(i18n("\n*** Logfile started\n*** on %1\n\n", QDateTime::currentDateTime().toString())); logStream << intro; firstLog=false; } QDateTime dateTime = QDateTime::currentDateTime(); QString logLine(QString("[%1] [%2] %3\n").arg(QLocale().toString(dateTime.date(), QLocale::LongFormat)). arg(QLocale().toString(dateTime.time(), QLocale::LongFormat)).arg(text)); logStream << logLine; // detach stream from file logStream.setDevice(nullptr); // close file logfile.close(); } else qWarning() << "open(QIODevice::Append) for " << logfile.fileName() << " failed!"; } } void ChatWindow::setChannelEncodingSupported(bool enabled) { m_channelEncodingSupported = enabled; } bool ChatWindow::isChannelEncodingSupported() const { return m_channelEncodingSupported; } int ChatWindow::spacing() { if(Preferences::self()->useSpacing()) return Preferences::self()->spacing(); else return style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical); } int ChatWindow::margin() { if(Preferences::self()->useSpacing()) return Preferences::self()->margin(); else return 0; } // Accessors IRCView* ChatWindow::getTextView() const { return textView; } bool ChatWindow::log() { return Preferences::self()->log(); } // reimplement this in all panels that have user input QString ChatWindow::getTextInLine() { if (m_inputBar) return m_inputBar->toPlainText(); else return QString(); } bool ChatWindow::canBeFrontView() { return false; } bool ChatWindow::searchView() { return false; } // reimplement this in all panels that have user input void ChatWindow::indicateAway(bool) { } // reimplement this in all panels that have user input void ChatWindow::appendInputText(const QString& text, bool fromCursor) { if (!fromCursor) m_inputBar->append(text); else { const int position = m_inputBar->textCursor().position(); m_inputBar->textCursor().insertText(text); QTextCursor cursor = m_inputBar->textCursor(); cursor.setPosition(position + text.length()); m_inputBar->setTextCursor(cursor); } } bool ChatWindow::eventFilter(QObject* watched, QEvent* e) { if(e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); bool scrollMod = (Preferences::self()->useMultiRowInputBox() ? false : (ke->modifiers() == Qt::ShiftModifier)); if(ke->key() == Qt::Key_Up && scrollMod) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() - sbar->singleStep()); } return true; } else if(ke->key() == Qt::Key_Down && scrollMod) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() + sbar->singleStep()); } return true; } else if(ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_PageUp) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() - sbar->pageStep()); } return true; } else if(ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_PageDown) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() + sbar->pageStep()); } return true; } } return QWidget::eventFilter(watched, e); } void ChatWindow::adjustFocus() { childAdjustFocus(); } void ChatWindow::emitUpdateInfo() { QString info = getName(); emit updateInfo(info); } QColor ChatWindow::highlightColor() { return getTextView()->highlightColor(); } void ChatWindow::activateTabNotification(Konversation::TabNotifyType type) { if (!notificationsEnabled()) return; if (type < Konversation::tnfNormal && (!m_server || m_server->getViewContainer()->getFrontView() != this)) { ++m_unreadMentions; emit unreadMentionsChanged(); } if(type > m_currentTabNotify) return; m_currentTabNotify = type; emit updateTabNotification(this,type); } void ChatWindow::resetTabNotification() { m_currentTabNotify = Konversation::tnfNone; m_unreadMentions = 0; emit unreadMentionsChanged(); } void ChatWindow::msgHelper(const QString& recipient, const QString& message) { // A helper method for handling the 'msg' and 'query' (with a message // payload) commands. When the user uses either, we show a visualiza- // tion of what he/she has sent in the form of '<-> target> message>' // in the chat view of the tab the command was issued in, as well as // add the resulting message to the target view (if present), in that // order. The order is especially important as the origin and target // views may be the same, and the two messages may thus appear toge- // ther and should be sensibly ordered. if (recipient.isEmpty() || message.isEmpty()) return; bool isAction = false; QString result = message; QString visualization; if (result.startsWith(Preferences::self()->commandChar() + "me")) { isAction = true; result = result.mid(4); visualization = QString("* %1 %2").arg(m_server->getNickname()).arg(result); } else visualization = result; appendQuery(recipient, visualization, QHash(), true); if (!getServer()) return; ::Query* query = m_server->getQueryByName(recipient); if (query) { if (isAction) query->appendAction(m_server->getNickname(), result); else query->appendQuery(m_server->getNickname(), result); return; } ::Channel* channel = m_server->getChannelByName(recipient); if (channel) { if (isAction) channel->appendAction(m_server->getNickname(), result); else channel->append(m_server->getNickname(), result); } } void ChatWindow::activateView() { for(QWidget* widget = this; widget; widget = widget->parentWidget()) { if (widget->window()) { widget->show(); widget->activateWindow(); widget->raise(); } } } diff --git a/src/viewer/chatwindow.h b/src/viewer/chatwindow.h index 584d7c94..2d3c6ffd 100644 --- a/src/viewer/chatwindow.h +++ b/src/viewer/chatwindow.h @@ -1,246 +1,248 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2006-2008 Eike Hein */ #ifndef CHATWINDOW_H #define CHATWINDOW_H #include "identity.h" #include "common.h" #include "irccontextmenus.h" // WIPQTQUICK #include #include #include class IRCView; class IRCInput; class Server; class ChatWindow : public QWidget { Q_OBJECT Q_PROPERTY(QString name READ getName NOTIFY nameChanged) Q_PROPERTY(QString title READ getTitle NOTIFY titleChanged) Q_PROPERTY(QString description READ getDescription NOTIFY descriptionChanged) Q_PROPERTY(IrcContextMenus::MenuOptions contextMenuOptions READ contextMenuOptions CONSTANT) Q_PROPERTY(int unreadMentions READ unreadMentions NOTIFY unreadMentionsChanged) + Q_PROPERTY(Server * server READ getServer NOTIFY serverChanged) public: explicit ChatWindow(QWidget* parent); ~ChatWindow() override; enum WindowType { Status=0, Channel, Query, DccChat, DccTransferPanel, RawLog, Notice, SNotice, ChannelList, Konsole, UrlCatcher, NicksOnline, LogFileReader }; /** Clean up and close this tab. Return false if you want to cancel the close. */ virtual bool closeYourself(bool askForConfirmation = true); virtual void cycle(); /** This should be called and set with a non-null server as soon * as possibly after ChatWindow is created. * @param newServer The server to set it to. */ virtual void setServer(Server* newServer); /** This should be called if setServer is not called - e.g. * in the case of konsolepanel. This should be set as soon * as possible after creation. */ /** Get the server this is linked to. * @return The server it is associated with, or null if none. */ Server* getServer() const; void setTextView(IRCView* newView); IRCView* getTextView() const; void setInputBar(IRCInput* newInputBar) { m_inputBar = newInputBar; } IRCInput* getInputBar() { return m_inputBar; } virtual bool log(); // WIPQTQUICK Holy does all of this mess need cleaning some day QString getName() const; QString getTitle() const; virtual QString getDescription() const; QString getURI(bool passNetwork = true); virtual IrcContextMenus::MenuOptions contextMenuOptions() const; int unreadMentions() const { return m_unreadMentions; } void setType(WindowType newType); WindowType getType() const; virtual bool isTopLevelView() const; Q_INVOKABLE virtual void sendText(const QString& /*text*/) {} Q_INVOKABLE virtual void textPasted(const QString& text); virtual void append(const QString& nickname,const QString& message, const QHash &messageTags, const QString& label = QString()); virtual void appendRaw(const QString& message, bool self = false); virtual void appendLog(const QString& message); virtual void appendQuery(const QString& nickname,const QString& message, const QHash &messageTags = QHash(), bool inChannel = false); virtual void appendAction(const QString& nickname,const QString& message, const QHash &messageTags = QHash()); virtual void appendServerMessage(const QString& type,const QString& message, const QHash &messageTags = QHash(), bool parseURL = true); virtual void appendCommandMessage(const QString& command, const QString& message, const QHash &messageTags = QHash(), bool parseURL = true, bool self = false); virtual void appendBacklogMessage(const QString& firstColumn,const QString& message); void clear(); virtual QString getTextInLine(); /** Reimplement this to return true in all classes that /can/ become front view. */ virtual bool canBeFrontView(); /** Reimplement this to return true in all classes that you can search in - i.e. use "Edit->Find Text" in. */ virtual bool searchView(); virtual bool notificationsEnabled() { return m_notificationsEnabled; } bool eventFilter(QObject* watched, QEvent* e) Q_DECL_OVERRIDE; QString logFileName() { return logfile.fileName(); } virtual void setChannelEncoding(const QString& /* encoding */) {} virtual QString getChannelEncoding() { return QString(); } virtual QString getChannelEncodingDefaultDesc() { return QString(); } bool isChannelEncodingSupported() const; /** Force updateInfo(info) to be emitted. * Useful for when this tab has just gained focus */ virtual void emitUpdateInfo(); /** child classes have to override this and return true if they want the * "insert character" item on the menu to be enabled. */ virtual bool isInsertSupported() { return m_inputBar != nullptr; } /** child classes have to override this and return true if they want the * "irc color" item on the menu to be enabled. */ virtual bool areIRCColorsSupported() {return false; } Konversation::TabNotifyType currentTabNotification() const { return m_currentTabNotify; } QColor highlightColor(); void msgHelper(const QString& recipient, const QString& message); void setMargin(int margin) { layout()->setMargin(margin); } void setSpacing(int spacing) { layout()->setSpacing(spacing); } void activateView(); Q_SIGNALS: void nameChanged(ChatWindow* view, const QString& newName); void titleChanged(); void descriptionChanged(); + void serverChanged(); //void online(ChatWindow* myself, bool state); /** Emit this signal when you want to change the status bar text for this tab. * It is ignored if this tab isn't focused. */ void updateInfo(const QString &info); void updateTabNotification(ChatWindow* chatWin, const Konversation::TabNotifyType& type); void unreadMentionsChanged() const; void setStatusBarTempText(const QString&); void clearStatusBarTempText(); void closing(ChatWindow* myself); void showView(QObject* myself); // WIPQTQUICK public Q_SLOTS: void updateAppearance(); void logText(const QString& text); /** * This is called when a chat window gains focus. * It enables and disables the appropriate menu items, * then calls childAdjustFocus. * You can call this manually to focus this tab. */ void adjustFocus(); virtual void appendInputText(const QString& text, bool fromCursor); virtual void indicateAway(bool away); virtual void setNotificationsEnabled(bool enable) { m_notificationsEnabled = enable; } void activateTabNotification(Konversation::TabNotifyType type); void resetTabNotification(); protected Q_SLOTS: ///Used to disable functions when not connected virtual void serverOnline(bool online); protected: void childEvent(QChildEvent* event) Q_DECL_OVERRIDE; /** Some children may handle the name themselves, and not want this public. * Increase the visibility in the subclass if you want outsiders to call this. * The name is the string that is shown in the tab. * @param newName The name to show in the tab */ virtual void setName(const QString& newName); /** Called from adjustFocus */ virtual void childAdjustFocus() = 0; void setLogfileName(const QString& name); void setChannelEncodingSupported(bool enabled); void cdIntoLogPath(); int spacing(); int margin(); bool firstLog; QString name; QString logName; QFont font; IRCView* textView; /** A pointer to the server this chatwindow is part of. * Not always non-null - e.g. for konsolepanel */ IRCInput* m_inputBar; Server* m_server; QFile logfile; WindowType type; bool m_isTopLevelView; bool m_notificationsEnabled; bool m_channelEncodingSupported; Konversation::TabNotifyType m_currentTabNotify; int m_unreadMentions; bool m_recreationScheduled; }; #endif diff --git a/src/viewer/viewcontainer.cpp b/src/viewer/viewcontainer.cpp index 4b42bf13..1d9e47a3 100644 --- a/src/viewer/viewcontainer.cpp +++ b/src/viewer/viewcontainer.cpp @@ -1,3128 +1,3130 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2006-2008 Eike Hein */ #include "viewcontainer.h" #include "connectionmanager.h" #include "queuetuner.h" #include "application.h" #include "notificationhandler.h" #include "images.h" #include "irccharsets.h" #include "ircview.h" #include "ircinput.h" #include "logfilereader.h" #include "konsolepanel.h" #include "urlcatcher.h" #include "transferpanel.h" #include "transfermanager.h" #include "chatcontainer.h" #include "statuspanel.h" #include "channel.h" #include "query.h" #include "rawlog.h" #include "channellistpanel.h" #include "nicksonline.h" #include "insertchardialog.h" #include "irccolorchooser.h" #include "joinchanneldialog.h" #include "servergroupsettings.h" #include "irccontextmenus.h" #include "viewtree.h" #include "viewspringloader.h" #include "nicklistview.h" // WIPQTQUICK #include "messagemodel.h" // WIPQTQUICK #include "inputhistorymodel.h" // WIPQTQUICK #include #include #include #include #include // WIPQTQUICK #include #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; ViewMimeData::ViewMimeData(ChatWindow *view) : QMimeData() , m_view(view) { if (view) { setData("application/x-konversation-chatwindow", view->getName().toUtf8()); } } ViewMimeData::~ViewMimeData() = default; ChatWindow* ViewMimeData::view() const { return m_view; } TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent) { } TabWidget::~TabWidget() = default; void TabWidget::contextMenuEvent(QContextMenuEvent* event) { event->accept(); QPoint pos = event->globalPos(); int tabIndex = tabBar()->tabAt(tabBar()->mapFromGlobal(pos)); if (tabIndex != -1) { emit contextMenu(widget(tabIndex), pos); } } void TabWidget::mouseReleaseEvent(QMouseEvent* event) { if(event->button() == Qt::MiddleButton) { event->accept(); QPoint pos = event->globalPos(); int tabIndex = tabBar()->tabAt(tabBar()->mapFromGlobal(pos)); if(tabIndex != -1) { emit tabBarMiddleClicked(tabIndex); } } QTabWidget::mouseReleaseEvent(event); } ViewContainer::ViewContainer(MainWindow* window) : QAbstractItemModel(window) , m_window(window) , m_tabWidget(nullptr) , m_viewTree(nullptr) , m_vbox(nullptr) , m_queueTuner(nullptr) , m_urlCatcherPanel(nullptr) , m_nicksOnlinePanel(nullptr) , m_dccPanel(nullptr) , m_insertCharDialog(nullptr) , m_queryViewCount(0) , m_dataChangedLock(false) { m_viewSpringLoader = new ViewSpringLoader(this); images = Application::instance()->images(); m_viewTreeSplitter = new QSplitter(m_window); m_viewTreeSplitter->setObjectName("view_tree_splitter"); m_saveSplitterSizesLock = true; // The tree needs to be initialized before the tab widget so that it // may assume a leading role in view selection management. if (Preferences::self()->tabPlacement()==Preferences::Left) setupViewTree(); setupTabWidget(); initializeSplitterSizes(); m_dccPanel = new DCC::TransferPanel(m_tabWidget); m_dccPanel->hide(); m_dccPanelOpen = false; connect(m_dccPanel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); // Pre-construct context menus for better responsiveness when then // user opens them the first time. This is optional; the IrcContext- // Menus API would work fine without doing this here. // IrcContextMenus' setup code calls Application::instance(), and // ViewContainer is constructed in the scope of the Application // constructor, so to avoid a crash we need to queue. QMetaObject::invokeMethod(this, "setupIrcContextMenus", Qt::QueuedConnection); } ViewContainer::~ViewContainer() = default; QHash ViewContainer::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("DataRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } void ViewContainer::setupIrcContextMenus() { IrcContextMenus::self(); } void ViewContainer::showQueueTuner(bool p) { if (p) m_queueTuner->open(); else m_queueTuner->close(); } ///Use this instead of setting m_frontServer directly so we can emit the frontServerChanging signal easily. void ViewContainer::setFrontServer(Server* newserver) { if (m_frontServer == QPointer(newserver)) return; emit frontServerChanging(newserver); m_frontServer = newserver; } void ViewContainer::prepareShutdown() { if (!m_tabWidget) return; deleteDccPanel(); closeNicksOnlinePanel(); for (int i = 0; i < m_tabWidget->count(); ++i) m_tabWidget->widget(i)->blockSignals(true); m_tabWidget->blockSignals(true); m_tabWidget = nullptr; } void ViewContainer::initializeSplitterSizes() { if (m_viewTree && !m_viewTree->isHidden()) { QList sizes = Preferences::self()->treeSplitterSizes(); if (sizes.isEmpty()) sizes << 145 << (m_window->width() - 145); // FIXME: Make DPI-aware. m_viewTreeSplitter->setSizes(sizes); m_saveSplitterSizesLock = false; } } void ViewContainer::saveSplitterSizes() { if (!m_saveSplitterSizesLock) { Preferences::self()->setTreeSplitterSizes(m_viewTreeSplitter->sizes()); m_saveSplitterSizesLock = false; } } void ViewContainer::setupTabWidget() { m_popupViewIndex = -1; m_vbox = new QWidget(m_viewTreeSplitter); QVBoxLayout* vboxLayout = new QVBoxLayout(m_vbox); vboxLayout->setMargin(0); m_viewTreeSplitter->setStretchFactor(m_viewTreeSplitter->indexOf(m_vbox), 1); m_vbox->setObjectName("main_window_right_side"); m_tabWidget = new TabWidget(m_vbox); vboxLayout->addWidget(m_tabWidget); m_tabWidget->setObjectName("main_window_tab_widget"); m_viewSpringLoader->addWidget(m_tabWidget->tabBar()); m_queueTuner = new QueueTuner(m_vbox, this); vboxLayout->addWidget(m_queueTuner); m_queueTuner->hide(); m_tabWidget->setMovable(true); m_tabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); m_vbox->hide(); QToolButton* closeBtn = new QToolButton(m_tabWidget); closeBtn->setIcon(SmallIcon("tab-close")); closeBtn->adjustSize(); m_tabWidget->setCornerWidget(closeBtn, Qt::BottomRightCorner); connect(closeBtn, SIGNAL(clicked()), this, SLOT(closeCurrentView())); connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT (viewSwitched(int))); connect(m_tabWidget->tabBar(), SIGNAL(tabCloseRequested(int)), this, SLOT(closeView(int))); connect(m_tabWidget, SIGNAL(contextMenu(QWidget*,QPoint)), this, SLOT(showViewContextMenu(QWidget*,QPoint))); connect(m_tabWidget, SIGNAL(tabBarMiddleClicked(int)), this, SLOT(closeViewMiddleClick(int))); updateTabWidgetAppearance(); } void ViewContainer::resetFont() { m_frontView->getTextView()->resetFontSize(); } void ViewContainer::zoomIn() { if (m_frontView->getTextView()) m_frontView->getTextView()->increaseFontSize(); } void ViewContainer::zoomOut() { if (m_frontView->getTextView()) m_frontView->getTextView()->decreaseFontSize(); } void ViewContainer::setupViewTree() { unclutterTabs(); m_viewTree = new ViewTree(m_viewTreeSplitter); m_viewTree->setModel(this); m_viewTreeSplitter->insertWidget(0, m_viewTree); m_viewTreeSplitter->setStretchFactor(m_viewTreeSplitter->indexOf(m_viewTree), 0); m_viewSpringLoader->addWidget(m_viewTree->viewport()); if (m_tabWidget) { m_viewTree->selectView(indexForView(static_cast(m_tabWidget->currentWidget()))); setViewTreeShown(m_tabWidget->count()); } else { setViewTreeShown(false); } connect(m_viewTree, SIGNAL(sizeChanged()), this, SLOT(saveSplitterSizes())); connect(m_viewTree, SIGNAL(showView(QObject*)), this, SLOT(showView(QObject*))); // WIPQTQUICK connect(m_viewTree, SIGNAL(closeView(ChatWindow*)), this, SLOT(closeView(ChatWindow*))); connect(m_viewTree, SIGNAL(showViewContextMenu(QModelIndex,QPoint)), this, SLOT(showViewContextMenu(QModelIndex,QPoint))); connect(m_viewTree, SIGNAL(destroyed(QObject*)), this, SLOT(onViewTreeDestroyed(QObject*))); connect(this, SIGNAL(contextMenuClosed()), m_viewTree->viewport(), SLOT(update())); connect(Application::instance(), SIGNAL(appearanceChanged()), m_viewTree, SLOT(updateAppearance())); connect(this, SIGNAL(viewChanged(QModelIndex)), m_viewTree, SLOT(selectView(QModelIndex))); QAction* action; action = actionCollection()->action("move_tab_left"); if (action) { action->setText(i18n("Move Tab Up")); action->setIcon(QIcon::fromTheme("arrow-up")); } action = actionCollection()->action("move_tab_right"); if (action) { action->setText(i18n("Move Tab Down")); action->setIcon(QIcon::fromTheme("arrow-down")); } } void ViewContainer::onViewTreeDestroyed(QObject* object) { Q_UNUSED(object) setViewTreeShown(false); } void ViewContainer::setViewTreeShown(bool show) { if (m_viewTree) { if (!show) { m_saveSplitterSizesLock = true; m_viewTree->hide(); } else { m_viewTree->show(); initializeSplitterSizes(); m_saveSplitterSizesLock = false; } } } void ViewContainer::removeViewTree() { QAction* action; action = actionCollection()->action("move_tab_left"); if (action) { action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme("arrow-left")); } action = actionCollection()->action("move_tab_right"); if (action) { action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme("arrow-right")); } delete m_viewTree; m_viewTree = nullptr; } int ViewContainer::rowCount(const QModelIndex& parent) const { if (!m_tabWidget) { return 0; } if (parent.isValid()) { ChatWindow* statusView = static_cast(parent.internalPointer()); if (!statusView) { return 0; } int count = 0; for (int i = m_tabWidget->indexOf(statusView) + 1; i < m_tabWidget->count(); ++i) { const ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view != statusView && view->getServer() && view->getServer()->getStatusView() == statusView) { ++count; } if (view->isTopLevelView()) { break; } } return count; } else { int count = 0; for (int i = 0; i < m_tabWidget->count(); ++i) { const ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->isTopLevelView()) { ++count; } } return count; } } int ViewContainer::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QModelIndex ViewContainer::index(int row, int column, const QModelIndex& parent) const { if (!m_tabWidget || column != 0) { + // we have only one column return QModelIndex(); } int tabIndex = -1; - if (parent.isValid()) { + // This looks like one of server tabs int parentTabIndex = m_tabWidget->indexOf(static_cast(parent.internalPointer())); - if (parentTabIndex != -1) { tabIndex = parentTabIndex + row + 1; } else { return QModelIndex(); } } else { + // We don't have parent, this may be top-level view int count = -1; - for (int i = 0; i < m_tabWidget->count(); ++i) { - if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { + // There can be more than one connected server, we should + // find correct top-level window for this row + auto window = static_cast(m_tabWidget->widget(i)); + if (window->isTopLevelView()) { ++count; } if (count == row) { tabIndex = i; - break; } } } if (tabIndex == -1) { return QModelIndex(); } return createIndex(row, column, m_tabWidget->widget(tabIndex)); } QModelIndex ViewContainer::indexForView(ChatWindow* view) const { if (!view || !m_tabWidget) { return QModelIndex(); } int index = m_tabWidget->indexOf(view); if (index == -1) { return QModelIndex(); } if (view->isTopLevelView()) { int count = -1; for (int i = 0; i <= index; ++i) { if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++count; } } return createIndex(count, 0, view); } else { if (!view->getServer() || !view->getServer()->getStatusView()) { return QModelIndex(); } ChatWindow* statusView = view->getServer()->getStatusView(); int statusViewIndex = m_tabWidget->indexOf(statusView); return createIndex(index - statusViewIndex - 1, 0, view); } } QModelIndex ViewContainer::parent(const QModelIndex& index) const { if (!m_tabWidget) { return QModelIndex(); } const ChatWindow* view = static_cast(index.internalPointer()); if (!view || view->isTopLevelView() || !view->getServer() || !view->getServer()->getStatusView()) { return QModelIndex(); } return indexForView(view->getServer()->getStatusView()); } QVariant ViewContainer::data(const QModelIndex& index, int role) const { if (!index.isValid() || !index.internalPointer() || !m_tabWidget) { return QVariant(); } int row = m_tabWidget->indexOf(static_cast(index.internalPointer())); if (role == Qt::DisplayRole) { return static_cast(index.internalPointer())->getName(); } else if (role == Qt::DecorationRole) { // FIXME KF5 port: Don't show close buttons on the view tree for now. if (m_viewTree && Preferences::self()->closeButtons() && !Preferences::self()->tabNotificationsLeds()) { return QVariant(); } return m_tabWidget->tabIcon(row); } else if (role == ColorRole) { const ChatWindow* view = static_cast(index.internalPointer()); if (view->currentTabNotification() != Konversation::tnfNone) { if ((view->currentTabNotification() == Konversation::tnfNormal && Preferences::self()->tabNotificationsMsgs()) || (view->currentTabNotification() == Konversation::tnfPrivate && Preferences::self()->tabNotificationsPrivate()) || (view->currentTabNotification() == Konversation::tnfSystem && Preferences::self()->tabNotificationsSystem()) || (view->currentTabNotification() == Konversation::tnfControl && Preferences::self()->tabNotificationsEvents()) || (view->currentTabNotification() == Konversation::tnfNick && Preferences::self()->tabNotificationsNick()) || (view->currentTabNotification() == Konversation::tnfHighlight && Preferences::self()->tabNotificationsHighlights())) { return m_tabWidget->tabBar()->tabTextColor(row); } } } else if (role == DisabledRole) { const ChatWindow* view = static_cast(index.internalPointer()); if (view->getType() == ChatWindow::Channel) { return !static_cast(view)->joined(); } else if (view->getType() == ChatWindow::Query) { return !view->getServer()->isConnected(); } return false; } else if (role == HighlightRole) { return (row == m_popupViewIndex); } else if (role == ViewRole) { - return qVariantFromValue(static_cast(index.internalPointer())); + return qVariantFromValue(static_cast(index.internalPointer())); } else if (role == IsChild) { return index.parent().isValid(); } else if (role == HasActivity) { const ChatWindow* view = static_cast(index.internalPointer()); return (view->currentTabNotification() < Konversation::tnfSystem); } return QVariant(); } Qt::DropActions ViewContainer::supportedDragActions() const { return Qt::MoveAction; } Qt::DropActions ViewContainer::supportedDropActions() const { return Qt::MoveAction; } Qt::ItemFlags ViewContainer::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; } QStringList ViewContainer::mimeTypes() const { return QStringList() << QLatin1String("application/x-konversation-chatwindow"); } QMimeData* ViewContainer::mimeData(const QModelIndexList &indexes) const { if (!indexes.length()) { return new ViewMimeData(nullptr); } const QModelIndex &idx = indexes.at(0); if (!idx.isValid()) { return new ViewMimeData(nullptr); } return new ViewMimeData(static_cast(idx.internalPointer())); } bool ViewContainer::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (action != Qt::MoveAction) { return false; } if (!data->hasFormat("application/x-konversation-chatwindow")) { return false; } if (row == -1 || column != 0) { return false; } ChatWindow *dragView = static_cast(data)->view(); if (!dragView->isTopLevelView() && (!parent.isValid() || (dragView->getServer() != static_cast(parent.internalPointer())->getServer()))) { return false; } if (dragView->isTopLevelView() && parent.isValid()) { return false; } if (m_viewTree && !m_viewTree->showDropIndicator()) { m_viewTree->setDropIndicatorShown(true); m_viewTree->viewport()->update(); } return true; } bool ViewContainer::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action != Qt::MoveAction) { return false; } if (!data->hasFormat("application/x-konversation-chatwindow")) { return false; } if (row == -1 || column != 0) { return false; } ChatWindow *dragView = static_cast(data)->view(); QModelIndex dragIdx = indexForView(dragView); if (dragView->isTopLevelView()) { if (parent.isValid()) { return false; } for (int i = row < dragIdx.row() ? 0 : 1; i < qAbs(dragIdx.row() - row); ++i) { (row < dragIdx.row()) ? moveViewLeft() : moveViewRight(); } return true; } else { if (!parent.isValid()) { return false; } int from = m_tabWidget->indexOf(dragView); int to = m_tabWidget->indexOf(static_cast(parent.internalPointer())) + row; if (to < from) { ++to; } if (from == to) { return false; } beginMoveRows(parent, dragIdx.row(), dragIdx.row(), parent, row); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(from, to); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); return true; } } bool ViewContainer::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(row) Q_UNUSED(count) Q_UNUSED(parent) return true; } void ViewContainer::updateAppearance() { if (Preferences::self()->tabPlacement()==Preferences::Left && m_viewTree == nullptr) { m_saveSplitterSizesLock = true; setupViewTree(); } if (!(Preferences::self()->tabPlacement()==Preferences::Left) && m_viewTree) { m_saveSplitterSizesLock = true; removeViewTree(); } updateViews(); updateTabWidgetAppearance(); KToggleAction* action = qobject_cast(actionCollection()->action("hide_nicknamelist")); Q_ASSERT(action); action->setChecked(Preferences::self()->showNickList()); if (m_insertCharDialog) { QFont font; if (Preferences::self()->customTextFont()) font = Preferences::self()->textFont(); else font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); m_insertCharDialog->setFont(font); } } void ViewContainer::updateTabWidgetAppearance() { bool noTabBar = (Preferences::self()->tabPlacement()==Preferences::Left); m_tabWidget->tabBar()->setHidden(noTabBar); m_tabWidget->setDocumentMode(true); if (Preferences::self()->customTabFont()) m_tabWidget->tabBar()->setFont(Preferences::self()->tabFont()); else m_tabWidget->tabBar()->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_tabWidget->setTabPosition((Preferences::self()->tabPlacement()==Preferences::Top) ? QTabWidget::North : QTabWidget::South); if (Preferences::self()->showTabBarCloseButton() && !noTabBar) m_tabWidget->cornerWidget()->show(); else m_tabWidget->cornerWidget()->hide(); m_tabWidget->tabBar()->setTabsClosable(Preferences::self()->closeButtons()); } void ViewContainer::updateViewActions(int index) { if (!m_tabWidget) return; QAction* action; ChatWindow* view = nullptr; if (index != -1) view = static_cast(m_tabWidget->widget(index)); if (m_tabWidget->count() > 0 && view) { ChatWindow::WindowType viewType = view->getType(); Server* server = view->getServer(); bool insertSupported = view->isInsertSupported(); IRCView* textView = view->getTextView(); // FIXME ViewTree port: Take hierarchy into account. action = actionCollection()->action("move_tab_left"); if (action) action->setEnabled(canMoveViewLeft()); action = actionCollection()->action("move_tab_right"); if (action) action->setEnabled(canMoveViewRight()); if (server && (viewType == ChatWindow::Status || server == m_frontServer)) { action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(true); action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(server->isConnected() || server->isConnecting() || server->isScheduledToConnect()); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(server->isConnected()); } else { action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(false); } KToggleAction* notifyAction = static_cast(actionCollection()->action("tab_notifications")); if (notifyAction) { notifyAction->setEnabled(viewType == ChatWindow::Channel || viewType == ChatWindow::Query || viewType == ChatWindow::Status || viewType == ChatWindow::Konsole || viewType == ChatWindow::DccTransferPanel || viewType == ChatWindow::RawLog); notifyAction->setChecked(view->notificationsEnabled()); } KToggleAction* autoJoinAction = static_cast(actionCollection()->action("tab_autojoin")); Channel* channel = static_cast(view); if (autoJoinAction && viewType == ChatWindow::Channel && channel->getServer()->getServerGroup()) { autoJoinAction->setEnabled(true); autoJoinAction->setChecked(channel->autoJoin()); } else if (!(viewType != ChatWindow::Channel && index != m_tabWidget->currentIndex())) { autoJoinAction->setEnabled(false); autoJoinAction->setChecked(false); } KToggleAction* autoConnectAction = static_cast(actionCollection()->action("tab_autoconnect")); if (autoConnectAction && server && (viewType == ChatWindow::Status || server == m_frontServer) && server->getServerGroup()) { autoConnectAction->setEnabled(true); autoConnectAction->setChecked(server->getServerGroup()->autoConnectEnabled()); } else if (!(viewType != ChatWindow::Status && index != m_tabWidget->currentIndex())) { autoConnectAction->setEnabled(false); autoConnectAction->setChecked(false); } action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(viewType == ChatWindow::Channel && channel->rejoinable()); action = actionCollection()->action("close_queries"); if (action) action->setEnabled(m_queryViewCount > 0); action = actionCollection()->action("clear_tabs"); if (action) action->setEnabled(true); action = actionCollection()->action("increase_font"); if (action) action->setEnabled(true); action = actionCollection()->action("shrink_font"); if (action) action->setEnabled(true); action = actionCollection()->action("reset_font"); if (action) action->setEnabled(true); action = actionCollection()->action("toggle_away"); if (action) action->setEnabled(true); action = actionCollection()->action("next_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("previous_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("next_active_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("close_tab"); if (action) action->setEnabled(true); if (index == m_tabWidget->currentIndex()) { // The following only need to be updated when this run is related // to the active tab, e.g. when it was just changed. action = actionCollection()->action("insert_marker_line"); if (action) action->setEnabled(textView != nullptr); action = actionCollection()->action("insert_character"); if (action) action->setEnabled(insertSupported); action = actionCollection()->action("irc_colors"); if (action) action->setEnabled(insertSupported); action = actionCollection()->action("auto_replace"); if (action) action->setEnabled(view->getInputBar() != nullptr); action = actionCollection()->action("focus_input_box"); if (action) { action->setEnabled(view->getInputBar() != nullptr); if (view->getTextView() && view->getTextView()->parent()) { //HACK See notes in SearchBar::eventFilter QEvent e(static_cast(QEvent::User+414)); // Magic number to avoid QEvent::registerEventType Application::instance()->sendEvent(view->getTextView()->parent(), &e); } } action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(textView != nullptr && view->getTextView()->hasLines()); action = actionCollection()->action("clear_window"); if (action) action->setEnabled(textView != nullptr); action = actionCollection()->action("edit_find"); if (action) { action->setText(i18n("Find Text...")); action->setEnabled(view->searchView()); action->setStatusTip(i18n("Search for text in the current tab")); } action = actionCollection()->action("edit_find_next"); if (action) action->setEnabled(view->searchView()); action = actionCollection()->action("edit_find_prev"); if (action) action->setEnabled(view->searchView()); KToggleAction* channelListAction = static_cast(actionCollection()->action("open_channel_list")); if (channelListAction) { if (m_frontServer) { QString name = m_frontServer->getDisplayName(); name = name.replace('&', "&&"); channelListAction->setEnabled(true); channelListAction->setChecked(m_frontServer->getChannelListPanel()); channelListAction->setText(i18n("Channel &List for %1",name)); } else { channelListAction->setEnabled(false); channelListAction->setChecked(false); channelListAction->setText(i18n("Channel &List")); } } action = actionCollection()->action("open_logfile"); if (action) { action->setEnabled(!view->logFileName().isEmpty()); if (view->logFileName().isEmpty()) action->setText(i18n("&Open Logfile")); else { QString name = view->getName(); name = name.replace('&', "&&"); action->setText(i18n("&Open Logfile for %1",name)); } } action = actionCollection()->action("hide_nicknamelist"); if (action) action->setEnabled(view->getType() == ChatWindow::Channel); action = actionCollection()->action("channel_settings"); if (action && view->getType() == ChatWindow::Channel) { action->setEnabled(true); action->setText(i18n("&Channel Settings for %1...",view->getName())); } else if (action) { action->setEnabled(false); action->setText(i18n("&Channel Settings...")); } } } else { action = actionCollection()->action("move_tab_left"); if (action) action->setEnabled(false); action = actionCollection()->action("move_tab_right"); if(action) action->setEnabled(false); action = actionCollection()->action("next_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("previous_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("close_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("next_active_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_notifications"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_autojoin"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_autoconnect"); if (action) action->setEnabled(false); action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(false); action = actionCollection()->action("insert_marker_line"); if (action) action->setEnabled(false); action = actionCollection()->action("insert_character"); if (action) action->setEnabled(false); action = actionCollection()->action("irc_colors"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_window"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_tabs"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find_next"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find_prev"); if (action) action->setEnabled(false); action = actionCollection()->action("open_channel_list"); if (action) action->setEnabled(false); action = actionCollection()->action("open_logfile"); if (action) action->setEnabled(false); action = actionCollection()->action("toggle_away"); if (action) action->setEnabled(false); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(false); action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("hide_nicknamelist"); if (action) action->setEnabled(false); action = actionCollection()->action("channel_settings"); if (action) action->setEnabled(false); action = actionCollection()->action("close_queries"); if (action) action->setEnabled(false); } action = actionCollection()->action("last_focused_tab"); if (action) action->setEnabled(m_lastFocusedView != nullptr); } void ViewContainer::updateFrontView() { if (!m_tabWidget) return; ChatWindow* view = static_cast(m_tabWidget->currentWidget()); if (!view) return; // Make sure that only views with info output get to be the m_frontView if (m_frontView) { disconnect(m_frontView, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); } if (view->canBeFrontView()) { m_frontView = view; connect(view, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); view->emitUpdateInfo(); } else { QString viewName = Konversation::removeIrcMarkup(view->getName()); if(viewName != "ChatWindowObject") emit setStatusBarInfoLabel(viewName); else emit clearStatusBarInfoLabel(); } switch (view->getType()) { case ChatWindow::Channel: case ChatWindow::Query: case ChatWindow::Status: case ChatWindow::ChannelList: case ChatWindow::RawLog: emit setStatusBarLagLabelShown(true); break; default: emit setStatusBarLagLabelShown(false); break; } // Make sure that only text views get to be the m_searchView if (view->searchView()) m_searchView = view; updateViewActions(m_tabWidget->currentIndex()); } void ViewContainer::updateViews(const Konversation::ServerGroupSettingsPtr serverGroup) { for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); bool announce = false; if (serverGroup) { if (view->getType() == ChatWindow::Status && view->getServer()->getServerGroup() == serverGroup) { QString label = view->getServer()->getDisplayName(); if (!label.isEmpty() && m_tabWidget->tabText(i) != label) { m_tabWidget->setTabText(i, label); announce = true; if (view == m_frontView) { emit setStatusBarInfoLabel(label); emit setWindowCaption(label); } static_cast(view)->updateName(); } } if (i == m_tabWidget->currentIndex()) updateViewActions(i); } if (!Preferences::self()->tabNotificationsLeds()) { m_tabWidget->setTabIcon(i, QIcon()); announce = true; } if (!Preferences::self()->tabNotificationsText()) { m_tabWidget->tabBar()->setTabTextColor(i, m_window->palette().foreground().color()); announce = true; } if (Preferences::self()->tabNotificationsLeds() || Preferences::self()->tabNotificationsText()) { if (view->currentTabNotification()==Konversation::tnfNone) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfNormal && !Preferences::self()->tabNotificationsMsgs()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfPrivate && !Preferences::self()->tabNotificationsPrivate()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfSystem && !Preferences::self()->tabNotificationsSystem()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfControl && !Preferences::self()->tabNotificationsEvents()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfNick && !Preferences::self()->tabNotificationsNick()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfHighlight && !Preferences::self()->tabNotificationsHighlights()) unsetViewNotification(view); else if (view==m_tabWidget->currentWidget()) unsetViewNotification(view); else setViewNotification(view, view->currentTabNotification()); } if (announce && !m_dataChangedLock) { const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx); } } } void ViewContainer::setViewNotification(ChatWindow* view, const Konversation::TabNotifyType& type) { if (!view || view == m_tabWidget->currentWidget()) return; if (type < Konversation::tnfControl && !m_activeViewOrderList.contains(view)) m_activeViewOrderList.append(view); if (!Preferences::self()->tabNotificationsLeds() && !Preferences::self()->self()->tabNotificationsText()) return; const int tabIndex = m_tabWidget->indexOf(view); switch (type) { case Konversation::tnfNormal: if (Preferences::self()->tabNotificationsMsgs()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getMsgsLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsMsgsColor()); } break; case Konversation::tnfPrivate: if (Preferences::self()->tabNotificationsPrivate()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getPrivateLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsPrivateColor()); } break; case Konversation::tnfSystem: if (Preferences::self()->tabNotificationsSystem()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getSystemLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsSystemColor()); } break; case Konversation::tnfControl: if (Preferences::self()->tabNotificationsEvents()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getEventsLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsEventsColor()); } break; case Konversation::tnfNick: if (Preferences::self()->tabNotificationsNick()) { if (Preferences::self()->tabNotificationsOverride() && Preferences::self()->highlightNick()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getLed(Preferences::self()->highlightNickColor(),true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->highlightNickColor()); } else { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getNickLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsNickColor()); } } else { setViewNotification(view,Konversation::tnfNormal); } break; case Konversation::tnfHighlight: if (Preferences::self()->tabNotificationsHighlights()) { if (Preferences::self()->tabNotificationsOverride() && view->highlightColor().isValid()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getLed(view->highlightColor(),true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, view->highlightColor()); } else { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getHighlightsLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsHighlightsColor()); } } else { setViewNotification(view,Konversation::tnfNormal); } break; default: break; } const QModelIndex& idx = indexForView(view); if (!m_dataChangedLock) { emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole << HasActivity); // WIPQTQUICK } } void ViewContainer::unsetViewNotification(ChatWindow* view) { if (!m_tabWidget) return; const int tabIndex = m_tabWidget->indexOf(view); if (Preferences::self()->tabNotificationsLeds()) { switch (view->getType()) { case ChatWindow::Channel: case ChatWindow::DccChat: m_tabWidget->setTabIcon(tabIndex, images->getMsgsLed(false)); break; case ChatWindow::Query: m_tabWidget->setTabIcon(tabIndex, images->getPrivateLed(false)); break; case ChatWindow::Status: m_tabWidget->setTabIcon(tabIndex, images->getServerLed(false)); break; default: m_tabWidget->setTabIcon(tabIndex, images->getSystemLed(false)); break; } } QColor textColor = m_window->palette().foreground().color(); if (view->getType() == ChatWindow::Channel) { Channel *channel = static_cast(view); if (!channel->joined()) textColor = m_tabWidget->palette().color(QPalette::Disabled, QPalette::Text); } else if (view->getType() == ChatWindow::Query) { if (!view->getServer()->isConnected()) textColor = m_tabWidget->palette().color(QPalette::Disabled, QPalette::Text); } m_tabWidget->tabBar()->setTabTextColor(tabIndex, textColor); const QModelIndex& idx = indexForView(view); if (!m_dataChangedLock) { emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole << DisabledRole << HasActivity); // WIPQTQUICK } m_activeViewOrderList.removeAll(view); } void ViewContainer::toggleViewNotifications() { ChatWindow* view = nullptr; if (m_popupViewIndex == -1) view = static_cast(m_tabWidget->currentWidget()); else view = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (view) { if (!view->notificationsEnabled()) { view->setNotificationsEnabled(true); updateViews(); KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } else { view->setNotificationsEnabled(false); unsetViewNotification(view); KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } } m_popupViewIndex = -1; } void ViewContainer::toggleAutoJoin() { Channel* channel = nullptr; if (m_popupViewIndex == -1) channel = static_cast(m_tabWidget->currentWidget()); else channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (channel && channel->getType() == ChatWindow::Channel) { bool autoJoin = channel->autoJoin(); channel->setAutoJoin(!autoJoin); emit autoJoinToggled(channel->getServer()->getServerGroup()); } m_popupViewIndex = -1; } void ViewContainer::toggleConnectOnStartup() { ChatWindow* view = nullptr; if (m_popupViewIndex == -1) view = static_cast(m_tabWidget->currentWidget()); else view = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (view && view->getType() == ChatWindow::Status) { Server* server = view->getServer(); if (server) { Konversation::ServerGroupSettingsPtr settings = server->getConnectionSettings().serverGroup(); bool autoConnect = settings->autoConnectEnabled(); settings->setAutoConnectEnabled(!autoConnect); emit autoConnectOnStartupToggled(settings); } } m_popupViewIndex = -1; } void ViewContainer::addView(ChatWindow* view, const QString& label, bool weinitiated) { int placement = insertIndex(view); QIcon iconSet; if (Preferences::self()->closeButtons() && m_viewTree) iconSet = QIcon::fromTheme("dialog-close"); connect(Application::instance(), SIGNAL(appearanceChanged()), view, SLOT(updateAppearance())); connect(view, SIGNAL(setStatusBarTempText(QString)), this, SIGNAL(setStatusBarTempText(QString))); connect(view, SIGNAL(clearStatusBarTempText()), this, SIGNAL(clearStatusBarTempText())); connect(view, SIGNAL(closing(ChatWindow*)), this, SLOT(cleanupAfterClose(ChatWindow*))); connect(view, SIGNAL(showView(QObject*)), this, SLOT(showView(QObject*))); // WIPQTQUICK switch (view->getType()) { case ChatWindow::Channel: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getMsgsLed(false); break; case ChatWindow::RawLog: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; case ChatWindow::Query: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getPrivateLed(false); break; case ChatWindow::DccChat: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getMsgsLed(false); break; case ChatWindow::Status: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getServerLed(false); break; case ChatWindow::ChannelList: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; default: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; } if (view->isTopLevelView()) { int diff = 0; for (int i = 0; i < placement; ++i) { if (!static_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++diff; } } m_dataChangedLock = true; beginInsertRows(QModelIndex(), placement - diff, placement - diff); } else { int statusViewIndex = m_tabWidget->indexOf(view->getServer()->getStatusView()); const QModelIndex idx = indexForView(view->getServer()->getStatusView()); if (m_viewTree) { m_viewTree->setExpanded(idx, true); } m_dataChangedLock = true; beginInsertRows(idx, placement - statusViewIndex - 1, placement - statusViewIndex - 1); } m_tabWidget->insertTab(placement, view, iconSet, QString(label).replace('&', "&&")); endInsertRows(); m_dataChangedLock = false; m_vbox->show(); // Check, if user was typing in old input line bool doBringToFront=false; if (Preferences::self()->focusNewQueries() && view->getType()==ChatWindow::Query && !weinitiated) doBringToFront = true; if (Preferences::self()->bringToFront() && view->getType()!=ChatWindow::RawLog) doBringToFront = true; // make sure that bring to front only works when the user wasn't typing something if (m_frontView && view->getType() != ChatWindow::UrlCatcher && view->getType() != ChatWindow::Konsole) { if (!m_frontView->getTextInLine().isEmpty()) doBringToFront = false; } if (doBringToFront) showView(view); updateViewActions(m_tabWidget->currentIndex()); if (m_viewTree && m_tabWidget->count() == 1) { setViewTreeShown(true); } } int ViewContainer::insertIndex(ChatWindow* view) { int placement = m_tabWidget->count(); ChatWindow::WindowType wtype; ChatWindow *tmp_ChatWindow; // Please be careful about changing any of the grouping behavior in here, // because it needs to match up with the sorting behavior of the tree list, // otherwise they may become out of sync, wreaking havoc with the move // actions. Yes, this would do well with a more reliable approach in the // future. Then again, while this is ugly, it's also very fast. switch (view->getType()) { case ChatWindow::Channel: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { for (int index = sindex + 1; index < m_tabWidget->count(); index++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(index)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog) { placement = index; break; } } break; } } break; case ChatWindow::RawLog: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { placement = sindex + 1; break; } } break; case ChatWindow::Query: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { for (int index = sindex + 1; index < m_tabWidget->count(); index++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(index)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog && wtype != ChatWindow::Query) { placement = index; break; } } break; } } break; case ChatWindow::DccChat: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Status && wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog && wtype != ChatWindow::Query && wtype != ChatWindow::DccChat && wtype != ChatWindow::ChannelList) { placement = sindex; break; } } break; case ChatWindow::Status: if (m_viewTree) { for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() != ChatWindow::Channel && tmp_ChatWindow->getType() != ChatWindow::Status && tmp_ChatWindow->getType() != ChatWindow::RawLog && tmp_ChatWindow->getType() != ChatWindow::Query && tmp_ChatWindow->getType() != ChatWindow::DccChat) { placement = sindex; break; } } } break; case ChatWindow::ChannelList: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getServer() == view->getServer()) placement = sindex + 1; } break; default: break; } return placement; } void ViewContainer::unclutterTabs() { if (!m_tabWidget || !m_tabWidget->count()) { return; } emit beginResetModel(); m_tabWidget->blockSignals(true); QWidget* currentView = m_tabWidget->currentWidget(); QList views; while (m_tabWidget->count()) { views << static_cast(m_tabWidget->widget(0)); m_tabWidget->removeTab(0); } foreach(ChatWindow *view, views) { if (view->isTopLevelView()) { m_tabWidget->insertTab(insertIndex(view), view, QIcon(), view->getName().replace('&', "&&")); views.removeAll(view); } } foreach(ChatWindow *view, views) { if (!view->isTopLevelView()) { m_tabWidget->insertTab(insertIndex(view), view, QIcon(), view->getName().replace('&', "&&")); } } updateViews(); if (currentView) { m_tabWidget->setCurrentWidget(currentView); } m_tabWidget->blockSignals(false); emit endResetModel(); viewSwitched(m_tabWidget->currentIndex()); } void ViewContainer::setCurrentNick(const QString &nick) { ChatWindow* view = static_cast(m_tabWidget->widget(m_tabWidget->currentIndex())); if (view) { view->getServer()->queue(QStringLiteral("NICK ")+nick.simplified()); } } void ViewContainer::viewSwitched(int newIndex) { ChatWindow* view = static_cast(m_tabWidget->widget(newIndex)); if (!view) return; m_lastFocusedView = m_currentView; m_currentView = view; const QModelIndex &idx = indexForView(view); if (m_frontView) { m_frontView->resetTabNotification(); if (!m_dataChangedLock) { const QModelIndex &oldIdx = indexForView(m_frontView); emit dataChanged(oldIdx, oldIdx, QVector() << Qt::DecorationRole << ColorRole << HasActivity); } disconnect(m_frontView, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); if (Preferences::self()->automaticRememberLine() && m_frontView->getTextView() != nullptr) m_frontView->getTextView()->insertRememberLine(); } m_frontView = nullptr; m_searchView = nullptr; setFrontServer(view->getServer()); // display this server's lag time if (m_frontServer) { updateStatusBarSSLLabel(m_frontServer); updateStatusBarLagLabel(m_frontServer, m_frontServer->getLag()); } emit clearStatusBarTempText(); updateFrontView(); view->resetTabNotification(); unsetViewNotification(view); if (!m_viewTree || !m_viewTree->hasFocus()) view->adjustFocus(); if (view->getTextView() != nullptr) view->getTextView()->cancelRememberLine(); updateViewEncoding(view); QString tabName = Konversation::removeIrcMarkup(view->getName()); if (tabName != "ChatWindowObject") emit setWindowCaption(tabName); else emit setWindowCaption(QString()); emit viewChanged(idx); } void ViewContainer::showView(QObject* view) { // Don't bring Tab to front if TabWidget is hidden. Otherwise QT gets confused // and shows the Tab as active but will display the wrong pane if (true /* m_tabWidget->isVisible() */) { // WIPQTQUICK m_tabWidget->setCurrentIndex(m_tabWidget->indexOf(static_cast(view))); } } void ViewContainer::goToView(int page) { if (page == m_tabWidget->currentIndex()) return; if (page > m_tabWidget->count()) return; if (page >= m_tabWidget->count()) page = 0; else if (page < 0) page = m_tabWidget->count() - 1; if (page >= 0) m_tabWidget->setCurrentIndex(page); m_popupViewIndex = -1; } void ViewContainer::showNextView() { goToView(m_tabWidget->currentIndex()+1); } void ViewContainer::showPreviousView() { goToView(m_tabWidget->currentIndex()-1); } void ViewContainer::showNextActiveView() { if (m_window->isHidden()) m_window->show(); if (m_window->isMinimized()) KWindowSystem::unminimizeWindow(m_window->winId()); if (!m_window->isActiveWindow()) { m_window->raise(); KWindowSystem::activateWindow(m_window->winId()); } if (!m_activeViewOrderList.isEmpty()) { ChatWindow* prev = m_activeViewOrderList.first(); ChatWindow* view = prev; QList::ConstIterator it; for (it = m_activeViewOrderList.constBegin(); it != m_activeViewOrderList.constEnd(); ++it) { if ((*it)->currentTabNotification() < prev->currentTabNotification()) view = (*it); } m_tabWidget->setCurrentIndex(m_tabWidget->indexOf(view)); } } void ViewContainer::showLastFocusedView() { if (m_lastFocusedView) showView(m_lastFocusedView); } bool ViewContainer::canMoveViewLeft() const { if (!m_tabWidget || !m_tabWidget->count()) { return false; } int index = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(index)); if (view->isTopLevelView() && index > 0) { return true; } else if (!view->isTopLevelView()) { ChatWindow* statusView = view->getServer()->getStatusView(); int statusViewIndex = m_tabWidget->indexOf(statusView); index = index - statusViewIndex - 1; return (index > 0); } return false; } bool ViewContainer::canMoveViewRight() const { if (!m_tabWidget || !m_tabWidget->count()) { return false; } int index = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(index)); if (view->isTopLevelView()) { int lastTopLevelView = -1; for (int i = m_tabWidget->count() - 1; i >= index; --i) { if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { lastTopLevelView = i; break; } } return (index != lastTopLevelView); } else if (!view->isTopLevelView()) { view = static_cast(m_tabWidget->widget(index + 1)); return (view && !view->isTopLevelView()); } return false; } void ViewContainer::moveViewLeft() { if (!m_tabWidget || !m_tabWidget->count()) { return; } int tabIndex = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(tabIndex)); const QModelIndex idx = indexForView(view); if (view->isTopLevelView()) { const QModelIndex aboveIdx = index(idx.row() - 1, 0); int aboveTabIndex = m_tabWidget->indexOf(static_cast(aboveIdx.internalPointer())); beginMoveRows(QModelIndex(), idx.row(), idx.row(), QModelIndex(), aboveIdx.row()); m_tabWidget->blockSignals(true); if (view->getType() == ChatWindow::Status) { for (int i = m_tabWidget->count() - 1; i > tabIndex; --i) { ChatWindow* tab = static_cast(m_tabWidget->widget(i)); if (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->tabBar()->moveTab(i, aboveTabIndex); ++tabIndex; ++i; } } } m_tabWidget->tabBar()->moveTab(tabIndex, aboveTabIndex); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } else { beginMoveRows(idx.parent(), idx.row(), idx.row(), idx.parent(), idx.row() - 1); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, tabIndex - 1); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } } void ViewContainer::moveViewRight() { if (!m_tabWidget || !m_tabWidget->count()) { return; } int tabIndex = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(tabIndex)); const QModelIndex idx = indexForView(view); if (view->isTopLevelView()) { const QModelIndex belowIdx = index(idx.row() + 1, 0); int belowTabIndex = m_tabWidget->indexOf(static_cast(belowIdx.internalPointer())); int children = rowCount(belowIdx); if (children) { belowTabIndex = m_tabWidget->indexOf(static_cast(belowIdx.child(children - 1, 0).internalPointer())); } beginMoveRows(QModelIndex(), idx.row(), idx.row(), QModelIndex(), belowIdx.row() + 1); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, belowTabIndex); if (view->getType() == ChatWindow::Status) { ChatWindow* tab = static_cast(m_tabWidget->widget(tabIndex)); while (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->tabBar()->moveTab(tabIndex, belowTabIndex); tab = static_cast(m_tabWidget->widget(tabIndex)); } } m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } else { beginMoveRows(idx.parent(), idx.row(), idx.row(), idx.parent(), idx.row() + 2); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, tabIndex + 1); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } } void ViewContainer::closeView(int view) { ChatWindow* viewToClose = static_cast(m_tabWidget->widget(view)); closeView(viewToClose); } void ViewContainer::closeView(ChatWindow* view) { if (view) { ChatWindow::WindowType viewType = view->getType(); switch (viewType) { case ChatWindow::DccTransferPanel: closeDccPanel(); break; case ChatWindow::UrlCatcher: closeUrlCatcher(); break; case ChatWindow::NicksOnline: closeNicksOnlinePanel(); break; default: view->closeYourself(); break; } } } void ViewContainer::cleanupAfterClose(ChatWindow* view) { // BEGIN: WIPQTQUICK if (m_window->getFilteredMessageModel()->filterView() == view) { m_window->getFilteredMessageModel()->setFilterView(nullptr); m_window->getFilteredUserModel()->setFilterView(nullptr); m_window->getFilteredInputHistoryModel()->setFilterView(nullptr); } m_window->getMessageModel()->cullMessages(view); m_window->getInputHistoryModel()->cull(view); // END: WIPQTQUICK if (view == m_frontView) m_frontView = nullptr; if (view == m_lastFocusedView) { QAction* action = actionCollection()->action("last_focused_tab"); if (action) action->setEnabled(false); } if (m_tabWidget) { const int tabIndex = m_tabWidget->indexOf(view); if (tabIndex != -1) { if (tabIndex == m_popupViewIndex) m_popupViewIndex = -1; m_tabWidget->blockSignals(true); const QModelIndex& idx = indexForView(view); beginRemoveRows(idx.parent(), idx.row(), idx.row()); if (view->getType() == ChatWindow::Status) { for (int i = m_tabWidget->count() - 1; i > tabIndex; --i) { const ChatWindow* tab = static_cast(m_tabWidget->widget(i)); if (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->removeTab(i); } } } m_tabWidget->removeTab(tabIndex); endRemoveRows(); m_tabWidget->blockSignals(false); viewSwitched(m_tabWidget->currentIndex()); } if (m_tabWidget->count() <= 0) { m_saveSplitterSizesLock = true; m_vbox->hide(); emit resetStatusBar(); emit setWindowCaption(QString()); updateViewActions(-1); } } // Remove the view from the active view list if it's still on it m_activeViewOrderList.removeAll(view); if (view->getType() == ChatWindow::Query) --m_queryViewCount; if (m_queryViewCount == 0 && actionCollection()) { QAction* action = actionCollection()->action("close_queries"); if (action) action->setEnabled(false); } if (!m_tabWidget->count() && m_viewTree) { setViewTreeShown(false); } } void ViewContainer::closeViewMiddleClick(int view) { if (Preferences::self()->middleClickClose()) closeView(view); } void ViewContainer::renameKonsole() { bool ok = false; if (!m_tabWidget) return; int popup = m_popupViewIndex ? m_popupViewIndex : m_tabWidget->currentIndex(); QString label = QInputDialog::getText(m_tabWidget->widget(popup), i18n("Rename Tab"), i18n("Enter new tab name:"), QLineEdit::Normal, m_tabWidget->tabText(popup), &ok); if (ok) { KonsolePanel* view = static_cast(m_tabWidget->widget(popup)); if (!view) return; view->setName(label); m_tabWidget->setTabText(popup, label); const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DisplayRole); if (popup == m_tabWidget->currentIndex()) { emit setStatusBarInfoLabel(label); emit setWindowCaption(label); } } } void ViewContainer::closeCurrentView() { if (m_popupViewIndex == -1) closeView(m_tabWidget->currentIndex()); else closeView(m_popupViewIndex); m_popupViewIndex = -1; } void ViewContainer::changeViewCharset(int index) { ChatWindow* chatWin; if (m_popupViewIndex == -1) chatWin = static_cast(m_tabWidget->currentWidget()); else chatWin = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (chatWin) { if (index == 0) chatWin->setChannelEncoding(QString()); else chatWin->setChannelEncoding(Konversation::IRCCharsets::self()->availableEncodingShortNames()[index - 1]); } m_popupViewIndex = -1; } void ViewContainer::updateViewEncoding(ChatWindow* view) { if (view) { ChatWindow::WindowType viewType = view->getType(); KSelectAction* codecAction = qobject_cast(actionCollection()->action("tab_encoding")); if (codecAction) { if(viewType == ChatWindow::Channel || viewType == ChatWindow::Query || viewType == ChatWindow::Status) { codecAction->setEnabled(view->isChannelEncodingSupported()); QString encoding = view->getChannelEncoding(); if (view->getServer()) { codecAction->changeItem(0, i18nc("Default encoding", "Default ( %1 )", view->getServer()->getIdentity()->getCodecName())); } if (encoding.isEmpty()) { codecAction->setCurrentItem(0); } else { codecAction->setCurrentItem(Konversation::IRCCharsets::self()->shortNameToIndex(encoding) + 1); } } else { codecAction->setEnabled(false); } } } } void ViewContainer::showViewContextMenu(QWidget* tab, const QPoint& pos) { if (!tab) { return; } ChatWindow* view = static_cast(tab); showViewContextMenu(indexForView(view), pos); } void ViewContainer::showViewContextMenu(const QModelIndex &index, const QPoint& pos) { ChatWindow* view = static_cast(index.internalPointer()); m_popupViewIndex = m_tabWidget->indexOf(view); updateViewActions(m_popupViewIndex); QMenu* menu = static_cast(m_window->guiFactory()->container("tabContextMenu", m_window)); if (!menu) return; KToggleAction* autoJoinAction = qobject_cast(actionCollection()->action("tab_autojoin")); KToggleAction* autoConnectAction = qobject_cast(actionCollection()->action("tab_autoconnect")); QAction* rejoinAction = actionCollection()->action("rejoin_channel"); QAction* closeAction = actionCollection()->action("close_tab"); QAction* renameAct = new QAction(this); renameAct->setText(i18n("&Rename Tab...")); connect(renameAct, SIGNAL(triggered()), this, SLOT(renameKonsole())); ChatWindow::WindowType viewType = view->getType(); updateViewEncoding(view); if (viewType == ChatWindow::Channel) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, autoJoinAction); Channel *channel = static_cast(view); if (channel->rejoinable() && rejoinAction) { menu->insertAction(closeAction, rejoinAction); rejoinAction->setEnabled(true); } } if (viewType == ChatWindow::Konsole) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, renameAct); } if (viewType == ChatWindow::Status) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, autoConnectAction); QList serverActions; action = actionCollection()->action("disconnect_server"); if (action) serverActions.append(action); action = actionCollection()->action("reconnect_server"); if (action) serverActions.append(action); action = actionCollection()->action("join_channel"); if (action) serverActions.append(action); // TODO FIXME who wants to own this action? action = new QAction(this); action->setSeparator(true); if (action) serverActions.append(action); m_window->plugActionList("server_actions", serverActions); m_contextServer = view->getServer(); } else { m_contextServer = nullptr; } const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << HighlightRole); const QAction* action = menu->exec(pos); m_popupViewIndex = -1; menu->removeAction(autoJoinAction); menu->removeAction(autoConnectAction); menu->removeAction(rejoinAction); menu->removeAction(renameAct); m_window->unplugActionList("server_actions"); emit contextMenuClosed(); emit dataChanged(idx, idx, QVector() << HighlightRole); if (action != actionCollection()->action("close_tab")) { updateViewEncoding(view); } updateViewActions(m_tabWidget->currentIndex()); } QString ViewContainer::currentViewTitle() { if (m_frontServer) { if (m_frontView && m_frontView->getType() == ChatWindow::Channel) return m_frontView->getTitle(); else return m_frontServer->getDisplayName(); } else { return QString(); } } QString ViewContainer::currentViewURL(bool passNetwork) { QString url; QString channel; QString port; QString server; if (m_frontServer && m_frontView) { updateFrontView(); url = m_frontView->getURI(passNetwork); } return url; } int ViewContainer::getViewIndex(QWidget* widget) { return m_tabWidget->indexOf(widget); } ChatWindow* ViewContainer::getViewAt(int index) { return static_cast(m_tabWidget->widget(index)); } QList > ViewContainer::getChannelsURI() { QList > URIList; for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->getType() == ChatWindow::Channel) { QString uri = view->getURI(); QString name = QString("%1 (%2)") .arg(view->getName()) .arg(view->getServer()->getDisplayName()); URIList += QPair(name,uri); } } return URIList; } void ViewContainer::clearView() { if (m_frontView) m_frontView->getTextView()->clear(); } void ViewContainer::clearAllViews() { for (int i = 0; i < m_tabWidget->count(); i++) static_cast(m_tabWidget->widget(i))->clear(); } void ViewContainer::findText() { if (!m_searchView) { KMessageBox::sorry(m_window, i18n("You can only search in text fields."), i18n("Find Text Information")); } else { m_searchView->getTextView()->findText(); } } void ViewContainer::findNextText() { if (m_searchView) { m_searchView->getTextView()->findNextText(); } } void ViewContainer::findPrevText() { if (m_searchView) { m_searchView->getTextView()->findPreviousText(); } } void ViewContainer::appendToFrontmost(const QString& type, const QString& message, ChatWindow* serverView, const QHash &messageTags, bool parseURL) { if (!m_tabWidget) return; if (!serverView) // e.g. DCOP info call { if (m_frontView) // m_frontView == nullptr if canBeFrontView() == false for active ChatWindow serverView = m_frontView->getServer()->getStatusView(); else if (m_frontServer) // m_frontView == nullptr && m_frontServer != nullptr if ChannelListPanel is active. serverView = m_frontServer->getStatusView(); } // This might happen if canBeFrontView() is false for active ChatWindow // and the view does not belong to any server (e.g. DCC Status View). // Discard message in this case. if (!serverView) return; updateFrontView(); if (!m_frontView || // Check if the m_frontView can actually display text or ... // if it does not belong to this server or... serverView->getServer()!=m_frontView->getServer() || // if the user decided to force it. Preferences::self()->redirectServerAndAppMsgToStatusPane()) { // if not, take server specified fallback view instead serverView->appendServerMessage(type, message, messageTags, parseURL); // FIXME: this signal should be sent from the status panel instead, so it // can be using the correct highlight color, would be more consistent // anyway! // FIXME newText(serverView,QString::null,true); } else m_frontView->appendServerMessage(type, message, messageTags, parseURL); } void ViewContainer::insertCharacter() { QFont font; if (Preferences::self()->customTextFont()) font = Preferences::self()->textFont(); else font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); if (!m_insertCharDialog) { m_insertCharDialog = new Konversation::InsertCharDialog(font.family(), m_window); connect(m_insertCharDialog, SIGNAL(insertChar(uint)), this, SLOT(insertChar(uint))); } m_insertCharDialog->setFont(font); m_insertCharDialog->show(); } void ViewContainer::insertChar(uint chr) { ChatWindow* view = static_cast(m_tabWidget->currentWidget()); if (view) view->appendInputText(QString::fromUcs4(&chr, 1), true/*fromCursor*/); } void ViewContainer::insertIRCColor() { // TODO FIXME QPointer dlg = new IRCColorChooser(m_window); if (dlg->exec() == QDialog::Accepted) { if(m_frontView) m_frontView->appendInputText(dlg->color(), true/*fromCursor*/); } delete dlg; } void ViewContainer::doAutoReplace() { if (!m_frontView) return; // Check for active window in case action was triggered from a modal dialog, like the Paste Editor if (!m_window->isActiveWindow()) return; if (m_frontView->getInputBar()) m_frontView->getInputBar()->doInlineAutoreplace(); } void ViewContainer::focusInputBox() { if (m_frontView && m_frontView->isInsertSupported()) m_frontView->adjustFocus(); } void ViewContainer::clearViewLines() { if (m_frontView && m_frontView->getTextView() != nullptr) { m_frontView->getTextView()->clearLines(); QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(false); } } void ViewContainer::insertRememberLine() { if (Preferences::self()->automaticRememberLine()) { if (m_frontView && m_frontView->getTextView() != nullptr) m_frontView->getTextView()->insertRememberLine(); } } void ViewContainer::insertRememberLines(Server* server) { for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->getServer() == server && view->getTextView() != nullptr) view->getTextView()->insertRememberLine(); } } void ViewContainer::cancelRememberLine() { if (m_frontView && m_frontView->getTextView() != nullptr) { m_frontView->getTextView()->cancelRememberLine(); QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(m_frontView->getTextView()->hasLines()); } } void ViewContainer::insertMarkerLine() { if (Preferences::self()->markerLineInAllViews()) { int total = m_tabWidget->count()-1; ChatWindow* view; for (int i = 0; i <= total; ++i) { view = static_cast(m_tabWidget->widget(i)); if (view->getTextView() != nullptr) view->getTextView()->insertMarkerLine(); } } else { if (m_frontView && m_frontView->getTextView() != nullptr) m_frontView->getTextView()->insertMarkerLine(); } if (m_frontView && m_frontView->getTextView() != nullptr) { QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(m_frontView->getTextView()->hasLines()); } } void ViewContainer::openLogFile() { if (m_frontView) { ChatWindow* view = static_cast(m_frontView); if (!view->logFileName().isEmpty()) openLogFile(view->getName(), view->logFileName()); } } void ViewContainer::openLogFile(const QString& caption, const QString& file) { if (!file.isEmpty()) { if(Preferences::self()->useExternalLogViewer()) { new KRun(QUrl::fromLocalFile(file), m_window, false); } else { LogfileReader* logReader = new LogfileReader(m_tabWidget, file, caption); addView(logReader, logReader->getName()); logReader->setServer(nullptr); } } } void ViewContainer::addKonsolePanel() { KonsolePanel* panel=new KonsolePanel(m_tabWidget); panel->setName(i18n("Konsole")); addView(panel, i18n("Konsole")); connect(panel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(panel, SIGNAL(closeView(ChatWindow*)), this, SLOT(closeView(ChatWindow*))); } void ViewContainer::addUrlCatcher() { if (m_urlCatcherPanel == nullptr) { m_urlCatcherPanel = new UrlCatcher(m_tabWidget); addView(m_urlCatcherPanel, i18n("URL Catcher")); (dynamic_cast(actionCollection()->action("open_url_catcher")))->setChecked(true); } else closeUrlCatcher(); } void ViewContainer::closeUrlCatcher() { if (m_urlCatcherPanel) { delete m_urlCatcherPanel; m_urlCatcherPanel = nullptr; (dynamic_cast(actionCollection()->action("open_url_catcher")))->setChecked(false); } } void ViewContainer::toggleDccPanel() { if (m_dccPanel == nullptr || !m_dccPanelOpen) addDccPanel(); else closeDccPanel(); } void ViewContainer::addDccPanel() { qDebug(); if (!m_dccPanelOpen) { addView(m_dccPanel, i18n("DCC Status")); m_dccPanelOpen=true; (dynamic_cast(actionCollection()->action("open_dccstatus_window")))->setChecked(true); } } void ViewContainer::closeDccPanel() { // if there actually is a dcc panel if (m_dccPanel && m_dccPanelOpen) { // hide it from view, does not delete it if (m_tabWidget) { if (m_popupViewIndex == m_tabWidget->indexOf(m_dccPanel)) { m_popupViewIndex = -1; } cleanupAfterClose(m_dccPanel); } m_dccPanelOpen=false; (dynamic_cast(actionCollection()->action("open_dccstatus_window")))->setChecked(false); } } void ViewContainer::deleteDccPanel() { if (m_dccPanel) { closeDccPanel(); delete m_dccPanel; m_dccPanel = nullptr; } } ChatWindow* ViewContainer::getDccPanel() { return m_dccPanel; } void ViewContainer::addDccChat(DCC::Chat* chat) { if (!chat->selfOpened()) // Someone else initiated dcc chat { Application* konv_app=Application::instance(); konv_app->notificationHandler()->dccChat(m_frontView, chat->partnerNick()); } DCC::ChatContainer *chatcontainer = new DCC::ChatContainer(m_tabWidget,chat); connect(chatcontainer, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); addView(chatcontainer, chatcontainer->getName()); } StatusPanel* ViewContainer::addStatusView(Server* server) { StatusPanel* statusView = new StatusPanel(m_tabWidget); // Get group name for tab if available QString label = server->getDisplayName(); statusView->setName(label); statusView->setServer(server); if (server->getServerGroup()) statusView->setNotificationsEnabled(server->getServerGroup()->enableNotifications()); QObject::connect(server, SIGNAL(sslInitFailure()), this, SIGNAL(removeStatusBarSSLLabel())); QObject::connect(server, SIGNAL(sslConnected(Server*)), this, SIGNAL(updateStatusBarSSLLabel(Server*))); // ... then put it into the tab widget, otherwise we'd have a race with server member addView(statusView, label); connect(statusView, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(statusView, SIGNAL(sendFile()), server, SLOT(requestDccSend())); connect(server, SIGNAL(awayState(bool)), statusView, SLOT(indicateAway(bool)) ); // Make sure that m_frontServer gets set on adding the first status panel, too, // since there won't be a viewSwitched happening. if (!m_frontServer) setFrontServer(server); return statusView; } RawLog* ViewContainer::addRawLog(Server* server) { RawLog* rawLog = new RawLog(m_tabWidget); rawLog->setServer(server); if (server->getServerGroup()) rawLog->setNotificationsEnabled(server->getServerGroup()->enableNotifications()); addView(rawLog, i18n("Raw Log")); connect(rawLog, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); return rawLog; } void ViewContainer::reconnectFrontServer() { Server* server = nullptr; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (server) server->reconnectServer(); } void ViewContainer::disconnectFrontServer() { Server* server = nullptr; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (server && (server->isConnected() || server->isConnecting() || server->isScheduledToConnect())) server->disconnectServer(); } void ViewContainer::showJoinChannelDialog() { Server* server = nullptr; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (!server) return; QPointer dlg = new Konversation::JoinChannelDialog(server, m_window); if (dlg->exec() == QDialog::Accepted) { Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(dlg->connectionId()); if (server) server->sendJoinCommand(dlg->channel(), dlg->password()); } delete dlg; } void ViewContainer::connectionStateChanged(Server* server, Konversation::ConnectionState state) { Server* updateServer = nullptr; if (m_contextServer) updateServer = m_contextServer; else updateServer = m_frontServer; if (updateServer && updateServer == server) { QAction* action = actionCollection()->action("disconnect_server"); if (action) - action->setEnabled(state == Konversation::SSConnected || state == Konversation::SSConnecting || state == Konversation::SSScheduledToConnect); + action->setEnabled(state == Konversation::Connected || state == Konversation::Connecting || state == Konversation::ScheduledToConnect); action = actionCollection()->action("join_channel"); if (action) - action->setEnabled(state == Konversation::SSConnected); + action->setEnabled(state == Konversation::Connected); if (m_frontView && m_frontView->getServer() == server && m_frontView->getType() == ChatWindow::Channel) { ChatWindow* view = m_frontView; Channel* channel = static_cast(view); action = actionCollection()->action("rejoin_channel"); - if (action) action->setEnabled(state == Konversation::SSConnected && channel->rejoinable()); + if (action) action->setEnabled(state == Konversation::Connected && channel->rejoinable()); } } } void ViewContainer::channelJoined(Channel* channel) { ChatWindow* view = m_frontView; if (view == channel) { QAction* action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(false); } } Channel* ViewContainer::addChannel(Server* server, const QString& name) { Channel* channel=new Channel(m_tabWidget, name); channel->setServer(server); channel->setName(name); //still have to do this for now addView(channel, name); connect(this, SIGNAL(updateChannelAppearance()), channel, SLOT(updateAppearance())); connect(channel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(server, SIGNAL(awayState(bool)), channel, SLOT(indicateAway(bool)) ); connect(channel, SIGNAL(joined(Channel*)), this, SLOT(channelJoined(Channel*))); return channel; } void ViewContainer::rejoinChannel() { Channel* channel = nullptr; if (m_popupViewIndex == -1) channel = static_cast(m_tabWidget->currentWidget()); else channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (channel && channel->getType() == ChatWindow::Channel) channel->rejoin(); } void ViewContainer::openChannelSettings() { if (m_frontView->getType() == ChatWindow::Channel) { Channel* channel = static_cast(m_tabWidget->currentWidget()); channel->showOptionsDialog(); } } void ViewContainer::toggleChannelNicklists() { KToggleAction* action = static_cast(actionCollection()->action("hide_nicknamelist")); if (action) { Preferences::self()->setShowNickList(action->isChecked()); Preferences::self()->save(); emit updateChannelAppearance(); } } Query* ViewContainer::addQuery(Server* server, const NickInfoPtr& nickInfo, bool weinitiated) { QString name = nickInfo->getNickname(); Query* query=new Query(m_tabWidget, name); query->setServer(server); query->setNickInfo(nickInfo); //still have to do this addView(query, name, weinitiated); // About to increase the number of queries, so enable the close action if (m_queryViewCount == 0) actionCollection()->action("close_queries")->setEnabled(true); ++m_queryViewCount; connect(query, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(query, SIGNAL(updateQueryChrome(ChatWindow*,QString)), this, SLOT(updateQueryChrome(ChatWindow*,QString))); connect(server, SIGNAL(awayState(bool)), query, SLOT(indicateAway(bool))); return query; } void ViewContainer::updateQueryChrome(ChatWindow* view, const QString& name) { //FIXME: updateQueryChrome is a last minute fix for 0.19 because // the updateInfo mess is indecipherable. Replace with a sane and // encompassing system. QString newName = Konversation::removeIrcMarkup(name); if (!newName.isEmpty() && m_tabWidget->tabText(m_tabWidget->indexOf(view)) != newName) { int tabIndex = m_tabWidget->indexOf(view); m_tabWidget->setTabText(tabIndex, newName); const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DisplayRole); } if (!newName.isEmpty() && view==m_frontView) emit setWindowCaption(newName); } void ViewContainer::closeQueries() { int total=m_tabWidget->count()-1; int operations = 0; ChatWindow* nextPage; for (int i=0; i <=total; i++) { if (operations > total) break; nextPage = static_cast(m_tabWidget->widget(i)); if (nextPage && nextPage->getType()==ChatWindow::Query) { closeView(nextPage); if (m_tabWidget->indexOf(nextPage) == -1) --i; } ++operations; } actionCollection()->action("close_queries")->setEnabled(false); } ChannelListPanel* ViewContainer::addChannelListPanel(Server* server) { ChannelListPanel* channelListPanel=new ChannelListPanel(m_tabWidget); channelListPanel->setServer(server); addView(channelListPanel, i18n("Channel List")); KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if ((server == m_frontServer) && action) action->setChecked(true); return channelListPanel; } void ViewContainer::openChannelList(Server* server, const QString& filter, bool getList) { if (!server) server = m_frontServer; if (!server) { KMessageBox::information(m_window, i18n( "To know which server to display the channel list " "for, the list can only be opened from a " "query, channel or status window." ), i18n("Channel List"), "ChannelListNoServerSelected"); return; } ChannelListPanel* panel = server->getChannelListPanel(); if (panel && filter.isEmpty()) { closeView(panel); if (server == m_frontServer) { KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if (action) action->setChecked(false); } return; } if (!panel) { int ret = KMessageBox::Continue; if (filter.isEmpty()) { ret = KMessageBox::warningContinueCancel(m_window, i18n("Using this function may result in a lot " "of network traffic. If your connection is not fast " "enough, it is possible that your client will be " "disconnected by the server."), i18n("Channel List Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "ChannelListWarning"); } if (ret != KMessageBox::Continue) { if (server == m_frontServer) { KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if (action) action->setChecked(false); } return; } panel = server->addChannelListPanel(); } panel->setFilter(filter); if (getList) panel->refreshList(); } void ViewContainer::openNicksOnlinePanel() { if (!m_nicksOnlinePanel) { m_nicksOnlinePanel=new NicksOnline(m_window); addView(m_nicksOnlinePanel, i18n("Watched Nicks")); connect(m_nicksOnlinePanel, SIGNAL(doubleClicked(int,QString)), m_window, SLOT(notifyAction(int,QString))); connect(m_nicksOnlinePanel, SIGNAL(showView(ChatWindow*)), this, SLOT(showView(ChatWindow*))); connect(m_window, SIGNAL(nicksNowOnline(Server*)), m_nicksOnlinePanel, SLOT(updateServerOnlineList(Server*))); (dynamic_cast(actionCollection()->action("open_nicksonline_window")))->setChecked(true); } else { closeNicksOnlinePanel(); } } void ViewContainer::closeNicksOnlinePanel() { delete m_nicksOnlinePanel; m_nicksOnlinePanel = nullptr; (dynamic_cast(actionCollection()->action("open_nicksonline_window")))->setChecked(false); } /*! \fn ViewContainer::frontServerChanging(Server *newServer) This signal is emitted immediately before the front server is changed. If the server is being removed this will fire with a null pointer. */