diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index d025447c..9873dd40 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,25 +1,23 @@ add_subdirectory(images) add_subdirectory(scripts) add_subdirectory(scripting_support) ########### install files ############### install(PROGRAMS org.kde.konversation.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install(FILES konversationui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/konversation) install(FILES konversation.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) install(FILES updaters/konversation.upd DESTINATION ${DATA_INSTALL_DIR}/kconf_update) install(PROGRAMS updaters/konversation-0.19-colors.pl updaters/konversation-0.19-sortorder.pl updaters/konversation-0.19-appearance.pl updaters/konversation-0.19-tabplacement.pl updaters/konversation-0.19-custombrowser.pl updaters/konversation-0.19-colorcodes.pl updaters/konversation-0.19-notifylists.pl updaters/konversation-0.20-quickbuttons.pl updaters/konversation-0.20-customfonts.pl DESTINATION ${DATA_INSTALL_DIR}/kconf_update) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.konversation.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) - -install(PROGRAMS konvi2x DESTINATION bin) diff --git a/data/konvi2x b/data/konvi2x deleted file mode 100755 index 8137a206..00000000 --- a/data/konvi2x +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -konversation --qtquick --nui "$@" diff --git a/data/org.kde.konversation.desktop b/data/org.kde.konversation.desktop index bb837dc5..709d1149 100755 --- a/data/org.kde.konversation.desktop +++ b/data/org.kde.konversation.desktop @@ -1,121 +1,121 @@ [Desktop Entry] Type=Application -Exec=konvi2x -qwindowtitle %c %u +Exec=konversation -qwindowtitle %c %u Icon=konversation X-DocPath=konversation/index.html MimeType=x-scheme-handler/irc;x-scheme-handler/ircs; GenericName=IRC Client GenericName[ar]=عميل آي‌آر‌سي GenericName[be]=Кліент IRC GenericName[bg]=IRC клиент GenericName[bs]=IRC klijent GenericName[ca]=Client d'IRC GenericName[ca@valencia]=Client d'IRC GenericName[cs]=IRC klient GenericName[da]=IRC-klient GenericName[de]=IRC-Programm GenericName[el]=Πελάτης IRC GenericName[en_GB]=IRC Client GenericName[es]=Cliente de IRC GenericName[et]=IRC klient GenericName[eu]=IRC bezeroa GenericName[fi]=IRC-keskustelu GenericName[fr]=Client IRC GenericName[ga]=Cliant IRC GenericName[gl]=Cliente de IRC GenericName[he]=לקוח IRC GenericName[hne]=आईआरसी क्लायंट GenericName[hu]=IRC-kliens GenericName[it]=Client IRC GenericName[kk]=IRC клиенті GenericName[km]=ម៉ាស៊ីន​ភ្ញៀវ IRC GenericName[ko]=IRC 클라이언트 GenericName[lt]=IRC klientas GenericName[mr]=IRC ग्राहक GenericName[nb]=IRC-klient GenericName[nds]=IRC-Client GenericName[nl]=IRC-client GenericName[nn]=IRC-klient GenericName[pa]=IRC ਕਲਾਇਟ GenericName[pl]=Klient IRC GenericName[pt]=Cliente de IRC GenericName[pt_BR]=Cliente IRC GenericName[ro]=Client IRC GenericName[ru]=Клиент IRC GenericName[si]=IRC GenericName[sk]=IRC Client GenericName[sl]=Odjemalec za IRC GenericName[sq]=IRC Klient GenericName[sr]=ИРЦ клијент GenericName[sr@ijekavian]=ИРЦ клијент GenericName[sr@ijekavianlatin]=IRC klijent GenericName[sr@latin]=IRC klijent GenericName[sv]=IRC-klient GenericName[tr]=IRC İstemcisi GenericName[ug]=IRC خېرىدارى GenericName[uk]=Клієнт IRC GenericName[x-test]=xxIRC Clientxx GenericName[zh_CN]=IRC 客户端 GenericName[zh_TW]=IRC 客戶端程式 Terminal=false Name=Konversation Name[ar]=محادثك Name[be]=Konversation Name[bg]=Konversation Name[bs]=Konverzacija Name[ca]=Konversation Name[ca@valencia]=Konversation Name[cs]=Konversation Name[da]=Konversation Name[de]=Konversation Name[el]=Konversation Name[en_GB]=Konversation Name[eo]=Konversation Name[es]=Konversation Name[et]=Konversation Name[eu]=Konversation Name[fi]=Konversation Name[fr]=Konversation Name[ga]=Konversation Name[gl]=Konversation Name[he]=Konversation Name[hi]=कनवर्सेसन Name[hne]=कनवर्सेसन Name[hr]=Konversation Name[hu]=Konversation Name[it]=Konversation Name[kk]=Konversation Name[km]=Konversation Name[ko]=Konversation Name[lt]=Konversation Name[mr]=कंव्हर्झेशन Name[nb]=Konversation Name[nds]=Konversation Name[nl]=Konversation Name[nn]=Konversation Name[pa]=ਕੰਨਵਰਸ਼ੇਸ਼ਨ Name[pl]=Konversation Name[pt]=Konversation Name[pt_BR]=Konversation Name[ro]=Konversație Name[ru]=Konversation Name[si]=සාකච්ඡාව Name[sk]=Konversation Name[sl]=Konversation Name[sq]=Konversation Name[sr]=Конверзација Name[sr@ijekavian]=Конверзација Name[sr@ijekavianlatin]=Konverzacija Name[sr@latin]=Konverzacija Name[sv]=Konversation Name[tr]=Konversation Name[ug]=Konversation Name[uk]=Konversation Name[x-test]=xxKonversationxx Name[zh_CN]=Konversation Name[zh_TW]=Konversation Categories=Qt;KDE;Network;IRCClient; X-KDE-ServiceTypes=DBUS/InstantMessenger X-DBUS-StartupType=Unique X-DBUS-ServiceName=org.kde.konversation StartupNotify=true diff --git a/src/application.cpp b/src/application.cpp index d9f1da27..e492efe5 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,1412 +1,1569 @@ /* 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 "messagemodel.h" // WIPQTQUICK +#include "usermodel.h" // WIPQTQUICK +#include "identitymodel.h" // WIPQTQUICK +#include "completer.h" // WIPQTQUICK +#include "inputhistorymodel.h" // WIPQTQUICK +#include "irccontextmenus.h" // WIPQTQUICK +#include "qclipboardwrapper.h" // WIPQTQUICK +#include "statusbar.h" // WIPQTQUICK +#include "trayicon.h" // WIPQTQUICK #include #include #include #include #include #include #include #include #include #include +#include // WIPQTQUICK +#include // WIPQTQUICK +#include // WIPQTQUICK +#include // WIPQTQUICK +#include // WIPQTQUICK #include #include #include #include #include #include #include #include +#include // WIPQTQUICK +#include // WIPQTQUICK 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(args->isSet(QStringLiteral("qtquick")), - args->value(QStringLiteral("uipackage"))); // WIPQTQUICK + mainWindow = new MainWindow(); + mainWindow->hide(); // WIPQTQUICK 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(); + // BEGIN WIPQTQUICK + mainWindow->hide(); + + m_identityModel = new IdentityModel(this); + + m_messageModel = new MessageModel(this); + + m_filteredMessageModel = new FilteredMessageModel(this); + m_filteredMessageModel->setSourceModel(m_messageModel); + + m_filteredUserModel = new FilteredUserModel(this); + + m_completer = new Completer(this); + m_completer->setSourceModel(m_filteredUserModel); + + m_inputHistoryModel = new InputHistoryModel(this); + m_filteredInputHistoryModel = new FilteredInputHistoryModel(this); + m_filteredInputHistoryModel->setSourceModel(m_inputHistoryModel); + + // Filter on the new view. + connect(mainWindow->getViewContainer(), &ViewContainer::viewChanged, this, + [this](const QModelIndex &idx) { + if (mainWindow->getCloseApp()) { + return; + } + + m_filteredMessageModel->setFilterView(static_cast(idx.internalPointer())); + m_filteredUserModel->setFilterView(static_cast(idx.internalPointer())); + m_completer->setContextView(static_cast(idx.internalPointer())); + } + ); + + // Update filter when ViewContainer resets. + QObject::connect(mainWindow->getViewContainer(), &QAbstractItemModel::modelAboutToBeReset, this, + [this]() { + m_filteredMessageModel->setFilterView(nullptr); + } + ); + + m_viewListModel = new KDescendantsProxyModel(this); + m_viewListModel->setSourceModel(mainWindow->getViewContainer()); + + QObject::connect(mainWindow->systemTrayIcon(), &KStatusNotifierItem::activateRequested, this, + [this](bool active, const QPoint &pos) { + Q_UNUSED(pos) + + if (active) { + getQuickMainWindow()->show(); + } else { + getQuickMainWindow()->hide(); + } + } + ); + + qputenv("QT_QUICK_CONTROLS_STYLE", "org.kde.desktop"); + m_qmlEngine = new QQmlApplicationEngine(this); + + // register common enums needed in QML + qRegisterMetaType("Konversation::ConnectionState"); // C++ -> QML signal + qmlRegisterUncreatableMetaObject(Konversation::staticMetaObject, "org.kde.konversation", 1, 0, "Konversation", "Enums only"); + qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "MessageModel", ""); + qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "InputHistoryModel", ""); + qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "IrcContextMenus", ""); + + // setup qml context + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("konvApp"), Application::instance()); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("viewModel"), mainWindow->getViewContainer()); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("viewListModel"), m_viewListModel); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("messageModel"), m_filteredMessageModel); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("userModel"), m_filteredUserModel); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("identityModel"), m_identityModel); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("completer"), m_completer); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("inputHistoryModel"), m_filteredInputHistoryModel); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("contextMenus"), IrcContextMenus::self()); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("clipboard"), new QClipboardWrapper(this)); + + loadUiPackage(args->value(QStringLiteral("uipackage"))); + if (Preferences::self()->showTrayIcon() && Preferences::self()->hideToTrayOnStartup()) - mainWindow->hide(); + getQuickMainWindow()->hide(); else - mainWindow->show(); + getQuickMainWindow()->show(); + // END: WIPQTQUICK 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 (auto & it : serversToAutoconnect) { 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()); } +bool Application::loadUiPackage(const QString &packageName) +{ + if (packageName.isEmpty()) { + qDebug() << "Error loading UI package: Package name is empty. Doing nothing."; + + return false; + } + + QLatin1Literal packageNamePrefix("org.kde.konversation.uipackages."); + QString fixedName(packageName.startsWith(packageNamePrefix) ? packageName : packageNamePrefix + packageName); + + KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Konversation/UiPackage"), + fixedName); + + if (!p.isValid()) { + qDebug() << "Error loading UI package: Package" << packageName << "is invalid."; + + qDebug() << "Available Qt Quick UI packages for Konversation (name / id):"; + + auto plist = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Konversation/UiPackage")); + + for (const auto &pkg : plist) { + qDebug() << " " << pkg.name() << "/" << pkg.pluginId(); + } + + return false; + } + + if (m_qmlEngine->rootObjects().count()) { + qDebug() << "Unloading current UI package:" << m_currentUiPackage; + + // Close the window. + static_cast(m_qmlEngine->rootObjects().first())->close(); + + // Delete the root object. + qDeleteAll(m_qmlEngine->rootObjects()); + + // Clear the component cache. + m_qmlEngine->clearComponentCache(); + } + + m_qmlEngine->load(QUrl::fromLocalFile(p.filePath("window"))); + + m_currentUiPackage = fixedName; + + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(openServerList()), + mainWindow, SLOT(openServerList())); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(openIdentities()), + mainWindow, SLOT(openIdentitiesDialog())); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(showLegacyMainWindow()), + mainWindow, SLOT(show())); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(openLegacyConfigDialog()), + mainWindow, SLOT(openPrefsDialog())); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(quitApp()), + mainWindow, SLOT(quitProgram())); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(setStatusBarTempText(QString)), + mainWindow->getStatusBar(), SLOT(setMainLabelTempText(QString))); + QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(clearStatusBarTempText()), + mainWindow->getStatusBar(), SLOT(clearMainLabelTempText())); + + return true; +} + +QWindow* Application::getQuickMainWindow() +{ + return static_cast(m_qmlEngine->rootObjects().first()); +} + 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 QVariantList 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); } } } } QVariantList ret; ret << line << cursorPos; return ret; } void Application::doInlineAutoreplace(KTextEdit* textEdit) { QTextCursor cursor(textEdit->document()); cursor.beginEditBlock(); const QVariantList& replace = Application::instance()->doAutoreplace(textEdit->toPlainText(), true, textEdit->textCursor().position()); cursor.select(QTextCursor::Document); cursor.insertText(replace[0].toString()); cursor.setPosition(replace[1].toInt()); 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(); - - // WIPQTQUICK - if (m_commandLineParser->isSet(QStringLiteral("reloaduipackage"))) - { - mainWindow->reloadUiPackage(); - } } // 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/application.h b/src/application.h index 735584ed..93a156db 100644 --- a/src/application.h +++ b/src/application.h @@ -1,208 +1,238 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005 Peter Simonsson Copyright (C) 2005 John Tapsell Copyright (C) 2005-2008 Eike Hein */ #ifndef APPLICATION_H #define APPLICATION_H #include #include #include "preferences.h" #include "mainwindow.h" #include "server.h" #include "osd.h" #include "identity.h" #include "ircqueue.h" #include class ConnectionManager; class AwayManager; class ScriptLauncher; class Server; class QuickConnectDialog; class Images; class ServerGroupSettings; class QStandardItemModel; class QCommandLineParser; +class QWindow; +class ViewContainer; // WIPQTQUICK +class MessageModel; // WIPQTQUICK +class FilteredMessageModel; // WIPQTQUICK +class FilteredUserModel; // WIPQTQUICK +class IdentityModel; // WIPQTQUICK +class Completer; // WIPQTQUICK +class InputHistoryModel; // WIPQTQUICK +class FilteredInputHistoryModel; // WIPQTQUICK +class KDescendantsProxyModel; // WIPQTQUICK class KTextEdit; namespace Konversation { class DBus; class IdentDBus; class Sound; class NotificationHandler; namespace DCC { class TransferManager; } } namespace KWallet { class Wallet; } class Application : public QApplication { Q_OBJECT public: /** This function in general shouldn't be called, because in the future there * may be multiple windows. * However, in some situations we have messageboxes that aren't targeted for * any particular main window, such as general errors from dcop calls. * * Note to any MDI developer - get this to return any of the windows, or some * 'main' one. */ MainWindow* getMainWindow() { return mainWindow; } + QWindow* getQuickMainWindow(); // WIPQTQUICK ConnectionManager* getConnectionManager() { return m_connectionManager; } AwayManager* getAwayManager() { return m_awayManager; } ScriptLauncher* getScriptLauncher() { return m_scriptLauncher; } Konversation::DCC::TransferManager* getDccTransferManager() { return m_dccTransferManager; } + MessageModel *getMessageModel() { return m_messageModel; } // WIPQTQUICK + KDescendantsProxyModel *getViewListModel() { return m_viewListModel; } // WIPQTQUICK + FilteredMessageModel *getFilteredMessageModel() { return m_filteredMessageModel; } // WIPQTQUICK + FilteredUserModel *getFilteredUserModel() { return m_filteredUserModel; } // WIPQTQUICK + InputHistoryModel *getInputHistoryModel() { return m_inputHistoryModel; } // WIPQTQUICK + FilteredInputHistoryModel *getFilteredInputHistoryModel() { return m_filteredInputHistoryModel; } // WIPQTQUICK } + bool loadUiPackage(const QString &packageName); // WIPQTQUICK // HACK void showQueueTuner(bool); // URL-Catcher QStandardItemModel* getUrlModel() { return m_urlModel; } Application(int &argc, char **argv); ~Application() override; static Application* instance(); /** For D-Bus, a user can be specified as user\@irc.server.net * or user\@servergroup or using the unicode separator symbol 0xE120 instead * of the "@". This function takes a string like the above examples, and * modifies ircnick and serverOrGroup to contain the split up string. If * the string doesn't have an @ or 0xE120, ircnick is set to the * nick_server, and serverOrGroup is set to empty. * Behaviour is undefined for serverOrGroup if multiple @ or 0xE120 are found. * @param nick_server A string containting ircnick and possibly servername or server group * @param ircnick This is modified to contain the ircnick * @param serverOrGroup This is modified to contain the servername, servergroup or an empty string. */ static void splitNick_Server(const QString& nick_server, QString &ircnick, QString &serverOrGroup); /** Tries to find a nickinfo for a given ircnick on a given ircserver. * @param ircnick The case-insensitive ircnick of the person you want to find. e.g. "johnflux" * @param serverOrGroup The case-insensitive server name (e.g. "irc.kde.org") or server group name (e.g. "freenode"), or null to search all servers * @return A nickinfo for this user and server if one is found. */ NickInfoPtr getNickInfo(const QString &ircnick, const QString &serverOrGroup); OSDWidget* osd; Konversation::Sound* sound(); IRCQueue::EmptyingRate staticrates[Server::_QueueListSize]; Images* images() { return m_images; } Konversation::NotificationHandler* notificationHandler() const { return m_notificationHandler; } // auto replacement for input or output lines Q_INVOKABLE QVariantList doAutoreplace(const QString& text, bool output, int cursorPos = -1); // inline auto replacement for input lines void doInlineAutoreplace(KTextEdit* textEdit); void newInstance(QCommandLineParser *args); Q_INVOKABLE static void openUrl(const QString& url); /// The wallet used to store passwords. Opens the wallet if it's closed. KWallet::Wallet* wallet(); void abortScheduledRestart() { m_restartScheduled = false; } /// The command line parser is needed for handling parsing arguments on new activations. void setCommandLineParser(QCommandLineParser *parser) { m_commandLineParser = parser; } QCommandLineParser *commandLineParser() const { return m_commandLineParser; } Q_SIGNALS: void serverGroupsChanged(const Konversation::ServerGroupSettingsPtr serverGroup); void appearanceChanged(); // FIXME TODO: Rather than relying on this catch-all, consumers should be rewritten to catch appropriate QEvents. public Q_SLOTS: void restart(); void readOptions(); void saveOptions(bool updateGUI=true); void fetchQueueRates(); ///< on Application::readOptions() void stashQueueRates(); ///< on application exit void resetQueueRates(); ///< when QueueTuner says to int countOfQueues() { return Server::_QueueListSize-1; } void prepareShutdown(); void storeUrl(const QString& origin, const QString& newUrl, const QDateTime& dateTime); void handleActivate(const QStringList& arguments); protected Q_SLOTS: void openQuickConnectDialog(); void dbusMultiServerRaw(const QString &command); void dbusRaw(const QString& connection, const QString &command); void dbusSay(const QString& connection, const QString& target, const QString& command); void dbusInfo(const QString& string); void sendMultiServerCommand(const QString& command, const QString& parameter); void updateProxySettings(); void closeWallet(); protected: bool event(QEvent* event) Q_DECL_OVERRIDE; private: void implementRestart(); ConnectionManager* m_connectionManager; AwayManager* m_awayManager; Konversation::DCC::TransferManager* m_dccTransferManager; ScriptLauncher* m_scriptLauncher; QStandardItemModel* m_urlModel; Konversation::DBus* dbusObject; Konversation::IdentDBus* identDBus; QPointer mainWindow; Konversation::Sound* m_sound; QuickConnectDialog* quickConnectDialog; Images* m_images; bool m_restartScheduled; Konversation::NotificationHandler* m_notificationHandler; KWallet::Wallet* m_wallet; QNetworkConfigurationManager* m_networkConfigurationManager; QCommandLineParser *m_commandLineParser; QStringList m_restartArguments; + + MessageModel *m_messageModel; // WIPQTQUICK + FilteredMessageModel *m_filteredMessageModel; // WIPQTQUICK + FilteredUserModel *m_filteredUserModel; // WIPQTQUICK + IdentityModel *m_identityModel; + Completer *m_completer; // WIPQTQUICK + InputHistoryModel *m_inputHistoryModel; + FilteredInputHistoryModel *m_filteredInputHistoryModel; // WIPQTQUICK + QQmlApplicationEngine *m_qmlEngine; // WIPQTQUICK + QString m_currentUiPackage; // WIPQTQUICK + QStackedWidget *m_uiStack; // WIPQTQUICK + KDescendantsProxyModel *m_viewListModel; // WIPQTQUICK }; #endif // 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/outputfilter.cpp b/src/irc/outputfilter.cpp index 7fbd6086..d75464b9 100644 --- a/src/irc/outputfilter.cpp +++ b/src/irc/outputfilter.cpp @@ -1,2085 +1,2079 @@ /* 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-2009 Eike Hein */ #include "outputfilter.h" #include "application.h" #include "mainwindow.h" #include "channel.h" #include "awaymanager.h" #include "ignore.h" #include "server.h" #include "scriptlauncher.h" #include "irccharsets.h" #include "query.h" #include "viewcontainer.h" #include "outputfilterresolvejob.h" #include #ifdef HAVE_QCA2 #include "cipher.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include "ircview.h" QDebug operator<<(QDebug d, QTextDocument* document); namespace Konversation { QSet OutputFilter::m_commands; void OutputFilter::fillCommandList() { if (m_commands.size() > 0) return; QString methodSignature; for (int i = OutputFilter::staticMetaObject.methodOffset(); i < OutputFilter::staticMetaObject.methodCount(); ++i) { methodSignature = QString::fromLatin1(OutputFilter::staticMetaObject.method(i).methodSignature()); if (methodSignature.startsWith(QLatin1String("command_"))) m_commands << methodSignature.mid(8).section(QLatin1Char('('), 0, 0).toLower(); } } OutputFilter::OutputFilter(Server* server) : QObject(server) { m_server = server; fillCommandList(); } OutputFilter::~OutputFilter() = default; // replace all aliases in the string and return true if anything got replaced at all bool OutputFilter::replaceAliases(QString& line, ChatWindow* context) { QStringList aliasList = Preferences::self()->aliasList(); for(int index = 0; indexcommandChar() + aliasPattern) { QString aliasReplace = aliasList[index].section(QLatin1Char(' '),1); if (context) aliasReplace = context->getServer()->parseWildcards(aliasReplace, context); if (!aliasList[index].contains(QStringLiteral("%p"))) aliasReplace.append(QLatin1Char(' ') + line.section(QLatin1Char(' '), 1)); // protect "%%" aliasReplace.replace(QStringLiteral("%%"),QStringLiteral("%\x01")); // replace %p placeholder with rest of line aliasReplace.replace(QStringLiteral("%p"), line.section(QLatin1Char(' '), 1)); // restore "%<1>" as "%%" aliasReplace.replace(QStringLiteral("%\x01"),QStringLiteral("%%")); // modify line line=aliasReplace; // return "replaced" return true; } } return false; } QStringList OutputFilter::splitForEncoding(const QString& destination, const QString& inputLine, int max, int segments) { int sublen = 0; //The encoded length since the last split int charLength = 0; //the length of this char int lastBreakPoint = 0; //FIXME should we run this through the encoder first, checking with "canEncode"? QString text = inputLine; // the text we'll send, currently in Unicode QStringList finals; // The strings we're going to output QString channelCodecName = Preferences::channelEncoding(m_server->getDisplayName(), destination); //Get the codec we're supposed to use. This must not fail. (not verified) QTextCodec* codec; // I copied this bit straight out of Server::send if (channelCodecName.isEmpty()) { codec = m_server->getIdentity()->getCodec(); } else { codec = Konversation::IRCCharsets::self()->codecForName(channelCodecName); } Q_ASSERT(codec); int index = 0; while (index < text.length() && (segments == -1 || finals.size() < segments-1)) { // The most important bit - turn the current char into a QCString so we can measure it QByteArray ch = codec->fromUnicode(QString(text[index])); charLength = ch.length(); // If adding this char puts us over the limit: if (charLength + sublen > max) { if (lastBreakPoint != 0) { finals.append(text.left(lastBreakPoint + 1)); text = text.mid(lastBreakPoint + 1); } else { finals.append(text.left(index)); text = text.mid(index); } lastBreakPoint = 0; sublen = 0; index = 0; } else if (text[index].isSpace() || text[index].isPunct()) { lastBreakPoint = index; } ++index; sublen += charLength; } if (!text.isEmpty()) { finals.append(text); } return finals; } bool OutputFilter::checkForEncodingConflict(QString *line, const QString& target) { QTextCodec* codec = nullptr; QString encoding; QString oldLine(*line); if(m_server->getServerGroup()) encoding = Preferences::channelEncoding(m_server->getServerGroup()->id(), target); else encoding = Preferences::channelEncoding(m_server->getDisplayName(), target); if(encoding.isEmpty()) codec = m_server->getIdentity()->getCodec(); else codec = Konversation::IRCCharsets::self()->codecForName(encoding); QTextCodec::ConverterState state; QString newLine; if(codec) newLine = codec->fromUnicode(oldLine.constData(),oldLine.length(),&state); if(!newLine.isEmpty() && state.invalidChars) { int ret = KMessageBox::Continue; ret = KMessageBox::warningContinueCancel(m_server->getViewContainer()->getWindow(), i18n("The message you are sending includes characters " "that do not exist in your current encoding. If " "you choose to continue anyway those characters " "will be replaced by a '?'."), i18n("Encoding Conflict Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "WarnEncodingConflict"); *line = newLine; //set so we show it as it's sent if (ret != KMessageBox::Continue) return true; } return false; } OutputFilterResult OutputFilter::parse(const QString& myNick, const QString& originalLine, const QString& destination, ChatWindow* inputContext) { OutputFilterInput input; input.myNick = myNick; input.destination = destination; input.context = inputContext; OutputFilterResult result; QString inputLine(originalLine); if (inputLine.isEmpty() || inputLine == "\n" || checkForEncodingConflict(&inputLine, destination)) return result; //Protect against nickserv auth being sent as a message on the off chance // someone didn't notice leading spaces { QString testNickServ(inputLine.trimmed()); if(testNickServ.startsWith(Preferences::self()->commandChar() + "nickserv", Qt::CaseInsensitive) || testNickServ.startsWith(Preferences::self()->commandChar() + "ns", Qt::CaseInsensitive)) { inputLine = testNickServ; } } //perform variable expansion according to prefs inputLine = Konversation::doVarExpansion(inputLine); QString line = inputLine.toLower(); // Convert double command chars at the beginning to single ones if(line.startsWith(Preferences::self()->commandChar() + Preferences::self()->commandChar()) && !destination.isEmpty()) { inputLine = inputLine.mid(1); goto BYPASS_COMMAND_PARSING; } // Server command? else if (line.startsWith(Preferences::self()->commandChar())) { QString command = inputLine.section(' ', 0, 0).mid(1).toLower(); input.parameter = inputLine.section(' ', 1); if (command != "topic") input.parameter = input.parameter.trimmed(); if (supportedCommands().contains(command)) { QString methodSignature("command_" + command); QMetaObject::invokeMethod(this, methodSignature.toLatin1().constData(), Qt::DirectConnection, Q_RETURN_ARG(OutputFilterResult, result), Q_ARG(OutputFilterInput, input)); } // Forward unknown commands to server else { result.toServer = inputLine.mid(1); result.type = Message; } } // Ordinary message to channel/query? else if (!destination.isEmpty()) { BYPASS_COMMAND_PARSING: QStringList outputList = splitForEncoding(destination, inputLine, m_server->getPreLength("PRIVMSG", destination)); if (outputList.count() > 1) { result.output.clear(); result.outputList = outputList; for (QStringList::ConstIterator it = outputList.constBegin(); it != outputList.constEnd(); ++it) { result.toServerList += "PRIVMSG " + destination + " :" + *it; } } else { result.output = inputLine; result.toServer = "PRIVMSG " + destination + " :" + inputLine; } result.type = Message; } // Eveything else goes to the server unchanged else { result.toServer = inputLine; result.output = inputLine; result.typeString = i18n("Raw"); result.type = Program; } return result; } OutputFilterResult OutputFilter::command_op(const OutputFilterInput& input) { return changeMode(input.parameter, input.destination, 'o', '+'); } OutputFilterResult OutputFilter::command_deop(const OutputFilterInput& input) { return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination, 'o', '-'); } OutputFilterResult OutputFilter::command_hop(const OutputFilterInput& input) { return changeMode(input.parameter, input.destination, 'h', '+'); } OutputFilterResult OutputFilter::command_dehop(const OutputFilterInput& input) { return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination, 'h', '-'); } OutputFilterResult OutputFilter::command_voice(const OutputFilterInput& input) { return changeMode(input.parameter, input.destination, 'v', '+'); } OutputFilterResult OutputFilter::command_unvoice(const OutputFilterInput& input) { return changeMode(addNickToEmptyNickList(input.myNick, input.parameter), input.destination, 'v', '-'); } OutputFilterResult OutputFilter::command_devoice(const OutputFilterInput& input) { return command_unvoice(input); } OutputFilterResult OutputFilter::command_join(const OutputFilterInput& _input) { OutputFilterInput input(_input); OutputFilterResult result; if (input.parameter.contains(",")) // Protect against #foo,0 tricks input.parameter = input.parameter.remove(",0"); //else if(channelName == "0") // FIXME IRC RFC 2812 section 3.2.1 if (input.parameter.isEmpty()) { if (input.destination.isEmpty() || !isAChannel(input.destination)) return usage(i18n("Usage: %1JOIN [password]", Preferences::self()->commandChar())); input.parameter = input.destination; } else if (!isAChannel(input.parameter)) input.parameter = '#' + input.parameter.trimmed(); Channel* channel = m_server->getChannelByName(input.parameter); if (channel) { // Note that this relies on the channels-flush-nicklists-on-disconnect behavior. if (!channel->numberOfNicks()) result.toServer = "JOIN " + input.parameter; if (channel->joined()) emit showView(channel); } else result.toServer = "JOIN " + input.parameter; return result; } OutputFilterResult OutputFilter::command_j(const OutputFilterInput& input) { return command_join(input); } OutputFilterResult OutputFilter::command_kick(const OutputFilterInput& input) { OutputFilterResult result; if (isAChannel(input.destination)) { // get nick to kick QString victim = input.parameter.left(input.parameter.indexOf(' ')); if (victim.isEmpty()) result = usage(i18n("Usage: %1KICK [reason]", Preferences::self()->commandChar())); else { // get kick reason (if any) QString reason = input.parameter.mid(victim.length() + 1); // if no reason given, take default reason if (reason.isEmpty()) reason = m_server->getIdentity()->getKickReason(); result.toServer = "KICK " + input.destination + ' ' + victim + " :" + reason; } } else result = error(i18n("%1KICK only works from within channels.", Preferences::self()->commandChar())); return result; } OutputFilterResult OutputFilter::command_part(const OutputFilterInput& input) { OutputFilterResult result; // No parameter, try default part message if (input.parameter.isEmpty()) { // But only if we actually are in a channel if (isAChannel(input.destination)) result.toServer = "PART " + input.destination + " :" + m_server->getIdentity()->getPartReason(); else if (m_server->getQueryByName(input.destination)) m_server->closeQuery(input.destination); else result = error(i18n("%1PART and %1LEAVE without parameters only work from within a " "channel or a query.", Preferences::self()->commandChar())); } else { // part a given channel if (isAChannel(input.parameter)) { // get channel name QString channel = input.parameter.left(input.parameter.indexOf(' ')); // get part reason (if any) QString reason = input.parameter.mid(channel.length() + 1); // if no reason given, take default reason if (reason.isEmpty()) reason = m_server->getIdentity()->getPartReason(); result.toServer = "PART " + channel + " :" + reason; } // part this channel with a given reason else { if (isAChannel(input.destination)) result.toServer = "PART " + input.destination + " :" + input.parameter; else result = error(i18n("%1PART without channel name only works from within a channel.", Preferences::self()->commandChar())); } } return result; } OutputFilterResult OutputFilter::command_leave(const OutputFilterInput& input) { return command_part(input); } OutputFilterResult OutputFilter::command_topic(const OutputFilterInput& input) { OutputFilterResult result; // No parameter, try to get current topic if (input.parameter.isEmpty()) { // But only if we actually are in a channel if (isAChannel(input.destination)) result.toServer = "TOPIC " + input.destination; else result = error(i18n("%1TOPIC without parameters only works from within a channel.", Preferences::self()->commandChar())); } else { // retrieve or set topic of a given channel if (isAChannel(input.parameter)) { // get channel name QString channel = input.parameter.left(input.parameter.indexOf(' ')); // get topic (if any) QString topic = input.parameter.mid(channel.length()+1); // if no topic given, retrieve topic if (topic.isEmpty()) m_server->requestTopic(channel); // otherwise set topic there else { result.toServer = "TOPIC " + channel + " :"; //If we get passed a ^A as a topic its a sign we should clear the topic. //Used to be a \n, but those get smashed by QStringList::split and readded later //Now is not the time to fight with that. FIXME //If anyone out there *can* actually set the topic to a single ^A, now they have to //specify it twice to get one. if (topic =="\x01\x01") result.toServer += '\x01'; else if (topic!="\x01") result.toServer += topic; } } // set this channel's topic else { if (isAChannel(input.destination)) result.toServer = "TOPIC " + input.destination + " :" + input.parameter; else result = error(i18n("%1TOPIC without channel name only works from within a channel.", Preferences::self()->commandChar())); } } return result; } OutputFilterResult OutputFilter::command_away(const OutputFilterInput& input) { if (input.parameter.isEmpty() && m_server->isAway()) m_server->requestUnaway(); else m_server->requestAway(input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_unaway(const OutputFilterInput& /* input */) { m_server->requestUnaway(); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_back(const OutputFilterInput& input) { return command_unaway(input); } OutputFilterResult OutputFilter::command_aaway(const OutputFilterInput& input) { Application::instance()->getAwayManager()->requestAllAway(input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_aunaway(const OutputFilterInput& /* input */) { Application::instance()->getAwayManager()->requestAllUnaway(); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_aback(const OutputFilterInput& input) { return command_aunaway(input); } OutputFilterResult OutputFilter::command_names(const OutputFilterInput& input) { OutputFilterResult result; result.toServer = "NAMES "; if (input.parameter.isNull()) result = error(i18n("%1NAMES with no target may disconnect you from the server. Specify " "'*' if you really want this.", Preferences::self()->commandChar())); else if (input.parameter != QChar('*')) result.toServer.append(input.parameter); return result; } OutputFilterResult OutputFilter::command_close(const OutputFilterInput& _input) { OutputFilterInput input(_input); OutputFilterResult result; if (input.parameter.isEmpty()) input.parameter = input.destination; if (isAChannel(input.parameter) && m_server->getChannelByName(input.parameter)) m_server->getChannelByName(input.parameter)->closeYourself(false); else if (m_server->getQueryByName(input.parameter)) m_server->getQueryByName(input.parameter)->closeYourself(false); else if (input.parameter.isEmpty()) // this can only mean one thing.. we're in the Server tab m_server->closeYourself(false); else result = usage(i18n("Usage: %1close [window] closes the named channel or query tab, " "or the current tab if none specified.", Preferences::self()->commandChar())); return result; } OutputFilterResult OutputFilter::command_reconnect(const OutputFilterInput& input) { emit reconnectServer(input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_disconnect(const OutputFilterInput& input) { emit disconnectServer(input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_quit(const OutputFilterInput& input) { emit disconnectServer(input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_notice(const OutputFilterInput& input) { OutputFilterResult result; QString recipient = input.parameter.left(input.parameter.indexOf(' ')); QString message = input.parameter.mid(recipient.length()+1); if (input.parameter.isEmpty() || message.isEmpty()) result = usage(i18n("Usage: %1NOTICE ", Preferences::self()->commandChar())); else { result.typeString = i18n("Notice"); result.toServer = "NOTICE " + recipient + " :" + message; result.output=i18nc("%1 is the message, %2 the recipient nickname", "Sending notice \"%1\" to %2.", message, recipient); result.type = Program; } return result; } OutputFilterResult OutputFilter::command_me(const OutputFilterInput& input) { OutputFilterResult result; if (input.destination.isEmpty()) return error(i18n("%1ME only works from a channel, query or DCC chat tab.", Preferences::self()->commandChar())); else if (input.parameter.isEmpty()) return usage(i18n("Usage: %1ME text", Preferences::self()->commandChar())); QString command("PRIVMSGACTION \x01\x01"); QStringList outputList = splitForEncoding(input.destination, input.parameter, m_server->getPreLength(command, input.destination), 2); if (outputList.count() > 1) { command = "PRIVMSG"; outputList += splitForEncoding(input.destination, outputList.at(1), m_server->getPreLength(command, input.destination)); outputList.removeAt(1); result.output.clear(); result.outputList = outputList; for (int i = 0; i < outputList.count(); ++i) { if (i == 0) result.toServerList += "PRIVMSG " + input.destination + " :" + '\x01' + "ACTION " + outputList.at(i) + '\x01'; else result.toServerList += "PRIVMSG " + input.destination + " :" + outputList.at(i); } } else { result.output = input.parameter; result.toServer = "PRIVMSG " + input.destination + " :" + '\x01' + "ACTION " + input.parameter + '\x01'; } result.type = Action; return result; } OutputFilterResult OutputFilter::command_msg(const OutputFilterInput& input) { return handleMsg(input.parameter, false); } OutputFilterResult OutputFilter::command_m(const OutputFilterInput& input) { return command_msg(input); } OutputFilterResult OutputFilter::command_query(const OutputFilterInput& input) { return handleMsg(input.parameter, true); } OutputFilterResult OutputFilter::handleMsg(const QString& parameter, bool commandIsQuery) { OutputFilterResult result; QString recipient = parameter.section(' ', 0, 0, QString::SectionSkipEmpty); QString message = parameter.section(' ', 1); QString output; bool recipientIsAChannel = false; if (recipient.isEmpty()) return error(i18n("You need to specify a recipient.")); else recipientIsAChannel = m_server->isAChannel(recipient); if (commandIsQuery && recipientIsAChannel) return error(i18n("You cannot open queries to channels.")); if (message.trimmed().isEmpty()) { // Empty result - we don't want to send any message to the server. if (!commandIsQuery) return error(i18n("You need to specify a message.")); } else { output = message; if (message.startsWith(Preferences::self()->commandChar() + "me")) result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "ACTION " + message.mid(4) + '\x01'; else result.toServer = "PRIVMSG " + recipient + " :" + message; } // If this is a /query, always open a query window. // Treat "/msg nick" as "/query nick". if (commandIsQuery || output.isEmpty()) { // Note we have to be a bit careful here. // We only want to create ('obtain') a new nickinfo if we have done /query // or "/msg nick". Not "/msg nick message". NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient); ::Query* query = m_server->addQuery(nickInfo, true /*we initiated*/); // Force focus if the user did not specify any message. if (output.isEmpty() && Preferences::self()->focusNewQueries()) emit showView(query); } // Result should be completely empty; if (output.isEmpty()) return result; //FIXME: Don't do below line if query is focused. result.output = output; result.typeString = recipient; result.type = PrivateMessage; return result; } OutputFilterResult OutputFilter::command_smsg(const OutputFilterInput& input) { OutputFilterResult result; QString recipient = input.parameter.left(input.parameter.indexOf(' ')); QString message = input.parameter.mid(recipient.length() + 1); if (message.startsWith(Preferences::self()->commandChar() + "me")) result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "ACTION " + message.mid(4) + '\x01'; else result.toServer = "PRIVMSG " + recipient + " :" + message; return result; } OutputFilterResult OutputFilter::command_ping(const OutputFilterInput& input) { return handleCtcp(input.parameter.section(' ', 0, 0) + " ping"); } OutputFilterResult OutputFilter::command_ctcp(const OutputFilterInput& input) { return handleCtcp(input.parameter); } OutputFilterResult OutputFilter::handleCtcp(const QString& parameter) { OutputFilterResult result; // who is the recipient? QString recipient = parameter.section(' ', 0, 0); // what is the first word of the ctcp? QString request = parameter.section(' ', 1, 1, QString::SectionSkipEmpty).toUpper(); // what is the complete ctcp command? QString message = parameter.section(' ', 2, 0xffffff, QString::SectionSkipEmpty); QString out = request; if (!message.isEmpty()) out+= ' ' + message; if (request == "PING") { unsigned int timeT = QDateTime::currentDateTime().toTime_t(); result.toServer = QString("PRIVMSG %1 :\x01PING %2\x01").arg(recipient).arg(timeT); result.output = i18n("Sending CTCP-%1 request to %2.", QString::fromLatin1("PING"), recipient); } else { result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + out + '\x01'; result.output = i18n("Sending CTCP-%1 request to %2.", out, recipient); } result.typeString = i18n("CTCP"); result.type = Program; return result; } OutputFilterResult OutputFilter::command_ame(const OutputFilterInput& input) { if (input.parameter.isEmpty()) return usage(i18n("Usage: %1AME [-LOCAL] text", Preferences::self()->commandChar())); if (isParameter("local", input.parameter.section(' ', 0, 0))) m_server->sendToAllChannelsAndQueries(Preferences::self()->commandChar() + "me " + input.parameter.section(' ', 1)); else emit multiServerCommand("me", input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_amsg(const OutputFilterInput& input) { if (input.parameter.isEmpty()) return usage(i18n("Usage: %1AMSG [-LOCAL] text", Preferences::self()->commandChar())); if (isParameter("local", input.parameter.section(' ', 0, 0))) m_server->sendToAllChannelsAndQueries(input.parameter.section(' ', 1)); else emit multiServerCommand("msg", input.parameter); return OutputFilterResult(); } OutputFilterResult OutputFilter::command_omsg(const OutputFilterInput& input) { if (input.parameter.isEmpty()) return usage(i18n("Usage: %1OMSG text", Preferences::self()->commandChar())); OutputFilterResult result; result.toServer = "PRIVMSG @" + input.destination + " :" + input.parameter; return result; } OutputFilterResult OutputFilter::command_onotice(const OutputFilterInput& input) { OutputFilterResult result; if (!input.parameter.isEmpty()) { result.toServer = "NOTICE @" + input.destination + " :" + input.parameter; result.typeString = i18n("Notice"); result.type = Program; result.output = i18n("Sending notice \"%1\" to %2.", input.parameter, input.destination); } else result = usage(i18n("Usage: %1ONOTICE text", Preferences::self()->commandChar())); return result; } OutputFilterResult OutputFilter::command_quote(const OutputFilterInput& input) { OutputFilterResult result; if (input.parameter.isEmpty()) result = usage(i18n("Usage: %1QUOTE command list", Preferences::self()->commandChar())); else result.toServer = input.parameter; return result; } OutputFilterResult OutputFilter::command_say(const OutputFilterInput& input) { OutputFilterResult result; if (input.parameter.isEmpty()) result = usage(i18n("Usage: %1SAY text", Preferences::self()->commandChar())); else { result.toServer = "PRIVMSG " + input.destination + " :" + input.parameter; result.output = input.parameter; } return result; } OutputFilterResult OutputFilter::command_dcc(const OutputFilterInput& _input) { OutputFilterInput input(_input); OutputFilterResult result; qDebug() << input.parameter; // No parameter, just open DCC panel if (input.parameter.isEmpty()) { emit addDccPanel(); } else { QStringList parameterList = input.parameter.replace("\\ ", "%20").split(' '); QString dccType = parameterList[0].toLower(); //TODO close should not just refer to the gui-panel, let it close connections if (dccType == "close") { emit closeDccPanel(); } else if (dccType == "send") { if (parameterList.count() == 1) // DCC SEND { emit requestDccSend(); } else if (parameterList.count() == 2) // DCC SEND { emit requestDccSend(parameterList[1]); } else if (parameterList.count() > 2) // DCC SEND [file] ... { // TODO: make sure this will work: //output=i18n("Usage: %1DCC SEND nickname [filename] [filename] ...").arg(commandChar); QUrl fileURL(parameterList[2]); //We could easily check if the remote file exists, but then we might //end up asking for creditionals twice, so settle for only checking locally if (!fileURL.isLocalFile() || QFile::exists(fileURL.path())) { emit openDccSend(parameterList[1],fileURL); } else { result = error(i18n("File \"%1\" does not exist.", parameterList[2])); } } else // Don't know how this should happen, but ... result = usage(i18n("Usage: %1DCC [SEND [nickname [filename]]]", Preferences::self()->commandChar())); } else if (dccType == "get") { //dcc get [nick [file]] switch (parameterList.count()) { case 1: emit acceptDccGet(QString(),QString()); break; case 2: emit acceptDccGet(parameterList.at(1),QString()); break; case 3: emit acceptDccGet(parameterList.at(1),parameterList.at(2)); break; default: result = usage(i18n("Usage: %1DCC [GET [nickname [filename]]]", Preferences::self()->commandChar())); } } else if (dccType == "chat") { //dcc chat nick switch (parameterList.count()) { case 1: emit openDccChat(QString()); break; case 2: emit openDccChat(parameterList[1]); break; default: result = usage(i18n("Usage: %1DCC [CHAT [nickname]]", Preferences::self()->commandChar())); } } else if (dccType == "whiteboard") { //dcc whiteboard nick switch (parameterList.count()) { case 1: emit openDccWBoard(QString()); break; case 2: emit openDccWBoard(parameterList[1]); break; default: result = usage(i18n("Usage: %1DCC [WHITEBOARD [nickname]]", Preferences::self()->commandChar())); } } else result = error(i18n("Unrecognized command %1DCC %2. Possible commands are SEND, " "CHAT, CLOSE, GET, WHITEBOARD.", Preferences::self()->commandChar(), parameterList[0])); } return result; } OutputFilterResult OutputFilter::sendRequest(const QString &recipient, const QString &fileName, const QString &address, quint16 port, quint64 size) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC SEND " + fileName + ' ' + address + ' ' + QString::number(port) + ' ' + QString::number(size) + '\x01'; return result; } OutputFilterResult OutputFilter::passiveSendRequest(const QString& recipient, const QString &fileName, const QString &address, quint64 size, const QString &token) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC SEND " + fileName + ' ' + address + " 0 " + QString::number(size) + ' ' + token + '\x01'; return result; } // Accepting Resume Request OutputFilterResult OutputFilter::acceptResumeRequest(const QString &recipient, const QString &fileName, quint16 port, quint64 startAt) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC ACCEPT " + fileName + ' ' + QString::number(port) + ' ' + QString::number(startAt) + '\x01'; return result; } // Accepting Passive Resume Request OutputFilterResult OutputFilter::acceptPassiveResumeRequest(const QString &recipient, const QString &fileName, quint16 port, quint64 startAt, const QString &token) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC ACCEPT " + fileName + ' ' + QString::number(port) + ' ' + QString::number(startAt) + ' ' + token +'\x01'; return result; } // Requesting Resume Request OutputFilterResult OutputFilter::resumeRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt) { OutputFilterResult result; result.toServer = "PRIVMSG " + sender + " :" + '\x01' + "DCC RESUME " + fileName + ' ' + QString::number(port) + ' ' + QString::number(startAt) + '\x01'; return result; } // Requesting Passive Resume Request OutputFilterResult OutputFilter::resumePassiveRequest(const QString &sender, const QString &fileName, quint16 port, KIO::filesize_t startAt, const QString &token) { OutputFilterResult result; result.toServer = "PRIVMSG " + sender + " :" + '\x01' + "DCC RESUME " + fileName + ' ' + QString::number(port) + ' ' + QString::number(startAt) + ' ' + token + '\x01'; return result; } // Accept Passive Send Request, there active doesn't need that OutputFilterResult OutputFilter::acceptPassiveSendRequest(const QString& recipient, const QString &fileName, const QString &address, quint16 port, quint64 size, const QString &token) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC SEND " + fileName + ' ' + address + ' ' + QString::number(port) + ' ' + QString::number(size) + ' ' + token + '\x01'; return result; } // Rejecting quened dcc receive, is not send when abort an active send OutputFilterResult OutputFilter::rejectDccSend(const QString& partnerNick, const QString & fileName) { OutputFilterResult result; result.toServer = "NOTICE " + partnerNick + " :" + '\x01' + "DCC REJECT SEND " + fileName + '\x01'; return result; } OutputFilterResult OutputFilter::rejectDccChat(const QString & partnerNick, const QString& extension) { OutputFilterResult result; result.toServer = "NOTICE " + partnerNick + " :" + '\x01' + "DCC REJECT CHAT " + extension.toUpper() + '\x01'; return result; } OutputFilterResult OutputFilter::requestDccChat(const QString& partnerNick, const QString& extension, const QString& numericalOwnIp, quint16 ownPort) { OutputFilterResult result; result.toServer = "PRIVMSG " + partnerNick + " :" + '\x01' + "DCC CHAT " + extension.toUpper() + ' ' + numericalOwnIp + ' ' + QString::number(ownPort) + '\x01'; return result; } OutputFilterResult OutputFilter::passiveChatRequest(const QString& recipient, const QString& extension, const QString& address, const QString& token) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC CHAT " + extension.toUpper() + ' ' + address + " 0 " + token + '\x01'; return result; } OutputFilterResult OutputFilter::acceptPassiveChatRequest(const QString& recipient, const QString& extension, const QString& numericalOwnIp, quint16 ownPort, const QString& token) { OutputFilterResult result; result.toServer = "PRIVMSG " + recipient + " :" + '\x01' + "DCC CHAT " + extension.toUpper() + ' ' + numericalOwnIp + ' ' + QString::number(ownPort) + ' ' + token + '\x01'; return result; } OutputFilterResult OutputFilter::command_invite(const OutputFilterInput& input) { OutputFilterResult result; if (input.parameter.isEmpty()) result = usage(i18n("Usage: %1INVITE [channel]", Preferences::self()->commandChar())); else { QString nick = input.parameter.section(' ', 0, 0, QString::SectionSkipEmpty); QString channel = input.parameter.section(' ', 1, 1, QString::SectionSkipEmpty); if (channel.isEmpty()) { if (isAChannel(input.destination)) channel = input.destination; else result = error(i18n("%1INVITE without channel name works only from within channels.", Preferences::self()->commandChar())); } if (!channel.isEmpty()) { if (isAChannel(channel)) result.toServer = "INVITE " + nick + ' ' + channel; else result = error(i18n("%1 is not a channel.", channel)); } } return result; } OutputFilterResult OutputFilter::command_exec(const OutputFilterInput& input) { OutputFilterResult result; if (input.parameter.isEmpty()) result = usage(i18n("Usage: %1EXEC [-SHOWPATH]