diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index d94dbbb..cc5bef4 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,23 +1,19 @@ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Xml Test) -remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) -remove_definitions(-DQT_NO_CAST_FROM_ASCII) -remove_definitions(-DQT_NO_CAST_TO_ASCII) - include(ECMAddTests) ecm_add_tests( kactioncategorytest.cpp kactioncollectiontest.cpp LINK_LIBRARIES Qt5::Test KF5::XmlGui ) ecm_add_tests( kmainwindow_unittest.cpp ktoolbar_unittest.cpp kxmlgui_unittest.cpp GUI LINK_LIBRARIES Qt5::Test KF5::IconThemes KF5::XmlGui ) set_tests_properties(ktoolbar_unittest PROPERTIES RUN_SERIAL TRUE) # it wipes out ~/.qttest/share diff --git a/autotests/kactioncollectiontest.cpp b/autotests/kactioncollectiontest.cpp index eff11b2..0ac4a1a 100644 --- a/autotests/kactioncollectiontest.cpp +++ b/autotests/kactioncollectiontest.cpp @@ -1,248 +1,248 @@ #include #include "kactioncollectiontest.h" #include #include #include #include void tst_KActionCollection::init() { collection = new KActionCollection(static_cast(nullptr)); } void tst_KActionCollection::cleanup() { delete collection; collection = nullptr; } void tst_KActionCollection::clear() { QPointer action1 = collection->add(QStringLiteral("test1")); QPointer action2 = collection->add(QStringLiteral("test2")); QPointer action3 = collection->add(QStringLiteral("test3")); QPointer action4 = collection->add(QStringLiteral("test4")); QPointer action5 = collection->add(QStringLiteral("test5")); QPointer action6 = collection->add(QStringLiteral("test6")); QPointer action7 = collection->add(QStringLiteral("test7")); collection->clear(); QVERIFY(collection->isEmpty()); QVERIFY(action1.isNull()); QVERIFY(action2.isNull()); QVERIFY(action3.isNull()); QVERIFY(action4.isNull()); QVERIFY(action5.isNull()); QVERIFY(action6.isNull()); QVERIFY(action7.isNull()); } void tst_KActionCollection::deleted() { // Delete action -> automatically removed from collection QAction *a = collection->add(QStringLiteral("test")); delete a; QVERIFY(collection->isEmpty()); // Delete action's parent -> automatically removed from collection QWidget *myWidget = new QWidget(nullptr); QPointer action = new QAction(/*i18n()*/ QStringLiteral("Foo"), myWidget); collection->addAction(QStringLiteral("foo"), action); delete myWidget; QVERIFY(collection->isEmpty()); QVERIFY(action.isNull()); // Delete action's parent, but the action was added to another widget with setAssociatedWidget // and that widget gets deleted first. myWidget = new QWidget(nullptr); QWidget *myAssociatedWidget = new QWidget(myWidget); // child widget action = new QAction(/*i18n()*/ QStringLiteral("Foo"), myWidget); // child action collection->addAction(QStringLiteral("foo"), action); collection->addAssociatedWidget(myAssociatedWidget); QVERIFY(myAssociatedWidget->actions().contains(action)); delete myAssociatedWidget; // would be done by the line below, but let's make sure it happens first delete myWidget; QVERIFY(collection->isEmpty()); QVERIFY(action.isNull()); } void tst_KActionCollection::take() { QAction *a = collection->add(QStringLiteral("test")); collection->takeAction(a); QVERIFY(collection->isEmpty()); delete a; } void tst_KActionCollection::writeSettings() { KConfigGroup cfg = clearConfig(); QList defaultShortcut; defaultShortcut << Qt::Key_A << Qt::Key_B; QList temporaryShortcut; temporaryShortcut << Qt::Key_C << Qt::Key_D; QAction *actionWithDifferentShortcut = new QAction(this); collection->setDefaultShortcuts(actionWithDifferentShortcut, defaultShortcut); actionWithDifferentShortcut->setShortcuts(temporaryShortcut); collection->addAction(QStringLiteral("actionWithDifferentShortcut"), actionWithDifferentShortcut); QAction *immutableAction = new QAction(this); collection->setDefaultShortcuts(immutableAction, defaultShortcut); immutableAction->setShortcuts(temporaryShortcut); collection->setShortcutsConfigurable(immutableAction, false); collection->addAction(QStringLiteral("immutableAction"), immutableAction); QAction *actionWithSameShortcut = new QAction(this); collection->setDefaultShortcuts(actionWithSameShortcut, defaultShortcut); collection->addAction(QStringLiteral("actionWithSameShortcut"), actionWithSameShortcut); cfg.writeEntry("actionToDelete", QStringLiteral("Foobar")); QAction *actionToDelete = new QAction(this); collection->setDefaultShortcuts(actionToDelete, defaultShortcut); collection->addAction(QStringLiteral("actionToDelete"), actionToDelete); collection->writeSettings(&cfg); QCOMPARE(cfg.readEntry("actionWithDifferentShortcut", QString()), QKeySequence::listToString(actionWithDifferentShortcut->shortcuts())); QCOMPARE(cfg.readEntry("immutableAction", QString()), QString()); QCOMPARE(cfg.readEntry("actionWithSameShortcut", QString()), QString()); QCOMPARE(cfg.readEntry("actionToDelete", QString()), QString()); qDeleteAll(collection->actions()); } void tst_KActionCollection::readSettings() { KConfigGroup cfg = clearConfig(); QList defaultShortcut; defaultShortcut << Qt::Key_A << Qt::Key_B; QList temporaryShortcut; temporaryShortcut << Qt::Key_C << Qt::Key_D; cfg.writeEntry("normalAction", QKeySequence::listToString(defaultShortcut)); cfg.writeEntry("immutable", QKeySequence::listToString(defaultShortcut)); cfg.writeEntry("empty", QString()); QAction *normal = new QAction(this); collection->addAction(QStringLiteral("normalAction"), normal); QAction *immutable = new QAction(this); immutable->setShortcuts(temporaryShortcut); collection->setDefaultShortcuts(immutable, temporaryShortcut); collection->setShortcutsConfigurable(immutable, false); collection->addAction(QStringLiteral("immutable"), immutable); QAction *empty = new QAction(this); collection->addAction(QStringLiteral("empty"), empty); collection->setDefaultShortcuts(empty, defaultShortcut); empty->setShortcuts(temporaryShortcut); QCOMPARE(QKeySequence::listToString(empty->shortcuts()), QKeySequence::listToString(temporaryShortcut)); collection->readSettings(&cfg); QCOMPARE(QKeySequence::listToString(normal->shortcuts()), QKeySequence::listToString(defaultShortcut)); QCOMPARE(QKeySequence::listToString(empty->shortcuts()), QKeySequence::listToString(defaultShortcut)); QCOMPARE(QKeySequence::listToString(immutable->shortcuts()), QKeySequence::listToString(temporaryShortcut)); qDeleteAll(collection->actions()); } void tst_KActionCollection::insertReplaces1() { QAction *a = new QAction(nullptr); QAction *b = new QAction(nullptr); collection->addAction(QStringLiteral("a"), a); QVERIFY(collection->actions().contains(a)); QVERIFY(collection->action(QStringLiteral("a")) == a); collection->addAction(QStringLiteral("a"), b); QVERIFY(!collection->actions().contains(a)); QVERIFY(collection->actions().contains(b)); QVERIFY(collection->action(QStringLiteral("a")) == b); delete a; delete b; } /** * Check that a action added twice under different names only ends up once in * the collection */ void tst_KActionCollection::insertReplaces2() { QAction *a = new QAction(nullptr); collection->addAction(QStringLiteral("a"), a); QVERIFY(collection->actions().contains(a)); QVERIFY(collection->action(QStringLiteral("a")) == a); // Simple test: Just add it twice collection->addAction(QStringLiteral("b"), a); QVERIFY(collection->actions().contains(a)); QVERIFY(!collection->action(QStringLiteral("a"))); QVERIFY(collection->action(QStringLiteral("b")) == a); // Complex text: Mesh with the objectname a->setObjectName(QStringLiteral("c")); collection->addAction(QStringLiteral("d"), a); QVERIFY(collection->actions().contains(a)); QVERIFY(!collection->action(QStringLiteral("b"))); QVERIFY(!collection->action(QStringLiteral("c"))); QVERIFY(collection->action(QStringLiteral("d")) == a); delete a; } KConfigGroup tst_KActionCollection::clearConfig() { KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); cfg->deleteGroup(collection->configGroup()); return KConfigGroup(cfg, collection->configGroup()); } void tst_KActionCollection::testSetShortcuts() { QAction *action = new QAction(/*i18n*/(QStringLiteral("Next Unread &Folder")), this); collection->addAction(QStringLiteral("go_next_unread_folder"), action); collection->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Plus)); QList shortcut = action->shortcuts(); shortcut << QKeySequence(Qt::CTRL + Qt::Key_Plus); action->setShortcuts(shortcut); QCOMPARE(QKeySequence::listToString(action->shortcuts()), QStringLiteral("Alt++; Ctrl++")); // Simpler way: QList shortcut2; shortcut2 << QKeySequence(Qt::ALT + Qt::Key_Plus) << QKeySequence(Qt::CTRL + Qt::Key_Plus); QCOMPARE(QKeySequence::listToString(shortcut2), QStringLiteral("Alt++; Ctrl++")); } void tst_KActionCollection::implicitStandardActionInsertionUsingCreate() { KActionCollection collection(static_cast(nullptr)); QAction *a = KStandardAction::create(KStandardAction::Undo, qApp, SLOT(quit()), &collection); QVERIFY(a); QVERIFY(a->parent() == &collection); - QVERIFY(collection.action(KStandardAction::name(KStandardAction::Undo)) == a); + QVERIFY(collection.action(QString::fromLatin1(KStandardAction::name(KStandardAction::Undo))) == a); } void tst_KActionCollection::implicitStandardActionInsertionUsingCut() { KActionCollection collection(static_cast(nullptr)); QAction *cut = KStandardAction::cut(&collection); - QAction *a = collection.action(KStandardAction::name(KStandardAction::Cut)); + QAction *a = collection.action(QString::fromLatin1(KStandardAction::name(KStandardAction::Cut))); QVERIFY(a); QVERIFY(a == cut); } QTEST_MAIN(tst_KActionCollection) diff --git a/autotests/kxmlgui_unittest.cpp b/autotests/kxmlgui_unittest.cpp index 05d1fef..f8aaf05 100644 --- a/autotests/kxmlgui_unittest.cpp +++ b/autotests/kxmlgui_unittest.cpp @@ -1,1078 +1,1078 @@ /* This file is part of the KDE libraries Copyright 2007-2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kxmlgui_unittest.h" #include "testxmlguiwindow.h" #include "testguiclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // it's not exported, so we need to include the code here #include QTEST_MAIN(KXmlGui_UnitTest) enum Flags { NoFlags = 0, AddToolBars = 1, AddModifiedToolBars = 2, AddActionProperties = 4, AddModifiedMenus = 8 // next item is 16 }; static void createXmlFile(QFile &file, int version, int flags, const QByteArray &toplevelTag = "gui") { const QByteArray xml = "\n" "\n" "<" + toplevelTag + " version=\"" + QByteArray::number(version) + "\" name=\"foo\" >\n" "\n"; file.write(xml); if (flags & AddModifiedMenus) { file.write( "\n" "&File\n" "\n" "\n" ); } file.write("\n"); if (flags & AddToolBars) { file.write( "\n" " Main Toolbar\n" " \n" "\n" "\n" " Bookmark Toolbar\n" "\n" ); } if (flags & AddModifiedToolBars) { file.write( "\n" " Main Toolbar\n" " \n" "\n" "\n" " Modified toolbars\n" "\n" ); } if (flags & AddActionProperties) { file.write( "\n" " \n" "\n" ); } file.write("\n"); } static void clickApply(KEditToolBar *dialog) { QDialogButtonBox *box = dialog->findChild(); Q_ASSERT(box != nullptr); box->button(QDialogButtonBox::Apply)->setEnabled(true); box->button(QDialogButtonBox::Apply)->click(); } void KXmlGui_UnitTest::initTestCase() { QStandardPaths::enableTestMode(true); // Leftover configuration breaks testAutoSaveSettings const QString configFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, KSharedConfig::openConfig()->name()); if (!configFile.isEmpty()) { qDebug() << "Removing old config file"; QFile::remove(configFile); KSharedConfig::openConfig()->reparseConfiguration(); } } void KXmlGui_UnitTest::testFindVersionNumber_data() { QTest::addColumn("xml"); QTest::addColumn("version"); QTest::newRow("simple test") << "\n" "\n" "\n" << "3"; QTest::newRow("two digits") << "\n" "\n" << "42"; QTest::newRow("with spaces") << // as found in dirfilterplugin.rc for instance "\n" "\n" << "1"; QTest::newRow("with a dot") << // as was found in autorefresh.rc "\n" "\n" << QString() /*error*/; QTest::newRow("with a comment") << // as found in kmail.rc "\n" "\n" "\n" << "452"; } void KXmlGui_UnitTest::testFindVersionNumber() { QFETCH(QString, xml); QFETCH(QString, version); QCOMPARE(KXmlGuiVersionHandler::findVersionNumber(xml), version); } void KXmlGui_UnitTest::testVersionHandlerSameVersion() { // This emulates the case where the user has modified stuff locally // and the application hasn't changed since, so the version number is unchanged. QTemporaryFile userFile; QVERIFY(userFile.open()); createXmlFile(userFile, 2, AddActionProperties | AddModifiedToolBars); const QString firstFile = userFile.fileName(); QTemporaryFile appFile; QVERIFY(appFile.open()); createXmlFile(appFile, 2, AddToolBars); const QString secondFile = appFile.fileName(); QStringList files; files << firstFile << secondFile; userFile.close(); appFile.close(); KXmlGuiVersionHandler versionHandler(files); QCOMPARE(versionHandler.finalFile(), firstFile); QString finalDoc = versionHandler.finalDocument(); QVERIFY(finalDoc.startsWith(QStringLiteral(""))); QVERIFY(finalDoc.contains(QStringLiteral("sidebartng"))); // Check that the toolbars modified by the user were kept QVERIFY(finalDoc.contains(QStringLiteral(" fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); - QFile fileV2(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "testui.rc"); + QFile fileV2(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QStringLiteral("testui.rc")); QVERIFY2(fileV2.open(QIODevice::WriteOnly), qPrintable(fileV2.fileName())); createXmlFile(fileV2, 2, NoFlags); fileToVersionMap.insert(fileV2.fileName(), 2); QTemporaryFile fileV5; QVERIFY(fileV5.open()); createXmlFile(fileV5, 5, NoFlags); fileToVersionMap.insert(fileV5.fileName(), 5); // The highest version is neither the first nor last one in the list, // to make sure the code really selects the highest version, not just by chance :) // (This is why we add the v1 version at the end of the list) QTemporaryFile fileV1; QVERIFY(fileV1.open()); createXmlFile(fileV1, 1, NoFlags); fileToVersionMap.insert(fileV1.fileName(), 1); QStringList files; files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName(); fileV2.close(); fileV5.close(); fileV1.close(); KXmlGuiVersionHandler versionHandler(files); QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 5); QString finalDoc = versionHandler.finalDocument(); QVERIFY(finalDoc.startsWith(QStringLiteral(" fileToVersionMap; // makes QCOMPARE failures more readable than just temp filenames // local file - QFile fileV2(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "testui.rc"); + QFile fileV2(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QStringLiteral("testui.rc")); QVERIFY(fileV2.open(QIODevice::WriteOnly)); createXmlFile(fileV2, 2, AddActionProperties | AddModifiedToolBars); fileToVersionMap.insert(fileV2.fileName(), 2); // more-global file QTemporaryFile fileV5; QVERIFY(fileV5.open()); createXmlFile(fileV5, 5, AddToolBars | AddModifiedMenus, "kpartgui"); fileToVersionMap.insert(fileV5.fileName(), 5); // The highest version is neither the first nor last one in the list, // to make sure the code really selects the highest version, not just by chance :) // (This is why we add the v1 version at the end of the list) QTemporaryFile fileV1; QVERIFY(fileV1.open()); createXmlFile(fileV1, 1, AddToolBars); fileToVersionMap.insert(fileV1.fileName(), 1); QStringList files; files << fileV2.fileName() << fileV5.fileName() << fileV1.fileName(); fileV2.close(); fileV5.close(); fileV1.close(); KXmlGuiVersionHandler versionHandler(files); // We end up with the local file, so in our map it has version 2. // But of course by now it says "version=5" in it :) QCOMPARE(fileToVersionMap.value(versionHandler.finalFile()), 2); const QString finalDoc = versionHandler.finalDocument(); //qDebug() << finalDoc; QVERIFY(finalDoc.startsWith(QStringLiteral(""))); QVERIFY(finalDoc.contains(QStringLiteral("sidebartng"))); // Check that the menus modified by the app are still there QVERIFY(finalDoc.contains(QStringLiteral(" containers = factory.containers(QStringLiteral("Menu")); QStringList containerNames; Q_FOREACH (QWidget *w, containers) { containerNames << w->objectName(); } return containerNames; } void debugActions(const QList &actions) { Q_FOREACH (QAction *action, actions) { - qDebug() << (action->isSeparator() ? QString("separator") : action->objectName()); + qDebug() << (action->isSeparator() ? QString::fromLatin1("separator") : action->objectName()); } } static void checkActions(const QList &actions, const QStringList &expectedActions) { for (int i = 0; i < expectedActions.count(); ++i) { if (i >= actions.count()) break; QAction *action = actions.at(i); if (action->isSeparator()) { QCOMPARE(QStringLiteral("separator"), expectedActions[i]); } else { QCOMPARE(action->objectName(), expectedActions[i]); } } QCOMPARE(actions.count(), expectedActions.count()); } void KXmlGui_UnitTest::testPartMerging() { const QByteArray hostXml = "\n" "\n" "\n" "\n" " &Go\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " Section title\n" " \n" " \n" " &File\n" " \n" " \n" "\n" "\n"; TestGuiClient hostClient; hostClient.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home") << QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("last_from_host") << QStringLiteral("file_new") << QStringLiteral("file_open") << QStringLiteral("file_quit")); hostClient.createGUI(hostXml, true /*ui_standards.rc*/); KMainWindow mainWindow; KXMLGUIBuilder builder(&mainWindow); KXMLGUIFactory factory(&builder); factory.addClient(&hostClient); const QString hostDomDoc = hostClient.domDocument().toString(); QWidget *goMenuW = factory.container(QStringLiteral("go"), &hostClient); QVERIFY(goMenuW); QMenu *goMenu = qobject_cast(goMenuW); QVERIFY(goMenu); QMenu *fileMenu = qobject_cast(factory.container(QStringLiteral("file"), &hostClient)); //debugActions(goMenu->actions()); checkActions(goMenu->actions(), QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home") << QStringLiteral("separator") << QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("separator") << QStringLiteral("separator") // separator << QStringLiteral("last_from_host")); checkActions(fileMenu->actions(), QStringList() << QStringLiteral("file_new") << QStringLiteral("file_open") << QStringLiteral("separator") << QStringLiteral("file_quit")); qDebug() << "Now merging the part"; const QByteArray partXml = "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"part\" >\n" "<MenuBar>\n" " <Menu name=\"go\"><text>&Go</text>\n" " <Action name=\"go_previous\" group=\"before_merge\"/>\n" " <Action name=\"go_next\" group=\"before_merge\"/>\n" " <Separator/>\n" " <Action name=\"first_page\"/>\n" " <Action name=\"last_page\"/>\n" " <Title>Part Section\n" " \n" " \n" " \n" " \n" " \n" " &File\n" " \n" " \n" " \n" "\n" "\n"; TestGuiClient partClient(partXml); partClient.createActions(QStringList() << QStringLiteral("go_previous") << QStringLiteral("go_next") << QStringLiteral("first_page") << QStringLiteral("last_page") << QStringLiteral("last_from_part") << QStringLiteral("action_in_merge_group") << QStringLiteral("undefined_group") << QStringLiteral("action_in_placed_merge") << QStringLiteral("other_file_action") << QStringLiteral("action1") << QStringLiteral("action2")); const QList actionList = { partClient.actionCollection()->action(QStringLiteral("action1")), partClient.actionCollection()->action(QStringLiteral("action2")) }; for (int i = 0 ; i < 5 ; ++i) { //qDebug() << "addClient, iteration" << i; factory.addClient(&partClient); partClient.plugActionList(QStringLiteral("action_list"), actionList); //debugActions(goMenu->actions()); checkActions(goMenu->actions(), QStringList() << QStringLiteral("go_up") << QStringLiteral("go_back") << QStringLiteral("go_forward") << QStringLiteral("go_home") << QStringLiteral("separator") << QStringLiteral("go_previous") << QStringLiteral("go_next") // Contents of the : << QStringLiteral("separator") << QStringLiteral("first_page") << QStringLiteral("last_page") << QStringLiteral("separator") // in the part << QStringLiteral("action1") << QStringLiteral("action2") << QStringLiteral("undefined_group") << QStringLiteral("last_from_part") // End of <Merge> << QStringLiteral("host_after_merge") << QStringLiteral("host_after_merge_2") << QStringLiteral("separator") // Contents of <DefineGroup> << QStringLiteral("action_in_merge_group") // End of <DefineGroup> << QStringLiteral("separator") // <title> is a separator qaction with text << QStringLiteral("last_from_host") ); checkActions(fileMenu->actions(), QStringList() << QStringLiteral("file_new") << QStringLiteral("action_in_placed_merge") << QStringLiteral("file_open") << QStringLiteral("separator") << QStringLiteral("file_quit") << QStringLiteral("other_file_action")); factory.removeClient(&partClient); QCOMPARE(hostClient.domDocument().toString(), hostDomDoc); } factory.removeClient(&hostClient); } void KXmlGui_UnitTest::testPartMergingSettings() // #252911 { const QByteArray hostXml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" // The solution was to remove the duplicated definition // " <Menu name=\"settings\"><text>&Settings</text>\n" // " <Action name=\"options_configure_keybinding\"/>\n" // " <Action name=\"options_configure_toolbars\"/>\n" // " <Merge name=\"configure_merge\"/>\n" // " <Separator/>\n" // " <Merge/>\n" // " </Menu>\n" "</MenuBar></gui>\n"; TestGuiClient hostClient; hostClient.createActions(QStringList() << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars")); hostClient.createGUI(hostXml, true /*ui_standards.rc*/); //qDebug() << hostClient.domDocument().toString(); KMainWindow mainWindow; KXMLGUIBuilder builder(&mainWindow); KXMLGUIFactory factory(&builder); factory.addClient(&hostClient); QWidget *settingsMenu = qobject_cast<QMenu *>(factory.container(QStringLiteral("settings"), &hostClient)); QVERIFY(settingsMenu); //debugActions(settingsMenu->actions()); const QByteArray partXml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"settings\"><text>&Settings</text>\n" " <Action name=\"configure_klinkstatus\"/>\n" " </Menu>\n" "</MenuBar></gui>\n"; TestGuiClient partClient(partXml); partClient.createActions(QStringList() << QStringLiteral("configure_klinkstatus")); factory.addClient(&partClient); //debugActions(settingsMenu->actions()); checkActions(settingsMenu->actions(), QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it << QStringLiteral("options_configure_keybinding") << QStringLiteral("options_configure_toolbars") << QStringLiteral("configure_klinkstatus")); factory.removeClient(&partClient); factory.removeClient(&hostClient); } void KXmlGui_UnitTest::testUiStandardsMerging_data() { QTest::addColumn<QByteArray>("xml"); QTest::addColumn<QStringList>("actions"); QTest::addColumn<QStringList>("expectedMenus"); const QByteArray xmlBegin = "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n"; const QByteArray xmlEnd = "</MenuBar>\n" "</gui>"; // Merging an empty menu (or a menu with only non-existing actions) would make // the empty menu appear at the end after all other menus (fixed for KDE-4.2) QTest::newRow("empty file menu, implicit settings menu") << QByteArray(xmlBegin + "<Menu name=\"file\"/>\n" + xmlEnd) << (QStringList() << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("settings")); QTest::newRow("file menu with non existing action, implicit settings menu") << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"foo\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("settings")); QTest::newRow("file menu with existing action, implicit settings menu") << QByteArray(xmlBegin + "<Menu name=\"file\"><Action name=\"open\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("open") << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); QTest::newRow("implicit file and settings menu") << QByteArray(xmlBegin + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we could check that file_open is in the mainToolBar, too // Check that unknown non-empty menus are added at the "MergeLocal" position (before settings). QTest::newRow("foo menu added at the end") << QByteArray(xmlBegin + "<Menu name=\"foo\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars") << QStringLiteral("foo_action")) << (QStringList() << QStringLiteral("file") << QStringLiteral("foo") << QStringLiteral("settings")); QTest::newRow("Bille's testcase: menu patch + menu edit") << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n" + "<Menu name=\"edit\"><Action name=\"edit_foo\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo")) << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch")); QTest::newRow("Bille's testcase: menu patch + menu edit, lowercase tag") << QByteArray(xmlBegin + "<Menu name=\"patch\"><Action name=\"patch_generate\"/></Menu>\n" + "<menu name=\"edit\"><Action name=\"edit_foo\"/></menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("patch_generate") << QStringLiteral("edit_foo")) << (QStringList() << QStringLiteral("file") << QStringLiteral("edit") << QStringLiteral("patch")); // Check that <Menu append="..."> allows to insert menus at specific positions QTest::newRow("Menu append") << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"settings_merge\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars") << QStringLiteral("foo_action") << QStringLiteral("help_contents")) << (QStringList() << QStringLiteral("file") << QStringLiteral("settings") << QStringLiteral("foo") << QStringLiteral("help")); QTest::newRow("Custom first menu") << QByteArray(xmlBegin + "<Menu name=\"foo\" append=\"first_menu\"><Action name=\"foo_action\"/></Menu>\n" + xmlEnd) << (QStringList() << QStringLiteral("edit_undo") << QStringLiteral("foo_action") << QStringLiteral("help_contents")) << (QStringList() << QStringLiteral("foo") << QStringLiteral("edit") << QStringLiteral("help")); // Tests for noMerge="1" QTest::newRow("noMerge empty file menu, implicit settings menu") << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("file") << QStringLiteral("settings")); // we keep empty menus, see #186382 QTest::newRow("noMerge empty file menu, file_open moved elsewhere") << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd) << (QStringList() << QStringLiteral("file_open")) << (QStringList() << QStringLiteral("file") << QStringLiteral("foo")); QTest::newRow("noMerge file menu with open before new") << QByteArray(xmlBegin + "<Menu name=\"file\" noMerge=\"1\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new")) << (QStringList() << QStringLiteral("file")); // TODO check the order of the actions in the menu? how? // Tests for deleted="true" QTest::newRow("deleted file menu, implicit settings menu") << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("options_configure_toolbars")) << (QStringList() << QStringLiteral("settings")); QTest::newRow("deleted file menu, file_open moved elsewhere") << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"/>\n<Menu name=\"foo\"><Action name=\"file_open\"/></Menu>" + xmlEnd) << (QStringList() << QStringLiteral("file_open")) << (QStringList() << QStringLiteral("foo")); QTest::newRow("deleted file menu with actions (contradiction)") << QByteArray(xmlBegin + "<Menu name=\"file\" deleted=\"true\"><Action name=\"file_open\"/><Action name=\"file_new\"/></Menu>" + xmlEnd) << (QStringList() << QStringLiteral("file_open") << QStringLiteral("file_new")) << (QStringList()); } void KXmlGui_UnitTest::testUiStandardsMerging() { QFETCH(QByteArray, xml); QFETCH(QStringList, actions); QFETCH(QStringList, expectedMenus); TestGuiClient client; client.createActions(actions); client.createGUI(xml, true /*ui_standards.rc*/); const QDomDocument domDocument = client.domDocument(); const QDomElement docElem = domDocument.documentElement(); QCOMPARE(docElem.attribute(QStringLiteral("name")), QStringLiteral("foo")); // not standard_containers from ui_standards.rc QCOMPARE(docElem.attribute(QStringLiteral("version")), QStringLiteral("1")); // not standard_containers from ui_standards.rc KMainWindow mainWindow; KXMLGUIBuilder builder(&mainWindow); KXMLGUIFactory factory(&builder); factory.addClient(&client); const QStringList containerNames = collectMenuNames(factory); //qDebug() << containerNames; QCOMPARE(containerNames, expectedMenus); factory.removeClient(&client); } void KXmlGui_UnitTest::testActionListAndSeparator() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"groups\"><text>Add to Group</text>\n" " <ActionList name=\"view_groups_list\"/>\n" " <Separator />" " <Action name=\"view_add_to_new_group\" />\n" " <ActionList name=\"second_list\"/>\n" " </Menu>\n" "</MenuBar>\n" "</gui>"; TestGuiClient client(xml); client.createActions(QStringList() << QStringLiteral("view_add_to_new_group")); KMainWindow mainWindow; KXMLGUIBuilder builder(&mainWindow); KXMLGUIFactory factory(&builder); factory.addClient(&client); QWidget *menuW = factory.container(QStringLiteral("groups"), &client); QVERIFY(menuW); QMenu *menu = qobject_cast<QMenu *>(menuW); QVERIFY(menu); //debugActions(menu->actions()); checkActions(menu->actions(), QStringList() << QStringLiteral("separator") // that's ok, QMenuPrivate::filterActions won't show it << QStringLiteral("view_add_to_new_group")); qDebug() << "Now plugging the actionlist"; QAction *action1 = new QAction(this); action1->setObjectName(QStringLiteral("action1")); client.actionCollection()->setDefaultShortcut(action1, QKeySequence(QStringLiteral("Ctrl+2"))); QAction *action2 = new QAction(this); action2->setObjectName(QStringLiteral("action2")); const QList<QAction *> actionList = { action1, action2 }; client.plugActionList(QStringLiteral("view_groups_list"), actionList); QCOMPARE(QKeySequence::listToString(action1->shortcuts()), QStringLiteral("Ctrl+2")); const QStringList expectedActionsOneList = { QStringLiteral("action1"), QStringLiteral("action2"), QStringLiteral("separator"), QStringLiteral("view_add_to_new_group") }; //debugActions(menu->actions()); checkActions(menu->actions(), expectedActionsOneList); QAction *action3 = new QAction(this); action3->setObjectName(QStringLiteral("action3")); const QList<QAction *> secondActionList = { action3 }; client.plugActionList(QStringLiteral("second_list"), secondActionList); QStringList expectedActions = expectedActionsOneList; expectedActions << QStringLiteral("action3"); checkActions(menu->actions(), expectedActions); qDebug() << "Now remove+add gui client"; // While I'm here, what happens with the action list if I remove+add the guiclient, // like KXmlGuiWindow::newToolBarConfig does? factory.removeClient(&client); factory.addClient(&client); // We need to get the container widget again, it was re-created. menuW = factory.container(QStringLiteral("groups"), &client); QVERIFY(menuW); menu = qobject_cast<QMenu *>(menuW); //debugActions(menu->actions()); checkActions(menu->actions(), QStringList() << QStringLiteral("separator") // yep, it removed the actionlist thing... << QStringLiteral("view_add_to_new_group")); qDebug() << "Now plugging the actionlist again"; client.plugActionList(QStringLiteral("second_list"), secondActionList); client.plugActionList(QStringLiteral("view_groups_list"), actionList); checkActions(menu->actions(), expectedActions); factory.removeClient(&client); } void KXmlGui_UnitTest::testHiddenToolBar() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" "</MenuBar>\n" "<ToolBar hidden=\"true\" name=\"mainToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "<ToolBar name=\"visibleToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "<ToolBar hidden=\"true\" name=\"hiddenToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "</gui>\n"; KConfigGroup cg(KSharedConfig::openConfig(), "testHiddenToolBar"); TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc"); mainWindow.setAutoSaveSettings(cg); mainWindow.createActions(QStringList() << QStringLiteral("go_up")); mainWindow.createGUI(); KToolBar *mainToolBar = mainWindow.toolBarByName(QStringLiteral("mainToolBar")); QVERIFY(mainToolBar->isHidden()); KXMLGUIFactory *factory = mainWindow.guiFactory(); QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden()); KToolBar *hiddenToolBar = qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow)); qDebug() << hiddenToolBar; QVERIFY(hiddenToolBar->isHidden()); // Now open KEditToolBar (#105525) KEditToolBar editToolBar(factory); // KEditToolBar loads the stuff in showEvent... QShowEvent ev; qApp->sendEvent(&editToolBar, &ev); clickApply(&editToolBar); QVERIFY(qobject_cast<KToolBar *>(factory->container(QStringLiteral("hiddenToolBar"), &mainWindow))->isHidden()); mainWindow.close(); } // taken from KMainWindow_UnitTest::testAutoSaveSettings() void KXmlGui_UnitTest::testAutoSaveSettings() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" "</MenuBar>\n" "<ToolBar name=\"mainToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "<ToolBar name=\"secondToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "</gui>\n"; { // do not interfere with the "toolbarVisibility" unit test KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings"); TestXmlGuiWindow mw(xml, "kxmlgui_unittest.rc"); mw.show(); mw.setAutoSaveSettings(cg); // Test resizing first (like show() does). mw.reallyResize(400, 400); QTest::qWait(200); mw.createActions(QStringList() << QStringLiteral("go_up")); mw.createGUI(); // Resize again, should be saved mw.reallyResize(800, 600); QTest::qWait(200); KToolBar *mainToolBar = mw.toolBarByName(QStringLiteral("mainToolBar")); QCOMPARE(mw.toolBarArea(mainToolBar), Qt::TopToolBarArea); KToolBar *secondToolBar = mw.toolBarByName(QStringLiteral("secondToolBar")); QCOMPARE((int)mw.toolBarArea(secondToolBar), (int)Qt::TopToolBarArea); // REFERENCE #1 (see below) // Move second toolbar to bottom const QPoint oldPos = secondToolBar->pos(); mw.addToolBar(Qt::BottomToolBarArea, secondToolBar); const QPoint newPos = secondToolBar->pos(); QCOMPARE(mw.toolBarArea(secondToolBar), Qt::BottomToolBarArea); // Calling to addToolBar is not enough to trigger the event filter for move events // in KMainWindow, because there is no layouting happening in hidden mainwindows. QMoveEvent moveEvent(newPos, oldPos); QApplication::sendEvent(secondToolBar, &moveEvent); mw.close(); } { KConfigGroup cg(KSharedConfig::openConfig(), "testAutoSaveSettings"); TestXmlGuiWindow mw2(xml, "kxmlgui_unittest.rc"); mw2.show(); mw2.setAutoSaveSettings(cg); QTest::qWait(200); // Check window size was restored QCOMPARE(mw2.size(), QSize(800, 600)); mw2.createActions(QStringList() << QStringLiteral("go_up")); mw2.createGUI(); // Force window layout to happen mw2.reallyResize(800, 600); QTest::qWait(200); // Check toolbar positions were restored KToolBar *mainToolBar = mw2.toolBarByName(QStringLiteral("mainToolBar")); QCOMPARE(mw2.toolBarArea(mainToolBar), Qt::TopToolBarArea); KToolBar *secondToolBar = mw2.toolBarByName(QStringLiteral("secondToolBar")); QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea); mw2.applyMainWindowSettings(mw2.autoSaveConfigGroup()); QCOMPARE(mw2.toolBarArea(secondToolBar), Qt::BottomToolBarArea); } } void KXmlGui_UnitTest::testDeletedContainers() // deleted="true" { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu deleted=\"true\" name=\"game\"/>\n" "</MenuBar>\n" "<ToolBar deleted=\"true\" name=\"mainToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "<ToolBar name=\"visibleToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "<ToolBar deleted=\"true\" name=\"deletedToolBar\">\n" " <Action name=\"go_up\"/>\n" "</ToolBar>\n" "</gui>\n"; KConfigGroup cg(KSharedConfig::openConfig(), "testDeletedToolBar"); TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc"); mainWindow.setAutoSaveSettings(cg); mainWindow.createActions(QStringList() << QStringLiteral("go_up") << QStringLiteral("file_new") << QStringLiteral("game_new")); mainWindow.createGUI(); KXMLGUIFactory *factory = mainWindow.guiFactory(); //qDebug() << "containers:" << factory->containers("ToolBar"); QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow)); QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden()); QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow)); QVERIFY(factory->container(QStringLiteral("file"), &mainWindow)); // File menu was created QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow)); // Game menu was not created // Now open KEditToolBar, just to check it doesn't crash. KEditToolBar editToolBar(factory); // KEditToolBar loads the stuff in showEvent... QShowEvent ev; qApp->sendEvent(&editToolBar, &ev); clickApply(&editToolBar); QVERIFY(!factory->container(QStringLiteral("mainToolBar"), &mainWindow)); QVERIFY(!factory->container(QStringLiteral("visibleToolBar"), &mainWindow)->isHidden()); QVERIFY(!factory->container(QStringLiteral("deletedToolBar"), &mainWindow)); QVERIFY(factory->container(QStringLiteral("file"), &mainWindow)); QVERIFY(!factory->container(QStringLiteral("game"), &mainWindow)); mainWindow.close(); } void KXmlGui_UnitTest::testTopLevelSeparator() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"before_separator\"><text>Before Separator</text></Menu>\n" " <Separator />\n" " <Menu name=\"after_separator\"><text>After Separator</text></Menu>\n" "</MenuBar>\n" "</gui>"; TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc"); mainWindow.createGUI(); checkActions(mainWindow.menuBar()->actions(), QStringList() << QStringLiteral("before_separator") << QStringLiteral("separator") << QStringLiteral("after_separator") << QStringLiteral("separator") << QStringLiteral("help")); } // Check that the objectName() of the menus is set from the name in the XML file void KXmlGui_UnitTest::testMenuNames() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"filemenu\"><text>File Menu</text></Menu>\n" "</MenuBar>\n" "</gui>"; TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc"); mainWindow.createGUI(); checkActions(mainWindow.menuBar()->actions(), QStringList() << QStringLiteral("filemenu") << QStringLiteral("separator") << QStringLiteral("help")); } // Test what happens when the application's rc file isn't found // We want a warning to be printed, but we don't want to see all menus from ui_standards.rc void KXmlGui_UnitTest::testMenusNoXmlFile() { TestXmlGuiWindow mainWindow(QByteArray(), "nolocalfile_either.rc"); mainWindow.createGUIBad(); checkActions(mainWindow.menuBar()->actions(), QStringList() << QStringLiteral("help")); } void KXmlGui_UnitTest::testXMLFileReplacement() { // to differentiate "original" and replacement xml file, one is created with "modified" toolbars QTemporaryFile fileOrig; QVERIFY(fileOrig.open()); createXmlFile(fileOrig, 2, AddToolBars); const QString filenameOrig = fileOrig.fileName(); fileOrig.close(); QTemporaryFile fileReplace; QVERIFY(fileReplace.open()); createXmlFile(fileReplace, 2, AddModifiedToolBars); const QString filenameReplace = fileReplace.fileName(); fileReplace.close(); // finally, our local xml file has <ActionProperties/> - QFile fileLocal(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "testui.rc"); + QFile fileLocal(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QStringLiteral("testui.rc")); QVERIFY2(fileLocal.open(QIODevice::WriteOnly), qPrintable(fileLocal.fileName())); createXmlFile(fileLocal, 1, AddActionProperties); const QString filenameLocal = fileLocal.fileName(); fileLocal.close(); TestGuiClient client; // first make sure that the "original" file is loaded, correctly client.setXMLFilePublic(filenameOrig); QString xml = client.domDocument().toString(); //qDebug() << xml; QVERIFY(xml.contains(QStringLiteral("<Action name=\"print\""))); QVERIFY(!xml.contains(QStringLiteral("<Action name=\"home\""))); QVERIFY(!xml.contains(QStringLiteral("<ActionProperties>"))); // now test the replacement (+ local file) client.replaceXMLFile(filenameReplace, filenameLocal); xml = client.domDocument().toString(); QVERIFY(!xml.contains(QStringLiteral("<Action name=\"print\""))); QVERIFY(xml.contains(QStringLiteral("<Action name=\"home\""))); QVERIFY(xml.contains(QStringLiteral("<ActionProperties>"))); // re-check after a reload client.reloadXML(); QString reloadedXml = client.domDocument().toString(); QVERIFY(!reloadedXml.contains(QStringLiteral("<Action name=\"print\""))); QVERIFY(reloadedXml.contains(QStringLiteral("<Action name=\"home\""))); QVERIFY(reloadedXml.contains(QStringLiteral("<ActionProperties>"))); // Check what happens when the local file doesn't exist TestGuiClient client2; QFile::remove(filenameLocal); client2.replaceXMLFile(filenameReplace, filenameLocal); xml = client2.domDocument().toString(); //qDebug() << xml; QVERIFY(!xml.contains(QStringLiteral("<Action name=\"print\""))); QVERIFY(xml.contains(QStringLiteral("<Action name=\"home\""))); // modified toolbars QVERIFY(!xml.contains(QStringLiteral("<ActionProperties>"))); // but no local xml file } void KXmlGui_UnitTest::testClientDestruction() // #170806 { const QByteArray hostXml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"file\"><text>&File</text>\n" " </Menu>\n" " <Merge/>\n" "</MenuBar>\n" "</gui>"; const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"file\"><text>&File</text>\n" " <Action name=\"file_open\"/>\n" " <Action name=\"file_quit\"/>\n" " </Menu>\n" "</MenuBar>\n" "</gui>"; TestXmlGuiWindow mainWindow(hostXml, "kxmlgui_unittest.rc"); TestGuiClient *client = new TestGuiClient(xml); client->createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit")); mainWindow.insertChildClient(client); mainWindow.createGUI(); checkActions(mainWindow.menuBar()->actions(), QStringList() << QStringLiteral("file") << QStringLiteral("separator") << QStringLiteral("help")); QVERIFY(mainWindow.factory()->clients().contains(client)); delete client; QVERIFY(!mainWindow.factory()->clients().contains(client)); // No change, because deletion is fast, it doesn't do manual unplugging. checkActions(mainWindow.menuBar()->actions(), QStringList() << QStringLiteral("file") << QStringLiteral("separator") << QStringLiteral("help")); } void KXmlGui_UnitTest::testShortcuts() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<MenuBar>\n" " <Menu name=\"file\"><text>&File</text>\n" " <Action name=\"file_open\" shortcut=\"Ctrl+O\"/>\n" " <Action name=\"file_quit\" shortcut=\"Ctrl+Q; Ctrl+D\"/>\n" " </Menu>\n" "</MenuBar>\n" "<ActionProperties scheme=\"Default\">\n" " <Action shortcut=\"Ctrl+O\" name=\"file_open\"/>\n" " <Action shortcut=\"Ctrl+Q; Ctrl+D\" name=\"file_quit\"/>\n" "</ActionProperties>\n" "</gui>"; TestGuiClient client; client.createActions(QStringList() << QStringLiteral("file_open") << QStringLiteral("file_quit")); client.createGUI(xml, false /*ui_standards.rc*/); KMainWindow mainWindow; KXMLGUIBuilder builder(&mainWindow); KXMLGUIFactory factory(&builder); factory.addClient(&client); QAction* actionOpen = client.action("file_open"); QAction* actionQuit = client.action("file_quit"); QVERIFY(actionOpen && actionQuit); QCOMPARE(actionOpen->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+O"))); // #345411 QCOMPARE(actionQuit->shortcuts(), QList<QKeySequence>() << QKeySequence(QStringLiteral("Ctrl+Q")) << QKeySequence(QStringLiteral("Ctrl+D"))); factory.removeClient(&client); } void KXmlGui_UnitTest::testPopupMenuParent() { const QByteArray xml = "<?xml version = '1.0'?>\n" "<!DOCTYPE gui SYSTEM \"kpartgui.dtd\">\n" "<gui version=\"1\" name=\"foo\" >\n" "<Menu name=\"foo\"><text>Foo</text></Menu>\n" "</gui>"; TestXmlGuiWindow mainWindow(xml, "kxmlgui_unittest.rc"); mainWindow.createGUI(); auto popupMenu = mainWindow.menuByName(QStringLiteral("foo")); QVERIFY(popupMenu); QCOMPARE(popupMenu->parent(), &mainWindow); } diff --git a/src/ksendbugmail/CMakeLists.txt b/src/ksendbugmail/CMakeLists.txt index 0f4054f..7bc1325 100644 --- a/src/ksendbugmail/CMakeLists.txt +++ b/src/ksendbugmail/CMakeLists.txt @@ -1,22 +1,20 @@ -remove_definitions(-DQT_NO_CAST_FROM_ASCII) - set(ksendbugmail_SRCS main.cpp smtp.cpp ) add_executable(ksendbugmail ${ksendbugmail_SRCS}) ecm_mark_nongui_executable(ksendbugmail) target_link_libraries(ksendbugmail Qt5::Widgets Qt5::Network KF5::ConfigCore # KEmailSettings KF5::I18n ) if(WIN32) target_link_libraries(ksendbugmail secur32) # GetUserNameEx() endif() install(TARGETS ksendbugmail DESTINATION ${KDE_INSTALL_LIBEXECDIR_KF5} ) diff --git a/src/ksendbugmail/main.cpp b/src/ksendbugmail/main.cpp index 45bbe8d..f1bb3a9 100644 --- a/src/ksendbugmail/main.cpp +++ b/src/ksendbugmail/main.cpp @@ -1,161 +1,161 @@ /* Copyright (c) 2000 Bernd Johannes Wuebben <wuebben@math.cornell.edu> Copyright (c) 2000 Stephan Kulow <coolo@kde.org> 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "main.h" #include <qplatformdefs.h> #include <QtCore/QTextStream> #include <QApplication> #include <QHostInfo> #include <kemailsettings.h> #include <klocalizedstring.h> #include <QDebug> #include <kconfig.h> #include <qcommandlineparser.h> #include <qcommandlineoption.h> #include "smtp.h" #include "../systeminformation_p.h" void BugMailer::slotError(int errornum) { QString lstr; switch (errornum) { case SMTP::ConnectError: lstr = i18n("Error connecting to server."); break; case SMTP::NotConnected: lstr = i18n("Not connected."); break; case SMTP::ConnectTimeout: lstr = i18n("Connection timed out."); break; case SMTP::InteractTimeout: lstr = i18n("Time out waiting for server interaction."); break; default: - lstr = sm->getLastLine().trimmed(); + lstr = QString::fromLatin1(sm->getLastLine().trimmed()); lstr = i18n("Server said: \"%1\"", lstr); } // qCDebug(DEBUG_KXMLGUI) << lstr; fputs(lstr.toUtf8().data(), stdout); fflush(stdout); qApp->exit(1); } void BugMailer::slotSend() { // qCDebug(DEBUG_KXMLGUI); qApp->exit(0); } int main(int argc, char **argv) { QCoreApplication a(argc, argv); a.setApplicationName(QStringLiteral("ksendbugmail")); a.setApplicationVersion(QStringLiteral("1.0")); KLocalizedString::setApplicationDomain("kxmlgui5"); //d.addAuthor(ki18n("Stephan Kulow"), ki18n("Author"), "coolo@kde.org"); QString subject, recipient; { QCommandLineParser parser; parser.addVersionOption(); parser.setApplicationDescription(i18n("Sends a bug report by email.")); parser.addHelpOption(); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("subject"), i18n("The subject line of the email."), QStringLiteral("argument"))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("recipient"), i18n("The email address to send the bug report to."), QStringLiteral("argument"), QStringLiteral("submit@bugs.kde.org"))); parser.process(a); recipient = parser.value(QStringLiteral("recipient")); subject = parser.value(QStringLiteral("subject")); } if (recipient.isEmpty()) { recipient = QStringLiteral("submit@bugs.kde.org"); } else { - if (recipient.at(0) == '\'') { + if (recipient.at(0) == QLatin1Char('\'')) { recipient = recipient.mid(1).left(recipient.length() - 2); } } // qCDebug(DEBUG_KXMLGUI) << "recp" << recipient; if (subject.isEmpty()) { subject = QStringLiteral("(no subject)"); } else { - if (subject.at(0) == '\'') { + if (subject.at(0) == QLatin1Char('\'')) { subject = subject.mid(1).left(subject.length() - 2); } } QTextStream input(stdin, QIODevice::ReadOnly); input.setCodec("UTF-8"); QString text, line; while (!input.atEnd()) { line = input.readLine(); - text += line + "\r\n"; + text += line + QStringLiteral("\r\n"); } // qCDebug(DEBUG_KXMLGUI) << text; KEMailSettings emailConfig; emailConfig.setProfile(emailConfig.defaultProfileName()); QString fromaddr = emailConfig.getSetting(KEMailSettings::EmailAddress); if (!fromaddr.isEmpty()) { QString name = emailConfig.getSetting(KEMailSettings::RealName); if (!name.isEmpty()) { fromaddr = name + QLatin1String(" <") + fromaddr + QLatin1String(">"); } } else { fromaddr = SystemInformation::userName(); - fromaddr += '@'; + fromaddr += QLatin1Char('@'); fromaddr += QHostInfo::localHostName(); } // qCDebug(DEBUG_KXMLGUI) << "fromaddr \"" << fromaddr << "\""; QString server = emailConfig.getSetting(KEMailSettings::OutServer); if (server.isEmpty()) { server = QStringLiteral("bugs.kde.org"); } SMTP *sm = new SMTP; BugMailer bm(sm); QObject::connect(sm, SIGNAL(messageSent()), &bm, SLOT(slotSend())); QObject::connect(sm, SIGNAL(error(int)), &bm, SLOT(slotError(int))); sm->setServerHost(server); sm->setPort(25); sm->setSenderAddress(fromaddr); sm->setRecipientAddress(recipient); sm->setMessageSubject(subject); sm->setMessageHeader(QStringLiteral("From: %1\r\nTo: %2\r\n").arg(fromaddr, recipient)); sm->setMessageBody(text); sm->sendMessage(); int r = a.exec(); // qCDebug(DEBUG_KXMLGUI) << "execing " << r; delete sm; return r; } diff --git a/src/ksendbugmail/smtp.cpp b/src/ksendbugmail/smtp.cpp index d3bdda8..5d12fd3 100644 --- a/src/ksendbugmail/smtp.cpp +++ b/src/ksendbugmail/smtp.cpp @@ -1,348 +1,348 @@ /* Copyright (c) 2000 Bernd Johannes Wuebben <wuebben@math.cornell.edu> Copyright (c) 2000 Stephan Kulow <coolo@kde.org> 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "smtp.h" #include "../systeminformation_p.h" #include <stdio.h> #include <QDebug> #include <QSslSocket> #include <QHostInfo> SMTP::SMTP(char *serverhost, unsigned short int port, int timeout) { - serverHost = serverhost; + serverHost = QString::fromUtf8(serverhost); hostPort = port; timeOut = timeout * 1000; senderAddress = QStringLiteral("user@example.net"); recipientAddress = QStringLiteral("user@example.net"); messageSubject = QStringLiteral("(no subject)"); messageBody = QStringLiteral("empty"); messageHeader = QLatin1String(""); connected = false; finished = false; sock = nullptr; state = Init; serverState = None; domainName = QHostInfo::localDomainName(); if (domainName.isEmpty()) { domainName = QStringLiteral("somemachine.example.net"); } // qCDebug(DEBUG_KXMLGUI) << "SMTP object created"; connect(&connectTimer, SIGNAL(timeout()), this, SLOT(connectTimerTick())); connect(&timeOutTimer, SIGNAL(timeout()), this, SLOT(connectTimedOut())); connect(&interactTimer, SIGNAL(timeout()), this, SLOT(interactTimedOut())); // some sendmail will give 'duplicate helo' error, quick fix for now connect(this, SIGNAL(messageSent()), SLOT(closeConnection())); } SMTP::~SMTP() { delete sock; sock = nullptr; connectTimer.stop(); timeOutTimer.stop(); } void SMTP::setServerHost(const QString &serverhost) { serverHost = serverhost; } void SMTP::setPort(unsigned short int port) { hostPort = port; } void SMTP::setTimeOut(int timeout) { timeOut = timeout; } void SMTP::setSenderAddress(const QString &sender) { senderAddress = sender; - int index = senderAddress.indexOf('<'); + int index = senderAddress.indexOf(QLatin1Char('<')); if (index == -1) { return; } senderAddress = senderAddress.mid(index + 1); - index = senderAddress.indexOf('>'); + index = senderAddress.indexOf(QLatin1Char('>')); if (index != -1) { senderAddress = senderAddress.left(index); } senderAddress = senderAddress.simplified(); while (1) { - index = senderAddress.indexOf(' '); + index = senderAddress.indexOf(QLatin1Char(' ')); if (index != -1) { senderAddress = senderAddress.mid(index + 1); // take one side } else { break; } } - index = senderAddress.indexOf('@'); + index = senderAddress.indexOf(QLatin1Char('@')); if (index == -1) { - senderAddress.append("@localhost"); // won't go through without a local mail system + senderAddress.append(QStringLiteral("@localhost")); // won't go through without a local mail system } } void SMTP::setRecipientAddress(const QString &recipient) { recipientAddress = recipient; } void SMTP::setMessageSubject(const QString &subject) { messageSubject = subject; } void SMTP::setMessageBody(const QString &message) { messageBody = message; } void SMTP::setMessageHeader(const QString &header) { messageHeader = header; } void SMTP::openConnection(void) { // qCDebug(DEBUG_KXMLGUI) << "started connect timer"; connectTimer.setSingleShot(true); connectTimer.start(100); } void SMTP::closeConnection(void) { socketClosed(); } void SMTP::sendMessage(void) { if (!connected) { connectTimerTick(); } if (state == Finished && connected) { // qCDebug(DEBUG_KXMLGUI) << "state was == Finished\n"; finished = false; state = In; writeString = QStringLiteral("helo %1\r\n").arg(domainName); sock->write(writeString.toLatin1().constData(), writeString.length()); } if (connected) { // qCDebug(DEBUG_KXMLGUI) << "enabling read on sock...\n"; interactTimer.setSingleShot(true); interactTimer.start(timeOut); } } void SMTP::connectTimerTick(void) { connectTimer.stop(); // timeOutTimer.start(timeOut, true); // qCDebug(DEBUG_KXMLGUI) << "connectTimerTick called..."; delete sock; sock = nullptr; // qCDebug(DEBUG_KXMLGUI) << "connecting to " << serverHost << ":" << hostPort << " ..... "; sock = new QSslSocket(this); sock->connectToHost(serverHost, hostPort); connected = true; finished = false; state = Init; serverState = None; connect(sock, SIGNAL(readyRead()), this, SLOT(socketReadyToRead())); connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect(sock, SIGNAL(disconnected()), this, SLOT(socketClosed())); timeOutTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "connected"; } void SMTP::connectTimedOut(void) { timeOutTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "socket connection timed out"; socketClosed(); emit error(ConnectTimeout); } void SMTP::interactTimedOut(void) { interactTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "time out waiting for server interaction"; socketClosed(); emit error(InteractTimeout); } void SMTP::socketReadyToRead() { int n, nl; // qCDebug(DEBUG_KXMLGUI) << "socketRead() called..."; interactTimer.stop(); if (!sock) { return; } n = sock->read(readBuffer, SMTP_READ_BUFFER_SIZE - 1); if (n < 0) { return; } readBuffer[n] = 0; - lineBuffer += readBuffer; + lineBuffer += QByteArray(readBuffer); nl = lineBuffer.indexOf('\n'); if (nl == -1) { return; } lastLine = lineBuffer.left(nl); lineBuffer = lineBuffer.right(lineBuffer.length() - nl - 1); processLine(&lastLine); if (connected) { interactTimer.setSingleShot(true); interactTimer.start(timeOut); } } void SMTP::socketError(QAbstractSocket::SocketError socketError) { // qCDebug(DEBUG_KXMLGUI) << socketError << sock->errorString(); Q_UNUSED(socketError); emit error(ConnectError); socketClosed(); } void SMTP::socketClosed() { timeOutTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "connection terminated"; connected = false; if (sock) { sock->deleteLater(); } sock = nullptr; emit connectionClosed(); } -void SMTP::processLine(QString *line) +void SMTP::processLine(QByteArray *line) { int i, stat; - QString tmpstr; + QByteArray tmpstr; i = line->indexOf(' '); tmpstr = line->left(i); if (i > 3) { // qCDebug(DEBUG_KXMLGUI) << "warning: SMTP status code longer than 3 digits: " << tmpstr; } stat = tmpstr.toInt(); serverState = static_cast<SMTPServerStatus>(stat); lastState = state; // qCDebug(DEBUG_KXMLGUI) << "smtp state: [" << stat << "][" << *line << "]"; switch (stat) { case Greet: //220 state = In; writeString = QStringLiteral("helo %1\r\n").arg(domainName); // qCDebug(DEBUG_KXMLGUI) << "out: " << writeString; sock->write(writeString.toLatin1().constData(), writeString.length()); break; case Goodbye: //221 state = Quit; break; case Successful://250 switch (state) { case In: state = Ready; writeString = QStringLiteral("mail from: %1\r\n").arg(senderAddress); // qCDebug(DEBUG_KXMLGUI) << "out: " << writeString; sock->write(writeString.toLatin1().constData(), writeString.length()); break; case Ready: state = SentFrom; writeString = QStringLiteral("rcpt to: %1\r\n").arg(recipientAddress); // qCDebug(DEBUG_KXMLGUI) << "out: " << writeString; sock->write(writeString.toLatin1().constData(), writeString.length()); break; case SentFrom: state = SentTo; writeString = QStringLiteral("data\r\n"); // qCDebug(DEBUG_KXMLGUI) << "out: " << writeString; sock->write(writeString.toLatin1().constData(), writeString.length()); break; case Data: state = Finished; finished = true; emit messageSent(); break; default: state = CError; // qCDebug(DEBUG_KXMLGUI) << "smtp error (state error): [" << lastState << "]:[" << stat << "][" << *line << "]"; socketClosed(); emit error(Command); break; } break; case ReadyData: //354 state = Data; writeString = QStringLiteral("Subject: %1\r\n").arg(messageSubject); writeString += messageHeader; writeString += QLatin1String("\r\n"); writeString += messageBody; writeString += QLatin1String(".\r\n"); // qCDebug(DEBUG_KXMLGUI) << "out: " << writeString; sock->write(writeString.toLatin1().constData(), writeString.length()); break; case Error: //501 state = CError; // qCDebug(DEBUG_KXMLGUI) << "smtp error (command error): [" << lastState << "]:[" << stat << "][" << *line << "]\n"; socketClosed(); emit error(Command); break; case Unknown: //550 state = CError; // qCDebug(DEBUG_KXMLGUI) << "smtp error (unknown user): [" << lastState << "]:[" << stat << "][" << *line << "]"; socketClosed(); emit error(UnknownUser); break; default: state = CError; // qCDebug(DEBUG_KXMLGUI) << "unknown response: [" << lastState << "]:[" << stat << "][" << *line << "]"; socketClosed(); emit error(UnknownResponse); } } diff --git a/src/ksendbugmail/smtp.h b/src/ksendbugmail/smtp.h index 413c2e0..7f54e6f 100644 --- a/src/ksendbugmail/smtp.h +++ b/src/ksendbugmail/smtp.h @@ -1,171 +1,171 @@ /* Copyright (c) 2000 Bernd Johannes Wuebben <wuebben@math.cornell.edu> Copyright (c) 2000 Stephan Kulow <coolo@kde.org> 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, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SMTP_H #define SMTP_H #include <QtCore/QObject> #include <QtCore/QTimer> #include <QtNetwork/QTcpSocket> /*int SMTPServerStatus[] = { 220, // greeting from server 221, // server acknolages goodbye 250, // command successful 354, // ready to receive data 501, // error 550, // user unknown 0 // null }; int SMTPClientStatus[] = { 50, // not logged in yet. 100, // logged in, got 220 150, // sent helo, got 250 200, // sent mail from, got 250 250, // sent rctp to, got 250 300, // data sent, got 354 350, // sent data/., got 250 400, // send quit, got 221 450, // finished, logged out 0 // null }; */ #define DEFAULT_SMTP_PORT 25 #define DEFAULT_SMTP_SERVER localhost #define DEFAULT_SMTP_TIMEOUT 60 #define SMTP_READ_BUFFER_SIZE 256 class SMTP: public QObject { Q_OBJECT public: explicit SMTP(char *serverhost = nullptr, unsigned short int port = 0, int timeout = DEFAULT_SMTP_TIMEOUT); ~SMTP(); void setServerHost(const QString &serverhost); void setPort(unsigned short int port); void setTimeOut(int timeout); - bool isConnected() + bool isConnected() const { return connected; } - bool isFinished() + bool isFinished() const { return finished; } - QString getLastLine() + QByteArray getLastLine() const { return lastLine; } void setSenderAddress(const QString &sender); void setRecipientAddress(const QString &recipient); void setMessageSubject(const QString &subject); void setMessageBody(const QString &message); void setMessageHeader(const QString &header); typedef enum { None = 0, // null Greet = 220, // greeting from server Goodbye = 221, // server acknolages quit Successful = 250, // command successful ReadyData = 354, // server ready to receive data Error = 501, // error Unknown = 550 // user unknown } SMTPServerStatus; typedef enum { Init = 50, // not logged in yet In = 100, // logged in, got 220 Ready = 150, // sent HELO, got 250 SentFrom = 200, // sent MAIL FROM:, got 250 SentTo = 250, // sent RCTP TO:, got 250 Data = 300, // Data sent, got 354 Finished = 350, // finished sending data, got 250 Quit = 400, // sent Quit, got 221 Out = 450, // finished, logged out CError = 500 // didn't finish, had error or connection drop } SMTPClientStatus; typedef enum { NoError = 0, ConnectError = 10, NotConnected = 11, ConnectTimeout = 15, InteractTimeout = 16, UnknownResponse = 20, UnknownUser = 30, Command = 40 } SMTPError; protected: - void processLine(QString *line); + void processLine(QByteArray *line); public Q_SLOTS: void openConnection(); void sendMessage(); void closeConnection(); void connectTimerTick(); void connectTimedOut(); void interactTimedOut(); void socketReadyToRead(); void socketClosed(); void socketError(QAbstractSocket::SocketError); Q_SIGNALS: void connectionClosed(); void messageSent(); void error(int); private: QString serverHost; unsigned short int hostPort; int timeOut; bool connected; bool finished; QString senderAddress; QString recipientAddress; QString messageSubject; QString messageBody, messageHeader; SMTPClientStatus state; SMTPClientStatus lastState; SMTPServerStatus serverState; QString domainName; QTcpSocket *sock; QTimer connectTimer; QTimer timeOutTimer; QTimer interactTimer; char readBuffer[SMTP_READ_BUFFER_SIZE]; - QString lineBuffer; - QString lastLine; + QByteArray lineBuffer; + QByteArray lastLine; QString writeString; }; #endif