diff --git a/src/application.cpp b/src/application.cpp index 54a9c09b..be773cd4 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 static_cast(QApplication::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 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/highlight_config.cpp b/src/config/highlight_config.cpp index ef420f66..3418874d 100644 --- a/src/config/highlight_config.cpp +++ b/src/config/highlight_config.cpp @@ -1,322 +1,322 @@ /* 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 Dario Abatianni Copyright (C) 2006 John Tapsell */ #include "highlight_config.h" #include "highlightviewitem.h" #include "application.h" #include "sound.h" #include "preferences.h" #include #include #include Highlight_Config::Highlight_Config(QWidget* parent, const char* name) : QWidget(parent) { setObjectName(name); setupUi(this); // reset flag to defined state (used to block signals when just selecting a new item) newItemSelected = false; loadSettings(); soundPlayBtn->setIcon(QIcon::fromTheme("media-playback-start")); soundURL->setWhatsThis(i18n("Select Sound File")); // This code was copied from KNotifyWidget::openSoundDialog() (knotifydialog.cpp) [it's under LGPL v2] // find the first "sound"-resource that contains files QStringList soundDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "konversation/sounds"); soundDirs += QStandardPaths::locate(QStandardPaths::GenericDataLocation, "sounds", QStandardPaths::LocateDirectory); if (!soundDirs.isEmpty()) { QDir dir; dir.setFilter( QDir::Files | QDir::Readable ); QStringList::ConstIterator it = soundDirs.constBegin(); while ( it != soundDirs.constEnd() ) { dir = *it; if ( dir.isReadable() && dir.count() > 2 ) { soundURL->setStartDir(QUrl(*it)); break; } ++it; } } // End copy connect(highlightListView, &QTreeWidget::currentItemChanged, this, &Highlight_Config::highlightSelected); connect(highlightListView, &QTreeWidget::itemChanged, this, &Highlight_Config::modified); connect(highlightListView, &HighlightTreeWidget::itemDropped, this, &Highlight_Config::modified); connect(patternInput, &KLineEdit::textChanged, this, &Highlight_Config::patternChanged); connect(enableNotificationsCheckbox, &QCheckBox::toggled, this, &Highlight_Config::notifyModeChanged); connect(patternColor, &KColorButton::changed, this, &Highlight_Config::colorChanged); connect(soundURL, &KUrlRequester::textChanged, this, &Highlight_Config::soundURLChanged); connect(soundPlayBtn, &QToolButton::clicked, this, &Highlight_Config::playSound); connect(autoTextInput, &KLineEdit::textChanged, this, &Highlight_Config::autoTextChanged); connect(chatWindowsInput, &KLineEdit::textChanged, this, &Highlight_Config::chatWindowsChanged); connect(newButton, &QPushButton::clicked, this, &Highlight_Config::addHighlight); connect(removeButton, &QPushButton::clicked, this, &Highlight_Config::removeHighlight); updateButtons(); } Highlight_Config::~Highlight_Config() { } void Highlight_Config::restorePageToDefaults() { if (highlightListView->topLevelItemCount() != 0) { highlightListView->clear(); emit modified(); } } void Highlight_Config::loadSettings() { highlightListView->clear(); foreach (Highlight* currentHighlight, Preferences::highlightList()) { HighlightViewItem *item = new HighlightViewItem(highlightListView,currentHighlight); item->setFlags(item->flags() &~ Qt::ItemIsDropEnabled); } highlightListView->setCurrentItem(highlightListView->topLevelItem(0)); // remember current list for hasChanged() m_oldHighlightList=currentHighlightList(); } bool Highlight_Config::hasChanged() { return (m_oldHighlightList!=currentHighlightList()); } // Slots: void Highlight_Config::highlightSelected(QTreeWidgetItem* item) { // check if there was a widget selected at all if (item) { // make a highlight item out of the generic qlistviewitem - HighlightViewItem* highlightItem = static_cast(item); + HighlightViewItem* highlightItem = dynamic_cast(item); // tell all now emitted signals that we just clicked on a new item, so they should // not emit the modified() signal. newItemSelected = true; patternColor->setColor(highlightItem->getColor()); patternInput->setText(highlightItem->getPattern()); enableNotificationsCheckbox->setChecked(highlightItem->getNotify()); soundURL->setUrl(highlightItem->getSoundURL()); autoTextInput->setText(highlightItem->getAutoText()); chatWindowsInput->setText(highlightItem->getChatWindows()); // all signals will now emit the modified() signal again newItemSelected = false; // remember to enable all edit widgets } updateButtons(); } void Highlight_Config::updateButtons() { bool enabled = highlightListView->currentItem() != nullptr; // enable or disable edit widgets patternLabel->setEnabled(enabled); patternInput->setEnabled(enabled); colorLabel->setEnabled(enabled); patternColor->setEnabled(enabled); enableNotificationsLabel->setEnabled(enabled); enableNotificationsCheckbox->setEnabled(enabled); soundURL->setEnabled(enabled); soundLabel->setEnabled(enabled); soundPlayBtn->setEnabled(enabled); autoTextLabel->setEnabled(enabled); autoTextInput->setEnabled(enabled); chatWindowsLabel->setEnabled(enabled); chatWindowsInput->setEnabled(enabled); } void Highlight_Config::patternChanged(const QString& newPattern) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setPattern(newPattern); emit modified(); } } void Highlight_Config::notifyModeChanged(bool enabled) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setNotify(enabled); emit modified(); } } void Highlight_Config::colorChanged(const QColor& newColor) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setColor(newColor); emit modified(); } } void Highlight_Config::soundURLChanged(const QString& newURL) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setSoundURL(QUrl(newURL)); emit modified(); } } void Highlight_Config::autoTextChanged(const QString& newText) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setAutoText(newText); emit modified(); } } void Highlight_Config::chatWindowsChanged(const QString& newChatWindows) { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (!newItemSelected && item) { item->setChatWindows(newChatWindows); emit modified(); } } void Highlight_Config::addHighlight() { Highlight* newHighlight = new Highlight(i18n("New"), false, QColor("#ff0000"), QUrl(), QString(), QString(), true); HighlightViewItem* item = new HighlightViewItem(highlightListView, newHighlight); item->setFlags(item->flags() &~ Qt::ItemIsDropEnabled); highlightListView->setCurrentItem(item); patternInput->setFocus(); patternInput->selectAll(); emit modified(); } void Highlight_Config::removeHighlight() { - HighlightViewItem* item = static_cast(highlightListView->currentItem()); + HighlightViewItem* item = dynamic_cast(highlightListView->currentItem()); if (item) { delete item; - item = static_cast(highlightListView->currentItem()); + item = dynamic_cast(highlightListView->currentItem()); if (item) { highlightListView->setCurrentItem(item); } emit modified(); } updateButtons(); } QList Highlight_Config::getHighlightList() { QList newList; - HighlightViewItem* item = static_cast(highlightListView->topLevelItem(0)); + HighlightViewItem* item = dynamic_cast(highlightListView->topLevelItem(0)); while (item) { newList.append(new Highlight(item->getPattern(), item->getRegExp(), item->getColor(), item->getSoundURL(), item->getAutoText(), item->getChatWindows(), item->getNotify())); - item = static_cast(highlightListView->itemBelow(item)); + item = dynamic_cast(highlightListView->itemBelow(item)); } return newList; } QStringList Highlight_Config::currentHighlightList() { QStringList newList; - HighlightViewItem* item = static_cast(highlightListView->topLevelItem(0)); + HighlightViewItem* item = dynamic_cast(highlightListView->topLevelItem(0)); while (item) { newList.append(item->getPattern() + QString(item->getRegExp()) + item->getColor().name() + item->getSoundURL().url() + item->getAutoText() + item->getChatWindows() + QString::number(item->getNotify())); - item = static_cast(highlightListView->itemBelow(item)); + item = dynamic_cast(highlightListView->itemBelow(item)); } return newList; } void Highlight_Config::playSound() { Application *konvApp = Application::instance(); konvApp->sound()->play(soundURL->url()); } void Highlight_Config::saveSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); // Write all highlight entries QList hiList = getHighlightList(); int i = 0; foreach (Highlight* hl, hiList) { KConfigGroup grp = config->group(QString("Highlight%1").arg(i)); grp.writeEntry("Pattern", hl->getPattern()); grp.writeEntry("RegExp", hl->getRegExp()); grp.writeEntry("Color", hl->getColor()); grp.writePathEntry("Sound", hl->getSoundURL().url()); grp.writeEntry("AutoText", hl->getAutoText()); grp.writeEntry("ChatWindows", hl->getChatWindows()); grp.writeEntry("Notify", hl->getNotify()); i++; } Preferences::setHighlightList(hiList); // Remove unused entries... while (config->hasGroup(QString("Highlight%1").arg(i))) { config->deleteGroup(QString("Highlight%1").arg(i)); i++; } // remember current list for hasChanged() m_oldHighlightList=currentHighlightList(); } diff --git a/src/config/ignore_config.cpp b/src/config/ignore_config.cpp index 78ad74da..04dc0c23 100644 --- a/src/config/ignore_config.cpp +++ b/src/config/ignore_config.cpp @@ -1,210 +1,210 @@ /* 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 Dario Abatianni Copyright (C) 2006 John Tapsell */ #include "ignore_config.h" #include "ignorelistviewitem.h" #include "ignore.h" #include "preferences.h" #include Ignore_Config::Ignore_Config( QWidget* parent, const char* name, Qt::WindowFlags fl ) : QWidget( parent, fl ) { setObjectName(QString::fromLatin1(name)); setupUi(this); connect(newButton, &QPushButton::clicked, this, &Ignore_Config::newIgnore); connect(removeButton, &QPushButton::clicked, this, &Ignore_Config::removeIgnore); connect(removeAllButton, &QPushButton::clicked, this, &Ignore_Config::removeAllIgnore); connect(ignoreListView, &QTreeWidget::currentItemChanged, this, &Ignore_Config::select); connect(chkChannel, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(chkQuery, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(chkNotice, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(chkCTCP, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(chkDCC, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(chkInvite, &QCheckBox::clicked, this, &Ignore_Config::flagCheckboxChanged); connect(txtPattern, &KLineEdit::textChanged, this, &Ignore_Config::flagCheckboxChanged); // connect(chkException, SIGNAL(clicked()), this, SLOT(flagCheckboxChanged())); loadSettings(); ignoreListView->header()->setSectionsMovable(false); } Ignore_Config::~Ignore_Config() { } void Ignore_Config::newIgnore() { QTreeWidgetItem *item = new IgnoreListViewItem(ignoreListView, "new!new@new.new", Ignore::Channel | Ignore::Query | Ignore::Notice | Ignore::CTCP | Ignore::DCC | Ignore::Invite); ignoreListView->setCurrentItem(item); txtPattern->setFocus(); txtPattern->selectAll(); updateEnabledness(); emit modified(); } void Ignore_Config::removeAllIgnore() { ignoreListView->clear(); updateEnabledness(); emit modified(); } void Ignore_Config::removeIgnore() { delete ignoreListView->currentItem(); updateEnabledness(); emit modified(); } QList Ignore_Config::getIgnoreList() { QList newList; QTreeWidgetItem *root = ignoreListView->invisibleRootItem(); for (int i = 0; i < root->childCount(); ++i) { - IgnoreListViewItem* item = static_cast(root->child(i)); + IgnoreListViewItem* item = dynamic_cast(root->child(i)); Ignore* newItem=new Ignore(item->text(0),item->getFlags()); newList.append(newItem); } return newList; } // returns the currently visible ignore list as QStringList to make comparing easy QStringList Ignore_Config::currentIgnoreList() { QStringList newList; QTreeWidgetItem *root = ignoreListView->invisibleRootItem(); for (int i = 0; i < root->childCount(); ++i) { - IgnoreListViewItem* item = static_cast(root->child(i)); + IgnoreListViewItem* item = dynamic_cast(root->child(i)); newList.append(item->text(0)+QLatin1Char(' ')+QString(item->getFlags())); } return newList; } // checks if the currently visible ignore list differs from the currently saved one bool Ignore_Config::hasChanged() { return(m_oldIgnoreList!=currentIgnoreList()); } void Ignore_Config::restorePageToDefaults() { if(ignoreListView->topLevelItemCount() > 0) { ignoreListView->clear(); updateEnabledness(); emit modified(); } } void Ignore_Config::saveSettings() { Preferences::setIgnoreList(getIgnoreList()); // remember the list for hasChanged() m_oldIgnoreList=currentIgnoreList(); } void Ignore_Config::loadSettings() { ignoreListView->clear(); foreach (Ignore* item, Preferences::ignoreList()) { new IgnoreListViewItem(ignoreListView, item->getName(), item->getFlags()); } ignoreListView->sortItems(0, Qt::AscendingOrder); // remember the list for hasChanged() m_oldIgnoreList=currentIgnoreList(); updateEnabledness(); } void Ignore_Config::updateEnabledness() { - IgnoreListViewItem* selectedItem=static_cast(ignoreListView->currentItem()); + IgnoreListViewItem* selectedItem=dynamic_cast(ignoreListView->currentItem()); chkChannel->setEnabled(selectedItem != nullptr); chkQuery->setEnabled(selectedItem != nullptr); chkNotice->setEnabled(selectedItem != nullptr); chkCTCP->setEnabled(selectedItem != nullptr); chkDCC->setEnabled(selectedItem != nullptr); chkInvite->setEnabled(selectedItem != nullptr); // chkExceptions->setEnabled(selectedItem != NULL); txtPattern->setEnabled(selectedItem != nullptr); removeButton->setEnabled(selectedItem != nullptr); removeAllButton->setEnabled(ignoreListView->topLevelItemCount() > 0); } void Ignore_Config::select(QTreeWidgetItem* item) { updateEnabledness(); - IgnoreListViewItem* selectedItem=static_cast(item); + IgnoreListViewItem* selectedItem=dynamic_cast(item); if(selectedItem) { int flags = selectedItem->getFlags(); chkChannel->setChecked(flags & Ignore::Channel); chkQuery->setChecked(flags & Ignore::Query); chkNotice->setChecked(flags & Ignore::Notice); chkCTCP->setChecked(flags & Ignore::CTCP); chkDCC->setChecked(flags & Ignore::DCC); chkInvite->setChecked(flags & Ignore::Invite); txtPattern->blockSignals(true); txtPattern->setText(selectedItem->getName()); txtPattern->blockSignals(false); // chkExceptions->setChecked(flags & Ignore::Exception) ; } } void Ignore_Config::flagCheckboxChanged() { int flags = 0; if(chkChannel->isChecked()) flags |= Ignore::Channel; if(chkQuery->isChecked()) flags |= Ignore::Query; if(chkNotice->isChecked()) flags |= Ignore::Notice; if(chkCTCP->isChecked()) flags |= Ignore::CTCP; if(chkDCC->isChecked()) flags |= Ignore::DCC; if(chkInvite->isChecked()) flags |= Ignore::Invite; // if(chkExceptions->isChecked()) flags |= Ignore::Exceptions; - IgnoreListViewItem* selectedItem=static_cast(ignoreListView->currentItem()); + IgnoreListViewItem* selectedItem=dynamic_cast(ignoreListView->currentItem()); if(selectedItem) { selectedItem->setFlags(flags); selectedItem->setName(txtPattern->text()); } emit modified(); } /* * Sets the strings of the subwidgets using the current * language. */ void Ignore_Config::languageChange() { loadSettings(); } diff --git a/src/dcc/transferdetailedinfopanel.cpp b/src/dcc/transferdetailedinfopanel.cpp index 3ef9d63a..2a9b06ea 100644 --- a/src/dcc/transferdetailedinfopanel.cpp +++ b/src/dcc/transferdetailedinfopanel.cpp @@ -1,285 +1,285 @@ /* 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) 2007 Shintaro Matsuoka Copyright (C) 2009 Bernd Buschinski */ #include "transferdetailedinfopanel.h" #include "transfer.h" #include "transferrecv.h" #include "transfermanager.h" #include "application.h" #include "connectionmanager.h" #include "server.h" #include "transferlistmodel.h" #include "dcccommon.h" #include #include namespace Konversation { namespace DCC { TransferDetailedInfoPanel::TransferDetailedInfoPanel(QWidget * parent) : QTabWidget(parent) { QWidget *tab = new QWidget(this); m_locationInfo.setupUi(tab); addTab(tab, i18n("Status") ); tab = new QWidget(this); m_timeInfo.setupUi(tab); addTab(tab, i18n("Details")); m_transfer = nullptr; m_autoViewUpdateTimer = new QTimer(this); m_autoViewUpdateTimer->setInterval(1000); connect(m_locationInfo.m_urlreqLocation, &KUrlRequester::textChanged, this, &TransferDetailedInfoPanel::slotLocationChanged); connect(Application::instance()->getDccTransferManager(), SIGNAL(fileURLChanged(Konversation::DCC::TransferRecv*)), this, SLOT(updateView())); // it's a little rough.. //only enable when needed m_locationInfo.m_urlreqLocation->lineEdit()->setReadOnly(true); m_locationInfo.m_urlreqLocation->button()->setEnabled(false); m_locationInfo.m_urlreqLocation->setMode(KFile::File | KFile::LocalOnly); } TransferDetailedInfoPanel::~TransferDetailedInfoPanel() { } void TransferDetailedInfoPanel::setTransfer(Transfer *item) { if (item == m_transfer) { return; } clear(); m_transfer = item; // set up the auto view-update timer connect(m_autoViewUpdateTimer, &QTimer::timeout, this, &TransferDetailedInfoPanel::updateChangeableView); // If the file is already being transferred, the timer must be started here, // otherwise the information will not be updated every 0.5sec if (m_transfer->getStatus() == Transfer::Transferring) m_autoViewUpdateTimer->start(500); connect(item, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(slotTransferStatusChanged(Konversation::DCC::Transfer*,int,int))); updateView(); } Transfer *TransferDetailedInfoPanel::transfer() const { return m_transfer; } void TransferDetailedInfoPanel::clear() { m_autoViewUpdateTimer->stop(); // disconnect all slots once disconnect(this); // we can't do disconnect( m_item->transfer(), 0, this, 0 ) here // because m_item can have been deleted already. m_transfer = nullptr; m_locationInfo.m_urlreqLocation->lineEdit()->setReadOnly(true); m_locationInfo.m_urlreqLocation->lineEdit()->setFrame(false); m_locationInfo.m_urlreqLocation->button()->setEnabled(false); m_locationInfo.m_labelDccType->clear(); m_locationInfo.m_labelFilename->clear(); m_locationInfo.m_urlreqLocation->clear(); m_locationInfo.m_labelPartner->clear(); m_locationInfo.m_labelSelf->clear(); m_timeInfo.m_labelFileSize->clear(); m_timeInfo.m_labelIsResumed->clear(); m_timeInfo.m_labelTimeOffered->clear(); m_timeInfo.m_labelTimeStarted->clear(); m_timeInfo.m_labelTimeFinished->clear(); m_locationInfo.m_labelStatus->clear(); m_locationInfo.m_progress->setValue(0); m_timeInfo.m_labelCurrentPosition->clear(); m_timeInfo.m_labelCurrentSpeed->clear(); m_timeInfo.m_labelAverageSpeed->clear(); m_timeInfo.m_labelTransferringTime->clear(); m_timeInfo.m_labelTimeLeft->clear(); } void TransferDetailedInfoPanel::updateView() { if (!m_transfer) { return; } // Type: QString type(m_transfer->getType() == Transfer::Send ? i18n("DCC Send") : i18n("DCC Receive")); if (m_transfer->isReverse()) { type += i18n(" (Reverse DCC)"); } m_locationInfo.m_labelDccType->setText(type); // Filename: m_locationInfo.m_labelFilename->setText(m_transfer->getFileName()); // Location: m_locationInfo.m_urlreqLocation->setUrl(m_transfer->getFileURL()); //m_urlreqLocation->lineEdit()->setFocusPolicy( m_item->getStatus() == Transfer::Queued ? Qt::StrongFocus : ClickFocus ); m_locationInfo.m_urlreqLocation->lineEdit()->setReadOnly(m_transfer->getStatus() != Transfer::Queued); m_locationInfo.m_urlreqLocation->lineEdit()->setFrame(m_transfer->getStatus() == Transfer::Queued); m_locationInfo.m_urlreqLocation->button()->setEnabled(m_transfer->getStatus() == Transfer::Queued); // Partner: QString partnerInfoServerName; if (m_transfer->getConnectionId() == -1) partnerInfoServerName = i18n("Unknown server"); else if (Application::instance()->getConnectionManager()->getServerByConnectionId(m_transfer->getConnectionId())) partnerInfoServerName = Application::instance()->getConnectionManager()->getServerByConnectionId(m_transfer->getConnectionId())->getServerName(); else partnerInfoServerName = i18n("Unknown server"); if (!m_transfer->getPartnerIp().isEmpty()) { m_locationInfo.m_labelPartner->setText(i18nc("%1=partnerNick, %2=IRC Servername, %3=partnerIP, %4=partnerPort", "%1 on %2, %3 (port %4)", m_transfer->getPartnerNick().isEmpty() ? "?" : m_transfer->getPartnerNick(), partnerInfoServerName, m_transfer->getPartnerIp(), QString::number(m_transfer->getPartnerPort()))); } else { m_locationInfo.m_labelPartner->setText(i18nc("%1 = PartnerNick, %2 = Partner IRC Servername","%1 on %2", m_transfer->getPartnerNick().isEmpty() ? "?" : m_transfer->getPartnerNick(), partnerInfoServerName)); } // Self: if (!m_transfer->getOwnIp().isEmpty()) m_locationInfo.m_labelSelf->setText(i18nc("%1=ownIP, %2=ownPort", "%1 (port %2)", m_transfer->getOwnIp(), QString::number(m_transfer->getOwnPort()))); // File Size: m_timeInfo.m_labelFileSize->setText(KFormat().formatByteSize(m_transfer->getFileSize())); // Resumed: if (m_transfer->isResumed()) m_timeInfo.m_labelIsResumed->setText(ki18nc("%1=Transferstartposition","Yes, %1").subs(m_transfer->getTransferStartPosition()).toString()); else m_timeInfo.m_labelIsResumed->setText(i18nc("no - not a resumed transfer","No")); // Offered at: m_timeInfo.m_labelTimeOffered->setText(m_transfer->getTimeOffer().toString("hh:mm:ss")); // Started at: if (!m_transfer->getTimeTransferStarted().isNull()) m_timeInfo.m_labelTimeStarted->setText(m_transfer->getTimeTransferStarted().toString("hh:mm:ss")); // Finished at: if (!m_transfer->getTimeTransferFinished().isNull()) { m_timeInfo.m_labelTimeFinished->setText(m_transfer->getTimeTransferFinished().toString("hh:mm:ss")); } updateChangeableView(); } void TransferDetailedInfoPanel::updateChangeableView() { if (!m_transfer) { return; } // Status: if (m_transfer->getStatus() == Transfer::Transferring) { m_locationInfo.m_labelStatus->setText(TransferListModel::getStatusText(m_transfer->getStatus(), m_transfer->getType()) + " ( " + TransferListModel::getSpeedPrettyText(m_transfer->getCurrentSpeed()) + " )"); } else { m_locationInfo.m_labelStatus->setText(m_transfer->getStatusDetail().isEmpty() ? TransferListModel::getStatusText(m_transfer->getStatus(), m_transfer->getType()) : TransferListModel::getStatusText(m_transfer->getStatus(), m_transfer->getType()) + " (" + m_transfer->getStatusDetail() + ')'); } // Progress: m_locationInfo.m_progress->setValue(m_transfer->getProgress()); // Current Position: m_timeInfo.m_labelCurrentPosition->setText(KFormat().formatByteSize(m_transfer->getTransferringPosition())); // Current Speed: m_timeInfo.m_labelCurrentSpeed->setText(TransferListModel::getSpeedPrettyText(m_transfer->getCurrentSpeed())); // Average Speed: m_timeInfo.m_labelAverageSpeed->setText(TransferListModel::getSpeedPrettyText(m_transfer->getAverageSpeed())); // Transferring Time: if (!m_transfer->getTimeTransferStarted().isNull()) { int m_itemringTime; // The m_item is still in progress if (m_transfer->getTimeTransferFinished().isNull()) m_itemringTime = m_transfer->getTimeTransferStarted().secsTo(QDateTime::currentDateTime()); // The m_item has finished else m_itemringTime = m_transfer->getTimeTransferStarted().secsTo(m_transfer->getTimeTransferFinished()); if (m_itemringTime >= 1) m_timeInfo.m_labelTransferringTime->setText(TransferListModel::secToHMS(m_itemringTime)); else m_timeInfo.m_labelTransferringTime->setText(i18nc("less than 1 sec","< 1sec")); } // Estimated Time Left: m_timeInfo.m_labelTimeLeft->setText(TransferListModel::getTimeLeftPrettyText(m_transfer->getTimeLeft())); } void TransferDetailedInfoPanel::slotTransferStatusChanged(Transfer *transfer, int newStatus, int oldStatus) { Q_UNUSED(transfer); updateView(); if (newStatus == Transfer::Transferring) { // start auto view-update timer m_autoViewUpdateTimer->start(); } else if (oldStatus == Transfer::Transferring) { // stop auto view-update timer m_autoViewUpdateTimer->stop(); } } void TransferDetailedInfoPanel::slotLocationChanged(const QString &url) { if (m_transfer && m_transfer->getType() == Transfer::Receive) { - TransferRecv *transfer = static_cast(m_transfer); + TransferRecv *transfer = qobject_cast(m_transfer); transfer->setFileURL(QUrl::fromLocalFile(url)); updateView(); } } } } diff --git a/src/dcc/transfermanager.cpp b/src/dcc/transfermanager.cpp index 589496aa..09c558b8 100644 --- a/src/dcc/transfermanager.cpp +++ b/src/dcc/transfermanager.cpp @@ -1,428 +1,428 @@ /* 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) 2007 Shintaro Matsuoka Copyright (C) 2009 Michael Kreitzer Copyright (C) 2009 Bernd Buschinski */ #include #include "transfermanager.h" #include "transferrecv.h" #include "transfersend.h" #include "application.h" #include "preferences.h" #include "upnpmcastsocket.h" #include "upnprouter.h" #include "transfer.h" #include "chat.h" using namespace Konversation::UPnP; namespace Konversation { namespace DCC { TransferManager::TransferManager( QObject* parent ) : QObject( parent ) { // initial number m_nextReverseTokenNumber = 1001; m_defaultIncomingFolder = Preferences::self()->dccPath(); connect( Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(slotSettingsChanged()) ); m_upnpRouter = nullptr; m_upnpSocket = nullptr; if (Preferences::self()->dccUPnP()) startupUPnP(); } TransferManager::~TransferManager() { qDebug(); foreach (TransferSend* sendItem, m_sendItems) { sendItem->abort(); } foreach (TransferRecv* recvItem, m_recvItems) { recvItem->abort(); } foreach (Chat* chatItem, m_chatItems) { chatItem->close(); } // give the sockets a minor break to close everything 100% // yes I am aware of the fact that this is not really needed, theoretical while (!m_sendItems.isEmpty()) { delete m_sendItems.takeFirst(); } while (!m_recvItems.isEmpty()) { delete m_recvItems.takeFirst(); } while (!m_chatItems.isEmpty()) { delete m_chatItems.takeFirst(); } shutdownUPnP(); } void TransferManager::startupUPnP(void) { m_upnpSocket = new UPnPMCastSocket(); connect(m_upnpSocket, &UPnP::UPnPMCastSocket::discovered, this, &TransferManager::upnpRouterDiscovered); m_upnpSocket->discover(); } void TransferManager::shutdownUPnP(void) { // This deletes the router too. delete m_upnpSocket; m_upnpSocket = nullptr; m_upnpRouter = nullptr; } TransferRecv* TransferManager::newDownload() { TransferRecv* transfer = new TransferRecv(this); m_recvItems.push_back( transfer ); connect(transfer, &TransferRecv::removed, this, &TransferManager::removeRecvItem); initTransfer( transfer ); return transfer; } TransferSend* TransferManager::newUpload() { TransferSend* transfer = new TransferSend(this); m_sendItems.push_back( transfer ); connect(transfer, &TransferSend::removed, this, &TransferManager::removeSendItem); initTransfer( transfer ); return transfer; } Chat* TransferManager::newChat() { Chat* chat = new Chat(this); m_chatItems.append(chat); connect(chat, &Chat::removed, this, &TransferManager::removeChatItem); return chat; } TransferSend* TransferManager::rejectSend(int connectionId, const QString& partnerNick, const QString& fileName) { TransferSend* transfer = nullptr; // find applicable one foreach (TransferSend* it, m_sendItems ) { if ( ( it->getStatus() == Transfer::Queued || it->getStatus() == Transfer::WaitingRemote ) && it->getConnectionId() == connectionId && it->getPartnerNick() == partnerNick && it->getFileName() == fileName ) { transfer = it; qDebug() << "Filename match: " << fileName; break; } } if ( transfer ) transfer->reject(); return transfer; } Chat* TransferManager::rejectChat(int connectionId, const QString& partnerNick) { Chat* chat = nullptr; // find applicable one foreach (Chat* it, m_chatItems) { if (it->status() == Chat::WaitingRemote && it->connectionId() == connectionId && it->partnerNick() == partnerNick) { chat = it; break; } } if (chat) chat->reject(); return chat; } TransferRecv* TransferManager::resumeDownload( int connectionId, const QString& partnerNick, const QString& fileName, quint16 ownPort, quint64 position ) { TransferRecv* transfer = nullptr; // find applicable one foreach (TransferRecv* it, m_recvItems ) { if ( ( it->getStatus() == Transfer::Queued || it->getStatus() == Transfer::WaitingRemote ) && it->getConnectionId() == connectionId && it->getPartnerNick() == partnerNick && it->getFileName() == fileName && it->isResumed() ) { transfer = it; qDebug() << "Filename match: " << fileName << ", claimed port: " << ownPort << ", item port: " << transfer->getOwnPort(); // the port number can be changed behind NAT, so we pick an item which only the filename is correspondent in that case. if ( transfer->getOwnPort() == ownPort ) { break; } } } if ( transfer ) transfer->startResume( position ); return transfer; } TransferSend* TransferManager::resumeUpload( int connectionId, const QString& partnerNick, const QString& fileName, quint16 ownPort, quint64 position ) { TransferSend* transfer = nullptr; // find applicable one foreach ( TransferSend* it, m_sendItems ) { if ( ( it->getStatus() == Transfer::Queued || it->getStatus() == Transfer::WaitingRemote ) && it->getConnectionId() == connectionId && it->getPartnerNick() == partnerNick && it->getFileName() == fileName && !it->isResumed() ) { transfer = it; qDebug() << "Filename match: " << fileName << ", claimed port: " << ownPort << ", item port: " << transfer->getOwnPort(); // the port number can be changed behind NAT, so we pick an item which only the filename is correspondent in that case. if ( transfer->getOwnPort() == ownPort ) { break; } } } if ( transfer ) transfer->setResume( position ); return transfer; } TransferSend* TransferManager::startReverseSending( int connectionId, const QString& partnerNick, const QString& fileName, const QString& partnerHost, quint16 partnerPort, quint64 fileSize, const QString& token ) { qDebug() << "Server group ID: " << connectionId << ", partner: " << partnerNick << ", filename: " << fileName << ", partner IP: " << partnerHost << ", parnter port: " << partnerPort << ", filesize: " << fileSize << ", token: " << token; TransferSend* transfer = nullptr; // find applicable one foreach ( TransferSend* it, m_sendItems ) { if ( it->getStatus() == Transfer::WaitingRemote && it->getConnectionId() == connectionId && it->getPartnerNick() == partnerNick && it->getFileName() == fileName && it->getFileSize() == fileSize && it->getReverseToken() == token ) { transfer = it; break; } } if ( transfer ) transfer->connectToReceiver( partnerHost, partnerPort ); return transfer; } Chat* TransferManager::startReverseChat(int connectionId, const QString& partnerNick, const QString& partnerHost, quint16 partnerPort, const QString& token) { qDebug() << "Server group ID: " << connectionId << ", partner: " << partnerNick << ", partner IP: " << partnerHost << ", parnter port: " << partnerPort << ", token: " << token; Chat* chat = nullptr; // find applicable one foreach (Chat* it, m_chatItems) { if ( it->status() == Chat::WaitingRemote && it->connectionId() == connectionId && it->partnerNick() == partnerNick && it->reverseToken() == token ) { chat = it; break; } } if (chat) { chat->setPartnerIp(partnerHost); chat->setPartnerPort(partnerPort); chat->connectToPartner(); } return chat; } void TransferManager::acceptDccGet(int connectionId, const QString& partnerNick, const QString& fileName) { qDebug() << "Server group ID: " << connectionId << ", partner: " << partnerNick << ", filename: " << fileName; bool nickEmpty = partnerNick.isEmpty(); bool fileEmpty = fileName.isEmpty(); foreach ( TransferRecv* it, m_recvItems ) { if ( it->getStatus() == Transfer::Queued && it->getConnectionId() == connectionId && (nickEmpty || it->getPartnerNick() == partnerNick) && (fileEmpty || it->getFileName() == fileName) ) { it->start(); } } } void TransferManager::initTransfer( Transfer* transfer ) { connect(transfer, &TransferSend::statusChanged, this, &TransferManager::slotTransferStatusChanged); emit newTransferAdded( transfer ); } bool TransferManager::isLocalFileInWritingProcess( const QUrl &url ) const { foreach ( TransferRecv* it, m_recvItems ) { if ( ( it->getStatus() == Transfer::Connecting || it->getStatus() == Transfer::Transferring ) && it->getFileURL() == url ) { return true; } } return false; } int TransferManager::generateReverseTokenNumber() { return m_nextReverseTokenNumber++; } bool TransferManager::hasActiveTransfers() { foreach ( TransferSend* it, m_sendItems ) { if (it->getStatus() == Transfer::Transferring) return true; } foreach ( TransferRecv* it, m_recvItems ) { if (it->getStatus() == Transfer::Transferring) return true; } return false; } bool TransferManager::hasActiveChats() { foreach (Chat* chat, m_chatItems) { if (chat->status() == Chat::Chatting) return true; } return false; } void TransferManager::slotTransferStatusChanged( Transfer* item, int newStatus, int oldStatus ) { qDebug() << oldStatus << " -> " << newStatus << " " << item->getFileName() << " (" << item->getType() << ")"; if ( newStatus == Transfer::Queued ) emit newDccTransferQueued( item ); } void TransferManager::slotSettingsChanged() { // update the default incoming directory for already existed DCCRECV items if ( Preferences::self()->dccPath() != m_defaultIncomingFolder ) { foreach ( TransferRecv* it, m_recvItems ) { if ( it->getStatus() == Transfer::Queued && it->getFileURL().adjusted(QUrl::RemoveFilename) == m_defaultIncomingFolder.adjusted(QUrl::RemoveFilename)) { QUrl url = QUrl::fromLocalFile(Preferences::self()->dccPath().adjusted(QUrl::StripTrailingSlash).toString() + QDir::separator() + it->getFileURL().fileName()); it->setFileURL(url); emit fileURLChanged( it ); } } m_defaultIncomingFolder = Preferences::self()->dccPath(); } } void TransferManager::removeSendItem( Transfer* item ) { - TransferSend* transfer = static_cast< TransferSend* > ( item ); + TransferSend* transfer = qobject_cast< TransferSend* > ( item ); m_sendItems.removeOne( transfer ); item->deleteLater(); } void TransferManager::removeRecvItem( Transfer* item ) { - TransferRecv* transfer = static_cast< TransferRecv* > ( item ); + TransferRecv* transfer = qobject_cast< TransferRecv* > ( item ); m_recvItems.removeOne( transfer ); item->deleteLater(); } void TransferManager::removeChatItem(Konversation::DCC::Chat* chat) { m_chatItems.removeOne(chat); chat->deleteLater(); } void TransferManager::upnpRouterDiscovered(UPnPRouter *router) { qDebug() << "Router discovered!"; // Assuming only 1 router for now m_upnpRouter = router; } UPnPRouter* TransferManager::getUPnPRouter() { return m_upnpRouter; } } } diff --git a/src/dcc/transferpanel.cpp b/src/dcc/transferpanel.cpp index acf3ec32..1dc6b2a5 100644 --- a/src/dcc/transferpanel.cpp +++ b/src/dcc/transferpanel.cpp @@ -1,533 +1,533 @@ /* 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. */ /* begin: Mit Aug 7 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ /* Copyright (C) 2004-2008 Shintaro Matsuoka Copyright (C) 2009,2010 Bernd Buschinski */ #include "transferpanel.h" #include "application.h" #include "transferdetailedinfopanel.h" #include "transfermanager.h" #include "transfersend.h" #include "preferences.h" #include "transferview.h" #include "transferlistmodel.h" #include #include #include #include #include #include #include namespace Konversation { namespace DCC { TransferPanel::TransferPanel(QWidget *parent) : ChatWindow(parent) { setType(ChatWindow::DccTransferPanel); setName(i18n("DCC Status")); initGUI(); connect(Application::instance()->getDccTransferManager(), SIGNAL(newTransferAdded(Konversation::DCC::Transfer*)), this, SLOT(slotNewTransferAdded(Konversation::DCC::Transfer*))); } TransferPanel::~TransferPanel() { KConfigGroup config(KSharedConfig::openConfig(), "DCC Settings"); const QByteArray state = m_splitter->saveState(); config.writeEntry(QString("PanelSplitter"), state.toBase64()); } void TransferPanel::initGUI() { setSpacing(0); m_toolBar = new KToolBar(this, true, true); m_toolBar->setObjectName("dccstatus_toolbar"); m_splitter = new QSplitter(this); m_splitter->setOrientation(Qt::Vertical); m_transferView = new TransferView(m_splitter); connect(m_transferView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateButton())); connect(m_transferView, &TransferView::runSelectedTransfers, this, &TransferPanel::runDcc); // detailed info panel m_detailPanel = new TransferDetailedInfoPanel(m_splitter); m_splitter->setStretchFactor(0, QSizePolicy::Expanding); // popup menu m_popup = new QMenu(this); m_selectAll = m_popup->addAction(i18n("&Select All Items"), this, SLOT(selectAll())); m_selectAllCompleted = m_popup->addAction(i18n("S&elect All Completed Items"), this, SLOT(selectAllCompleted())); m_popup->addSeparator(); // ----- m_accept = m_popup->addAction(QIcon::fromTheme("media-playback-start"), i18n("&Accept"), this, SLOT(acceptDcc())); m_accept->setStatusTip(i18n("Start receiving")); m_abort = m_popup->addAction(QIcon::fromTheme("process-stop"),i18n("A&bort"), this, SLOT(abortDcc())); m_abort->setStatusTip(i18n("Abort the transfer(s)")); m_popup->addSeparator(); // ----- m_resend = m_popup->addAction(QIcon::fromTheme("edit-redo"),i18n("Resend"), this, SLOT(resendFile())); m_clear = m_popup->addAction(QIcon::fromTheme("edit-delete"),i18nc("clear selected dcctransfer","&Clear"), this, SLOT(clearDcc())); m_clear->setStatusTip(i18n("Clear all selected Items")); m_clearCompleted = m_popup->addAction(QIcon::fromTheme("edit-clear-list"),i18n("Clear Completed"), this, SLOT(clearCompletedDcc())); m_clearCompleted->setStatusTip(i18n("Clear Completed Items")); m_popup->addSeparator(); // ----- m_open = m_popup->addAction(QIcon::fromTheme("system-run"), i18n("&Open File"), this, SLOT(runDcc())); m_open->setStatusTip(i18n("Run the file")); m_openLocation = m_popup->addAction(QIcon::fromTheme("document-open-folder"), i18n("Open Location"), this, SLOT(openLocation())); m_openLocation->setStatusTip(i18n("Open the file location")); m_transferView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_transferView, &TransferView::customContextMenuRequested, this, &TransferPanel::popupRequested); // misc. connect(m_transferView, &TransferView::doubleClicked, this, &TransferPanel::doubleClicked); connect(m_transferView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(setDetailPanelItem(QItemSelection,QItemSelection))); m_toolBar->addAction(m_accept); m_toolBar->addAction(m_abort); m_toolBar->addAction(m_clear); m_toolBar->addAction(m_clearCompleted); m_toolBar->addAction(m_open); m_toolBar->addAction(m_openLocation); KConfigGroup config(KSharedConfig::openConfig(), "DCC Settings"); QByteArray state; if (config.hasKey("PanelSplitter")) { state = config.readEntry("PanelSplitter", state); state = QByteArray::fromBase64(state); m_splitter->restoreState(state); } updateButton(); } void TransferPanel::slotNewTransferAdded(Transfer *transfer) { connect(transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(slotTransferStatusChanged())); m_transferView->addTransfer(transfer); if (m_transferView->itemCount() == 1) { m_transferView->selectAll(); m_detailPanel->setTransfer(transfer); updateButton(); } } void TransferPanel::slotTransferStatusChanged() { updateButton(); activateTabNotification(Konversation::tnfSystem); } void TransferPanel::updateButton() { bool accept = false, abort = false, clear = false, info = false, open = false, openLocation = false, resend = false, selectAll = false, selectAllCompleted = false; QItemSelectionModel *selectionModel = m_transferView->selectionModel(); foreach (const QModelIndex &index, m_transferView->rowIndexes()) { Transfer::Type type = (Transfer::Type)index.data(TransferListModel::TransferType).toInt(); Transfer::Status status = (Transfer::Status)index.data(TransferListModel::TransferStatus).toInt(); selectAll = true; selectAllCompleted |= (status >= Transfer::Done); if (selectionModel->isRowSelected(index.row(), QModelIndex())) { accept |= (status == Transfer::Queued); abort |= (status < Transfer::Done); clear |= (status >= Transfer::Done); info |= (type == Transfer::Send || status == Transfer::Done); open |= (type == Transfer::Send || status == Transfer::Done); openLocation = true; resend |= (type == Transfer::Send && status >= Transfer::Done); } } if (!KAuthorized::authorizeAction("allow_downloading")) { accept = false; } m_selectAll->setEnabled(selectAll); m_selectAllCompleted->setEnabled(selectAllCompleted); m_accept->setEnabled(accept); m_abort->setEnabled(abort); m_clear->setEnabled(clear); m_clearCompleted->setEnabled(selectAllCompleted); m_open->setEnabled(open); m_openLocation->setEnabled(openLocation); m_resend->setEnabled(resend); } void TransferPanel::setDetailPanelItem (const QItemSelection &/*newindex*/, const QItemSelection &/*oldindex*/) { QModelIndex index; if (m_transferView->selectionModel()->selectedRows().contains(m_transferView->selectionModel()->currentIndex())) { index = m_transferView->selectionModel()->currentIndex(); } else if (!m_transferView->selectionModel()->selectedRows().isEmpty()) { index = m_transferView->selectionModel()->selectedRows().first(); } if (index.isValid()) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { m_detailPanel->setTransfer(transfer); } } } void TransferPanel::acceptDcc() { foreach (const QModelIndex &index, m_transferView->selectedRows()) { if (index.data(TransferListModel::TransferType).toInt() == Transfer::Receive && index.data(TransferListModel::TransferStatus).toInt() == Transfer::Queued) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { transfer->start(); } } } updateButton(); } void TransferPanel::abortDcc() { foreach (const QModelIndex &index, m_transferView->selectedRows()) { if (index.data(TransferListModel::TransferStatus).toInt() < Transfer::Done) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { transfer->abort(); } } } updateButton(); } void TransferPanel::resendFile() { QList transferList; foreach (const QModelIndex &index, m_transferView->selectedRows()) { if (index.data(TransferListModel::TransferType).toInt() == Transfer::Send && index.data(TransferListModel::TransferStatus).toInt() >= Transfer::Done) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (!transfer) { continue; } transferList.append(transfer); } } foreach (Transfer* transfer, transferList) { TransferSend *newTransfer = Application::instance()->getDccTransferManager()->newUpload(); newTransfer->setConnectionId(transfer->getConnectionId()); newTransfer->setPartnerNick(transfer->getPartnerNick()); newTransfer->setFileURL(transfer->getFileURL()); newTransfer->setFileName(transfer->getFileName()); newTransfer->setReverse(transfer->isReverse()); if (newTransfer->queue()) { newTransfer->start(); } } } //sort QModelIndexList descending bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2) { return index1.row() >= index2.row(); } void TransferPanel::clearDcc() { //selected item Transfer *transfer = m_detailPanel->transfer(); if (transfer && transfer->getStatus() >= Transfer::Done) { //item will be gone transfer = nullptr; } QModelIndexList indexes = m_transferView->selectedRows(); QModelIndexList indexesToRemove; foreach (const QModelIndex &index, indexes) { if (index.data(TransferListModel::TransferStatus).toInt() >= Transfer::Done) { indexesToRemove.append(index); } } //sort QModelIndexList descending //NOTE: selectedRows() returned an unsorted list qSort(indexesToRemove.begin(), indexesToRemove.end(), rowGreaterThan); //remove from last to first item, to keep a valid row foreach (const QModelIndex &index, indexesToRemove) { m_transferView->model()->removeRow(index.row(), QModelIndex()); //needed, otherwise valid rows "can be treated" as invalid, //proxymodel does not keep up with changes m_transferView->updateModel(); } //remove all gone items foreach (const QModelIndex &index, indexesToRemove) { indexes.removeOne(index); } m_transferView->clearSelection(); QList toSelectList; //select everything that got not removed foreach (const QModelIndex &index, indexes) { int offset = 0; foreach (const QModelIndex &removedIndex, indexesToRemove) { if (removedIndex.row() < index.row()) { ++offset; } } toSelectList.append(index.row() - offset); } m_transferView->selectRows(toSelectList); if (transfer) { m_detailPanel->setTransfer(transfer); } else if (!transfer || m_transferView->itemCount() == 0 || m_transferView->selectedIndexes().count() == 0) { m_detailPanel->clear(); } updateButton(); } void TransferPanel::clearCompletedDcc() { //save selected item Transfer *transfer = m_detailPanel->transfer(); if (transfer && transfer->getStatus() >= Transfer::Done) { //item will be gone transfer = nullptr; } QModelIndexList indexesToRemove; QModelIndexList selectedIndexes = m_transferView->selectedRows(); foreach (const QModelIndex &index, m_transferView->rowIndexes()) { if (index.data(TransferListModel::TransferStatus).toInt() >= Transfer::Done) { indexesToRemove.append(index); } } //sort QModelIndexList descending //NOTE: selectedRows() returned an unsorted list qSort(indexesToRemove.begin(), indexesToRemove.end(), rowGreaterThan); //remove from last to first item, to keep a valid row foreach (const QModelIndex &index, indexesToRemove) { m_transferView->model()->removeRow(index.row(), QModelIndex()); //needed, otherwise valid rows "can be treated" as invalid, //proxymodel does not keep up with changes m_transferView->updateModel(); } //remove all gone items foreach (const QModelIndex &index, indexesToRemove) { selectedIndexes.removeOne(index); } m_transferView->clearSelection(); QList toSelectList; //select everything that got not removed foreach (const QModelIndex &index, selectedIndexes) { int offset = 0; foreach (const QModelIndex &removedIndex, indexesToRemove) { if (removedIndex.row() < index.row()) { ++offset; } } toSelectList.append(index.row() - offset); } m_transferView->selectRows(toSelectList); if (transfer) { m_detailPanel->setTransfer(transfer); } else if (m_transferView->itemCount() == 0 || m_transferView->selectedIndexes().count() == 0 || !transfer) { m_detailPanel->clear(); } updateButton(); } void TransferPanel::runDcc() { const int selectedRows = m_transferView->selectedRows().count(); if (selectedRows > 3) { int ret = KMessageBox::questionYesNo(this, i18np("You have selected %1 file to execute, are you sure you want to continue?", "You have selected %1 files to execute, are you sure you want to continue?", selectedRows), i18np("Execute %1 file", "Execute %1 files", selectedRows) ); if (ret == KMessageBox::No) { return; } } foreach (const QModelIndex &index, m_transferView->selectedRows()) { if (index.data(TransferListModel::TransferType).toInt() == Transfer::Send || index.data(TransferListModel::TransferStatus).toInt() == Transfer::Done) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { transfer->runFile(); } } } } void TransferPanel::openLocation() { foreach (const QModelIndex &index, m_transferView->selectedRows()) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { openLocation(transfer); } } } void TransferPanel::selectAll() { m_transferView->selectAll(); updateButton(); } void TransferPanel::selectAllCompleted() { m_transferView->selectAllCompleted(); updateButton(); } void TransferPanel::popupRequested(const QPoint &pos) { updateButton(); m_popup->popup(QWidget::mapToGlobal(m_transferView->viewport()->mapTo(this, pos))); } void TransferPanel::doubleClicked(const QModelIndex &index) { - Transfer *transfer = static_cast(index.data(TransferListModel::TransferPointer).value()); + Transfer *transfer = qobject_cast(index.data(TransferListModel::TransferPointer).value()); if (transfer) { transfer->runFile(); } } // virtual void TransferPanel::childAdjustFocus() { } TransferView *TransferPanel::getTransferView() { return m_transferView; } void TransferPanel::openLocation(Transfer *transfer) { QString urlString = transfer->getFileURL().toString(QUrl::PreferLocalFile|QUrl::RemoveFilename|QUrl::StripTrailingSlash); if (!urlString.isEmpty()) { QUrl url(QUrl::fromLocalFile(urlString)); new KRun(url, nullptr, true); } } } } diff --git a/src/dcc/transferrecv.cpp b/src/dcc/transferrecv.cpp index 05db293e..5b76382b 100644 --- a/src/dcc/transferrecv.cpp +++ b/src/dcc/transferrecv.cpp @@ -1,966 +1,966 @@ /* receive a file on DCC protocol begin: Mit Aug 7 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ /* Copyright (C) 2004-2007 Shintaro Matsuoka Copyright (C) 2004,2005 John Tapsell Copyright (C) 2009 Michael Kreitzer Copyright (C) 2009 Bernd Buschinski */ /* 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. */ #include #ifdef Q_OS_WIN // Prevent windows system header files from defining min/max as macros. #define NOMINMAX 1 #include #endif #include "transferrecv.h" #include "dcccommon.h" #include "transfermanager.h" #include "application.h" #include "connectionmanager.h" #include "server.h" #include "upnprouter.h" #include #include #include #include #include #include #include /* *flow chart* TransferRecv() start() : called from TransferPanel when user pushes the accept button | \ | requestResume() : called when user chooses to resume in ResumeDialog. it emits the signal ResumeRequest() | | startResume() : called by "Server" | | connectToSender() connectionSuccess() : called by recvSocket */ namespace Konversation { namespace DCC { TransferRecv::TransferRecv(QObject *parent) : Transfer(Transfer::Receive, parent) { qDebug(); m_serverSocket = nullptr; m_recvSocket = nullptr; m_writeCacheHandler = nullptr; m_connectionTimer = new QTimer(this); m_connectionTimer->setSingleShot(true); connect(m_connectionTimer, &QTimer::timeout, this, &TransferRecv::connectionTimeout); //timer hasn't started yet. qtimer will be deleted automatically when 'this' object is deleted } TransferRecv::~TransferRecv() { qDebug(); cleanUp(); } void TransferRecv::cleanUp() { qDebug(); stopConnectionTimer(); disconnect(m_connectionTimer, nullptr, nullptr, nullptr); finishTransferLogger(); if (m_serverSocket) { m_serverSocket->close(); m_serverSocket = nullptr; if (m_reverse && Preferences::self()->dccUPnP()) { UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter(); if (router) { router->undoForward(m_ownPort, QAbstractSocket::TcpSocket); } } } if (m_recvSocket) { disconnect(m_recvSocket, nullptr, nullptr, nullptr); m_recvSocket->close(); m_recvSocket = nullptr; // the instance will be deleted automatically by its parent } if (m_writeCacheHandler) { m_writeCacheHandler->closeNow(); m_writeCacheHandler->deleteLater(); m_writeCacheHandler = nullptr; } Transfer::cleanUp(); } void TransferRecv::setPartnerIp(const QString &ip) { if (getStatus() == Configuring) { m_partnerIp = ip; } } void TransferRecv::setPartnerPort(quint16 port) { if (getStatus() == Configuring) { m_partnerPort = port; } } void TransferRecv::setFileSize(quint64 fileSize) { if (getStatus() == Configuring) { m_fileSize = fileSize; } } void TransferRecv::setFileName(const QString &fileName) { if (getStatus() == Configuring) { m_fileName = fileName; m_saveFileName = m_fileName; } } void TransferRecv::setFileURL(const QUrl &url) { if (getStatus() == Preparing || getStatus() == Configuring || getStatus() == Queued) { m_fileURL = url; m_saveFileName = url.fileName(); } } void TransferRecv::setReverse(bool reverse, const QString &reverseToken) { if (getStatus() == Configuring) { m_reverse = reverse; if (reverse) { m_partnerPort = 0; m_reverseToken = reverseToken; } } } bool TransferRecv::queue() { qDebug(); if (getStatus() != Configuring) { return false; } if (m_partnerIp.isEmpty()) { return false; } if (m_ownIp.isEmpty()) { m_ownIp = DccCommon::getOwnIp(Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId)); } if (!KAuthorized::authorizeAction("allow_downloading")) { //note we have this after the initialisations so that item looks okay //Do not have the rights to send the file. Shouldn't have gotten this far anyway failed(i18n("The admin has restricted the right to receive files")); return false; } // check if the sender IP is valid if (m_partnerIp == "0.0.0.0") { failed(i18n("Invalid sender address (%1)", m_partnerIp)); return false; } // TODO: should we support it? if (m_fileSize == 0) { failed(i18n("Unsupported negotiation (filesize=0)")); return false; } if (m_fileName.isEmpty()) { m_fileName = "unnamed_file_" + QDateTime::currentDateTime().toString(Qt::ISODate).remove(':'); m_saveFileName = m_fileName; } if (m_fileURL.isEmpty()) { // determine default incoming file URL // set default folder if (!Preferences::self()->dccPath().isEmpty()) { m_fileURL = Preferences::self()->dccPath(); } else { m_fileURL.setPath(KUser(KUser::UseRealUserID).homeDir()); // default folder is *not* specified } //buschinski TODO CHECK ME // add a slash if there is none //m_fileURL.adjustPath(KUrl::AddTrailingSlash); // Append folder with partner's name if wanted if (Preferences::self()->dccCreateFolder()) { m_fileURL = m_fileURL.adjusted(QUrl::StripTrailingSlash); m_fileURL.setPath(m_fileURL.path() + QDir::separator() + m_partnerNick); } // Just incase anyone tries to do anything nasty QString fileNameSanitized = sanitizeFileName(m_saveFileName); // Append partner's name to file name if wanted if (Preferences::self()->dccAddPartner()) { m_fileURL = m_fileURL.adjusted(QUrl::StripTrailingSlash); m_fileURL.setPath(m_fileURL.path() + QDir::separator() + m_partnerNick + '.' + fileNameSanitized); } else { m_fileURL = m_fileURL.adjusted(QUrl::StripTrailingSlash); m_fileURL.setPath(m_fileURL.path() + QDir::separator() + fileNameSanitized); } } return Transfer::queue(); } void TransferRecv::abort() // public slot { qDebug(); if (getStatus() == Transfer::Queued) { Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId); if (server) { server->dccRejectSend(m_partnerNick, transferFileName(m_fileName)); } } if(m_writeCacheHandler) { m_writeCacheHandler->write(true); // flush } cleanUp(); setStatus(Aborted); emit done(this); } void TransferRecv::start() // public slot { qDebug() << "[BEGIN]"; if (getStatus() != Queued) { return; } setStatus(Preparing); prepareLocalKio(false, false); qDebug() << "[END]"; } void TransferRecv::prepareLocalKio(bool overwrite, bool resume, KIO::fileoffset_t startPosition) { qDebug() << "URL: " << m_fileURL << endl << "Overwrite: " << overwrite << endl << "Resume: " << resume << " (Position: " << startPosition << ")"; m_resumed = resume; m_transferringPosition = startPosition; if (!createDirs(KIO::upUrl(m_fileURL))) { askAndPrepareLocalKio(i18n("Cannot create the folder or destination is not writable.
" "Folder: %1
", KIO::upUrl(m_fileURL).toString()), ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel | ResumeDialog::RA_OverwriteDefaultPath, ResumeDialog::RA_Rename); return; } if (Application::instance()->getDccTransferManager()->isLocalFileInWritingProcess(m_fileURL)) { askAndPrepareLocalKio(i18n("The file is used by another transfer.
" "%1
", m_fileURL.toString()), ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel, ResumeDialog::RA_Rename); return; } KIO::JobFlags flags; if(overwrite) { flags |= KIO::Overwrite; } if(m_resumed) { flags |= KIO::Resume; } //for now, maybe later flags |= KIO::HideProgressInfo; KIO::TransferJob *transferJob = KIO::put(m_fileURL, -1, flags); if (!transferJob) { qDebug() << "KIO::put() returned NULL. what happened?"; failed(i18n("Could not create a KIO instance")); return; } transferJob->setAutoDelete(true); connect(transferJob, &KIO::TransferJob::canResume, this, &TransferRecv::slotLocalCanResume); connect(transferJob, &KIO::TransferJob::result, this, &TransferRecv::slotLocalGotResult); connect(transferJob, &KIO::TransferJob::dataReq, this, &TransferRecv::slotLocalReady); } void TransferRecv::askAndPrepareLocalKio(const QString &message, int enabledActions, ResumeDialog::ReceiveAction defaultAction, KIO::fileoffset_t startPosition) { switch (ResumeDialog::ask(this, message, enabledActions, defaultAction)) { case ResumeDialog::RA_Resume: prepareLocalKio(false, true, startPosition); break; case ResumeDialog::RA_Overwrite: prepareLocalKio(true, false); break; case ResumeDialog::RA_Rename: prepareLocalKio(false, false); break; case ResumeDialog::RA_Cancel: default: setStatus(Queued); } } bool TransferRecv::createDirs(const QUrl &dirURL) const { QUrl kurl(dirURL); //First we split directories until we reach to the top, //since we need to create directories one by one QList dirList; while (kurl != KIO::upUrl(kurl)) { dirList.prepend(kurl); kurl = KIO::upUrl(kurl); } //Now we create the directories QList::ConstIterator it; for (it=dirList.constBegin(); it != dirList.constEnd(); ++it) { KIO::StatJob* statJob = KIO::stat(*it, KIO::StatJob::SourceSide, 0); statJob->exec(); if (statJob->error()) { KIO::MkdirJob* job = KIO::mkdir(*it, -1); if (!job->exec()) { return false; } } } #ifndef Q_OS_WIN QFileInfo dirInfo(dirURL.toLocalFile()); if (!dirInfo.isWritable()) { return false; } #else //!TODO find equivalent windows solution //from 4.7 QFile Doc: // File permissions are handled differently on Linux/Mac OS X and Windows. // In a non writable directory on Linux, files cannot be created. // This is not always the case on Windows, where, for instance, // the 'My Documents' directory usually is not writable, but it is still // possible to create files in it. #endif return true; } void TransferRecv::slotLocalCanResume(KIO::Job *job, KIO::filesize_t size) { qDebug() << "[BEGIN]" << endl << "size: " << size; KIO::TransferJob* transferJob = dynamic_cast(job); if (!transferJob) { qDebug() << "not a TransferJob? returning"; return; } if (size != 0) { disconnect(transferJob, nullptr, nullptr, nullptr); if (Preferences::self()->dccAutoResume()) { prepareLocalKio(false, true, size); } else { askAndPrepareLocalKio(i18np( "A partial file exists:
" "%2
" "Size of the partial file: 1 byte.
", "A partial file exists:
" "%2
" "Size of the partial file: %1 bytes.
", size, m_fileURL.toString()), ResumeDialog::RA_Resume | ResumeDialog::RA_Overwrite | ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel, ResumeDialog::RA_Resume, size); } transferJob->putOnHold(); } qDebug() << "[END]"; } void TransferRecv::slotLocalGotResult(KJob *job) { qDebug() << "[BEGIN]"; - KIO::TransferJob* transferJob = static_cast(job); + KIO::TransferJob* transferJob = qobject_cast(job); disconnect(transferJob, nullptr, nullptr, nullptr); switch (transferJob->error()) { case 0: // no error qDebug() << "job->error() returned 0." << endl << "Why was I called in spite of no error?"; break; case KIO::ERR_FILE_ALREADY_EXIST: askAndPrepareLocalKio(i18nc("%1=fileName, %2=local filesize, %3=sender filesize", "The file already exists.
" "%1 (%2)
" "Sender reports file size of %3
", m_fileURL.toString(), KIO::convertSize(QFileInfo(m_fileURL.path()).size()), KIO::convertSize(m_fileSize)), ResumeDialog::RA_Overwrite | ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel, ResumeDialog::RA_Overwrite); break; default: askAndPrepareLocalKio(i18n("Could not open the file.
" "Error: %1

" "%2
", transferJob->error(), m_fileURL.toString()), ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel, ResumeDialog::RA_Rename); } qDebug() << "[END]"; } void TransferRecv::slotLocalReady(KIO::Job *job) { qDebug(); - KIO::TransferJob* transferJob = static_cast(job); + KIO::TransferJob* transferJob = qobject_cast(job); disconnect(transferJob, nullptr, nullptr, nullptr); // WriteCacheHandler will control the job after this m_writeCacheHandler = new TransferRecvWriteCacheHandler(transferJob); connect(m_writeCacheHandler, &TransferRecvWriteCacheHandler::done, this, &TransferRecv::slotLocalWriteDone); connect(m_writeCacheHandler, &TransferRecvWriteCacheHandler::gotError, this, &TransferRecv::slotLocalGotWriteError); if (!m_resumed) { connectWithSender(); } else { requestResume(); } } void TransferRecv::connectWithSender() { if (m_reverse) { if (!startListeningForSender()) { return; } Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId); if (!server) { failed(i18n("Could not send Reverse DCC SEND acknowledgement to the partner via the IRC server.")); return; } m_ownIp = DccCommon::getOwnIp(server); m_ownPort = m_serverSocket->serverPort(); if (Preferences::self()->dccUPnP()) { UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter(); if (router && router->forward(QHostAddress(server->getOwnIpByNetworkInterface()), m_ownPort, QAbstractSocket::TcpSocket)) { connect(router, &UPnP::UPnPRouter::forwardComplete, this, &TransferRecv::sendReverseAck); } else { sendReverseAck(true, 0); // Try anyways on error } } else { sendReverseAck(false, 0); } } else { connectToSendServer(); } } void TransferRecv::sendReverseAck(bool error, quint16 port) { Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId); if (!server) { failed(i18n("Could not send Reverse DCC SEND acknowledgement to the partner via the IRC server.")); return; } qDebug(); if (Preferences::self()->dccUPnP() && this->sender()) { if (port != m_ownPort) return; // Somebody elses forward succeeded disconnect (this->sender(), SIGNAL(forwardComplete(bool,quint16)), this, SLOT(sendRequest(bool,quint16))); if (error) { server->appendMessageToFrontmost(i18nc("Universal Plug and Play", "UPnP"), i18n("Failed to forward port %1. Sending DCC request to remote user regardless.", QString::number(m_ownPort)), QHash(), false); } } setStatus(WaitingRemote, i18n("Waiting for connection")); server->dccReverseSendAck(m_partnerNick, transferFileName(m_fileName), DccCommon::textIpToNumericalIp(m_ownIp), m_ownPort, m_fileSize, m_reverseToken); } void TransferRecv::requestResume() { qDebug(); setStatus(WaitingRemote, i18n("Waiting for remote host's acceptance")); startConnectionTimer(30); qDebug() << "Requesting resume for " << m_partnerNick << " file " << m_fileName << " partner " << m_partnerPort; Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId); if (!server) { qDebug() << "Could not retrieve the instance of Server. Connection id: " << m_connectionId; failed(i18n("Could not send DCC RECV resume request to the partner via the IRC server.")); return; } if (m_reverse) { server->dccPassiveResumeGetRequest(m_partnerNick, transferFileName(m_fileName), m_partnerPort, m_transferringPosition, m_reverseToken); } else { server->dccResumeGetRequest(m_partnerNick, transferFileName(m_fileName), m_partnerPort, m_transferringPosition); } } // public slot void TransferRecv::startResume(quint64 position) { qDebug() << "Position:" << position; stopConnectionTimer(); if ((quint64)m_transferringPosition != position) { qDebug() << "remote responded with an unexpected position"<< endl << "expected: " << m_transferringPosition << endl << "remote response: " << position; failed(i18n("Unexpected response from remote host")); return; } connectWithSender(); } void TransferRecv::connectToSendServer() { qDebug(); // connect to sender setStatus(Connecting); startConnectionTimer(30); m_recvSocket = new QTcpSocket(this); connect(m_recvSocket, &QTcpSocket::connected, this, &TransferRecv::startReceiving); connect(m_recvSocket, static_cast(&QTcpSocket::error), this, &TransferRecv::connectionFailed); qDebug() << "Attempting to connect to " << m_partnerIp << ":" << m_partnerPort; m_recvSocket->connectToHost(m_partnerIp, m_partnerPort); } bool TransferRecv::startListeningForSender() { // Set up server socket QString failedReason; if (Preferences::self()->dccSpecificSendPorts()) { m_serverSocket = DccCommon::createServerSocketAndListen(this, &failedReason, Preferences::self()->dccSendPortsFirst(), Preferences::self()->dccSendPortsLast()); } else { m_serverSocket = DccCommon::createServerSocketAndListen(this, &failedReason); } if (!m_serverSocket) { failed(failedReason); return false; } connect(m_serverSocket, &QTcpServer::newConnection, this, &TransferRecv::slotServerSocketReadyAccept); startConnectionTimer(30); return true; } void TransferRecv::slotServerSocketReadyAccept() { //reverse dcc m_recvSocket = m_serverSocket->nextPendingConnection(); if (!m_recvSocket) { failed(i18n("Could not accept the connection (socket error).")); return; } connect(m_recvSocket, static_cast(&QTcpSocket::error), this, &TransferRecv::connectionFailed); // we don't need ServerSocket anymore m_serverSocket->close(); m_serverSocket = nullptr; // Will be deleted by parent if (Preferences::self()->dccUPnP()) { UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter(); if (router) { router->undoForward(m_ownPort, QAbstractSocket::TcpSocket); } } startReceiving(); } void TransferRecv::startReceiving() { qDebug(); stopConnectionTimer(); connect(m_recvSocket, &QTcpSocket::readyRead, this, &TransferRecv::readData); m_transferStartPosition = m_transferringPosition; //we don't need the original filename anymore, overwrite it to display the correct one in transfermanager/panel m_fileName = m_saveFileName; m_ownPort = m_recvSocket->localPort(); startTransferLogger(); // initialize CPS counter, ETA counter, etc... setStatus(Transferring); } // slot void TransferRecv::connectionFailed(QAbstractSocket::SocketError errorCode) { qDebug() << "Code = " << errorCode << ", string = " << m_recvSocket->errorString(); failed(m_recvSocket->errorString()); } void TransferRecv::readData() // slot { //qDebug(); qint64 actual = m_recvSocket->read(m_buffer, m_bufferSize); if (actual > 0) { //actual is the size we read in, and is guaranteed to be less than m_bufferSize m_transferringPosition += actual; m_writeCacheHandler->append(m_buffer, actual); m_writeCacheHandler->write(false); //in case we could not read all the data, leftover data could get lost if (m_recvSocket->bytesAvailable() > 0) { readData(); } else { sendAck(); } } } void TransferRecv::sendAck() // slot { //qDebug() << m_transferringPosition << "/" << (KIO::fileoffset_t)m_fileSize; //It is bound to be 32bit according to dcc specs, -> 4GB limit. //But luckily no client ever reads this value, //except for old mIRC versions, but they couldn't send or receive files over 4GB anyway. //Note: The resume and filesize are set via dcc send command and can be over 4GB - quint32 pos = htonl((quint32)m_transferringPosition); + quint32 pos = htonl(static_cast(m_transferringPosition)); m_recvSocket->write((char*)&pos, 4); - if (m_transferringPosition == (KIO::fileoffset_t)m_fileSize) + if (m_transferringPosition == static_cast(m_fileSize)) { qDebug() << "Sent final ACK."; disconnect(m_recvSocket, nullptr, nullptr, nullptr); m_writeCacheHandler->close(); // WriteCacheHandler will send the signal done() } - else if (m_transferringPosition > (KIO::fileoffset_t)m_fileSize) + else if (m_transferringPosition > static_cast(m_fileSize)) { qDebug() << "The remote host sent larger data than expected: " << m_transferringPosition; failed(i18n("Transfer error")); } } void TransferRecv::slotLocalWriteDone() // <-WriteCacheHandler::done() { qDebug(); cleanUp(); setStatus(Done); emit done(this); } // <- WriteCacheHandler::gotError() void TransferRecv::slotLocalGotWriteError(const QString &errorString) { qDebug(); failed(i18n("KIO error: %1", errorString)); } void TransferRecv::startConnectionTimer(int secs) { qDebug(); m_connectionTimer->start(secs * 1000); } void TransferRecv::stopConnectionTimer() { if (m_connectionTimer->isActive()) { m_connectionTimer->stop(); qDebug(); } } void TransferRecv::connectionTimeout() // slot { qDebug(); failed(i18n("Timed out")); } // WriteCacheHandler TransferRecvWriteCacheHandler::TransferRecvWriteCacheHandler(KIO::TransferJob *transferJob) : m_transferJob(transferJob) { m_writeReady = true; m_cacheStream = nullptr; connect(m_transferJob, &KIO::TransferJob::dataReq, this, &TransferRecvWriteCacheHandler::slotKIODataReq); connect(m_transferJob, &KIO::TransferJob::result, this, &TransferRecvWriteCacheHandler::slotKIOResult); m_transferJob->setAsyncDataEnabled(m_writeAsyncMode = true); } TransferRecvWriteCacheHandler::~TransferRecvWriteCacheHandler() { closeNow(); } // public void TransferRecvWriteCacheHandler::append(char *data, int size) { // sendAsyncData() and dataReq() cost a lot of time, so we should pack some caches. static const int maxWritePacketSize = 1 * 1024 * 1024; // 1meg if (m_cacheList.isEmpty() || m_cacheList.back().size() + size > maxWritePacketSize) { m_cacheList.append(QByteArray()); delete m_cacheStream; m_cacheStream = new QDataStream(&m_cacheList.back(), QIODevice::WriteOnly); } m_cacheStream->writeRawData(data, size); } // public bool TransferRecvWriteCacheHandler::write(bool force) { // force == false: return without doing anything when the whole cache size is smaller than maxWritePacketSize if (m_cacheList.isEmpty() || !m_transferJob || !m_writeReady || !m_writeAsyncMode) { return false; } if (!force && m_cacheList.count() < 2) { return false; } // do write m_writeReady = false; m_transferJob->sendAsyncData(m_cacheList.front()); //qDebug() << "wrote " << m_cacheList.front().size() << " bytes."; m_cacheList.pop_front(); return true; } void TransferRecvWriteCacheHandler::close() // public { qDebug(); write(true); // write once if kio is ready to write m_transferJob->setAsyncDataEnabled(m_writeAsyncMode = false); qDebug() << "switched to synchronized mode."; qDebug() << "flushing... (remaining caches: " << m_cacheList.count() << ")"; } void TransferRecvWriteCacheHandler::closeNow() // public { write(true); // flush if (m_transferJob) { m_transferJob->kill(); m_transferJob = nullptr; } m_cacheList.clear(); delete m_cacheStream; m_cacheStream = nullptr; } void TransferRecvWriteCacheHandler::slotKIODataReq(KIO::Job *job, QByteArray &data) { Q_UNUSED(job); // We are in writeAsyncMode if there is more data to be read in from dcc if (m_writeAsyncMode) { m_writeReady = true; } else { // No more data left to read from incoming dcctransfer if (!m_cacheList.isEmpty()) { // once we write everything in cache, the file is complete. // This function will be called once more after this last data is written. data = m_cacheList.front(); qDebug() << "will write " << m_cacheList.front().size() << " bytes."; m_cacheList.pop_front(); } else { // finally, no data left to write or read. qDebug() << "flushing done."; m_transferJob = nullptr; emit done(); // -> TransferRecv::slotLocalWriteDone() } } } void TransferRecvWriteCacheHandler::slotKIOResult(KJob *job) { Q_ASSERT(m_transferJob); disconnect(m_transferJob, nullptr, nullptr, nullptr); m_transferJob = nullptr; if (job->error()) { QString errorString = job->errorString(); closeNow(); emit gotError(errorString); // -> TransferRecv::slotLocalGotWriteError() } } } } diff --git a/src/dcc/transferview.cpp b/src/dcc/transferview.cpp index 58eac581..9fd58530 100644 --- a/src/dcc/transferview.cpp +++ b/src/dcc/transferview.cpp @@ -1,657 +1,657 @@ /* This class represents a DCC transferview for transfermodel. */ /* 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) 2009,2010 Bernd Buschinski */ #include "transferview.h" #include #include #include #include #include #include #include "dcccommon.h" namespace Konversation { namespace DCC { TransferView::TransferView(QWidget *parent) : QTreeView(parent) { m_categorieFlags = None; m_dccModel = new TransferListModel(this); m_proxyModel = new TransferListProxyModel(this); m_proxyModel->setDynamicSortFilter(true); m_proxyModel->setSourceModel(m_dccModel); setModel(m_proxyModel); // doc says it improves performance // but brings problems with KCategoryDrawer starting with kde4.4 setUniformRowHeights(false); setSortingEnabled(true); setRootIsDecorated(false); //not implemented for special items setSelectionMode(QAbstractItemView::ExtendedSelection); m_categoryDrawer = new KCategoryDrawer(nullptr); setItemDelegate(new TransferSizeDelegate(m_categoryDrawer, this)); //only after model was set restoreColumns(); //only after model and columns were set setProgressBarDeletegate(); header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerCustomContextMenuRequested(QPoint))); m_activeTransfers = 0; m_itemCategoryToRemove = 0; m_updateTimer = new QTimer(this); m_updateTimer->setInterval(1000); connect(m_updateTimer, &QTimer::timeout, this, &TransferView::update); connect(model(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(rowsAboutToBeRemovedFromModel(QModelIndex,int,int))); //we can't use rowsRemoved here, it seems when rowsRemoved is emitted //the rows are not permanently removed from model, //so if we trigger a new removeRows in our slot, //the new remove happens before the old pending connect(m_dccModel, &TransferListModel::rowsPermanentlyRemoved, this, &TransferView::rowsRemovedFromModel); } TransferView::~TransferView() { disconnect(m_updateTimer, nullptr, nullptr, nullptr); saveColumns(); clear(); delete m_categoryDrawer; } void TransferView::clear() { if (rowCount() > 0) { removeItems(TransferItemData::SendItem); removeItems(TransferItemData::ReceiveItem); } } void TransferView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int type = index.data(TransferListModel::TransferDisplayType).toInt(); if (type == TransferItemData::SendCategory || type == TransferItemData::ReceiveCategory) { QStyleOptionViewItem _option(option); _option.rect.adjust(1, 1, -1, 0); m_categoryDrawer->drawCategory(index, 0, //ignored anyway _option, painter); } else { QTreeView::drawRow(painter, option, index); } } void TransferView::addTransfer(Transfer *transfer) { //save selected rows QModelIndexList selectedIndexes = selectedRows(); QList selectedItems; foreach (const QModelIndex &index, selectedIndexes) { selectedItems.append(index.data(TransferListModel::TransferPointer)); } if (transfer->getType() == Transfer::Send) { if (!(m_categorieFlags & SendCategory)) { addItem(nullptr, TransferItemData::SendCategory); m_categorieFlags |= SendCategory; } if ((m_categorieFlags & ReceiveCategory) && !(m_categorieFlags & SpacerRow)) { addItem(nullptr, TransferItemData::SpaceRow); m_categorieFlags |= SpacerRow; } addItem(transfer, TransferItemData::SendItem); } else if (transfer->getType() == Transfer::Receive) { if (!(m_categorieFlags & ReceiveCategory)) { addItem(nullptr, TransferItemData::ReceiveCategory); m_categorieFlags |= ReceiveCategory; } if ((m_categorieFlags & SendCategory) && !(m_categorieFlags & SpacerRow)) { addItem(nullptr, TransferItemData::SpaceRow); m_categorieFlags |= SpacerRow; } addItem(transfer, TransferItemData::ReceiveItem); } //catch already running transfers if (transfer->getStatus() == Transfer::Transferring) { ++m_activeTransfers; if (m_activeTransfers > 0 && !m_updateTimer->isActive()) { m_updateTimer->start(); qDebug() << "timer start"; } } connect (transfer, SIGNAL(statusChanged(Konversation::DCC::Transfer*,int,int)), this, SLOT(transferStatusChanged(Konversation::DCC::Transfer*,int,int))); clearSelection(); //restore selected QList rows; foreach (const QModelIndex &index, rowIndexes()) { QVariant pointer = index.data(TransferListModel::TransferPointer); if (selectedItems.contains(pointer)) { selectedItems.removeOne(pointer); rows.append(index.row()); if (selectedItems.isEmpty()) { break; } } } selectRows(rows); } void TransferView::addItem(Transfer *transfer, TransferItemData::ItemDisplayType type) { TransferItemData tD; tD.displayType = type; tD.transfer = transfer; m_dccModel->append(tD); } void TransferView::transferStatusChanged(Transfer *transfer, int newStatus, int oldStatus) { Q_ASSERT(newStatus != oldStatus); QModelIndex rowIndex = index(transfer); if (rowIndex.isValid()) { dataChanged(rowIndex, index(rowIndex.row(), model()->columnCount()-1)); } if (newStatus == Transfer::Transferring) { ++m_activeTransfers; if (m_activeTransfers > 0 && !m_updateTimer->isActive()) { m_updateTimer->start(); } } if (oldStatus == Transfer::Transferring) { --m_activeTransfers; if (m_activeTransfers <= 0 && m_updateTimer->isActive()) { m_updateTimer->stop(); } } update(); } int TransferView::itemCount() const { int offset = 0; if (m_categorieFlags & SendCategory) { ++offset; } if (m_categorieFlags & ReceiveCategory) { ++offset; } if (m_categorieFlags & SpacerRow) { ++offset; } return m_dccModel->rowCount() - offset; } int TransferView::rowCount() const { return m_dccModel->rowCount(); } QList TransferView::rowIndexes(int column) const { QList list; if (column >= m_dccModel->columnCount()) { return list; } for (int i = 0; i < m_dccModel->rowCount(); ++i) { QModelIndex index = m_proxyModel->index(i, column); int displaytype = index.data(TransferListModel::TransferDisplayType).toInt(); if (displaytype == TransferItemData::ReceiveItem || displaytype == TransferItemData::SendItem) { list.append(index); } } return list; } QList TransferView::selectedIndexes() const { return selectionModel()->selectedIndexes(); } QList TransferView::selectedRows(int column) const { return selectionModel()->selectedRows(column); } QModelIndex TransferView::index(int row, int column) const { return model()->index(row, column); } QModelIndex TransferView::index(Transfer *transfer) const { if (!transfer) { return QModelIndex(); } foreach (const QModelIndex &rowIndex, rowIndexes()) { - Transfer *rowTransfer = static_cast(rowIndex.data(TransferListModel::TransferPointer).value()); + Transfer *rowTransfer = qobject_cast(rowIndex.data(TransferListModel::TransferPointer).value()); if (rowTransfer == transfer) { return rowIndex; } } return QModelIndex(); } void TransferView::headerCustomContextMenuRequested(const QPoint &pos) { QMenu menu(this); menu.addSection(i18n("Columns")); for (int i = 0; i < m_dccModel->columnCount(); ++i) { QAction *tAction = new QAction(m_dccModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(), &menu); tAction->setCheckable(true); int headerType = m_dccModel->headerData(i, Qt::Horizontal, TransferListModel::HeaderType).toInt(); //there must be at least one column that is not hideable if (headerType == TransferHeaderData::FileName) { delete tAction; continue; } switch (headerType) { // case DccHeaderData::FileName: // connect (tAction, SIGNAL(toggled(bool)), this, SLOT(toggleFilenameColumn(bool))); // break; case TransferHeaderData::PartnerNick: connect(tAction, &QAction::toggled, this, &TransferView::togglePartnerNickColumn); break; case TransferHeaderData::Progress: connect(tAction, &QAction::toggled, this, &TransferView::toggleProgressColumn); break; case TransferHeaderData::OfferDate: connect(tAction, &QAction::toggled, this, &TransferView::toggleStartedAtColumn); break; case TransferHeaderData::Position: connect(tAction, &QAction::toggled, this, &TransferView::togglePositionColumn); break; case TransferHeaderData::CurrentSpeed: connect(tAction, &QAction::toggled, this, &TransferView::toggleCurrentSpeedColumn); break; case TransferHeaderData::SenderAdress: connect(tAction, &QAction::toggled, this, &TransferView::toggleSenderAdressColumn); break; case TransferHeaderData::Status: connect(tAction, &QAction::toggled, this, &TransferView::toggleStatusColumn); break; case TransferHeaderData::TimeLeft: connect(tAction, &QAction::toggled, this, &TransferView::toggleTimeLeftColumn); break; case TransferHeaderData::TypeIcon: connect(tAction, &QAction::toggled, this, &TransferView::toogleTypeIconColumn); break; } tAction->setChecked(!isColumnHidden(i)); menu.addAction(tAction); } menu.exec(QWidget::mapToGlobal(pos)); } void TransferView::toggleFilenameColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::FileName), !visible); } void TransferView::togglePartnerNickColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::PartnerNick), !visible); } void TransferView::toggleProgressColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::Progress), !visible); } void TransferView::toggleStartedAtColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::OfferDate), !visible); } void TransferView::toggleCurrentSpeedColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::CurrentSpeed), !visible); } void TransferView::togglePositionColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::Position), !visible); } void TransferView::toggleSenderAdressColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::SenderAdress), !visible); } void TransferView::toggleStatusColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::Status), !visible); } void TransferView::toggleTimeLeftColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::TimeLeft), !visible); } void TransferView::toogleTypeIconColumn(bool visible) { setColumnHidden(headerTypeToColumn(TransferHeaderData::TypeIcon), !visible); } int TransferView::headerTypeToColumn(int headerType) const { for (int i = 0; i < m_dccModel->columnCount(); ++i) { if (m_dccModel->headerData(i, Qt::Horizontal, TransferListModel::HeaderType).toInt() == headerType) { return i; } } qDebug() << "unknown headerType: " << headerType; return -1; } void TransferView::setProgressBarDeletegate() { for (int i = 0; i < m_dccModel->columnCount(); ++i) { int headerType = m_dccModel->headerData(i, Qt::Horizontal, TransferListModel::HeaderType).toInt(); if (headerType == TransferHeaderData::Progress) { setItemDelegateForColumn (i, new TransferProgressBarDelegate(this)); return; } } } void TransferView::saveColumns() { QList columnWidths; QList columnOrder; QList columnVisible; for (int i = 0; i < header()->count(); ++i) { int index = header()->logicalIndex(i); columnWidths.append(columnWidth(index)); columnOrder.append(m_dccModel->headerData(index, Qt::Horizontal, TransferListModel::HeaderType).toInt()); columnVisible.append(!isColumnHidden(index)); } Preferences::self()->setDccColumnWidths(columnWidths); Preferences::self()->setDccColumnOrders(columnOrder); Preferences::self()->setDccColumnVisibles(columnVisible); Preferences::self()->setDccColumnSorted(m_proxyModel->sortColumn()); Preferences::self()->setDccColumnSortDescending(m_proxyModel->sortOrder() == Qt::DescendingOrder ? true : false); } void TransferView::restoreColumns() { QList columnWidths = Preferences::self()->dccColumnWidths(); QList columnOrder = Preferences::self()->dccColumnOrders(); QList columnVisible = Preferences::self()->dccColumnVisibles(); //fallback, columnOrder is empty for me after crash //rather restore default than show an empty TransferView if (columnOrder.count() == TransferHeaderData::COUNT && columnWidths.count() == TransferHeaderData::COUNT && columnVisible.count() == TransferHeaderData::COUNT) { for (int i = 0; i < columnOrder.count(); ++i) { TransferHeaderData data; data.type = columnOrder.at(i); m_dccModel->appendHeader(data); } //update model, otherwise new column are unknown updateModel(); for (int i = 0; i < columnOrder.count(); ++i) { int column = headerTypeToColumn(columnOrder.at(i)); setColumnWidth(column, columnWidths.at(i) == 0 ? 100 : columnWidths.at(i)); setColumnHidden(column, (columnVisible.at(i) > 0) ? false : true); } Qt::SortOrder order; if (Preferences::self()->dccColumnSortDescending()) { order = Qt::DescendingOrder; } else { order = Qt::AscendingOrder; } sortByColumn(Preferences::self()->dccColumnSorted(), order); } else { qDebug() << "transferview fallback, did we crash last time?\n" << " columnOrder.count():"<< columnOrder.count() << " columnWidths.count():"<< columnWidths.count() << " columnVisible.count():"<< columnVisible.count() << ", expected: " << TransferHeaderData::COUNT; for (int i = 0; i < TransferHeaderData::COUNT; ++i) { TransferHeaderData data; data.type = i; m_dccModel->appendHeader(data); } updateModel(); } } void TransferView::updateModel() { m_proxyModel->setSourceModel(m_dccModel); m_proxyModel->invalidate(); } void TransferView::scrollContentsBy(int dx, int dy) { if (dx) //KCategoryDrawer is a bit slow when it comes to horiz redraws, force it { update(); } QTreeView::scrollContentsBy(dx, dy); } void TransferView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { emit runSelectedTransfers(); } QTreeView::keyPressEvent(event); } void TransferView::selectAllCompleted() { QItemSelection selection; foreach (const QModelIndex &index, rowIndexes()) { if (index.data(TransferListModel::TransferStatus).toInt() >= Transfer::Done) { selection.append(QItemSelectionRange(index)); } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void TransferView::selectRow(int row) { if (row >= rowCount()) { return; } selectionModel()->select(m_proxyModel->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void TransferView::selectRows(QList rows) { QItemSelection selection; foreach (const QModelIndex &index, rowIndexes()) { foreach (int row, rows) { if (row == index.row()) { selection.append(QItemSelectionRange(index)); break; } } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void TransferView::update() { const int columnCount = model()->columnCount()-1; foreach (const QModelIndex &rowIndex, rowIndexes(0)) { int status = rowIndex.data(TransferListModel::TransferStatus).toInt(); if (status == Transfer::Transferring) { dataChanged(rowIndex, index(rowIndex.row(), columnCount)); } } } void TransferView::rowsAboutToBeRemovedFromModel(const QModelIndex &/*parent*/, int start, int end) { // The items that will be removed are those between start and end inclusive for (int i = start; i < end+1; ++i) { m_itemCategoryToRemove |= model()->index(i, 0).data(TransferListModel::TransferType).toInt(); } } void TransferView::rowsRemovedFromModel(int start, int end) { if (m_itemCategoryToRemove & Transfer::Send) { if (m_dccModel->itemCount(TransferItemData::SendItem) == (start - end)) { m_itemCategoryToRemove &= ~Transfer::Send; m_categorieFlags &= ~TransferView::SendCategory; int removed = removeItems(TransferItemData::SendCategory); //qDebug() << "Sendremoved:" << removed; if (removed > 0 && (m_categorieFlags & SpacerRow)) { removeItems(TransferItemData::SpaceRow); m_categorieFlags &= ~TransferView::SpacerRow; } } } if (m_itemCategoryToRemove & Transfer::Receive) { if (m_dccModel->itemCount(TransferItemData::ReceiveItem) == (start - end)) { m_itemCategoryToRemove &= ~Transfer::Receive; m_categorieFlags &= ~TransferView::ReceiveCategory; int removed = removeItems(TransferItemData::ReceiveCategory); //qDebug() << "Receiveremoved:" << removed; if (removed > 0 && (m_categorieFlags & SpacerRow)) { removeItems(TransferItemData::SpaceRow); m_categorieFlags &= ~TransferView::SpacerRow; } } } } int TransferView::removeItems(TransferItemData::ItemDisplayType displaytype) { int removed = 0; for (int i = model()->rowCount()-1; i >= 0; --i) { QModelIndex index = m_proxyModel->index(i, 0); if (index.data(TransferListModel::TransferDisplayType).toInt() == displaytype) { model()->removeRow(index.row()); ++removed; } } return removed; } } } diff --git a/src/irc/channellistpanel.cpp b/src/irc/channellistpanel.cpp index 8fbf32c4..754b818e 100644 --- a/src/irc/channellistpanel.cpp +++ b/src/irc/channellistpanel.cpp @@ -1,541 +1,541 @@ /* 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 = static_cast(sender()); + const QAction* action = qobject_cast(sender()); if (action) { Application* konvApp = Application::instance(); konvApp->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/nick.cpp b/src/irc/nick.cpp index 44277919..a3ab707c 100644 --- a/src/irc/nick.cpp +++ b/src/irc/nick.cpp @@ -1,263 +1,263 @@ /* 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. */ /* begin: Fri Jan 25 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ #include "nick.h" #include "application.h" #include "images.h" #include "preferences.h" #include "nicklistview.h" #include Nick::Nick(NickListView *listView, Channel* channel, const ChannelNickPtr& channelnick) : QTreeWidgetItem (listView) { m_channelnickptr = channelnick; m_channel = channel; Q_ASSERT(channelnick); Q_ASSERT(m_channel); m_flags = 0; refresh(); setFlags((flags() & ~Qt::ItemIsDragEnabled) | Qt::ItemIsDropEnabled); } Nick::~Nick() { } ChannelNickPtr Nick::getChannelNick() const { Q_ASSERT(m_channelnickptr); return m_channelnickptr; } void Nick::refresh() { int flags = 0; NickInfoPtr nickInfo(getChannelNick()->getNickInfo()); bool away = false; int textChangedFlags = 0; { // NOTE: this scoping is for NoSorting below. Do not remove it // Disable auto-sorting while updating data NickListView::NoSorting noSorting(qobject_cast(treeWidget())); if ( nickInfo ) away = nickInfo->isAway(); if (away) { // Brush of the first column will be used for all columns setForeground(NicknameColumn, qApp->palette(treeWidget()).brush(QPalette::Disabled, QPalette::Text)); flags = 1; } else { // Brush of the first column will be used for all columns setForeground(NicknameColumn, treeWidget()->palette().brush(QPalette::Normal, QPalette::Text)); } Images* images = Application::instance()->images(); QPixmap icon; if ( getChannelNick()->isOwner() ) { flags += 64; icon = images->getNickIcon( Images::Owner, away ); } else if ( getChannelNick()->isAdmin() ) { flags += 128; icon = images->getNickIcon( Images::Admin, away ); } else if ( getChannelNick()->isOp() ) { flags += 32; icon = images->getNickIcon( Images::Op, away ); } else if ( getChannelNick()->isHalfOp() ) { flags += 16; icon = images->getNickIcon( Images::HalfOp, away ); } else if ( getChannelNick()->hasVoice() ) { flags += 8; icon = images->getNickIcon( Images::Voice, away ); } else { flags += 4; icon = images->getNickIcon( Images::Normal, away ); } setIcon( NicknameColumn, icon ); QString newtext = calculateLabel1(); if(newtext != text(NicknameColumn)) { setText(NicknameColumn, newtext); textChangedFlags |= 1 << NicknameColumn; } newtext = calculateLabel2(); if(newtext != text(HostmaskColumn)) { setText(HostmaskColumn, newtext); textChangedFlags |= 1 << HostmaskColumn; } } if(m_flags != flags || textChangedFlags) { m_flags = flags; // Announce about nick update (and reposition the nick in the nick list as needed). emitDataChanged(); m_channel->nicknameListViewTextChanged(textChangedFlags); treeWidget()->repaint(); } } // Triggers reposition of this nick (QTreeWidgetItem) in the nick list void Nick::repositionMe() { if (treeWidget()->isSortingEnabled()) emitDataChanged(); } QString Nick::calculateLabel1() const { NickInfoPtr nickinfo = getChannelNick()->getNickInfo(); QString retString = nickinfo->getNickname(); if(Preferences::self()->showRealNames() && !nickinfo->getRealName().isEmpty()) { retString += QStringLiteral(" (") + Konversation::removeIrcMarkup(nickinfo->getRealName()) + QLatin1Char(')'); } return retString; } QString Nick::calculateLabel2() const { return getChannelNick()->getNickInfo()->getHostmask(); } bool Nick::operator<(const QTreeWidgetItem& other) const { - const Nick& otherNick = static_cast(other); + const Nick& otherNick = dynamic_cast(other); if(Preferences::self()->sortByActivity()) { uint thisRecentActivity = getChannelNick()->recentActivity(); uint otherRecentActivity = otherNick.getChannelNick()->recentActivity(); if(thisRecentActivity > otherRecentActivity) { return true; } if(thisRecentActivity < otherRecentActivity) { return false; } uint thisTimestamp = getChannelNick()->timeStamp(); uint otherTimestamp = otherNick.getChannelNick()->timeStamp(); if(thisTimestamp > otherTimestamp) { return true; } if(thisTimestamp < otherTimestamp) { return false; } } if(Preferences::self()->sortByStatus()) { int thisFlags = getSortingValue(); int otherFlags = otherNick.getSortingValue(); if(thisFlags > otherFlags) { return false; } if(thisFlags < otherFlags) { return true; } } QString thisKey; QString otherKey; int col = treeWidget()->sortColumn(); if(col == NicknameColumn) { if(Preferences::self()->sortCaseInsensitive()) { thisKey = getChannelNick()->loweredNickname(); otherKey = otherNick.getChannelNick()->loweredNickname(); } else { thisKey = text(col); otherKey = otherNick.text(col); } } else if (col > 0) //the reason we need this: enabling hostnames adds another column { if(Preferences::self()->sortCaseInsensitive()) { thisKey = text(col).toLower(); otherKey = otherNick.text(col).toLower(); } else { thisKey = text(col); otherKey = otherNick.text(col); } } return thisKey < otherKey; } QVariant Nick::data(int column, int role) const { if (role == Qt::ForegroundRole && column > 0) { // Use brush of the first column for all columns return data(NicknameColumn, role); } return QTreeWidgetItem::data(column, role); } int Nick::getSortingValue() const { int flags; QString sortingOrder = Preferences::self()->sortOrder(); if(getChannelNick()->isOwner()) flags=sortingOrder.indexOf(QLatin1Char('q')); else if(getChannelNick()->isAdmin()) flags=sortingOrder.indexOf(QLatin1Char('p')); else if(getChannelNick()->isOp() ) flags=sortingOrder.indexOf(QLatin1Char('o')); else if(getChannelNick()->isHalfOp()) flags=sortingOrder.indexOf(QLatin1Char('h')); else if(getChannelNick()->hasVoice()) flags=sortingOrder.indexOf(QLatin1Char('v')); else flags=sortingOrder.indexOf(QLatin1Char('-')); return flags; } diff --git a/src/irc/nicklistview.cpp b/src/irc/nicklistview.cpp index a51d8f09..4abd060f 100644 --- a/src/irc/nicklistview.cpp +++ b/src/irc/nicklistview.cpp @@ -1,321 +1,321 @@ /* 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. */ /* This is the class that shows the channel nick list begin: Fre Jun 7 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ #include "nicklistview.h" #include "nick.h" #include "application.h" #include "images.h" #include "irccontextmenus.h" #include #include #include #include #include #include class NickItemDelegate : public QStyledItemDelegate { public: NickItemDelegate(QObject *parent = nullptr); QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; }; NickItemDelegate::NickItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize NickItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const QSize& size = QStyledItemDelegate::sizeHint(option, index); return QSize(size.width(), qMax(NickListView::getMinimumRowHeight(), size.height())); } int NickListView::s_minimumRowHeight = 0; NickListView::NickListView(QWidget* parent, Channel *chan) : QTreeWidget(parent) { setWhatsThis(); channel=chan; // Enable Drag & Drop viewport()->setAcceptDrops(true); setDropIndicatorShown(false); setDragDropMode(QAbstractItemView::DropOnly); // Make the minimum height of nicklist items the height of their // icons plus two pixels. setUniformRowHeights(true); QAbstractItemDelegate *prevDelegate = itemDelegate(); setItemDelegate(new NickItemDelegate(this)); delete prevDelegate; updateMinimumRowHeight(); // General layout setRootIsDecorated(false); // single level view setColumnCount(2); setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::ExtendedSelection); setAllColumnsShowFocus(true); // These two below must be called after setColumnCount(). header()->setSortIndicator(Nick::NicknameColumn, Qt::AscendingOrder); setSortingEnabled(true); header()->hide(); header()->setStretchLastSection(false); } NickListView::~NickListView() { } int NickListView::getMinimumRowHeight() { return s_minimumRowHeight; } void NickListView::updateMinimumRowHeight() { Images* images = Application::instance()->images(); s_minimumRowHeight = images->getNickIcon(Images::Normal, false).height() + 2; } bool NickListView::event(QEvent *event) { if(( event->type() == QEvent::ToolTip ) ) { - QHelpEvent* helpEvent = static_cast( event ); + QHelpEvent* helpEvent = dynamic_cast( event ); QTreeWidgetItem *item = itemAt(viewport()->mapFromParent(helpEvent->pos())); if( item ) { Nick *nick = dynamic_cast( item ); if( nick ) { QString text = Konversation::removeIrcMarkup(nick->getChannelNick()->tooltip()); if( !text.isEmpty() ) QToolTip::showText( helpEvent->globalPos(), text, this ); else QToolTip::hideText(); } } else QToolTip::hideText(); } return QTreeWidget::event( event ); } // Make this public void NickListView::executeDelayedItemsLayout() { QTreeWidget::executeDelayedItemsLayout(); } void NickListView::setWhatsThis() { Images* images = Application::instance()->images(); if(!images->getNickIcon( Images::Normal, false).isNull()) { QTreeWidget::setWhatsThis(i18n("

This shows all the people in the channel. The nick for each person is shown, with a picture showing their status.

" "" "" "" "" "" "" "" "" "
This person has administrator privileges.
This person is a channel owner.
This person is a channel operator.
This person is a channel half-operator.
This person has voice, and can therefore talk in a moderated channel.
This person does not have any special privileges.
This, overlaid on any of the above, indicates that this person is currently away.

" "The meaning of admin, owner and halfop varies between different IRC servers.

" "Hovering over any nick shows their current status. See the Konversation Handbook for more information." "

", images->getNickIconPath(Images::Admin), images->getNickIconPath(Images::Owner), images->getNickIconPath(Images::Op), images->getNickIconPath(Images::HalfOp), images->getNickIconPath(Images::Voice), images->getNickIconPath(Images::Normal), images->getNickIconAwayPath())); } } void NickListView::refresh() { updateMinimumRowHeight(); QTreeWidgetItemIterator it(this); while (*it) { - static_cast(*it)->refresh(); + dynamic_cast(*it)->refresh(); ++it; } setWhatsThis(); } void NickListView::setSortingEnabled(bool enable) { QTreeWidget::setSortingEnabled(enable); // We want to decouple header and this object with regard to sorting // (for performance reasons). By default there is no way to reenable // sorting without full resort (which is necessary for us) since both // header indicator change and setSortingEnabled(false/true) trigger // full resort of QTreeView. However, after disconnect, it is possible // to use header()->setSortIndicator() for this. // This could probably be done in a better way if nick list was model // based. NOTE: QTreeView::sortByColumn() won't work after this change // if sorting is enabled. if (enable && header()) { disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, nullptr); } } void NickListView::fastSetSortingEnabled(bool value) { if (value) { int sortCol = header()->sortIndicatorSection(); if (sortCol > -1) { header()->setSortIndicator(-1, header()->sortIndicatorOrder()); // since indicator section is -1, this basically sets the flag only // while setSortIndicator() is decoupled from triggerring resort. setSortingEnabled(true); // NOTE:: ResizeToContents mode on sortCol would be performance killer header()->setSortIndicator(sortCol, header()->sortIndicatorOrder()); } } else { setSortingEnabled(false); } } void NickListView::sortByColumn(int column, Qt::SortOrder order) { if (isSortingEnabled()) { // original implementation relies on sortIndicatorChanged signal model()->sort(column, order); } else QTreeWidget::sortByColumn(column, order); } void NickListView::resort() { sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); } int NickListView::findLowerBound(const QTreeWidgetItem& item) const { int start = 0, end = topLevelItemCount(); int mid; while (start < end) { mid = start + (end-start)/2; if ((*topLevelItem(mid)) < item) start = mid + 1; else end = mid; } return start; } void NickListView::contextMenuEvent(QContextMenuEvent* ev) { if (selectedItems().count()) { IrcContextMenus::nickMenu(ev->globalPos(), IrcContextMenus::ShowChannelActions, channel->getServer(), channel->getSelectedNickList(), channel->getName()); } } QStringList NickListView::mimeTypes () const { return KUrlMimeData::mimeDataTypes(); } bool NickListView::canDecodeMime(QDropEvent const *event) const { // Verify if the URL is not irc:// if (event->mimeData()->hasUrls()) { const QList uris = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (!uris.isEmpty()) { const QUrl first = uris.first(); if (first.scheme() == QLatin1String("irc") || first.scheme() == QLatin1String("ircs") || channel->getNickList().containsNick(first.url())) { return false; } } return true; } return false; } void NickListView::dragEnterEvent(QDragEnterEvent *event) { if (canDecodeMime(event)) { QTreeWidget::dragEnterEvent(event); return; } else { event->ignore(); } } void NickListView::dragMoveEvent(QDragMoveEvent *event) { QTreeWidget::dragMoveEvent(event); if (!indexAt(event->pos()).isValid()) { event->ignore(); return; } } bool NickListView::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action) { Q_UNUSED(index); Q_UNUSED(action); Nick* nick = dynamic_cast(parent); if (nick) { const QList uris = KUrlMimeData::urlsFromMimeData(data); channel->getServer()->sendURIs(uris, nick->getChannelNick()->getNickname()); return true; } return false; } diff --git a/src/irc/nicksonline.cpp b/src/irc/nicksonline.cpp index e0f67ce3..3132ee1f 100644 --- a/src/irc/nicksonline.cpp +++ b/src/irc/nicksonline.cpp @@ -1,868 +1,868 @@ // -*- 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. */ /* shows a user tree of friends per server begin: Sam Aug 31 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ #include "nicksonline.h" #include #include "channel.h" #include "server.h" #include "application.h" #include "connectionmanager.h" #include "editnotifydialog.h" #include "images.h" #include "query.h" #include "mainwindow.h" #include "viewcontainer.h" #include "nicksonlineitem.h" #include #include #include #include #include NicksOnline::NicksOnline(QWidget* parent): ChatWindow(parent) { setName(i18n("Watched Nicks Online")); setType(ChatWindow::NicksOnline); setSpacing(0); m_toolBar = new KToolBar(this, true, true); m_addNickname = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("&Add Nickname...")); m_addNickname->setWhatsThis(i18n("Click to add a new nick to the list of nicknames that appear on this screen.")); m_removeNickname = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("&Remove Nickname")); m_removeNickname->setWhatsThis(i18n("Click to remove a nick from the list of nicknames that appear on this screen.")); m_toolBar->addSeparator(); m_whois = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("office-address-book")), i18n("&Whois")); m_openQuery = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("office-address-book")), i18n("Open &Query")); m_toolBar->addSeparator(); m_joinChannel = m_toolBar->addAction(QIcon::fromTheme(QStringLiteral("irc-join-channel")), i18n("&Join Channel")); connect(m_toolBar, &KToolBar::actionTriggered, this, &NicksOnline::slotPopupMenu_Activated); m_nickListView=new QTreeWidget(this); // Set to false every 8 seconds to permit a whois on watched nicks lacking information. // Remove when server does this automatically. m_whoisRequested = true; m_onlineIcon = QIcon::fromTheme(QStringLiteral("im-user")); m_offlineIcon = QIcon::fromTheme(QStringLiteral("im-user-offline")); m_nickListView->setColumnCount(2); m_nickListView->headerItem()->setText(0, i18n("Network/Nickname/Channel")); m_nickListView->headerItem()->setText(1, i18n("Additional Information")); m_nickListView->setRootIsDecorated(true); m_nickListView->setSortingEnabled(true); Preferences::restoreColumnState(m_nickListView, QStringLiteral("NicksOnline ViewSettings")); QString nickListViewWT = i18n( "

These are all the nicknames on your Nickname Watch list, listed under the " "server network they are connected to.

" "

The Additional Information column shows the information known " "for each nickname.

" "

The channels the nickname has joined are listed underneath each nickname.

" "

Nicknames appearing under Offline are not connected to any of the " "servers in the network.

" "

Right-click with the mouse on a nickname to perform additional functions.

"); m_nickListView->setWhatsThis(nickListViewWT); m_nickListView->viewport()->installEventFilter(this); connect(m_nickListView, &QTreeWidget::itemDoubleClicked, this, &NicksOnline::processDoubleClick); setupToolbarActions(nullptr); // Create context menu. m_popupMenu = new QMenu(this); m_popupMenu->setObjectName(QStringLiteral("nicksonline_context_menu")); m_nickListView->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_nickListView, &QTreeWidget::customContextMenuRequested, this, &NicksOnline::slotCustomContextMenuRequested); connect(m_nickListView, &QTreeWidget::itemSelectionChanged, this, &NicksOnline::slotNickListView_SelectionChanged); // Display info for all currently-connected servers. refreshAllServerOnlineLists(); // Connect and start refresh timer. m_timer = new QTimer(this); m_timer->setObjectName(QStringLiteral("nicksOnlineTimer")); connect(m_timer, &QTimer::timeout, this, &NicksOnline::timerFired); // TODO: User preference for refresh interval. m_timer->start(8000); } NicksOnline::~NicksOnline() { Preferences::saveColumnState(m_nickListView, QStringLiteral("NicksOnline ViewSettings")); m_timer->stop(); delete m_timer; delete m_nickListView; } bool NicksOnline::eventFilter(QObject*obj, QEvent* event ) { if( ( obj == m_nickListView->viewport() ) && ( event->type() == QEvent::ToolTip ) ) { - QHelpEvent* helpEvent = static_cast( event ); + QHelpEvent* helpEvent = dynamic_cast( event ); QTreeWidgetItem *item = m_nickListView->itemAt( helpEvent->pos() ); if( item ) { NickInfoPtr nickInfo = getNickInfo(item); if ( nickInfo ) { QString text = nickInfo->tooltip(); if( !text.isEmpty() ) QToolTip::showText( helpEvent->globalPos(), text ); else QToolTip::hideText(); } else QToolTip::hideText(); } else QToolTip::hideText(); } return ChatWindow::eventFilter( obj, event ); } QTreeWidget* NicksOnline::getNickListView() { return m_nickListView; } /** * Returns the named child of parent item in a NicksOnlineItem * @param parent Pointer to a NicksOnlineItem. * @param name The name in the desired child QListViewItem, must be in column 0. * @param type The type of entry to be found * @return Pointer to the child QListViewItem or 0 if not found. */ QTreeWidgetItem* NicksOnline::findItemChild(const QTreeWidgetItem* parent, const QString& name, NicksOnlineItem::NickListViewColumn type) { if (!parent) return nullptr; for (int i = 0; i < parent->childCount(); ++i) { QTreeWidgetItem* child = parent->child(i); - if(static_cast(child)->type() == type && child->text(0) == name) return child; + if(dynamic_cast(child)->type() == type && child->text(0) == name) return child; } return nullptr; } /** * Returns the first occurrence of a child item of a given type in a NicksOnlineItem * @param parent Pointer to a NicksOnlineItem. * @param type The type of entry to be found * @return Pointer to the child QListViewItem or 0 if not found. */ QTreeWidgetItem* NicksOnline::findItemType(const QTreeWidgetItem* parent, NicksOnlineItem::NickListViewColumn type) { if (!parent) return nullptr; for (int i = 0; i < parent->childCount(); ++i) { QTreeWidgetItem* child = parent->child(i); - if(static_cast(child)->type() == type) return child; + if(dynamic_cast(child)->type() == type) return child; } return nullptr; } /** * Returns a pointer to the network QListViewItem with the given name. * @param name The name of the network. * @return Pointer to the QListViewItem or 0 if not found. */ QTreeWidgetItem* NicksOnline::findNetworkRoot(int serverGroupId) { for (int i = 0; i < m_nickListView->invisibleRootItem()->childCount(); ++i) { QTreeWidgetItem* child = m_nickListView->invisibleRootItem()->child(i); if (child->data(0, Qt::UserRole).toInt() == serverGroupId) return child; } return nullptr; } /** * Return a string containing formatted additional information about a nick. * @param nickInfo A pointer to NickInfo structure for the nick. May be Null. * @return A string formatted for display containing the information * about the nick. * @return needWhois True if a WHOIS needs to be performed on the nick * to get additional information. */ QString NicksOnline::getNickAdditionalInfo(NickInfoPtr nickInfo, bool& needWhois) { Q_UNUSED(needWhois) QString niInfo; if (nickInfo) { if (nickInfo->isAway()) { niInfo += i18n("Away"); if (!nickInfo->getAwayMessage().isEmpty()) niInfo += QStringLiteral(" (") + nickInfo->getAwayMessage() + QLatin1Char(')'); } if (!nickInfo->getHostmask().isEmpty()) niInfo += QLatin1Char(' ') + nickInfo->getHostmask(); if (!nickInfo->getRealName().isEmpty()) niInfo += QStringLiteral(" (") + nickInfo->getRealName() + QLatin1Char(')'); if (!nickInfo->getNetServer().isEmpty()) { niInfo += i18n( " online via %1", nickInfo->getNetServer() ); if (!nickInfo->getNetServerInfo().isEmpty()) niInfo += QStringLiteral(" (") + nickInfo->getNetServerInfo() + QLatin1Char(')'); } if (!nickInfo->getOnlineSince().isNull()) niInfo += i18n( " since %1", nickInfo->getPrettyOnlineSince() ); } return niInfo; } /** * Refresh the nicklistview for a single server. * @param server The server to be refreshed. */ void NicksOnline::updateServerOnlineList(Server* servr) { // Return if connection is an ephemeral one, because // we cant watch them anyway. if (!servr->getServerGroup()) return; bool newNetworkRoot = false; QString serverName = servr->getServerName(); QString networkName = servr->getDisplayName(); QTreeWidgetItem* networkRoot = findNetworkRoot(servr->getServerGroup()->id()); // If network is not in our list, add it. if (!networkRoot) { networkRoot = new NicksOnlineItem(NicksOnlineItem::NetworkRootItem,m_nickListView,networkName); networkRoot->setData(0, Qt::UserRole, servr->getServerGroup()->id()); newNetworkRoot = true; } // Store server name in hidden column. // Note that there could be more than one server in the network connected, // but it doesn't matter because all the servers in a network have the same // watch list. networkRoot->setText(nlvcServerName, serverName); // Update list of servers in the network that are connected. QStringList serverList = networkRoot->text(nlvcAdditionalInfo).split(QLatin1Char(','), QString::SkipEmptyParts); if (!serverList.contains(serverName)) serverList.append(serverName); networkRoot->setText(nlvcAdditionalInfo, serverList.join(QStringLiteral(","))); // Get watch list. QStringList watchList = servr->getWatchList(); QStringList::iterator itEnd = watchList.end(); QString nickname; for (QStringList::iterator it = watchList.begin(); it != itEnd; ++it) { nickname = (*it); NickInfoPtr nickInfo = getOnlineNickInfo(networkName, nickname); if (nickInfo && nickInfo->getPrintedOnline()) { // Nick is online. // Which server did NickInfo come from? Server* server=nickInfo->getServer(); // Construct additional information string for nick. bool needWhois = false; QString nickAdditionalInfo = getNickAdditionalInfo(nickInfo, needWhois); // Add to network if not already added. QTreeWidgetItem* nickRoot = findItemChild(networkRoot, nickname, NicksOnlineItem::NicknameItem); if (!nickRoot) nickRoot = new NicksOnlineItem(NicksOnlineItem::NicknameItem, networkRoot, nickname, nickAdditionalInfo); - NicksOnlineItem* nickitem = static_cast(nickRoot); + NicksOnlineItem* nickitem = dynamic_cast(nickRoot); nickitem->setConnectionId(server->connectionId ()); // Mark nick as online nickitem->setOffline(false); // Update icon nickitem->setIcon(nlvcNick, m_onlineIcon); nickRoot->setText(nlvcAdditionalInfo, nickAdditionalInfo); nickRoot->setText(nlvcServerName, serverName); // If no additional info available, request a WHOIS on the nick. if (!m_whoisRequested) { if (needWhois) { requestWhois(networkName, nickname); m_whoisRequested = true; } } QStringList channelList = server->getNickChannels(nickname); QStringList::iterator itEnd2 = channelList.end(); for (QStringList::iterator it2 = channelList.begin(); it2 != itEnd2; ++it2) { // Known channels where nickname is online and mode in each channel. // FIXME: If user connects to multiple servers in same network, the // channel info will differ between the servers, resulting in inaccurate // mode and led info displayed. QString channelName = (*it2); ChannelNickPtr channelNick = server->getChannelNick(channelName, nickname); QString nickMode; if (channelNick->hasVoice()) nickMode = nickMode + i18n(" Voice"); if (channelNick->isHalfOp()) nickMode = nickMode + i18n(" HalfOp"); if (channelNick->isOp()) nickMode = nickMode + i18n(" Operator"); if (channelNick->isOwner()) nickMode = nickMode + i18n(" Owner"); if (channelNick->isAdmin()) nickMode = nickMode + i18n(" Admin"); QTreeWidgetItem* channelItem = findItemChild(nickRoot, channelName, NicksOnlineItem::ChannelItem); if (!channelItem) channelItem = new NicksOnlineItem(NicksOnlineItem::ChannelItem,nickRoot, channelName, nickMode); channelItem->setText(nlvcAdditionalInfo, nickMode); // Icon for mode of nick in each channel. Images::NickPrivilege nickPrivilege = Images::Normal; if (channelNick->hasVoice()) nickPrivilege = Images::Voice; if (channelNick->isHalfOp()) nickPrivilege = Images::HalfOp; if (channelNick->isOp()) nickPrivilege = Images::Op; if (channelNick->isOwner()) nickPrivilege = Images::Owner; if (channelNick->isAdmin()) nickPrivilege = Images::Admin; if (server->getJoinedChannelMembers(channelName) != nullptr) channelItem->setIcon(nlvcChannel, QIcon(Application::instance()->images()->getNickIcon(nickPrivilege, false))); else channelItem->setIcon(nlvcChannel, QIcon(Application::instance()->images()->getNickIcon(nickPrivilege, true))); } // Remove channel if nick no longer in it. for (int i = 0; i < nickRoot->childCount(); ++i) { QTreeWidgetItem* child = nickRoot->child(i); if (!channelList.contains(child->text(nlvcNick))) { delete nickRoot->takeChild(i); i--; } } } else { // Nick is offline. QTreeWidgetItem* nickRoot = findItemChild(networkRoot, nickname, NicksOnlineItem::NicknameItem); if (!nickRoot) nickRoot = new NicksOnlineItem(NicksOnlineItem::NicknameItem, networkRoot, nickname); // remove channels from the nick qDeleteAll(nickRoot->takeChildren()); - NicksOnlineItem* nickitem = static_cast(nickRoot); + NicksOnlineItem* nickitem = dynamic_cast(nickRoot); nickitem->setConnectionId(servr->connectionId ()); // Mark nick as offline nickitem->setOffline (true); // Update icon nickitem->setIcon(nlvcNick, m_offlineIcon); nickRoot->setText(nlvcServerName, serverName); nickRoot->setText(nlvcAdditionalInfo, QString()); } } // Erase nicks no longer being watched. for (int i = 0; i < networkRoot->childCount(); ++i) { QTreeWidgetItem* item = networkRoot->child(i); QString nickname = item->text(nlvcNick); if (!watchList.contains(nickname) && serverName == item->text(nlvcServerName)) { delete networkRoot->takeChild(i); i--; } } // Expand server if newly added to list. if (newNetworkRoot) { networkRoot->setExpanded(true); // Connect server NickInfo updates. connect (servr, SIGNAL(nickInfoChanged(Server*,NickInfoPtr)), this, SLOT(slotNickInfoChanged(Server*,NickInfoPtr))); } } /** * Determines if a nick is online in any of the servers in a network and returns * a NickInfo if found, otherwise 0. * @param networkName Server network name. * @param nickname Nick name. * @return NickInfo if nick is online in any server, otherwise 0. */ NickInfoPtr NicksOnline::getOnlineNickInfo(QString& networkName, QString& nickname) { // Get list of pointers to all servers. Application* konvApp = Application::instance(); const QList serverList = konvApp->getConnectionManager()->getServerList(); foreach (Server* server, serverList) { if (server->getDisplayName() == networkName) { NickInfoPtr nickInfo = server->getNickInfo(nickname); if (nickInfo) return nickInfo; } } return NickInfoPtr(); //TODO FIXME NULL NULL NULL } /** * Requests a WHOIS for a specified server network and nickname. * The request is sent to the first server found in the network. * @param groupName Server group name. * @param nickname Nick name. */ void NicksOnline::requestWhois(QString& networkName, QString& nickname) { Application* konvApp = Application::instance(); const QList serverList = konvApp->getConnectionManager()->getServerList(); foreach (Server* server, serverList) { if (server->getDisplayName() == networkName) { server->requestWhois(nickname); return; } } } /** * Updates the notify list based on the current state of the tree */ void NicksOnline::updateNotifyList() { // notify list QMap notifyList; // fill in the notify list for (int i = 0; i < m_nickListView->topLevelItemCount(); ++i) { QTreeWidgetItem* networkRoot = m_nickListView->topLevelItem(i); // nick list for this network root QStringList nicks; for (int j = 0; j < networkRoot->childCount(); ++j) { NicksOnlineItem *item = dynamic_cast(networkRoot->child(j)); if (item->type() == NicksOnlineItem::NicknameItem) { // add the nick to the list nicks << item->text(nlvcNick); } } // insert nick list to the notify list notifyList.insert(networkRoot->data(0, Qt::UserRole).toInt(), nicks); } // update notify list Preferences::setNotifyList(notifyList); // save notify list Application::instance()->saveOptions(false); } /** * Refresh the nicklistview for all servers. */ void NicksOnline::refreshAllServerOnlineLists() { Application* konvApp = Application::instance(); const QList serverList = konvApp->getConnectionManager()->getServerList(); // Remove servers no longer connected. for (int i = 0; i < m_nickListView->invisibleRootItem()->childCount(); ++i) { QTreeWidgetItem *child = m_nickListView->invisibleRootItem()->child(i); QString networkName = child->text(nlvcNetwork); QStringList serverNameList = child->text(nlvcAdditionalInfo).split(QLatin1Char(','), QString::SkipEmptyParts); QStringList::Iterator itEnd = serverNameList.end(); QStringList::Iterator it = serverNameList.begin(); while (it != itEnd) { QString serverName = *it; // Locate server in server list. bool found = false; foreach (Server* server, serverList) { if ((server->getServerName() == serverName) && (server->getDisplayName() == networkName)) found = true; } if (!found) it = serverNameList.erase(it); else ++it; } // Remove Networks with no servers connected, otherwise update list of connected // servers. if (serverNameList.empty()) { delete m_nickListView->invisibleRootItem()->takeChild(i); i--; } else child->setText(nlvcAdditionalInfo, serverNameList.join(QStringLiteral(","))); } // Display info for all currently-connected servers. foreach (Server* server, serverList) { updateServerOnlineList(server); } // Refresh buttons. slotNickListView_SelectionChanged(); } void NicksOnline::timerFired() { // Allow one WHOIS request per cycle. m_whoisRequested = false; refreshAllServerOnlineLists(); } /** * When a user double-clicks a nickname in the nicklistview, let server know so that * it can perform the user's chosen default action for that. */ void NicksOnline::processDoubleClick(QTreeWidgetItem* item, int column) { Q_UNUSED(column); NicksOnlineItem* nickitem = dynamic_cast(item); if (!nickitem || nickitem->isOffline()) return; // Only emit signal when the user double clicked a nickname rather than // a server name or channel name. if (nickitem->type() == NicksOnlineItem::NicknameItem) emit doubleClicked(nickitem->connectionId(), nickitem->text(nlvcNick)); if (nickitem->type() == NicksOnlineItem::ChannelItem) { NicksOnlineItem* nickRoot = dynamic_cast(nickitem->parent()); Server* server = Application::instance()->getConnectionManager()->getServerByConnectionId(nickRoot->connectionId()); ChatWindow* channel = server->getChannelByName(nickitem->text(nlvcChannel)); if (channel) emit showView(channel); else { // Get the server object corresponding to the connection id. server->queue( QStringLiteral("JOIN ")+ nickitem->text(nlvcChannel) ); } } } /** * Returns the server name and nickname of the specified nicklistview item. * @param item The nicklistview item. * @return serverName Name of the server for the nick at the item, or Null if not a nick. * @return nickname The nickname at the item. */ bool NicksOnline::getItemServerAndNick(const QTreeWidgetItem* item, QString& serverName, QString& nickname) { if (!item) return false; // convert into NicksOnlineItem - const NicksOnlineItem* nlItem=static_cast(item); + const NicksOnlineItem* nlItem=dynamic_cast(item); // If on a network, return false; if (nlItem->type() == NicksOnlineItem::NetworkRootItem) return false; // get server name serverName = item->text(nlvcServerName); // If on a channel, move up to the nickname. if (nlItem->type() == NicksOnlineItem::ChannelItem) { item = item->parent(); serverName = item->text(nlvcServerName); } nickname = item->text(nlvcNick); return true; } NickInfoPtr NicksOnline::getNickInfo(const QTreeWidgetItem* item) { QString serverName; QString nickname; getItemServerAndNick(item, serverName, nickname); if (serverName.isEmpty() || nickname.isEmpty()) return NickInfoPtr(); //TODO FIXME NULL NULL NULL Server* server = Application::instance()->getConnectionManager()->getServerByName(serverName); if (server) return server->getNickInfo(nickname); return NickInfoPtr(); //TODO FIXME NULL NULL NULL } /** * Given a server name and nickname, returns the item in the Nick List View displaying * the nick. * @param serverName Name of server. * @param nickname Nick name. * @return Pointer to QListViewItem displaying the nick, or 0 if not found. * * @see getItemServerAndNick */ QTreeWidgetItem* NicksOnline::getServerAndNickItem(const QString& serverName, const QString& nickname) { Server* server = Application::instance()->getConnectionManager()->getServerByName(serverName); if (!server) return nullptr; QString networkName = server->getDisplayName(); QList items = m_nickListView->findItems(networkName, Qt::MatchExactly | Qt::MatchCaseSensitive, nlvcNetwork); if (items.count() == 0) return nullptr; QTreeWidgetItem* networkRoot = items.at(0); if (!networkRoot) return nullptr; QTreeWidgetItem* nickRoot = findItemChild(networkRoot, nickname, NicksOnlineItem::NicknameItem); return nickRoot; } /** * Perform a command. * @param id The command id. @ref CommandIDs. * * The operation is performed on the nickname at the currently-selected item in * the nicklistview. * * Also refreshes the nicklistview display to reflect the new state * for the nick. */ void NicksOnline::doCommand(QAction* id) { if(id == nullptr) return; if ( id == m_addNickname ) { int serverGroupId = -1; if (m_nickListView->selectedItems().count()) { NicksOnlineItem *networkRoot = dynamic_cast(m_nickListView->selectedItems().at(0)); if (networkRoot) { while (networkRoot->type() != NicksOnlineItem::NetworkRootItem) networkRoot = dynamic_cast(networkRoot->parent()); serverGroupId = networkRoot->data(0, Qt::UserRole).toInt(); } } EditNotifyDialog *end = new EditNotifyDialog(this, serverGroupId); connect(end, &EditNotifyDialog::notifyChanged, this, &NicksOnline::slotAddNickname); end->show(); return; } QString serverName; QString nickname; if (m_nickListView->selectedItems().count() == 0) return; QTreeWidgetItem* item = m_nickListView->selectedItems().at(0); NicksOnlineItem* nickitem = dynamic_cast(item); if(!nickitem) return; if ( id == m_removeNickname ) { // remove watch from the tree widget delete nickitem; // update notify list updateNotifyList(); return; } if (!getItemServerAndNick(item, serverName, nickname)) return; // Get the server object corresponding to the connection id. Server* server = Application::instance()->getConnectionManager()->getServerByConnectionId(nickitem->connectionId()); if (!server) return; if ( id == m_joinChannel ) { if (m_nickListView->selectedItems().count() > 0) { // only join real channels - if (static_cast(m_nickListView->selectedItems().at(0))->type() == NicksOnlineItem::ChannelItem) + if (dynamic_cast(m_nickListView->selectedItems().at(0))->type() == NicksOnlineItem::ChannelItem) { QString contactChannel = m_nickListView->selectedItems().at(0)->text(nlvcChannel); server->queue( QStringLiteral("JOIN ")+contactChannel ); } } } else if ( id == m_whois ) { server->queue(QStringLiteral("WHOIS ")+nickname); } else if ( id == m_openQuery ) { NickInfoPtr nickInfo = server->obtainNickInfo(nickname); class Query* query = server->addQuery(nickInfo, true /*we initiated*/); emit showView(query); } else refreshItem(item); } /** * Sets up toolbar actions based on the given item. * @param item Item of the nicklistview. */ void NicksOnline::setupToolbarActions(NicksOnlineItem *item) { // disable all actions m_removeNickname->setEnabled(false); m_whois->setEnabled(false); m_openQuery->setEnabled(false); m_joinChannel->setEnabled(false); // check for null if (item == nullptr) return; // add items depending on the item type switch (item->type()) { case NicksOnlineItem::ChannelItem: m_joinChannel->setEnabled(true); break; case NicksOnlineItem::NicknameItem: m_removeNickname->setEnabled(true); if (!item->isOffline()) { m_whois->setEnabled(true); m_openQuery->setEnabled(true); } break; } } /** * Sets up popup menu actions based on the given item. * @param item Item of the nicklistview. */ void NicksOnline::setupPopupMenuActions(NicksOnlineItem *item) { // clear the popup menu m_popupMenu->clear(); // check for null if (item == nullptr) return; // add items depending on the item type switch (item->type()) { case NicksOnlineItem::NetworkRootItem: m_popupMenu->insertAction(nullptr, m_addNickname); break; case NicksOnlineItem::ChannelItem: m_popupMenu->insertAction(nullptr, m_joinChannel); break; case NicksOnlineItem::NicknameItem: m_popupMenu->insertAction(nullptr, m_removeNickname); if (!item->isOffline()) { m_popupMenu->addSeparator(); m_popupMenu->insertAction(nullptr, m_whois); m_popupMenu->insertAction(nullptr, m_openQuery); } break; } } /** * Received when user selects a different item in the nicklistview. */ void NicksOnline::slotNickListView_SelectionChanged() { if (m_nickListView->selectedItems().count() == 0) return; setupToolbarActions(dynamic_cast(m_nickListView->selectedItems().at(0))); } /** * Received when right-clicking an item in the NickListView. */ void NicksOnline::slotCustomContextMenuRequested(const QPoint& point) { QTreeWidgetItem *item = m_nickListView->itemAt(point); if (item == nullptr) return; // select the item item->setSelected(true); // set up actions setupPopupMenuActions(dynamic_cast(item)); // show the popup menu if (m_popupMenu->actions().count() > 0) m_popupMenu->popup(QCursor::pos()); } /** * Received from popup menu when user chooses something. */ void NicksOnline::slotPopupMenu_Activated(QAction* id) { doCommand(id); } /** * Received from server when a NickInfo changes its information. */ void NicksOnline::slotNickInfoChanged(Server* server, const NickInfoPtr nickInfo) { if (!nickInfo) return; QString nickname = nickInfo->getNickname(); if (!server) return; QString serverName = server->getServerName(); QTreeWidgetItem* item = getServerAndNickItem(serverName, nickname); refreshItem(item); } /** * Received when user added a new nick to the watched nicks. */ void NicksOnline::slotAddNickname(int serverGroupId, const QString& nickname) { Preferences::addNotify(serverGroupId, nickname); Application::instance()->saveOptions(true); } /** * Refreshes the information for the given item in the list. * @param item Pointer to listview item. */ void NicksOnline::refreshItem(QTreeWidgetItem* item) { if (!item) return; QString serverName; QString nickname; if (getItemServerAndNick(item, serverName, nickname)) { Server *server = Application::instance()->getConnectionManager()->getServerByName(serverName); if (server) { NickInfoPtr nickInfo = server->getNickInfo(nickname); QString nickAdditionalInfo; bool needWhois = false; if (nickInfo) nickAdditionalInfo = getNickAdditionalInfo(nickInfo, needWhois); item->setText(nlvcAdditionalInfo, nickAdditionalInfo); if (m_nickListView->selectedItems().count() != 0 && item == m_nickListView->selectedItems().at(0)) setupToolbarActions(dynamic_cast(m_nickListView->selectedItems().at(0))); } } } void NicksOnline::childAdjustFocus() {} // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/irc/server.cpp b/src/irc/server.cpp index f9cfceec..a0773bd7 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); // parse wildcards (toParse,nickname,channelName,nickList,parameter) out = parseWildcards(out, getNickname(), QString(), QString(), nick, QString()); // Send all strings, one after another QStringList outList = out.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (int index=0; indexparse(getNickname(),outList[index],QString()); queue(result.toServer); } // endfor } void Server::notifyResponse(const QString& nicksOnline) { bool nicksOnlineChanged = false; QStringList actualList = nicksOnline.split(QLatin1Char(' '), QString::SkipEmptyParts); QString lcActual = QLatin1Char(' ') + nicksOnline + QLatin1Char(' '); QString lcPrevISON = QLatin1Char(' ') + (m_prevISONList.join(QStringLiteral(" "))) + QLatin1Char(' '); QStringList::iterator it; //Are any nicks gone offline for (it = m_prevISONList.begin(); it != m_prevISONList.end(); ++it) { if (!lcActual.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setNickOffline(*it); nicksOnlineChanged = true; } } //Are any nicks gone online for (it = actualList.begin(); it != actualList.end(); ++it) { if (!lcPrevISON.contains(QLatin1Char(' ') + (*it) + QLatin1Char(' '), Qt::CaseInsensitive)) { setWatchedNickOnline(*it); nicksOnlineChanged = true; } } // Note: The list emitted in this signal *does* include nicks in joined channels. emit nicksNowOnline(this, actualList, nicksOnlineChanged); m_prevISONList = actualList; // Next round startNotifyTimer(); } void Server::notifyListStarted(int serverGroupId) { if (getServerGroup()) if (getServerGroup()->id() == serverGroupId) startNotifyTimer(1000); } void Server::startNotifyTimer(int msec) { // make sure the timer gets started properly in case we have reconnected m_notifyTimer.stop(); if (Preferences::self()->useNotify()) { if (msec == 0) msec = Preferences::self()->notifyDelay() * 1000; m_notifyTimer.start(msec); } } void Server::notifyTimeout() { // Notify delay time is over, send ISON request if desired if (Preferences::self()->useNotify()) { // But only if there actually are nicks in the notify list QString list = getISONListString(); if (!list.isEmpty()) queue(QStringLiteral("ISON ") + list, LowPriority); } } void Server::autoCommandsAndChannels() { if (getServerGroup() && !getServerGroup()->connectCommands().isEmpty()) { QString connectCommands = getServerGroup()->connectCommands(); if (!getNickname().isEmpty()) connectCommands.replace(QStringLiteral("%nick"), getNickname()); QStringList connectCommandsList = connectCommands.split(QLatin1Char(';'), QString::SkipEmptyParts); QStringList::iterator iter; for (iter = connectCommandsList.begin(); iter != connectCommandsList.end(); ++iter) { QString output(*iter); output = output.simplified(); getOutputFilter()->replaceAliases(output); Konversation::OutputFilterResult result = getOutputFilter()->parse(getNickname(),output,QString()); queue(result.toServer); } } if (getAutoJoin()) { for ( 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 = static_cast(context); + 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/upnp/upnprouter.cpp b/src/upnp/upnprouter.cpp index b10b653c..310225ff 100644 --- a/src/upnp/upnprouter.cpp +++ b/src/upnp/upnprouter.cpp @@ -1,412 +1,412 @@ /* 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) 2005-2007 Joris Guisson Copyright (C) 2009 Michael Kreitzer */ #include "upnprouter.h" #include "upnpdescriptionparser.h" #include "soap.h" #include #include #include #include #include #include #include namespace Konversation { namespace UPnP { UPnPService::UPnPService() { ready = false; } UPnPService::UPnPService(const UPnPService & s) { this->servicetype = s.servicetype; this->controlurl = s.controlurl; this->eventsuburl = s.eventsuburl; this->serviceid = s.serviceid; this->scpdurl = s.scpdurl; ready = false; } void UPnPService::setProperty(const QString & name,const QString & value) { if (name == "serviceType") servicetype = value; else if (name == "controlURL") controlurl = value; else if (name == "eventSubURL") eventsuburl = value; else if (name == "SCPDURL") scpdurl = value; else if (name == "serviceId") serviceid = value; } void UPnPService::clear() { servicetype = controlurl = eventsuburl = scpdurl = serviceid = QString(); } UPnPService & UPnPService::operator = (const UPnPService & s) { this->servicetype = s.servicetype; this->controlurl = s.controlurl; this->eventsuburl = s.eventsuburl; this->serviceid = s.serviceid; this->scpdurl = s.scpdurl; return *this; } /////////////////////////////////////// void UPnPDeviceDescription::setProperty(const QString & name,const QString & value) { if (name == "friendlyName") friendlyName = value; else if (name == "manufacturer") manufacturer = value; else if (name == "modelDescription") modelDescription = value; else if (name == "modelName") modelName = value; else if (name == "modelNumber") modelNumber = value; } /////////////////////////////////////// UPnPRouter::UPnPRouter(const QString & server,const QUrl &location,const QString & uuid) : server(server),location(location),uuid(uuid) { } UPnPRouter::~UPnPRouter() { QListIterator itr(forwards); while (itr.hasNext()) { Forwarding *check = itr.next(); undoForward(check->port, check->proto); } // We need to give time for the QNetworkManager to process the undo forward commands. Continue // Processing the event loop from here until there are no more forwards. while(forwards.size() > 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } } void UPnPRouter::addService(const UPnPService & s) { if (!( s.servicetype.contains("WANIPConnection") || s.servicetype.contains("WANPPPConnection") )) return; // Confirm this service is connected. Place in pending queue. KJob *req = getStatusInfo(s); if (req) pending_services[req] = s; } void UPnPRouter::downloadFinished(KJob* j) { if (j->error()) { error = i18n("Failed to download %1: %2",location.url(),j->errorString()); qDebug() << error << endl; return; } - KIO::StoredTransferJob* st = (KIO::StoredTransferJob*)j; + KIO::StoredTransferJob* st = qobject_cast(j); // load in the file (target is always local) UPnPDescriptionParser desc_parse; bool ret = desc_parse.parse(st->data(),this); if (!ret) { error = i18n("Error parsing router description."); } emit xmlFileDownloaded(this,ret); } void UPnPRouter::downloadXMLFile() { error.clear(); // downlaod XML description into a temporary file in /tmp qDebug() << "Downloading XML file " << location << endl; KIO::Job* job = KIO::storedGet(location,KIO::NoReload, KIO::Overwrite | KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, &UPnPRouter::downloadFinished); } KJob *UPnPRouter::getStatusInfo(UPnPService s) { qDebug() << "UPnP - Checking service status: " << s.servicetype << endl; QString action = "GetStatusInfo"; QString comm = SOAP::createCommand(action,s.servicetype); return sendSoapQuery(comm,s.servicetype + '#' + action,s.controlurl); } bool UPnPRouter::forward(const QHostAddress & host, quint16 port, QAbstractSocket::SocketType proto) { qDebug() << "Forwarding port " << host.toString() << port << " (" << (proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP") << ")" << endl; if (service.ready) { // add all the arguments for the command QList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = QString::number(port); args.append(a); // the protocol a.element = "NewProtocol"; a.value = proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP"; args.append(a); // the local port a.element = "NewInternalPort"; a.value = QString::number(port); args.append(a); // the local IP address a.element = "NewInternalClient"; a.value = host.toString(); args.append(a); a.element = "NewEnabled"; a.value = '1'; args.append(a); a.element = "NewPortMappingDescription"; a.value = QString("Konversation UPNP"); args.append(a); a.element = "NewLeaseDuration"; a.value = '0'; args.append(a); QString action = "AddPortMapping"; QString comm = SOAP::createCommand(action,service.servicetype,args); Forwarding *forward = new Forwarding; forward->port = port; forward->host = host; forward->proto = proto; if (KJob *req = sendSoapQuery(comm,service.servicetype + '#' + action,service.controlurl)) { // erase old forwarding if one exists // The UPnP spec states if an IGD receives a forward request that matches an existing request that it must accept it. QListIterator itr(forwards); while (itr.hasNext()) { Forwarding *check = itr.next(); if (check->port == forward->port && check->host == forward->host && check->proto == forward->proto) { forwards.removeAll(check); delete check; } } forwards.append(forward); pending_forwards[req] = forward; return true; } qDebug() << "Forwarding Failed: Failed to send SOAP query."; delete forward; } qDebug() << "Forwarding Failed: No UPnP Service."; return false; } bool UPnPRouter::undoForward(quint16 port, QAbstractSocket::SocketType proto) { qDebug() << "Undoing forward of port " << port << " (" << (proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP") << ")" << endl; if (service.ready) { Forwarding *forward = nullptr; QListIterator itr(forwards); while (itr.hasNext()) { Forwarding *check = itr.next(); if (check->port == port && check->proto == proto) forward = check; } if (forward == nullptr || pending_forwards.keys(forward).size() > 0) return false; // Either forward not found or forward is still pending // add all the arguments for the command QList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = QString::number(forward->port); args.append(a); // the protocol a.element = "NewProtocol"; a.value = forward->proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP"; args.append(a); QString action = "DeletePortMapping"; QString comm = SOAP::createCommand(action,service.servicetype,args); if (KJob *req = sendSoapQuery(comm,service.servicetype + '#' + action,service.controlurl)) { pending_unforwards[req] = forward; return true; } qDebug() << "Undo forwarding Failed: Failed to send SOAP query."; } qDebug() << "Undo forwarding Failed: No UPnP Service."; return false; } KJob *UPnPRouter::sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl) { // if port is not set, 0 will be returned // thanks to Diego R. Brogna for spotting this bug if (location.port()<=0) location.setPort(80); QUrl address; address.setScheme(QString("http")); address.setHost(location.host()); address.setPort(location.port()); address.setPath(controlurl); KIO::TransferJob *req = KIO::http_post( address, query.toLatin1(), KIO::HideProgressInfo ); req->addMetaData("content-type", QString("text/xml")); req->addMetaData("UserAgent", QString("Konversation UPnP")); req->addMetaData("customHTTPHeader", QString("SOAPAction: ") + soapact); soap_data_out[req] = QByteArray(); soap_data_in[req] = QByteArray(); connect(req, &KIO::TransferJob::data, this, &UPnPRouter::recvSoapData); connect(req, &KIO::TransferJob::dataReq, this, &UPnPRouter::sendSoapData); connect(req, &KIO::TransferJob::result, this, &UPnPRouter::onRequestFinished); return req; } void UPnPRouter::sendSoapData(KIO::Job *job, QByteArray &data) { data.append(soap_data_out[job]); soap_data_out[job].clear(); } void UPnPRouter::recvSoapData(KIO::Job *job, const QByteArray &data) { soap_data_in[job].append(data); } void UPnPRouter::onRequestFinished(KJob *r) { if (r->error()) { qDebug() << "UPnPRouter : Error: " << r->errorString() << endl; if (pending_services.contains(r)) { pending_services.remove(r); } else if (pending_forwards.contains(r)) { emit forwardComplete(true, pending_forwards[r]->port); forwards.removeAll(pending_forwards[r]); pending_forwards.remove(r); } else if (pending_unforwards.contains(r)) { emit unforwardComplete(true, pending_unforwards[r]->port); forwards.removeAll(pending_unforwards[r]); pending_unforwards.remove(r); } } else { QString reply(soap_data_in[r]); soap_data_in[r].clear(); qDebug() << "UPnPRouter : OK:" << endl; if (pending_services.contains(r)) { if (reply.contains("Connected")) { // Lets just deal with one connected service for now. Last one wins. service = pending_services[r]; service.ready = true; qDebug() << "Found connected service: " << service.servicetype << endl; } pending_services.remove(r); } else if (pending_forwards.contains(r)) { emit forwardComplete(false, pending_forwards[r]->port); pending_forwards.remove(r); } else if (pending_unforwards.contains(r)) { emit unforwardComplete(false, pending_unforwards[r]->port); forwards.removeAll(pending_unforwards[r]); pending_unforwards.remove(r); } } soap_data_in.remove(r); soap_data_out.remove(r); } } } diff --git a/src/viewer/channeloptionsdialog.cpp b/src/viewer/channeloptionsdialog.cpp index b31653a8..be5c0c9c 100644 --- a/src/viewer/channeloptionsdialog.cpp +++ b/src/viewer/channeloptionsdialog.cpp @@ -1,612 +1,612 @@ /* 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) 2005-2007 Peter Simonsson Copyright (C) 2006 Dario Abatianni Copyright (C) 2006-2007 Eike Hein */ #include "channeloptionsdialog.h" #include "application.h" #include "channel.h" #include "topichistorymodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Konversation { ChannelOptionsDialog::ChannelOptionsDialog(Channel *channel) : QDialog(channel) { setWindowTitle( i18n("Channel Settings for %1", channel->getName() ) ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ChannelOptionsDialog::changeOptions); connect(buttonBox, &QDialogButtonBox::rejected, this, &ChannelOptionsDialog::reject); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); Q_ASSERT(channel); m_channel = channel; m_ui.setupUi(mainWidget); m_ui.addBan->setIcon(QIcon::fromTheme("list-add")); m_ui.updateBan->setIcon(QIcon::fromTheme("edit-rename")); m_ui.removeBan->setIcon(QIcon::fromTheme("list-remove")); QStandardItemModel *modesModel = new QStandardItemModel(m_ui.otherModesList); m_ui.otherModesList->setModel(modesModel); m_ui.otherModesList->hide(); m_ui.banListSearchLine->setTreeWidget(m_ui.banList); m_ui.topicHistoryView->setServer(m_channel->getServer()); m_ui.topicHistoryView->setModel(m_channel->getTopicHistory()); - m_ui.topicHistorySearchLine->setProxy(static_cast(m_ui.topicHistoryView->model())); + m_ui.topicHistorySearchLine->setProxy(qobject_cast(m_ui.topicHistoryView->model())); m_ui.topicHistorySearchLine->lineEdit()->setPlaceholderText(QString()); m_editingTopic = false; m_ui.topicEdit->setChannel(channel); m_ui.topicEdit->setMaximumLength(m_channel->getServer()->topicLength()); connect(m_ui.topicHistoryView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(topicHistoryItemClicked(QItemSelection))); connect(m_ui.toggleAdvancedModes, &QPushButton::clicked, this, &ChannelOptionsDialog::toggleAdvancedModes); connect(m_ui.topicEdit, &TopicEdit::undoAvailable, this, &ChannelOptionsDialog::topicBeingEdited); connect(this, &ChannelOptionsDialog::finished, m_ui.topicEdit, &TopicEdit::clear); connect(m_channel, &Channel::modesChanged, this, &ChannelOptionsDialog::refreshModes); connect(m_channel->getServer(), SIGNAL(channelNickChanged(QString)), this, SLOT(refreshEnableModes())); connect(m_channel, &Channel::banAdded, this, &ChannelOptionsDialog::addBan); connect(m_channel, &Channel::banRemoved, this, &ChannelOptionsDialog::removeBan); connect(m_channel, &Channel::banListCleared, m_ui.banList, &QTreeWidget::clear); connect(m_ui.addBan, &QPushButton::clicked, this, &ChannelOptionsDialog::addBanClicked); connect(m_ui.updateBan, &QPushButton::clicked, this, &ChannelOptionsDialog::updateBanClicked); connect(m_ui.removeBan, &QPushButton::clicked, this, &ChannelOptionsDialog::removeBanClicked); connect(m_ui.banList, &QTreeWidget::itemSelectionChanged, this, &ChannelOptionsDialog::banSelectionChanged); connect(m_ui.hostmask, &KLineEdit::textChanged, this, &ChannelOptionsDialog::hostmaskChanged); m_ui.topicModeChBox->setWhatsThis(whatsThisForMode('T')); m_ui.messageModeChBox->setWhatsThis(whatsThisForMode('N')); m_ui.secretModeChBox->setWhatsThis(whatsThisForMode('S')); m_ui.inviteModeChBox->setWhatsThis(whatsThisForMode('I')); m_ui.moderatedModeChBox->setWhatsThis(whatsThisForMode('M')); m_ui.keyModeChBox->setWhatsThis(whatsThisForMode('P')); m_ui.keyModeEdit->setWhatsThis(whatsThisForMode('P')); m_ui.userLimitEdit->setWhatsThis(whatsThisForMode('L')); m_ui.userLimitChBox->setWhatsThis(whatsThisForMode('L')); refreshBanList(); resize(QSize(450, 420)); } ChannelOptionsDialog::~ChannelOptionsDialog() { } void ChannelOptionsDialog::showEvent(QShowEvent* event) { if (!event->spontaneous()) { refreshAllowedChannelModes(); refreshModes(); m_ui.topicEdit->clear(); m_editingTopic = false; m_ui.topicHistoryView->selectionModel()->clearSelection(); const QModelIndex& currentTopic = m_ui.topicHistoryView->model()->index(m_ui.topicHistoryView->model()->rowCount() - 1, 0); m_ui.topicHistoryView->selectionModel()->select(currentTopic, QItemSelectionModel::Select); m_ui.topicHistoryView->scrollTo(currentTopic, QAbstractItemView::EnsureVisible); if (!m_ui.topicEdit->isReadOnly()) m_ui.topicEdit->setFocus(); KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); resize(config.readEntry("Size", sizeHint())); const QList& sizes = config.readEntry("SplitterSizes", QList()); if (!sizes.isEmpty()) m_ui.splitter->setSizes(sizes); Preferences::restoreColumnState(m_ui.banList, "BanList ViewSettings"); } QDialog::showEvent(event); } void ChannelOptionsDialog::hideEvent(QHideEvent* event) { KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); config.writeEntry("Size", size()); config.writeEntry("SplitterSizes", m_ui.splitter->sizes()); Preferences::saveColumnState(m_ui.banList, "BanList ViewSettings"); QDialog::hideEvent(event); } void ChannelOptionsDialog::changeOptions() { const QString& newTopic = topic(); const QString& oldTopic = m_channel->getTopic(); if (newTopic != oldTopic) { // Pass a ^A so we can determine if we want to clear the channel topic. if (newTopic.isEmpty()) { if (!oldTopic.isEmpty()) m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + " \x01"); } else m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + ' ' + newTopic); } QStringList newModeList = modes(); QStringList currentModeList = m_channel->getModeList(); QStringList rmModes; QStringList addModes; QStringList tmp; QString modeString; bool plus; QString command("MODE %1 %2%3 %4"); for(QStringList::ConstIterator it = newModeList.constBegin(); it != newModeList.constEnd(); ++it) { modeString = (*it).mid(1); plus = ((*it)[0] == '+'); tmp = currentModeList.filter(QRegExp('^' + modeString)); if(tmp.isEmpty() && plus) { m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("+").arg(modeString[0]).arg(modeString.mid(1))); } else if(!tmp.isEmpty() && !plus) { //FIXME: Bahamuth requires the key parameter for -k, but ircd breaks on -l with limit number. //Hence two versions of this. if (modeString[0] == 'k') m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(modeString.mid(1))); else m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(QString())); } } hide(); } void ChannelOptionsDialog::toggleAdvancedModes() { bool ison = m_ui.toggleAdvancedModes->isChecked(); m_ui.otherModesList->setVisible(ison); if(ison) { m_ui.toggleAdvancedModes->setText(i18n("&Hide Advanced Modes <<")); } else { m_ui.toggleAdvancedModes->setText(i18n("&Show Advanced Modes >>")); } } void ChannelOptionsDialog::topicBeingEdited(bool edited) { m_editingTopic = edited; m_ui.topicHistoryView->setTextSelectable(edited); } QString ChannelOptionsDialog::topic() { return m_ui.topicEdit->toPlainText().replace('\n', ' '); } void ChannelOptionsDialog::topicHistoryItemClicked(const QItemSelection& selection) { if (!m_editingTopic) { m_ui.topicEdit->clear(); if (!selection.isEmpty()) { m_ui.topicEdit->setUndoRedoEnabled(false); m_ui.topicEdit->setPlainText(m_ui.topicHistoryView->model()->data(selection.indexes().first()).toString()); m_ui.topicEdit->setUndoRedoEnabled(true); } } } void ChannelOptionsDialog::refreshEnableModes(bool forceUpdate) { if(!m_channel->getOwnChannelNick() || m_channel->getOwnChannelNick()->isChanged() || forceUpdate) { // cache the value m_isAnyTypeOfOp = m_channel->getOwnChannelNick() ? m_channel->getOwnChannelNick()->isAnyTypeOfOp() : false; m_ui.topicEdit->setReadOnly(!m_isAnyTypeOfOp && m_ui.topicModeChBox->isChecked()); m_ui.topicModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.messageModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitEdit->setEnabled(m_isAnyTypeOfOp); m_ui.inviteModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.moderatedModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.secretModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeEdit->setEnabled(m_isAnyTypeOfOp); QStandardItemModel* model = qobject_cast(m_ui.otherModesList->model()); if (model) { QList items = model->findItems("*", Qt::MatchWildcard, 0); items += model->findItems("*", Qt::MatchWildcard, 1); foreach (QStandardItem* item, items) item->setEnabled(m_isAnyTypeOfOp); } m_ui.addBan->setEnabled(m_isAnyTypeOfOp); m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); banSelectionChanged(); m_ui.hostmask->setEnabled(m_isAnyTypeOfOp); hostmaskChanged(m_ui.hostmask->text()); } } void ChannelOptionsDialog::refreshAllowedChannelModes() { QString modeString = m_channel->getServer()->allowedChannelModes(); // These modes are handled in a special way: ntimslkbeI modeString.remove('t'); modeString.remove('n'); modeString.remove('l'); modeString.remove('i'); modeString.remove('m'); modeString.remove('s'); modeString.remove('k'); modeString.remove('b'); modeString.remove('e'); modeString.remove('I'); modeString.remove('O'); modeString.remove('o'); modeString.remove('v'); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); modesModel->clear(); modesModel->setHorizontalHeaderLabels(QStringList() << i18n("Mode") << i18n("Parameter")); for(int i = 0; i < modeString.length(); i++) { QList newRow; QStandardItem *item = nullptr; if(!Preferences::self()->useLiteralModes() && getChannelModesHash().contains(modeString[i])) item = new QStandardItem(i18nc(" ()","%1 (%2)", modeString[i], getChannelModesHash().value(modeString[i]))); else item = new QStandardItem(QString(modeString[i])); item->setData(QString(modeString[i])); item->setCheckable(true); item->setEditable(false); newRow.append(item); item = new QStandardItem(); item->setEditable(true); newRow.append(item); modesModel->invisibleRootItem()->appendRow(newRow); } } void ChannelOptionsDialog::refreshModes() { QStringList modes = m_channel->getModeList(); m_ui.topicModeChBox->setChecked(false); m_ui.messageModeChBox->setChecked(false); m_ui.userLimitChBox->setChecked(false); m_ui.userLimitEdit->setValue(0); m_ui.inviteModeChBox->setChecked(false); m_ui.moderatedModeChBox->setChecked(false); m_ui.secretModeChBox->setChecked(false); m_ui.keyModeChBox->setChecked(false); m_ui.keyModeEdit->setText(QString()); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { modesModel->item(i, 0)->setCheckState(Qt::Unchecked); } char mode; foreach (const QString ¤tMode, modes) { mode = currentMode[0].toLatin1(); switch(mode) { case 't': m_ui.topicModeChBox->setChecked(true); break; case 'n': m_ui.messageModeChBox->setChecked(true); break; case 'l': m_ui.userLimitChBox->setChecked(true); m_ui.userLimitEdit->setValue(currentMode.mid(1).toInt()); break; case 'i': m_ui.inviteModeChBox->setChecked(true); break; case 'm': m_ui.moderatedModeChBox->setChecked(true); break; case 's': m_ui.secretModeChBox->setChecked(true); break; case 'k': m_ui.keyModeChBox->setChecked(true); m_ui.keyModeEdit->setText(currentMode.mid(1)); break; default: { bool found = false; QString modeString; modeString = mode; for (int i = 0; !found && i < modesModel->rowCount(); ++i) { QStandardItem *item = modesModel->item(i, 0); if (item->data().toString() == modeString) { found = true; item->setCheckState(Qt::Checked); modesModel->item(i, 1)->setText(currentMode.mid(1)); } } break; } } } refreshEnableModes(true); } QStringList ChannelOptionsDialog::modes() { QStringList modes; QString mode; mode = (m_ui.topicModeChBox->isChecked() ? "+" : "-"); mode += 't'; modes.append(mode); mode = (m_ui.messageModeChBox->isChecked() ? "+" : "-"); mode += 'n'; modes.append(mode); mode = (m_ui.userLimitChBox->isChecked() ? "+" : "-"); mode += 'l' + QString::number( m_ui.userLimitEdit->value() ); modes.append(mode); mode = (m_ui.inviteModeChBox->isChecked() ? "+" : "-"); mode += 'i'; modes.append(mode); mode = (m_ui.moderatedModeChBox->isChecked() ? "+" : "-"); mode += 'm'; modes.append(mode); mode = (m_ui.secretModeChBox->isChecked() ? "+" : "-"); mode += 's'; modes.append(mode); if (m_ui.keyModeChBox->isChecked() && !m_ui.keyModeEdit->text().isEmpty()) { mode = '+'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } else if (!m_ui.keyModeChBox->isChecked()) { mode = '-'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { mode = (modesModel->item(i, 0)->checkState() == Qt::Checked ? "+" : "-"); mode += modesModel->item(i, 0)->data().toString() + modesModel->item(i, 1)->text(); modes.append(mode); } return modes; } // Ban List tab related functions void ChannelOptionsDialog::refreshBanList() { QStringList banlist = m_channel->getBanList(); m_ui.banList->clear(); for (QStringList::const_iterator it = --banlist.constEnd(); it != --banlist.constBegin(); --it) addBan((*it)); } void ChannelOptionsDialog::addBan(const QString& newban) { BanListViewItem *item = new BanListViewItem(m_ui.banList, newban.section(' ', 0, 0), newban.section(' ', 1, 1).section('!', 0, 0), newban.section(' ', 2 ,2).toUInt()); // set item as current item m_ui.banList->setCurrentItem(item); // update button states hostmaskChanged(m_ui.hostmask->text()); } void ChannelOptionsDialog::removeBan(const QString& ban) { QList items = m_ui.banList->findItems(ban, Qt::MatchCaseSensitive | Qt::MatchExactly, 0); if (items.count() > 0) delete items.at(0); } void ChannelOptionsDialog::addBanClicked() { QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty()) m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } void ChannelOptionsDialog::removeBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); } void ChannelOptionsDialog::updateBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty() && newHostmask.compare(oldHostmask)) { // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban for the of the old hostmask m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); // request ban for the of the old hostmask m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } } /// Enables/disables updateBan and removeBan buttons depending on the currentItem of the banList void ChannelOptionsDialog::banSelectionChanged() { if (m_ui.banList->currentItem()) { m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); // update line edit content m_ui.hostmask->setText(m_ui.banList->currentItem()->text(0)); } else { m_ui.updateBan->setEnabled(false); m_ui.removeBan->setEnabled(false); } } /// Enables/disables addBan and updateBan buttons depending on the value of @p text void ChannelOptionsDialog::hostmaskChanged(const QString& text) { if (text.trimmed().length() != 0) { if (m_isAnyTypeOfOp) { QList items = m_ui.banList->findItems(text, Qt::MatchExactly | Qt::MatchCaseSensitive, 0); m_ui.addBan->setEnabled(items.count() == 0); m_ui.updateBan->setEnabled(items.count() == 0 && m_ui.banList->currentItem()); } } else { m_ui.addBan->setEnabled(false); m_ui.updateBan->setEnabled(false); } } // This is our implementation of BanListViewItem BanListViewItem::BanListViewItem(QTreeWidget *parent) : QTreeWidgetItem() { parent->addTopLevelItem(this); } BanListViewItem::BanListViewItem (QTreeWidget *parent, const QString& label1, const QString& label2, uint timestamp) : QTreeWidgetItem() { setText(0, label1); setText(1, label2); m_timestamp.setTime_t(timestamp); setText(2, QLocale().toString(m_timestamp, QLocale::ShortFormat)); setData(2, Qt::UserRole, m_timestamp); parent->addTopLevelItem(this); } bool BanListViewItem::operator<(const QTreeWidgetItem &item) const { if (treeWidget()->sortColumn() == 2) { QVariant userdata = item.data(2, Qt::UserRole); if (userdata.isValid() && userdata.type() == QVariant::DateTime) { return m_timestamp < userdata.toDateTime(); } } return text(treeWidget()->sortColumn()) < item.text(treeWidget()->sortColumn()); } } QString Konversation::ChannelOptionsDialog::whatsThisForMode(char mode) { switch (mode) { case 'T': return i18n("

These control the mode of the channel. Only an operator can change these.

The Topic mode means that only the channel operator can change the topic for the channel.

"); case 'N': return i18n("

These control the mode of the channel. Only an operator can change these.

No messages from outside means users who are not in the channel cannot send messages for everybody in the channel to see. Almost all channels have this set to prevent nuisance messages.

"); case 'S': return i18n("

These control the mode of the channel. Only an operator can change these.

A Secret channel will not show up in the channel list, nor will any user be able to see that you are in the channel with the WHOIS command or anything similar. Only the people that are in the same channel will know that you are in this channel, if this mode is set.

"); case 'I': return i18n("

These control the mode of the channel. Only an operator can change these.

An Invite only channel means that people can only join the channel if they are invited. To invite someone, a channel operator needs to issue the command /invite nick from within the channel.

"); case 'P': return i18n("

These control the mode of the channel. Only an operator can change these.

A Private channel is shown in a listing of all channels, but the topic is not shown. A user's WHOIS may or may not show them as being in a private channel depending on the IRC server.

"); case 'M': return i18n("

These control the mode of the channel. Only an operator can change these.

A Moderated channel is one where only operators, half-operators and those with voice can talk.

"); case 'K': return i18n("

These control the mode of the channel. Only an operator can change these.

A protected channel requires users to enter a password in order to join.

"); case 'L': return i18n("

These control the mode of the channel. Only an operator can change these.

A channel that has a user Limit means that only that many users can be in the channel at any one time. Some channels have a bot that sits in the channel and changes this automatically depending on how busy the channel is.

"); default: qWarning() << "called for unknown mode" << mode; return QString(); } } diff --git a/src/viewer/chatwindow.cpp b/src/viewer/chatwindow.cpp index 7ce43b78..170abc3b 100644 --- a/src/viewer/chatwindow.cpp +++ b/src/viewer/chatwindow.cpp @@ -1,762 +1,762 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2006-2008 Eike Hein */ #include "chatwindow.h" #include "channel.h" #include "query.h" #include "ircview.h" #include "ircinput.h" #include "server.h" #include "application.h" #include "logfilereader.h" #include "viewcontainer.h" #include #include #include #include #include #include #include ChatWindow::ChatWindow(QWidget* parent) : QWidget(parent) { QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setMargin(margin()); mainLayout->setSpacing(spacing()); setName("ChatWindowObject"); setTextView(nullptr); setInputBar(nullptr); firstLog = true; m_server = nullptr; m_recreationScheduled = false; m_isTopLevelView = true; m_notificationsEnabled = true; m_channelEncodingSupported = false; m_currentTabNotify = Konversation::tnfNone; } ChatWindow::~ChatWindow() { if (getInputBar() && getServer()) { const QString& language = getInputBar()->spellCheckingLanguage(); if (!language.isEmpty()) { Konversation::ServerGroupSettingsPtr serverGroup = getServer()->getConnectionSettings().serverGroup(); if (serverGroup) Preferences::setSpellCheckingLanguage(serverGroup, getName(), language); else Preferences::setSpellCheckingLanguage(getServer()->getDisplayName(), getName(), language); } } emit closing(this); m_server=nullptr; } void ChatWindow::childEvent(QChildEvent* event) { if(event->type() == QChildEvent::ChildAdded) { if(event->child()->isWidgetType()) { layout()->addWidget(qobject_cast< QWidget* >(event->child())); } } else if(event->type() == QChildEvent::ChildRemoved) { if(event->child()->isWidgetType()) { layout()->removeWidget(qobject_cast(event->child())); } } } // reimplement this if your window needs special close treatment bool ChatWindow::closeYourself(bool /* askForConfirmation */) { deleteLater(); return true; } void ChatWindow::cycle() { m_recreationScheduled = true; closeYourself(false); } void ChatWindow::updateAppearance() { if (getTextView()) getTextView()->updateAppearance(); // The font size of the KTabWidget container may be inappropriately // small due to the "Tab bar" font size setting. setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); } void ChatWindow::setName(const QString& newName) { name=newName; emit nameChanged(this,newName); } QString ChatWindow::getName() const { return name; } QString ChatWindow::getTitle() const { QString title; if (getType() == Channel) { title = QString("%1 (%2)") .arg(getName()) .arg(getServer()->getDisplayName()); } else { title = getName(); } return title; } QString ChatWindow::getURI(bool passNetwork) { QString protocol; QString url; QString port; QString server; QString channel; if (getServer()->getUseSSL()) protocol = "ircs://"; else protocol = "irc://"; if (getType() == Channel) { channel = getName().replace(QRegExp("^#"), QString()); // must protect second #, but might as well protect all of them channel.replace("#", "%23"); } if (passNetwork) { server = getServer()->getDisplayName(); QUrl test(protocol+server); // QUrl (ultimately used by the bookmark system, which is the // primary consumer here) doesn't like spaces in hostnames as // well as other things which are possible in user-chosen net- // work names, so let's fall back to the hostname if we can't // get the network name by it. if (!test.isValid()) passNetwork = false; } if (!passNetwork) { server = getServer()->getServerName(); port = ':'+QString::number(getServer()->getPort()); } if (server.contains(':')) // IPv6 server = '['+server+']'; url = protocol+server+port+'/'+channel; return url; } void ChatWindow::setType(WindowType newType) { type=newType; } ChatWindow::WindowType ChatWindow::getType() const { return type; } bool ChatWindow::isTopLevelView() const { return m_isTopLevelView; } void ChatWindow::setServer(Server* newServer) { if (!newServer) { qDebug() << "ChatWindow::setServer(0)!"; } else { m_server=newServer; connect(m_server, &Server::serverOnline, this, &ChatWindow::serverOnline); // check if we need to set up the signals if(getType() != ChannelList) { if(textView) textView->setServer(newServer); else qDebug() << "textView==0!"; } serverOnline(m_server->isConnected()); } if (getInputBar()) { QString language; Konversation::ServerGroupSettingsPtr serverGroup = newServer->getConnectionSettings().serverGroup(); if (serverGroup) language = Preferences::spellCheckingLanguage(serverGroup, getName()); else language = Preferences::spellCheckingLanguage(newServer->getDisplayName(), getName()); if (!language.isEmpty()) getInputBar()->setSpellCheckingLanguage(language); } } Server* ChatWindow::getServer() const { return m_server; } void ChatWindow::serverOnline(bool /* state */) { //emit online(this,state); } void ChatWindow::setTextView(IRCView* newView) { textView = newView; if(!textView) { return; } textView->setVerticalScrollBarPolicy(Preferences::self()->showIRCViewScrollBar() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); textView->setChatWin(this); connect(textView, &IRCView::textToLog, this, &ChatWindow::logText); connect(textView, &IRCView::setStatusBarTempText, this, &ChatWindow::setStatusBarTempText); connect(textView, &IRCView::clearStatusBarTempText, this, &ChatWindow::clearStatusBarTempText); } void ChatWindow::appendRaw(const QString& message, bool self) { if(!textView) return; textView->appendRaw(message, self); } void ChatWindow::appendLog(const QString& message) { if(!textView) return; textView->appendLog(message); } void ChatWindow::append(const QString& nickname, const QString& message, const QHash &messageTags, const QString& label) { if(!textView) return; textView->append(nickname, message, messageTags, label); } void ChatWindow::appendQuery(const QString& nickname, const QString& message, const QHash &messageTags, bool inChannel) { if(!textView) return ; textView->appendQuery(nickname, message, messageTags, inChannel); } void ChatWindow::appendAction(const QString& nickname, const QString& message, const QHash &messageTags) { if(!textView) return; if (getType() == Query || getType() == DccChat) textView->appendQueryAction(nickname, message, messageTags); else textView->appendChannelAction(nickname, message, messageTags); } void ChatWindow::appendServerMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { if(!textView) return ; textView->appendServerMessage(type, message, messageTags, parseURL); } void ChatWindow::appendCommandMessage(const QString& command, const QString& message, const QHash &messageTags, bool parseURL, bool self) { if(!textView) return ; textView->appendCommandMessage(command, message, messageTags, parseURL, self); } void ChatWindow::appendBacklogMessage(const QString& firstColumn,const QString& message) { if(!textView) return ; textView->appendBacklogMessage(firstColumn,Konversation::sterilizeUnicode(message)); } void ChatWindow::clear() { if (!textView) return; textView->clear(); resetTabNotification(); if (m_server) m_server->getViewContainer()->unsetViewNotification(this); } void ChatWindow::cdIntoLogPath() { QString home = KUser(KUser::UseRealUserID).homeDir(); QUrl logUrl = Preferences::self()->logfilePath(); if(!logUrl.isLocalFile()) { return; } QString logPath = logUrl.toLocalFile(); QDir logDir(home); // Try to "cd" into the logfile path. if (!logDir.cd(logPath)) { // Only create log path if logging is enabled. if (log()) { // Try to create the logfile path and "cd" into it again. logDir.mkpath(logPath); logDir.cd(logPath); } } // Add the logfile name to the path. logfile.setFileName(logDir.path() + '/' + logName); } void ChatWindow::setLogfileName(const QString& name) { // Only change name of logfile if the window was new. if(firstLog) { if (getTextView()) getTextView()->setContextMenuOptions(IrcContextMenus::ShowLogAction, true); // status panels get special treatment here, since they have no server at the beginning if (getType() == Status || getType() == DccChat) { logName = name + ".log"; } else if (m_server) { // make sure that no path delimiters are in the name logName = QString(m_server->getDisplayName().toLower()).append('_').append(name).append(".log").replace('/','_'); } // load backlog to show if(Preferences::self()->showBacklog()) { // "cd" into log path or create path, if it's not there cdIntoLogPath(); // Show last log lines. This idea was stole ... um ... inspired by PMP :) // Don't do this for the server status windows, though if((getType() != Status) && logfile.open(QIODevice::ReadOnly)) { qint64 filePosition; QString backlogLine; QTextStream backlog(&logfile); backlog.setCodec(QTextCodec::codecForName("UTF-8")); backlog.setAutoDetectUnicode(true); QStringList firstColumns; QStringList messages; int offset = 0; qint64 lastPacketHeadPosition = backlog.device()->size(); const unsigned int packetSize = 4096; while(messages.count() < Preferences::self()->backlogLines() && backlog.device()->size() > packetSize * offset) { QStringList firstColumnsInPacket; QStringList messagesInPacket; // packetSize * offset < size <= packetSize * ( offset + 1 ) // Check if the log is bigger than packetSize * ( offset + 1 ) if(backlog.device()->size() > packetSize * ( offset + 1 )) { // Set file pointer to the packet size above the offset backlog.seek(backlog.device()->size() - packetSize * ( offset + 1 )); // Skip first line, since it may be incomplete backlog.readLine(); } else { // Set file pointer to the head // Qt 4.5 Doc: Note that when using a QTextStream on a // QFile, calling reset() on the QFile will not have the // expected result because QTextStream buffers the file. // Use the QTextStream::seek() function instead. // backlog.device()->reset(); backlog.seek( 0 ); } // remember actual file position to check for deadlocks filePosition = backlog.pos(); qint64 currentPacketHeadPosition = filePosition; // Loop until end of file reached while(!backlog.atEnd() && filePosition < lastPacketHeadPosition) { backlogLine = backlog.readLine(); // check for deadlocks if(backlog.pos() == filePosition) { backlog.seek(filePosition + 1); } // if a tab character is present in the line, meaning it is a valid chatline if (backlogLine.contains('\t')) { // extract first column from log QString backlogFirst = backlogLine.left(backlogLine.indexOf('\t')); // cut first column from line backlogLine = backlogLine.mid(backlogLine.indexOf('\t') + 1); // Logfile is in utf8 so we don't need to do encoding stuff here // append backlog with time and first column to text view firstColumnsInPacket << backlogFirst; messagesInPacket << backlogLine; } // remember actual file position to check for deadlocks filePosition = backlog.pos(); } // while // remember the position not to read the same lines again lastPacketHeadPosition = currentPacketHeadPosition; ++offset; firstColumns = firstColumnsInPacket + firstColumns; messages = messagesInPacket + messages; } backlog.setDevice(nullptr); logfile.close(); // trim int surplus = messages.count() - Preferences::self()->backlogLines(); // "surplus" can be a minus value. (when the backlog is too short) if(surplus > 0) { for(int i = 0 ; i < surplus ; ++i) { firstColumns.pop_front(); messages.pop_front(); } } QStringList::Iterator itFirstColumn = firstColumns.begin(); QStringList::Iterator itMessage = messages.begin(); for( ; itFirstColumn != firstColumns.end() ; ++itFirstColumn, ++itMessage ) appendBacklogMessage(*itFirstColumn, *itMessage); } } // if(Preferences::showBacklog()) } } void ChatWindow::logText(const QString& text) { if(log()) { // "cd" into log path or create path, if it's not there cdIntoLogPath(); if(logfile.open(QIODevice::WriteOnly | QIODevice::Append)) { // wrap the file into a stream QTextStream logStream(&logfile); // write log in utf8 to help i18n logStream.setCodec(QTextCodec::codecForName("UTF-8")); logStream.setAutoDetectUnicode(true); if(firstLog) { QString intro(i18n("\n*** Logfile started\n*** on %1\n\n", QDateTime::currentDateTime().toString())); logStream << intro; firstLog=false; } QDateTime dateTime = QDateTime::currentDateTime(); QString logLine(QString("[%1] [%2] %3\n").arg(QLocale().toString(dateTime.date(), QLocale::LongFormat)). arg(QLocale().toString(dateTime.time(), QLocale::LongFormat)).arg(text)); logStream << logLine; // detach stream from file logStream.setDevice(nullptr); // close file logfile.close(); } else qWarning() << "open(QIODevice::Append) for " << logfile.fileName() << " failed!"; } } void ChatWindow::setChannelEncodingSupported(bool enabled) { m_channelEncodingSupported = enabled; } bool ChatWindow::isChannelEncodingSupported() const { return m_channelEncodingSupported; } int ChatWindow::spacing() { if(Preferences::self()->useSpacing()) return Preferences::self()->spacing(); else return style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Vertical); } int ChatWindow::margin() { if(Preferences::self()->useSpacing()) return Preferences::self()->margin(); else return 0; } // Accessors IRCView* ChatWindow::getTextView() const { return textView; } bool ChatWindow::log() { return Preferences::self()->log(); } // reimplement this in all panels that have user input QString ChatWindow::getTextInLine() { if (m_inputBar) return m_inputBar->toPlainText(); else return QString(); } bool ChatWindow::canBeFrontView() { return false; } bool ChatWindow::searchView() { return false; } // reimplement this in all panels that have user input void ChatWindow::indicateAway(bool) { } // reimplement this in all panels that have user input void ChatWindow::appendInputText(const QString& text, bool fromCursor) { if (!fromCursor) m_inputBar->append(text); else { const int position = m_inputBar->textCursor().position(); m_inputBar->textCursor().insertText(text); QTextCursor cursor = m_inputBar->textCursor(); cursor.setPosition(position + text.length()); m_inputBar->setTextCursor(cursor); } } bool ChatWindow::eventFilter(QObject* watched, QEvent* e) { if(e->type() == QEvent::KeyPress) { - QKeyEvent* ke = static_cast(e); + QKeyEvent* ke = dynamic_cast(e); bool scrollMod = (Preferences::self()->useMultiRowInputBox() ? false : (ke->modifiers() == Qt::ShiftModifier)); if(ke->key() == Qt::Key_Up && scrollMod) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() - sbar->singleStep()); } return true; } else if(ke->key() == Qt::Key_Down && scrollMod) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() + sbar->singleStep()); } return true; } else if(ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_PageUp) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() - sbar->pageStep()); } return true; } else if(ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_PageDown) { if(textView) { QScrollBar* sbar = textView->verticalScrollBar(); sbar->setValue(sbar->value() + sbar->pageStep()); } return true; } } return QWidget::eventFilter(watched, e); } void ChatWindow::adjustFocus() { childAdjustFocus(); } void ChatWindow::emitUpdateInfo() { QString info = getName(); emit updateInfo(info); } QColor ChatWindow::highlightColor() { return getTextView()->highlightColor(); } void ChatWindow::activateTabNotification(Konversation::TabNotifyType type) { if (!notificationsEnabled()) return; if(type > m_currentTabNotify) return; m_currentTabNotify = type; emit updateTabNotification(this,type); } void ChatWindow::resetTabNotification() { m_currentTabNotify = Konversation::tnfNone; } void ChatWindow::msgHelper(const QString& recipient, const QString& message) { // A helper method for handling the 'msg' and 'query' (with a message // payload) commands. When the user uses either, we show a visualiza- // tion of what he/she has sent in the form of '<-> target> message>' // in the chat view of the tab the command was issued in, as well as // add the resulting message to the target view (if present), in that // order. The order is especially important as the origin and target // views may be the same, and the two messages may thus appear toge- // ther and should be sensibly ordered. if (recipient.isEmpty() || message.isEmpty()) return; bool isAction = false; QString result = message; QString visualization; if (result.startsWith(Preferences::self()->commandChar() + "me")) { isAction = true; result = result.mid(4); visualization = QString("* %1 %2").arg(m_server->getNickname()).arg(result); } else visualization = result; appendQuery(recipient, visualization, QHash(), true); if (!getServer()) return; ::Query* query = m_server->getQueryByName(recipient); if (query) { if (isAction) query->appendAction(m_server->getNickname(), result); else query->appendQuery(m_server->getNickname(), result); return; } ::Channel* channel = m_server->getChannelByName(recipient); if (channel) { if (isAction) channel->appendAction(m_server->getNickname(), result); else channel->append(m_server->getNickname(), result); } } void ChatWindow::activateView() { for(QWidget* widget = this; widget; widget = widget->parentWidget()) { if (widget->window()) { widget->show(); widget->activateWindow(); widget->raise(); } } } diff --git a/src/viewer/irccontextmenus.cpp b/src/viewer/irccontextmenus.cpp index 4ce11500..80c35b4a 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 = static_cast(actionCollection->action("options_show_menubar")); + 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()); } } 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/ircinput.cpp b/src/viewer/ircinput.cpp index 817ec190..0bf5aff1 100644 --- a/src/viewer/ircinput.cpp +++ b/src/viewer/ircinput.cpp @@ -1,569 +1,569 @@ /* 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. */ /* The line input widget with chat enhanced functions begin: Tue Mar 5 2002 copyright: (C) 2002 by Dario Abatianni email: eisfuchs@tigress.com */ #include "ircinput.h" #include "application.h" #include "pasteeditor.h" #include #include #include #include #include #include #include #define MAXHISTORY 100 IRCInput::IRCInput(QWidget* parent) : KTextEdit(parent) { enableFindReplace(false); setAcceptRichText(false); //I am not terribly interested in finding out where this value comes from //nor in compensating for it if my guess is incorrect. so, cache it. m_qtBoxPadding = document()->size().toSize().height() - fontMetrics().lineSpacing(); connect(qApp, SIGNAL(appearanceChanged()), this, SLOT(updateAppearance())); m_multiRow = Preferences::self()->useMultiRowInputBox(); // add one empty line to the history (will be overwritten with newest entry) historyList.prepend(QString()); // reset history line counter lineNum=0; // reset completion mode setCompletionMode('\0'); completionBox = new KCompletionBox(this); connect(completionBox, &KCompletionBox::activated, this, &IRCInput::insertCompletion); // widget may not be resized vertically setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::Fixed)); updateAppearance(); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setWhatsThis(i18n("

The input line is where you type messages to be sent the channel, query, or server. A message sent to a channel is seen by everyone on the channel, whereas a message in a query is sent only to the person in the query with you.

To automatically complete the nickname you began typing, press Tab. If you have not begun typing, the last successfully completed nickname will be used.

You can also send special commands:

/me actionshows up as an action in the channel or query. For example: /me sings a song will show up in the channel as 'Nick sings a song'.
/whois nicknameshows information about this person, including what channels they are in.

For more commands, see the Konversation Handbook.

A message cannot contain multiple lines.

")); m_disableSpellCheckTimer = new QTimer(this); connect(m_disableSpellCheckTimer, &QTimer::timeout, this, &IRCInput::disableSpellChecking); document()->adjustSize(); document()->setDocumentMargin(2); } IRCInput::~IRCInput() { } void IRCInput::createHighlighter() { KTextEdit::createHighlighter(); setSpellCheckingLanguage(spellCheckingLanguage()); } QSize IRCInput::sizeHint() const { QFontMetrics fm(font()); int h = document()->size().toSize().height() - fm.descent() + 2 * frameWidth(); QStyleOptionFrame opt; opt.initFrom(this); opt.rect = QRect(0, 0, 100, h); opt.lineWidth = lineWidth(); opt.midLineWidth = 0; opt.state |= QStyle::State_Sunken; QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), this); return s; } QSize IRCInput::minimumSizeHint() const { return sizeHint(); } void IRCInput::maybeResize() { updateGeometry(); } void IRCInput::showEvent(QShowEvent* /* e */) { m_disableSpellCheckTimer->stop(); setCheckSpellingEnabled(Preferences::self()->spellChecking()); setSpellCheckingLanguage(spellCheckingLanguage()); connect(this, &IRCInput::checkSpellingChanged, this, &IRCInput::setSpellChecking); } void IRCInput::hideEvent(QHideEvent* /* event */) { // If we disable spell-checking here immediately, tab switching will // be very slow. If we delay it by five seconds, a user would have to // need more than five seconds to switch between all his tabs before // the slowdown starts to occur (show event stops the timer, i.e. wrap- // around is not an issue). Unless he has unlikely amounts of channels, // needing more than five seconds indicates very slow switching speed, // which makes the delay a non-issue to begin with. Hence this fixes // the problem on the surface. In the KDE 4 version, we want to look // into having only one spell-checker instance instead of starting and // stopping at all. //TODO FIXME when we get to require 4.2 the above is possible with //KTextEditSpellInterface and is actually quite easy to do. disconnect(SIGNAL(checkSpellingChanged(bool))); m_disableSpellCheckTimer->setSingleShot(true); m_disableSpellCheckTimer->start(5000); } void IRCInput::resizeEvent(QResizeEvent* e) { maybeResize(); KTextEdit::resizeEvent(e); } void IRCInput::disableSpellChecking() { setCheckSpellingEnabled(false); } void IRCInput::setSpellChecking(bool set) { Preferences::self()->setSpellChecking(set); } void IRCInput::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)); } setPalette(palette); if (Preferences::self()->customTextFont()) setFont(Preferences::self()->textFont()); else setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_multiRow = Preferences::self()->useMultiRowInputBox(); setLineWrapMode(m_multiRow ? WidgetWidth : NoWrap); if (m_multiRow) connect(this, &IRCInput::textChanged, this, &IRCInput::maybeResize); else disconnect(this, &IRCInput::textChanged, this, &IRCInput::maybeResize); maybeResize(); ensureCursorVisible(); //appears to trigger updateGeometry } void IRCInput::setText(const QString& text, bool preserveContents) { if (!text.isEmpty() && preserveContents) getHistory(false); // reimplemented to set cursor at the end of the new text KTextEdit::setPlainText(text); moveCursor(QTextCursor::End); } // FIXME - find a better way to do this. eventfilters introduce nebulous behaviour //take text events from IRCView and TopicLabel bool IRCInput::eventFilter(QObject *object,QEvent *event) { if (object->metaObject()->className() == QString("IRCView") || object->metaObject()->className() == QString("Konversation::TopicLabel")) { if (event->type() == QEvent::KeyPress) { - QKeyEvent* ke = static_cast(event); + QKeyEvent* ke = dynamic_cast(event); // Allow tab to be handled naturally by the widget. // Once it runs out of links it goes to the next control. if (ke->key() == Qt::Key_Tab && (ke->modifiers() == 0 || ke->modifiers() == Qt::ShiftModifier)) return false; if (!ke->text().isEmpty() && ((ke->modifiers() & (Qt::ShiftModifier|Qt::KeypadModifier|Qt::GroupSwitchModifier)) || ke->modifiers() == 0)) { setFocus(); Application::sendEvent(this,event); return true; } } } return KTextEdit::eventFilter(object,event); } // Take care of Tab, Cursor and so on void IRCInput::keyPressEvent(QKeyEvent* e) { switch(e->key()) { case Qt::Key_Tab: emit nickCompletion(); return; break; case Qt::Key_Up: if (m_multiRow && textCursor().movePosition(QTextCursor::Up)) break; getHistory(true); return; break; case Qt::Key_Down: if (m_multiRow && textCursor().movePosition(QTextCursor::Down)) break; getHistory(false); return; break; case Qt::Key_Enter: case Qt::Key_Return: { if(!toPlainText().isEmpty()) addHistory(toPlainText()); if(completionBox->isHidden()) { // Reset completion mode setCompletionMode('\0'); // Ctrl+Enter is a special case in which commands should be send as normal messages if ( e->modifiers() & Qt::ControlModifier ) { emit envelopeCommand(); } else { setText(Application::instance()->doAutoreplace(toPlainText(), true).first); emit submit(); } } else { insertCompletion(completionBox->currentItem() ? completionBox->currentItem()->text() : completionBox->item(0)->text()); completionBox->hide(); } // prevent widget from adding lines return; } break; default: // Check if the keystroke actually produced text. If not it was just a qualifier. if(!e->text().isEmpty() || ((e->key() >= Qt::Key_Home) && (e->key() <= Qt::Key_Down))) { if(getCompletionMode()!='\0') { setCompletionMode('\0'); emit endCompletion(); } completionBox->hide(); } // support ASCII BEL if(e->text().unicode()->toLatin1() == 7) insertPlainText("%G"); // support ^U (delete text in input box) else if(e->text().unicode()->toLatin1() == 21) setText(QString()); } KTextEdit::keyPressEvent(e); } bool IRCInput::event(QEvent* e) { if (e->type() == QEvent::ShortcutOverride) { // Make sure KTextEdit doesn't eat actionCollection shortcuts - QKeyEvent* event = static_cast(e); + QKeyEvent* event = dynamic_cast(e); const int key = event->key() | event->modifiers(); foreach(QAction* action, Application::instance()->getMainWindow()->actionCollection()->actions()) { if (action->shortcuts().contains(QKeySequence(key))) { event->ignore(); return false; } } } return KTextEdit::event(e); } void IRCInput::wheelEvent(QWheelEvent* e) { if (e->delta() > 0) getHistory(true); else if (e->delta() < 0) getHistory(false); KTextEdit::wheelEvent(e); } void IRCInput::addHistory(const QString& line) { // Only add line if it's not the same as the last was if(historyList.value(1)!=line) { // Replace empty first entry with line historyList[0]=line; // Add new empty entry to history historyList.prepend(QString()); // Remove oldest line in history, if the list grows beyond MAXHISTORY if(historyList.count()>MAXHISTORY) historyList.removeLast(); } // Reset history counter lineNum=0; } void IRCInput::getHistory(bool up) { // preserve text historyList[lineNum]=toPlainText(); // Did the user press cursor up? if(up) { // increment the line counter lineNum++; // if we are past the end of the list, go to the last entry if (lineNum==historyList.count()) { lineNum--; return; } } // no, it was cursor down else { // If we are at the top of the lest, arrow-down shall add the text to the history and clear the field for new input if(lineNum==0) { if(!toPlainText().isEmpty()) addHistory(toPlainText()); setText(QString()); } // If we aren't at the top of the list, decrement the line counter else { lineNum--; } } // replace the text in the input field with history setText(historyList[lineNum]); } void IRCInput::paste(bool useSelection) { insertFromMimeData(QApplication::clipboard()->mimeData(useSelection ? QClipboard::Selection : QClipboard::Clipboard)); } void IRCInput::insertFromMimeData(const QMimeData * source) { if(!source) return; setFocus(); // Copy text from the clipboard (paste) QString pasteText = source->text(); // is there any text in the clipboard? if(!pasteText.isEmpty()) { //End completion on paste setCompletionMode('\0'); emit endCompletion(); bool signal=false; //filter out crashy crap Konversation::sterilizeUnicode(pasteText); // replace \r with \n to make xterm pastes happy pasteText.replace('\r','\n'); // remove blank lines while(pasteText.contains("\n\n")) pasteText.replace("\n\n","\n"); QRegExp reTopSpace("^ *\n"); while(pasteText.contains(reTopSpace)) pasteText.remove(reTopSpace); QRegExp reBottomSpace("\n *$"); while(pasteText.contains(reBottomSpace)) pasteText.remove(reBottomSpace); // does the text contain at least one newline character? if(pasteText.contains('\n')) { // make comparisons easier (avoid signed / unsigned warnings) int pos=pasteText.indexOf('\n'); int rpos=pasteText.lastIndexOf('\n'); // emit the signal if there's a line break in the middle of the text if(pos>0 && pos!=(pasteText.length()-1)) signal=true; // emit the signal if there's more than one line break in the text if(pos!=rpos) signal=true; // Remove the \n from end of the line if there's only one \n if(!signal) pasteText.remove('\n'); } else { insertPlainText(pasteText); ensureCursorVisible(); return; } // should we signal the application due to newlines in the paste? if(signal) { // if there is text in the input line if(!toPlainText().isEmpty()) { // prepend text to the paste pasteText=toPlainText()+'\n'+pasteText; } // ask the user on long pastes if(checkPaste(pasteText)) { pasteText = Application::instance()->doAutoreplace(pasteText, true).first; Konversation::sterilizeUnicode(pasteText); // signal pasted text emit textPasted(pasteText); // remember old line, in case the user does not paste eventually addHistory(pasteText); // delete input text setText(QString()); } } // otherwise let the KLineEdit handle the pasting else KTextEdit::insertFromMimeData(source); } } bool IRCInput::checkPaste(QString& text) { int doPaste=KMessageBox::Yes; //text is now preconditioned when you get here int lines=text.count('\n'); if(text.length()>256 || lines) { QString bytesString = i18np("1 byte", "%1 bytes", text.length()); QString linesString = i18np("1 line", "%1 lines", lines+1); doPaste=KMessageBox::warningYesNoCancel (this, i18nc( "%1 is, for instance, '200 bytes'. %2 is, for instance, '7 lines'. Both are localised (see the two previous messages).", "You are attempting to paste a large portion of text (%1 or %2) into " "the chat. This can cause connection resets or flood kills. " "Do you really want to continue?", bytesString, linesString), i18n("Large Paste Warning"), KGuiItem(i18n("Paste")), KGuiItem(i18n("&Edit...")), KStandardGuiItem::cancel(), QString("LargePaste"), KMessageBox::Dangerous); } if (doPaste==KMessageBox::No) { QString ret(PasteEditor::edit(this,text)); if (ret.isEmpty()) return false; text=ret; return true; } return (doPaste==KMessageBox::Yes); } void IRCInput::showCompletionList(const QStringList& nicks) { completionBox->setItems(nicks); completionBox->popup(); } void IRCInput::insertCompletion(const QString& nick) { int pos; // = cursorPosition(); int oldPos; // = cursorPosition(); //getCursorPosition(&oldPos,&pos); oldPos=pos=textCursor().position(); QString line = toPlainText(); while(pos && line[pos-1] != ' ') pos--; line.remove(pos, oldPos - pos); // did we find the nick in the middle of the line? if(pos) { QString addMiddle(Preferences::self()->nickCompleteSuffixMiddle()); line.insert(pos, nick + addMiddle); pos += nick.length() + addMiddle.length(); } // no, it was at the beginning else { setLastCompletion(nick); QString addStart(Preferences::self()->nickCompleteSuffixStart()); line.insert(pos, nick + addStart); pos += nick.length() + addStart.length(); } setText(line); textCursor().setPosition(pos); } void IRCInput::setLastCompletion(const QString& completion) { m_lastCompletion = completion; } void IRCInput::doInlineAutoreplace() { Application::instance()->doInlineAutoreplace(this); } // Accessor methods void IRCInput::setCompletionMode(char mode) { completionMode=mode; } char IRCInput::getCompletionMode() { return completionMode; } void IRCInput::setOldCursorPosition(int pos) { oldPos=pos; } int IRCInput::getOldCursorPosition() { return oldPos; } diff --git a/src/viewer/ircview.cpp b/src/viewer/ircview.cpp index e013a0ef..8c6242f2 100644 --- a/src/viewer/ircview.cpp +++ b/src/viewer/ircview.cpp @@ -1,2396 +1,2396 @@ // -*- 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-2016 Peter Simonsson Copyright (C) 2006-2010 Eike Hein Copyright (C) 2004-2011 Eli Mackenzie */ #include "ircview.h" #include "channel.h" #include "dcc/chatcontainer.h" #include "application.h" #include "highlight.h" #include "sound.h" #include "emoticons.h" #include "notificationhandler.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; class ScrollBarPin { QPointer m_bar; public: ScrollBarPin(QScrollBar *scrollBar) : m_bar(scrollBar) { if (m_bar) m_bar = m_bar->value() == m_bar->maximum()? m_bar : nullptr; } ~ScrollBarPin() { if (m_bar) m_bar->setValue(m_bar->maximum()); } }; // Scribe bug - if the cursor position or anchor points to the last character in the document, // the cursor becomes glued to the end of the document instead of retaining the actual position. // This causes the selection to expand when something is appended to the document. class SelectionPin { int pos, anc; QPointer d; public: SelectionPin(IRCView *doc) : pos(0), anc(0), d(doc) { if (d->textCursor().hasSelection()) { int end = d->document()->rootFrame()->lastPosition(); //WARNING if selection pins don't work in some build environments, we need to keep the result d->document()->lastBlock(); pos = d->textCursor().position(); anc = d->textCursor().anchor(); if (pos != end && anc != end) anc = pos = 0; } } ~SelectionPin() { if (d && (pos || anc)) { QTextCursor mv(d->textCursor()); mv.setPosition(anc); mv.setPosition(pos, QTextCursor::KeepAnchor); d->setTextCursor(mv); } } }; IRCView::IRCView(QWidget* parent) : QTextBrowser(parent), m_rememberLine(nullptr), m_lastMarkerLine(nullptr), m_rememberLineDirtyBit(false), markerFormatObject(this) { m_mousePressedOnUrl = false; m_isOnNick = false; m_isOnChannel = false; m_chatWin = nullptr; m_server = nullptr; m_fontSizeDelta = 0; setAcceptDrops(false); // Marker lines connect(document(), SIGNAL(contentsChange(int,int,int)), SLOT(cullMarkedLine(int,int,int))); //This assert is here because a bad build environment can cause this to fail. There is a note // in the Qt source that indicates an error should be output, but there is no such output. QTextObjectInterface *iface = qobject_cast(&markerFormatObject); if (!iface) { Q_ASSERT(iface); } document()->documentLayout()->registerHandler(IRCView::MarkerLine, &markerFormatObject); document()->documentLayout()->registerHandler(IRCView::RememberLine, &markerFormatObject); connect(this, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl))); connect( this, SIGNAL(highlighted(QString)), this, SLOT(highlightedSlot(QString)) ); setOpenLinks(false); setUndoRedoEnabled(0); document()->setDefaultStyleSheet("a.nick:link {text-decoration: none}"); setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); setFocusPolicy(Qt::ClickFocus); setReadOnly(true); viewport()->setCursor(Qt::ArrowCursor); setTextInteractionFlags(Qt::TextBrowserInteraction); viewport()->setMouseTracking(true); //HACK to workaround an issue with the QTextDocument //doing a relayout/scrollbar over and over resulting in 100% //proc usage. See bug 215256 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setContextMenuOptions(IrcContextMenus::ShowTitle | IrcContextMenus::ShowFindAction, true); } IRCView::~IRCView() { } void IRCView::increaseFontSize() { QFont newFont(Preferences::self()->customTextFont() ? Preferences::self()->textFont() : QFontDatabase::systemFont(QFontDatabase::GeneralFont)); ++m_fontSizeDelta; newFont.setPointSize(newFont.pointSize() + m_fontSizeDelta); setFont(newFont); } void IRCView::decreaseFontSize() { QFont newFont(Preferences::self()->customTextFont() ? Preferences::self()->textFont() : QFontDatabase::systemFont(QFontDatabase::GeneralFont)); --m_fontSizeDelta; newFont.setPointSize(newFont.pointSize() + m_fontSizeDelta); setFont(newFont); } void IRCView::resetFontSize() { QFont newFont(Preferences::self()->customTextFont() ? Preferences::self()->textFont() : QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_fontSizeDelta = 0; setFont(newFont); } void IRCView::setServer(Server* newServer) { if (m_server == newServer) return; m_server = newServer; } void IRCView::setChatWin(ChatWindow* chatWin) { m_chatWin = chatWin; } void IRCView::findText() { emit doSearch(); } void IRCView::findNextText() { emit doSearchNext(); } void IRCView::findPreviousText() { emit doSearchPrevious(); } bool IRCView::search(const QString& pattern, QTextDocument::FindFlags flags, bool fromCursor) { if (pattern.isEmpty()) return true; m_pattern = pattern; m_searchFlags = flags; if (!fromCursor) moveCursor(QTextCursor::End); else moveCursor(QTextCursor::StartOfWord); // Do this to that if possible the same position is kept when changing search options return searchNext(); } bool IRCView::searchNext(bool reversed) { QTextDocument::FindFlags flags = m_searchFlags; if(!reversed) flags |= QTextDocument::FindBackward; return find(m_pattern, flags); } class IrcViewMimeData : public QMimeData { public: IrcViewMimeData(const QTextDocumentFragment& _fragment): fragment(_fragment) {} QStringList formats() const Q_DECL_OVERRIDE; protected: QVariant retrieveData(const QString &mimeType, QVariant::Type type) const Q_DECL_OVERRIDE; private: mutable QTextDocumentFragment fragment; }; QStringList IrcViewMimeData::formats() const { if (!fragment.isEmpty()) return QStringList() << QString::fromLatin1("text/plain"); else return QMimeData::formats(); } QVariant IrcViewMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const { if (!fragment.isEmpty()) { IrcViewMimeData *that = const_cast(this); //Copy the text, skipping any QChar::ObjectReplacementCharacter QRegExp needle(QString("\\xFFFC\\n?")); that->setText(fragment.toPlainText().remove(needle)); fragment = QTextDocumentFragment(); } return QMimeData::retrieveData(mimeType, type); } QMimeData *IRCView::createMimeDataFromSelection() const { const QTextDocumentFragment fragment(textCursor()); return new IrcViewMimeData(fragment); } void IRCView::dragEnterEvent(QDragEnterEvent* e) { if (e->mimeData()->hasUrls()) e->acceptProposedAction(); else e->ignore(); } void IRCView::dragMoveEvent(QDragMoveEvent* e) { if (e->mimeData()->hasUrls()) e->accept(); else e->ignore(); } void IRCView::dropEvent(QDropEvent* e) { if (e->mimeData() && e->mimeData()->hasUrls()) emit urlsDropped(KUrlMimeData::urlsFromMimeData(e->mimeData(), KUrlMimeData::PreferLocalUrls)); } // Marker lines #define _S(x) #x << (x) QDebug operator<<(QDebug dbg, QTextBlockUserData *bd); QDebug operator<<(QDebug d, QTextFrame* feed); QDebug operator<<(QDebug d, QTextDocument* document); QDebug operator<<(QDebug d, QTextBlock b); // This object gets stuffed into the userData field of a text block. // Qt does not give us a way to track blocks, so we have to // rely on the destructor of this object to notify us that a // block we care about was removed from the document. This does not // prevent the first block bug from deleting the wrong block's data, // however that should not result in a crash. struct Burr: public QTextBlockUserData { Burr(IRCView* o, Burr* prev, QTextBlock b, int objFormat) : m_block(b), m_format(objFormat), m_prev(prev), m_next(nullptr), m_owner(o) { if (m_prev) m_prev->m_next = this; } ~Burr() { m_owner->blockDeleted(this); unlink(); } void unlink() { if (m_prev) m_prev->m_next = m_next; if (m_next) m_next->m_prev = m_prev; } QTextBlock m_block; int m_format; Burr* m_prev, *m_next; IRCView* m_owner; }; void IrcViewMarkerLine::drawObject(QPainter *painter, const QRectF &r, QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(format); QTextBlock block=doc->findBlock(posInDocument); QPen pen; Burr* b = dynamic_cast(block.userData()); Q_ASSERT(b); // remember kids, only YOU can makes this document support two user data types switch (b->m_format) { case IRCView::BlockIsMarker: pen.setColor(Preferences::self()->color(Preferences::ActionMessage)); break; case IRCView::BlockIsRemember: pen.setColor(Preferences::self()->color(Preferences::CommandMessage)); // pen.setStyle(Qt::DashDotDotLine); break; default: //nice color, eh? pen.setColor(Qt::cyan); } pen.setWidth(2); // FIXME this is a hardcoded value... painter->setPen(pen); qreal y = (r.top() + r.height() / 2); QLineF line(r.left(), y, r.right(), y); painter->drawLine(line); } QSizeF IrcViewMarkerLine::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(posInDocument); Q_UNUSED(format); QTextFrameFormat f=doc->rootFrame()->frameFormat(); qreal width = doc->pageSize().width()-(f.leftMargin()+f.rightMargin()); return QSizeF(width, 6); // FIXME this is a hardcoded value... } QTextCharFormat IRCView::getFormat(ObjectFormats x) { QTextCharFormat f; f.setObjectType(x); return f; } void IRCView::blockDeleted(Burr* b) //slot { Q_ASSERT(b); // this method only to be called from a ~Burr(); //tracking only the tail if (b == m_lastMarkerLine) m_lastMarkerLine = b->m_prev; if (b == m_rememberLine) m_rememberLine = nullptr; } void IRCView::cullMarkedLine(int, int, int) //slot { QTextBlock prime = document()->firstBlock(); if (prime.length() == 1 && document()->blockCount() == 1) //the entire document was wiped. was a signal such a burden? apparently.. wipeLineParagraphs(); } void IRCView::insertMarkerLine() //slot { //if the last line is already a marker of any kind, skip out if (lastBlockIsLine(BlockIsMarker)) return; //the code used to preserve the dirty bit status, but that was never affected by appendLine... //maybe i missed something appendLine(IRCView::MarkerLine); } void IRCView::insertRememberLine() //slot { m_rememberLineDirtyBit = true; // means we're going to append a remember line if some text gets inserted if (!Preferences::self()->automaticRememberLineOnlyOnTextChange()) { appendRememberLine(); } } void IRCView::cancelRememberLine() //slot { m_rememberLineDirtyBit = false; } bool IRCView::lastBlockIsLine(int select) { Burr *b = dynamic_cast(document()->lastBlock().userData()); int state = -1; if (b) state = b->m_format; if (select == -1) return (state == BlockIsRemember || state == BlockIsMarker); return state == select; } void IRCView::appendRememberLine() { //clear this now, so that doAppend doesn't double insert m_rememberLineDirtyBit = false; //if the last line is already the remember line, do nothing if (lastBlockIsLine(BlockIsRemember)) return; if (m_rememberLine) { QTextBlock rem = m_rememberLine->m_block; voidLineBlock(rem); if (m_rememberLine != nullptr) { // this probably means we had a block containing only 0x2029, so Scribe merged the userData/userState into the next m_rememberLine = nullptr; } } m_rememberLine = appendLine(IRCView::RememberLine); } void IRCView::voidLineBlock(QTextBlock rem) { QTextCursor c(rem); c.select(QTextCursor::BlockUnderCursor); c.removeSelectedText(); } void IRCView::clearLines() { while (hasLines()) { //IRCView::blockDeleted takes care of the pointers voidLineBlock(m_lastMarkerLine->m_block); }; } void IRCView::wipeLineParagraphs() { m_rememberLine = m_lastMarkerLine = nullptr; } bool IRCView::hasLines() { return m_lastMarkerLine != nullptr; } Burr* IRCView::appendLine(IRCView::ObjectFormats type) { ScrollBarPin barpin(verticalScrollBar()); SelectionPin selpin(this); QTextCursor cursor(document()); cursor.movePosition(QTextCursor::End); if (cursor.block().length() > 1) // this will be a 0x2029 cursor.insertBlock(); cursor.insertText(QString(QChar::ObjectReplacementCharacter), getFormat(type)); QTextBlock block = cursor.block(); Burr *b = new Burr(this, m_lastMarkerLine, block, type == MarkerLine? BlockIsMarker : BlockIsRemember); block.setUserData(b); m_lastMarkerLine = b; //TODO figure out what this is for cursor.setPosition(block.position()); return b; } // Other stuff void IRCView::updateAppearance() { QFont newFont(Preferences::self()->customTextFont() ? Preferences::self()->textFont() : QFontDatabase::systemFont(QFontDatabase::GeneralFont)); newFont.setPointSize(newFont.pointSize() + m_fontSizeDelta); setFont(newFont); setVerticalScrollBarPolicy(Preferences::self()->showIRCViewScrollBar() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); if (Preferences::self()->showBackgroundImage()) { QUrl url = Preferences::self()->backgroundImage(); if (url.isValid()) { viewport()->setStyleSheet("QWidget { background-image: url("+url.path()+"); background-attachment:fixed; }"); return; } } if (!viewport()->styleSheet().isEmpty()) viewport()->setStyleSheet(QString()); QPalette p; p.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground)); viewport()->setPalette(p); } bool IRCView::dateRtlDirection() { // Keep format synced with IRCView::timeStamp return QLocale().toString(QDate::currentDate(), QLocale::ShortFormat).isRightToLeft(); } // To minimize the use of bidi marks, for cases below, some bidi marks are // needed. // * left aligned lines in LTR locales, and // * right aligned lines in RTL locales // // First, check if the direction of the message is the same as the // direction of the timestamp, if not, then add a mark depending on // message's direction, so that timestamp don't be first strong character. // // If we have a LTR label, and the message is right-aligned, we prepend // it with LRM to look correct (check nickname case below), and then append // it with LRM also and then a RLM to preserve the direction of the // right-aligned line. // // Later, if the message is RTL, nicknames like "_nick]" will appear // as "[nick_". // First, add a LRM mark to make underscore on the left, next add the // nickname, and then another LRM mark. Since we use RTL/LTR count, the // message may start with a LTR word, and appear to the right of the // nickname. That's why we add a RLM mark before the nick to force it // appearing on left. QString IRCView::formatFinalLine(bool rtl, QString lineColor, QString label, QString nickLine, QString nickStar, QString text) { // Nick correctly displayed: <_nick]> QString line; // It's right-aligned under LTR locale, or left-aligned under RTL locale if (!rtl == dateRtlDirection()) line += (rtl ? RLM : LRM); if (!label.isEmpty()) { // Label correctly displayed: [_label.] if (rtl) line += LRM; // [.label_] -> [._label] line += "[%4]"; if (!label.isRightToLeft() == rtl) line += LRM + RLM; // [._label] -> [_label.] } line += "%1"; if (!nickStar.isEmpty()) // Used for [timeStamp] * nick action line += nickStar; if (rtl) line += LRM; // <[nick_> -> <[_nick]> line += nickLine; if (rtl) { line += LRM; // <[_nick]> -> <_nick]> // It might start with an English word, but it's RTL because of counting if (!text.isEmpty() && !text.isRightToLeft()) line += RLM; // ARABIC_TEXT <_nick]> Hi -> ARABIC_TEXT Hi <_nick]> } if (text.isEmpty()) line += ""; else line += " %3"; return line; } // Data insertion void IRCView::append(const QString& nick, const QString& message, const QHash &messageTags, const QString& label) { QString channelColor = Preferences::self()->color(Preferences::ChannelMessage).name(); m_tabNotification = Konversation::tnfNormal; QString nickLine = createNickLine(nick, channelColor); QChar::Direction dir; QString text(filter(message, channelColor, nick, true, true, false, &dir)); QString line; bool rtl = (dir == QChar::DirR); // Normal chat lines // [timestamp] chat message line = formatFinalLine(rtl, channelColor, label, nickLine, QString(), text); line = line.arg(timeStamp(messageTags, rtl), nick, text); if (!label.isEmpty()) { line = line.arg(label); } emit textToLog(QString("<%1>\t%2").arg(nick, message)); doAppend(line, rtl); } void IRCView::appendRaw(const QString& message, bool self) { QColor color = self ? Preferences::self()->color(Preferences::ChannelMessage) : Preferences::self()->color(Preferences::ServerMessage); m_tabNotification = Konversation::tnfNone; // Raw log is always left-aligned // [timestamp] << server line // If the timedate string is RTL, prepend a LTR mark to force the direction // to be LTR, as the datetime string is already returned as it's for a // left-aligned line. QString line; if (dateRtlDirection()) line += LRM; line += (timeStamp(QHash(), false) + " " + message + ""); doAppend(line, false, self); } void IRCView::appendLog(const QString & message) { QColor channelColor = Preferences::self()->color(Preferences::ChannelMessage); m_tabNotification = Konversation::tnfNone; // Log view is plain log files. // Direction will be depending on the logfile line direction. QString line("" + message + ""); doRawAppend(line, message.isRightToLeft()); } void IRCView::appendQuery(const QString& nick, const QString& message, const QHash &messageTags, bool inChannel) { QString queryColor=Preferences::self()->color(Preferences::QueryMessage).name(); m_tabNotification = Konversation::tnfPrivate; QString nickLine = createNickLine(nick, queryColor, true, inChannel); QString line; QChar::Direction dir; QString text(filter(message, queryColor, nick, true, true, false, &dir)); bool rtl = (dir == QChar::DirR); // Private chat lines // [timestamp] chat message line = formatFinalLine(rtl, queryColor, QString(), nickLine, QString(), text); line = line.arg(timeStamp(messageTags, rtl), nick, text); if (inChannel) { emit textToLog(QString("<-> %1>\t%2").arg(nick, message)); } else { emit textToLog(QString("<%1>\t%2").arg(nick, message)); } doAppend(line, rtl); } void IRCView::appendChannelAction(const QString& nick, const QString& message, const QHash &messageTags) { m_tabNotification = Konversation::tnfNormal; appendAction(nick, message, messageTags); } void IRCView::appendQueryAction(const QString& nick, const QString& message, const QHash &messageTags) { m_tabNotification = Konversation::tnfPrivate; appendAction(nick, message, messageTags); } void IRCView::appendAction(const QString& nick, const QString& message, const QHash &messageTags) { QString actionColor = Preferences::self()->color(Preferences::ActionMessage).name(); QString line; QString nickLine = createNickLine(nick, actionColor, false); if (message.isEmpty()) { // No text to check direction. Better to check last line, if it's RTL, // treat it as that. QTextCursor formatCursor(document()->lastBlock()); bool rtl = (formatCursor.blockFormat().alignment().testFlag(Qt::AlignRight)); line = formatFinalLine(rtl, actionColor, QString(), nickLine, QString(" * "), QString()); line = line.arg(timeStamp(messageTags, rtl), nick); emit textToLog(QString("\t * %1").arg(nick)); doAppend(line, rtl); } else { QChar::Direction dir; QString text(filter(message, actionColor, nick, true,true, false, &dir)); bool rtl = (dir == QChar::DirR); // Actions line // [timestamp] * nickname action line = formatFinalLine(rtl, actionColor, QString(), nickLine, QString(" * "), text); line = line.arg(timeStamp(messageTags, rtl), nick, text); emit textToLog(QString("\t * %1 %2").arg(nick, message)); doAppend(line, rtl); } } void IRCView::appendServerMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { QString serverColor = Preferences::self()->color(Preferences::ServerMessage).name(); m_tabNotification = Konversation::tnfControl; // Fixed width font option for MOTD QString fixed; if(Preferences::self()->fixedMOTD() && !m_fontDataBase.isFixedPitch(font().family())) { if(type == i18n("MOTD")) fixed=" face=\"" + QFontDatabase::systemFont(QFontDatabase::FixedFont).family() + "\""; } QString line; QChar::Direction dir; QString text(filter(message, serverColor, nullptr , true, parseURL, false, &dir)); // Server text may be translated strings. It's not user input: treat with first strong. bool rtl = text.isRightToLeft(); // It's right-aligned under LTR locale, or left-aligned under RTL locale if (!rtl == dateRtlDirection()) line += (rtl ? RLM : LRM); line += "%1 [%2]"; if (!rtl == type.isRightToLeft()) line += (rtl ? RLM : LRM); // [50 [ARABIC_TEXT users -> [ARABIC_TEXT] 50 users line += " %3"; line = line.arg(timeStamp(messageTags, rtl), type, text); emit textToLog(QString("%1\t%2").arg(type, message)); doAppend(line, rtl); } void IRCView::appendCommandMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL, bool self) { QString commandColor = Preferences::self()->color(Preferences::CommandMessage).name(); QString prefix="***"; m_tabNotification = Konversation::tnfControl; if(type == i18nc("Message type", "Join")) { prefix="-->"; parseURL=false; } else if(type == i18nc("Message type", "Part") || type == i18nc("Message type", "Quit")) { prefix="<--"; } prefix=prefix.toHtmlEscaped(); QString line; QChar::Direction dir; QString text(filter(message, commandColor, nullptr, true, parseURL, self, &dir)); // Commands are translated and contain LTR IP addresses. Treat with first strong. bool rtl = text.isRightToLeft(); // It's right-aligned under LTR locale, or left-aligned under RTL locale if (!rtl == dateRtlDirection()) line += (rtl ? RLM : LRM); line += "%1 %2 %3"; line = line.arg(timeStamp(messageTags, rtl), prefix, text); emit textToLog(QString("%1\t%2").arg(type, message)); doAppend(line, rtl, self); } void IRCView::appendBacklogMessage(const QString& firstColumn,const QString& rawMessage) { QString time; QString message = rawMessage; QString nick = firstColumn; QString backlogColor = Preferences::self()->color(Preferences::BacklogMessage).name(); m_tabNotification = Konversation::tnfNone; //The format in Chatwindow::logText is not configurable, so as long as nobody allows square brackets in a date/time format.... int eot = nick.lastIndexOf(' '); time = nick.left(eot); nick = nick.mid(eot+1); if(!nick.isEmpty() && !nick.startsWith('<') && !nick.startsWith('*')) { nick = '|' + nick + '|'; } // Nicks are in "" format so replace the "<>" nick.replace('<',"<"); nick.replace('>',">"); QString line; QChar::Direction dir; QString text(filter(message, backlogColor, nullptr, false, false, false, &dir)); bool rtl = nick.startsWith('|') ? text.isRightToLeft() : (dir == QChar::DirR); // It's right-aligned under LTR locale, or left-aligned under RTL locale if (!rtl == time.isRightToLeft()) line += (rtl ? RLM : LRM); line += ""; // Prepend and append timestamp's correct bidi mark if the time and text // directions are different. if (rtl == time.isRightToLeft()) line += "%1"; else line += (time.isRightToLeft() ? RLM+"%1"+RLM : LRM+"%1"+LRM); // Partially copied from IRCView::formatFinalLine if (rtl) { // Return back to the normal direction after setting mark if (!rtl == time.isRightToLeft()) line += (!time.isRightToLeft() ? RLM : LRM); line += LRM; // <[nick_> -> <[_nick]> } line += "%2"; if (rtl) { line += LRM; // <[_nick]> -> <_nick]> if (!text.isRightToLeft()) line += RLM; // ARABIC_TEXT <_nick]> Hi -> ARABIC_TEXT Hi <_nick]> } line += " %3"; line = line.arg(time, nick, text); doAppend(line, rtl); } void IRCView::doAppend(const QString& newLine, bool rtl, bool self) { if (m_rememberLineDirtyBit) appendRememberLine(); if (!self && m_chatWin) m_chatWin->activateTabNotification(m_tabNotification); int scrollMax = Preferences::self()->scrollbackMax(); if (scrollMax != 0) { //don't remove lines if the user has scrolled up to read old lines bool atBottom = (verticalScrollBar()->value() == verticalScrollBar()->maximum()); document()->setMaximumBlockCount(atBottom ? scrollMax : document()->maximumBlockCount() + 1); } doRawAppend(newLine, rtl); //FIXME: Disable auto-text for DCC Chats since we don't have a server to parse wildcards. if (!m_autoTextToSend.isEmpty() && m_server) { // replace placeholders in autoText QString sendText = m_server->parseWildcards(m_autoTextToSend,m_server->getNickname(), QString(), QString(), QString(), QString()); // avoid recursion due to signalling m_autoTextToSend.clear(); // send signal only now emit autoText(sendText); } else { m_autoTextToSend.clear(); } if (!m_lastStatusText.isEmpty()) emit clearStatusBarTempText(); } void IRCView::doRawAppend(const QString& newLine, bool rtl) { SelectionPin selpin(this); // HACK stop selection at end from growing QString line(newLine); line.remove('\n'); QTextBrowser::append(line); QTextCursor formatCursor(document()->lastBlock()); QTextBlockFormat format = formatCursor.blockFormat(); format.setAlignment(Qt::AlignAbsolute|(rtl ? Qt::AlignRight : Qt::AlignLeft)); formatCursor.setBlockFormat(format); } QString IRCView::timeStamp(QHash messageTags, bool rtl) { if(Preferences::self()->timestamping()) { QDateTime serverTime; if (messageTags.contains(QStringLiteral("time"))) // If it exists use the supplied server time. serverTime = QDateTime::fromString(messageTags[QStringLiteral("time")], Qt::ISODate).toLocalTime(); QTime time = serverTime.isValid() ? serverTime.time() : QTime::currentTime(); QString timeColor = Preferences::self()->color(Preferences::Time).name(); QString timeFormat = Preferences::self()->timestampFormat(); QString timeString; bool dateRtl = dateRtlDirection(); if(!Preferences::self()->showDate()) { timeString = QString(QLatin1String("[%1] ")).arg(time.toString(timeFormat)); } else { QDate date = serverTime.isValid() ? serverTime.date() : QDate::currentDate(); timeString = QString("[%1%2 %3%4] ") .arg((dateRtl==rtl) ? QString() : (dateRtl ? RLM : LRM), QLocale().toString(date, QLocale::ShortFormat), time.toString(timeFormat), (dateRtl==rtl) ? QString() : (!dateRtl ? RLM : LRM)); } return timeString; } return QString(); } QString IRCView::createNickLine(const QString& nick, const QString& defaultColor, bool encapsulateNick, bool privMsg) { QString nickLine ="%2"; QString nickColor; if (Preferences::self()->useColoredNicks()) { if (m_server) { if (nick != m_server->getNickname()) nickColor = Preferences::self()->nickColor(m_server->obtainNickInfo(nick)->getNickColor()).name(); else nickColor = Preferences::self()->nickColor(8).name(); } else if (m_chatWin->getType() == ChatWindow::DccChat) { - QString ownNick = static_cast(m_chatWin)->ownNick(); + QString ownNick = qobject_cast(m_chatWin)->ownNick(); if (nick != ownNick) nickColor = Preferences::self()->nickColor(Konversation::colorForNick(ownNick)).name(); else nickColor = Preferences::self()->nickColor(8).name(); } } else nickColor = defaultColor; nickLine = QLatin1String("") + nickLine + QLatin1String(""); if (Preferences::self()->useClickableNicks()) nickLine = "" + nickLine + ""; if (privMsg) nickLine.prepend(QLatin1String("-> ")); if(encapsulateNick) nickLine = QLatin1String("<") + nickLine + QLatin1String(">"); if(Preferences::self()->useBoldNicks()) nickLine = QLatin1String("") + nickLine + QLatin1String(""); return nickLine; } void IRCView::replaceDecoration(QString& line, char decoration, char replacement) { int pos; bool decorated = false; while((pos=line.indexOf(decoration))!=-1) { line.replace(pos,1,(decorated) ? QString("").arg(replacement) : QString("<%1>").arg(replacement)); decorated = !decorated; } } QString IRCView::filter(const QString& line, const QString& defaultColor, const QString& whoSent, bool doHighlight, bool parseURL, bool self, QChar::Direction* direction) { QString filteredLine(line); Application* konvApp = Application::instance(); //Since we can't turn off whitespace simplification withouteliminating text wrapping, // if the line starts with a space turn it into a non-breaking space. // (which magically turns back into a space on copy) if (filteredLine[0] == ' ') { filteredLine[0] = '\xA0'; } // TODO: Use QStyleSheet::escape() here // Replace all < with < filteredLine.replace('<', "\x0blt;"); // Replace all > with > filteredLine.replace('>', "\x0bgt;"); if (filteredLine.contains('\x07')) { if (Preferences::self()->beep()) { qApp->beep(); } //remove char after beep filteredLine.remove('\x07'); } filteredLine = ircTextToHtml(filteredLine, parseURL, defaultColor, whoSent, true, direction); // Highlight QString ownNick; if (m_server) { ownNick = m_server->getNickname(); } else if (m_chatWin->getType() == ChatWindow::DccChat) { - ownNick = static_cast(m_chatWin)->ownNick(); + ownNick = qobject_cast(m_chatWin)->ownNick(); } if(doHighlight && (whoSent != ownNick) && !self) { QString highlightColor; if (Preferences::self()->highlightNick() && line.toLower().contains(QRegExp("(^|[^\\d\\w])" + QRegExp::escape(ownNick.toLower()) + "([^\\d\\w]|$)"))) { // highlight current nickname highlightColor = Preferences::self()->highlightNickColor().name(); m_tabNotification = Konversation::tnfNick; } else { QList highlightList = Preferences::highlightList(); QListIterator it(highlightList); Highlight* highlight; QStringList highlightChatWindowList; bool patternFound = false; QStringList captures; while (it.hasNext()) { highlight = it.next(); highlightChatWindowList = highlight->getChatWindowList(); if (highlightChatWindowList.isEmpty() || highlightChatWindowList.contains(m_chatWin->getName(), Qt::CaseInsensitive)) { if (highlight->getRegExp()) { QRegExp needleReg(highlight->getPattern()); needleReg.setCaseSensitivity(Qt::CaseInsensitive); // highlight regexp in text patternFound = ((line.contains(needleReg)) || // highlight regexp in nickname (whoSent.contains(needleReg))); // remember captured patterns for later captures = needleReg.capturedTexts(); } else { QString needle = highlight->getPattern(); // highlight patterns in text patternFound = ((line.contains(needle, Qt::CaseInsensitive)) || // highlight patterns in nickname (whoSent.contains(needle, Qt::CaseInsensitive))); } if (patternFound) { break; } } } if (patternFound) { highlightColor = highlight->getColor().name(); m_highlightColor = highlightColor; if (highlight->getNotify()) { m_tabNotification = Konversation::tnfHighlight; if (Preferences::self()->highlightSoundsEnabled() && m_chatWin->notificationsEnabled()) { konvApp->sound()->play(highlight->getSoundURL()); } konvApp->notificationHandler()->highlight(m_chatWin, whoSent, line); } m_autoTextToSend = highlight->getAutoText(); // replace %0 - %9 in regex groups for (int capture = 0; capture < captures.count(); capture++) { m_autoTextToSend.replace(QString("%%1").arg(capture), captures[capture]); } m_autoTextToSend.remove(QRegExp("%[0-9]")); } } // apply found highlight color to line if (!highlightColor.isEmpty()) { filteredLine = QLatin1String("") + filteredLine + QLatin1String(""); } } else if (doHighlight && (whoSent == ownNick) && Preferences::self()->highlightOwnLines()) { // highlight own lines filteredLine = QLatin1String("highlightOwnLinesColor().name() + QLatin1String("\">") + filteredLine + QLatin1String(""); } filteredLine = Konversation::Emoticons::parseEmoticons(filteredLine); return filteredLine; } QString IRCView::ircTextToHtml(const QString& text, bool parseURL, const QString& defaultColor, const QString& whoSent, bool closeAllTags, QChar::Direction* direction) { TextHtmlData data; data.defaultColor = defaultColor; QString htmlText(text); bool allowColors = Preferences::self()->allowColorCodes(); QString linkColor = Preferences::self()->color(Preferences::Hyperlink).name(); unsigned int rtl_chars = 0; unsigned int ltr_chars = 0; QString fromNick; TextUrlData urlData; TextChannelData channelData; if (parseURL) { QString strippedText(removeIrcMarkup(htmlText)); urlData = extractUrlData(strippedText); if (!urlData.urlRanges.isEmpty()) { // we detected the urls on a clean richtext-char-less text // to make 100% sure we get the correct urls, but as a result // we have to map them back to the original url adjustUrlRanges(urlData.urlRanges, urlData.fixedUrls, htmlText, strippedText); //Only set fromNick if we actually have a url, //yes this is a ultra-minor-optimization if (whoSent.isEmpty()) fromNick = m_chatWin->getName(); else fromNick = whoSent; } channelData = extractChannelData(strippedText); adjustUrlRanges(channelData.channelRanges, channelData.fixedChannels , htmlText, strippedText); } else { // Change & to & to prevent html entities to do strange things to the text htmlText.replace('&', "&"); htmlText.replace("\x0b", "&"); } int linkPos = -1; int linkOffset = 0; bool doChannel = false; if (parseURL) { //get next recent channel or link pos if (!urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { if (urlData.urlRanges.first() < channelData.channelRanges.first()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else { doChannel = true; linkPos = channelData.channelRanges.first().first; } } else if (!urlData.urlRanges.isEmpty() && channelData.channelRanges.isEmpty()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else if (urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { doChannel = true; linkPos = channelData.channelRanges.first().first; } else { linkPos = -1; } } // Remember last char for pair of spaces situation, see default in switch (htmlText.at(pos)... QChar lastChar; int offset; for (int pos = 0; pos < htmlText.length(); ++pos) { //check for next relevant url or channel link to insert if (parseURL && pos == linkPos+linkOffset) { if (doChannel) { QString fixedChannel = channelData.fixedChannels.takeFirst(); const QPair& range = channelData.channelRanges.takeFirst(); QString oldChannel = htmlText.mid(pos, range.second); QString strippedChannel = removeIrcMarkup(oldChannel); QString colorCodes = extractColorCodes(oldChannel); QString link("%1%3%4%5"); link = link.arg(closeTags(&data), fixedChannel, strippedChannel, openTags(&data, 0), colorCodes); htmlText.replace(pos, oldChannel.length(), link); pos += link.length() - colorCodes.length() - 1; linkOffset += link.length() - oldChannel.length(); } else { QString fixedUrl = urlData.fixedUrls.takeFirst(); const QPair& range = urlData.urlRanges.takeFirst(); QString oldUrl = htmlText.mid(pos, range.second); QString strippedUrl = removeIrcMarkup(oldUrl); QString closeTagsString(closeTags(&data)); QString colorCodes = extractColorCodes(oldUrl); colorCodes = removeDuplicateCodes(colorCodes, &data, allowColors); QString link("%1%3%4%5"); link = link.arg(closeTagsString, fixedUrl, strippedUrl, openTags(&data, 0), colorCodes); htmlText.replace(pos, oldUrl.length(), link); //url catcher QMetaObject::invokeMethod(Application::instance(), "storeUrl", Qt::QueuedConnection, Q_ARG(QString, fromNick), Q_ARG(QString, fixedUrl), Q_ARG(QDateTime, QDateTime::currentDateTime())); pos += link.length() - colorCodes.length() - 1; linkOffset += link.length() - oldUrl.length(); } bool invalidNextLink = false; do { if (!urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { if (urlData.urlRanges.first() < channelData.channelRanges.first()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else { doChannel = true; linkPos = channelData.channelRanges.first().first; } } else if (!urlData.urlRanges.isEmpty() && channelData.channelRanges.isEmpty()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else if (urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { doChannel = true; linkPos = channelData.channelRanges.first().first; } else { linkPos = -1; } //for cases like "#www.some.url" we get first channel //and also url, the channel->clickable-channel replace we are //already after the url, so just forget it, as a clickable //channel is correct in this case if (linkPos > -1 && linkPos+linkOffset < pos) { invalidNextLink = true; if (doChannel) { channelData.channelRanges.removeFirst(); channelData.fixedChannels.removeFirst(); } else { urlData.urlRanges.removeFirst(); urlData.fixedUrls.removeFirst(); } } else { invalidNextLink = false; } } while (invalidNextLink); continue; } switch (htmlText.at(pos).toLatin1()) { case '\x02': //bold offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("b")); pos += offset -1; linkOffset += offset -1; break; case '\x1d': //italic offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("i")); pos += offset -1; linkOffset += offset -1; break; case '\x15': //mirc underline case '\x1f': //kvirc underline offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("u")); pos += offset -1; linkOffset += offset -1; break; case '\x13': //strikethru offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("s")); pos += offset -1; linkOffset += offset -1; break; case '\x03': //color { QString fgColor, bgColor; bool fgOK = true, bgOK = true; QString colorMatch(getColors(htmlText, pos, fgColor, bgColor, &fgOK, &bgOK)); if (!allowColors) { htmlText.remove(pos, colorMatch.length()); pos -= 1; linkOffset -= colorMatch.length(); break; } QString colorString; // check for color reset conditions //TODO check if \x11 \017 is really valid here if (colorMatch == QLatin1String("\x03") || colorMatch == QLatin1String("\x11") || (fgColor.isEmpty() && bgColor.isEmpty()) || (!fgOK && !bgOK)) { //in reverse mode, just reset both colors //color tags are already closed before the reverse start if (data.reverse) { data.lastFgColor.clear(); data.lastBgColor.clear(); } else { if (data.openHtmlTags.contains(QLatin1String("font")) && data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); data.lastBgColor.clear(); colorString += closeToTagString(&data, QLatin1String("font")); data.lastFgColor.clear(); } else if (data.openHtmlTags.contains("font")) { colorString += closeToTagString(&data, QLatin1String("font")); data.lastFgColor.clear(); } } htmlText.replace(pos, colorMatch.length(), colorString); pos += colorString.length() - 1; linkOffset += colorString.length() -colorMatch.length(); break; } if (!fgOK) { fgColor = defaultColor; } if (!bgOK) { bgColor = fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } // if we are in reverse mode, just remember the new colors if (data.reverse) { if (!fgColor.isEmpty()) { data.lastFgColor = fgColor; if (!bgColor.isEmpty()) { data.lastBgColor = bgColor; } } } // do we have a new fgColor? // NOTE: there is no new bgColor is there is no fgColor else if (!fgColor.isEmpty()) { if (data.openHtmlTags.contains(QLatin1String("font")) && data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); colorString += closeToTagString(&data, QLatin1String("font")); } else if (data.openHtmlTags.contains(QLatin1String("font"))) { colorString += closeToTagString(&data, QLatin1String("font")); } data.lastFgColor = fgColor; if (!bgColor.isEmpty()) data.lastBgColor = bgColor; if (!data.lastFgColor.isEmpty()) { colorString += fontColorOpenTag(data.lastFgColor); data.openHtmlTags.append(QLatin1String("font")); if (!data.lastBgColor.isEmpty()) { colorString += spanColorOpenTag(data.lastBgColor); data.openHtmlTags.append(QLatin1String("span")); } } } htmlText.replace(pos, colorMatch.length(), colorString); pos += colorString.length() - 1; linkOffset += colorString.length() -colorMatch.length(); break; } break; case '\x0f': //reset to default { QString closeText; while (!data.openHtmlTags.isEmpty()) { closeText += QLatin1String("'); } data.lastBgColor.clear(); data.lastFgColor.clear(); data.reverse = false; htmlText.replace(pos, 1, closeText); pos += closeText.length() - 1; linkOffset += closeText.length() - 1; } break; case '\x16': //reverse { // treat inverse as color and block it if colors are not allowed if (!allowColors) { htmlText.remove(pos, 1); pos -= 1; linkOffset -= 1; break; } QString colorString; // close current color strings and open reverse tags if (!data.reverse) { if (data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); } if (data.openHtmlTags.contains(QLatin1String("font"))) { colorString += closeToTagString(&data, QLatin1String("font")); } data.reverse = true; colorString += fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); data.openHtmlTags.append(QLatin1String("font")); colorString += spanColorOpenTag(defaultColor); data.openHtmlTags.append(QLatin1String("span")); } else { // if reset reverse, close reverse and set old fore- and // back-groundcolor if set in data colorString += closeToTagString(&data, QLatin1String("span")); colorString += closeToTagString(&data, QLatin1String("font")); data.reverse = false; if (!data.lastFgColor.isEmpty()) { colorString += fontColorOpenTag(data.lastFgColor); data.openHtmlTags.append(QLatin1String("font")); if (!data.lastBgColor.isEmpty()) { colorString += spanColorOpenTag(data.lastBgColor); data.openHtmlTags.append(QLatin1String("span")); } } } htmlText.replace(pos, 1, colorString); pos += colorString.length() -1; linkOffset += colorString.length() -1; } break; default: { const QChar& dirChar = htmlText.at(pos); // Replace pairs of spaces with " " to preserve some semblance of text wrapping //filteredLine.replace(" ", " \xA0"); // This used to work like above. But just for normal text like "test test" // It got replaced as "test \xA0 \xA0test" and QTextEdit showed 4 spaces. // In case of color/italic/bold codes we don't necessary get a real pair of spaces // just "test test" and QTextEdit shows it as 1 space. // Now if we remember the last char, to ignore html tags, and check if current and last ones are spaces // we replace the current one with \xA0 (a forced space) and get // "test \xA0 \xA0test", which QTextEdit correctly shows as 4 spaces. //NOTE: replacing all spaces with forced spaces will break text wrapping if (dirChar == ' ' && !lastChar.isNull() && lastChar == ' ') { htmlText[pos] = '\xA0'; lastChar = '\xA0'; } else { lastChar = dirChar; } if (!(dirChar.isNumber() || dirChar.isSymbol() || dirChar.isSpace() || dirChar.isPunct() || dirChar.isMark())) { switch(dirChar.direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: ltr_chars++; break; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: rtl_chars++; break; default: break; } } } } } if (direction) { // in case we found no right or left direction chars both // values are 0, but rtl_chars > ltr_chars is still false and QChar::DirL // is returned as default. if (rtl_chars > ltr_chars) *direction = QChar::DirR; else *direction = QChar::DirL; } if (parseURL) { // Change & to & to prevent html entities to do strange things to the text htmlText.replace('&', "&"); htmlText.replace("\x0b", "&"); } if (closeAllTags) { htmlText += closeTags(&data); } return htmlText; } int IRCView::defaultHtmlReplace(QString& htmlText, TextHtmlData* data, int pos, const QString& tag) { QString replace; if (data->openHtmlTags.contains(tag)) { replace = closeToTagString(data, tag); } else { data->openHtmlTags.append(tag); replace = QLatin1Char('<') + tag + QLatin1Char('>'); } htmlText.replace(pos, 1, replace); return replace.length(); } QString IRCView::closeToTagString(TextHtmlData* data, const QString& _tag) { QString ret; QString tag; int i = data->openHtmlTags.count() - 1; //close all tags to _tag for ( ; i >= 0 ; --i) { tag = data->openHtmlTags.at(i); ret += QLatin1String("'); if (tag == _tag) { data->openHtmlTags.removeAt(i); break; } } // reopen relevant tags if (i > -1) ret += openTags(data, i); return ret; } QString IRCView::openTags(TextHtmlData* data, int from) { QString ret, tag; int i = from > -1 ? from : 0; for ( ; i < data->openHtmlTags.count(); ++i) { tag = data->openHtmlTags.at(i); if (tag == QLatin1String("font")) { if (data->reverse) { ret += fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } else { ret += fontColorOpenTag(data->lastFgColor); } } else if (tag == QLatin1String("span")) { if (data->reverse) { ret += spanColorOpenTag(data->defaultColor); } else { ret += spanColorOpenTag(data->lastBgColor); } } else { ret += QLatin1Char('<') + tag + QLatin1Char('>'); } } return ret; } QString IRCView::closeTags(TextHtmlData* data) { QString ret; QListIterator< QString > i(data->openHtmlTags); i.toBack(); while (i.hasPrevious()) { ret += QLatin1String("'); } return ret; } QString IRCView::fontColorOpenTag(const QString& fgColor) { return QLatin1String(""); } QString IRCView::spanColorOpenTag(const QString& bgColor) { return QLatin1String(""); } QString IRCView::removeDuplicateCodes(const QString& codes, TextHtmlData* data, bool allowColors) { int pos = 0; QString ret; while (pos < codes.length()) { switch (codes.at(pos).toLatin1()) { case '\x02': //bold defaultRemoveDuplicateHandling(data, QLatin1String("b")); ++pos; break; case '\x1d': //italic defaultRemoveDuplicateHandling(data, QLatin1String("i")); ++pos; break; case '\x15': //mirc underline case '\x1f': //kvirc underline defaultRemoveDuplicateHandling(data, QLatin1String("u")); ++pos; break; case '\x13': //strikethru defaultRemoveDuplicateHandling(data, QLatin1String("s")); ++pos; break; case '\x0f': //reset to default data->openHtmlTags.clear(); data->lastBgColor.clear(); data->lastFgColor.clear(); data->reverse = false; ++pos; break; case '\x16': //reverse if (!allowColors) { pos += 1; continue; } if (data->reverse) { data->openHtmlTags.removeOne(QLatin1String("span")); data->openHtmlTags.removeOne(QLatin1String("font")); data->reverse = false; if (!data->lastFgColor.isEmpty()) { data->openHtmlTags.append(QLatin1String("font")); if (!data->lastBgColor.isEmpty()) { data->openHtmlTags.append(QLatin1String("span")); } } } else { data->openHtmlTags.removeOne(QLatin1String("span")); data->openHtmlTags.removeOne(QLatin1String("font")); data->reverse = true; data->openHtmlTags.append(QLatin1String("font")); data->openHtmlTags.append(QLatin1String("span")); } ++pos; break; case '\x03': //color { QString fgColor, bgColor; bool fgOK = true, bgOK = true; QString colorMatch(getColors(codes, pos, fgColor, bgColor, &fgOK, &bgOK)); if (!allowColors) { pos += colorMatch.length(); continue; } // check for color reset conditions //TODO check if \x11 \017 is really valid here if (colorMatch == QLatin1String("\x03") || colorMatch == QLatin1String("\x11") || (fgColor.isEmpty() && bgColor.isEmpty()) || (!fgOK && !bgOK)) { if (!data->lastBgColor.isEmpty()) { data->lastBgColor.clear(); data->openHtmlTags.removeOne(QLatin1String("span")); } if (!data->lastFgColor.isEmpty()) { data->lastFgColor.clear(); data->openHtmlTags.removeOne(QLatin1String("font")); } pos += colorMatch.length(); break; } if (!fgOK) { fgColor = data->defaultColor; } if (!bgOK) { bgColor = fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } if (!fgColor.isEmpty()) { data->lastFgColor = fgColor; data->openHtmlTags.append(QLatin1String("font")); if (!bgColor.isEmpty()) { data->lastBgColor = bgColor; data->openHtmlTags.append(QLatin1String("span")); } } pos += colorMatch.length(); } break; default: // qDebug() << "unsupported duplicate code:" << QString::number(codes.at(pos).toLatin1(), 16); ret += codes.at(pos); ++pos; } } return ret; } void IRCView::defaultRemoveDuplicateHandling(TextHtmlData* data, const QString& tag) { if (data->openHtmlTags.contains(tag)) { data->openHtmlTags.removeOne(tag); } else { data->openHtmlTags.append(tag); } } void IRCView::adjustUrlRanges(QList< QPair >& urlRanges, const QStringList& fixedUrls, QString& richtext, const QString& strippedText) { Q_UNUSED(fixedUrls); QRegExp ircRichtextRegExp(colorRegExp); int start = 0, j; int i = 0; QString url; int htmlTextLength = richtext.length(), urlCount = urlRanges.count(); for (int x = 0; x < urlCount; ++x) { if (x == 0) i = urlRanges.first().first; j = 0; const QPair& range = urlRanges.at(x); url = strippedText.mid(range.first, range.second); for ( ; i < htmlTextLength; ++i) { if (richtext.at(i) == url.at(j)) { if (j == 0) start = i; ++j; if (j == url.length()) { urlRanges[x].first = start; urlRanges[x].second = i - start + 1; break; } } else if (ircRichtextRegExp.exactMatch(richtext.at(i))) { ircRichtextRegExp.indexIn(richtext, i); i += ircRichtextRegExp.matchedLength() - 1; } else { j = 0; } } } } QString IRCView::getColors(const QString& text, int start, QString& _fgColor, QString& _bgColor, bool* fgValueOK, bool* bgValueOK) { QRegExp ircColorRegExp("(\003([0-9][0-9]|[0-9]|)(,([0-9][0-9]|[0-9]|)|,|)|\017)"); if (ircColorRegExp.indexIn(text,start) == -1) return QString(); QString ret(ircColorRegExp.cap(0)); QString fgColor(ircColorRegExp.cap(2)), bgColor(ircColorRegExp.cap(4)); if (!fgColor.isEmpty()) { int foregroundColor = fgColor.toInt(); if (foregroundColor > -1 && foregroundColor < 16) { _fgColor = Preferences::self()->ircColorCode(foregroundColor).name(); if (fgValueOK) *fgValueOK = true; } else { if (fgValueOK) *fgValueOK = false; } } else { if (fgValueOK) *fgValueOK = true; } if (!bgColor.isEmpty()) { int backgroundColor = bgColor.toInt(); if (backgroundColor > -1 && backgroundColor < 16) { _bgColor = Preferences::self()->ircColorCode(backgroundColor).name(); if (bgValueOK) *bgValueOK = true; } else { if (bgValueOK) *bgValueOK = false; } } else { if (bgValueOK) *bgValueOK = true; } return ret; } void IRCView::resizeEvent(QResizeEvent *event) { ScrollBarPin b(verticalScrollBar()); QTextBrowser::resizeEvent(event); } void IRCView::mouseMoveEvent(QMouseEvent* ev) { if (m_mousePressedOnUrl && (m_mousePressPosition - ev->pos()).manhattanLength() > QApplication::startDragDistance()) { m_mousePressedOnUrl = false; QTextCursor textCursor = this->textCursor(); textCursor.clearSelection(); setTextCursor(textCursor); QPointer drag = new QDrag(this); QMimeData* mimeData = new QMimeData; QUrl url(m_dragUrl); mimeData->setUrls(QList() << url); drag->setMimeData(mimeData); QPixmap pixmap = KIO::pixmapForUrl(url, 0, KIconLoader::Desktop, KIconLoader::SizeMedium); drag->setPixmap(pixmap); drag->exec(); return; } else { // Store the url here instead of in highlightedSlot as the link given there is decoded. m_urlToCopy = anchorAt(ev->pos()); } QTextBrowser::mouseMoveEvent(ev); } void IRCView::mousePressEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { m_dragUrl = anchorAt(ev->pos()); if (!m_dragUrl.isEmpty() && Konversation::isUrl(m_dragUrl)) { m_mousePressedOnUrl = true; m_mousePressPosition = ev->pos(); } } QTextBrowser::mousePressEvent(ev); } void IRCView::wheelEvent(QWheelEvent *ev) { if(ev->modifiers()==Qt::ControlModifier) { if(ev->delta() < 0) decreaseFontSize(); if(ev->delta() > 0) increaseFontSize(); } QTextBrowser::wheelEvent(ev); } void IRCView::mouseReleaseEvent(QMouseEvent *ev) { if (ev->button() == Qt::LeftButton) { m_mousePressedOnUrl = false; } else if (ev->button() == Qt::MidButton) { if (m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) { // The QUrl magic is what QTextBrowser's anchorClicked() does internally; // we copy it here for consistent behavior between left and middle clicks. openLink(QUrl::fromEncoded(m_urlToCopy.toUtf8())); // krazy:exclude=qclasses return; } else { emit textPasted(true); return; } } QTextBrowser::mouseReleaseEvent(ev); } void IRCView::keyPressEvent(QKeyEvent* ev) { const int key = ev->key() | ev->modifiers(); if (KStandardShortcut::paste().contains(key)) { emit textPasted(false); ev->accept(); return; } QTextBrowser::keyPressEvent(ev); } void IRCView::anchorClicked(const QUrl& url) { openLink(url); } void IRCView::openLink(const QUrl& url) { QString link(url.toString()); // HACK Replace " " with %20 for channelnames, NOTE there can't be 2 channelnames in one link link = link.replace (' ', "%20"); // HACK Handle pipe as toString doesn't seem to decode that correctly link = link.replace ("%7C", "|"); // HACK Handle ` as toString doesn't seem to decode that correctly link = link.replace ("%60", "`"); if (!link.isEmpty() && !link.startsWith('#')) Application::openUrl(url.toEncoded()); //FIXME: Don't do channel links in DCC Chats to begin with since they don't have a server. else if (link.startsWith(QLatin1String("##")) && m_server && m_server->isConnected()) { m_server->sendJoinCommand(link.mid(1)); } //FIXME: Don't do user links in DCC Chats to begin with since they don't have a server. else if (link.startsWith('#') && m_server && m_server->isConnected()) { QString recipient(link); recipient.remove('#'); NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient); m_server->addQuery(nickInfo, true /*we initiated*/); } } void IRCView::highlightedSlot(const QString& /*_link*/) { QString link = m_urlToCopy; // HACK Replace " " with %20 for channelnames, NOTE there can't be 2 channelnames in one link link = link.replace (' ', "%20"); //we just saw this a second ago. no need to reemit. if (link == m_lastStatusText && !link.isEmpty()) return; if (link.isEmpty()) { if (!m_lastStatusText.isEmpty()) { emit clearStatusBarTempText(); m_lastStatusText.clear(); } } else { m_lastStatusText = link; } if (!link.startsWith(QLatin1Char('#'))) { m_isOnNick = false; m_isOnChannel = false; if (!link.isEmpty()) { //link therefore != m_lastStatusText so emit with this new text emit setStatusBarTempText(link); } if (link.isEmpty() && m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) setContextMenuOptions(IrcContextMenus::ShowLinkActions, false); else if (!link.isEmpty() && !m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) setContextMenuOptions(IrcContextMenus::ShowLinkActions, true); } else if (link.startsWith(QLatin1Char('#')) && !link.startsWith(QLatin1String("##"))) { m_currentNick = link.mid(1); m_isOnNick = true; emit setStatusBarTempText(i18n("Open a query with %1", m_currentNick)); } else { // link.startsWith("##") m_currentChannel = link.mid(1); m_isOnChannel = true; emit setStatusBarTempText(i18n("Join the channel %1", m_currentChannel)); } } void IRCView::setContextMenuOptions(IrcContextMenus::MenuOptions options, bool on) { if (on) m_contextMenuOptions |= options; else m_contextMenuOptions &= ~options; } void IRCView::contextMenuEvent(QContextMenuEvent* ev) { // Consider the following scenario: (1) context menu opened, (2) mouse // pointer moved, (3) mouse button clicked to dismiss menu, (4) mouse // button clicked to reopen context menu. In this scenario, if there is // no mouse movement between steps (3) and (4), highlighted() is never // emitted, and the data we use here to display the correct context menu // is outdated. Thus what we're going to do here is post a fake mouse // move event using the context menu event coordinate, forcing an update // just before we display the context menu. QMouseEvent fake(QEvent::MouseMove, ev->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); mouseMoveEvent(&fake); if (m_isOnChannel && m_server) { IrcContextMenus::channelMenu(ev->globalPos(), m_server, m_currentChannel); m_isOnChannel = false; return; } if (m_isOnNick && m_server) { IrcContextMenus::nickMenu(ev->globalPos(), m_contextMenuOptions, m_server, QStringList() << m_currentNick, m_chatWin->getName()); m_currentNick.clear(); m_isOnNick = false; return; } int contextMenuActionId = IrcContextMenus::textMenu(ev->globalPos(), m_contextMenuOptions, m_server, textCursor().selectedText(), m_urlToCopy, m_contextMenuOptions.testFlag(IrcContextMenus::ShowNickActions) ? m_chatWin->getName() : QString()); switch (contextMenuActionId) { case -1: break; case IrcContextMenus::TextCopy: copy(); break; case IrcContextMenus::TextSelectAll: selectAll(); break; default: if (m_contextMenuOptions.testFlag(IrcContextMenus::ShowNickActions)) { IrcContextMenus::processNickAction(contextMenuActionId, m_server, QStringList() << m_chatWin->getName(), m_contextMenuOptions.testFlag(IrcContextMenus::ShowChannelActions) ? m_chatWin->getName() : QString()); } break; } } // For more information about these RTFM // http://www.unicode.org/reports/tr9/ // http://www.w3.org/TR/unicode-xml/ QChar IRCView::LRM = (ushort)0x200e; // Right-to-Left Mark QChar IRCView::RLM = (ushort)0x200f; // Left-to-Right Mark QChar IRCView::LRE = (ushort)0x202a; // Left-to-Right Embedding QChar IRCView::RLE = (ushort)0x202b; // Right-to-Left Embedding QChar IRCView::RLO = (ushort)0x202e; // Right-to-Left Override QChar IRCView::LRO = (ushort)0x202d; // Left-to-Right Override QChar IRCView::PDF = (ushort)0x202c; // Previously Defined Format QChar::Direction IRCView::basicDirection(const QString& string) { // The following code decides between LTR or RTL direction for // a line based on the amount of each type of characters pre- // sent. It does so by counting, but stops when one of the two // counters becomes higher than half of the string length to // avoid unnecessary work. unsigned int pos = 0; unsigned int rtl_chars = 0; unsigned int ltr_chars = 0; unsigned int str_len = string.length(); unsigned int str_half_len = str_len/2; for(pos=0; pos < str_len; ++pos) { if (!(string[pos].isNumber() || string[pos].isSymbol() || string[pos].isSpace() || string[pos].isPunct() || string[pos].isMark())) { switch(string[pos].direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: ltr_chars++; break; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: rtl_chars++; break; default: break; } } if (ltr_chars > str_half_len) return QChar::DirL; else if (rtl_chars > str_half_len) return QChar::DirR; } if (rtl_chars > ltr_chars) return QChar::DirR; else return QChar::DirL; } #define dS d.space() #define dN d.nospace() QDebug operator<<(QDebug d, QTextBlockUserData *bd) { Burr* b = dynamic_cast(bd); if (b) { dN; d << "("; d << (void*)(b) << ", format=" << b->m_format << ", blockNumber=" << b->m_block.blockNumber() << " p,n=" << (void*)b->m_prev << ", " << (void*)b->m_next; d << ")"; } else if (bd) dN << "(UNKNOWN! " << (void*)bd << ")"; else d << "(none)"; return d.space(); } QDebug operator<<(QDebug d, QTextFrame* feed) { if (feed) { d << "\nDumping frame..."; dN << hex << (void*)feed << dec; QTextFrame::iterator it = feed->begin(); if (it.currentFrame() == feed) dS << "loop!" << endl; dS << "position" << feed->firstPosition() << feed->lastPosition(); dN << "parentFrame=" << (void*)feed->parentFrame(); dS; while (!it.atEnd()) { //d << "spin"; QTextFrame *frame = it.currentFrame(); if (!frame) // this is a block { //d<<"dumping blocks:"; QTextBlock b = it.currentBlock(); //d << "block" << b.position() << b.length(); d << endl << b; } else if (frame != feed) { d << frame; } ++it; }; d << "\n...done.\n"; } else d << "No frame to dump."; return d; } QDebug operator<<(QDebug d, QTextDocument* document) { d << "====================================================================================================================================================================="; if (document) d << document->rootFrame(); return d; } QDebug operator<<(QDebug d, QTextBlock b) { QTextBlock::Iterator it = b.begin(); int fragCount = 0; d << "blockNumber" << b.blockNumber(); d << "position" << b.position(); d << "length" << b.length(); dN << "firstChar 0x" << hex << b.document()->characterAt(b.position()).unicode() << dec; if (b.length() == 2) dN << " second 0x" << hex << b.document()->characterAt(b.position()+1).unicode() << dec; dS << "userState" << b.userState(); dN << "userData " << (void*)b.userData(); //dS << "text" << b.text(); dS << endl; if (b.userData()) d << b.userData(); for (it = b.begin(); !(it.atEnd()); ++it) { QTextFragment f = it.fragment(); if (f.isValid()) { fragCount++; //d << "frag" << fragCount << _S(f.position()) << _S(f.length()); } } d << _S(fragCount); return d; } diff --git a/src/viewer/logfilereader.cpp b/src/viewer/logfilereader.cpp index b2b061e0..460841eb 100644 --- a/src/viewer/logfilereader.cpp +++ b/src/viewer/logfilereader.cpp @@ -1,199 +1,199 @@ /* 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 content of a log file begin: Fri Dec 5 2003 copyright: (C) 2003 by Dario Abatianni email: eisfuchs@tigress.com */ #include "logfilereader.h" #include "application.h" #include "ircview.h" #include "ircviewbox.h" #include #include #include #include #include #include #include #include #include #include #include #include LogfileReader::LogfileReader(QWidget* parent, const QString& log, const QString& caption) : ChatWindow(parent) { setType(ChatWindow::LogFileReader); setName(i18n("Logfile of %1", caption)); fileName = log; setSpacing(0); toolBar = new KToolBar(this, true, true); toolBar->setObjectName("logfile_toolbar"); toolBar->addAction(QIcon::fromTheme("document-save-as"), i18n("Save As..."), this, SLOT(saveLog())); toolBar->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload"), this, SLOT(updateView())); toolBar->addAction(QIcon::fromTheme("edit-delete"), i18n("Clear Logfile"), this, SLOT(clearLog())); toolBar->addWidget(new QLabel(i18n("Show last:"),toolBar)); sizeSpin = new QSpinBox(toolBar); sizeSpin->setMinimum(10); sizeSpin->setMaximum(1000); sizeSpin->setSingleStep(10); sizeSpin->setObjectName("logfile_size_spinbox"); sizeSpin->setWhatsThis(i18n("Use this box to set the maximum size of the log file. This setting does not take effect until you restart Konversation. Each log file may have a separate setting.")); sizeSpin->setValue(Preferences::self()->logfileBufferSize()); sizeSpin->setSuffix(i18n(" KB")); sizeSpin->installEventFilter(this); toolBar->addWidget(sizeSpin); connect(sizeSpin, static_cast(&QSpinBox::valueChanged), this, &LogfileReader::storeBufferSize); IRCViewBox* ircBox = new IRCViewBox(this); setTextView(ircBox->ircView()); getTextView()->setWhatsThis(i18n("The messages in the log file are displayed here. The oldest messages are at the top and the most recent are at the bottom.")); updateView(); ircBox->ircView()->setFocusPolicy(Qt::StrongFocus); setFocusPolicy(Qt::StrongFocus); setFocusProxy(ircBox->ircView()); updateAppearance(); connect(getTextView(), SIGNAL(gotFocus()), getTextView(), SLOT(setFocus())); } LogfileReader::~LogfileReader() { } bool LogfileReader::eventFilter(QObject* /* watched */, QEvent* e) { if (e->type() == QEvent::KeyPress) { - QKeyEvent* ke = static_cast(e); + QKeyEvent* ke = dynamic_cast(e); if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { updateView(); return true; } else return false; } return false; } void LogfileReader::storeBufferSize(int kb) { Preferences::self()->setLogfileBufferSize(kb); } void LogfileReader::updateView() { // get maximum size of logfile to display qint64 pos = Q_INT64_C(1024) * sizeSpin->value(); getTextView()->clear(); QFile file(fileName); if(file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); stream.setAutoDetectUnicode(true); // Set file pointer to bytes from the end if(stream.device()->size() > pos) { stream.device()->seek(stream.device()->size() - pos); } // Skip first line, since it may be incomplete // NOTE: we always skip the first line(in the log), but the // first line is just a '\n', so it's ok stream.readLine(); QString str; while(!stream.atEnd()) { str = stream.readLine().toHtmlEscaped(); getTextView()->appendLog(str); } stream.setDevice(nullptr); file.close(); } } void LogfileReader::clearLog() { if(KMessageBox::warningContinueCancel(this, i18n("Do you really want to permanently discard all log information of this file?"), i18n("Clear Logfile"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), "ClearLogfileQuestion")==KMessageBox::Continue) { QFile::remove(fileName); updateView(); } } void LogfileReader::saveLog() { KMessageBox::information(this, i18n("Note: By saving the logfile you will save all data in the file, not only the part you can see in this viewer."), i18n("Save Logfile"), "SaveLogfileNote"); QUrl logUrl = QUrl::fromLocalFile(fileName); QUrl destination = QFileDialog::getSaveFileUrl(this, i18n("Choose Destination Folder"), logUrl); if(!destination.isEmpty()) { KIO::Job* job = KIO::copy(logUrl, destination); connect(job, &KIO::Job::result, this, &LogfileReader::copyResult); } } void LogfileReader::copyResult(KJob* job) { if(job->error()) job->uiDelegate()->showErrorMessage(); job->deleteLater(); } void LogfileReader::closeLog() { delete this; } void LogfileReader::childAdjustFocus() { getTextView()->setFocus(); } int LogfileReader::margin() { return style()->pixelMetric(QStyle::PM_LayoutLeftMargin); } int LogfileReader::spacing() { return style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal); } bool LogfileReader::searchView() { return true; } diff --git a/src/viewer/topichistoryview.cpp b/src/viewer/topichistoryview.cpp index ac49284c..76823029 100644 --- a/src/viewer/topichistoryview.cpp +++ b/src/viewer/topichistoryview.cpp @@ -1,311 +1,311 @@ /* 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) 2012 Eike Hein */ #include "topichistoryview.h" #include "application.h" #include "irccontextmenus.h" #include "topichistorymodel.h" #include #define MARGIN 2 TopicHistorySortfilterProxyModel::TopicHistorySortfilterProxyModel(QObject* parent) : KCategorizedSortFilterProxyModel(parent) { setCategorizedModel(true); } TopicHistorySortfilterProxyModel::~TopicHistorySortfilterProxyModel() { } QVariant TopicHistorySortfilterProxyModel::data(const QModelIndex& index, int role) const { if (role == KCategorizedSortFilterProxyModel::CategoryDisplayRole) { const QModelIndex& sourceIndex = mapToSource(index); const QString& author = sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 1)).toString(); const QString& timestamp = sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 2)).toString(); return i18nc("%1 is a timestamp, %2 is the author's name", "On %1 by %2", timestamp, author); } else if (role == KCategorizedSortFilterProxyModel::CategorySortRole) { const QModelIndex& sourceIndex = mapToSource(index); return sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 2)).toDateTime().toTime_t(); } return KCategorizedSortFilterProxyModel::data(index, role); } void TopicHistorySortfilterProxyModel::setSourceModel(QAbstractItemModel* model) { if (sourceModel()) disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); KCategorizedSortFilterProxyModel::setSourceModel(model); connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); } bool TopicHistorySortfilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const { Q_UNUSED(source_parent); return (source_column == 0); } void TopicHistorySortfilterProxyModel::sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); emit layoutAboutToBeChanged(); emit layoutChanged(); } TopicHistoryLabel::TopicHistoryLabel(QWidget* parent) : KTextEdit(parent) { viewport()->setAutoFillBackground(false); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameStyle(QFrame::NoFrame); document()->setDocumentMargin(2); setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); setAcceptRichText(false); setReadOnly(true); setTextSelectable(false); } TopicHistoryLabel::~TopicHistoryLabel() { } void TopicHistoryLabel::setTextSelectable(bool selectable) { setAttribute(Qt::WA_TransparentForMouseEvents, !selectable); setTextInteractionFlags(selectable ? Qt::TextBrowserInteraction : Qt::NoTextInteraction); if (!selectable) setTextCursor(QTextCursor()); } TopicHistoryItemDelegate::TopicHistoryItemDelegate(QAbstractItemView* itemView, QObject* parent) : KWidgetItemDelegate(itemView, parent) { m_hiddenLabel = new TopicHistoryLabel(itemView); m_hiddenLabel->setFixedHeight(0); m_hiddenLabel->lower(); m_shownBefore = false; itemView->installEventFilter(this); } TopicHistoryItemDelegate::~TopicHistoryItemDelegate() { } bool TopicHistoryItemDelegate::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched); // NOTE: QTextEdit needs to have been shown at least once (and while its // parents are shown, too) before it starts to calculate the document sizes // we need in sizeHint(). if (!m_shownBefore && event->type() == QEvent::Show && !event->spontaneous()) { m_hiddenLabel->show(); m_hiddenLabel->hide(); } return false; } QList TopicHistoryItemDelegate::createItemWidgets(const QModelIndex& index) const { Q_UNUSED(index) QList widgets; TopicHistoryLabel* label = new TopicHistoryLabel(); - connect(static_cast(itemView()), SIGNAL(textSelectableChanged(bool)), + connect(qobject_cast(itemView()), SIGNAL(textSelectableChanged(bool)), label, SLOT(setTextSelectable(bool))); widgets << label; return widgets; } void TopicHistoryItemDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem& option, const QPersistentModelIndex& index) const { if (widgets.isEmpty()) return; - TopicHistoryView* historyView = static_cast(itemView()); - TopicHistoryLabel* label = static_cast(widgets[0]); + TopicHistoryView* historyView = qobject_cast(itemView()); + TopicHistoryLabel* label = qobject_cast(widgets[0]); QPalette::ColorRole colorRole = !historyView->textSelectable() && historyView->selectionModel()->isRowSelected(index.row(), index.parent()) ? QPalette::HighlightedText : QPalette::Text; QPalette::ColorGroup colorGroup = historyView->hasFocus() ? QPalette::Active : QPalette::Inactive; const QColor& color = historyView->palette().color(colorGroup, colorRole); label->setTextColor(color); label->setPlainText(index.model()->data(index).toString()); label->setGeometry(QRect(0, 0, option.rect.width(), option.rect.height() - (2 * MARGIN))); } void TopicHistoryItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); - if (!static_cast(itemView())->textSelectable()) + if (!qobject_cast(itemView())->textSelectable()) { QStyleOptionViewItem* hack = const_cast(&option); hack->rect.setHeight(hack->rect.height() - (2 * MARGIN) - 1); itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, hack, painter, nullptr); } } QSize TopicHistoryItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); m_hiddenLabel->setPlainText(index.model()->data(index).toString()); m_hiddenLabel->setFixedWidth(itemView()->viewport()->width() - - (2 * static_cast(itemView())->categorySpacing()) + - (2 * qobject_cast(itemView())->categorySpacing()) - (2 * MARGIN)); int documentHeight = m_hiddenLabel->document()->size().toSize().height(); return QSize(itemView()->viewport()->width(), documentHeight + (2 * MARGIN)); } TopicHistoryView::TopicHistoryView(QWidget* parent): KCategorizedView(parent) { m_proxyModel = new TopicHistorySortfilterProxyModel(this); m_textSelectable = false; setCategoryDrawer(new KCategoryDrawer(this)); setModelColumn(0); setItemDelegateForColumn(0, new TopicHistoryItemDelegate(this, this)); setVerticalScrollMode(QListView::ScrollPerPixel); setWhatsThis(i18n("This is a list of all topics that have been set for this channel " "while its tab was open.\n\n" "If the same topic is set multiple times consecutively by someone, " "only the final occurrence is shown.\n\n" "When you select a topic in the list, the edit field below it will " "receive its text. Once you start modifying the contents of the field, " "however, the list will switch from the regular entry selection mode to " "allowing you to perform text selection on the entries in case you may " "wish to incorporate some of their text into the new topic. To return to " "entry selection mode and a synchronized edit field, undo back to the " "original text or close and reopen the dialog.")); connect(Application::instance(), &Application::appearanceChanged, this, &TopicHistoryView::updateSelectedItemWidgets); } TopicHistoryView::~TopicHistoryView() { } bool TopicHistoryView::textSelectable() const { return m_textSelectable; } void TopicHistoryView::setTextSelectable(bool selectable) { if (selectable != m_textSelectable) { m_textSelectable = selectable; updateSelectedItemWidgets(); emit textSelectableChanged(selectable); } } void TopicHistoryView::setModel(QAbstractItemModel* model) { m_proxyModel->setSourceModel(model); KCategorizedView::setModel(m_proxyModel); } void TopicHistoryView::resizeEvent(QResizeEvent* event) { KCategorizedView::resizeEvent(event); const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); if (!selectedIndexes.isEmpty()) scrollTo(selectedIndexes.first(), QAbstractItemView::EnsureVisible); } void TopicHistoryView::contextMenuEvent(QContextMenuEvent* event) { const QModelIndex& sourceIndex = m_proxyModel->mapToSource(indexAt(event->pos())); QAbstractItemModel* sourceModel = m_proxyModel->sourceModel(); const QString& text = sourceModel->data(sourceModel->index(sourceIndex.row(), 0)).toString(); QString author = sourceModel->data(sourceModel->index(sourceIndex.row(), 1)).toString(); if (author == TopicHistoryModel::authorPlaceholder()) author.clear(); IrcContextMenus::topicHistoryMenu(event->globalPos(), m_server, text, author); } void TopicHistoryView::updateSelectedItemWidgets() { // KWidgetItemDelegate::updateItemWidgets() is documented to run in response to // data changes, so in order to update our widgets outside of data changes we // fake a data change. const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); if (!selectedIndexes.isEmpty()) m_proxyModel->dataChanged(selectedIndexes.first(), selectedIndexes.first()); } void TopicHistoryView::updateGeometries() { KCategorizedView::updateGeometries(); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } diff --git a/src/viewer/viewcontainer.cpp b/src/viewer/viewcontainer.cpp index 7541460e..25ccb1a1 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(static_cast(m_tabWidget->currentWidget()))); + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { + 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 (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { + 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 !static_cast(view)->joined(); + 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 = static_cast(data)->view(); + 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 = static_cast(data)->view(); + 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 = static_cast(m_tabWidget->widget(index)); + 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 = static_cast(actionCollection()->action("tab_notifications")); + 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 = static_cast(actionCollection()->action("tab_autojoin")); - Channel* channel = static_cast(view); + 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 = static_cast(actionCollection()->action("tab_autoconnect")); + 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 = static_cast(actionCollection()->action("open_channel_list")); + 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 = static_cast(m_tabWidget->currentWidget()); + 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 = static_cast(m_tabWidget->widget(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); } - static_cast(view)->updateName(); + 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()) 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 = static_cast(view); + 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 = static_cast(m_tabWidget->currentWidget()); + view = qobject_cast(m_tabWidget->currentWidget()); else - view = static_cast(m_tabWidget->widget(m_popupViewIndex)); + view = qobject_cast(m_tabWidget->widget(m_popupViewIndex)); if (view) { if (!view->notificationsEnabled()) { view->setNotificationsEnabled(true); updateViews(); - KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); + KToggleAction* action = qobject_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } else { view->setNotificationsEnabled(false); unsetViewNotification(view); - KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); + 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 = static_cast(m_tabWidget->currentWidget()); + channel = qobject_cast(m_tabWidget->currentWidget()); else - channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); + 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 = static_cast(m_tabWidget->currentWidget()); + view = qobject_cast(m_tabWidget->currentWidget()); else - view = static_cast(m_tabWidget->widget(m_popupViewIndex)); + 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 (!static_cast(m_tabWidget->widget(i))->isTopLevelView()) { + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 << static_cast(m_tabWidget->widget(0)); + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(index)); + 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 = static_cast(m_tabWidget->widget(index)); + 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 (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { + if (qobject_cast(m_tabWidget->widget(i))->isTopLevelView()) { lastTopLevelView = i; break; } } return (index != lastTopLevelView); } else if (!view->isTopLevelView()) { - view = static_cast(m_tabWidget->widget(index + 1)); + 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 = static_cast(m_tabWidget->widget(tabIndex)); + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(tabIndex)); + 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 = static_cast(m_tabWidget->widget(tabIndex)); + 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 = static_cast(m_tabWidget->widget(tabIndex)); + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(popup)); + 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 = static_cast(m_tabWidget->currentWidget()); + chatWin = qobject_cast(m_tabWidget->currentWidget()); else - chatWin = static_cast(m_tabWidget->widget(m_popupViewIndex)); + 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 = static_cast(tab); + ChatWindow* view = qobject_cast(tab); m_popupViewIndex = m_tabWidget->indexOf(tab); updateViewActions(m_popupViewIndex); - QMenu* menu = static_cast(m_window->guiFactory()->container("tabContextMenu", m_window)); + 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 = static_cast(view); + 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 static_cast(m_tabWidget->widget(index)); + return qobject_cast(m_tabWidget->widget(index)); } QList > ViewContainer::getChannelsURI() { QList > URIList; for (int i = 0; i < m_tabWidget->count(); ++i) { - ChatWindow* view = static_cast(m_tabWidget->widget(i)); + 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++) - static_cast(m_tabWidget->widget(i))->clear(); + 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 = static_cast(m_tabWidget->currentWidget()); + 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 = static_cast(m_tabWidget->widget(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 = static_cast(m_tabWidget->widget(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 = static_cast(view); + 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 = static_cast(m_tabWidget->currentWidget()); + channel = qobject_cast(m_tabWidget->currentWidget()); else - channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); + 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 = static_cast(m_tabWidget->currentWidget()); + Channel* channel = qobject_cast(m_tabWidget->currentWidget()); channel->showOptionsDialog(); } } void ViewContainer::toggleChannelNicklists() { - KToggleAction* action = static_cast(actionCollection()->action("hide_nicknamelist")); + 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 = static_cast(m_tabWidget->widget(i)); + 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 = static_cast(actionCollection()->action("open_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 = static_cast(actionCollection()->action("open_channel_list")); + 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 = static_cast(actionCollection()->action("open_channel_list")); + 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. */ diff --git a/src/viewer/viewspringloader.cpp b/src/viewer/viewspringloader.cpp index 5adda03a..01228d1d 100644 --- a/src/viewer/viewspringloader.cpp +++ b/src/viewer/viewspringloader.cpp @@ -1,124 +1,124 @@ /* 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) 2012 Eike Hein */ #include "viewspringloader.h" #include "viewtree.h" #include #include #include ViewSpringLoader::ViewSpringLoader(ViewContainer* viewContainer) : QObject(viewContainer) { m_viewContainer = viewContainer; m_hoverTimer.setSingleShot(true); connect(&m_hoverTimer, &QTimer::timeout, this, &ViewSpringLoader::springLoad); } ViewSpringLoader::~ViewSpringLoader() { } void ViewSpringLoader::addWidget(QWidget* widget) { widget->installEventFilter(this); } bool ViewSpringLoader::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::DragEnter) { - if (!static_cast(event)->mimeData()->hasFormat("application/x-konversation-chatwindow")) + if (!dynamic_cast(event)->mimeData()->hasFormat("application/x-konversation-chatwindow")) { - m_hoveredWidget = static_cast(watched); + m_hoveredWidget = qobject_cast(watched); event->accept(); return true; } } else if (event->type() == QEvent::DragMove) { - QDragMoveEvent* dragMoveEvent = static_cast(event); + QDragMoveEvent* dragMoveEvent = dynamic_cast(event); if (!dragMoveEvent->mimeData()->hasFormat("application/x-konversation-chatwindow")) { ChatWindow* hoveredView = viewForPos(watched, dragMoveEvent->pos()); if (hoveredView != m_hoveredView) { m_hoveredView = hoveredView; if (m_hoveredView) m_hoverTimer.start(400); } event->ignore(); return true; } } else if (event->type() == QEvent::Drop || event->type() == QEvent::DragLeave) { m_hoverTimer.stop(); m_hoveredWidget = nullptr; m_hoveredView = nullptr; } return QObject::eventFilter(watched, event); } void ViewSpringLoader::springLoad() { if (m_hoveredView && m_hoveredView == viewForPos(m_hoveredWidget, m_hoveredWidget->mapFromGlobal(QCursor::pos()))) { m_viewContainer->showView(m_hoveredView); m_hoveredView = nullptr; } } ChatWindow* ViewSpringLoader::viewForPos(QObject* widget, const QPoint& pos) { QTabBar* tabBar = qobject_cast(widget); if (tabBar) return m_viewContainer->getViewAt(tabBar->tabAt(pos)); else { ViewTree* viewTree = qobject_cast(widget->parent()); if (viewTree) { const QModelIndex& idx = viewTree->indexAt(QPoint(0, pos.y())); if (idx.isValid()) { ChatWindow* view = static_cast(idx.internalPointer()); return view; } } } return nullptr; } diff --git a/src/viewer/viewtree.cpp b/src/viewer/viewtree.cpp index 0e70f0f8..b3e3e41e 100644 --- a/src/viewer/viewtree.cpp +++ b/src/viewer/viewtree.cpp @@ -1,507 +1,507 @@ /* 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-2009 Eike Hein */ #include "viewtree.h" #include "viewcontainer.h" #include "preferences.h" #include "chatwindow.h" #include "konsolepanel.h" #include "ircview.h" #include #include #include #include #include #include #include #include // FIXME KF5 Port: Not DPI-aware. #define LED_ICON_SIZE 14 #define MARGIN 2 #define RADIUS 4 ViewTreeDelegate::ViewTreeDelegate(QObject* parent) : QStyledItemDelegate(parent) { m_view = qobject_cast(parent); } ViewTreeDelegate::~ViewTreeDelegate() { } QSize ViewTreeDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); const QRect& textRect = m_view->fontMetrics().boundingRect(index.data(Qt::DisplayRole).toString()); size.setHeight(MARGIN + qMax(LED_ICON_SIZE, textRect.height()) + MARGIN); size.setWidth(1); return size; } QSize ViewTreeDelegate::preferredSizeHint(const QModelIndex& index) const { QStyleOptionViewItem option; initStyleOption(&option, index); return QStyledItemDelegate::sizeHint(option, index); } void ViewTreeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { bool selected = (option.state & QStyle::State_Selected); bool highlighted = index.data(ViewContainer::HighlightRole).toBool(); painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); const QColor &selColor = m_view->palette().color(QPalette::Highlight); if (selected || highlighted) { QColor bgColor = selColor; if (highlighted && !selected) { bgColor = Preferences::self()->inputFieldsBackgroundColor() ? Preferences::self()->color(Preferences::AlternateBackground) : m_view->palette().color(QPalette::AlternateBase); } painter->setBrush(bgColor); QRect baseRect = option.rect; painter->drawRoundedRect(baseRect, RADIUS - 1, RADIUS - 1); baseRect.setLeft(baseRect.left() + (baseRect.width() - RADIUS)); painter->drawRect(baseRect); } const QModelIndex idxAbove = m_view->indexAbove(index); if (idxAbove.isValid() && m_view->selectionModel()->isSelected(idxAbove)) { QPainterPath bottomWing; const QPoint &startPos = option.rect.topRight() + QPoint(1, 0); bottomWing.moveTo(startPos); bottomWing.lineTo(bottomWing.currentPosition().x() - RADIUS, bottomWing.currentPosition().y()); bottomWing.moveTo(startPos); bottomWing.lineTo(bottomWing.currentPosition().x(), bottomWing.currentPosition().y() + RADIUS); bottomWing.quadTo(startPos, QPoint(bottomWing.currentPosition().x() - RADIUS, bottomWing.currentPosition().y() - RADIUS)); painter->fillPath(bottomWing, selColor); } const QModelIndex idxBelow = m_view->indexBelow(index); if (idxBelow.isValid() && m_view->selectionModel()->isSelected(idxBelow)) { QPainterPath topWing; const QPoint &startPos = option.rect.bottomRight() + QPoint(1, 1); topWing.moveTo(startPos); topWing.lineTo(topWing.currentPosition().x() - RADIUS, topWing.currentPosition().y()); topWing.moveTo(startPos); topWing.lineTo(topWing.currentPosition().x(), topWing.currentPosition().y() - RADIUS); topWing.quadTo(startPos, QPoint(topWing.currentPosition().x() - RADIUS, topWing.currentPosition().y() + RADIUS)); painter->fillPath(topWing, selColor); } painter->restore(); QStyleOptionViewItem _option = option; _option.state = QStyle::State_None; _option.palette.setColor(QPalette::Base, Qt::transparent); _option.palette.setColor(QPalette::AlternateBase, Qt::transparent); if (index.data(ViewContainer::DisabledRole).toBool()) { _option.palette.setColor(QPalette::Text, QGuiApplication::palette().color(QPalette::Disabled, QPalette::Text)); } else { const QColor &textColor = index.data(ViewContainer::ColorRole).value(); if (textColor.isValid() && !selected) { _option.palette.setColor(QPalette::Text, textColor); } else { _option.palette.setColor(QPalette::Text, selected ? m_view->palette().color(QPalette::HighlightedText) : m_view->palette().color(QPalette::Text)); } } QStyledItemDelegate::paint(painter, _option, index); } ViewTree::ViewTree(QWidget* parent) : QTreeView(parent) , m_accumulatedWheelDelta(0) , m_lastWheelDeltaDirection(false) { setUniformRowHeights(true); setRootIsDecorated(false); setItemsExpandable(false); setHeaderHidden(true); setSortingEnabled(false); setVerticalScrollMode(QAbstractItemView::ScrollPerItem); setSelectionMode(QAbstractItemView::SingleSelection); setSelectionBehavior(QAbstractItemView::SelectRows); setEditTriggers(QAbstractItemView::NoEditTriggers); setDragEnabled(true); // We initiate drags ourselves below. setAcceptDrops(true); viewport()->setAcceptDrops(true); setDragDropMode(QAbstractItemView::DropOnly); setDropIndicatorShown(true); setBackgroundRole(QPalette::Base); setItemDelegateForColumn(0, new ViewTreeDelegate(this)); updateAppearance(); } ViewTree::~ViewTree() { } void ViewTree::setModel(QAbstractItemModel *model) { QTreeView::setModel(model); expandAll(); connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ViewTree::selectionChanged); } bool ViewTree::dropIndicatorOnItem() const { return (dropIndicatorPosition() == OnItem); } void ViewTree::updateAppearance() { if (Preferences::self()->customTabFont()) setFont(Preferences::self()->tabFont()); else setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); setAlternatingRowColors(Preferences::self()->inputFieldsBackgroundColor()); 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)); } setPalette(palette); } bool ViewTree::event(QEvent* event) { if (event->type() == QEvent::ToolTip) { event->accept(); - const QHelpEvent* helpEvent = static_cast(event); + const QHelpEvent* helpEvent = dynamic_cast(event); const QModelIndex& idx = indexAt(helpEvent->pos()); if (idx.isValid()) { const QString &text = idx.model()->data(idx, Qt::DisplayRole).toString(); - const QSize& preferredSize = static_cast(itemDelegate())->preferredSizeHint(idx); + const QSize& preferredSize = qobject_cast(itemDelegate())->preferredSizeHint(idx); const QRect& itemRect = visualRect(idx); if (preferredSize.width() > itemRect.width()) { event->accept(); QToolTip::showText(helpEvent->globalPos(), text, this); return true; } } QToolTip::hideText(); return true; } return QTreeView::event(event); } void ViewTree::paintEvent(QPaintEvent* event) { QTreeView::paintEvent(event); const QModelIndex ¤tRow = selectionModel()->currentIndex(); if (!currentRow.isValid()) { return; } const QAbstractItemModel* model = currentRow.model(); QModelIndex lastRow = model->index(model->rowCount() - 1, 0); int count = model->rowCount(lastRow); if (count) { lastRow = lastRow.child(count - 1, 0); } if (lastRow.isValid() && lastRow == currentRow) { const QRect &baseRect = visualRect(lastRow); QPainter painter(viewport()); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(Qt::NoPen); painter.setBrush(palette().color(QPalette::Highlight)); QPainterPath bottomWing; const QPoint &startPos = baseRect.bottomRight() + QPoint(1, 1); bottomWing.moveTo(startPos); bottomWing.lineTo(bottomWing.currentPosition().x() - RADIUS, bottomWing.currentPosition().y()); bottomWing.moveTo(startPos); bottomWing.lineTo(bottomWing.currentPosition().x(), bottomWing.currentPosition().y() + RADIUS); bottomWing.quadTo(startPos, QPoint(bottomWing.currentPosition().x() - RADIUS, bottomWing.currentPosition().y() - RADIUS)); painter.fillPath(bottomWing, painter.brush()); } } void ViewTree::drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { // Avoid style engine's row pre-fill. QStyleOptionViewItem _option = option; _option.palette.setColor(QPalette::Highlight, Qt::transparent); QTreeView::drawRow(painter, _option, index); } void ViewTree::resizeEvent(QResizeEvent* event) { setColumnWidth(0, event->size().width()); QTreeView::resizeEvent(event); emit sizeChanged(); } void ViewTree::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::RightButton) { event->ignore(); return; } else if (event->button() == Qt::MiddleButton) { m_pressPos = event->pos(); const QModelIndex& idx = indexAt(event->pos()); if (idx.isValid()) { m_pressedView = static_cast(idx.internalPointer()); } } QTreeView::mousePressEvent(event); } void ViewTree::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::MiddleButton) { m_pressPos = QPoint(); if (Preferences::self()->middleClickClose()) { const QModelIndex& idx = indexAt(event->pos()); if (idx.isValid()) { if (m_pressedView != nullptr && m_pressedView == static_cast(idx.internalPointer())) { emit closeView(m_pressedView.data()); } } } } m_pressedView = nullptr; QTreeView::mouseReleaseEvent(event); } void ViewTree::mouseMoveEvent(QMouseEvent* event) { if (m_pressedView && (m_pressPos - event->pos()).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { selectionModel()->select(indexAt(event->pos()), QItemSelectionModel::ClearAndSelect); startDrag(Qt::MoveAction); m_pressPos = QPoint(); m_pressedView = nullptr; } QTreeView::mouseMoveEvent(event); } void ViewTree::dragEnterEvent(QDragEnterEvent* event) { if (event->source() == this && event->possibleActions() & Qt::MoveAction) { event->accept(); setState(DraggingState); } else { event->ignore(); } } void ViewTree::dragMoveEvent(QDragMoveEvent* event) { QTreeView::dragMoveEvent(event); // Work around Qt being idiotic and not hiding the drop indicator when we want it to. setDropIndicatorShown(event->isAccepted() && !dropIndicatorOnItem()); viewport()->update(); } void ViewTree::contextMenuEvent(QContextMenuEvent* event) { const QModelIndex& idx = indexAt(event->pos()); if (idx.isValid()) { QWidget* widget = static_cast(idx.internalPointer()); if (widget) { event->accept(); emit showViewContextMenu(widget, event->globalPos()); } } event->ignore(); } void ViewTree::wheelEvent(QWheelEvent* event) { event->accept(); bool direction = (event->angleDelta().y() > 0); if (m_lastWheelDeltaDirection != direction) { m_accumulatedWheelDelta = 0; m_lastWheelDeltaDirection = direction; } m_accumulatedWheelDelta += event->angleDelta().y(); bool thresholdReached = false; // magic number 120 // See: http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop if (m_accumulatedWheelDelta >= 120) { thresholdReached = true; m_accumulatedWheelDelta -= 120; } if (m_accumulatedWheelDelta <= -120) { thresholdReached = true; m_accumulatedWheelDelta += 120; } if (!thresholdReached) { return; } QModelIndex idx = moveCursor(direction ? QAbstractItemView::MoveUp : QAbstractItemView::MoveDown, Qt::NoModifier); if (idx == currentIndex()) { const QAbstractItemModel* model = idx.model(); if (!model) { return; } if (direction) { idx = model->index(model->rowCount() - 1, 0); int count = model->rowCount(idx); if (count) { idx = idx.child(count - 1, 0); } } else { idx = model->index(0, 0); } } selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate); } void ViewTree::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down) { QTreeView::keyPressEvent(event); } else { const QModelIndex& idx = currentIndex(); if (idx.isValid()) { ChatWindow* view = static_cast(idx.internalPointer()); if (view) { if (view->getInputBar()) QCoreApplication::sendEvent(view->getTextView(), event); else if (view->isInsertSupported()) view->appendInputText(event->text(), true); else if (view->getType() == ChatWindow::Konsole) { - KonsolePanel* panel = static_cast(view); + KonsolePanel* panel = qobject_cast(view); QCoreApplication::sendEvent(panel->getWidget(), event); } view->adjustFocus(); } } } } void ViewTree::selectView(const QModelIndex& index) { if (!index.isValid()) { return; } selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } void ViewTree::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { Q_UNUSED(deselected) const QModelIndexList& idxList = selected.indexes(); if (idxList.count()) { const QModelIndex& idx = idxList.at(0); ChatWindow* view = static_cast(idx.internalPointer()); if (view) { emit showView(view); } } viewport()->update(); }