diff --git a/autotests/kxmlgui_unittest.cpp b/autotests/kxmlgui_unittest.cpp index 2cf486a..0939007 100644 --- a/autotests/kxmlgui_unittest.cpp +++ b/autotests/kxmlgui_unittest.cpp @@ -1,1101 +1,1099 @@ /* 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 #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('/') + 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('/') + 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::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('/') + 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); } void KXmlGui_UnitTest::testSpecificApplicationLanguageQLocale() { -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) const QLocale originalSystemLocale = QLocale::system(); KDEPrivate::setApplicationSpecificLanguage("ru_RU"); KDEPrivate::initializeLanguages(); QCOMPARE(QLocale::system().language(), QLocale::Russian); KDEPrivate::setApplicationSpecificLanguage("wa"); KDEPrivate::initializeLanguages(); QCOMPARE(QLocale::system().language(), QLocale::Walloon); KDEPrivate::setApplicationSpecificLanguage(QByteArray()); KDEPrivate::initializeLanguages(); QCOMPARE(QLocale::system(), originalSystemLocale); -#endif } diff --git a/src/kswitchlanguagedialog_p.cpp b/src/kswitchlanguagedialog_p.cpp index c1b5580..316e1c3 100644 --- a/src/kswitchlanguagedialog_p.cpp +++ b/src/kswitchlanguagedialog_p.cpp @@ -1,450 +1,446 @@ /* * This file is part of the KDE Libraries * Copyright (C) 2007 Krzysztof Lichota (lichota@mimuw.edu.pl) * * 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 "kswitchlanguagedialog_p.h" #include <QApplication> #include <QDialogButtonBox> #include <QDir> #include <QLayout> #include <QLabel> #include <QPushButton> #include <QEvent> #include <QMap> #include <QSettings> #include <QSharedPointer> #include <QStandardPaths> #include <QDebug> -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) #include <private/qlocale_p.h> -#endif #include <klanguagebutton.h> #include <klocalizedstring.h> #include <kmessagebox.h> // Believe it or not we can't use KConfig from here // (we need KConfig during QCoreApplication ctor which is too early for it) // So we cooked a QSettings based solution typedef QSharedPointer<QSettings> QSettingsPtr; static QSettingsPtr localeOverridesSettings() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); const QDir configDir(configPath); if (!configDir.exists()) { configDir.mkpath(QStringLiteral(".")); } return QSettingsPtr(new QSettings(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat)); } static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode = QByteArray()) { QSettingsPtr settings = localeOverridesSettings(); settings->beginGroup(QStringLiteral("Language")); return settings->value(qAppName(), defaultCode).toByteArray(); } namespace KDEPrivate { Q_COREAPP_STARTUP_FUNCTION(initializeLanguages) void setApplicationSpecificLanguage(const QByteArray &languageCode) { QSettingsPtr settings = localeOverridesSettings(); settings->beginGroup(QStringLiteral("Language")); if (languageCode.isEmpty()) { settings->remove(qAppName()); } else { settings->setValue(qAppName(), languageCode); } } void initializeLanguages() { const QByteArray languageCode = getApplicationSpecificLanguage(); if (!languageCode.isEmpty()) { QByteArray languages = qgetenv("LANGUAGE"); if (languages.isEmpty()) { qputenv("LANGUAGE", languageCode); } else { qputenv("LANGUAGE", languageCode + ':' + languages); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) // Ideally setting the LANGUAGE would change the default QLocale too // but unfortunately this is too late since the QCoreApplication constructor // already created a QLocale at this stage so we need to set the reset it // by triggering the creation and destruction of a QSystemLocale // this is highly dependant on Qt internals, so may break, but oh well QSystemLocale *dummy = new QSystemLocale(); delete dummy; -#endif } } struct LanguageRowData { LanguageRowData() { label = nullptr; languageButton = nullptr; removeButton = nullptr; } QLabel *label; KLanguageButton *languageButton; QPushButton *removeButton; void setRowWidgets( QLabel *label, KLanguageButton *languageButton, QPushButton *removeButton ) { this->label = label; this->languageButton = languageButton; this->removeButton = removeButton; } }; class KSwitchLanguageDialogPrivate { public: KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent); KSwitchLanguageDialog *p; //parent class /** Fills language button with names of languages for which given application has translation. */ void fillApplicationLanguages(KLanguageButton *button); /** Adds one button with language to widget. */ void addLanguageButton(const QString &languageCode, bool primaryLanguage); /** Returns list of languages chosen for application or default languages is they are not set. */ QStringList applicationLanguageList(); QMap<QPushButton *, LanguageRowData> languageRows; QList<KLanguageButton *> languageButtons; QGridLayout *languagesLayout; }; /*************************** KSwitchLanguageDialog **************************/ KSwitchLanguageDialog::KSwitchLanguageDialog(QWidget *parent) : QDialog(parent), d(new KSwitchLanguageDialogPrivate(this)) { setWindowTitle(i18n("Switch Application Language")); QVBoxLayout *topLayout = new QVBoxLayout; setLayout(topLayout); QLabel *label = new QLabel(i18n("Please choose the language which should be used for this application:"), this); topLayout->addWidget(label); QHBoxLayout *languageHorizontalLayout = new QHBoxLayout(); topLayout->addLayout(languageHorizontalLayout); d->languagesLayout = new QGridLayout(); languageHorizontalLayout->addLayout(d->languagesLayout); languageHorizontalLayout->addStretch(); const QStringList defaultLanguages = d->applicationLanguageList(); int count = defaultLanguages.count(); for (int i = 0; i < count; ++i) { QString language = defaultLanguages[i]; bool primaryLanguage = (i == 0); d->addLanguageButton(language, primaryLanguage); } if (!count) { QLocale l; d->addLanguageButton(l.name(), true); } QHBoxLayout *addButtonHorizontalLayout = new QHBoxLayout(); topLayout->addLayout(addButtonHorizontalLayout); QPushButton *addLangButton = new QPushButton(i18n("Add Fallback Language"), this); addLangButton->setToolTip(i18n("Adds one more language which will be used if other translations do not contain a proper translation.")); connect(addLangButton, &QPushButton::clicked, this, &KSwitchLanguageDialog::slotAddLanguageButton); addButtonHorizontalLayout->addWidget(addLangButton); addButtonHorizontalLayout->addStretch(); topLayout->addStretch(10); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); topLayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, this, &KSwitchLanguageDialog::slotOk); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &KSwitchLanguageDialog::slotDefault); } KSwitchLanguageDialog::~KSwitchLanguageDialog() { delete d; } void KSwitchLanguageDialog::slotAddLanguageButton() { //adding new button with en_US as it should always be present d->addLanguageButton(QStringLiteral("en_US"), d->languageButtons.isEmpty()); } void KSwitchLanguageDialog::removeButtonClicked() { QObject const *signalSender = sender(); if (!signalSender) { qCritical() << "KSwitchLanguageDialog::removeButtonClicked() called directly, not using signal" << endl; return; } QPushButton *removeButton = const_cast<QPushButton *>(::qobject_cast<const QPushButton *>(signalSender)); if (!removeButton) { qCritical() << "KSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton" << endl; return; } QMap<QPushButton *, LanguageRowData>::iterator it = d->languageRows.find(removeButton); if (it == d->languageRows.end()) { qCritical() << "KSwitchLanguageDialog::removeButtonClicked called from unknown QPushButton" << endl; return; } LanguageRowData languageRowData = it.value(); d->languageButtons.removeAll(languageRowData.languageButton); languageRowData.label->deleteLater(); languageRowData.languageButton->deleteLater(); languageRowData.removeButton->deleteLater(); d->languageRows.erase(it); } void KSwitchLanguageDialog::languageOnButtonChanged(const QString &languageCode) { Q_UNUSED(languageCode); #if 0 for (int i = 0, count = d->languageButtons.count(); i < count; ++i) { KLanguageButton *languageButton = d->languageButtons[i]; if (languageButton->current() == languageCode) { //update all buttons which have matching id //might update buttons which were not changed, but well... languageButton->setText(KLocale::global()->languageCodeToName(languageCode)); } } #endif } void KSwitchLanguageDialog::slotOk() { QStringList languages; for (int i = 0, count = d->languageButtons.count(); i < count; ++i) { KLanguageButton *languageButton = d->languageButtons[i]; languages << languageButton->current(); } if (d->applicationLanguageList() != languages) { QString languageString = languages.join(QLatin1Char(':')); //list is different from defaults or saved languages list setApplicationSpecificLanguage(languageString.toLatin1()); KMessageBox::information( this, i18n("The language for this application has been changed. The change will take effect the next time the application is started."), //text i18n("Application Language Changed"), //caption QStringLiteral("ApplicationLanguageChangedWarning") //dontShowAgainName ); } accept(); } void KSwitchLanguageDialog::slotDefault() { const QStringList defaultLanguages = d->applicationLanguageList(); setApplicationSpecificLanguage(QByteArray()); // read back the new default QString language = QString::fromLatin1(getApplicationSpecificLanguage("en_US")); if (defaultLanguages != (QStringList() << language)) { KMessageBox::information( this, i18n("The language for this application has been changed. The change will take effect the next time the application is started."), //text i18n("Application Language Changed"), //caption QStringLiteral("ApplicationLanguageChangedWarning") //dontShowAgainName ); } accept(); } /************************ KSwitchLanguageDialogPrivate ***********************/ KSwitchLanguageDialogPrivate::KSwitchLanguageDialogPrivate( KSwitchLanguageDialog *parent) : p(parent) { //NOTE: do NOT use "p" in constructor, it is not fully constructed } static bool stripCountryCode(QString *languageCode) { const int idx = languageCode->indexOf(QLatin1String("_")); if (idx != -1) { *languageCode = languageCode->left(idx); return true; } return false; } void KSwitchLanguageDialogPrivate::fillApplicationLanguages(KLanguageButton *button) { const QLocale cLocale(QLocale::C); QSet<QString> insertedLanguges; const QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); foreach(const QLocale &l, allLocales) { if (l != cLocale) { QString languageCode = l.name(); if (!insertedLanguges.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) { button->insertLanguage(languageCode); insertedLanguges << languageCode; } else if (stripCountryCode(&languageCode)) { if (!insertedLanguges.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) { button->insertLanguage(languageCode); insertedLanguges << languageCode; } } } } } QStringList KSwitchLanguageDialogPrivate::applicationLanguageList() { QStringList languagesList; QByteArray languageCode = getApplicationSpecificLanguage(); if (!languageCode.isEmpty()) { languagesList = QString::fromLatin1(languageCode).split(QLatin1Char(':')); } if (languagesList.isEmpty()) { QLocale l; languagesList = l.uiLanguages(); // We get en-US here but we use en_US for (int i = 0; i < languagesList.count(); ++i) { languagesList[i].replace(QLatin1String("-"), QLatin1String("_")); } } for (int i = 0; i < languagesList.count();) { QString languageCode = languagesList[i]; if (!KLocalizedString::isApplicationTranslatedInto(languageCode)) { if (stripCountryCode(&languageCode)) { if (KLocalizedString::isApplicationTranslatedInto(languageCode)) { languagesList[i] = languageCode; ++i; continue; } } languagesList.removeAt(i); } else { ++i; } } return languagesList; } void KSwitchLanguageDialogPrivate::addLanguageButton(const QString &languageCode, bool primaryLanguage) { QString labelText = primaryLanguage ? i18n("Primary language:") : i18n("Fallback language:"); KLanguageButton *languageButton = new KLanguageButton(p); fillApplicationLanguages(languageButton); languageButton->setCurrentItem(languageCode); QObject::connect(languageButton, &KLanguageButton::activated, p, &KSwitchLanguageDialog::languageOnButtonChanged); LanguageRowData languageRowData; QPushButton *removeButton = nullptr; if (!primaryLanguage) { removeButton = new QPushButton(i18n("Remove"), p); QObject::connect(removeButton, &QPushButton::clicked, p, &KSwitchLanguageDialog::removeButtonClicked); } languageButton->setToolTip(primaryLanguage ? i18n("This is the main application language which will be used first, before any other languages.") : i18n("This is the language which will be used if any previous languages do not contain a proper translation.")); int numRows = languagesLayout->rowCount(); QLabel *languageLabel = new QLabel(labelText, p); languagesLayout->addWidget(languageLabel, numRows + 1, 1, Qt::AlignLeft); languagesLayout->addWidget(languageButton, numRows + 1, 2, Qt::AlignLeft); if (!primaryLanguage) { languagesLayout->addWidget(removeButton, numRows + 1, 3, Qt::AlignLeft); languageRowData.setRowWidgets(languageLabel, languageButton, removeButton); removeButton->show(); } languageRows.insert(removeButton, languageRowData); languageButtons.append(languageButton); languageButton->show(); languageLabel->show(); } }