diff --git a/src/application.cpp b/src/application.cpp index be773cd4..c179bd6a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,1403 +1,1403 @@ /* 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 "application.h" #include "connectionmanager.h" #include "scriptlauncher.h" #include "transfermanager.h" #include "viewcontainer.h" #include "urlcatcher.h" #include "highlight.h" #include "server.h" #include "sound.h" #include "quickconnectdialog.h" #include "dbus.h" #include "servergroupsettings.h" #include "serversettings.h" #include "channel.h" #include "images.h" #include "notificationhandler.h" #include "awaymanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; Application::Application(int &argc, char **argv) : QApplication(argc, argv) { mainWindow = nullptr; m_restartScheduled = false; m_connectionManager = nullptr; m_awayManager = nullptr; m_scriptLauncher = nullptr; quickConnectDialog = nullptr; osd = nullptr; m_wallet = nullptr; m_images = nullptr; m_sound = nullptr; m_dccTransferManager = nullptr; m_notificationHandler = nullptr; m_urlModel = nullptr; dbusObject = nullptr; identDBus = nullptr; m_networkConfigurationManager = nullptr; } Application::~Application() { qDebug(); if (!m_images) return; // Nothing to do, newInstance() has never been called. stashQueueRates(); Preferences::self()->save(); // FIXME i can't figure out why this isn't in saveOptions --argonel saveOptions(false); // Delete m_dccTransferManager here as its destructor depends on the main loop being in tact which it // won't be if if we wait till Qt starts deleting parent pointers. delete m_dccTransferManager; delete m_images; delete m_sound; //delete dbusObject; //delete prefsDCOP; //delete identDBus; delete osd; osd = nullptr; closeWallet(); delete m_networkConfigurationManager; if (m_restartScheduled) implementRestart(); } void Application::implementRestart() { // Pop off the executable name. May not be the first argument in argv // everywhere, so verify first. if (QFileInfo(m_restartArguments.first()) == QFileInfo(QCoreApplication::applicationFilePath())) m_restartArguments.removeFirst(); // Don't round-trip --restart. m_restartArguments.removeAll(QStringLiteral("--restart")); // Avoid accumulating multiple --startupdelay arguments across multiple // uses of restart(). if (m_restartArguments.contains(QStringLiteral("--startupdelay"))) { int index = m_restartArguments.lastIndexOf(QStringLiteral("--startupdelay")); if (index < m_restartArguments.count() - 1 && !m_restartArguments.at(index + 1).startsWith(QLatin1Char('-'))) { QString delayArgument = m_restartArguments.at(index + 1); bool ok; uint delay = delayArgument.toUInt(&ok, 10); // If the argument is invalid or too low, raise to at least 2000 msecs. if (!ok || delay < 2000) m_restartArguments.replace(index + 1, QStringLiteral("2000")); } } else m_restartArguments << QStringLiteral("--startupdelay") << QStringLiteral("2000"); KProcess::startDetached(QCoreApplication::applicationFilePath(), m_restartArguments); } void Application::newInstance(QCommandLineParser *args) { QString url; if (args->positionalArguments().count() > 1) url = args->positionalArguments().at(1); if (!mainWindow) { connect(this, &Application::aboutToQuit, this, &Application::prepareShutdown); m_connectionManager = new ConnectionManager(this); m_awayManager = new AwayManager(this); connect(m_connectionManager, &ConnectionManager::identityOnline, m_awayManager, &AwayManager::identityOnline); connect(m_connectionManager, &ConnectionManager::identityOffline, m_awayManager, &AwayManager::identityOffline); connect(m_connectionManager, &ConnectionManager::connectionChangedAwayState, m_awayManager, &AwayManager::updateGlobalAwayAction); m_networkConfigurationManager = new QNetworkConfigurationManager(); connect(m_networkConfigurationManager, SIGNAL(onlineStateChanged(bool)), m_connectionManager, SLOT(onOnlineStateChanged(bool))); m_scriptLauncher = new ScriptLauncher(this); // an instance of DccTransferManager needs to be created before GUI class instances' creation. m_dccTransferManager = new DCC::TransferManager(this); // make sure all vars are initialized properly quickConnectDialog = nullptr; // Sound object used to play sound is created when needed. m_sound = nullptr; // initialize OSD display here, so we can read the Preferences::properly osd = new OSDWidget( QStringLiteral("Konversation") ); Preferences::self(); readOptions(); // Images object providing LEDs, NickIcons m_images = new Images(); m_urlModel = new QStandardItemModel(0, 3, this); // Auto-alias scripts. This adds any missing aliases QStringList aliasList(Preferences::self()->aliasList()); const QStringList scripts(Preferences::defaultAliasList()); bool changed = false; for ( QStringList::ConstIterator it = scripts.constBegin(); it != scripts.constEnd(); ++it ) { if(!aliasList.contains(*it)) { changed = true; aliasList.append(*it); } } if(changed) Preferences::self()->setAliasList(aliasList); // open main window mainWindow = new MainWindow(); connect(mainWindow.data(), &MainWindow::showQuickConnectDialog, this, &Application::openQuickConnectDialog); connect(Preferences::self(), &Preferences::updateTrayIcon, mainWindow.data(), &MainWindow::updateTrayIcon); connect(mainWindow.data(), &MainWindow::endNotification, osd, &OSDWidget::hide); // take care of user style changes, setting back colors and stuff // apply GUI settings emit appearanceChanged(); if (Preferences::self()->showTrayIcon() && Preferences::self()->hideToTrayOnStartup()) mainWindow->hide(); else mainWindow->show(); bool openServerList = Preferences::self()->showServerList(); // handle autoconnect on startup Konversation::ServerGroupHash serverGroups = Preferences::serverGroupHash(); if (!args->isSet(QStringLiteral("noautoconnect")) && url.isEmpty() && !args->isSet(QStringLiteral("server"))) { QList serversToAutoconnect; QHashIterator it(serverGroups); while(it.hasNext()) { it.next(); if (it.value()->autoConnectEnabled()) { openServerList = false; serversToAutoconnect << it.value(); } } std::sort(serversToAutoconnect.begin(), serversToAutoconnect.end(), [] (const ServerGroupSettingsPtr &left, const ServerGroupSettingsPtr &right) { return left->sortIndex() < right->sortIndex(); }); for (QList::iterator it = serversToAutoconnect.begin(); it != serversToAutoconnect.end(); ++it) { m_connectionManager->connectTo(Konversation::CreateNewConnection, (*it)->id()); } } if (openServerList) mainWindow->openServerList(); connect(this, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), this, SLOT(saveOptions())); // prepare dbus interface dbusObject = new Konversation::DBus(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/irc"), dbusObject, QDBusConnection::ExportNonScriptableSlots); identDBus = new Konversation::IdentDBus(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/identity"), identDBus, QDBusConnection::ExportNonScriptableSlots); if (dbusObject) { connect(dbusObject,SIGNAL (dbusMultiServerRaw(QString)), this,SLOT (dbusMultiServerRaw(QString)) ); connect(dbusObject,SIGNAL (dbusRaw(QString,QString)), this,SLOT (dbusRaw(QString,QString)) ); connect(dbusObject,SIGNAL (dbusSay(QString,QString,QString)), this,SLOT (dbusSay(QString,QString,QString)) ); connect(dbusObject,SIGNAL (dbusInfo(QString)), this,SLOT (dbusInfo(QString)) ); connect(dbusObject,SIGNAL (dbusInsertMarkerLine()), mainWindow,SIGNAL(insertMarkerLine())); connect(dbusObject, SIGNAL(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), m_connectionManager, SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); } m_notificationHandler = new Konversation::NotificationHandler(this); connect(this, &Application::appearanceChanged, this, &Application::updateProxySettings); } else if (args->isSet(QStringLiteral("restart"))) { restart(); return; } if (!url.isEmpty()) getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, url); else if (args->isSet(QStringLiteral("server"))) { getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, args->value(QStringLiteral("server")), args->value(QStringLiteral("port")), args->value(QStringLiteral("password")), args->value(QStringLiteral("nick")), args->value(QStringLiteral("channel")), args->isSet(QStringLiteral("ssl"))); } return; } Application* Application::instance() { return qobject_cast(QApplication::instance()); } void Application::restart() { m_restartScheduled = true; mainWindow->quitProgram(); } void Application::prepareShutdown() { if (mainWindow) mainWindow->getViewContainer()->prepareShutdown(); if (m_awayManager) { m_awayManager->blockSignals(true); delete m_awayManager; m_awayManager = nullptr; } if (m_connectionManager) { m_connectionManager->quitServers(); m_connectionManager->blockSignals(true); delete m_connectionManager; m_connectionManager = nullptr; } } bool Application::event(QEvent* event) { if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ApplicationFontChange) { emit appearanceChanged(); } return QApplication::event(event); } void Application::showQueueTuner(bool p) { getMainWindow()->getViewContainer()->showQueueTuner(p); } void Application::dbusMultiServerRaw(const QString &command) { sendMultiServerCommand(command.section(QLatin1Char(' '), 0,0), command.section(QLatin1Char(' '), 1)); } void Application::dbusRaw(const QString& connection, const QString &command) { Server* server = getConnectionManager()->getServerByName(connection, ConnectionManager::MatchByIdThenName); if (server) server->dbusRaw(command); } void Application::dbusSay(const QString& connection, const QString& target, const QString& command) { Server* server = getConnectionManager()->getServerByName(connection, ConnectionManager::MatchByIdThenName); if (server) server->dbusSay(target, command); } void Application::dbusInfo(const QString& string) { mainWindow->getViewContainer()->appendToFrontmost(i18n("D-Bus"), string, nullptr); } void Application::readOptions() { // get standard config file // read nickname sorting order for channel nick lists KConfigGroup cgSortNicknames(KSharedConfig::openConfig()->group("Sort Nicknames")); QString sortOrder=cgSortNicknames.readEntry("SortOrder"); QStringList sortOrderList=sortOrder.split(QString()); sortOrderList.sort(); if (sortOrderList.join(QString())!=QStringLiteral("-hopqv")) { sortOrder=Preferences::defaultNicknameSortingOrder(); Preferences::self()->setSortOrder(sortOrder); } // Identity list QStringList identityList=KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("Identity [0-9]+"))); if (!identityList.isEmpty()) { Preferences::clearIdentityList(); for(int index=0;indexgroup(identityList[index])); newIdentity->setName(cgIdentity.readEntry("Name")); newIdentity->setIdent(cgIdentity.readEntry("Ident")); newIdentity->setRealName(cgIdentity.readEntry("Realname")); newIdentity->setNicknameList(cgIdentity.readEntry("Nicknames",QStringList())); newIdentity->setAuthType(cgIdentity.readEntry("AuthType", "nickserv")); newIdentity->setAuthPassword(cgIdentity.readEntry("Password")); newIdentity->setNickservNickname(cgIdentity.readEntry("Bot")); newIdentity->setNickservCommand(cgIdentity.readEntry("NickservCommand", "identify")); newIdentity->setSaslAccount(cgIdentity.readEntry("SaslAccount")); newIdentity->setPemClientCertFile(cgIdentity.readEntry("PemClientCertFile", QUrl())); newIdentity->setInsertRememberLineOnAway(cgIdentity.readEntry("InsertRememberLineOnAway", false)); newIdentity->setRunAwayCommands(cgIdentity.readEntry("ShowAwayMessage", false)); newIdentity->setAwayCommand(cgIdentity.readEntry("AwayMessage")); newIdentity->setReturnCommand(cgIdentity.readEntry("ReturnMessage")); newIdentity->setAutomaticAway(cgIdentity.readEntry("AutomaticAway", false)); newIdentity->setAwayInactivity(cgIdentity.readEntry("AwayInactivity", 10)); newIdentity->setAutomaticUnaway(cgIdentity.readEntry("AutomaticUnaway", false)); newIdentity->setQuitReason(cgIdentity.readEntry("QuitReason")); newIdentity->setPartReason(cgIdentity.readEntry("PartReason")); newIdentity->setKickReason(cgIdentity.readEntry("KickReason")); newIdentity->setShellCommand(cgIdentity.readEntry("PreShellCommand")); newIdentity->setCodecName(cgIdentity.readEntry("Codec")); newIdentity->setAwayMessage(cgIdentity.readEntry("AwayReason")); newIdentity->setAwayNickname(cgIdentity.readEntry("AwayNick")); Preferences::addIdentity(newIdentity); } } osd->setEnabled(Preferences::self()->useOSD()); //How to load the font from the text? osd->setFont(Preferences::self()->oSDFont()); osd->setDuration(Preferences::self()->oSDDuration()); osd->setScreen(Preferences::self()->oSDScreen()); osd->setShadow(Preferences::self()->oSDDrawShadow()); osd->setOffset(Preferences::self()->oSDOffsetX(), Preferences::self()->oSDOffsetY()); osd->setAlignment((OSDWidget::Alignment)Preferences::self()->oSDAlignment()); if(Preferences::self()->oSDUseCustomColors()) { osd->setTextColor(Preferences::self()->oSDTextColor()); QPalette p = osd->palette(); p.setColor(osd->backgroundRole(), Preferences::self()->oSDBackgroundColor()); osd->setPalette(p); } // Check if there is old server list config //TODO FIXME why are we doing this here? KConfigGroup cgServerList(KSharedConfig::openConfig()->group("Server List")); // Read the new server settings QStringList groups = KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("ServerGroup [0-9]+"))); QMap notifyList; QList sgKeys; if(!groups.isEmpty()) { Konversation::ServerGroupHash serverGroups; QStringList::iterator it; QStringList tmp1; QStringList::iterator it2; int index = 0; Konversation::ChannelList channelHistory; Konversation::ServerSettings server; Konversation::ChannelSettings channel; for (it = groups.begin(); it != groups.end(); ++it) { KConfigGroup cgServerGroup(KSharedConfig::openConfig()->group(*it)); Konversation::ServerGroupSettingsPtr serverGroup(new Konversation::ServerGroupSettings); serverGroup->setName(cgServerGroup.readEntry("Name")); serverGroup->setSortIndex(groups.at(index).section(' ', -1).toInt() ); serverGroup->setIdentityId(Preferences::identityByName(cgServerGroup.readEntry("Identity"))->id()); serverGroup->setConnectCommands(cgServerGroup.readEntry("ConnectCommands")); serverGroup->setAutoConnectEnabled(cgServerGroup.readEntry("AutoConnect", false)); serverGroup->setNotificationsEnabled(cgServerGroup.readEntry("EnableNotifications", true)); serverGroup->setExpanded(cgServerGroup.readEntry("Expanded", false)); notifyList.insert((*serverGroup).id(), cgServerGroup.readEntry("NotifyList", QString()).split(QLatin1Char(' '), QString::SkipEmptyParts)); tmp1 = cgServerGroup.readEntry("ServerList", QStringList()); for (it2 = tmp1.begin(); it2 != tmp1.end(); ++it2) { KConfigGroup cgServer(KSharedConfig::openConfig()->group(*it2)); server.setHost(cgServer.readEntry("Server")); server.setPort(cgServer.readEntry("Port", 0)); server.setPassword(cgServer.readEntry("Password")); server.setSSLEnabled(cgServer.readEntry("SSLEnabled", false)); server.setBypassProxy(cgServer.readEntry("BypassProxy", false)); serverGroup->addServer(server); } //config->setGroup((*it)); tmp1 = cgServerGroup.readEntry("AutoJoinChannels", QStringList()); for (it2 = tmp1.begin(); it2 != tmp1.end(); ++it2) { KConfigGroup cgJoin(KSharedConfig::openConfig()->group(*it2)); if (!cgJoin.readEntry("Name").isEmpty()) { channel.setName(cgJoin.readEntry("Name")); channel.setPassword(cgJoin.readEntry("Password")); serverGroup->addChannel(channel); } } //config->setGroup((*it)); tmp1 = cgServerGroup.readEntry("ChannelHistory", QStringList()); channelHistory.clear(); for (it2 = tmp1.begin(); it2 != tmp1.end(); ++it2) { KConfigGroup cgChanHistory(KSharedConfig::openConfig()->group(*it2)); if (!cgChanHistory.readEntry("Name").isEmpty()) { channel.setName(cgChanHistory.readEntry("Name")); channel.setPassword(cgChanHistory.readEntry("Password")); channel.setNotificationsEnabled(cgChanHistory.readEntry("EnableNotifications", true)); channelHistory.append(channel); } } serverGroup->setChannelHistory(channelHistory); serverGroups.insert(serverGroup->id(), serverGroup); sgKeys.append(serverGroup->id()); index++; } Preferences::setServerGroupHash(serverGroups); } // Notify Settings and lists. Must follow Server List. Preferences::setNotifyList(notifyList); Preferences::self()->setNotifyDelay(Preferences::self()->notifyDelay()); Preferences::self()->setUseNotify(Preferences::self()->useNotify()); // Quick Buttons List // if there are button definitions in the config file, remove default buttons if (KSharedConfig::openConfig()->hasGroup("Button List")) Preferences::clearQuickButtonList(); KConfigGroup cgQuickButtons(KSharedConfig::openConfig()->group("Button List")); // Read all default buttons QStringList buttonList(Preferences::quickButtonList()); // Read all quick buttons int index=0; while (cgQuickButtons.hasKey(QString(QStringLiteral("Button%1")).arg(index))) { buttonList.append(cgQuickButtons.readEntry(QString(QStringLiteral("Button%1")).arg(index++))); } // while // Put back the changed button list Preferences::setQuickButtonList(buttonList); // Autoreplace List // if there are autoreplace definitions in the config file, remove default entries if (KSharedConfig::openConfig()->hasGroup("Autoreplace List")) Preferences::clearAutoreplaceList(); KConfigGroup cgAutoreplace(KSharedConfig::openConfig()->group("Autoreplace List")); // Read all default entries QList autoreplaceList(Preferences::autoreplaceList()); // Read all entries index=0; // legacy code for old autoreplace format 4/6/09 QString autoReplaceString(QStringLiteral("Autoreplace")); while (cgAutoreplace.hasKey(autoReplaceString + QString::number(index))) { // read entry and get length of the string QString entry=cgAutoreplace.readEntry(autoReplaceString + QString::number(index++)); int length=entry.length()-1; // if there's a "#" in the end, strip it (used to preserve blanks at the end of the replacement text) // there should always be one, but older versions did not do it, so we check first if (entry.at(length)==QLatin1Char('#')) entry=entry.left(length); QString regex = entry.section(QLatin1Char(','),0,0); QString direction = entry.section(QLatin1Char(','),1,1); QString pattern = entry.section(QLatin1Char(','),2,2); QString replace = entry.section(QLatin1Char(','),3); // add entry to internal list autoreplaceList.append(QStringList() << regex << direction << pattern << replace); } // while //end legacy code for old autoreplace format index=0; //new code for autoreplace config QString indexString(QString::number(index)); QString regexString(QStringLiteral("Regex")); QString directString(QStringLiteral("Direction")); QString patternString(QStringLiteral("Pattern")); QString replaceString(QStringLiteral("Replace")); while (cgAutoreplace.hasKey(patternString + indexString)) { QString pattern = cgAutoreplace.readEntry(patternString + indexString); QString regex = cgAutoreplace.readEntry(regexString + indexString, QStringLiteral("0")); QString direction = cgAutoreplace.readEntry(directString + indexString, QStringLiteral("o")); QString replace = cgAutoreplace.readEntry(replaceString + indexString, QString()); if (replace.length()>0) { int repLen=replace.length()-1; if (replace.at(repLen)==QLatin1Char('#')) replace=replace.left(repLen); } if (pattern.length()>0) { int patLen=pattern.length()-1; if (pattern.at(patLen)==QLatin1Char('#')) pattern=pattern.left(patLen); } index++; indexString = QString::number(index); autoreplaceList.append(QStringList() << regex << direction << pattern << replace); } // Put back the changed autoreplace list Preferences::setAutoreplaceList(autoreplaceList); //TODO FIXME I assume this is in the group, but I have a hunch we just don't care about <1.0.1 // Highlight List KConfigGroup cgDefault(KSharedConfig::openConfig()->group("")); if (cgDefault.hasKey("Highlight")) // Stay compatible with versions < 0.14 { QString highlight=cgDefault.readEntry("Highlight"); QStringList hiList = highlight.split(QLatin1Char(' '), QString::SkipEmptyParts); for (int hiIndex=0; hiIndex < hiList.count(); hiIndex+=2) { Preferences::addHighlight(hiList[hiIndex], false, QString(QLatin1Char('#')+hiList[hiIndex+1]), QString(), QString(), QString(), true); } cgDefault.deleteEntry("Highlight"); } else { int i = 0; while (KSharedConfig::openConfig()->hasGroup(QString(QStringLiteral("Highlight%1")).arg(i))) { KConfigGroup cgHilight(KSharedConfig::openConfig()->group(QString(QStringLiteral("Highlight%1")).arg(i))); Preferences::addHighlight( cgHilight.readEntry("Pattern"), cgHilight.readEntry("RegExp", false), cgHilight.readEntry("Color", QColor(Qt::black)), cgHilight.readPathEntry("Sound", QString()), cgHilight.readEntry("AutoText"), cgHilight.readEntry("ChatWindows"), cgHilight.readEntry("Notify", true) ); i++; } } // Ignore List KConfigGroup cgIgnoreList(KSharedConfig::openConfig()->group("Ignore List")); // Remove all default entries if there is at least one Ignore in the Preferences::file if (cgIgnoreList.hasKey("Ignore0")) Preferences::clearIgnoreList(); // Read all ignores index=0; while (cgIgnoreList.hasKey(QString(QStringLiteral("Ignore%1")).arg(index))) { Preferences::addIgnore(cgIgnoreList.readEntry(QString(QStringLiteral("Ignore%1")).arg(index++))); } // Aliases KConfigGroup cgAliases(KSharedConfig::openConfig()->group("Aliases")); QStringList newList=cgAliases.readEntry("AliasList", QStringList()); if (!newList.isEmpty()) Preferences::self()->setAliasList(newList); // Channel Encodings //Legacy channel encodings read in Jun. 29, 2009 KConfigGroup cgChannelEncodings(KSharedConfig::openConfig()->group("Channel Encodings")); QMap channelEncodingEntries=cgChannelEncodings.entryMap(); QRegExp re(QStringLiteral("^(.+) ([^\\s]+)$")); QList channelEncodingEntryKeys=channelEncodingEntries.keys(); for(QList::const_iterator itStr=channelEncodingEntryKeys.constBegin(); itStr != channelEncodingEntryKeys.constEnd(); ++itStr) { if(re.indexIn(*itStr) > -1) { Preferences::setChannelEncoding(re.cap(1),re.cap(2),channelEncodingEntries[*itStr]); } } //End legacy channel encodings read in Jun 29, 2009 KConfigGroup cgEncodings(KSharedConfig::openConfig()->group("Encodings")); QMap encodingEntries=cgEncodings.entryMap(); QList encodingEntryKeys=encodingEntries.keys(); QRegExp reg(QStringLiteral("^([^\\s]+) ([^\\s]+)\\s?([^\\s]*)$")); for(QList::const_iterator itStr=encodingEntryKeys.constBegin(); itStr != encodingEntryKeys.constEnd(); ++itStr) { if(reg.indexIn(*itStr) > -1) { if(reg.cap(1) == QStringLiteral("ServerGroup") && !reg.cap(3).isEmpty()) Preferences::setChannelEncoding(sgKeys.at(reg.cap(2).toInt()), reg.cap(3), encodingEntries[*itStr]); else Preferences::setChannelEncoding(reg.cap(1), reg.cap(2), encodingEntries[*itStr]); } } // Spell Checking Languages KConfigGroup cgSpellCheckingLanguages(KSharedConfig::openConfig()->group("Spell Checking Languages")); QMap spellCheckingLanguageEntries=cgSpellCheckingLanguages.entryMap(); QList spellCheckingLanguageEntryKeys=spellCheckingLanguageEntries.keys(); for (QList::const_iterator itStr=spellCheckingLanguageEntryKeys.constBegin(); itStr != spellCheckingLanguageEntryKeys.constEnd(); ++itStr) { if (reg.indexIn(*itStr) > -1) { if (reg.cap(1) == QStringLiteral("ServerGroup") && !reg.cap(3).isEmpty()) { ServerGroupSettingsPtr serverGroup = Preferences::serverGroupById(sgKeys.at(reg.cap(2).toInt())); if (serverGroup) Preferences::setSpellCheckingLanguage(serverGroup, reg.cap(3), spellCheckingLanguageEntries[*itStr]); } else Preferences::setSpellCheckingLanguage(reg.cap(1), reg.cap(2), spellCheckingLanguageEntries[*itStr]); } } fetchQueueRates(); updateProxySettings(); } void Application::saveOptions(bool updateGUI) { // template: KConfigGroup (KSharedConfig::openConfig()->group( )); //KConfig* config=KSharedConfig::openConfig(); // Should be handled in NicklistBehaviorConfigController now // config->setGroup("Sort Nicknames"); // Clean up identity list QStringList identities=KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("Identity [0-9]+"))); if (identities.count()) { // remove old identity list from Preferences::file to keep numbering under control for (int index=0; index < identities.count(); index++) KSharedConfig::openConfig()->deleteGroup(identities[index]); } IdentityList identityList = Preferences::identityList(); int index = 0; for (IdentityList::ConstIterator it = identityList.constBegin(); it != identityList.constEnd(); ++it) { IdentityPtr identity = (*it); KConfigGroup cgIdentity(KSharedConfig::openConfig()->group(QString(QStringLiteral("Identity %1")).arg(index))); cgIdentity.writeEntry("Name",identity->getName()); cgIdentity.writeEntry("Ident",identity->getIdent()); cgIdentity.writeEntry("Realname",identity->getRealName()); cgIdentity.writeEntry("Nicknames",identity->getNicknameList()); cgIdentity.writeEntry("AuthType",identity->getAuthType()); cgIdentity.writeEntry("Password",identity->getAuthPassword()); cgIdentity.writeEntry("Bot",identity->getNickservNickname()); cgIdentity.writeEntry("NickservCommand",identity->getNickservCommand()); cgIdentity.writeEntry("SaslAccount",identity->getSaslAccount()); cgIdentity.writeEntry("PemClientCertFile", identity->getPemClientCertFile().toString()); cgIdentity.writeEntry("InsertRememberLineOnAway", identity->getInsertRememberLineOnAway()); cgIdentity.writeEntry("ShowAwayMessage",identity->getRunAwayCommands()); cgIdentity.writeEntry("AwayMessage",identity->getAwayCommand()); cgIdentity.writeEntry("ReturnMessage",identity->getReturnCommand()); cgIdentity.writeEntry("AutomaticAway", identity->getAutomaticAway()); cgIdentity.writeEntry("AwayInactivity", identity->getAwayInactivity()); cgIdentity.writeEntry("AutomaticUnaway", identity->getAutomaticUnaway()); cgIdentity.writeEntry("QuitReason",identity->getQuitReason()); cgIdentity.writeEntry("PartReason",identity->getPartReason()); cgIdentity.writeEntry("KickReason",identity->getKickReason()); cgIdentity.writeEntry("PreShellCommand",identity->getShellCommand()); cgIdentity.writeEntry("Codec",identity->getCodecName()); cgIdentity.writeEntry("AwayReason",identity->getAwayMessage()); cgIdentity.writeEntry("AwayNick", identity->getAwayNickname()); index++; } // endfor // Remove the old servergroups from the config QStringList groups = KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("ServerGroup [0-9]+"))); if (groups.count()) { QStringList::iterator it; for(it = groups.begin(); it != groups.end(); ++it) { KSharedConfig::openConfig()->deleteGroup((*it)); } } // Remove the old servers from the config groups = KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("Server [0-9]+"))); if (groups.count()) { QStringList::iterator it; for(it = groups.begin(); it != groups.end(); ++it) { KSharedConfig::openConfig()->deleteGroup((*it)); } } // Remove the old channels from the config groups = KSharedConfig::openConfig()->groupList().filter(QRegExp(QStringLiteral("Channel [0-9]+"))); if (groups.count()) { QStringList::iterator it; for(it = groups.begin(); it != groups.end(); ++it) { KSharedConfig::openConfig()->deleteGroup((*it)); } } // Add the new servergroups to the config Konversation::ServerGroupHash serverGroupHash = Preferences::serverGroupHash(); QHashIterator hashIt(serverGroupHash); QMap sortedServerGroupMap; // Make the indices in the group headers reflect the server list dialog sorting. while (hashIt.hasNext()) { hashIt.next(); sortedServerGroupMap.insert(hashIt.value()->sortIndex(), hashIt.value()); } QMapIterator it(sortedServerGroupMap); index = 0; int index2 = 0; int index3 = 0; int width = 0; QList keys = serverGroupHash.keys(); for(int i=0; i sgKeys; while(it.hasNext()) { it.next(); serverlist = (it.value())->serverList(); servers.clear(); sgKeys.append(it.value()->id()); for(it2 = serverlist.begin(); it2 != serverlist.end(); ++it2) { groupName = QString(QStringLiteral("Server %1")).arg(index2); servers.append(groupName); KConfigGroup cgServer(KSharedConfig::openConfig()->group(groupName)); cgServer.writeEntry("Server", (*it2).host()); cgServer.writeEntry("Port", (*it2).port()); cgServer.writeEntry("Password", (*it2).password()); cgServer.writeEntry("SSLEnabled", (*it2).SSLEnabled()); cgServer.writeEntry("BypassProxy", (*it2).bypassProxy()); index2++; } channelList = it.value()->channelList(); channels.clear(); for(it3 = channelList.begin(); it3 != channelList.end(); ++it3) { groupName = QString(QStringLiteral("Channel %1")).arg(index3); channels.append(groupName); KConfigGroup cgChannel(KSharedConfig::openConfig()->group(groupName)); cgChannel.writeEntry("Name", (*it3).name()); cgChannel.writeEntry("Password", (*it3).password()); index3++; } channelList = it.value()->channelHistory(); channelHistory.clear(); for(it3 = channelList.begin(); it3 != channelList.end(); ++it3) { // TODO FIXME: is it just me or is this broken? groupName = QString(QStringLiteral("Channel %1")).arg(index3); channelHistory.append(groupName); KConfigGroup cgChannelHistory(KSharedConfig::openConfig()->group(groupName)); cgChannelHistory.writeEntry("Name", (*it3).name()); cgChannelHistory.writeEntry("Password", (*it3).password()); cgChannelHistory.writeEntry("EnableNotifications", (*it3).enableNotifications()); index3++; } QString sgn = QString(QStringLiteral("ServerGroup %1")).arg(QString::number(index).rightJustified(width,QLatin1Char('0'))); KConfigGroup cgServerGroup(KSharedConfig::openConfig()->group(sgn)); cgServerGroup.writeEntry("Name", it.value()->name()); cgServerGroup.writeEntry("Identity", it.value()->identity()->getName()); cgServerGroup.writeEntry("ServerList", servers); cgServerGroup.writeEntry("AutoJoinChannels", channels); cgServerGroup.writeEntry("ConnectCommands", it.value()->connectCommands()); cgServerGroup.writeEntry("AutoConnect", it.value()->autoConnectEnabled()); cgServerGroup.writeEntry("ChannelHistory", channelHistory); cgServerGroup.writeEntry("EnableNotifications", it.value()->enableNotifications()); cgServerGroup.writeEntry("Expanded", it.value()->expanded()); cgServerGroup.writeEntry("NotifyList",Preferences::notifyStringByGroupId(it.value()->id())); index++; } KSharedConfig::openConfig()->deleteGroup("Server List"); // Ignore List KSharedConfig::openConfig()->deleteGroup("Ignore List"); KConfigGroup cgIgnoreList(KSharedConfig::openConfig()->group("Ignore List")); QList ignoreList=Preferences::ignoreList(); for (int i = 0; i < ignoreList.size(); ++i) { cgIgnoreList.writeEntry(QString(QStringLiteral("Ignore%1")).arg(i),QString(QStringLiteral("%1,%2")).arg(ignoreList.at(i)->getName()).arg(ignoreList.at(i)->getFlags())); } // Channel Encodings // remove all entries once KSharedConfig::openConfig()->deleteGroup("Channel Encodings"); // legacy Jun 29, 2009 KSharedConfig::openConfig()->deleteGroup("Encodings"); KConfigGroup cgEncoding(KSharedConfig::openConfig()->group("Encodings")); QList encServers=Preferences::channelEncodingsServerGroupIdList(); //i have no idea these would need to be sorted //encServers.sort(); QList::iterator encServer; for ( encServer = encServers.begin(); encServer != encServers.end(); ++encServer ) { Konversation::ServerGroupSettingsPtr sgsp = Preferences::serverGroupById(*encServer); if ( sgsp ) // sgsp == 0 when the entry is of QuickConnect or something? { QStringList encChannels=Preferences::channelEncodingsChannelList(*encServer); //ditto //encChannels.sort(); QStringList::iterator encChannel; for ( encChannel = encChannels.begin(); encChannel != encChannels.end(); ++encChannel ) { QString enc = Preferences::channelEncoding(*encServer, *encChannel); QString key = QLatin1Char(' ') + (*encChannel); if(sgKeys.contains(*encServer)) key.prepend(QStringLiteral("ServerGroup ")+QString::number(sgKeys.indexOf(*encServer))); else key.prepend(sgsp->name()); cgEncoding.writeEntry(key, enc); } } } // Spell Checking Languages KSharedConfig::openConfig()->deleteGroup("Spell Checking Languages"); KConfigGroup cgSpellCheckingLanguages(KSharedConfig::openConfig()->group("Spell Checking Languages")); QHashIterator > i(Preferences::serverGroupSpellCheckingLanguages()); while (i.hasNext()) { i.next(); QHashIterator i2(i.value()); while (i2.hasNext()) { i2.next(); int serverGroupIndex = sgKeys.indexOf(i.key()->id()); if (serverGroupIndex != -1) cgSpellCheckingLanguages.writeEntry(QStringLiteral("ServerGroup ") + QString::number(serverGroupIndex) + QLatin1Char(' ') + i2.key(), i2.value()); } } QHashIterator > i3(Preferences::serverSpellCheckingLanguages()); while (i3.hasNext()) { i3.next(); QHashIterator i4(i3.value()); while (i4.hasNext()) { i4.next(); cgSpellCheckingLanguages.writeEntry(i3.key() + QLatin1Char(' ') + i4.key(), i4.value()); } } KSharedConfig::openConfig()->sync(); if(updateGUI) emit appearanceChanged(); } void Application::fetchQueueRates() { //The following rate was found in the rc for all queues, which were deliberately bad numbers chosen for debugging. //Its possible that the static array was constructed or deconstructed at the wrong time, and so those values saved //in the rc. When reading the values out of the rc, we must check to see if they're this specific value, //and if so, reset to defaults. --argonel IRCQueue::EmptyingRate shit(6, 50000, IRCQueue::EmptyingRate::Lines); int bad = 0; for (int i=0; i <= countOfQueues(); i++) { QList r = Preferences::self()->queueRate(i); staticrates[i] = IRCQueue::EmptyingRate(r[0], r[1]*1000, IRCQueue::EmptyingRate::RateType(r[2])); if (staticrates[i] == shit) bad++; } if (bad == 3) resetQueueRates(); } void Application::stashQueueRates() { for (int i=0; i <= countOfQueues(); i++) { QList r; r.append(staticrates[i].m_rate); r.append(staticrates[i].m_interval / 1000); r.append(int(staticrates[i].m_type)); Preferences::self()->setQueueRate(i, r); } } void Application::resetQueueRates() { for (int i=0; i <= countOfQueues(); i++) { Preferences::self()->queueRateItem(i)->setDefault(); QList r=Preferences::self()->queueRate(i); staticrates[i]=IRCQueue::EmptyingRate(r[0], r[1]*1000, IRCQueue::EmptyingRate::RateType(r[2])); } } void Application::storeUrl(const QString& origin, const QString& newUrl, const QDateTime& dateTime) { QString url(newUrl); url = url.replace(QStringLiteral("&"), QStringLiteral("&")); QList existing = m_urlModel->findItems(url, Qt::MatchExactly, 1); foreach(QStandardItem* item, existing) { if (m_urlModel->item(item->row(), 0)->data(Qt::DisplayRole).toString() == origin) m_urlModel->removeRow(item->row()); } m_urlModel->insertRow(0); m_urlModel->setData(m_urlModel->index(0, 0), origin, Qt::DisplayRole); m_urlModel->setData(m_urlModel->index(0, 1), url, Qt::DisplayRole); UrlDateItem* dateItem = new UrlDateItem(dateTime); m_urlModel->setItem(0, 2, dateItem); } void Application::openQuickConnectDialog() { quickConnectDialog = new QuickConnectDialog(mainWindow); connect(quickConnectDialog, SIGNAL(connectClicked(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool)), m_connectionManager, SLOT(connectTo(Konversation::ConnectionFlag,QString,QString,QString,QString,QString,bool))); quickConnectDialog->show(); } void Application::sendMultiServerCommand(const QString& command, const QString& parameter) { const QList serverList = getConnectionManager()->getServerList(); foreach (Server* server, serverList) server->executeMultiServerCommand(command, parameter); } void Application::splitNick_Server(const QString& nick_server, QString &ircnick, QString &serverOrGroup) { //kaddresbook uses the utf separator 0xE120, so treat that as a separator as well QString nickServer = nick_server; nickServer.replace(QChar(0xE120), QStringLiteral("@")); ircnick = nickServer.section(QLatin1Char('@'),0,0); serverOrGroup = nickServer.section(QLatin1Char('@'),1); } NickInfoPtr Application::getNickInfo(const QString &ircnick, const QString &serverOrGroup) { const QList serverList = getConnectionManager()->getServerList(); NickInfoPtr nickInfo; QString lserverOrGroup = serverOrGroup.toLower(); foreach (Server* lookServer, serverList) { if (lserverOrGroup.isEmpty() || lookServer->getServerName().toLower()==lserverOrGroup || lookServer->getDisplayName().toLower()==lserverOrGroup) { nickInfo = lookServer->getNickInfo(ircnick); if (nickInfo) return nickInfo; //If we found one } } return (NickInfoPtr)nullptr; } // auto replace on input/output QPair Application::doAutoreplace(const QString& text, bool output, int cursorPos) { // get autoreplace list QList autoreplaceList=Preferences::autoreplaceList(); // working copy QString line=text; // loop through the list of replacement patterns for (int index=0;index -1 && cursorPos >= index) { if (cursorPos < index + captures[0].length()) cursorPos = newIndex; else { if (captures[0].length() > replaceWith.length()) cursorPos -= captures[0].length() - replaceWith.length(); else cursorPos += replaceWith.length() - captures[0].length(); } } index = newIndex; } } while (index >= 0 && index < line.length()); } else { QRegExp needleReg(pattern); needleReg.setPatternSyntax(QRegExp::FixedString); int index=line.indexOf(needleReg); while (index>=0) { int length,nextLength,patLen,repLen; patLen=pattern.length(); repLen=replacement.length(); length=index; length+=patLen; nextLength=length; //nextlength is used to account for the replacement taking up less space QChar before,after; if (index!=0) before = line.at(index-1); if (line.length() > length) after = line.at(length); if (index==0 || before.isSpace() || before.isPunct()) { if (line.length() == length || after.isSpace() || after.isPunct()) { // allow for var expansion in autoreplace replacement = Konversation::doVarExpansion(replacement); line.replace(index,patLen,replacement); nextLength = index+repLen; } } if (cursorPos > -1 && cursorPos >= index) { if (cursorPos < length) cursorPos = nextLength; else { if (patLen > repLen) cursorPos -= patLen - repLen; else cursorPos += repLen - patLen; } } index=line.indexOf(needleReg,nextLength); } } } } return QPair(line, cursorPos); } void Application::doInlineAutoreplace(KTextEdit* textEdit) { QTextCursor cursor(textEdit->document()); cursor.beginEditBlock(); const QPair& replace = Application::instance()->doAutoreplace(textEdit->toPlainText(), true, textEdit->textCursor().position()); cursor.select(QTextCursor::Document); cursor.insertText(replace.first); cursor.setPosition(replace.second); cursor.endEditBlock(); textEdit->setTextCursor(cursor); } void Application::openUrl(const QString& url) { if (!Preferences::self()->useCustomBrowser() || url.startsWith(QLatin1String("mailto:")) || url.startsWith(QLatin1String("amarok:"))) { if (url.startsWith(QLatin1String("irc://")) || url.startsWith(QLatin1String("ircs://"))) Application::instance()->getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, url); else if (url.startsWith(QLatin1String("mailto:"))) QDesktopServices::openUrl(QUrl(url)); else #ifndef Q_OS_WIN new KRun(QUrl(url), Application::instance()->getMainWindow()); #else QDesktopServices::openUrl(QUrl::fromUserInput(url)); #endif } else { QHash map; map.insert(QLatin1Char('u'), url); - const QString cmd = KMacroExpander::expandMacrosShellQuote(Preferences::self()->webBrowserCmd(), map); + const QString cmd = KMacroExpander::expandMacrosShellQuote(Preferences::webBrowserCmd(), map); const QStringList args = KShell::splitArgs(cmd); if (!args.isEmpty()) { KProcess::startDetached(args); return; } } } Konversation::Sound* Application::sound() { if (!m_sound) m_sound = new Konversation::Sound; return m_sound; } void Application::updateProxySettings() { if (Preferences::self()->proxyEnabled()) { QNetworkProxy proxy; if (Preferences::self()->proxyType() == Preferences::Socksv5Proxy) { proxy.setType(QNetworkProxy::Socks5Proxy); } else { proxy.setType(QNetworkProxy::HttpProxy); } proxy.setHostName(Preferences::self()->proxyAddress()); proxy.setPort(Preferences::self()->proxyPort()); proxy.setUser(Preferences::self()->proxyUsername()); QString password; if(wallet()) { int ret = wallet()->readPassword(QStringLiteral("ProxyPassword"), password); if(ret != 0) { qCritical() << "Failed to read the proxy password from the wallet, error code:" << ret; } } proxy.setPassword(password); QNetworkProxy::setApplicationProxy(proxy); } else { QNetworkProxy::setApplicationProxy(QNetworkProxy::DefaultProxy); } } KWallet::Wallet* Application::wallet() { if(!m_wallet) { WId winid = 0; if(mainWindow) winid = mainWindow->winId(); m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), winid); if(!m_wallet) return nullptr; connect(m_wallet, &KWallet::Wallet::walletClosed, this, &Application::closeWallet); if(!m_wallet->hasFolder(QStringLiteral("Konversation"))) { if(!m_wallet->createFolder(QStringLiteral("Konversation"))) { qCritical() << "Failed to create folder Konversation in the network wallet."; closeWallet(); return nullptr; } } if(!m_wallet->setFolder(QStringLiteral("Konversation"))) { qCritical() << "Failed to set active folder to Konversation in the network wallet."; closeWallet(); return nullptr; } } return m_wallet; } void Application::closeWallet() { delete m_wallet; m_wallet = nullptr; } void Application::handleActivate(const QStringList& arguments) { m_commandLineParser->parse(QStringList(applicationFilePath()) << arguments); if(m_commandLineParser->isSet(QStringLiteral("restart"))) { m_restartArguments = arguments; } newInstance(m_commandLineParser); KStartupInfo::setNewStartupId(mainWindow, KStartupInfo::startupId()); mainWindow->show(); mainWindow->raise(); } // 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/config/configdialog.cpp b/src/config/configdialog.cpp index 860f9f75..debb41a3 100644 --- a/src/config/configdialog.cpp +++ b/src/config/configdialog.cpp @@ -1,429 +1,429 @@ /* * This file is part of the KDE libraries * Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net) * Copyright (C) 2003 Waldo Bastian * Copyright (C) 2004 Michael Brade * * Forked from KConfigDialog from KF5::ConfigWidgets to add tree support. * Copyright (C) 2014 Eike Hein * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "configdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include class ConfigDialog::ConfigDialogPrivate { public: ConfigDialogPrivate(ConfigDialog *q, const QString &name, KCoreConfigSkeleton *config) : q(q), shown(false), manager(nullptr) { q->setObjectName(name); q->setWindowTitle(tr("Configure")); q->setFaceType(List); if (!name.isEmpty()) { openDialogs.insert(name, q); } else { QString genericName; genericName.sprintf("SettingsDialog-%p", static_cast(q)); openDialogs.insert(genericName, q); q->setObjectName(genericName); } QDialogButtonBox *buttonBox = q->buttonBox(); buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help); connect(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), q, SLOT(updateSettings())); connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), q, SLOT(updateSettings())); connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), q, SLOT(_k_updateButtons())); connect(buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), q, SLOT(updateWidgets())); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), q, SLOT(updateWidgetsDefault())); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), q, SLOT(_k_updateButtons())); connect(buttonBox->button(QDialogButtonBox::Help), SIGNAL(clicked()), q, SLOT(showHelp())); connect(q, SIGNAL(pageRemoved(KPageWidgetItem*)), q, SLOT(onPageRemoved(KPageWidgetItem*))); manager = new KConfigDialogManager(q, config); setupManagerConnections(manager); setApplyButtonEnabled(false); } KPageWidgetItem *addPageInternal(KPageWidgetItem *parent, QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header); void setupManagerConnections(KConfigDialogManager *manager); void setApplyButtonEnabled(bool enabled); void setRestoreDefaultsButtonEnabled(bool enabled); void _k_updateButtons(); void _k_settingsChangedSlot(); ConfigDialog *q; QString mAnchor; QString mHelpApp; bool shown; KConfigDialogManager *manager; QMap managerForPage; /** * The list of existing dialogs. */ static QHash openDialogs; }; QHash ConfigDialog::ConfigDialogPrivate::openDialogs; ConfigDialog::ConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config) : KPageDialog(parent), d(new ConfigDialogPrivate(this, name, config)) { } ConfigDialog::~ConfigDialog() { ConfigDialogPrivate::openDialogs.remove(objectName()); delete d; } KPageWidgetItem *ConfigDialog::addPage(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage) { Q_ASSERT(page); if (!page) { return nullptr; } KPageWidgetItem *item = d->addPageInternal(nullptr, page, itemName, pixmapName, header); if (manage) { d->manager->addWidget(page); } if (d->shown && manage) { // update the default button if the dialog is shown QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (defaultButton) { bool is_default = defaultButton->isEnabled() && d->manager->isDefault(); defaultButton->setEnabled(!is_default); } } return item; } KPageWidgetItem *ConfigDialog::konviAddSubPage(KPageWidgetItem *parent, QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage) { Q_ASSERT(parent); if (!parent) { return nullptr; } Q_ASSERT(page); if (!page) { return nullptr; } KPageWidgetItem *item = d->addPageInternal(parent, page, itemName, pixmapName, header); if (manage) { d->manager->addWidget(page); } if (d->shown && manage) { // update the default button if the dialog is shown QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (defaultButton) { bool is_default = defaultButton->isEnabled() && d->manager->isDefault(); defaultButton->setEnabled(!is_default); } } return item; } KPageWidgetItem *ConfigDialog::addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName, const QString &header) { Q_ASSERT(page); if (!page) { return nullptr; } KPageWidgetItem *item = d->addPageInternal(nullptr, page, itemName, pixmapName, header); d->managerForPage[page] = new KConfigDialogManager(page, config); d->setupManagerConnections(d->managerForPage[page]); if (d->shown) { // update the default button if the dialog is shown QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (defaultButton) { bool is_default = defaultButton->isEnabled() && d->managerForPage[page]->isDefault(); defaultButton->setEnabled(!is_default); } } return item; } KPageWidgetItem *ConfigDialog::ConfigDialogPrivate::addPageInternal(KPageWidgetItem *parent, QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header) { QWidget *frame = new QWidget(q); QVBoxLayout *boxLayout = new QVBoxLayout(frame); boxLayout->setMargin(0); QScrollArea *scroll = new QScrollArea(q); scroll->setFrameShape(QFrame::NoFrame); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scroll->setWidget(page); scroll->setWidgetResizable(true); scroll->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); boxLayout->addWidget(scroll); KPageWidgetItem *item = new KPageWidgetItem(frame, itemName); item->setHeader(header); if (!pixmapName.isEmpty()) { item->setIcon(QIcon::fromTheme(pixmapName)); } if (parent) { q->KPageDialog::addSubPage(parent, item); } else { q->KPageDialog::addPage(item); } return item; } void ConfigDialog::ConfigDialogPrivate::setupManagerConnections(KConfigDialogManager *manager) { - q->connect(manager, SIGNAL(settingsChanged()), q, SLOT(_k_settingsChangedSlot())); - q->connect(manager, SIGNAL(widgetModified()), q, SLOT(_k_updateButtons())); + ConfigDialog::connect(manager, SIGNAL(settingsChanged()), q, SLOT(_k_settingsChangedSlot())); + ConfigDialog::connect(manager, SIGNAL(widgetModified()), q, SLOT(_k_updateButtons())); QDialogButtonBox *buttonBox = q->buttonBox(); - q->connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings); - q->connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings); - q->connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgets); - q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgetsDefault); + ConfigDialog::connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings); + ConfigDialog::connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings); + ConfigDialog::connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgets); + ConfigDialog::connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgetsDefault); } void ConfigDialog::ConfigDialogPrivate::setApplyButtonEnabled(bool enabled) { QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { applyButton->setEnabled(enabled); } } void ConfigDialog::ConfigDialogPrivate::setRestoreDefaultsButtonEnabled(bool enabled) { QPushButton *restoreDefaultsButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults); if (restoreDefaultsButton) { restoreDefaultsButton->setEnabled(enabled); } } void ConfigDialog::onPageRemoved(KPageWidgetItem *item) { QMap::iterator j = d->managerForPage.begin(); while (j != d->managerForPage.end()) { // there is a manager for this page, so remove it if (item->widget()->isAncestorOf(j.key())) { KConfigDialogManager *manager = j.value(); d->managerForPage.erase(j); delete manager; d->_k_updateButtons(); break; } ++j; } } ConfigDialog *ConfigDialog::exists(const QString &name) { QHash::const_iterator it = ConfigDialogPrivate::openDialogs.constFind(name); if (it != ConfigDialogPrivate::openDialogs.constEnd()) { return *it; } return nullptr; } bool ConfigDialog::showDialog(const QString &name) { ConfigDialog *dialog = exists(name); if (dialog) { dialog->show(); } return (dialog != nullptr); } void ConfigDialog::ConfigDialogPrivate::_k_updateButtons() { static bool only_once = false; if (only_once) { return; } only_once = true; QMap::iterator it; bool has_changed = manager->hasChanged() || q->hasChanged(); for (it = managerForPage.begin(); it != managerForPage.end() && !has_changed; ++it) { has_changed |= (*it)->hasChanged(); } setApplyButtonEnabled(has_changed); bool is_default = manager->isDefault() && q->isDefault(); for (it = managerForPage.begin(); it != managerForPage.end() && is_default; ++it) { is_default &= (*it)->isDefault(); } setRestoreDefaultsButtonEnabled(!is_default); emit q->widgetModified(); only_once = false; } void ConfigDialog::ConfigDialogPrivate::_k_settingsChangedSlot() { // Update the buttons _k_updateButtons(); emit q->settingsChanged(q->objectName()); } void ConfigDialog::showEvent(QShowEvent *e) { if (!d->shown) { QMap::iterator it; updateWidgets(); d->manager->updateWidgets(); for (it = d->managerForPage.begin(); it != d->managerForPage.end(); ++it) { (*it)->updateWidgets(); } bool has_changed = d->manager->hasChanged() || hasChanged(); for (it = d->managerForPage.begin(); it != d->managerForPage.end() && !has_changed; ++it) { has_changed |= (*it)->hasChanged(); } d->setApplyButtonEnabled(has_changed); bool is_default = d->manager->isDefault() && isDefault(); for (it = d->managerForPage.begin(); it != d->managerForPage.end() && is_default; ++it) { is_default &= (*it)->isDefault(); } d->setRestoreDefaultsButtonEnabled(!is_default); d->shown = true; } const QSize & availableSize = QApplication::desktop()->availableGeometry(this).size(); this->setMaximumSize(availableSize); KPageDialog::showEvent(e); } void ConfigDialog::moveEvent(QMoveEvent *e) { const QSize & availableSize = QApplication::desktop()->availableGeometry(this).size(); this->setMaximumSize(availableSize); KPageDialog::moveEvent(e); } void ConfigDialog::updateSettings() { } void ConfigDialog::updateWidgets() { } void ConfigDialog::updateWidgetsDefault() { } bool ConfigDialog::hasChanged() { return false; } bool ConfigDialog::isDefault() { return true; } void ConfigDialog::updateButtons() { d->_k_updateButtons(); } void ConfigDialog::settingsChangedSlot() { d->_k_settingsChangedSlot(); } void ConfigDialog::setHelp(const QString &anchor, const QString &appname) { d->mAnchor = anchor; d->mHelpApp = appname; } void ConfigDialog::showHelp() { KHelpClient::invokeHelp(d->mAnchor, d->mHelpApp); } #include "moc_configdialog.cpp" diff --git a/src/dcc/dccfiledialog.cpp b/src/dcc/dccfiledialog.cpp index 4134f2db..aef76340 100644 --- a/src/dcc/dccfiledialog.cpp +++ b/src/dcc/dccfiledialog.cpp @@ -1,68 +1,68 @@ /* 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) 2011 Bernd Buschinski */ #include "dccfiledialog.h" #include #include #include #include #include #include DccFileDialog::DccFileDialog(QWidget* parent) : QDialog(parent) { QGridLayout* mainLayout = new QGridLayout(this); m_fileWidget = new KFileWidget(QUrl(), this); mainLayout->addWidget(m_fileWidget, 0, 0); m_fileWidget->okButton()->show(); connect(m_fileWidget->okButton(), &QPushButton::clicked, m_fileWidget, &KFileWidget::slotOk); connect(m_fileWidget, &KFileWidget::accepted, m_fileWidget, &KFileWidget::accept); connect(m_fileWidget, &KFileWidget::accepted, this, &QDialog::accept); m_fileWidget->cancelButton()->show(); connect(m_fileWidget->cancelButton(), &QPushButton::clicked, this, &QDialog::reject); m_checkBox = new QCheckBox(i18nc("passive dcc send", "Passive Send")); m_fileWidget->setCustomWidget(m_checkBox); m_checkBox->setCheckState(Preferences::self()->dccPassiveSend() ? Qt::Checked : Qt::Unchecked); } QList DccFileDialog::getOpenUrls(const QUrl &startDir, const QString& filter, const QString& caption) { - m_fileWidget->setStartDir(startDir); + KFileWidget::setStartDir(startDir); m_fileWidget->setFilter(filter); m_fileWidget->setOperationMode( KFileWidget::Opening ); m_fileWidget->setMode( KFile::Files | KFile::ExistingOnly ); setWindowTitle(caption.isEmpty() ? i18n("Open") : caption); exec(); return m_fileWidget->selectedUrls(); } bool DccFileDialog::passiveSend() { return m_checkBox->isChecked(); } QSize DccFileDialog::sizeHint() const { return m_fileWidget->dialogSizeHint(); } diff --git a/src/irc/channel.cpp b/src/irc/channel.cpp index dbe86c59..8a477bce 100644 --- a/src/irc/channel.cpp +++ b/src/irc/channel.cpp @@ -1,2906 +1,2906 @@ /* 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 #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); } 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, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeN, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeS, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeI, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeP, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeM, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeK, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeL, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); connect(modeT, &ModeButton::modeClicked, this, &Channel::modeButtonClicked); 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->setClearButtonEnabled(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, 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) { if (server == m_server) { if (state != Konversation::SSConnected) { 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(); } void Channel::textPasted(const QString& text) { if(m_server) { 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); } } } // 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; } 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; } 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); + OutputFilter::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 == nullptr) { fastAddNickname(channelnick); if(channelnick->isAnyTypeOfOp()) { adjustOps(1); } adjustNicks(1); requestNickListSort(); } // TODO (re)-investigate why it was thought unusual to add an already added nick // -- see bug 333969 } // 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 == nullptr) { 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() { 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))); } 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(0); modeT->setDown(0); modeN->setOn(0); modeN->setDown(0); modeS->setOn(0); modeS->setDown(0); modeI->setOn(0); modeI->setDown(0); modeP->setOn(0); modeP->setDown(0); modeM->setOn(0); modeM->setDown(0); modeK->setOn(0); modeK->setDown(0); modeL->setOn(0); modeL->setDown(0); 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->capabilities() & Server::AwayNotify && !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->capabilities() & Server::UserHostInNames) { 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/channellistpanel.cpp b/src/irc/channellistpanel.cpp index 754b818e..3e40aae5 100644 --- a/src/irc/channellistpanel.cpp +++ b/src/irc/channellistpanel.cpp @@ -1,541 +1,540 @@ /* 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. */ /* Shows the list of channels Copyright (C) 2003 Dario Abatianni Copyright (C) 2009 Travis McHenry */ #include "channellistpanel.h" #include "channel.h" #include "preferences.h" #include "server.h" #include "common.h" #include "application.h" #include #include #include #include #include #include ChannelListModel::ChannelListModel(QObject* parent) : QAbstractListModel(parent) { } void ChannelListModel::append(const ChannelItem& item) { m_channelList.append(item); beginResetModel(); endResetModel(); } int ChannelListModel::columnCount(const QModelIndex& /*parent*/) const { return 3; } int ChannelListModel::rowCount(const QModelIndex& /*parent*/) const { return m_channelList.count(); } QVariant ChannelListModel::data(const QModelIndex& index, int role) const { if(!index.isValid() || index.row() >= m_channelList.count ()) return QVariant(); const ChannelItem& item = m_channelList[index.row()]; if(role == Qt::DisplayRole) { switch(index.column()) { case 0: return item.name; case 1: return item.users; case 2: return item.topic; default: return QVariant(); } } else if(role == Qt::ToolTipRole) { return QString(QLatin1String("") + item.topic.toHtmlEscaped() + QLatin1String("")); } return QVariant(); } QVariant ChannelListModel::headerData (int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Vertical || role != Qt::DisplayRole) return QVariant(); switch(section) { case 0: return i18n("Channel Name"); case 1: return i18n("Users"); case 2: return i18n("Channel Topic"); default: return QVariant(); } } ChannelListProxyModel::ChannelListProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { m_minUsers = 0; m_maxUsers = 0; m_filterChannel = true; m_filterTopic = false; } bool ChannelListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index1 = sourceModel()->index(sourceRow, 1, sourceParent); QModelIndex index2 = sourceModel()->index(sourceRow, 2, sourceParent); return (((m_filterChannel && sourceModel()->data(index0).toString().contains(filterRegExp())) || (m_filterTopic && sourceModel()->data(index2).toString().contains(filterRegExp())) || (!m_filterChannel && !m_filterTopic)) && usersInRange(sourceModel()->data(index1).toInt())); } bool ChannelListProxyModel::usersInRange(int users) const { return (!m_minUsers || users >= m_minUsers) && (!m_maxUsers || users <= m_maxUsers); } void ChannelListProxyModel::setFilterMinimumUsers(int users) { m_minUsers = users; } void ChannelListProxyModel::setFilterMaximumUsers(int users) { m_maxUsers = users; } void ChannelListProxyModel::setFilterTopic(bool filter) { m_filterTopic = filter; } void ChannelListProxyModel::setFilterChannel(bool filter) { m_filterChannel = filter; } ChannelListPanel::ChannelListPanel(QWidget* parent) : ChatWindow(parent) { setType(ChatWindow::ChannelList); m_isTopLevelView = false; setName(i18n("Channel List")); m_firstRun = true; m_regexState = false; m_numUsers = 0; m_numChannels = 0; m_visibleUsers = 0; m_visibleChannels = 0; m_progressTimer = new QTimer(this); m_filterTimer = new QTimer(this); m_filterTimer->setSingleShot(true); m_tempTimer = new QTimer(this); m_tempTimer->setSingleShot(true); setSpacing(0); m_toolBar = new KToolBar(this, true, true); m_toolBar->setObjectName(QStringLiteral("channellistpanel_toolbar")); m_saveList = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18nc("save list", "Save &List..."), this, SLOT(saveList())); m_saveList->setWhatsThis(i18n("Click here to save the channel list.")); m_refreshList = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("refresh list", "&Refresh List"), this, SLOT(refreshList())); m_refreshList->setWhatsThis(i18n("Click here to refresh the channel list.")); m_toolBar->addSeparator(); m_joinChannel = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("irc-join-channel")), i18nc("join channel", "&Join Channel"), this, SLOT(joinChannelClicked())); m_joinChannel->setWhatsThis(i18n("Click here to join the channel. A new tab is created for the channel.")); //UI Setup setupUi(this); m_channelListModel = new ChannelListModel(this); m_proxyModel = new ChannelListProxyModel(this); m_proxyModel->setSourceModel(m_channelListModel); m_channelListView->setModel(m_proxyModel); m_channelListView->header()->resizeSection(1,75); // resize users section to be smaller Preferences::restoreColumnState(m_channelListView, QStringLiteral("ChannelList ViewSettings")); // double click on channel entry joins the channel connect(m_channelListView, &QTreeView::doubleClicked, this, &ChannelListPanel::joinChannelClicked); connect(m_channelListView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(currentChanged(QModelIndex,QModelIndex))); connect(m_channelListView, &QTreeView::customContextMenuRequested, this, &ChannelListPanel::contextMenu); connect(m_regexBox, &QCheckBox::stateChanged, this, &ChannelListPanel::filterChanged); connect(m_topicBox, &QCheckBox::stateChanged, this, &ChannelListPanel::filterChanged); connect(m_channelBox, &QCheckBox::stateChanged, this, &ChannelListPanel::filterChanged); connect(m_minUser, static_cast(&QSpinBox::valueChanged), this, &ChannelListPanel::filterChanged); connect(m_maxUser, static_cast(&QSpinBox::valueChanged), this, &ChannelListPanel::filterChanged); connect(m_filterLine, &KLineEdit::returnPressed, this, &ChannelListPanel::applyFilterClicked); connect(m_filterLine, &KLineEdit::textChanged, this, &ChannelListPanel::filterChanged); connect(m_filterTimer, &QTimer::timeout, this, &ChannelListPanel::updateFilter); connect(m_progressTimer, &QTimer::timeout, this, &ChannelListPanel::setProgress); connect(m_tempTimer, &QTimer::timeout, this, &ChannelListPanel::endOfChannelList); updateUsersChannels(); } ChannelListPanel::~ChannelListPanel() { Preferences::saveColumnState(m_channelListView, QStringLiteral("ChannelList ViewSettings")); } void ChannelListPanel::refreshList() { if (!m_refreshList->isEnabled()) return; m_numUsers = 0; m_numChannels = 0; m_visibleUsers = 0; m_visibleChannels = 0; //hide temporarily to prevent multiple refreshes, but renable if it doesn't start in //3 seconds in case we got a 'server busy' error. It doesn't matter if it's slower than //that because addToChannelList's first run handles this anyway m_refreshList->setEnabled(false); m_tempTimer->start(3000); emit refreshChannelList(); } void ChannelListPanel::addToChannelList(const QString& channel,int users,const QString& topic) { if (m_firstRun) { if(m_tempTimer->isActive()) m_tempTimer->stop(); m_refreshList->setEnabled(false); m_statsLabel->setText(i18n("Refreshing.")); m_progressTimer->start(500); m_firstRun = false; m_channelListModel = new ChannelListModel(this); } ChannelItem item; item.name = channel; item.users = users; item.topic = Konversation::removeIrcMarkup(topic); m_channelListModel->append(item); ++m_numChannels; m_numUsers += users; } void ChannelListPanel::endOfChannelList() { m_progressTimer->stop(); m_proxyModel->setSourceModel(m_channelListModel); m_proxyModel->invalidate(); m_refreshList->setEnabled(true); m_firstRun = true; updateUsersChannels(); } void ChannelListPanel::filterChanged() { m_filterTimer->start(300); } void ChannelListPanel::updateFilter() { QString text = m_filterLine->text(); int max = m_maxUser->value(); int min = m_minUser->value(); bool topic = m_topicBox->isChecked(); bool channel = m_channelBox->isChecked(); bool regex = m_regexBox->isChecked(); bool regexChanged = (regex != m_regexState); if (regexChanged) m_regexState = regex; bool change = false; if (m_proxyModel->filterRegExp().pattern() != text || regexChanged) { change = true; if(m_regexState) m_proxyModel->setFilterRegExp(text); else m_proxyModel->setFilterWildcard(text); } if (m_proxyModel->filterMinimumUsers() != min) { change = true; m_proxyModel->setFilterMinimumUsers(min); } if (m_proxyModel->filterMaximumUsers() != max) { change = true; m_proxyModel->setFilterMaximumUsers(max); } if (m_proxyModel->filterTopic() != topic) { change = true; m_proxyModel->setFilterTopic(topic); } if (m_proxyModel->filterChannel() != channel) { change = true; m_proxyModel->setFilterChannel(channel); } if (change) m_proxyModel->invalidate(); updateUsersChannels(); } void ChannelListPanel::currentChanged(QModelIndex current,QModelIndex previous) { Q_UNUSED(previous); m_joinChannel->setEnabled(m_online && current.isValid()); } void ChannelListPanel::setProgress() { QString text = m_statsLabel->text(); if(text.length() < 13) m_statsLabel->setText(text + QLatin1Char('.')); else m_statsLabel->setText(i18n("Refreshing.")); } void ChannelListPanel::countUsers(const QModelIndex& index, int pos) { m_visibleUsers += index.data().toInt(); ++pos; if (pos < m_proxyModel->rowCount()) countUsers(index.sibling(pos,1), pos); } void ChannelListPanel::updateUsersChannels() { m_visibleUsers = 0; countUsers(m_proxyModel->index(0,1,QModelIndex()),0); m_visibleChannels = m_proxyModel->rowCount(); m_statsLabel->setText(i18n("Channels: %1 (%2 shown)", m_numChannels, m_visibleChannels) + i18n(" Non-unique users: %1 (%2 shown)", m_numUsers, m_visibleUsers)); } void ChannelListPanel::saveList() { // Ask user for file name QString fileName = QFileDialog::getSaveFileName( this, i18n("Save Channel List")); if (!fileName.isEmpty()) { // first find the longest channel name and nick number for clean table layouting int maxChannelWidth=0; int maxUsersWidth=0; int rows = m_proxyModel->rowCount(); QModelIndex index = m_proxyModel->index(0,0,QModelIndex()); for (int r = 0; r < rows; r++) { QString channel = index.sibling(r,0).data().toString(); QString users = index.sibling(r,1).data().toString(); if (channel.length()>maxChannelWidth) { maxChannelWidth = channel.length(); } if (users.length()>maxUsersWidth) { maxUsersWidth = users.length(); } } // now save the list to disk QFile listFile(fileName); listFile.open(QIODevice::WriteOnly); // wrap the file into a stream QTextStream stream(&listFile); QString header(i18n("Konversation Channel List: %1 - %2\n\n", m_server->getServerName(), QDateTime::currentDateTime().toString())); // send header to stream stream << header; for (int r = 0; r < rows; r++) { QString channel = index.sibling(r,0).data().toString(); QString users = index.sibling(r,1).data().toString(); QString topic = index.sibling(r,2).data().toString(); QString channelName; channelName.fill(QLatin1Char(' '), maxChannelWidth); channelName.replace(0, channel.length(), channel); QString usersPad; usersPad.fill(QLatin1Char(' '),maxUsersWidth); QString usersNum(usersPad+users); usersNum = usersNum.right(maxUsersWidth); QString line(channelName+QLatin1Char(' ')+usersNum+QLatin1Char(' ')+topic+QLatin1Char('\n')); stream << line; } listFile.close(); } } void ChannelListPanel::joinChannelClicked() { QModelIndex item = m_channelListView->currentIndex(); if(item.isValid()) { if(item.column() != 0) item = item.sibling(item.row(),0); emit joinChannel(item.data().toString()); } } void ChannelListPanel::applyFilterClicked() { if (!m_numChannels) { refreshList(); return; } } void ChannelListPanel::contextMenu(const QPoint& p) { QModelIndex item = m_channelListView->indexAt(p); if (!item.isValid()) return; if (item.column() != 2) item = item.sibling(item.row(),2); QString filteredLine = item.data().toString(); QMenu* menu = new QMenu(this); // Join Channel Action QAction *joinAction = new QAction(menu); joinAction->setText(i18n("Join Channel")); joinAction->setIcon(QIcon::fromTheme(QStringLiteral("irc-join-channel"))); menu->addAction(joinAction); connect(joinAction, &QAction::triggered, this, &ChannelListPanel::joinChannelClicked); // Adds a separator between the Join action and the URL(s) submenu menu->addSeparator(); // open URL submenu QMenu* showURLmenu = new QMenu(i18n("Open URL"), menu); QList > urlRanges = Konversation::getUrlRanges(filteredLine); QPair urlRange; QListIterator > i(urlRanges); while (i.hasNext()) { urlRange = i.next(); QString url = filteredLine.mid(urlRange.first, urlRange.second); QAction* action = new QAction(showURLmenu); action->setText(url); action->setData(url); showURLmenu->addAction(action); connect(action, &QAction::triggered, this, &ChannelListPanel::openURL); } if (showURLmenu->actions().count()==0) showURLmenu->setEnabled(false); menu->addMenu(showURLmenu); menu->exec(QCursor::pos()); delete menu; } void ChannelListPanel::openURL() { const QAction* action = qobject_cast(sender()); if (action) { - Application* konvApp = Application::instance(); - konvApp->openUrl(action->data().toString()); + Application::openUrl(action->data().toString()); } } bool ChannelListPanel::closeYourself() { // make the server delete us so server can reset the pointer to us m_server->closeChannelListPanel(); return true; } void ChannelListPanel::appendInputText(const QString& text, bool fromCursor) { Q_UNUSED(fromCursor); m_filterLine->setText(m_filterLine->text() + text); } //Used to disable functions when not connected void ChannelListPanel::serverOnline(bool online) { m_online = online; m_refreshList->setEnabled(m_online); m_joinChannel->setEnabled(m_online && m_channelListView->currentIndex().isValid()); } void ChannelListPanel::emitUpdateInfo() { QString info; info = i18n("Channel List for %1", m_server->getDisplayName()); emit updateInfo(info); } void ChannelListPanel::setFilter(const QString& filter) { m_filterLine->setText(filter); } diff --git a/src/irc/query.cpp b/src/irc/query.cpp index e75d4f2f..ea0ffff7 100644 --- a/src/irc/query.cpp +++ b/src/irc/query.cpp @@ -1,484 +1,484 @@ /* 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, &Query::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, 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) { if (server == m_server) { ViewContainer* viewContainer = Application::instance()->getMainWindow()->getViewContainer(); if (state == Konversation::SSConnected) { //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); } } 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); + OutputFilter::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::textPasted(const QString& text) { if(m_server) { 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 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/server.cpp b/src/irc/server.cpp index a0773bd7..87ec200e 100644 --- a/src/irc/server.cpp +++ b/src/irc/server.cpp @@ -1,4408 +1,4408 @@ // -*- 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_recreationScheduled = false; m_delayedConnectTimer = new QTimer(this); m_delayedConnectTimer->setSingleShot(true); connect(m_delayedConnectTimer, SIGNAL(timeout()), this, SLOT(connectToIRCServer())); m_reconnectImmediately = false; 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_capabilities = NoCapabilies; initCapablityNames(); 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); 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(); 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(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); 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(ChatWindow*)), getViewContainer(), SLOT(showView(ChatWindow*))); 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); // return; // } updateConnectionState(Konversation::SSConnecting); 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); } void Server::showSSLDialog() { //TODO /* SSLSocket* sslsocket = dynamic_cast(m_socket); if (sslsocket) sslsocket->showInfoDialog(); */ } void Server::rebuildTargetPrefixMatcher() { m_targetMatcher.setPattern(QLatin1String("^([") + getServerNickPrefixes() + QLatin1String("]*)([") + getChannelTypes() + QLatin1String("])(.+)")); } // set available channel types according to 005 RPL_ISUPPORT void Server::setChannelTypes(const QString &pre) { m_channelPrefixes = pre; rebuildTargetPrefixMatcher(); 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; rebuildTargetPrefixMatcher(); } 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 () { m_capRequested = 0; m_capAnswered = 0; m_capEndDelayed = false; m_capabilities = NoCapabilies; getStatusView()->appendServerMessage(i18n("Info"),i18n("Negotiating capabilities with server...")); m_inputFilter.setAutomaticRequest(QStringLiteral("CAP LS"), QString(), true); queue(QStringLiteral("CAP LS 302"), HighPriority); } void Server::capInitiateNegotiation(const QString &availableCaps) { QStringList requestCaps; QStringList capsList = availableCaps.split (QChar(' '), QString::SkipEmptyParts); QStringList nameValue; foreach(const QString &cap, capsList) { nameValue = cap.split(QChar('=')); if(nameValue[0] == QStringLiteral("sasl")) { QString authCommand; if (getIdentity()) { // A username is optional SASL EXTERNAL and a client cert substitutes // for the password. if (getIdentity()->getAuthType() == QStringLiteral("saslexternal")) { authCommand = QStringLiteral("EXTERNAL"); // PLAIN on the other hand requires both. } else if (getIdentity()->getAuthType() == QStringLiteral("saslplain") && !getIdentity()->getSaslAccount().isEmpty() && !getIdentity()->getAuthPassword().isEmpty()) { authCommand = QStringLiteral("PLAIN"); } } if(!authCommand.isEmpty()) { QSet supportedSaslTypes; if(nameValue.count() > 1) supportedSaslTypes = QSet::fromList(nameValue[1].split(QChar(','))); if(!supportedSaslTypes.isEmpty() && !supportedSaslTypes.contains(authCommand)) getStatusView()->appendServerMessage(i18n("Error"), i18n("Server does not support %1 as SASL authentication mechanism, skipping SASL authentication.", authCommand)); else requestCaps.append ("sasl"); } } else if(m_capabilityNames.contains(nameValue[0])) { requestCaps.append (nameValue[0]); } } 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; } m_capabilities |= m_capabilityNames.value(name); } 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(); 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); } else if (getConnectionState() == Konversation::SSDeliberatelyDisconnected) { 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); } // 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); QString errorReason; for (int i = 0; i < errors.size(); ++i) { errorReason += errors.at(i).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); // 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) emit serverOnline(true); else if (m_connectionState != Konversation::SSConnecting) emit serverOnline(false); emit connectionStateChanged(this, 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); 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); + OutputFilter::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); + OutputFilter::replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for ( QStringList::Iterator it = m_autoJoinCommands.begin(); it != m_autoJoinCommands.end(); ++it ) queue((*it)); } if (!m_connectionSettings.oneShotChannelList().isEmpty()) { QStringList oneShotJoin = generateJoinCommand(m_connectionSettings.oneShotChannelList()); for ( QStringList::Iterator it = oneShotJoin.begin(); it != oneShotJoin.end(); ++it ) { 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); // 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); } 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(); } 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 (capabilities() & WHOX && capabilities() & ExtendedJoin) { // 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 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 (quint16)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(); QRegularExpressionMatch p = m_targetMatcher.match(wanted); int index = p.capturedStart(2); if (index >= 0) { wanted = wanted.mid(index); 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); 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) 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); 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; } 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); } 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); // 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_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) 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(); 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(); 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; // ###### ERROR: not reached 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_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 = qobject_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 >= (int)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; QRegularExpressionMatch x = m_targetMatcher.match(channel); int index = x.capturedStart(2); return (index >= 0 && 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()) { 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) && (m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHost) && m_socket->peerAddress() != QHostAddress(QHostAddress::LocalHostIPv6))) { quitServer(); updateConnectionState(Konversation::SSInvoluntarilyDisconnected); } } void Server::reconnectInvoluntary() { if(m_connectionState == Konversation::SSInvoluntarilyDisconnected) reconnectServer(); } void Server::initCapablityNames() { m_capabilityNames.insert("away-notify", AwayNotify); m_capabilityNames.insert("extended-join", ExtendedJoin); m_capabilityNames.insert("server-time", ServerTime); m_capabilityNames.insert("znc.in/server-time-iso", ServerTime); m_capabilityNames.insert("userhost-in-names", UserHostInNames); m_capabilityNames.insert("sasl", SASL); m_capabilityNames.insert("multi-prefix", MultiPrefix); m_capabilityNames.insert("account-notify", AccountNotify); m_capabilityNames.insert("znc.in/self-message", SelfMessage); } // 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/main.cpp b/src/main.cpp index a19d9a6a..f33b2356 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,162 +1,162 @@ /* 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. */ /* Where it all began ... begin: Die Jan 15 05:59:05 CET 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ #include "application.h" #include "version.h" #include "commit.h" #include #include #include #include #include #include #include #define HACKSTR(x) #x #define STRHACK(x) HACKSTR(x) int main(int argc, char* argv[]) { Application app(argc, argv); // Migrate pre-existing (4.x) configuration QStringList configFiles; configFiles.append(QLatin1String("konversationrc")); configFiles.append(QLatin1String("konversation.notifyrc")); Kdelibs4ConfigMigrator migrate(QLatin1String("konversation")); migrate.setConfigFiles(configFiles); migrate.setUiFiles(QStringList() << QLatin1String("konversationui.rc")); migrate.migrate(); KLocalizedString::setApplicationDomain("konversation"); KAboutData aboutData("konversation", i18n("Konversation"), KONVI_VERSION " #" STRHACK(COMMIT), i18n("A user-friendly IRC client"), KAboutLicense::GPL, i18n("(C) 2002-2018 by the Konversation team"), i18n("Konversation is a client for the Internet Relay Chat (IRC) protocol.\n\n" "Meet friends on the net, make new acquaintances and lose yourself in talk about your favorite subject."), QStringLiteral("http://konversation.kde.org/")); aboutData.addAuthor(i18n("Dario Abatianni"),i18n("Original Author, Project Founder"),"eisfuchs@tigress.com"); aboutData.addAuthor(i18n("Peter Simonsson"),i18n("Maintainer"),"peter.simonsson@gmail.com"); aboutData.addAuthor(i18n("Eike Hein"),i18n("Maintainer, Release Manager, User interface, Connection management, Protocol handling, Auto-away"),"hein@kde.org"); aboutData.addAuthor(i18n("Shintaro Matsuoka"),i18n("DCC, Encoding handling, OSD positioning"),"shin@shoegazed.org"); aboutData.addAuthor(i18n("Eli MacKenzie"),i18n("Protocol handling, Input line"),"argonel@gmail.com"); aboutData.addAuthor(i18n("Ä°smail Dönmez"),i18n("Blowfish, SSL support, KNetwork port, Colored nicks, Nicklist themes"),"ismail@kde.org"); aboutData.addAuthor(i18n("John Tapsell"),i18n("Refactoring, KAddressBook/Kontact integration"), "john@geola.co.uk"); aboutData.addAuthor(i18n("Bernd Buschinski"),i18n("DCC port to KDE 4, various DCC improvements"), "b.buschinski@web.de"); aboutData.addCredit(i18n("Olivier Bédard"),i18n("Website hosting")); aboutData.addCredit(i18n("JÄ™drzej Lisowski"),i18n("Website maintenance"),"yesoos@gmail.com"); aboutData.addCredit(i18n("Christian Muehlhaeuser"),i18n("Multiple modes extension, Close widget placement, OSD functionality"),"chris@chris.de"); aboutData.addCredit(i18n("Gary Cramblitt"),i18n("Documentation, Watched Nicks improvements, Custom web browser extension"),"garycramblitt@comcast.net"); aboutData.addCredit(i18n("Matthias Gierlings"),i18n("Color configurator, Highlight dialog"),"gismore@users.sourceforge.net"); aboutData.addCredit(i18n("Alex Zepeda"),i18n("DCOP interface"),"garbanzo@hooked.net"); aboutData.addCredit(i18n("Stanislav Karchebny"),i18n("Non-Latin1-Encodings"),"berkus@users.sourceforge.net"); aboutData.addCredit(i18n("Mickael Marchand"),i18n("Konsole part view"),"marchand@kde.org"); aboutData.addCredit(i18n("Michael Goettsche"),i18n("Quick connect, Ported new OSD, other features and bugfixes"),"michael.goettsche@kdemail.net"); aboutData.addCredit(i18n("Benjamin Meyer"),i18n("A Handful of fixes and code cleanup"),"ben+konversation@meyerhome.net"); aboutData.addCredit(i18n("Jakub Stachowski"),i18n("Drag&Drop improvements"),"qbast@go2.pl"); aboutData.addCredit(i18n("Sebastian Sariego"),i18n("Artwork"),"segfault@kde.cl"); aboutData.addCredit(i18n("Renchi Raju"),i18n("Firefox style searchbar")); aboutData.addCredit(i18n("Michael Kreitzer"),i18n("Raw modes, Tab grouping per server, Ban list"),"mrgrim@gr1m.org"); aboutData.addCredit(i18n("Frauke Oster"),i18n("System tray patch"),"frauke@frsv.de"); aboutData.addCredit(i18n("Lucijan Busch"),i18n("Bug fixes"),"lucijan@kde.org"); aboutData.addCredit(i18n("Sascha Cunz"),i18n("Extended user modes patch"),"mail@sacu.de"); aboutData.addCredit(i18n("Steve Wollkind"),i18n("Close visible tab with shortcut patch"),"steve@njord.org"); aboutData.addCredit(i18n("Thomas Nagy"),i18n("Cycle tabs with mouse scroll wheel"),"thomas.nagy@eleve.emn.fr"); aboutData.addCredit(i18n("Tobias Olry"),i18n("Channel ownership mode patch"),"tobias.olry@web.de"); aboutData.addCredit(i18n("Ruud Nabben"),i18n("Option to enable IRC color filtering"),"r.nabben@gawab.com"); aboutData.addCredit(i18n("Lothar Braun"),i18n("Bug fixes"),"mail@lobraun.de"); aboutData.addCredit(i18n("Ivor Hewitt"),i18n("Bug fixes, OSD work, clearing topics"),"ivor@ivor.org"); aboutData.addCredit(i18n("Emil Obermayr"),i18n("Sysinfo script"),"nobs@tigress.com"); aboutData.addCredit(i18n("Stanislav Nikolov"),i18n("Bug fixes"),"valsinats@gmail.com"); aboutData.addCredit(i18n("Juan Carlos Torres"),i18n("Auto-join context menu"),"carlosdgtorres@gmail.com"); aboutData.addCredit(i18n("Travis McHenry"),i18n("Various fixes, ported encryption to QCA2, added DH1080 key exchange support."),"tmchenryaz@cox.net"); aboutData.addCredit(i18n("Modestas Vainius"),i18n("Bug fixes and enhancements."),"modestas@vainius.eu"); aboutData.addCredit(i18n("Abdurrahman AVCI"),i18n("Various bug fixes and enhancements."),"abdurrahmanavci@gmail.com"); aboutData.addCredit(i18n("Martin Blumenstingl"),i18n("KStatusNotifierItem support, KIdleTime support, other enhancements"),"darklight.xdarklight@googlemail.com"); KAboutData::setApplicationData(aboutData); KCrash::initialize(); - app.setWindowIcon(QIcon::fromTheme(QLatin1String("konversation"), app.windowIcon())); - app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); + QApplication::setWindowIcon(QIcon::fromTheme(QLatin1String("konversation"), QApplication::windowIcon())); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCommandLineParser cmdLineParser; cmdLineParser.addHelpOption(); cmdLineParser.addVersionOption(); cmdLineParser.addPositionalArgument(QStringLiteral("url"), i18n("irc:// URL or server hostname")); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("server"), i18n("Server to connect"), i18n("server"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("port"), i18n("Port to use"), i18n("port"), "6667")); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("channel"), i18n("Channel to join after connection"), i18n("channel"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("nick"), i18n("Nickname to use"), i18n("nickname"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("password"), i18n("Password for connection"), i18n("password"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("ssl"), i18n("Use SSL for connection"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("noautoconnect"), i18n("Disable auto-connecting to any IRC networks"))); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("startupdelay"), i18n("Delay D-Bus activity and UI creation by the specified amount of milliseconds"), i18n("msec"), "2000")); cmdLineParser.addOption(QCommandLineOption(QStringLiteral("restart"), i18n("Quits and restarts Konversation (if running, otherwise has no effect)"))); #ifndef QT_NO_DEBUG cmdLineParser.addOption(QCommandLineOption(QStringLiteral("nui"), i18n("Sets KUniqueApplication::NonUniqueInstance (debug only, use with caution)"))); #endif aboutData.setupCommandLine(&cmdLineParser); cmdLineParser.process(app); if (cmdLineParser.isSet(QStringLiteral("startupdelay"))) { bool ok; ulong delay = cmdLineParser.value(QStringLiteral("startupdelay")).toULong(&ok); if (ok) { QMutex dummy; dummy.lock(); QWaitCondition waitCondition; waitCondition.wait(&dummy, delay); } } KDBusService::StartupOptions startOptions = KDBusService::Unique; #ifndef QT_NO_DEBUG if (cmdLineParser.isSet(QStringLiteral("nui"))) startOptions = KDBusService::Multiple; #endif startOptions |= KDBusService::NoExitOnFailure; KDBusService dbusService(startOptions); aboutData.processCommandLine(&cmdLineParser); app.newInstance(&cmdLineParser); app.setCommandLineParser(&cmdLineParser); QObject::connect(&dbusService, &KDBusService::activateRequested, &app, &Application::handleActivate, Qt::DirectConnection); - return app.exec(); + return QApplication::exec(); } diff --git a/src/viewer/irccontextmenus.cpp b/src/viewer/irccontextmenus.cpp index 80c35b4a..a4b52084 100644 --- a/src/viewer/irccontextmenus.cpp +++ b/src/viewer/irccontextmenus.cpp @@ -1,904 +1,904 @@ /* 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor appro- ved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ /* Copyright (C) 2010 Eike Hein */ #include "irccontextmenus.h" #include "application.h" #include "nick.h" #include "server.h" #include "ircinput.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // For the Web Shortcuts context menu sub-menu. #include #include class IrcContextMenusPrivate { public: IrcContextMenusPrivate(); ~IrcContextMenusPrivate(); IrcContextMenus instance; }; Q_GLOBAL_STATIC(IrcContextMenusPrivate, s_ircContextMenusPrivate) IrcContextMenusPrivate::IrcContextMenusPrivate() { } IrcContextMenusPrivate::~IrcContextMenusPrivate() { } IrcContextMenus::IrcContextMenus() { createSharedBasicNickActions(); createSharedNickSettingsActions(); createSharedDccActions(); setupQuickButtonMenu(); setupNickMenu(); setupTextMenu(); // creates m_textCopyAction needed by the channel menu setupChannelMenu(); setupTopicHistoryMenu(); updateQuickButtonMenu(); } IrcContextMenus::~IrcContextMenus() { delete m_channelMenu; delete m_textMenu; delete m_nickMenu; delete m_quickButtonMenu; delete m_topicHistoryMenu; delete m_modesMenu; delete m_kickBanMenu; delete m_webShortcutsMenu; } IrcContextMenus* IrcContextMenus::self() { return &s_ircContextMenusPrivate->instance; } void IrcContextMenus::setupQuickButtonMenu() { //NOTE: if we depend on m_nickMenu we get an we an cyclic initialising m_quickButtonMenu = new QMenu(); m_quickButtonMenu->setTitle(i18n("Quick Buttons")); connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(updateQuickButtonMenu())); } bool IrcContextMenus::shouldShowQuickButtonMenu() { return Preferences::self()->showQuickButtonsInContextMenu() && !m_quickButtonMenu->isEmpty(); } void IrcContextMenus::updateQuickButtonMenu() { m_quickButtonMenu->clear(); QAction * action; QString pattern; foreach(const QString& button, Preferences::quickButtonList()) { pattern = button.section(',', 1); if (pattern.contains("%u")) { action = new QAction(button.section(',', 0, 0), m_quickButtonMenu); action->setData(pattern); m_quickButtonMenu->addAction(action); } } } void IrcContextMenus::processQuickButtonAction(QAction* action, Server* server, const QString& context, const QStringList nicks) { ChatWindow* chatWindow = server->getChannelOrQueryByName(context); QString line = server->parseWildcards(action->data().toString(), chatWindow, nicks); if (line.contains('\n')) chatWindow->sendText(line); else { if (chatWindow->getInputBar()) chatWindow->getInputBar()->setText(line, true); } } void IrcContextMenus::setupTextMenu() { m_textMenu = new QMenu(); m_textMenu->addSeparator(); m_linkActions << createAction(m_textMenu, LinkCopy, QIcon::fromTheme("edit-copy"), i18n("Copy Link Address")); // Not using KStandardAction is intentional here since the Ctrl+B // shortcut it would show in the menu is already used by our IRC- // wide bookmarking feature. m_linkActions << createAction(m_textMenu, LinkBookmark, QIcon::fromTheme("bookmark-new"), i18n("Add to Bookmarks")); m_linkActions << createAction(m_textMenu, LinkOpenWith, i18n("Open With...")); m_linkActions << createAction(m_textMenu, LinkSaveAs, QIcon::fromTheme("document-save"), i18n("Save Link As...")); m_textMenu->addSeparator(); m_textCopyAction = KStandardAction::copy(nullptr, nullptr, this); m_textCopyAction->setData(TextCopy); m_textMenu->addAction(m_textCopyAction); m_textCopyAction->setEnabled(false); QAction* action = KStandardAction::selectAll(nullptr, nullptr, this); action->setData(TextSelectAll); m_textMenu->addAction(action); m_webShortcutsMenu = new QMenu(); m_webShortcutsMenu->menuAction()->setIcon(QIcon::fromTheme("preferences-web-browser-shortcuts")); m_webShortcutsMenu->menuAction()->setVisible(false); m_textMenu->addMenu(m_webShortcutsMenu); m_textActionsSeparator = m_textMenu->addSeparator(); foreach(QAction* action, m_sharedBasicNickActions) m_textMenu->addAction(action); m_textMenu->addSeparator(); m_textMenu->addMenu(m_quickButtonMenu); m_textMenu->addSeparator(); foreach(QAction* action, m_sharedNickSettingsActions) m_textMenu->addAction(action); m_textMenu->addSeparator(); foreach(QAction* action, m_sharedDccActions) m_textMenu->addAction(action); m_textMenu->addSeparator(); } int IrcContextMenus::textMenu(const QPoint& pos, MenuOptions options, Server* server, const QString& selectedText, const QString& link, const QString& nick) { QMenu* textMenu = self()->m_textMenu; KActionCollection* actionCollection = Application::instance()->getMainWindow()->actionCollection(); KToggleAction* toggleMenuBarAction = qobject_cast(actionCollection->action("options_show_menubar")); if (toggleMenuBarAction && !toggleMenuBarAction->isChecked()) textMenu->insertAction(textMenu->actions().first(), toggleMenuBarAction); bool showLinkActions = options.testFlag(ShowLinkActions); foreach(QAction* action, self()->m_linkActions) action->setVisible(showLinkActions); self()->m_textCopyAction->setEnabled(!selectedText.isEmpty()); self()->updateWebShortcutsMenu(selectedText); bool showNickActions = options.testFlag(ShowNickActions); foreach(QAction* action, self()->m_sharedBasicNickActions) action->setVisible(showNickActions); self()->m_quickButtonMenu->menuAction()->setVisible(showNickActions && self()->shouldShowQuickButtonMenu()); if (showNickActions) { bool connected = server->isConnected(); foreach(QAction* action, self()->m_sharedBasicNickActions) action->setEnabled(connected); updateSharedNickSettingsActions(server, QStringList() << nick); foreach(QAction* action, self()->m_sharedDccActions) action->setEnabled(connected); } else { foreach(QAction* action, self()->m_sharedNickSettingsActions) action->setVisible(false); } foreach(QAction* action, self()->m_sharedDccActions) action->setVisible(showNickActions); if (options.testFlag(ShowFindAction)) textMenu->insertAction(self()->m_textActionsSeparator, actionCollection->action(KStandardAction::name(KStandardAction::Find))); if (options.testFlag(ShowLogAction)) textMenu->addAction(actionCollection->action("open_logfile")); if (options.testFlag(ShowChannelActions)) textMenu->addAction(actionCollection->action("channel_settings")); QAction* action = textMenu->exec(pos); int actionId = extractActionId(action); if (showLinkActions) processLinkAction(actionId, link); if (self()->m_quickButtonMenu->actions().contains(action)) processQuickButtonAction(action, server, nick, QStringList() << nick); textMenu->removeAction(toggleMenuBarAction); textMenu->removeAction(actionCollection->action(KStandardAction::name(KStandardAction::Find))); textMenu->removeAction(actionCollection->action("open_logfile")); textMenu->removeAction(actionCollection->action("channel_settings")); return actionId; } void IrcContextMenus::updateWebShortcutsMenu(const QString& selectedText) { m_webShortcutsMenu->menuAction()->setVisible(false); m_webShortcutsMenu->clear(); if (selectedText.isEmpty()) return; QString searchText = selectedText; searchText = searchText.replace('\n', ' ').replace('\r', ' ').simplified(); if (searchText.isEmpty()) return; KUriFilterData filterData(searchText); filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { const QStringList searchProviders = filterData.preferredSearchProviders(); if (!searchProviders.isEmpty()) { m_webShortcutsMenu->setTitle(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 21))); QAction * action = nullptr; foreach(const QString& searchProvider, searchProviders) { action = new QAction(searchProvider, m_webShortcutsMenu); action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider))); action->setData(filterData.queryForPreferredSearchProvider(searchProvider)); connect(action, &QAction::triggered, this, &IrcContextMenus::processWebShortcutAction); m_webShortcutsMenu->addAction(action); } m_webShortcutsMenu->addSeparator(); action = new QAction(i18n("Configure Web Shortcuts..."), m_webShortcutsMenu); action->setIcon(QIcon::fromTheme("configure")); connect(action, &QAction::triggered, this, &IrcContextMenus::configureWebShortcuts); m_webShortcutsMenu->addAction(action); m_webShortcutsMenu->menuAction()->setVisible(true); } } } void IrcContextMenus::processWebShortcutAction() { QAction * action = qobject_cast(sender()); if (action) { KUriFilterData filterData(action->data().toString()); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) - Application::instance()->openUrl(filterData.uri().url()); + Application::openUrl(filterData.uri().url()); } } void IrcContextMenus::configureWebShortcuts() { KToolInvocation::kdeinitExec("kcmshell5", QStringList() << "webshortcuts"); } void IrcContextMenus::setupChannelMenu() { m_channelMenu = new QMenu(); QAction* defaultAction = createAction(m_channelMenu, Join, QIcon::fromTheme("irc-join-channel"), i18n("&Join Channel...")); m_channelMenu->setDefaultAction(defaultAction); createAction(m_channelMenu, Topic, i18n("Get &topic")); createAction(m_channelMenu, Names, i18n("Get &user list")); m_channelMenu->addAction(m_textCopyAction); } void IrcContextMenus::channelMenu(const QPoint& pos, Server* server, const QString& channel) { QMenu* channelMenu = self()->m_channelMenu; if (!channel.isEmpty()) channelMenu->setTitle(KStringHandler::rsqueeze(channel, 15)); bool connected = server->isConnected(); foreach(QAction* action, channelMenu->actions()) action->setEnabled(connected); QAction* action = channelMenu->exec(pos); self()->m_textCopyAction->setEnabled(false); switch (extractActionId(action)) { case Join: commandToServer(server, "join " + channel); break; case Topic: server->requestTopic(channel); break; case Names: commandToServer(server, "names " + channel); break; case TextCopy: qApp->clipboard()->setText(channel, QClipboard::Clipboard); break; default: break; } } void IrcContextMenus::setupNickMenu() { m_nickMenu = new QMenu(); QAction* defaultAction = createAction(m_nickMenu, OpenQuery, i18n("Open Query")); m_nickMenu->setDefaultAction(defaultAction); m_nickMenu->addSeparator(); foreach(QAction* action, m_sharedBasicNickActions) m_nickMenu->addAction(action); m_nickMenu->addSeparator(); m_modesMenu = new QMenu(); m_nickMenu->addMenu(m_modesMenu); m_modesMenu->setTitle(i18n("Modes")); createAction(m_modesMenu, GiveOp, QIcon::fromTheme("irc-operator"), i18n("Give Op")); createAction(m_modesMenu, TakeOp, QIcon::fromTheme("irc-remove-operator"), i18n("Take Op")); createAction(m_modesMenu, GiveHalfOp, i18n("Give HalfOp")); createAction(m_modesMenu, TakeHalfOp, i18n("Take HalfOp")); createAction(m_modesMenu, GiveVoice, QIcon::fromTheme("irc-voice"), i18n("Give Voice")); createAction(m_modesMenu, TakeVoice, QIcon::fromTheme("irc-unvoice"), i18n("Take Voice")); m_kickBanMenu = new QMenu(); m_nickMenu->addMenu(m_kickBanMenu); m_kickBanMenu->setTitle(i18n("Kick / Ban")); createAction(m_kickBanMenu, Kick, i18n("Kick")); createAction(m_kickBanMenu, KickBan, i18n("Kickban")); createAction(m_kickBanMenu, BanNick, i18n("Ban Nickname")); m_kickBanMenu->addSeparator(); createAction(m_kickBanMenu, BanHost, i18n("Ban *!*@*.host")); createAction(m_kickBanMenu, BanDomain, i18n("Ban *!*@domain")); createAction(m_kickBanMenu, BanUserHost, i18n("Ban *!user@*.host")); createAction(m_kickBanMenu, BanUserDomain, i18n("Ban *!user@domain")); m_kickBanMenu->addSeparator(); createAction(m_kickBanMenu, KickBanHost, i18n("Kickban *!*@*.host")); createAction(m_kickBanMenu, KickBanDomain, i18n("Kickban *!*@domain")); createAction(m_kickBanMenu, KickBanUserHost, i18n("Kickban *!user@*.host")); createAction(m_kickBanMenu, KickBanUserDomain, i18n("Kickban *!user@domain")); m_nickMenu->addMenu(m_quickButtonMenu); m_nickMenu->addSeparator(); foreach(QAction* action, m_sharedNickSettingsActions) m_nickMenu->addAction(action); m_nickMenu->addSeparator(); foreach(QAction* action, m_sharedDccActions) m_nickMenu->addAction(action); } void IrcContextMenus::createSharedBasicNickActions() { m_sharedBasicNickActions << createAction(Whois, i18n("&Whois")); m_sharedBasicNickActions << createAction(Version, i18n("&Version")); m_sharedBasicNickActions << createAction(Ping, i18n("&Ping")); } void IrcContextMenus::createSharedNickSettingsActions() { m_ignoreAction = createAction(IgnoreNick, i18n("Ignore")); m_sharedNickSettingsActions << m_ignoreAction; m_unignoreAction = createAction(UnignoreNick, i18n("Unignore")); m_sharedNickSettingsActions << m_unignoreAction; m_addNotifyAction = createAction(AddNotify, QIcon::fromTheme("list-add-user"), i18n("Add to Watched Nicks")); m_sharedNickSettingsActions << m_addNotifyAction; m_removeNotifyAction = createAction(RemoveNotify, QIcon::fromTheme("list-remove-user"), i18n("Remove From Watched Nicks")); m_sharedNickSettingsActions << m_removeNotifyAction; } void IrcContextMenus::createSharedDccActions() { if (KAuthorized::authorizeAction("allow_downloading")) m_sharedDccActions << createAction(DccSend, QIcon::fromTheme("arrow-right-double"), i18n("Send &File...")); m_sharedDccActions << createAction(StartDccChat, i18n("Open DCC Chat")); m_sharedDccActions << createAction(StartDccWhiteboard, i18n("Open DCC Whiteboard")); } void IrcContextMenus::nickMenu(const QPoint& pos, MenuOptions options, Server* server, const QStringList& nicks, const QString& context) { QMenu* nickMenu = self()->m_nickMenu; if (options.testFlag(ShowTitle) && nicks.count() == 1) nickMenu->setTitle(KStringHandler::rsqueeze(nicks.first(), 15)); foreach(QAction* action, nickMenu->actions()) action->setVisible(true); self()->m_modesMenu->menuAction()->setVisible(options.testFlag(ShowChannelActions)); self()->m_kickBanMenu->menuAction()->setVisible(options.testFlag(ShowChannelActions)); self()->m_quickButtonMenu->menuAction()->setVisible(self()->shouldShowQuickButtonMenu()); bool connected = server->isConnected(); foreach(QAction* action, self()->m_sharedBasicNickActions) action->setEnabled(connected); foreach(QAction* action, self()->m_sharedDccActions) action->setEnabled(connected); self()->m_modesMenu->menuAction()->setEnabled(connected); self()->m_kickBanMenu->menuAction()->setEnabled(connected); updateSharedNickSettingsActions(server, nicks); QAction* action = nickMenu->exec(pos); if (self()->m_quickButtonMenu->actions().contains(action)) processQuickButtonAction(action, server, context, nicks); else processNickAction(extractActionId(action), server, nicks, context); } void IrcContextMenus::processNickAction(int actionId, Server* server, const QStringList& nicks, const QString& context) { QString channel; if (server->getChannelByName(context)) channel = context; QString pattern; QString mode; switch (actionId) { case OpenQuery: commandToServer(server, "query %1", nicks); break; case Whois: commandToServer(server, "whois %1 %1", nicks); break; case Version: commandToServer(server, "ctcp %1 VERSION", nicks); break; case Ping: commandToServer(server, "ctcp %1 PING", nicks); break; case GiveOp: if (channel.isEmpty()) break; pattern = "MODE %c +%m %l"; mode = 'o'; break; case TakeOp: if (channel.isEmpty()) break; pattern = "MODE %c -%m %l"; mode = 'o'; break; case GiveHalfOp: if (channel.isEmpty()) break; pattern = "MODE %c +%m %l"; mode = 'h'; break; case TakeHalfOp: if (channel.isEmpty()) break; pattern = "MODE %c -%m %l"; mode = 'h'; break; case GiveVoice: if (channel.isEmpty()) break; pattern = "MODE %c +%m %l"; mode = 'v'; break; case TakeVoice: if (channel.isEmpty()) break; pattern = "MODE %c -%m %l"; mode = 'v'; break; case Kick: commandToServer(server, "kick %1", nicks, channel); break; case KickBan: commandToServer(server, "kickban %1", nicks, channel); break; case BanNick: commandToServer(server, "ban %1", nicks, channel); break; case BanHost: commandToServer(server, "ban -HOST %1", nicks, channel); break; case BanDomain: commandToServer(server, "ban -DOMAIN %1", nicks, channel); break; case BanUserHost: commandToServer(server, "ban -USERHOST %1", nicks, channel); break; case BanUserDomain: commandToServer(server, "ban -USERDOMAIN %1", nicks, channel); break; case KickBanHost: commandToServer(server, "kickban -HOST %1", nicks, channel); break; case KickBanDomain: commandToServer(server, "kickban -DOMAIN %1", nicks, channel); break; case KickBanUserHost: commandToServer(server, "kickban -USERHOST %1", nicks, channel); break; case KickBanUserDomain: commandToServer(server, "kickban -USERDOMAIN %1", nicks, channel); break; case IgnoreNick: { QString question; if (nicks.size() == 1) question = i18n("Do you want to ignore %1?", nicks.first()); else question = i18n("Do you want to ignore the selected users?"); if (KMessageBox::warningContinueCancel( Application::instance()->getMainWindow(), question, i18n("Ignore"), KGuiItem(i18n("Ignore")), KStandardGuiItem::cancel(), "IgnoreNick" ) == KMessageBox::Continue) { commandToServer(server, "ignore -ALL " + nicks.join(" ")); } break; } case UnignoreNick: { QString question; QStringList selectedIgnoredNicks; foreach(const QString& nick, nicks) { if (Preferences::isIgnored(nick)) selectedIgnoredNicks << nick; } if (selectedIgnoredNicks.count() == 1) question = i18n("Do you want to stop ignoring %1?", selectedIgnoredNicks.first()); else question = i18n("Do you want to stop ignoring the selected users?"); if (KMessageBox::warningContinueCancel( Application::instance()->getMainWindow(), question, i18n("Unignore"), KGuiItem(i18n("Unignore")), KStandardGuiItem::cancel(), "UnignoreNick") == KMessageBox::Continue) { commandToServer(server, "unignore " + selectedIgnoredNicks.join(" ")); } break; } case AddNotify: { if (!server->getServerGroup()) break; foreach(const QString& nick, nicks) Preferences::addNotify(server->getServerGroup()->id(), nick); break; } case RemoveNotify: { if (!server->getServerGroup()) break; foreach(const QString& nick, nicks) Preferences::removeNotify(server->getServerGroup()->id(), nick); break; } case DccSend: commandToServer(server, "dcc send %1", nicks); break; case StartDccChat: commandToServer(server, "dcc chat %1", nicks); break; case StartDccWhiteboard: commandToServer(server, "dcc whiteboard %1", nicks); break; default: break; } if (!pattern.isEmpty()) { pattern.replace("%c", channel); QString command; QStringList partialList; int modesCount = server->getModesCount(); for (int index = 0; index < nicks.count(); index += modesCount) { command = pattern; partialList = nicks.mid(index, modesCount); command = command.replace("%l", partialList.join(" ")); const QString repeatedMode = mode.repeated(partialList.count()); command = command.replace("%m", repeatedMode); server->queue(command); } } } void IrcContextMenus::updateSharedNickSettingsActions(Server* server, const QStringList& nicks) { int ignoreCounter = 0; int unignoreCounter = 0; int addNotifyCounter = 0; int removeNotifyCounter = 0; int serverGroupId = -1; if (server->getServerGroup()) serverGroupId = server->getServerGroup()->id(); foreach(const QString& nick, nicks) { if (Preferences::isIgnored(nick)) ++unignoreCounter; else ++ignoreCounter; if (serverGroupId != -1) { if (Preferences::isNotify(serverGroupId, nick)) ++removeNotifyCounter; else ++addNotifyCounter; } } self()->m_ignoreAction->setVisible(ignoreCounter); self()->m_unignoreAction->setVisible(unignoreCounter); self()->m_addNotifyAction->setVisible(serverGroupId == -1 || addNotifyCounter); self()->m_addNotifyAction->setEnabled(serverGroupId != -1); self()->m_removeNotifyAction->setVisible(removeNotifyCounter); } void IrcContextMenus::processLinkAction(int actionId, const QString& link) { if (actionId == -1 || link.isEmpty()) return; switch (actionId) { case LinkCopy: { QClipboard* clipboard = qApp->clipboard(); clipboard->setText(link, QClipboard::Selection); clipboard->setText(link, QClipboard::Clipboard); break; } case LinkBookmark: { KBookmarkManager* manager = KBookmarkManager::userBookmarksManager(); KBookmarkDialog* dialog = new KBookmarkDialog(manager, Application::instance()->getMainWindow()); dialog->addBookmark(link, QUrl(link), QString()); delete dialog; break; } case LinkOpenWith: { KRun::displayOpenWithDialog(QList() << QUrl(link), Application::instance()->getMainWindow()); break; } case LinkSaveAs: { QUrl srcUrl(link); QUrl saveUrl = QFileDialog::getSaveFileUrl(Application::instance()->getMainWindow(), i18n("Save link as"), QUrl::fromLocalFile(srcUrl.fileName())); if (saveUrl.isEmpty() || !saveUrl.isValid()) break; KIO::copy(srcUrl, saveUrl); break; } default: break; } } void IrcContextMenus::setupTopicHistoryMenu() { m_topicHistoryMenu = new QMenu(); m_topicHistoryMenu->addAction(m_textCopyAction); m_queryTopicAuthorAction = createAction(m_topicHistoryMenu, OpenQuery, i18n("Query author")); } void IrcContextMenus::topicHistoryMenu(const QPoint& pos, Server* server, const QString& text, const QString& author) { QMenu* topicHistoryMenu = self()->m_topicHistoryMenu; self()->m_textCopyAction->setEnabled(true); self()->m_queryTopicAuthorAction->setEnabled(!author.isEmpty()); QAction* action = topicHistoryMenu->exec(pos); switch (extractActionId(action)) { case TextCopy: qApp->clipboard()->setText(text, QClipboard::Clipboard); break; case OpenQuery: commandToServer(server, QString("query %1").arg(author)); break; default: break; } } QAction* IrcContextMenus::createAction(ActionId id, const QString& text) { QAction* action = new QAction(text, this); action->setData(id); return action; } QAction* IrcContextMenus::createAction(ActionId id, const QIcon& icon) { QAction* action = new QAction(this); action->setData(id); action->setIcon(icon); return action; } QAction* IrcContextMenus::createAction(ActionId id, const QIcon& icon, const QString& text) { QAction* action = new QAction(icon, text, this); action->setData(id); return action; } QAction* IrcContextMenus::createAction(QMenu* menu, ActionId id, const QString& text) { QAction* action = createAction(id, text); menu->addAction(action); return action; } QAction* IrcContextMenus::createAction(QMenu* menu, ActionId id, const QIcon& icon, const QString& text) { QAction* action = createAction(id, text); action->setIcon(icon); menu->addAction(action); return action; } int IrcContextMenus::extractActionId(QAction* action) { if (action) { bool ok = false; int actionId = action->data().toInt(&ok); if (ok) return actionId; } return -1; } void IrcContextMenus::commandToServer(Server* server, const QString& command, const QString& destination) { Konversation::OutputFilterResult result = server->getOutputFilter()->parse(QString(), Preferences::self()->commandChar() + command, destination); server->queue(result.toServer); if (!result.output.isEmpty()) server->appendMessageToFrontmost(result.typeString, result.output); else if (!result.outputList.isEmpty()) { foreach(const QString& output, result.outputList) server->appendMessageToFrontmost(result.typeString, output); } } void IrcContextMenus::commandToServer(Server* server, const QString& command, const QStringList& arguments, const QString& destination) { foreach(const QString& argument, arguments) commandToServer(server, command.arg(argument), destination); } diff --git a/src/viewer/statuspanel.cpp b/src/viewer/statuspanel.cpp index 478c2eb7..7766ac09 100644 --- a/src/viewer/statuspanel.cpp +++ b/src/viewer/statuspanel.cpp @@ -1,328 +1,328 @@ /* 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) 2003 Dario Abatianni Copyright (C) 2006-2008 Eike Hein */ #include "statuspanel.h" #include "application.h" #include "ircinput.h" #include "ircview.h" #include "ircviewbox.h" #include "awaylabel.h" #include "server.h" #include #include #include #include using namespace Konversation; StatusPanel::StatusPanel(QWidget* parent) : ChatWindow(parent) { setType(ChatWindow::Status); setChannelEncodingSupported(true); awayChanged=false; awayState=false; // set up text view, will automatically take care of logging IRCViewBox* ircBox = new IRCViewBox(this); // Server will be set later in setServer() setTextView(ircBox->ircView()); QWidget* commandLineBox=new QWidget(this); QHBoxLayout* commandLineBoxLayout = new QHBoxLayout(commandLineBox); commandLineBoxLayout->setSpacing(spacing()); commandLineBoxLayout->setMargin(0); nicknameCombobox = new KComboBox(commandLineBox); commandLineBoxLayout->addWidget(nicknameCombobox); nicknameCombobox->setEditable(true); nicknameCombobox->setSizeAdjustPolicy(KComboBox::AdjustToContents); KLineEdit* nicknameComboboxLineEdit = qobject_cast(nicknameCombobox->lineEdit()); if (nicknameComboboxLineEdit) nicknameComboboxLineEdit->setClearButtonEnabled(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); commandLineBoxLayout->addWidget(awayLabel); awayLabel->hide(); m_inputBar=new IRCInput(commandLineBox); commandLineBoxLayout->addWidget(m_inputBar); getTextView()->installEventFilter(m_inputBar); m_inputBar->installEventFilter(this); 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(m_inputBar, &IRCInput::submit, this, &StatusPanel::statusTextEntered); connect(m_inputBar, &IRCInput::textPasted, this, &StatusPanel::textPasted); connect(getTextView(), SIGNAL(textPasted(bool)), m_inputBar, SLOT(paste(bool))); connect(nicknameCombobox, static_cast(&KComboBox::activated), this, &StatusPanel::nicknameComboboxChanged); Q_ASSERT(nicknameCombobox->lineEdit()); //it should be editable. if we design it so it isn't, remove these lines. if(nicknameCombobox->lineEdit()) connect(nicknameCombobox->lineEdit(), SIGNAL (editingFinished()),this,SLOT(nicknameComboboxChanged())); updateAppearance(); } StatusPanel::~StatusPanel() { } void StatusPanel::cycle() { if (m_server) m_server->cycle(); } void StatusPanel::setNickname(const QString& newNickname) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(newNickname)); } void StatusPanel::childAdjustFocus() { m_inputBar->setFocus(); } void StatusPanel::sendText(const QString& sendLine) { // create a work copy QString outputAll(sendLine); // replace aliases and wildcards - m_server->getOutputFilter()->replaceAliases(outputAll, this); + OutputFilter::replaceAliases(outputAll, this); // Send all strings, one after another QStringList outList=outputAll.split('\n'); for(int index=0;indexgetOutputFilter()->parse(m_server->getNickname(), output, QString(), this); if(!result.output.isEmpty()) { if(result.type == Konversation::PrivateMessage) msgHelper(result.typeString, result.output); else appendServerMessage(result.typeString, result.output); } m_server->queue(result.toServer); } // for } void StatusPanel::statusTextEntered() { QString line = sterilizeUnicode(m_inputBar->toPlainText()); m_inputBar->clear(); if (!line.isEmpty()) sendText(line); } void StatusPanel::textPasted(const QString& text) { if(m_server) { QStringList multiline=text.split('\n'); 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 StatusPanel::updateAppearance() { if (Preferences::self()->showNicknameBox()) { if (Preferences::self()->customTextFont()) nicknameCombobox->setFont(Preferences::self()->textFont()); else nicknameCombobox->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); nicknameCombobox->show(); } else nicknameCombobox->hide(); ChatWindow::updateAppearance(); } void StatusPanel::setName(const QString& newName) { ChatWindow::setName(newName); setLogfileName(newName.toLower()); } void StatusPanel::updateName() { QString newName = getServer()->getDisplayName(); setName(newName); setLogfileName(newName.toLower()); } void StatusPanel::sendFileMenu() { emit sendFile(); } void StatusPanel::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 Qt's broken behavior on hidden QListView pages void StatusPanel::showEvent(QShowEvent*) { if(awayChanged) { awayChanged=false; indicateAway(awayState); } } bool StatusPanel::canBeFrontView() { return true; } bool StatusPanel::searchView() { return true; } void StatusPanel::setNotificationsEnabled(bool enable) { if (m_server->getServerGroup()) m_server->getServerGroup()->setNotificationsEnabled(enable); m_notificationsEnabled = enable; } bool StatusPanel::closeYourself(bool confirm) { int result; if (confirm && !m_server->isConnected()) { result = KMessageBox::warningContinueCancel( this, i18n("Do you really want to close '%1'?\n\nAll associated tabs will be closed as well.",getName()), i18n("Close Tab"), KStandardGuiItem::close(), KStandardGuiItem::cancel(), "QuitServerTab"); } else { result = KMessageBox::warningContinueCancel( this, i18n("Do you want to disconnect from '%1'?\n\nAll associated tabs will be closed as well.",m_server->getServerName()), i18n("Disconnect From Server"), KGuiItem(i18n("Disconnect")), KStandardGuiItem::cancel(), "QuitServerTab"); } if (result==KMessageBox::Continue) { if (m_server->getServerGroup()) m_server->getServerGroup()->setNotificationsEnabled(notificationsEnabled()); m_server->quitServer(); // This will delete the status view as well. m_server->deleteLater(); m_server = nullptr; return true; } else { m_recreationScheduled = false; m_server->abortScheduledRecreation(); } return false; } void StatusPanel::nicknameComboboxChanged() { QString newNick=nicknameCombobox->currentText(); oldNick=m_server->getNickname(); if (oldNick != newNick) { nicknameCombobox->setCurrentIndex(nicknameCombobox->findText(oldNick)); m_server->queue("NICK "+newNick); } // return focus to input line m_inputBar->setFocus(); } void StatusPanel::changeNickname(const QString& newNickname) { m_server->queue("NICK "+newNickname); } void StatusPanel::emitUpdateInfo() { emit updateInfo(getServer()->getDisplayName()); } // virtual void StatusPanel::setChannelEncoding(const QString& encoding) { if(m_server->getServerGroup()) Preferences::setChannelEncoding(m_server->getServerGroup()->id(), ":server", encoding); else Preferences::setChannelEncoding(m_server->getDisplayName(), ":server", encoding); } QString StatusPanel::getChannelEncoding() // virtual { if(m_server->getServerGroup()) return Preferences::channelEncoding(m_server->getServerGroup()->id(), ":server"); return Preferences::channelEncoding(m_server->getDisplayName(), ":server"); } // virtual QString StatusPanel::getChannelEncodingDefaultDesc() { return i18n("Identity Default ( %1 )", getServer()->getIdentity()->getCodecName()); } //Used to disable functions when not connected void StatusPanel::serverOnline(bool online) { //m_inputBar->setEnabled(online); nicknameCombobox->setEnabled(online); } void StatusPanel::setServer(Server* server) { ChatWindow::setServer(server); nicknameCombobox->setModel(m_server->nickListModel()); connect(awayLabel, SIGNAL(unaway()), m_server, SLOT(requestUnaway())); connect(awayLabel, SIGNAL(awayMessageChanged(QString)), m_server, SLOT(requestAway(QString))); } diff --git a/src/viewer/viewcontainer.cpp b/src/viewer/viewcontainer.cpp index 25ccb1a1..26f825b2 100644 --- a/src/viewer/viewcontainer.cpp +++ b/src/viewer/viewcontainer.cpp @@ -1,3065 +1,3065 @@ /* 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 #include #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() { } ChatWindow* ViewMimeData::view() const { return m_view; } TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent) { } TabWidget::~TabWidget() { } 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_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() { } 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(qobject_cast(m_tabWidget->currentWidget()))); setViewTreeShown(m_tabWidget->count()); } else { setViewTreeShown(false); } connect(m_viewTree, SIGNAL(sizeChanged()), this, SLOT(saveSplitterSizes())); connect(m_viewTree, SIGNAL(showView(ChatWindow*)), this, SLOT(showView(ChatWindow*))); connect(m_viewTree, SIGNAL(closeView(ChatWindow*)), this, SLOT(closeView(ChatWindow*))); connect(m_viewTree, SIGNAL(showViewContextMenu(QWidget*,QPoint)), this, SLOT(showViewContextMenu(QWidget*,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 = qobject_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 = qobject_cast(m_tabWidget->widget(i)); if (view->isTopLevelView()) { ++count; } } return count; } return 0; } 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) { return QModelIndex(); } int tabIndex = -1; if (parent.isValid()) { int parentTabIndex = m_tabWidget->indexOf(static_cast(parent.internalPointer())); if (parentTabIndex != -1) { tabIndex = parentTabIndex + row + 1; } else { return QModelIndex(); } } else { int count = -1; for (int i = 0; i < m_tabWidget->count(); ++i) { if (qobject_cast(m_tabWidget->widget(i))->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 (qobject_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 !qobject_cast(view)->joined(); } else if (view->getType() == ChatWindow::Query) { return !view->getServer()->isConnected(); } return false; } else if (role == HighlightRole) { return (row == m_popupViewIndex); } 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 = dynamic_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 = dynamic_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; } return false; } 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 = qobject_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 = qobject_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 = qobject_cast(actionCollection()->action("tab_autojoin")); Channel* channel = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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); } qobject_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) { 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()) + if (!Preferences::self()->tabNotificationsLeds() && !Preferences::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); emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole); } 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 = qobject_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); emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole << DisabledRole); m_activeViewOrderList.removeAll(view); } void ViewContainer::toggleViewNotifications() { ChatWindow* view = nullptr; if (m_popupViewIndex == -1) view = qobject_cast(m_tabWidget->currentWidget()); else view = qobject_cast(m_tabWidget->widget(m_popupViewIndex)); if (view) { if (!view->notificationsEnabled()) { view->setNotificationsEnabled(true); updateViews(); KToggleAction* action = qobject_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } else { view->setNotificationsEnabled(false); unsetViewNotification(view); KToggleAction* action = qobject_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 = qobject_cast(m_tabWidget->currentWidget()); else channel = qobject_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 = qobject_cast(m_tabWidget->currentWidget()); else view = qobject_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(ChatWindow*)), this, SLOT(showView(ChatWindow*))); 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 (!qobject_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++diff; } } 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); } beginInsertRows(idx, placement - statusViewIndex - 1, placement - statusViewIndex - 1); } m_tabWidget->insertTab(placement, view, iconSet, QString(label).replace('&', "&&")); endInsertRows(); 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 = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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 << qobject_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::viewSwitched(int newIndex) { ChatWindow* view = qobject_cast(m_tabWidget->widget(newIndex)); if (!view) return; m_lastFocusedView = m_currentView; m_currentView = view; const QModelIndex &idx = indexForView(view); emit viewChanged(idx); if (m_frontView) { m_frontView->resetTabNotification(); 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(); unsetViewNotification(view); view->resetTabNotification(); 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()); } void ViewContainer::showView(ChatWindow* 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 (m_tabWidget->isVisible()) { m_tabWidget->setCurrentIndex(m_tabWidget->indexOf(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 = qobject_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 = qobject_cast(m_tabWidget->widget(index)); if (view->isTopLevelView()) { int lastTopLevelView = -1; for (int i = m_tabWidget->count() - 1; i >= index; --i) { if (qobject_cast(m_tabWidget->widget(i))->isTopLevelView()) { lastTopLevelView = i; break; } } return (index != lastTopLevelView); } else if (!view->isTopLevelView()) { view = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_cast(m_tabWidget->widget(tabIndex)); while (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->tabBar()->moveTab(tabIndex, belowTabIndex); tab = qobject_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 = qobject_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) { 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 = qobject_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 = qobject_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 = qobject_cast(m_tabWidget->currentWidget()); else chatWin = qobject_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 = qobject_cast(tab); m_popupViewIndex = m_tabWidget->indexOf(tab); updateViewActions(m_popupViewIndex); QMenu* menu = qobject_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 = qobject_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; 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 qobject_cast(m_tabWidget->widget(index)); } QList > ViewContainer::getChannelsURI() { QList > URIList; for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = qobject_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++) qobject_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 == NULL if canBeFrontView() == false for active ChatWindow serverView = m_frontView->getServer()->getStatusView(); else if (m_frontServer) // m_frontView == NULL && m_frontServer != NULL 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 = qobject_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 = qobject_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 = qobject_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 = actionCollection()->action("join_channel"); if (action) action->setEnabled(state == Konversation::SSConnected); if (m_frontView && m_frontView->getServer() == server && m_frontView->getType() == ChatWindow::Channel) { ChatWindow* view = m_frontView; Channel* channel = qobject_cast(view); action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(state == Konversation::SSConnected && 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 = qobject_cast(m_tabWidget->currentWidget()); else channel = qobject_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 = qobject_cast(m_tabWidget->currentWidget()); channel->showOptionsDialog(); } } void ViewContainer::toggleChannelNicklists() { KToggleAction* action = qobject_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 = qobject_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 = qobject_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 = qobject_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 = qobject_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. */