diff --git a/autotests/ktoolbar_unittest.cpp b/autotests/ktoolbar_unittest.cpp index 62571b0..2361876 100644 --- a/autotests/ktoolbar_unittest.cpp +++ b/autotests/ktoolbar_unittest.cpp @@ -1,671 +1,670 @@ /* This file is part of the KDE libraries Copyright (c) 2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 "testxmlguiwindow.h" #include "testguiclient.h" #include #include #include #include #include #include #include -#include #include #include #include #include #include #include // We use the data types below in a QVariant, so Q_DECLARE_METATYPE is needed for them. Q_DECLARE_METATYPE(Qt::MouseButton) Q_DECLARE_METATYPE(Qt::MouseButtons) Q_DECLARE_METATYPE(Qt::KeyboardModifiers) // Ensure everything uses test paths, including stuff run before main, such as the KdePlatformThemePlugin void enableTestMode() { QStandardPaths::setTestModeEnabled(true); } Q_CONSTRUCTOR_FUNCTION(enableTestMode) class tst_KToolBar : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); private Q_SLOTS: void ktoolbar(); void testIconSizeNoXmlGui_data(); void testIconSizeNoXmlGui(); void testIconSizeXmlGui_data(); void testIconSizeXmlGui(); void testToolButtonStyleNoXmlGui_data(); void testToolButtonStyleNoXmlGui(); void testToolButtonStyleXmlGui_data(); void testToolButtonStyleXmlGui(); void testToolBarPosition(); void testXmlGuiSwitching(); void testKAuthorizedDisableToggleAction(); Q_SIGNALS: void signalAppearanceChanged(); protected: bool eventFilter(QObject *watched, QEvent *event) override; private: void changeGlobalIconSizeSetting(int, int); void deleteGlobalIconSizeSetting(); void changeGlobalToolButtonStyleSetting(const QString &, const QString &); void deleteGlobalToolButtonStyleSetting(); QByteArray m_xml; bool m_showWasCalled; }; QTEST_MAIN(tst_KToolBar) static void copy_dir(const QString &from, const QDir &to) { QDir src = QDir(from); QDir dest = QDir(to.filePath(src.dirName())); to.mkpath(src.dirName()); foreach (const QFileInfo &fileInfo, src.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { if (fileInfo.isDir()) { copy_dir(fileInfo.filePath(), dest); } else { QFile::copy(fileInfo.filePath(), dest.filePath(fileInfo.fileName())); } } } // This will be called before the first test function is executed. // It is only called once. void tst_KToolBar::initTestCase() { // start with a clean place to put data QDir testDataDir = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); QVERIFY(testDataDir.absolutePath().contains(QStringLiteral("qttest"))); testDataDir.removeRecursively(); testDataDir.mkpath(QStringLiteral(".")); // setup action restriction so we can test whether this actually disables some functionality KConfigGroup actionRestrictions(KSharedConfig::openConfig(), "KDE Action Restrictions"); actionRestrictions.writeEntry("action/options_show_toolbar", false); // copy a minimal icon theme to where KIconTheme will find it, in case oxygen-icons is not // installed copy_dir(QFINDTESTDATA("icons"), testDataDir); m_xml = "\n" "\n" "\n" "\n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "\n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "\n"; qRegisterMetaType("Qt::MouseButtons"); qRegisterMetaType("Qt::KeyboardModifiers"); } // This will be called after the last test function is executed. // It is only called once. void tst_KToolBar::cleanupTestCase() { QDir testDataDir = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); QDir testIconsDir = QDir(testDataDir.absoluteFilePath(QStringLiteral("icons"))); QVERIFY(testIconsDir.absolutePath().contains(QStringLiteral("qttest"))); testIconsDir.removeRecursively(); } // This will be called before each test function is executed. void tst_KToolBar::init() { } // This will be called after every test function. void tst_KToolBar::cleanup() { QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QStringLiteral("tst_KToolBar")); deleteGlobalIconSizeSetting(); deleteGlobalToolButtonStyleSetting(); } void tst_KToolBar::ktoolbar() { KMainWindow kmw; // Creating a KToolBar directly KToolBar bar(&kmw); QCOMPARE(bar.mainWindow(), &kmw); // Asking KMainWindow for a KToolBar (more common) KToolBar *mainToolBar = kmw.toolBar(QStringLiteral("mainToolBar")); QCOMPARE(mainToolBar->mainWindow(), &kmw); } Q_DECLARE_METATYPE(KConfigGroup) void tst_KToolBar::testIconSizeNoXmlGui_data() { QTest::addColumn("iconSize"); QTest::newRow("16") << 16; QTest::newRow("22") << 22; QTest::newRow("32") << 32; QTest::newRow("64") << 64; } void tst_KToolBar::testIconSizeNoXmlGui() { QFETCH(int, iconSize); KConfig config(QStringLiteral("tst_KToolBar")); KConfigGroup group(&config, "group"); { KMainWindow kmw; KToolBar *mainToolBar = kmw.toolBar(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBar(QStringLiteral("otherToolBar")); // Default settings (applied by applyAppearanceSettings) QCOMPARE(mainToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::MainToolbar)); QCOMPARE(otherToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); // check the actual values - update this if kicontheme's defaults are changed QCOMPARE(KIconLoader::global()->currentSize(KIconLoader::MainToolbar), 22); QCOMPARE(KIconLoader::global()->currentSize(KIconLoader::Toolbar), 22); // Changing settings for a given toolbar, as user mainToolBar->setIconDimensions(iconSize); otherToolBar->setIconDimensions(iconSize); // Save settings kmw.saveMainWindowSettings(group); // was it the default value? if (iconSize == KIconLoader::global()->currentSize(KIconLoader::MainToolbar)) { QCOMPARE(group.groupList().count(), 0); // nothing to save QVERIFY(!group.group("Toolbar mainToolBar").hasKey("IconSize")); } else { QCOMPARE(group.groupList().count(), 2); // two subgroups (one for each toolbar) QVERIFY(group.group("Toolbar mainToolBar").hasKey("IconSize")); } } { // Recreate, load, compare. KMainWindow kmw; KToolBar *mainToolBar = kmw.toolBar(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBar(QStringLiteral("otherToolBar")); KToolBar *cleanToolBar = kmw.toolBar(QStringLiteral("cleanToolBar")); QCOMPARE(mainToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::MainToolbar)); QCOMPARE(otherToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); QCOMPARE(cleanToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); kmw.applyMainWindowSettings(group); QCOMPARE(mainToolBar->iconSize().width(), iconSize); QCOMPARE(otherToolBar->iconSize().width(), iconSize); QCOMPARE(cleanToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); // unchanged const bool mainToolBarWasUsingDefaultSize = iconSize == KIconLoader::global()->currentSize(KIconLoader::MainToolbar); const bool otherToolBarWasUsingDefaultSize = iconSize == KIconLoader::global()->currentSize(KIconLoader::Toolbar); // Now emulate a change of the kde-global setting (#168480#c12) changeGlobalIconSizeSetting(32, 33); QCOMPARE(KIconLoader::global()->currentSize(KIconLoader::MainToolbar), 32); QCOMPARE(KIconLoader::global()->currentSize(KIconLoader::Toolbar), 33); if (mainToolBarWasUsingDefaultSize) { QCOMPARE(mainToolBar->iconSize().width(), 32); } else { // the user chose a specific size for the toolbar, so the new global size isn't used QCOMPARE(mainToolBar->iconSize().width(), iconSize); } if (otherToolBarWasUsingDefaultSize) { QCOMPARE(otherToolBar->iconSize().width(), 33); } else { // the user chose a specific size for the toolbar, so the new global size isn't used QCOMPARE(otherToolBar->iconSize().width(), iconSize); } QCOMPARE(cleanToolBar->iconSize().width(), 33); } } void tst_KToolBar::testIconSizeXmlGui_data() { QTest::addColumn("iconSize"); // set by user and saved to KConfig QTest::addColumn("expectedSizeMainToolbar"); // ... after kde-global is changed to 25 QTest::addColumn("expectedSizeOtherToolbar"); // ... after kde-global is changed to 16 QTest::addColumn("expectedSizeCleanToolbar"); // ... after kde-global is changed to 16 QTest::addColumn("expectedSizeBigToolbar"); // ... after kde-global is changed to 16 // When the user chose a specific size for the toolbar (!= its default size), the new kde-global size isn't applied to that toolbar. // So, only in case the toolbar was at iconSize already, and there was no setting in xml, we end up with kdeGlobal being used: const int kdeGlobalMain = 25; const int kdeGlobalOther = 16; QTest::newRow("16") << 16 << 16 << 16 << kdeGlobalOther << 16; QTest::newRow("22") << 22 << kdeGlobalMain << kdeGlobalOther << kdeGlobalOther << 22; QTest::newRow("32") << 32 << 32 << 32 << kdeGlobalOther << 32; QTest::newRow("64") << 64 << 64 << 64 << kdeGlobalOther << 64; } void tst_KToolBar::testIconSizeXmlGui() { QFETCH(int, iconSize); QFETCH(int, expectedSizeMainToolbar); QFETCH(int, expectedSizeOtherToolbar); QFETCH(int, expectedSizeCleanToolbar); QFETCH(int, expectedSizeBigToolbar); KConfig config(QStringLiteral("tst_KToolBar")); KConfigGroup group(&config, "group"); { TestXmlGuiWindow kmw(m_xml, "tst_ktoolbar.rc"); kmw.createActions(QStringList() << QStringLiteral("go_up")); kmw.createGUI(); KToolBar *mainToolBar = kmw.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBarByName(QStringLiteral("otherToolBar")); KToolBar *cleanToolBar = kmw.toolBarByName(QStringLiteral("cleanToolBar")); KToolBar *bigToolBar = kmw.toolBarByName(QStringLiteral("bigToolBar")); KToolBar *bigUnchangedToolBar = kmw.toolBarByName(QStringLiteral("bigUnchangedToolBar")); // Default settings (applied by applyAppearanceSettings) QCOMPARE(mainToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::MainToolbar)); QCOMPARE(otherToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); // check the actual values - update this if kicontheme's defaults are changed QCOMPARE(mainToolBar->iconSize().width(), 22); QCOMPARE(otherToolBar->iconSize().width(), 22); QCOMPARE(cleanToolBar->iconSize().width(), 22); QCOMPARE(bigToolBar->iconSize().width(), 32); QCOMPARE(bigUnchangedToolBar->iconSize().width(), 32); // Changing settings for a given toolbar, as user (to test the initial report in #168480) mainToolBar->setIconDimensions(iconSize); otherToolBar->setIconDimensions(iconSize); bigToolBar->setIconDimensions(iconSize); // Save settings kmw.saveMainWindowSettings(group); // was it the default size? (for the main toolbar, we only check that one) const bool usingDefaultSize = iconSize == KIconLoader::global()->currentSize(KIconLoader::MainToolbar); if (usingDefaultSize) { QVERIFY(!group.groupList().contains(QStringLiteral("Toolbar mainToolBar"))); QVERIFY(!group.group("Toolbar mainToolBar").hasKey("IconSize")); } else { QVERIFY(group.group("Toolbar mainToolBar").hasKey("IconSize")); } // Now emulate a change of the kde-global setting (#168480#c12) changeGlobalIconSizeSetting(25, 16); QCOMPARE(mainToolBar->iconSize().width(), expectedSizeMainToolbar); QCOMPARE(otherToolBar->iconSize().width(), expectedSizeOtherToolbar); QCOMPARE(cleanToolBar->iconSize().width(), expectedSizeCleanToolbar); QCOMPARE(bigToolBar->iconSize().width(), expectedSizeBigToolbar); // The big unchanged toolbar should be, well, unchanged; AppXml has priority over KDE_Default. QCOMPARE(bigUnchangedToolBar->iconSize().width(), 32); } } void tst_KToolBar::changeGlobalIconSizeSetting(int mainToolbarIconSize, int iconSize) { // We could use KConfig::Normal|KConfig::Global here, to write to kdeglobals like kcmstyle does, // but we don't need to. Writing to the app's config file works too. KConfigGroup mglobals(KSharedConfig::openConfig(), "MainToolbarIcons"); mglobals.writeEntry("Size", mainToolbarIconSize); KConfigGroup globals(KSharedConfig::openConfig(), "ToolbarIcons"); //globals.writeEntry("Size", iconSize, KConfig::Normal|KConfig::Global); globals.writeEntry("Size", iconSize); KSharedConfig::openConfig()->sync(); QSignalSpy spy(KIconLoader::global(), SIGNAL(iconChanged(int))); KIconLoader::global()->emitChange(KIconLoader::Desktop); spy.wait(200); } void tst_KToolBar::deleteGlobalIconSizeSetting() { KConfigGroup mglobals(KSharedConfig::openConfig(), "MainToolbarIcons"); mglobals.deleteEntry("Size"); KConfigGroup globals(KSharedConfig::openConfig(), "ToolbarIcons"); globals.deleteEntry("Size"); KSharedConfig::openConfig()->sync(); QSignalSpy spy(KIconLoader::global(), SIGNAL(iconChanged(int))); KIconLoader::global()->emitChange(KIconLoader::Desktop); spy.wait(200); } Q_DECLARE_METATYPE(Qt::ToolButtonStyle) void tst_KToolBar::testToolButtonStyleNoXmlGui_data() { QTest::addColumn("toolButtonStyle"); QTest::newRow("Qt::ToolButtonIconOnly") << Qt::ToolButtonIconOnly; QTest::newRow("Qt::ToolButtonTextOnly") << Qt::ToolButtonTextOnly; QTest::newRow("Qt::ToolButtonTextBesideIcon") << Qt::ToolButtonTextBesideIcon; QTest::newRow("Qt::ToolButtonTextUnderIcon") << Qt::ToolButtonTextUnderIcon; } void tst_KToolBar::testToolButtonStyleNoXmlGui() { QFETCH(Qt::ToolButtonStyle, toolButtonStyle); const Qt::ToolButtonStyle mainToolBarDefaultStyle = Qt::ToolButtonTextBesideIcon; // was TextUnderIcon before KDE-4.4.0 const bool selectedDefaultForMainToolbar = toolButtonStyle == mainToolBarDefaultStyle; const bool selectedDefaultForOtherToolbar = toolButtonStyle == Qt::ToolButtonTextBesideIcon; KConfig config(QStringLiteral("tst_KToolBar")); KConfigGroup group(&config, "group"); { KMainWindow kmw; KToolBar *mainToolBar = kmw.toolBar(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBar(QStringLiteral("otherToolBar")); // Default settings (applied by applyAppearanceSettings) QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)mainToolBarDefaultStyle); QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)Qt::ToolButtonTextBesideIcon); // see r883541 QCOMPARE(kmw.toolBarArea(mainToolBar), Qt::TopToolBarArea); // Changing settings for a given toolbar, as user mainToolBar->setToolButtonStyle(toolButtonStyle); otherToolBar->setToolButtonStyle(toolButtonStyle); // Save settings kmw.saveMainWindowSettings(group); if (selectedDefaultForMainToolbar) { QCOMPARE(group.groupList().count(), 0); // nothing to save QVERIFY(!group.group("Toolbar mainToolBar").hasKey("ToolButtonStyle")); } else { QCOMPARE(group.groupList().count(), 2); // two subgroups (one for each toolbar) QVERIFY(group.group("Toolbar mainToolBar").hasKey("ToolButtonStyle")); } } { // Recreate, load, compare. KMainWindow kmw; KToolBar *mainToolBar = kmw.toolBar(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBar(QStringLiteral("otherToolBar")); QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)mainToolBarDefaultStyle); kmw.applyMainWindowSettings(group); QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)toolButtonStyle); QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)toolButtonStyle); // Now change KDE-global setting changeGlobalToolButtonStyleSetting(QStringLiteral("IconOnly"), QStringLiteral("TextOnly")); if (selectedDefaultForMainToolbar) { QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)Qt::ToolButtonIconOnly); } else { QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)toolButtonStyle); } if (selectedDefaultForOtherToolbar) { QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)Qt::ToolButtonTextOnly); } else { QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)toolButtonStyle); } } } void tst_KToolBar::testToolButtonStyleXmlGui_data() { QTest::addColumn("toolButtonStyle"); // Expected style after KDE-global is changed to main=IconOnly/other=TextOnly QTest::addColumn("expectedStyleMainToolbar"); QTest::addColumn("expectedStyleOtherToolbar"); // xml says text-under-icons, user-selected should always win QTest::addColumn("expectedStyleCleanToolbar"); // should always follow kde-global -> always textonly. QTest::newRow("Qt::ToolButtonTextUnderIcon") << Qt::ToolButtonTextUnderIcon << Qt::ToolButtonTextUnderIcon << Qt::ToolButtonTextUnderIcon << Qt::ToolButtonTextOnly; QTest::newRow("Qt::ToolButtonTextBesideIcon") << Qt::ToolButtonTextBesideIcon << Qt::ToolButtonIconOnly /* was default -> using kde global */ << Qt::ToolButtonTextBesideIcon << Qt::ToolButtonTextOnly; QTest::newRow("Qt::ToolButtonIconOnly") << Qt::ToolButtonIconOnly << Qt::ToolButtonIconOnly << Qt::ToolButtonIconOnly << Qt::ToolButtonTextOnly; QTest::newRow("Qt::ToolButtonTextOnly") << Qt::ToolButtonTextOnly << Qt::ToolButtonTextOnly << Qt::ToolButtonTextOnly << Qt::ToolButtonTextOnly; } void tst_KToolBar::testToolButtonStyleXmlGui() { QFETCH(Qt::ToolButtonStyle, toolButtonStyle); QFETCH(Qt::ToolButtonStyle, expectedStyleMainToolbar); QFETCH(Qt::ToolButtonStyle, expectedStyleOtherToolbar); QFETCH(Qt::ToolButtonStyle, expectedStyleCleanToolbar); const Qt::ToolButtonStyle mainToolBarDefaultStyle = Qt::ToolButtonTextBesideIcon; // was TextUnderIcon before KDE-4.4.0 KConfig config(QStringLiteral("tst_KToolBar")); KConfigGroup group(&config, "group"); { TestXmlGuiWindow kmw(m_xml, "tst_ktoolbar.rc"); kmw.createActions(QStringList() << QStringLiteral("go_up")); kmw.createGUI(); KToolBar *mainToolBar = kmw.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBarByName(QStringLiteral("otherToolBar")); KToolBar *cleanToolBar = kmw.toolBarByName(QStringLiteral("cleanToolBar")); QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)mainToolBarDefaultStyle); QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)Qt::ToolButtonTextUnderIcon); // from xml QCOMPARE((int)cleanToolBar->toolButtonStyle(), (int)Qt::ToolButtonTextBesideIcon); // Changing settings for a given toolbar, as user mainToolBar->setToolButtonStyle(toolButtonStyle); otherToolBar->setToolButtonStyle(toolButtonStyle); // Save settings kmw.saveMainWindowSettings(group); // Now change KDE-global setting changeGlobalToolButtonStyleSetting(QStringLiteral("IconOnly"), QStringLiteral("TextOnly")); QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)expectedStyleMainToolbar); QCOMPARE((int)otherToolBar->toolButtonStyle(), (int)expectedStyleOtherToolbar); QCOMPARE((int)cleanToolBar->toolButtonStyle(), (int)expectedStyleCleanToolbar); } } void tst_KToolBar::changeGlobalToolButtonStyleSetting(const QString &mainToolBar, const QString &otherToolBars) { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); group.writeEntry("ToolButtonStyle", mainToolBar); group.writeEntry("ToolButtonStyleOtherToolbars", otherToolBars); group.sync(); // Same dbus connect as the one in KToolBar. We want our spy to be notified of receiving it. QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), this, SIGNAL(signalAppearanceChanged())); QSignalSpy spy(this, SIGNAL(signalAppearanceChanged())); KToolBar::emitToolbarStyleChanged(); spy.wait(2000); } void tst_KToolBar::deleteGlobalToolButtonStyleSetting() { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); group.deleteEntry("ToolButtonStyle"); group.deleteEntry("ToolButtonStyleOtherToolbars"); KSharedConfig::openConfig()->sync(); } void tst_KToolBar::testToolBarPosition() { TestXmlGuiWindow kmw(m_xml, "tst_ktoolbar.rc"); kmw.createActions(QStringList() << QStringLiteral("go_up")); kmw.createGUI(); KToolBar *mainToolBar = kmw.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = kmw.toolBarByName(QStringLiteral("otherToolBar")); QCOMPARE(kmw.toolBarArea(mainToolBar), Qt::TopToolBarArea); QCOMPARE(kmw.toolBarArea(otherToolBar), Qt::BottomToolBarArea); } void tst_KToolBar::testXmlGuiSwitching() { const QByteArray windowXml = "\n" "\n" "\n" "\n" "\n" "\n"; TestXmlGuiWindow kmw(windowXml, "tst_ktoolbar.rc"); kmw.createActions(QStringList() << QStringLiteral("go_up")); kmw.createGUI(); TestGuiClient firstClient(m_xml); kmw.guiFactory()->addClient(&firstClient); { //qDebug() << "Added gui client"; KToolBar *mainToolBar = firstClient.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = firstClient.toolBarByName(QStringLiteral("otherToolBar")); KToolBar *bigToolBar = firstClient.toolBarByName(QStringLiteral("bigToolBar")); KToolBar *hiddenToolBar = firstClient.toolBarByName(QStringLiteral("hiddenToolBar")); KToolBar *secondHiddenToolBar = firstClient.toolBarByName(QStringLiteral("secondHiddenToolBar")); QCOMPARE(hiddenToolBar->isHidden(), true); QCOMPARE(secondHiddenToolBar->isHidden(), true); // Make (unsaved) changes as user QMetaObject::invokeMethod(mainToolBar, "slotContextTextRight"); // mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QMetaObject::invokeMethod(mainToolBar, "slotContextRight"); // kmw.addToolBar(Qt::RightToolBarArea, mainToolBar); otherToolBar->setIconDimensions(35); bigToolBar->setIconDimensions(35); bigToolBar->hide(); hiddenToolBar->show(); } kmw.guiFactory()->removeClient(&firstClient); //qDebug() << "Removed gui client"; QVERIFY(!kmw.guiFactory()->container(QStringLiteral("mainToolBar"), &kmw)); QVERIFY(!kmw.guiFactory()->container(QStringLiteral("otherToolBar"), &kmw)); QVERIFY(!kmw.guiFactory()->container(QStringLiteral("bigToolBar"), &kmw)); QVERIFY(!kmw.guiFactory()->container(QStringLiteral("mainToolBar"), &firstClient)); QVERIFY(!kmw.guiFactory()->container(QStringLiteral("otherToolBar"), &firstClient)); QVERIFY(!kmw.guiFactory()->container(QStringLiteral("bigToolBar"), &firstClient)); kmw.guiFactory()->addClient(&firstClient); //qDebug() << "Re-added gui client"; KToolBar *mainToolBar = firstClient.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = firstClient.toolBarByName(QStringLiteral("otherToolBar")); KToolBar *bigToolBar = firstClient.toolBarByName(QStringLiteral("bigToolBar")); KToolBar *cleanToolBar = firstClient.toolBarByName(QStringLiteral("cleanToolBar")); KToolBar *hiddenToolBar = firstClient.toolBarByName(QStringLiteral("hiddenToolBar")); KToolBar *secondHiddenToolBar = firstClient.toolBarByName(QStringLiteral("secondHiddenToolBar")); QCOMPARE((int)mainToolBar->toolButtonStyle(), (int)Qt::ToolButtonTextBesideIcon); QCOMPARE(mainToolBar->isHidden(), false); QCOMPARE(kmw.toolBarArea(mainToolBar), Qt::RightToolBarArea); QCOMPARE(mainToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::MainToolbar)); QCOMPARE(otherToolBar->iconSize().width(), 35); QCOMPARE(bigToolBar->iconSize().width(), 35); QCOMPARE(cleanToolBar->iconSize().width(), KIconLoader::global()->currentSize(KIconLoader::Toolbar)); QCOMPARE(bigToolBar->isHidden(), true); QCOMPARE(hiddenToolBar->isHidden(), false); QCOMPARE(secondHiddenToolBar->isHidden(), true); // Now change KDE-global setting, what happens to unsaved changes? changeGlobalIconSizeSetting(32, 33); QCOMPARE(bigToolBar->iconSize().width(), 35); // fine now, saved or unsaved makes no difference QCOMPARE(otherToolBar->iconSize().width(), 35); // Now save, and check what we saved KConfig config(QStringLiteral("tst_KToolBar")); KConfigGroup group(&config, "group"); kmw.saveMainWindowSettings(group); QCOMPARE(group.group("Toolbar bigToolBar").readEntry("IconSize", 0), 35); QCOMPARE(group.group("Toolbar otherToolBar").readEntry("IconSize", 0), 35); QVERIFY(!group.group("Toolbar cleanToolBar").hasKey("IconSize")); //QCOMPARE(group.group("Toolbar bigToolBar").readEntry("Hidden", false), true); //QVERIFY(!group.group("Toolbar cleanToolBar").hasKey("Hidden")); //QVERIFY(!group.group("Toolbar hiddenToolBar").hasKey("Hidden")); // Recreate window and apply config; is hidden toolbar shown as expected? { TestXmlGuiWindow kmw2(windowXml, "tst_ktoolbar.rc"); kmw2.createActions(QStringList() << QStringLiteral("go_up")); kmw2.createGUI(); TestGuiClient firstClient(m_xml); kmw2.guiFactory()->addClient(&firstClient); KToolBar *mainToolBar = firstClient.toolBarByName(QStringLiteral("mainToolBar")); KToolBar *otherToolBar = firstClient.toolBarByName(QStringLiteral("otherToolBar")); KToolBar *bigToolBar = firstClient.toolBarByName(QStringLiteral("bigToolBar")); KToolBar *hiddenToolBar = firstClient.toolBarByName(QStringLiteral("hiddenToolBar")); KToolBar *secondHiddenToolBar = firstClient.toolBarByName(QStringLiteral("secondHiddenToolBar")); QCOMPARE(bigToolBar->isHidden(), false); QCOMPARE(hiddenToolBar->isHidden(), true); QCOMPARE(secondHiddenToolBar->isHidden(), true); kmw2.show(); // Check that secondHiddenToolBar is not shown+hidden immediately? m_showWasCalled = false; secondHiddenToolBar->installEventFilter(this); kmw2.applyMainWindowSettings(group); QCOMPARE(mainToolBar->isHidden(), false); QCOMPARE(kmw2.toolBarArea(mainToolBar), Qt::RightToolBarArea); QCOMPARE(otherToolBar->iconSize().width(), 35); QCOMPARE(bigToolBar->iconSize().width(), 35); QCOMPARE(bigToolBar->isHidden(), true); QCOMPARE(hiddenToolBar->isHidden(), false); QCOMPARE(secondHiddenToolBar->isHidden(), true); QVERIFY(!m_showWasCalled); } } void tst_KToolBar::testKAuthorizedDisableToggleAction() { TestXmlGuiWindow kmw(m_xml, "tst_ktoolbar.rc"); kmw.createGUI(); foreach (KToolBar *toolbar, kmw.toolBars()) { QVERIFY(!toolbar->toggleViewAction()->isEnabled()); } } bool tst_KToolBar::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); if (event->type() == QEvent::Show) { m_showWasCalled = true; return true; } return false; } #include "ktoolbar_unittest.moc" diff --git a/autotests/testguiclient.h b/autotests/testguiclient.h index 0a6f71a..0245e6d 100644 --- a/autotests/testguiclient.h +++ b/autotests/testguiclient.h @@ -1,76 +1,75 @@ /* This file is part of the KDE libraries Copyright (c) 2009 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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. */ #ifndef TESTGUICLIENT_H #define TESTGUICLIENT_H -#include #include #include // because setDOMDocument and setXML are protected class TestGuiClient : public KXMLGUIClient { public: explicit TestGuiClient(const QByteArray &xml = QByteArray()) : KXMLGUIClient() { if (!xml.isNull()) { setXML(QString::fromLatin1(xml)); } } void setXMLFilePublic(const QString &file, bool merge = false, bool setXMLDoc = true) { setXMLFile(file, merge, setXMLDoc); } void createGUI(const QByteArray &xml, bool withUiStandards = false) { if (withUiStandards) { setXMLFile(KXMLGUIClient::standardsXmlFileLocation()); } setXML(QString::fromLatin1(xml), true); } void createActions(const QStringList &actionNames) { KActionCollection *coll = actionCollection(); Q_FOREACH (const QString &actionName, actionNames) { coll->addAction(actionName)->setText(QStringLiteral("Action")); } } // Find a toolbar (created by this guiclient) KToolBar *toolBarByName(const QString &name) { //qDebug() << "containers:" << factory()->containers("ToolBar"); QWidget *toolBarW = factory()->container(name, this); if (!toolBarW) { qWarning() << "No toolbar found with name" << name; } Q_ASSERT(toolBarW); KToolBar *toolBar = qobject_cast(toolBarW); Q_ASSERT(toolBar); return toolBar; } }; #endif /* TESTGUICLIENT_H */ diff --git a/src/kaboutapplicationdialog.cpp b/src/kaboutapplicationdialog.cpp index 327cbde..a621038 100644 --- a/src/kaboutapplicationdialog.cpp +++ b/src/kaboutapplicationdialog.cpp @@ -1,348 +1,348 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Urs Wolfer Copyright (C) 2008 Friedrich W. H. Kossebau Copyright (C) 2010 Teo Mrnjavac Parts of this class have been take from the KAboutApplication class, which was Copyright (C) 2000 Waldo Bastian (bastian@kde.org) and Espen Sand (espen@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kaboutapplicationdialog.h" #include "kaboutapplicationpersonmodel_p.h" #include "kaboutapplicationpersonlistview_p.h" #include "kaboutapplicationpersonlistdelegate_p.h" #include "../kxmlgui_version.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include +#include class Q_DECL_HIDDEN KAboutApplicationDialog::Private { public: Private(KAboutApplicationDialog *parent) : q(parent), aboutData(KAboutData::applicationData()) {} void init(const KAboutData &aboutData, Options opt); void _k_showLicense(const QString &number); KAboutApplicationDialog *q; KAboutData aboutData; }; KAboutApplicationDialog::KAboutApplicationDialog(const KAboutData &aboutData, QWidget *parent) : QDialog(parent), d(new Private(this)) { d->init(aboutData, NoOptions); } KAboutApplicationDialog::KAboutApplicationDialog(const KAboutData &aboutData, Options opt, QWidget *parent) : QDialog(parent), d(new Private(this)) { d->init(aboutData, opt); } void KAboutApplicationDialog::Private::init(const KAboutData &ad, Options opt) { aboutData = ad; q->setWindowTitle(i18n("About %1", aboutData.displayName())); q->setModal(false); //Set up the title widget... KTitleWidget *titleWidget = new KTitleWidget(q); QIcon windowIcon = qApp->windowIcon(); if (windowIcon.isNull() && !aboutData.programIconName().isEmpty()) { windowIcon = QIcon::fromTheme(aboutData.programIconName()); } titleWidget->setPixmap(windowIcon.pixmap(48, 48), KTitleWidget::ImageLeft); if (aboutData.programLogo().canConvert()) { titleWidget->setPixmap(aboutData.programLogo().value(), KTitleWidget::ImageLeft); } else if (aboutData.programLogo().canConvert()) { titleWidget->setPixmap(QPixmap::fromImage(aboutData.programLogo().value()), KTitleWidget::ImageLeft); } titleWidget->setText(i18n("%1
Version %2", aboutData.displayName(), aboutData.version())); //Then the tab bar... QTabWidget *tabWidget = new QTabWidget; tabWidget->setUsesScrollButtons(false); //Set up the first page... QString aboutPageText = aboutData.shortDescription() + QLatin1Char('\n'); if (!aboutData.otherText().isEmpty()) { aboutPageText += QLatin1Char('\n') + aboutData.otherText() + QLatin1Char('\n'); } if (!aboutData.copyrightStatement().isEmpty()) { aboutPageText += QLatin1Char('\n') + aboutData.copyrightStatement() + QLatin1Char('\n'); } if (!aboutData.homepage().isEmpty()) { aboutPageText += QLatin1Char('\n') + QStringLiteral("%1").arg(aboutData.homepage()) + QLatin1Char('\n'); } aboutPageText = aboutPageText.trimmed(); QLabel *aboutLabel = new QLabel; aboutLabel->setWordWrap(true); aboutLabel->setOpenExternalLinks(true); aboutLabel->setText(aboutPageText.replace(QLatin1Char('\n'), QStringLiteral("
"))); aboutLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); QVBoxLayout *aboutLayout = new QVBoxLayout; aboutLayout->addStretch(); aboutLayout->addWidget(aboutLabel); const int licenseCount = aboutData.licenses().count(); for (int i = 0; i < licenseCount; ++i) { const KAboutLicense &license = aboutData.licenses().at(i); QLabel *showLicenseLabel = new QLabel; showLicenseLabel->setText(QStringLiteral("%2").arg(QString::number(i), i18n("License: %1", license.name(KAboutLicense::FullName)))); showLicenseLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); connect(showLicenseLabel, SIGNAL(linkActivated(QString)), q, SLOT(_k_showLicense(QString))); aboutLayout->addWidget(showLicenseLabel); } aboutLayout->addStretch(); QWidget *aboutWidget = new QWidget(q); aboutWidget->setLayout(aboutLayout); tabWidget->addTab(aboutWidget, i18n("&About")); // Version QWidget *versionWidget = new QWidget(q); QVBoxLayout *versionLayout = new QVBoxLayout; if (!(opt & HideKdeVersion)) { QLabel *versionLabel = new QLabel( i18n("
  • KDE Frameworks %1
  • Qt %2 (built against %3)
  • The %4 windowing system
", QStringLiteral(KXMLGUI_VERSION_STRING), QString::fromLocal8Bit(qVersion()), QStringLiteral(QT_VERSION_STR), QGuiApplication::platformName())); versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); versionLayout->addWidget(versionLabel); } versionLayout->addStretch(); versionWidget->setLayout(versionLayout); tabWidget->addTab(versionWidget, i18n("&Libraries")); //Palette needed at least for translators... QPalette transparentBackgroundPalette; transparentBackgroundPalette.setColor(QPalette::Base, Qt::transparent); transparentBackgroundPalette.setColor(QPalette::Text, transparentBackgroundPalette.color(QPalette::WindowText)); //And here we go, authors page... const int authorCount = aboutData.authors().count(); if (authorCount) { QWidget *authorWidget = new QWidget(q); QVBoxLayout *authorLayout = new QVBoxLayout(authorWidget); authorLayout->setContentsMargins(0, 0, 0, 0); if (!aboutData.customAuthorTextEnabled() || !aboutData.customAuthorRichText().isEmpty()) { QLabel *bugsLabel = new QLabel(authorWidget); bugsLabel->setContentsMargins(4, 2, 0, 4); bugsLabel->setOpenExternalLinks(true); if (!aboutData.customAuthorTextEnabled()) { const QString bugAddress = aboutData.bugAddress(); if (bugAddress.isEmpty() || bugAddress == QLatin1String("submit@bugs.kde.org")) { bugsLabel->setText(i18n("Please use https://bugs.kde.org to report bugs.\n")); } else { QUrl bugUrl(bugAddress); if (bugUrl.scheme().isEmpty()) { bugUrl.setScheme(QStringLiteral("mailto")); } bugsLabel->setText(i18n("Please report bugs to %2.\n", bugUrl.toString(), bugAddress)); } } else { bugsLabel->setText(aboutData.customAuthorRichText()); } bugsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); authorLayout->addWidget(bugsLabel); } KDEPrivate::KAboutApplicationPersonModel *authorModel = new KDEPrivate::KAboutApplicationPersonModel(aboutData.authors(), aboutData.ocsProviderUrl(), authorWidget); KDEPrivate::KAboutApplicationPersonListView *authorView = new KDEPrivate::KAboutApplicationPersonListView(authorWidget); KDEPrivate::KAboutApplicationPersonListDelegate *authorDelegate = new KDEPrivate::KAboutApplicationPersonListDelegate(authorView, authorView); authorView->setModel(authorModel); authorView->setItemDelegate(authorDelegate); authorView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); authorLayout->addWidget(authorView); const QString authorPageTitle = (authorCount == 1) ? i18n("A&uthor") : i18n("A&uthors"); tabWidget->addTab(authorWidget, authorPageTitle); } //And credits page... const int creditsCount = aboutData.credits().count(); if (creditsCount) { QWidget *creditWidget = new QWidget(q); QVBoxLayout *creditLayout = new QVBoxLayout(creditWidget); creditLayout->setContentsMargins(0, 0, 0, 0); KDEPrivate::KAboutApplicationPersonModel *creditModel = new KDEPrivate::KAboutApplicationPersonModel(aboutData.credits(), aboutData.ocsProviderUrl(), creditWidget); KDEPrivate::KAboutApplicationPersonListView *creditView = new KDEPrivate::KAboutApplicationPersonListView(creditWidget); KDEPrivate::KAboutApplicationPersonListDelegate *creditDelegate = new KDEPrivate::KAboutApplicationPersonListDelegate(creditView, creditView); creditView->setModel(creditModel); creditView->setItemDelegate(creditDelegate); creditView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); creditLayout->addWidget(creditView); tabWidget->addTab(creditWidget, i18n("&Thanks To")); } //Finally, the optional translators page... if (!(opt & HideTranslators)) { const int translatorsCount = aboutData.translators().count(); if (translatorsCount) { QWidget *translatorWidget = new QWidget(q); QVBoxLayout *translatorLayout = new QVBoxLayout(translatorWidget); translatorLayout->setContentsMargins(0, 0, 0, 0); KDEPrivate::KAboutApplicationPersonModel *translatorModel = new KDEPrivate::KAboutApplicationPersonModel(aboutData.translators(), aboutData.ocsProviderUrl(), translatorWidget); KDEPrivate::KAboutApplicationPersonListView *translatorView = new KDEPrivate::KAboutApplicationPersonListView(translatorWidget); KDEPrivate::KAboutApplicationPersonListDelegate *translatorDelegate = new KDEPrivate::KAboutApplicationPersonListDelegate(translatorView, translatorView); translatorView->setModel(translatorModel); translatorView->setItemDelegate(translatorDelegate); translatorView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); translatorLayout->addWidget(translatorView); QString aboutTranslationTeam = KAboutData::aboutTranslationTeam(); if (!aboutTranslationTeam.isEmpty()) { QLabel *translationTeamLabel = new QLabel(translatorWidget); translationTeamLabel->setContentsMargins(4, 2, 4, 4); translationTeamLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); translationTeamLabel->setWordWrap(true); translationTeamLabel->setText(aboutTranslationTeam); translationTeamLabel->setOpenExternalLinks(true); translatorLayout->addWidget(translationTeamLabel); //TODO: this could be displayed as a view item to save space } tabWidget->addTab(translatorWidget, i18n("T&ranslation")); } } QDialogButtonBox *buttonBox = new QDialogButtonBox; buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); //And we jam everything together in a layout... QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(titleWidget); mainLayout->addWidget(tabWidget); mainLayout->addWidget(buttonBox); q->setLayout(mainLayout); } KAboutApplicationDialog::~KAboutApplicationDialog() { delete d; // The delegate wants to be deleted before the items it created, otherwise // complains bitterly about it qDeleteAll(findChildren()); } void KAboutApplicationDialog::Private::_k_showLicense(const QString &number) { QDialog *dialog = new QDialog(q); dialog->setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout *layout = new QVBoxLayout; dialog->setLayout(layout); dialog->setWindowTitle(i18n("License Agreement")); const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); QFontMetrics metrics(font); const QString licenseText = aboutData.licenses().at(number.toInt()).text(); QTextBrowser *licenseBrowser = new QTextBrowser(dialog); licenseBrowser->setFont(font); licenseBrowser->setLineWrapMode(QTextEdit::NoWrap); licenseBrowser->setText(licenseText); layout->addWidget(licenseBrowser); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); layout->addWidget(buttonBox); // try to set up the dialog such that the full width of the // document is visible without horizontal scroll-bars being required const int marginHint = dialog->style()->pixelMetric(QStyle::PM_DefaultChildMargin); const qreal idealWidth = licenseBrowser->document()->idealWidth() + (2 * marginHint) + licenseBrowser->verticalScrollBar()->width() * 2; // try to allow enough height for a reasonable number of lines to be shown const int idealHeight = metrics.height() * 30; dialog->resize(dialog->sizeHint().expandedTo(QSize(qRound(idealWidth), idealHeight))); dialog->adjustSize(); dialog->show(); } #include "moc_kaboutapplicationdialog.cpp" diff --git a/src/kaboutapplicationdialog.h b/src/kaboutapplicationdialog.h index 36eecdd..c6b096f 100644 --- a/src/kaboutapplicationdialog.h +++ b/src/kaboutapplicationdialog.h @@ -1,107 +1,106 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Urs Wolfer Parts of this class have been take from the KAboutApplication class, which was Copyright (C) 2000 Waldo Bastian (bastian@kde.org) and Espen Sand (espen@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KABOUT_APPLICATION_DIALOG_H #define KABOUT_APPLICATION_DIALOG_H #include #include -#include class KAboutData; /** * @class KAboutApplicationDialog kaboutapplicationdialog.h KAboutApplicationDialog * * @short Standard "About Application" dialog box. * * This class provides the standard "About Application" dialog box * that is used by KHelpMenu. It uses the information of the global * KAboutData that is specified at the start of your program in * main(). Normally you should not use this class directly but rather * the KHelpMenu class or even better just subclass your toplevel * window from KMainWindow. If you do the latter, the help menu and * thereby this dialog box is available through the * KMainWindow::helpMenu() function. * * \image html kaboutapplicationdialog.png "KAboutApplicationDialog" * * @author Urs Wolfer uwolfer @ kde.org */ class KXMLGUI_EXPORT KAboutApplicationDialog : public QDialog { Q_OBJECT public: /** * Defines some options which can be applied to the about dialog * * @since 4.4 */ enum Option { NoOptions = 0x0, ///< No options, show the standard about dialog HideTranslators = 0x1, ///< Don't show the translators tab HideKdeVersion = 0x2 ///< Don't show the KDE version next to the application name and version }; Q_DECLARE_FLAGS(Options, Option) Q_FLAG(Options) /** * Constructor. Creates a fully featured "About Application" dialog box. * * @param aboutData A KAboutData object which data * will be used for filling the dialog. * @param opts Additional options that can be applied, such as hiding the KDE version * or the translators tab. * @param parent The parent of the dialog box. You should use the * toplevel window so that the dialog becomes centered. * * @since 4.4 */ explicit KAboutApplicationDialog(const KAboutData &aboutData, Options opts, QWidget *parent = nullptr); /** * Constructor. Creates a fully featured "About Application" dialog box. * * @param aboutData A KAboutData object which data * will be used for filling the dialog. * @param parent The parent of the dialog box. You should use the * toplevel window so that the dialog becomes centered. */ explicit KAboutApplicationDialog(const KAboutData &aboutData, QWidget *parent = nullptr); virtual ~KAboutApplicationDialog(); private: class Private; Private *const d; Q_PRIVATE_SLOT(d, void _k_showLicense(const QString &)) Q_DISABLE_COPY(KAboutApplicationDialog) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KAboutApplicationDialog::Options) #endif diff --git a/src/kaboutapplicationpersonlistdelegate_p.cpp b/src/kaboutapplicationpersonlistdelegate_p.cpp index 6fbc6a3..155734d 100644 --- a/src/kaboutapplicationpersonlistdelegate_p.cpp +++ b/src/kaboutapplicationpersonlistdelegate_p.cpp @@ -1,305 +1,306 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kaboutapplicationpersonlistdelegate_p.h" #include "kaboutapplicationpersonmodel_p.h" #include "kaboutapplicationpersonlistview_p.h" #include "ktoolbar.h" #include #include #include #include #include #include +#include namespace KDEPrivate { enum { AVATAR_HEIGHT = 50, AVATAR_WIDTH = 50, MAIN_LINKS_HEIGHT = 32, SOCIAL_LINKS_HEIGHT = 26, MAX_SOCIAL_LINKS = 9 }; KAboutApplicationPersonListDelegate::KAboutApplicationPersonListDelegate( QAbstractItemView *itemView, QObject *parent) : KWidgetItemDelegate(itemView, parent) { } QList< QWidget *> KAboutApplicationPersonListDelegate::createItemWidgets(const QModelIndex &index) const { Q_UNUSED(index); QList< QWidget *> list; QLabel *textLabel = new QLabel(itemView()); list.append(textLabel); KToolBar *mainLinks = new KToolBar(itemView(), false, false); QAction *emailAction = new QAction(QIcon::fromTheme(QStringLiteral("internet-mail")), i18nc("Action to send an email to a contributor", "Email contributor"), mainLinks); emailAction->setVisible(false); mainLinks->addAction(emailAction); QAction *homepageAction = new QAction(QIcon::fromTheme(QStringLiteral("applications-internet")), i18n("Visit contributor's homepage"), mainLinks); homepageAction->setVisible(false); mainLinks->addAction(homepageAction); QAction *visitProfileAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), QString(), mainLinks); visitProfileAction->setVisible(false); mainLinks->addAction(visitProfileAction); list.append(mainLinks); KToolBar *socialLinks = new KToolBar(itemView(), false, false); for (int i = 0; i < MAX_SOCIAL_LINKS; ++i) { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("applications-internet")), QString(), socialLinks); action->setVisible(false); socialLinks->addAction(action); } list.append(socialLinks); connect(mainLinks, &QToolBar::actionTriggered, this, &KAboutApplicationPersonListDelegate::launchUrl); connect(socialLinks, &QToolBar::actionTriggered, this, &KAboutApplicationPersonListDelegate::launchUrl); return list; } void KAboutApplicationPersonListDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const { const int margin = option.fontMetrics.height() / 2; KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); QRect wRect = widgetsRect(option, index); //Let's fill in the text first... QLabel *label = qobject_cast< QLabel * >(widgets.at(TextLabel)); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QString text = buildTextForProfile(profile); label->move(wRect.left(), wRect.top()); label->resize(wRect.width(), heightForString(text, wRect.width() - margin, option) + margin); label->setWordWrap(true); label->setContentsMargins(0, 0, 0, 0); label->setAlignment(Qt::AlignBottom | Qt::AlignLeft); label->setForegroundRole(QPalette::WindowText); label->setText(text); //And now we fill in the main links (email + homepage + OCS profile)... KToolBar *mainLinks = qobject_cast< KToolBar * >(widgets.at(MainLinks)); mainLinks->setIconSize(QSize(22, 22)); mainLinks->setContentsMargins(0, 0, 0, 0); mainLinks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QAction *action; if (!profile.email().isEmpty()) { action = mainLinks->actions().at(EmailAction); action->setToolTip(i18nc("Action to send an email to a contributor", "Email contributor\n%1", profile.email())); action->setData(QStringLiteral("mailto:%1").arg(profile.email())); action->setVisible(true); } if (!profile.homepage().isEmpty()) { action = mainLinks->actions().at(HomepageAction); action->setToolTip(i18n("Visit contributor's homepage\n%1", profile.homepage().toString())); action->setData(profile.homepage().toString()); action->setVisible(true); } if (!profile.ocsProfileUrl().isEmpty()) { action = mainLinks->actions().at(VisitProfileAction); KAboutApplicationPersonModel *model = qobject_cast< KAboutApplicationPersonModel * >(itemView()->model()); action->setToolTip(i18n("Visit contributor's profile on %1\n%2", model->providerName(), profile.ocsProfileUrl())); action->setData(profile.ocsProfileUrl()); action->setVisible(true); } mainLinks->resize(QSize(mainLinks->sizeHint().width(), MAIN_LINKS_HEIGHT)); mainLinks->move(wRect.left(), wRect.top() + label->height()); //Finally, the social links... KToolBar *socialLinks = qobject_cast< KToolBar * >(widgets.at(SocialLinks)); socialLinks->setIconSize(QSize(16, 16)); socialLinks->setContentsMargins(0, 0, 0, 0); socialLinks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); int currentSocialLinkAction = 0; Q_FOREACH (KAboutApplicationPersonProfileOcsLink link, profile.ocsLinks()) { if (!profile.homepage().isEmpty() && profile.homepage() == link.url()) { continue; //We skip it if it's the same as the homepage from KAboutData } action = socialLinks->actions().at(currentSocialLinkAction); if (link.type() == KAboutApplicationPersonProfileOcsLink::Other) { action->setToolTip(i18n("Visit contributor's page\n%1", link.url().toString())); } else if (link.type() == KAboutApplicationPersonProfileOcsLink::Blog) { action->setToolTip(i18n("Visit contributor's blog\n%1", link.url().toString())); } else if (link.type() == KAboutApplicationPersonProfileOcsLink::Homepage) { action->setToolTip(i18n("Visit contributor's homepage\n%1", link.url().toString())); } else { action->setToolTip(i18n("Visit contributor's profile on %1\n%2", link.prettyType(), link.url().toString())); } action->setIcon(link.icon()); action->setData(link.url().toString()); action->setVisible(true); ++currentSocialLinkAction; if (currentSocialLinkAction > MAX_SOCIAL_LINKS - 1) { break; } } socialLinks->resize(QSize(socialLinks->sizeHint().width(), SOCIAL_LINKS_HEIGHT)); socialLinks->move(wRect.left() + mainLinks->width(), wRect.top() + label->height() + (MAIN_LINKS_HEIGHT - SOCIAL_LINKS_HEIGHT) / 2); itemView()->reset(); } QSize KAboutApplicationPersonListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); bool hasAvatar = !profile.avatar().isNull(); int margin = option.fontMetrics.height() / 2; int height = hasAvatar ? qMax(widgetsRect(option, index).height(), AVATAR_HEIGHT + 2 * margin) : widgetsRect(option, index).height(); QSize metrics(option.fontMetrics.height() * 7, height); return metrics; } void KAboutApplicationPersonListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int margin = option.fontMetrics.height() / 2; QStyle *style = QApplication::style(); style->drawPrimitive(QStyle::PE_Widget, &option, painter, nullptr); const KAboutApplicationPersonModel *model = qobject_cast< const KAboutApplicationPersonModel * >(index.model()); if (model->hasAvatarPixmaps()) { int height = qMax(widgetsRect(option, index).height(), AVATAR_HEIGHT + 2 * margin); QPoint point(option.rect.left() + 2 * margin, option.rect.top() + ((height - AVATAR_HEIGHT) / 2)); KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); if (!profile.avatar().isNull()) { QPixmap pixmap = profile.avatar(); point.setX((AVATAR_WIDTH - pixmap.width()) / 2 + 5); point.setY(option.rect.top() + ((height - pixmap.height()) / 2)); painter->drawPixmap(point, pixmap); QPoint framePoint(point.x() - 5, point.y() - 5); QPixmap framePixmap(QStringLiteral(":/kxmlgui5/thumb_frame.png")); painter->drawPixmap(framePoint, framePixmap.scaled(pixmap.width() + 10, pixmap.height() + 10)); } } } void KAboutApplicationPersonListDelegate::launchUrl(QAction *action) const { QString url = action->data().toString(); if (!url.isEmpty()) { QDesktopServices::openUrl(QUrl(url)); } } int KAboutApplicationPersonListDelegate::heightForString(const QString &string, int lineWidth, const QStyleOptionViewItem &option) const { QFontMetrics fm = option.fontMetrics; QRect boundingRect = fm.boundingRect(0, 0, lineWidth, 9999, Qt::AlignLeft | Qt::AlignBottom | Qt::TextWordWrap, string); return boundingRect.height(); } QString KAboutApplicationPersonListDelegate::buildTextForProfile(const KAboutApplicationPersonProfile &profile) const { QString text; text += QStringLiteral(""); text += i18nc("@item Contributor name in about dialog.", "%1", profile.name()); text += QStringLiteral(""); if (!profile.task().isEmpty()) { text += QStringLiteral("\n
"); text += profile.task(); text += QStringLiteral(""); } if (!profile.location().isEmpty()) { text += QStringLiteral("\n
"); text += profile.location(); } return text; } QRect KAboutApplicationPersonListDelegate::widgetsRect(const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const { KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); int margin = option.fontMetrics.height() / 2; QRect widgetsRect; if (qobject_cast< const KAboutApplicationPersonModel * >(index.model())->hasAvatarPixmaps()) { widgetsRect = QRect(option.rect.left() + AVATAR_WIDTH + 3 * margin, margin / 2, option.rect.width() - AVATAR_WIDTH - 4 * margin, 0); } else { widgetsRect = QRect(option.rect.left() + margin, margin / 2, option.rect.width() - 2 * margin, 0); } int textHeight = heightForString(buildTextForProfile(profile), widgetsRect.width() - margin, option); widgetsRect.setHeight(textHeight + MAIN_LINKS_HEIGHT + 1.5 * margin); return widgetsRect; } } //namespace KDEPrivate diff --git a/src/kaboutapplicationpersonlistdelegate_p.h b/src/kaboutapplicationpersonlistdelegate_p.h index b38789b..02c55b3 100644 --- a/src/kaboutapplicationpersonlistdelegate_p.h +++ b/src/kaboutapplicationpersonlistdelegate_p.h @@ -1,71 +1,70 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KABOUT_APPLICATION_PERSON_LIST_DELEGATE_H #define KABOUT_APPLICATION_PERSON_LIST_DELEGATE_H #include -#include namespace KDEPrivate { class KAboutApplicationPersonProfile; class KAboutApplicationPersonListDelegate : public KWidgetItemDelegate { Q_OBJECT public: explicit KAboutApplicationPersonListDelegate(QAbstractItemView *itemView, QObject *parent = nullptr); ~KAboutApplicationPersonListDelegate() override {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QList< QWidget *> createItemWidgets(const QModelIndex &index) const override; void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override; private Q_SLOTS: void launchUrl(QAction *action) const; private: int heightForString(const QString &string, int lineWidth, const QStyleOptionViewItem &option) const; QString buildTextForProfile(const KAboutApplicationPersonProfile &profile) const; QRect widgetsRect(const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const; enum DelegateWidgets { TextLabel = 0, MainLinks, SocialLinks }; enum MainLinkActions { EmailAction = 0, HomepageAction, VisitProfileAction }; }; } //namespace KDEPrivate #endif // KABOUT_APPLICATION_PERSON_LIST_DELEGATE_H diff --git a/src/kaboutkdedialog_p.cpp b/src/kaboutkdedialog_p.cpp index 23d55f4..ce78059 100644 --- a/src/kaboutkdedialog_p.cpp +++ b/src/kaboutkdedialog_p.cpp @@ -1,162 +1,162 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Urs Wolfer Parts of this class have been take from the KAboutKDE class, which was Copyright (C) 2000 Espen Sand This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kaboutkdedialog_p.h" #include -#include #include -#include #include #include +#include +#include #include #include #include "../kxmlgui_version.h" namespace KDEPrivate { KAboutKdeDialog::KAboutKdeDialog(QWidget *parent) : QDialog(parent), d(nullptr) { setWindowTitle(i18n("About KDE")); KTitleWidget *titleWidget = new KTitleWidget(this); titleWidget->setText(i18n("KDE - Be Free!")); titleWidget->setPixmap(QIcon::fromTheme(QStringLiteral("kde")).pixmap(48), KTitleWidget::ImageLeft); QLabel *about = new QLabel; about->setMargin(10); about->setAlignment(Qt::AlignTop); about->setWordWrap(true); about->setOpenExternalLinks(true); about->setTextInteractionFlags(Qt::TextBrowserInteraction); about->setText(i18n("" "KDE is a world-wide community of software engineers, artists, writers, " "translators and creators who are committed to Free Software " "development. KDE produces the Plasma desktop environment, hundreds of applications, " "and the many software libraries that support them.

" "KDE is a cooperative enterprise: no single entity controls its direction or products. " "Instead, we work together to achieve the common goal of building the world's finest " "Free Software. Everyone is welcome to join and contribute to KDE, including you.

" "Visit %3 for " "more information about the KDE community and the software we produce.", QStringLiteral("https://www.gnu.org/philosophy/free-sw.html"), QStringLiteral("https://community.kde.org/Get_Involved"), QStringLiteral("https://www.kde.org/"))); QLabel *report = new QLabel; report->setMargin(10); report->setAlignment(Qt::AlignTop); report->setWordWrap(true); report->setOpenExternalLinks(true); report->setTextInteractionFlags(Qt::TextBrowserInteraction); report->setText(i18n("" "Software can always be improved, and the KDE team is ready to " "do so. However, you - the user - must tell us when " "something does not work as expected or could be done better.

" "KDE has a bug tracking system. Visit " "%1 or " "use the \"Report Bug...\" dialog from the \"Help\" menu to report bugs.

" "If you have a suggestion for improvement then you are welcome to use " "the bug tracking system to register your wish. Make sure you use the " "severity called \"Wishlist\".", QStringLiteral("https://bugs.kde.org/"))); QLabel *join = new QLabel; join->setMargin(10); join->setAlignment(Qt::AlignTop); join->setWordWrap(true); join->setOpenExternalLinks(true); join->setTextInteractionFlags(Qt::TextBrowserInteraction); join->setText(i18n("" "You do not have to be a software developer to be a member of the " "KDE team. You can join the national teams that translate " "program interfaces. You can provide graphics, themes, sounds, and " "improved documentation. You decide!" "

" "Visit " "%1 " "for information on some projects in which you can participate." "

" "If you need more information or documentation, then a visit to " "%2 " "will provide you with what you need.", QStringLiteral("https://www.kde.org/community/getinvolved/"), QStringLiteral("https://techbase.kde.org/"))); QLabel *support = new QLabel; support->setMargin(10); support->setAlignment(Qt::AlignTop); support->setWordWrap(true); support->setOpenExternalLinks(true); support->setTextInteractionFlags(Qt::TextBrowserInteraction); support->setText(i18n("" "KDE software is and will always be available free of charge, however creating it is not free.

" "To support development the KDE community has formed the KDE e.V., a non-profit organization " "legally founded in Germany. KDE e.V. represents the KDE community in legal and financial matters. " "See %1" " for information on KDE e.V.

" "KDE benefits from many kinds of contributions, including financial. " "We use the funds to reimburse members and others for expenses " "they incur when contributing. Further funds are used for legal " "support and organizing conferences and meetings.

" "We would like to encourage you to support our efforts with a " "financial donation, using one of the ways described at " "%2." "

Thank you very much in advance for your support.", QStringLiteral("https://ev.kde.org/"), QStringLiteral("https://www.kde.org/community/donations/"))); support->setMinimumSize(support->sizeHint()); QTabWidget *tabWidget = new QTabWidget; tabWidget->setUsesScrollButtons(false); tabWidget->addTab(about, i18nc("About KDE", "&About")); tabWidget->addTab(report, i18n("&Report Bugs or Wishes")); tabWidget->addTab(join, i18n("&Join KDE")); tabWidget->addTab(support, i18n("&Support KDE")); QLabel *image = new QLabel; QIcon icon(QStringLiteral(":/kxmlgui5/aboutkde.svg")); image->setPixmap(icon.pixmap(150, 250)); QHBoxLayout *midLayout = new QHBoxLayout; midLayout->addWidget(image); midLayout->addWidget(tabWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox; buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(titleWidget); mainLayout->addLayout(midLayout); mainLayout->addWidget(buttonBox); setLayout(mainLayout); } } diff --git a/src/kbugreport.cpp b/src/kbugreport.cpp index 9553ea6..23ecd83 100644 --- a/src/kbugreport.cpp +++ b/src/kbugreport.cpp @@ -1,583 +1,583 @@ /* This file is part of the KDE project Copyright (C) 1999 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 "kbugreport.h" #include -#include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include "kdepackages.h" #include "../kxmlgui_version.h" #include "systeminformation_p.h" #include "config-xmlgui.h" class KBugReportPrivate { public: KBugReportPrivate(KBugReport *q): q(q), m_aboutData(KAboutData::applicationData()) {} enum BugDestination { BugsKdeOrg, CustomEmail, CustomUrl }; void _k_slotConfigureEmail(); void _k_slotSetFrom(); void _k_appChanged(int); void _k_updateUrl(); KBugReport *q; QProcess *m_process; KAboutData m_aboutData; KTextEdit *m_lineedit; QLineEdit *m_subject; QLabel *m_from; QLabel *m_version; QString m_strVersion; QGroupBox *m_bgSeverity; QPushButton *m_configureEmail; QComboBox *appcombo; QString lastError; QString kde_version; QString appname; QString os; QUrl url; QList severityButtons; int currentSeverity() { for (int i = 0; i < severityButtons.count(); i++) if (severityButtons[i]->isChecked()) { return i; } return -1; } BugDestination bugDestination; }; KBugReport::KBugReport(const KAboutData &aboutData, QWidget *_parent) : QDialog(_parent), d(new KBugReportPrivate(this)) { setWindowTitle(i18n("Submit Bug Report")); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); d->m_aboutData = aboutData; d->m_process = nullptr; d->bugDestination = KBugReportPrivate::CustomEmail; const QString bugAddress = d->m_aboutData.bugAddress(); if (bugAddress == QStringLiteral("submit@bugs.kde.org")) { // This is a core KDE application -> redirect to the web form d->bugDestination = KBugReportPrivate::BugsKdeOrg; } else if (!QUrl(bugAddress).scheme().isEmpty()) { // The bug reporting address is a URL -> redirect to that d->bugDestination = KBugReportPrivate::CustomUrl; } if (d->bugDestination != KBugReportPrivate::CustomEmail) { KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::close()); } QLabel *tmpLabel; QVBoxLayout *lay = new QVBoxLayout; setLayout(lay); KTitleWidget *title = new KTitleWidget(this); title->setText(i18n("Submit Bug Report")); title->setPixmap(QIcon::fromTheme(QStringLiteral("tools-report-bug")).pixmap(32)); lay->addWidget(title); QGridLayout *glay = new QGridLayout(); lay->addLayout(glay); int row = 0; if (d->bugDestination == KBugReportPrivate::CustomEmail) { // From QString qwtstr = i18n("Your email address. If incorrect, use the Configure Email button to change it"); tmpLabel = new QLabel(i18nc("Email sender address", "From:"), this); glay->addWidget(tmpLabel, row, 0); tmpLabel->setWhatsThis(qwtstr); d->m_from = new QLabel(this); glay->addWidget(d->m_from, row, 1); d->m_from->setWhatsThis(qwtstr); // Configure email button d->m_configureEmail = new QPushButton(i18n("Configure Email..."), this); connect(d->m_configureEmail, SIGNAL(clicked()), this, SLOT(_k_slotConfigureEmail())); glay->addWidget(d->m_configureEmail, 0, 2, 3, 1, Qt::AlignTop | Qt::AlignRight); // To qwtstr = i18n("The email address this bug report is sent to."); tmpLabel = new QLabel(i18nc("Email receiver address", "To:"), this); glay->addWidget(tmpLabel, ++row, 0); tmpLabel->setWhatsThis(qwtstr); tmpLabel = new QLabel(d->m_aboutData.bugAddress(), this); tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); glay->addWidget(tmpLabel, row, 1); tmpLabel->setWhatsThis(qwtstr); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KGuiItem(i18n("&Send"), QStringLiteral("mail-send"), i18n("Send bug report."), i18n("Send this bug report to %1.", d->m_aboutData.bugAddress()))); row++; } else { d->m_configureEmail = nullptr; d->m_from = nullptr; } // Program name QString qwtstr = i18n("The application for which you wish to submit a bug report - if incorrect, please use the Report Bug menu item of the correct application"); tmpLabel = new QLabel(i18n("Application: "), this); glay->addWidget(tmpLabel, row, 0); tmpLabel->setWhatsThis(qwtstr); d->appcombo = new QComboBox(this); d->appcombo->setWhatsThis(qwtstr); QStringList packageList; for (int c = 0; packages[c]; ++c) { packageList << QString::fromLatin1(packages[c]); } d->appcombo->addItems(packageList); connect(d->appcombo, SIGNAL(activated(int)), SLOT(_k_appChanged(int))); d->appname = d->m_aboutData.productName(); glay->addWidget(d->appcombo, row, 1); int index = 0; for (; index < d->appcombo->count(); index++) { if (d->appcombo->itemText(index) == d->appname) { break; } } if (index == d->appcombo->count()) { // not present d->appcombo->addItem(d->appname); } d->appcombo->setCurrentIndex(index); tmpLabel->setWhatsThis(qwtstr); // Version qwtstr = i18n("The version of this application - please make sure that no newer version is available before sending a bug report"); tmpLabel = new QLabel(i18n("Version:"), this); glay->addWidget(tmpLabel, ++row, 0); tmpLabel->setWhatsThis(qwtstr); d->m_strVersion = d->m_aboutData.version(); if (d->m_strVersion.isEmpty()) { d->m_strVersion = i18n("no version set (programmer error)"); } d->kde_version = QStringLiteral(KXMLGUI_VERSION_STRING); d->kde_version += QStringLiteral(", ") + QLatin1String(XMLGUI_DISTRIBUTION_TEXT); if (d->bugDestination != KBugReportPrivate::BugsKdeOrg) { d->m_strVersion += QLatin1Char(' ') + d->kde_version; } d->m_version = new QLabel(d->m_strVersion, this); d->m_version->setTextInteractionFlags(Qt::TextBrowserInteraction); //glay->addWidget( d->m_version, row, 1 ); glay->addWidget(d->m_version, row, 1, 1, 2); d->m_version->setWhatsThis(qwtstr); tmpLabel = new QLabel(i18n("OS:"), this); glay->addWidget(tmpLabel, ++row, 0); d->os = SystemInformation::operatingSystemVersion(); tmpLabel = new QLabel(d->os, this); tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); glay->addWidget(tmpLabel, row, 1, 1, 2); tmpLabel = new QLabel(i18n("Compiler:"), this); glay->addWidget(tmpLabel, ++row, 0); tmpLabel = new QLabel(QLatin1String(XMLGUI_COMPILER_VERSION), this); tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); glay->addWidget(tmpLabel, row, 1, 1, 2); if (d->bugDestination == KBugReportPrivate::CustomEmail) { // Severity d->m_bgSeverity = new QGroupBox(i18n("Se&verity"), this); static const char *const sevNames[5] = { "critical", "grave", "normal", "wishlist", "i18n" }; const QString sevTexts[5] = { i18n("Critical"), i18n("Grave"), i18nc("normal severity", "Normal"), i18n("Wishlist"), i18n("Translation") }; QHBoxLayout *severityLayout = new QHBoxLayout(d->m_bgSeverity); for (int i = 0; i < 5; i++) { // Store the severity string as the name QRadioButton *rb = new QRadioButton(sevTexts[i], d->m_bgSeverity); rb->setObjectName(QLatin1String(sevNames[i])); d->severityButtons.append(rb); severityLayout->addWidget(rb); if (i == 2) { rb->setChecked(true); // default : "normal" } } lay->addWidget(d->m_bgSeverity); // Subject QHBoxLayout *hlay = new QHBoxLayout(); lay->addItem(hlay); tmpLabel = new QLabel(i18n("S&ubject: "), this); hlay->addWidget(tmpLabel); d->m_subject = new QLineEdit(this); d->m_subject->setClearButtonEnabled(true); d->m_subject->setFocus(); tmpLabel->setBuddy(d->m_subject); hlay->addWidget(d->m_subject); QString text = i18n("Enter the text (in English if possible) that you wish to submit for the " "bug report.\n" "If you press \"Send\", a mail message will be sent to the maintainer of " "this program.\n"); QLabel *label = new QLabel(this); label->setText(text); lay->addWidget(label); // The multiline-edit d->m_lineedit = new KTextEdit(this); d->m_lineedit->setMinimumHeight(180); // make it big d->m_lineedit->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); d->m_lineedit->setLineWrapMode(QTextEdit::WidgetWidth); d->m_lineedit->setCheckSpellingEnabled(true); d->m_lineedit->setSpellCheckingLanguage(QStringLiteral("en")); lay->addWidget(d->m_lineedit, 10 /*stretch*/); d->_k_slotSetFrom(); } else { // Point to the web form QString text; if (d->bugDestination == KBugReportPrivate::BugsKdeOrg) { text = i18n("To submit a bug report, click on the button below. This will open a web browser " "window on https://bugs.kde.org where you will find " "a form to fill in. The information displayed above will be transferred to that server."); d->_k_updateUrl(); } else { text = i18n("To submit a bug report, click on the button below. This will open a web browser " "window on %2.", bugAddress, bugAddress); d->url = QUrl(bugAddress); } lay->addSpacing(10); QLabel *label = new QLabel(text, this); label->setOpenExternalLinks(true); label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); label->setWordWrap(true); lay->addWidget(label); lay->addSpacing(10); d->appcombo->setFocus(); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); if (d->bugDestination == KBugReportPrivate::BugsKdeOrg) { okButton->setText(i18n("&Launch Bug Report Wizard")); } else { okButton->setText(i18n("&Submit Bug Report")); } okButton->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); } lay->addWidget(buttonBox); setMinimumHeight(sizeHint().height() + 20); // WORKAROUND: prevent "cropped" qcombobox } KBugReport::~KBugReport() { delete d; } QString KBugReport::messageBody() const { if (d->bugDestination == KBugReportPrivate::CustomEmail) { return d->m_lineedit->toPlainText(); } else { return QString(); } } void KBugReport::setMessageBody(const QString &messageBody) { if (d->bugDestination == KBugReportPrivate::CustomEmail) { d->m_lineedit->setPlainText(messageBody); } } void KBugReportPrivate::_k_updateUrl() { url = QUrl(QStringLiteral("https://bugs.kde.org/enter_bug.cgi")); QUrlQuery query; query.addQueryItem(QStringLiteral("format"), QStringLiteral("guided")); // use the guided form // the string format is product/component, where component is optional QStringList list = appcombo->currentText().split(QLatin1Char('/')); query.addQueryItem(QStringLiteral("product"), list[0]); if (list.size() == 2) { query.addQueryItem(QStringLiteral("component"), list[1]); } query.addQueryItem(QStringLiteral("version"), m_strVersion); url.setQuery(query); // TODO: guess and fill OS(sys_os) and Platform(rep_platform) fields } void KBugReportPrivate::_k_appChanged(int i) { QString appName = appcombo->itemText(i); int index = appName.indexOf(QLatin1Char('/')); if (index > 0) { appName = appName.left(index); } //qCDebug(DEBUG_KXMLGUI) << "appName " << appName; QString strDisplayVersion; //Version string to show in the UI if (appname == appName && !m_aboutData.version().isEmpty()) { m_strVersion = m_aboutData.version(); strDisplayVersion = m_strVersion; } else { m_strVersion = QStringLiteral("unknown"); //English string to put in the bug report strDisplayVersion = i18nc("unknown program name", "unknown"); } if (bugDestination != KBugReportPrivate::BugsKdeOrg) { m_strVersion += QLatin1Char(' ') + kde_version; strDisplayVersion += QLatin1Char(' ') + kde_version; } m_version->setText(strDisplayVersion); if (bugDestination == KBugReportPrivate::BugsKdeOrg) { _k_updateUrl(); } } void KBugReportPrivate::_k_slotConfigureEmail() { if (m_process) { return; } m_process = new QProcess; QObject::connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), q, SLOT(_k_slotSetFrom())); m_process->start(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("user_manager")); if (!m_process->waitForStarted()) { //qCDebug(DEBUG_KXMLGUI) << "Couldn't start kcmshell5.."; delete m_process; m_process = nullptr; return; } m_configureEmail->setEnabled(false); } void KBugReportPrivate::_k_slotSetFrom() { delete m_process; m_process = nullptr; m_configureEmail->setEnabled(true); KEMailSettings emailSettings; QString fromaddr = emailSettings.getSetting(KEMailSettings::EmailAddress); if (fromaddr.isEmpty()) { fromaddr = SystemInformation::userName(); } else { QString name = emailSettings.getSetting(KEMailSettings::RealName); if (!name.isEmpty()) { fromaddr = name + QLatin1String(" <") + fromaddr + QLatin1String(">"); } } m_from->setText(fromaddr); } void KBugReport::accept() { if (d->bugDestination != KBugReportPrivate::CustomEmail) { QDesktopServices::openUrl(d->url); return; } if (d->m_lineedit->toPlainText().isEmpty() || d->m_subject->text().isEmpty()) { QString msg = i18n("You must specify both a subject and a description " "before the report can be sent."); KMessageBox::error(this, msg); return; } switch (d->currentSeverity()) { case 0: // critical if (KMessageBox::questionYesNo(this, i18n( "

You chose the severity Critical. " "Please note that this severity is intended only for bugs that:

" "
  • break unrelated software on the system (or the whole system)
  • " "
  • cause serious data loss
  • " "
  • introduce a security hole on the system where the affected package is installed
\n" "

Does the bug you are reporting cause any of the above damage? " "If it does not, please select a lower severity. Thank you.

"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::No) { return; } break; case 1: // grave if (KMessageBox::questionYesNo(this, i18n( "

You chose the severity Grave. " "Please note that this severity is intended only for bugs that:

" "
  • make the package in question unusable or mostly so
  • " "
  • cause data loss
  • " "
  • introduce a security hole allowing access to the accounts of users who use the affected package
\n" "

Does the bug you are reporting cause any of the above damage? " "If it does not, please select a lower severity. Thank you.

"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::No) { return; } break; default: break; } if (!sendBugReport()) { QString msg = i18n("Unable to send the bug report.\n" "Please submit a bug report manually....\n" "See https://bugs.kde.org/ for instructions."); KMessageBox::error(this, msg + QStringLiteral("\n\n") + d->lastError); return; } KMessageBox::information(this, i18n("Bug report sent, thank you for your input.")); QDialog::accept(); } void KBugReport::closeEvent(QCloseEvent *e) { if (d->bugDestination == KBugReportPrivate::CustomEmail && ((d->m_lineedit->toPlainText().length() > 0) || d->m_subject->isModified())) { int rc = KMessageBox::warningYesNo(this, i18n("Close and discard\nedited message?"), i18n("Close Message"), KStandardGuiItem::discard(), KStandardGuiItem::cont()); if (rc == KMessageBox::No) { e->ignore(); return; } } QDialog::closeEvent(e); } QString KBugReport::text() const { //qCDebug(DEBUG_KXMLGUI) << d->severityButtons[d->currentSeverity()]->objectName(); // Prepend the pseudo-headers to the contents of the mail QString severity = d->severityButtons[d->currentSeverity()]->objectName(); QString appname = d->appcombo->currentText(); QString os = QStringLiteral("OS: %1 (%2)\n"). arg(QStringLiteral(XMLGUI_COMPILING_OS), QStringLiteral(XMLGUI_DISTRIBUTION_TEXT)); QString bodyText; /* for(int i = 0; i < m_lineedit->numLines(); i++) { QString line = m_lineedit->textLine(i); if (!line.endsWith("\n")) line += '\n'; bodyText += line; } */ bodyText = d->m_lineedit->toPlainText(); if (bodyText.length() > 0) if (bodyText[bodyText.length() - 1] != QLatin1Char('\n')) { bodyText += QLatin1Char('\n'); } if (severity == QStringLiteral("i18n") && QLocale().language() != QLocale::system().language()) { // Case 1 : i18n bug QString package = QStringLiteral("i18n_%1").arg(QLocale::languageToString(QLocale().language())); package = package.replace(QLatin1Char('_'), QLatin1Char('-')); return QStringLiteral("Package: %1").arg(package) + QStringLiteral("\nApplication: %1\nVersion: %2\n").arg(appname, d->m_strVersion) + // not really i18n's version, so better here IMHO os + QLatin1String("\n") + bodyText; } else { appname = appname.replace(QLatin1Char('_'), QLatin1Char('-')); // Case 2 : normal bug return QStringLiteral("Package: %1\nVersion: %2\nSeverity: %3\n") .arg(appname, d->m_strVersion, severity) + QStringLiteral("Compiler: %1\n").arg(QStringLiteral(XMLGUI_COMPILER_VERSION)) + os + QLatin1String("\n") + bodyText; } } bool KBugReport::sendBugReport() { QString recipient = d->m_aboutData.bugAddress(); if (recipient.isEmpty()) { recipient = QStringLiteral("submit@bugs.kde.org"); } QString command = QStandardPaths::findExecutable(QStringLiteral("ksendbugmail")); if (command.isEmpty()) { command = QFile::decodeName(CMAKE_INSTALL_PREFIX "/" KF5_LIBEXEC_INSTALL_DIR "/ksendbugmail"); } QProcess proc; QStringList args; args << QStringLiteral("--subject") << d->m_subject->text() << QStringLiteral("--recipient") << recipient; proc.start(command, args); //qCDebug(DEBUG_KXMLGUI) << command << args; if (!proc.waitForStarted()) { qCritical() << "Unable to open a pipe to " << command << endl; return false; } proc.write(text().toUtf8()); proc.closeWriteChannel(); proc.waitForFinished(); //qCDebug(DEBUG_KXMLGUI) << "kbugreport: sendbugmail exit, status " << proc.exitStatus() << " code " << proc.exitCode(); QByteArray line; if (proc.exitStatus() == QProcess::NormalExit && proc.exitCode() != 0) { // XXX not stderr? while (!proc.atEnd()) { line = proc.readLine(); } d->lastError = QString::fromUtf8(line); return false; } return true; } #include "moc_kbugreport.cpp" diff --git a/src/kcheckaccelerators.cpp b/src/kcheckaccelerators.cpp index e08447d..0b2a763 100644 --- a/src/kcheckaccelerators.cpp +++ b/src/kcheckaccelerators.cpp @@ -1,353 +1,354 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) Copyright (C) 1998, 1999, 2000 KDE Team Copyright (C) 2008 Nick Shaforostoff 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 "kcheckaccelerators.h" #include #include #include #include #include -#include -#include #include #include #include #include #include #include #include #include #include #include +#include +#include +#include #include #include #include #include #include class KCheckAcceleratorsInitializer : public QObject { Q_OBJECT public: explicit KCheckAcceleratorsInitializer(QObject *parent = nullptr) : QObject(parent) { } public Q_SLOTS: void initiateIfNeeded() { KConfigGroup cg(KSharedConfig::openConfig(), "Development"); QString sKey = cg.readEntry("CheckAccelerators").trimmed(); int key = 0; if (!sKey.isEmpty()) { QList cuts = QKeySequence::listFromString(sKey); if (!cuts.isEmpty()) { key = cuts.first()[0]; } } const bool autoCheck = cg.readEntry("AutoCheckAccelerators", true); const bool copyWidgetText = cg.readEntry("CopyWidgetText", false); if (!copyWidgetText && key == 0 && !autoCheck) { deleteLater(); return; } new KCheckAccelerators(qApp, key, autoCheck, copyWidgetText); deleteLater(); } }; static void startupFunc() { // Static because in some cases this is called multiple times // but if an application had any of the bad cases we always want // to skip the check static bool doCheckAccelerators = true; if (!doCheckAccelerators) { return; } QCoreApplication *app = QCoreApplication::instance(); if (!app) { // We're being loaded by something that doesn't have a QCoreApplication // this would probably crash at some later point since we do use qApp-> // quite a lot, so skip the magic doCheckAccelerators = false; return; } if (!QCoreApplication::startingUp()) { // If the app has already started, this means we're not being run as part of // qt_call_pre_routines, which most probably means that we're being run as part // of KXmlGui being loaded as part of some plugin of the app, so don't // do any magic doCheckAccelerators = false; return; } if (!QCoreApplication::eventDispatcher()) { // We are called with event dispatcher being null when KXmlGui is being loaded // through plasma-integration instead of being linked to the app (i.e. QtCreator vs Okular) // For apps that don't link directly to KXmlGui do not do the accelerator magic doCheckAccelerators = false; return; } KCheckAcceleratorsInitializer *initializer = new KCheckAcceleratorsInitializer(app); // Call initiateIfNeeded once we're in the event loop // This is to prevent using KSharedConfig before main() can set the app name QMetaObject::invokeMethod(initializer, "initiateIfNeeded", Qt::QueuedConnection); } Q_COREAPP_STARTUP_FUNCTION(startupFunc) KCheckAccelerators::KCheckAccelerators(QObject *parent, int key_, bool autoCheck_, bool copyWidgetText_) : QObject(parent) , key(key_) , block(false) , autoCheck(autoCheck_) , copyWidgetText(copyWidgetText_) , drklash(nullptr) { setObjectName(QStringLiteral("kapp_accel_filter")); KConfigGroup cg(KSharedConfig::openConfig(), "Development"); alwaysShow = cg.readEntry("AlwaysShowCheckAccelerators", false); copyWidgetTextCommand = cg.readEntry("CopyWidgetTextCommand", QString()); parent->installEventFilter(this); connect(&autoCheckTimer, &QTimer::timeout, this, &KCheckAccelerators::autoCheckSlot); } bool KCheckAccelerators::eventFilter(QObject *obj, QEvent *e) { if (block) { return false; } switch (e->type()) { // just simplify debuggin case QEvent::ShortcutOverride: if (key && (static_cast(e)->key() == key)) { block = true; checkAccelerators(false); block = false; e->accept(); return true; } break; case QEvent::ChildAdded: case QEvent::ChildRemoved: // Only care about widgets; this also avoids starting the timer in other threads if (!static_cast(e)->child()->isWidgetType()) { break; } Q_FALLTHROUGH(); // fall-through case QEvent::Resize: case QEvent::LayoutRequest: case QEvent::WindowActivate: case QEvent::WindowDeactivate: if (autoCheck) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); // 20 ms } break; //case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: if (copyWidgetText && static_cast(e)->button() == Qt::MidButton) { //kWarning()<<"obj"<(obj)->childAt(static_cast(e)->pos()); if (!w) { w = static_cast(obj); } if (!w) { return false; } //kWarning()<<"MouseButtonDblClick"<(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->text(); } else if (qobject_cast(w)) { text = static_cast(w)->currentText(); } else if (qobject_cast(w)) { text = static_cast(w)->tabText(static_cast(w)->tabAt(static_cast(e)->pos())); } else if (qobject_cast(w)) { text = static_cast(w)->title(); } else if (qobject_cast(obj)) { QAction *a = static_cast(obj)->actionAt(static_cast(e)->pos()); if (!a) { return false; } text = a->text(); if (text.isEmpty()) { text = a->iconText(); } } if (text.isEmpty()) { return false; } if (static_cast(e)->modifiers() == Qt::ControlModifier) { text.remove(QChar::fromLatin1('&')); } //kWarning()<setText(text); } else { QProcess *script = new QProcess(this); script->start(copyWidgetTextCommand.arg(text, QFile::decodeName(KLocalizedString::applicationDomain()))); connect(script, QOverload::of(&QProcess::finished), script, &QObject::deleteLater); } e->accept(); return true; //kWarning()<<"MouseButtonDblClick"<(obj)->childAt(static_cast(e)->globalPos()); } return false; case QEvent::Timer: case QEvent::MouseMove: case QEvent::Paint: return false; default: // qCDebug(DEBUG_KXMLGUI) << "KCheckAccelerators::eventFilter " << e->type() << " " << autoCheck; break; } return false; } void KCheckAccelerators::autoCheckSlot() { if (block) { autoCheckTimer.setSingleShot(true); autoCheckTimer.start(20); return; } block = true; checkAccelerators(!alwaysShow); block = false; } void KCheckAccelerators::createDialog(QWidget *actWin, bool automatic) { if (drklash) { return; } drklash = new QDialog(actWin); drklash->setAttribute(Qt::WA_DeleteOnClose); drklash->setObjectName(QStringLiteral("kapp_accel_check_dlg")); drklash->setWindowTitle(i18nc("@title:window", "Dr. Klash' Accelerator Diagnosis")); drklash->resize(500, 460); QVBoxLayout *layout = new QVBoxLayout(drklash); drklash_view = new QTextBrowser(drklash); layout->addWidget(drklash_view); QCheckBox *disableAutoCheck = nullptr; if (automatic) { disableAutoCheck = new QCheckBox(i18nc("@option:check", "Disable automatic checking"), drklash); connect(disableAutoCheck, &QCheckBox::toggled, this, &KCheckAccelerators::slotDisableCheck); layout->addWidget(disableAutoCheck); } QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, drklash); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::rejected, drklash, &QDialog::close); if (disableAutoCheck) { disableAutoCheck->setFocus(); } else { drklash_view->setFocus(); } } void KCheckAccelerators::slotDisableCheck(bool on) { autoCheck = !on; if (!on) { autoCheckSlot(); } } void KCheckAccelerators::checkAccelerators(bool automatic) { QWidget *actWin = qApp->activeWindow(); if (!actWin) { return; } KAcceleratorManager::manage(actWin); QString a, c, r; KAcceleratorManager::last_manage(a, c, r); if (automatic) { // for now we only show dialogs on F12 checks return; } if (c.isEmpty() && r.isEmpty() && (automatic || a.isEmpty())) { return; } QString s; if (! c.isEmpty()) { s += i18n("

Accelerators changed

"); s += QStringLiteral(""); s += c; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral(""); s += i18n("New Text"); s += QStringLiteral("
"); } if (! r.isEmpty()) { s += i18n("

Accelerators removed

"); s += QStringLiteral(""); s += r; s += QStringLiteral("
"); s += i18n("Old Text"); s += QStringLiteral("
"); } if (! a.isEmpty()) { s += i18n("

Accelerators added (just for your info)

"); s += QStringLiteral(""); s += a; s += QStringLiteral("
"); s += i18n("New Text"); s += QStringLiteral("
"); } createDialog(actWin, automatic); drklash_view->setHtml(s); drklash->show(); drklash->raise(); // dlg will be destroyed before returning } #include "kcheckaccelerators.moc" diff --git a/src/kedittoolbar.cpp b/src/kedittoolbar.cpp index 7b8eb70..8916d48 100644 --- a/src/kedittoolbar.cpp +++ b/src/kedittoolbar.cpp @@ -1,1711 +1,1709 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Kurt Granroth Copyright (C) 2006 Hamish Rodda Copyright 2007 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 version 2 as published by the Free Software Foundation. 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 "kedittoolbar.h" #include "kedittoolbar_p.h" #include "debug.h" #include #include #include #include -#include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kactioncollection.h" #include "kxmlguifactory.h" #include "ktoolbar.h" #include "ktoolbarhelper_p.h" #include "../kxmlgui_version.h" static const char separatorstring[] = I18N_NOOP("--- separator ---"); #define SEPARATORSTRING i18n(separatorstring) //static const char *const s_XmlTypeToString[] = { "Shell", "Part", "Local", "Merged" }; typedef QList ToolBarList; namespace KDEPrivate { /** * Return a list of toolbar elements given a toplevel element */ static ToolBarList findToolBars(const QDomElement &start) { ToolBarList list; for (QDomElement elem = start; !elem.isNull(); elem = elem.nextSiblingElement()) { if (elem.tagName() == QStringLiteral("ToolBar")) { if (elem.attribute(QStringLiteral("noEdit")) != QLatin1String("true")) { list.append(elem); } } else { if (elem.tagName() != QStringLiteral("MenuBar")) { // there are no toolbars inside the menubar :) list += findToolBars(elem.firstChildElement()); // recursive } } } return list; } class XmlData { public: enum XmlType { Shell = 0, Part, Local, Merged }; explicit XmlData(XmlType xmlType, const QString &xmlFile, KActionCollection *collection) : m_isModified(false), m_xmlFile(xmlFile), m_type(xmlType), m_actionCollection(collection) { } void dump() const { #if 0 qDebug(240) << "XmlData" << this << "type" << s_XmlTypeToString[m_type] << "xmlFile:" << m_xmlFile; foreach (const QDomElement &element, m_barList) { qDebug(240) << " ToolBar:" << toolBarText(element); } if (m_actionCollection) { qDebug(240) << " " << m_actionCollection->actions().count() << "actions in the collection."; } else { qDebug(240) << " no action collection."; } #endif } QString xmlFile() const { return m_xmlFile; } XmlType type() const { return m_type; } KActionCollection *actionCollection() const { return m_actionCollection; } void setDomDocument(const QDomDocument &domDoc) { m_document = domDoc.cloneNode().toDocument(); m_barList = findToolBars(m_document.documentElement()); } // Return reference, for e.g. actionPropertiesElement() to modify the document QDomDocument &domDocument() { return m_document; } const QDomDocument &domDocument() const { return m_document; } /** * Return the text (user-visible name) of a given toolbar */ QString toolBarText(const QDomElement &it) const; bool m_isModified; ToolBarList &barList() { return m_barList; } const ToolBarList &barList() const { return m_barList; } private: ToolBarList m_barList; QString m_xmlFile; QDomDocument m_document; XmlType m_type; KActionCollection *m_actionCollection; }; QString XmlData::toolBarText(const QDomElement &it) const { QString name = KToolbarHelper::i18nToolBarName(it); // the name of the toolbar might depend on whether or not // it is in kparts if ((m_type == XmlData::Shell) || (m_type == XmlData::Part)) { QString doc_name(m_document.documentElement().attribute(QStringLiteral("name"))); name += QStringLiteral(" <") + doc_name + QLatin1Char('>'); } return name; } typedef QList XmlDataList; class ToolBarItem : public QListWidgetItem { public: ToolBarItem(QListWidget *parent, const QString &tag = QString(), const QString &name = QString(), const QString &statusText = QString()) : QListWidgetItem(parent), m_internalTag(tag), m_internalName(name), m_statusText(statusText), m_isSeparator(false), m_isTextAlongsideIconHidden(false) { // Drop between items, not onto items setFlags((flags() | Qt::ItemIsDragEnabled) & ~Qt::ItemIsDropEnabled); } void setInternalTag(const QString &tag) { m_internalTag = tag; } void setInternalName(const QString &name) { m_internalName = name; } void setStatusText(const QString &text) { m_statusText = text; } void setSeparator(bool sep) { m_isSeparator = sep; } void setTextAlongsideIconHidden(bool hidden) { m_isTextAlongsideIconHidden = hidden; } QString internalTag() const { return m_internalTag; } QString internalName() const { return m_internalName; } QString statusText() const { return m_statusText; } bool isSeparator() const { return m_isSeparator; } bool isTextAlongsideIconHidden() const { return m_isTextAlongsideIconHidden; } int index() const { return listWidget()->row(const_cast(this)); } private: QString m_internalTag; QString m_internalName; QString m_statusText; bool m_isSeparator; bool m_isTextAlongsideIconHidden; }; static QDataStream &operator<< (QDataStream &s, const ToolBarItem &item) { s << item.internalTag(); s << item.internalName(); s << item.statusText(); s << item.isSeparator(); s << item.isTextAlongsideIconHidden(); return s; } static QDataStream &operator>> (QDataStream &s, ToolBarItem &item) { QString internalTag; s >> internalTag; item.setInternalTag(internalTag); QString internalName; s >> internalName; item.setInternalName(internalName); QString statusText; s >> statusText; item.setStatusText(statusText); bool sep; s >> sep; item.setSeparator(sep); bool hidden; s >> hidden; item.setTextAlongsideIconHidden(hidden); return s; } //// ToolBarListWidget::ToolBarListWidget(QWidget *parent) : QListWidget(parent), m_activeList(true) { setDragDropMode(QAbstractItemView::DragDrop); // no internal moves } QMimeData *ToolBarListWidget::mimeData(const QList items) const { if (items.isEmpty()) { return nullptr; } QMimeData *mimedata = new QMimeData(); QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); // we only support single selection ToolBarItem *item = static_cast(items.first()); stream << *item; } mimedata->setData(QStringLiteral("application/x-kde-action-list"), data); mimedata->setData(QStringLiteral("application/x-kde-source-treewidget"), m_activeList ? "active" : "inactive"); return mimedata; } bool ToolBarListWidget::dropMimeData(int index, const QMimeData *mimeData, Qt::DropAction action) { Q_UNUSED(action) const QByteArray data = mimeData->data(QStringLiteral("application/x-kde-action-list")); if (data.isEmpty()) { return false; } QDataStream stream(data); const bool sourceIsActiveList = mimeData->data(QStringLiteral("application/x-kde-source-treewidget")) == "active"; ToolBarItem *item = new ToolBarItem(this); // needs parent, use this temporarily stream >> *item; emit dropped(this, index, item, sourceIsActiveList); return true; } ToolBarItem *ToolBarListWidget::currentItem() const { return static_cast(QListWidget::currentItem()); } IconTextEditDialog::IconTextEditDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Change Text")); setModal(true); QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); QGridLayout *grid = new QGridLayout; grid->setContentsMargins(0, 0, 0, 0); m_lineEdit = new QLineEdit(this); m_lineEdit->setClearButtonEnabled(true); QLabel *label = new QLabel(i18n("Icon te&xt:"), this); label->setBuddy(m_lineEdit); grid->addWidget(label, 0, 0); grid->addWidget(m_lineEdit, 0, 1); m_cbHidden = new QCheckBox(i18n("&Hide text when toolbar shows text alongside icons"), this); grid->addWidget(m_cbHidden, 1, 1); layout->addLayout(grid); m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(m_buttonBox); connect(m_lineEdit, &QLineEdit::textChanged, this, &IconTextEditDialog::slotTextChanged); m_lineEdit->setFocus(); setFixedHeight(sizeHint().height()); } void IconTextEditDialog::setIconText(const QString &text) { m_lineEdit->setText(text); } QString IconTextEditDialog::iconText() const { return m_lineEdit->text().trimmed(); } void IconTextEditDialog::setTextAlongsideIconHidden(bool hidden) { m_cbHidden->setChecked(hidden); } bool IconTextEditDialog::textAlongsideIconHidden() const { return m_cbHidden->isChecked(); } void IconTextEditDialog::slotTextChanged(const QString &text) { // Do not allow empty icon text m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.trimmed().isEmpty()); } class KEditToolBarWidgetPrivate { public: /** * * @param collection In the old-style constructor, this is the collection passed * to the KEditToolBar constructor. * In the xmlguifactory-based constructor, we let KXMLGUIClient create a dummy one, * but it probably isn't used. */ KEditToolBarWidgetPrivate(KEditToolBarWidget *widget, const QString &cName, KActionCollection *collection) : m_collection(collection), m_widget(widget), m_factory(nullptr), m_loadedOnce(false) { m_componentName = cName; m_isPart = false; m_helpArea = nullptr; // We want items with an icon to align with items without icon // So we use an empty QPixmap for that const int iconSize = widget->style()->pixelMetric(QStyle::PM_SmallIconSize); m_emptyIcon = QPixmap(iconSize, iconSize); m_emptyIcon.fill(Qt::transparent); } ~KEditToolBarWidgetPrivate() { } // private slots void slotToolBarSelected(int index); void slotInactiveSelectionChanged(); void slotActiveSelectionChanged(); void slotInsertButton(); void slotRemoveButton(); void slotUpButton(); void slotDownButton(); void selectActiveItem(const QString &); void slotChangeIcon(); void slotChangeIconText(); void slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList); void setupLayout(); void initOldStyle(const QString &file, bool global, const QString &defaultToolbar); void initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolbar); void loadToolBarCombo(const QString &defaultToolbar); void loadActions(const QDomElement &elem); QString xmlFile(const QString &xml_file) const { return xml_file.isEmpty() ? m_componentName + QStringLiteral("ui.rc") : xml_file; } /** * Load in the specified XML file and dump the raw xml */ QString loadXMLFile(const QString &_xml_file) { QString raw_xml; QString xml_file = xmlFile(_xml_file); //qCDebug(DEBUG_KXMLGUI) << "loadXMLFile xml_file=" << xml_file; if (!QDir::isRelativePath(xml_file)) { raw_xml = KXMLGUIFactory::readConfigFile(xml_file); } else { raw_xml = KXMLGUIFactory::readConfigFile(xml_file, m_componentName); } return raw_xml; } /** * Look for a given item in the current toolbar */ QDomElement findElementForToolBarItem(const ToolBarItem *item) const { //qDebug(240) << "looking for name=" << item->internalName() << "and tag=" << item->internalTag(); for (QDomNode n = m_currentToolBarElem.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement elem = n.toElement(); if ((elem.attribute(QStringLiteral("name")) == item->internalName()) && (elem.tagName() == item->internalTag())) { return elem; } } //qDebug(240) << "no item found in the DOM with name=" << item->internalName() << "and tag=" << item->internalTag(); return QDomElement(); } void insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend = false); void removeActive(ToolBarItem *item); void moveActive(ToolBarItem *item, ToolBarItem *before); void updateLocal(QDomElement &elem); #ifndef NDEBUG void dump() const { XmlDataList::const_iterator xit = m_xmlFiles.begin(); for (; xit != m_xmlFiles.end(); ++xit) { (*xit).dump(); } } #endif QComboBox *m_toolbarCombo; QToolButton *m_upAction; QToolButton *m_removeAction; QToolButton *m_insertAction; QToolButton *m_downAction; //QValueList m_actionList; KActionCollection *m_collection; KEditToolBarWidget * const m_widget; KXMLGUIFactory *m_factory; QString m_componentName; QPixmap m_emptyIcon; XmlData *m_currentXmlData; QDomElement m_currentToolBarElem; QString m_xmlFile; QString m_globalFile; QString m_rcFile; QDomDocument m_localDoc; ToolBarList m_barList; ToolBarListWidget *m_inactiveList; ToolBarListWidget *m_activeList; XmlDataList m_xmlFiles; QLabel *m_comboLabel; KSeparator *m_comboSeparator; QLabel *m_helpArea; QPushButton *m_changeIcon; QPushButton *m_changeIconText; bool m_isPart : 1; bool m_loadedOnce : 1; }; } using namespace KDEPrivate; class KEditToolBarPrivate { public: KEditToolBarPrivate(KEditToolBar *q): q(q), m_accept(false), m_global(false), m_collection(nullptr), m_factory(nullptr), m_widget(nullptr) {} void init(); void _k_slotButtonClicked(QAbstractButton *button); void _k_acceptOK(bool); void _k_enableApply(bool); void okClicked(); void applyClicked(); void defaultClicked(); KEditToolBar *q; bool m_accept; // Save parameters for recreating widget after resetting toolbar bool m_global; KActionCollection *m_collection; QString m_file; QString m_defaultToolBar; KXMLGUIFactory *m_factory; KEditToolBarWidget *m_widget; QVBoxLayout *m_layout; QDialogButtonBox *m_buttonBox; }; Q_GLOBAL_STATIC(QString, s_defaultToolBarName) KEditToolBar::KEditToolBar(KActionCollection *collection, QWidget *parent) : QDialog(parent), d(new KEditToolBarPrivate(this)) { d->m_widget = new KEditToolBarWidget(collection, this); d->init(); d->m_collection = collection; } KEditToolBar::KEditToolBar(KXMLGUIFactory *factory, QWidget *parent) : QDialog(parent), d(new KEditToolBarPrivate(this)) { d->m_widget = new KEditToolBarWidget(this); d->init(); d->m_factory = factory; } void KEditToolBarPrivate::init() { m_accept = false; m_factory = nullptr; q->setDefaultToolBar(QString()); q->setWindowTitle(i18n("Configure Toolbars")); q->setModal(false); m_layout = new QVBoxLayout; q->setLayout(m_layout); m_layout->addWidget(m_widget); m_buttonBox = new QDialogButtonBox(q); m_buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply()); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); q->connect(m_buttonBox, SIGNAL(clicked(QAbstractButton*)), SLOT(_k_slotButtonClicked(QAbstractButton*))); QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); m_layout->addWidget(m_buttonBox); q->connect(m_widget, SIGNAL(enableOk(bool)), SLOT(_k_acceptOK(bool))); q->connect(m_widget, SIGNAL(enableOk(bool)), SLOT(_k_enableApply(bool))); _k_enableApply(false); q->setMinimumSize(q->sizeHint()); } void KEditToolBar::setResourceFile(const QString &file, bool global) { d->m_file = file; d->m_global = global; d->m_widget->load(d->m_file, d->m_global, d->m_defaultToolBar); } KEditToolBar::~KEditToolBar() { delete d; s_defaultToolBarName()->clear(); } void KEditToolBar::setDefaultToolBar(const QString &toolBarName) { if (toolBarName.isEmpty()) { d->m_defaultToolBar = *s_defaultToolBarName(); } else { d->m_defaultToolBar = toolBarName; } } void KEditToolBarPrivate::_k_acceptOK(bool b) { m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(b); m_accept = b; } void KEditToolBarPrivate::_k_enableApply(bool b) { m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(b); } void KEditToolBarPrivate::defaultClicked() { if (KMessageBox::warningContinueCancel(q, i18n("Do you really want to reset all toolbars of this application to their default? The changes will be applied immediately."), i18n("Reset Toolbars"), KGuiItem(i18n("Reset"))) != KMessageBox::Continue) { return; } KEditToolBarWidget *oldWidget = m_widget; m_widget = nullptr; m_accept = false; if (m_factory) { foreach (KXMLGUIClient *client, m_factory->clients()) { const QString file = client->localXMLFile(); if (file.isEmpty()) { continue; } //qDebug(240) << "Deleting local xml file" << file; // << "for client" << client << typeid(*client).name(); if (QFile::exists(file)) if (!QFile::remove(file)) { qCWarning(DEBUG_KXMLGUI) << "Could not delete" << file; } } // Reload the xml files in all clients, now that the local files are gone oldWidget->rebuildKXMLGUIClients(); m_widget = new KEditToolBarWidget(q); m_widget->load(m_factory, m_defaultToolBar); } else { int slash = m_file.lastIndexOf(QLatin1Char('/')) + 1; if (slash) { m_file = m_file.mid(slash); } const QString xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kxmlgui5/") + QCoreApplication::instance()->applicationName() + QLatin1Char('/') + m_file; if (QFile::exists(xml_file)) if (!QFile::remove(xml_file)) { qCWarning(DEBUG_KXMLGUI) << "Could not delete " << xml_file; } m_widget = new KEditToolBarWidget(m_collection, q); q->setResourceFile(m_file, m_global); } // Copy the geometry to minimize UI flicker m_widget->setGeometry(oldWidget->geometry()); delete oldWidget; m_layout->insertWidget(0, m_widget); q->connect(m_widget, SIGNAL(enableOk(bool)), SLOT(_k_acceptOK(bool))); q->connect(m_widget, SIGNAL(enableOk(bool)), SLOT(_k_enableApply(bool))); _k_enableApply(false); emit q->newToolBarConfig(); emit q->newToolbarConfig(); // compat } void KEditToolBarPrivate::_k_slotButtonClicked(QAbstractButton *button) { QDialogButtonBox::StandardButton type = m_buttonBox->standardButton(button); switch (type) { case QDialogButtonBox::Ok: okClicked(); break; case QDialogButtonBox::Apply: applyClicked(); break; case QDialogButtonBox::RestoreDefaults: defaultClicked(); break; default: break; } } void KEditToolBarPrivate::okClicked() { if (!m_accept) { q->reject(); return; } // Do not rebuild GUI and emit the "newToolBarConfig" signal again here if the "Apply" // button was already pressed and no further changes were made. if (m_buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) { m_widget->save(); emit q->newToolBarConfig(); emit q->newToolbarConfig(); // compat } q->accept(); } void KEditToolBarPrivate::applyClicked() { (void)m_widget->save(); _k_enableApply(false); emit q->newToolBarConfig(); emit q->newToolbarConfig(); // compat } void KEditToolBar::setGlobalDefaultToolBar(const char *toolbarName) { *s_defaultToolBarName() = QString::fromLatin1(toolbarName); } KEditToolBarWidget::KEditToolBarWidget(KActionCollection *collection, QWidget *parent) : QWidget(parent), d(new KEditToolBarWidgetPrivate(this, componentName(), collection)) { d->setupLayout(); } KEditToolBarWidget::KEditToolBarWidget(QWidget *parent) : QWidget(parent), d(new KEditToolBarWidgetPrivate(this, componentName(), KXMLGUIClient::actionCollection() /*create new one*/)) { d->setupLayout(); } KEditToolBarWidget::~KEditToolBarWidget() { delete d; } void KEditToolBarWidget::load(const QString &file, bool global, const QString &defaultToolBar) { d->initOldStyle(file, global, defaultToolBar); } void KEditToolBarWidget::load(KXMLGUIFactory *factory, const QString &defaultToolBar) { d->initFromFactory(factory, defaultToolBar); } void KEditToolBarWidgetPrivate::initOldStyle(const QString &resourceFile, bool global, const QString &defaultToolBar) { //TODO: make sure we can call this multiple times? if (m_loadedOnce) { return; } m_loadedOnce = true; //d->m_actionList = collection->actions(); // handle the merging if (global) { m_widget->loadStandardsXmlFile(); // ui_standards.rc } const QString localXML = loadXMLFile(resourceFile); m_widget->setXML(localXML, global ? true /*merge*/ : false); // first, get all of the necessary info for our local xml XmlData local(XmlData::Local, xmlFile(resourceFile), m_collection); QDomDocument domDoc; domDoc.setContent(localXML); local.setDomDocument(domDoc); m_xmlFiles.append(local); // then, the merged one (ui_standards + local xml) XmlData merge(XmlData::Merged, QString(), m_collection); merge.setDomDocument(m_widget->domDocument()); m_xmlFiles.append(merge); #ifndef NDEBUG dump(); #endif // now load in our toolbar combo box loadToolBarCombo(defaultToolBar); m_widget->adjustSize(); m_widget->setMinimumSize(m_widget->sizeHint()); } void KEditToolBarWidgetPrivate::initFromFactory(KXMLGUIFactory *factory, const QString &defaultToolBar) { //TODO: make sure we can call this multiple times? if (m_loadedOnce) { return; } m_loadedOnce = true; m_factory = factory; // add all of the client data bool first = true; foreach (KXMLGUIClient *client, factory->clients()) { if (client->xmlFile().isEmpty()) { continue; } XmlData::XmlType type = XmlData::Part; if (first) { type = XmlData::Shell; first = false; Q_ASSERT(!client->localXMLFile().isEmpty()); // where would we save changes?? } XmlData data(type, client->localXMLFile(), client->actionCollection()); QDomDocument domDoc = client->domDocument(); data.setDomDocument(domDoc); m_xmlFiles.append(data); //d->m_actionList += client->actionCollection()->actions(); } #ifndef NDEBUG //d->dump(); #endif // now load in our toolbar combo box loadToolBarCombo(defaultToolBar); m_widget->adjustSize(); m_widget->setMinimumSize(m_widget->sizeHint()); m_widget->actionCollection()->addAssociatedWidget(m_widget); foreach (QAction *action, m_widget->actionCollection()->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } } void KEditToolBarWidget::save() { //qDebug(240) << "KEditToolBarWidget::save"; XmlDataList::Iterator it = d->m_xmlFiles.begin(); for (; it != d->m_xmlFiles.end(); ++it) { // let's not save non-modified files if (!((*it).m_isModified)) { continue; } // let's also skip (non-existent) merged files if ((*it).type() == XmlData::Merged) { continue; } // Add noMerge="1" to all the menus since we are saving the merged data QDomNodeList menuNodes = (*it).domDocument().elementsByTagName(QStringLiteral("Menu")); for (int i = 0; i < menuNodes.length(); ++i) { QDomNode menuNode = menuNodes.item(i); QDomElement menuElement = menuNode.toElement(); if (menuElement.isNull()) { continue; } menuElement.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); } //qCDebug(DEBUG_KXMLGUI) << (*it).domDocument().toString(); //qDebug(240) << "Saving " << (*it).xmlFile(); // if we got this far, we might as well just save it KXMLGUIFactory::saveConfigFile((*it).domDocument(), (*it).xmlFile()); } if (!d->m_factory) { return; } rebuildKXMLGUIClients(); } void KEditToolBarWidget::rebuildKXMLGUIClients() { if (!d->m_factory) { return; } const QList clients = d->m_factory->clients(); //qDebug(240) << "factory: " << clients.count() << " clients"; // remove the elements starting from the last going to the first if (clients.isEmpty()) { return; } QListIterator clientIterator = clients; clientIterator.toBack(); while (clientIterator.hasPrevious()) { KXMLGUIClient *client = clientIterator.previous(); //qDebug(240) << "factory->removeClient " << client; d->m_factory->removeClient(client); } KXMLGUIClient *firstClient = clients.first(); // now, rebuild the gui from the first to the last //qDebug(240) << "rebuilding the gui"; foreach (KXMLGUIClient *client, clients) { //qDebug(240) << "updating client " << client << " " << client->componentName() << " xmlFile=" << client->xmlFile(); QString file(client->xmlFile()); // before setting ui_standards! if (!file.isEmpty()) { // passing an empty stream forces the clients to reread the XML client->setXMLGUIBuildDocument(QDomDocument()); // for the shell, merge in ui_standards.rc if (client == firstClient) { // same assumption as in the ctor: first==shell client->loadStandardsXmlFile(); } // and this forces it to use the *new* XML file client->setXMLFile(file, client == firstClient /* merge if shell */); // [we can't use reloadXML, it doesn't load ui_standards.rc] } } // Now we can add the clients to the factory // We don't do it in the loop above because adding a part automatically // adds its plugins, so we must make sure the plugins were updated first. foreach (KXMLGUIClient *client, clients) { d->m_factory->addClient(client); } } void KEditToolBarWidgetPrivate::setupLayout() { // the toolbar name combo m_comboLabel = new QLabel(i18n("&Toolbar:"), m_widget); m_toolbarCombo = new QComboBox(m_widget); m_comboLabel->setBuddy(m_toolbarCombo); m_comboSeparator = new KSeparator(m_widget); QObject::connect(m_toolbarCombo, SIGNAL(activated(int)), m_widget, SLOT(slotToolBarSelected(int))); // QPushButton *new_toolbar = new QPushButton(i18n("&New"), this); // new_toolbar->setPixmap(BarIcon("document-new", KIconLoader::SizeSmall)); // new_toolbar->setEnabled(false); // disabled until implemented // QPushButton *del_toolbar = new QPushButton(i18n("&Delete"), this); // del_toolbar->setPixmap(BarIcon("edit-delete", KIconLoader::SizeSmall)); // del_toolbar->setEnabled(false); // disabled until implemented // our list of inactive actions QLabel *inactive_label = new QLabel(i18n("A&vailable actions:"), m_widget); m_inactiveList = new ToolBarListWidget(m_widget); m_inactiveList->setDragEnabled(true); m_inactiveList->setActiveList(false); m_inactiveList->setMinimumSize(180, 250); m_inactiveList->setDropIndicatorShown(false); // #165663 inactive_label->setBuddy(m_inactiveList); QObject::connect(m_inactiveList, SIGNAL(itemSelectionChanged()), m_widget, SLOT(slotInactiveSelectionChanged())); QObject::connect(m_inactiveList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), m_widget, SLOT(slotInsertButton())); QObject::connect(m_inactiveList, SIGNAL(dropped(ToolBarListWidget*,int,ToolBarItem*,bool)), m_widget, SLOT(slotDropped(ToolBarListWidget*,int,ToolBarItem*,bool))); KListWidgetSearchLine *inactiveListSearchLine = new KListWidgetSearchLine(m_widget, m_inactiveList); inactiveListSearchLine->setPlaceholderText(i18n("Filter")); // our list of active actions QLabel *active_label = new QLabel(i18n("Curr&ent actions:"), m_widget); m_activeList = new ToolBarListWidget(m_widget); m_activeList->setDragEnabled(true); m_activeList->setActiveList(true); // With Qt-4.1 only setting MiniumWidth results in a 0-width icon column ... m_activeList->setMinimumSize(m_inactiveList->minimumWidth(), 100); active_label->setBuddy(m_activeList); QObject::connect(m_activeList, SIGNAL(itemSelectionChanged()), m_widget, SLOT(slotActiveSelectionChanged())); QObject::connect(m_activeList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), m_widget, SLOT(slotRemoveButton())); QObject::connect(m_activeList, SIGNAL(dropped(ToolBarListWidget*,int,ToolBarItem*,bool)), m_widget, SLOT(slotDropped(ToolBarListWidget*,int,ToolBarItem*,bool))); KListWidgetSearchLine *activeListSearchLine = new KListWidgetSearchLine(m_widget, m_activeList); activeListSearchLine->setPlaceholderText(i18n("Filter")); // "change icon" button m_changeIcon = new QPushButton(i18n("Change &Icon..."), m_widget); m_changeIcon->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-icons"))); m_changeIcon->setEnabled(m_activeList->currentItem()); QObject::connect(m_changeIcon, SIGNAL(clicked()), m_widget, SLOT(slotChangeIcon())); // "change icon text" button m_changeIconText = new QPushButton(i18n("Change Te&xt..."), m_widget); m_changeIconText->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); m_changeIconText->setEnabled(m_activeList->currentItem() != nullptr); QObject::connect(m_changeIconText, SIGNAL(clicked()), m_widget, SLOT(slotChangeIconText())); // The buttons in the middle m_upAction = new QToolButton(m_widget); m_upAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); m_upAction->setEnabled(false); m_upAction->setAutoRepeat(true); QObject::connect(m_upAction, SIGNAL(clicked()), m_widget, SLOT(slotUpButton())); m_insertAction = new QToolButton(m_widget); m_insertAction->setIcon(QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("go-previous") : QStringLiteral("go-next"))); m_insertAction->setEnabled(false); QObject::connect(m_insertAction, SIGNAL(clicked()), m_widget, SLOT(slotInsertButton())); m_removeAction = new QToolButton(m_widget); m_removeAction->setIcon(QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("go-next") : QStringLiteral("go-previous"))); m_removeAction->setEnabled(false); QObject::connect(m_removeAction, SIGNAL(clicked()), m_widget, SLOT(slotRemoveButton())); m_downAction = new QToolButton(m_widget); m_downAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); m_downAction->setEnabled(false); m_downAction->setAutoRepeat(true); QObject::connect(m_downAction, SIGNAL(clicked()), m_widget, SLOT(slotDownButton())); m_helpArea = new QLabel(m_widget); m_helpArea->setWordWrap(true); // now start with our layouts QVBoxLayout *top_layout = new QVBoxLayout(m_widget); top_layout->setContentsMargins(0, 0, 0, 0); QVBoxLayout *name_layout = new QVBoxLayout(); QHBoxLayout *list_layout = new QHBoxLayout(); QVBoxLayout *inactive_layout = new QVBoxLayout(); QVBoxLayout *active_layout = new QVBoxLayout(); QHBoxLayout *changeIcon_layout = new QHBoxLayout(); QGridLayout *button_layout = new QGridLayout(); name_layout->addWidget(m_comboLabel); name_layout->addWidget(m_toolbarCombo); // name_layout->addWidget(new_toolbar); // name_layout->addWidget(del_toolbar); button_layout->setSpacing(0); button_layout->setRowStretch(0, 10); button_layout->addWidget(m_upAction, 1, 1); button_layout->addWidget(m_removeAction, 2, 0); button_layout->addWidget(m_insertAction, 2, 2); button_layout->addWidget(m_downAction, 3, 1); button_layout->setRowStretch(4, 10); inactive_layout->addWidget(inactive_label); inactive_layout->addWidget(inactiveListSearchLine); inactive_layout->addWidget(m_inactiveList, 1); active_layout->addWidget(active_label); active_layout->addWidget(activeListSearchLine); active_layout->addWidget(m_activeList, 1); active_layout->addLayout(changeIcon_layout); changeIcon_layout->addWidget(m_changeIcon); changeIcon_layout->addStretch(1); changeIcon_layout->addWidget(m_changeIconText); list_layout->addLayout(inactive_layout); list_layout->addLayout(button_layout); list_layout->addLayout(active_layout); top_layout->addLayout(name_layout); top_layout->addWidget(m_comboSeparator); top_layout->addLayout(list_layout, 10); top_layout->addWidget(m_helpArea); top_layout->addWidget(new KSeparator(m_widget)); } void KEditToolBarWidgetPrivate::loadToolBarCombo(const QString &defaultToolBar) { const QLatin1String attrName("name"); // just in case, we clear our combo m_toolbarCombo->clear(); int defaultToolBarId = -1; int count = 0; // load in all of the toolbar names into this combo box XmlDataList::const_iterator xit = m_xmlFiles.constBegin(); for (; xit != m_xmlFiles.constEnd(); ++xit) { // skip the merged one in favor of the local one, // so that we can change icons // This also makes the app-defined named for "mainToolBar" appear rather than the ui_standards-defined name. if ((*xit).type() == XmlData::Merged) { continue; } // each xml file may have any number of toolbars ToolBarList::const_iterator it = (*xit).barList().begin(); for (; it != (*xit).barList().constEnd(); ++it) { const QString text = (*xit).toolBarText(*it); m_toolbarCombo->addItem(text); const QString name = (*it).attribute(attrName); if (defaultToolBarId == -1 && name == defaultToolBar) { defaultToolBarId = count; } count++; } } const bool showCombo = (count > 1); m_comboLabel->setVisible(showCombo); m_comboSeparator->setVisible(showCombo); m_toolbarCombo->setVisible(showCombo); if (defaultToolBarId == -1) { defaultToolBarId = 0; } // we want to the specified item selected and its actions loaded m_toolbarCombo->setCurrentIndex(defaultToolBarId); slotToolBarSelected(m_toolbarCombo->currentIndex()); } void KEditToolBarWidgetPrivate::loadActions(const QDomElement &elem) { const QLatin1String tagSeparator("Separator"); const QLatin1String tagMerge("Merge"); const QLatin1String tagActionList("ActionList"); const QLatin1String tagAction("Action"); const QLatin1String attrName("name"); int sep_num = 0; QString sep_name(QStringLiteral("separator_%1")); // clear our lists m_inactiveList->clear(); m_activeList->clear(); m_insertAction->setEnabled(false); m_removeAction->setEnabled(false); m_upAction->setEnabled(false); m_downAction->setEnabled(false); // We'll use this action collection KActionCollection *actionCollection = m_currentXmlData->actionCollection(); // store the names of our active actions QSet active_list; // Filtering message requested by translators (scripting). KLocalizedString nameFilter = ki18nc("@item:intable Action name in toolbar editor", "%1"); // see if our current action is in this toolbar QDomNode n = elem.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { QDomElement it = n.toElement(); if (it.isNull()) { continue; } if (it.tagName() == tagSeparator) { ToolBarItem *act = new ToolBarItem(m_activeList, tagSeparator, sep_name.arg(sep_num++), QString()); act->setSeparator(true); act->setText(SEPARATORSTRING); it.setAttribute(attrName, act->internalName()); continue; } if (it.tagName() == tagMerge) { // Merge can be named or not - use the name if there is one QString name = it.attribute(attrName); ToolBarItem *act = new ToolBarItem(m_activeList, tagMerge, name, i18n("This element will be replaced with all the elements of an embedded component.")); if (name.isEmpty()) { act->setText(i18n("")); } else { act->setText(i18n("", name)); } continue; } if (it.tagName() == tagActionList) { ToolBarItem *act = new ToolBarItem(m_activeList, tagActionList, it.attribute(attrName), i18n("This is a dynamic list of actions. You can move it, but if you remove it you will not be able to re-add it.")); act->setText(i18n("ActionList: %1", it.attribute(attrName))); continue; } // iterate through this client's actions // This used to iterate through _all_ actions, but we don't support // putting any action into any client... foreach (QAction *action, actionCollection->actions()) { // do we have a match? if (it.attribute(attrName) == action->objectName()) { // we have a match! ToolBarItem *act = new ToolBarItem(m_activeList, it.tagName(), action->objectName(), action->toolTip()); act->setText(nameFilter.subs(KLocalizedString::removeAcceleratorMarker(action->iconText())).toString()); act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon); act->setTextAlongsideIconHidden(action->priority() < QAction::NormalPriority); active_list.insert(action->objectName()); break; } } } // go through the rest of the collection foreach (QAction *action, actionCollection->actions()) { // skip our active ones if (active_list.contains(action->objectName())) { continue; } ToolBarItem *act = new ToolBarItem(m_inactiveList, tagAction, action->objectName(), action->toolTip()); act->setText(nameFilter.subs(KLocalizedString::removeAcceleratorMarker(action->text())).toString()); act->setIcon(!action->icon().isNull() ? action->icon() : m_emptyIcon); } m_inactiveList->sortItems(Qt::AscendingOrder); // finally, add default separators to the inactive list ToolBarItem *act = new ToolBarItem(nullptr, tagSeparator, sep_name.arg(sep_num++), QString()); act->setSeparator(true); act->setText(SEPARATORSTRING); m_inactiveList->insertItem(0, act); } KActionCollection *KEditToolBarWidget::actionCollection() const { return d->m_collection; } void KEditToolBarWidgetPrivate::slotToolBarSelected(int index) { // We need to find the XmlData and toolbar element for this index // To do that, we do the same iteration as the one which filled in the combobox. int toolbarNumber = 0; XmlDataList::iterator xit = m_xmlFiles.begin(); for (; xit != m_xmlFiles.end(); ++xit) { // skip the merged one in favor of the local one, // so that we can change icons if ((*xit).type() == XmlData::Merged) { continue; } // each xml file may have any number of toolbars ToolBarList::Iterator it = (*xit).barList().begin(); for (; it != (*xit).barList().end(); ++it) { // is this our toolbar? if (toolbarNumber == index) { // save our current settings m_currentXmlData = & (*xit); m_currentToolBarElem = *it; //qCDebug(DEBUG_KXMLGUI) << "found toolbar" << m_currentXmlData->toolBarText(*it) << "m_currentXmlData set to"; m_currentXmlData->dump(); // If this is a Merged xmldata, clicking the "change icon" button would assert... Q_ASSERT(m_currentXmlData->type() != XmlData::Merged); // load in our values loadActions(m_currentToolBarElem); if ((*xit).type() == XmlData::Part || (*xit).type() == XmlData::Shell) { m_widget->setDOMDocument((*xit).domDocument()); } return; } ++toolbarNumber; } } } void KEditToolBarWidgetPrivate::slotInactiveSelectionChanged() { if (!m_inactiveList->selectedItems().isEmpty()) { m_insertAction->setEnabled(true); QString statusText = static_cast(m_inactiveList->selectedItems().first())->statusText(); m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText)); } else { m_insertAction->setEnabled(false); m_helpArea->setText(QString()); } } void KEditToolBarWidgetPrivate::slotActiveSelectionChanged() { ToolBarItem *toolitem = nullptr; if (!m_activeList->selectedItems().isEmpty()) { toolitem = static_cast(m_activeList->selectedItems().first()); } m_removeAction->setEnabled(toolitem); m_changeIcon->setEnabled(toolitem && toolitem->internalTag() == QStringLiteral("Action")); m_changeIconText->setEnabled(toolitem && toolitem->internalTag() == QStringLiteral("Action")); if (toolitem) { m_upAction->setEnabled(toolitem->index() != 0); m_downAction->setEnabled(toolitem->index() != toolitem->listWidget()->count() - 1); QString statusText = toolitem->statusText(); m_helpArea->setText(i18nc("@label Action tooltip in toolbar editor, below the action list", "%1", statusText)); } else { m_upAction->setEnabled(false); m_downAction->setEnabled(false); m_helpArea->setText(QString()); } } void KEditToolBarWidgetPrivate::slotInsertButton() { QString internalName = static_cast(m_inactiveList->currentItem())->internalName(); insertActive(m_inactiveList->currentItem(), m_activeList->currentItem(), false); // we're modified, so let this change emit m_widget->enableOk(true); slotToolBarSelected(m_toolbarCombo->currentIndex()); selectActiveItem(internalName); } void KEditToolBarWidgetPrivate::selectActiveItem(const QString &internalName) { int activeItemCount = m_activeList->count(); for (int i = 0; i < activeItemCount; i++) { ToolBarItem *item = static_cast(m_activeList->item(i)); if (item->internalName() == internalName) { m_activeList->setCurrentItem(item); break; } } } void KEditToolBarWidgetPrivate::slotRemoveButton() { removeActive(m_activeList->currentItem()); slotToolBarSelected(m_toolbarCombo->currentIndex()); } void KEditToolBarWidgetPrivate::insertActive(ToolBarItem *item, ToolBarItem *before, bool prepend) { if (!item) { return; } QDomElement new_item; // let's handle the separator specially if (item->isSeparator()) { new_item = m_widget->domDocument().createElement(QStringLiteral("Separator")); } else { new_item = m_widget->domDocument().createElement(QStringLiteral("Action")); } new_item.setAttribute(QStringLiteral("name"), item->internalName()); Q_ASSERT(!m_currentToolBarElem.isNull()); if (before) { // we have the item in the active list which is before the new // item.. so let's try our best to add our new item right after it QDomElement elem = findElementForToolBarItem(before); Q_ASSERT(!elem.isNull()); m_currentToolBarElem.insertAfter(new_item, elem); } else { // simply put it at the beginning or the end of the list. if (prepend) { m_currentToolBarElem.insertBefore(new_item, m_currentToolBarElem.firstChild()); } else { m_currentToolBarElem.appendChild(new_item); } } // and set this container as a noMerge m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); // update the local doc updateLocal(m_currentToolBarElem); } void KEditToolBarWidgetPrivate::removeActive(ToolBarItem *item) { if (!item) { return; } // we're modified, so let this change emit m_widget->enableOk(true); // now iterate through to find the child to nuke QDomElement elem = findElementForToolBarItem(item); if (!elem.isNull()) { // nuke myself! m_currentToolBarElem.removeChild(elem); // and set this container as a noMerge m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); // update the local doc updateLocal(m_currentToolBarElem); } } void KEditToolBarWidgetPrivate::slotUpButton() { ToolBarItem *item = m_activeList->currentItem(); if (!item) { Q_ASSERT(false); return; } int row = item->listWidget()->row(item) - 1; // make sure we're not the top item already if (row < 0) { Q_ASSERT(false); return; } // we're modified, so let this change emit m_widget->enableOk(true); moveActive(item, static_cast(item->listWidget()->item(row - 1))); } void KEditToolBarWidgetPrivate::moveActive(ToolBarItem *item, ToolBarItem *before) { QDomElement e = findElementForToolBarItem(item); if (e.isNull()) { return; } // remove item m_activeList->takeItem(m_activeList->row(item)); // put it where it's supposed to go m_activeList->insertItem(m_activeList->row(before) + 1, item); // make it selected again m_activeList->setCurrentItem(item); // and do the real move in the DOM if (!before) { m_currentToolBarElem.insertBefore(e, m_currentToolBarElem.firstChild()); } else { m_currentToolBarElem.insertAfter(e, findElementForToolBarItem(before)); } // and set this container as a noMerge m_currentToolBarElem.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); // update the local doc updateLocal(m_currentToolBarElem); } void KEditToolBarWidgetPrivate::slotDownButton() { ToolBarItem *item = m_activeList->currentItem(); if (!item) { Q_ASSERT(false); return; } // make sure we're not the bottom item already int newRow = item->listWidget()->row(item) + 1; if (newRow >= item->listWidget()->count()) { Q_ASSERT(false); return; } // we're modified, so let this change emit m_widget->enableOk(true); moveActive(item, static_cast(item->listWidget()->item(newRow))); } void KEditToolBarWidgetPrivate::updateLocal(QDomElement &elem) { XmlDataList::Iterator xit = m_xmlFiles.begin(); for (; xit != m_xmlFiles.end(); ++xit) { if ((*xit).type() == XmlData::Merged) { continue; } if ((*xit).type() == XmlData::Shell || (*xit).type() == XmlData::Part) { if (m_currentXmlData->xmlFile() == (*xit).xmlFile()) { (*xit).m_isModified = true; return; } continue; } (*xit).m_isModified = true; const QLatin1String attrName("name"); ToolBarList::Iterator it = (*xit).barList().begin(); for (; it != (*xit).barList().end(); ++it) { QString name((*it).attribute(attrName)); QString tag((*it).tagName()); if ((tag != elem.tagName()) || (name != elem.attribute(attrName))) { continue; } QDomElement toolbar = (*xit).domDocument().documentElement().toElement(); toolbar.replaceChild(elem, (*it)); return; } // just append it QDomElement toolbar = (*xit).domDocument().documentElement().toElement(); Q_ASSERT(!toolbar.isNull()); toolbar.appendChild(elem); } } void KEditToolBarWidgetPrivate::slotChangeIcon() { m_currentXmlData->dump(); Q_ASSERT(m_currentXmlData->type() != XmlData::Merged); QString icon = KIconDialog::getIcon(KIconLoader::Toolbar, KIconLoader::Action, false, 0, false, // all defaults m_widget, i18n("Change Icon")); if (icon.isEmpty()) { return; } ToolBarItem *item = m_activeList->currentItem(); //qCDebug(DEBUG_KXMLGUI) << item; if (item) { item->setIcon(QIcon::fromTheme(icon)); m_currentXmlData->m_isModified = true; // Get hold of ActionProperties tag QDomElement elem = KXMLGUIFactory::actionPropertiesElement(m_currentXmlData->domDocument()); // Find or create an element for this action QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, item->internalName(), true /*create*/); Q_ASSERT(!act_elem.isNull()); act_elem.setAttribute(QStringLiteral("icon"), icon); // we're modified, so let this change emit m_widget->enableOk(true); } } void KEditToolBarWidgetPrivate::slotChangeIconText() { m_currentXmlData->dump(); ToolBarItem *item = m_activeList->currentItem(); if (item) { QString iconText = item->text(); bool hidden = item->isTextAlongsideIconHidden(); IconTextEditDialog dialog(m_widget); dialog.setIconText(iconText); dialog.setTextAlongsideIconHidden(hidden); bool ok = dialog.exec() == QDialog::Accepted; iconText = dialog.iconText(); hidden = dialog.textAlongsideIconHidden(); bool hiddenChanged = hidden != item->isTextAlongsideIconHidden(); bool iconTextChanged = iconText != item->text(); if (!ok || (!hiddenChanged && !iconTextChanged)) { return; } item->setText(iconText); item->setTextAlongsideIconHidden(hidden); Q_ASSERT(m_currentXmlData->type() != XmlData::Merged); m_currentXmlData->m_isModified = true; // Get hold of ActionProperties tag QDomElement elem = KXMLGUIFactory::actionPropertiesElement(m_currentXmlData->domDocument()); // Find or create an element for this action QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, item->internalName(), true /*create*/); Q_ASSERT(!act_elem.isNull()); if (iconTextChanged) { act_elem.setAttribute(QStringLiteral("iconText"), iconText); } if (hiddenChanged) { act_elem.setAttribute(QStringLiteral("priority"), hidden ? QAction::LowPriority : QAction::NormalPriority); } // we're modified, so let this change emit m_widget->enableOk(true); } } void KEditToolBarWidgetPrivate::slotDropped(ToolBarListWidget *list, int index, ToolBarItem *item, bool sourceIsActiveList) { //qCDebug(DEBUG_KXMLGUI) << "slotDropped list=" << (list==m_activeList?"activeList":"inactiveList") // << "index=" << index << "sourceIsActiveList=" << sourceIsActiveList; if (list == m_activeList) { ToolBarItem *after = index > 0 ? static_cast(list->item(index - 1)) : nullptr; //qCDebug(DEBUG_KXMLGUI) << "after" << after->text() << after->internalTag(); if (sourceIsActiveList) { // has been dragged within the active list (moved). moveActive(item, after); } else { // dragged from the inactive list to the active list insertActive(item, after, true); } } else if (list == m_inactiveList) { // has been dragged to the inactive list -> remove from the active list. removeActive(item); } delete item; // not needed anymore. must be deleted before slotToolBarSelected clears the lists // we're modified, so let this change emit m_widget->enableOk(true); slotToolBarSelected(m_toolbarCombo->currentIndex()); } void KEditToolBar::showEvent(QShowEvent *event) { if (!event->spontaneous()) { // The dialog has been shown, enable toolbar editing if (d->m_factory) { // call the xmlgui-factory version d->m_widget->load(d->m_factory, d->m_defaultToolBar); } else { // call the action collection version d->m_widget->load(d->m_file, d->m_global, d->m_defaultToolBar); } KToolBar::setToolBarsEditable(true); } QDialog::showEvent(event); } void KEditToolBar::hideEvent(QHideEvent *event) { // The dialog has been hidden, disable toolbar editing KToolBar::setToolBarsEditable(false); QDialog::hideEvent(event); } #include "moc_kedittoolbar.cpp" #include "moc_kedittoolbar_p.cpp" diff --git a/src/kgesturemap.cpp b/src/kgesturemap.cpp index cd04ac7..17d2a79 100644 --- a/src/kgesturemap.cpp +++ b/src/kgesturemap.cpp @@ -1,364 +1,364 @@ /* This file is part of the KDE libraries Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com) 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 "kgesturemap_p.h" #include "debug.h" #include #include -#include +#include /* This is a class for internal use by the KDE libraries only. This class may change or go away without notice so don't try to use it in non-kdelibs code. */ class KGestureMapContainer { public: KGestureMap gestureMap; }; Q_GLOBAL_STATIC(KGestureMapContainer, g_instance) KGestureMap::~KGestureMap() { } KGestureMap *KGestureMap::self() { return &g_instance()->gestureMap; } KGestureMap::KGestureMap() { m_gestureTimeout.setSingleShot(true); connect(&m_gestureTimeout, &QTimer::timeout, this, &KGestureMap::stopAcquisition); //It would be nice to install the filter on demand. Unfortunately, //undesired behavior might result due to changing invocation //orders of different event filters. if (qApp) { qApp->installEventFilter(this); } } void KGestureMap::setShapeGesture(QAction *act, const KShapeGesture &gesture) { if (!gesture.isValid() || !act) { return; } qCDebug(DEBUG_KXMLGUI) << "KGestureMap::addGesture(KShapeGesture ...)"; if (m_shapeGestures.contains(gesture)) { qCWarning(DEBUG_KXMLGUI) << "Replacing an action for a gesture already taken"; } m_shapeGestures.insert(gesture, act); } void KGestureMap::setRockerGesture(QAction *act, const KRockerGesture &gesture) { if (!gesture.isValid() || !act) { return; } qCDebug(DEBUG_KXMLGUI) << "KGestureMap::addGesture(KRockerGesture ...)"; if (m_rockerGestures.contains(gesture)) { qCWarning(DEBUG_KXMLGUI) << "Replacing an action for a gesture already taken"; } m_rockerGestures.insert(gesture, act); } void KGestureMap::setDefaultShapeGesture(QAction *act, const KShapeGesture &gesture) { if (!gesture.isValid() || !act) { return; } qCDebug(DEBUG_KXMLGUI) << "KGestureMap::addGesture(KShapeGesture ...)"; if (m_defaultShapeGestures.contains(gesture)) { qCWarning(DEBUG_KXMLGUI) << "Replacing an action for a gesture already taken"; } m_defaultShapeGestures.insert(gesture, act); } void KGestureMap::setDefaultRockerGesture(QAction *act, const KRockerGesture &gesture) { if (!gesture.isValid() || !act) { return; } qCDebug(DEBUG_KXMLGUI) << "KGestureMap::addGesture(KRockerGesture ...)"; if (m_defaultRockerGestures.contains(gesture)) { qCWarning(DEBUG_KXMLGUI) << "Replacing an action for a gesture already taken"; } m_defaultRockerGestures.insert(gesture, act); } void KGestureMap::removeAllGestures(QAction *kact) { KShapeGesture activeGesture; ShapeGestureHash::iterator si = m_shapeGestures.begin(); ShapeGestureHash::iterator send = m_shapeGestures.end(); for (; si != send; ++si) { if (si.value() == kact) { m_shapeGestures.remove(si.key()); break; } } si = m_defaultShapeGestures.begin(); send = m_defaultShapeGestures.end(); for (; si != send; ++si) { if (si.value() == kact) { m_defaultShapeGestures.remove(si.key()); break; } } RockerGestureHash::iterator ri = m_rockerGestures.begin(); RockerGestureHash::iterator rend = m_rockerGestures.end(); for (; ri != rend; ++ri) { if (ri.value() == kact) { m_rockerGestures.remove(ri.key()); break; } } ri = m_defaultRockerGestures.begin(); rend = m_defaultRockerGestures.end(); for (; ri != rend; ++ri) { if (ri.value() == kact) { m_defaultRockerGestures.remove(ri.key()); break; } } } QAction *KGestureMap::findAction(const KShapeGesture &gesture) const { return m_shapeGestures.value(gesture); } QAction *KGestureMap::findAction(const KRockerGesture &gesture) const { return m_rockerGestures.value(gesture); } void KGestureMap::installEventFilterOnMe(QApplication *app) { app->installEventFilter(this); } KShapeGesture KGestureMap::shapeGesture(const QAction *kact) const { KShapeGesture activeGesture; ShapeGestureHash::const_iterator it = m_shapeGestures.constBegin(); ShapeGestureHash::const_iterator end = m_shapeGestures.constEnd(); for (; it != end; ++it) { if (it.value() == kact) { activeGesture = it.key(); break; } } return activeGesture; } KShapeGesture KGestureMap::defaultShapeGesture(const QAction *kact) const { KShapeGesture defaultGesture; ShapeGestureHash::const_iterator it = m_defaultShapeGestures.constBegin(); ShapeGestureHash::const_iterator end = m_defaultShapeGestures.constEnd(); for (; it != end; ++it) { if (it.value() == kact) { defaultGesture = it.key(); break; } } return defaultGesture; } KRockerGesture KGestureMap::rockerGesture(const QAction *kact) const { KRockerGesture activeGesture; RockerGestureHash::const_iterator it = m_rockerGestures.constBegin(); RockerGestureHash::const_iterator end = m_rockerGestures.constEnd(); for (; it != end; ++it) { if (it.value() == kact) { activeGesture = it.key(); break; } } return activeGesture; } KRockerGesture KGestureMap::defaultRockerGesture(const QAction *kact) const { KRockerGesture defaultGesture; RockerGestureHash::const_iterator it = m_defaultRockerGestures.constBegin(); RockerGestureHash::const_iterator end = m_defaultRockerGestures.constEnd(); for (; it != end; ++it) { if (it.value() == kact) { defaultGesture = it.key(); break; } } return defaultGesture; } inline int KGestureMap::bitCount(int n) { int count = 0; while (n) { n &= (n - 1); count++; } return count; } void KGestureMap::handleAction(QAction *kact) { if (!kact) { return; } qCDebug(DEBUG_KXMLGUI) << "handleAction"; //TODO: only activate in the action's context, just like keyboard shortcuts kact->trigger(); return; } void KGestureMap::matchShapeGesture() { //TODO: tune and tweak until satisfied with result :) m_shapeGesture.setShape(m_points); float dist, minDist = 20.0; QAction *bestMatch = nullptr; for (QHash::const_iterator it = m_shapeGestures.constBegin(); it != m_shapeGestures.constEnd(); ++it) { dist = m_shapeGesture.distance(it.key(), 1000.0); if (dist < minDist) { minDist = dist; bestMatch = it.value(); } } handleAction(bestMatch); } //slot void KGestureMap::stopAcquisition() { m_gestureTimeout.stop(); m_acquiring = false; } //TODO: Probably kwin, kded and others should not have a gesture map. //Maybe making them friends and providing a private "die()" function would work. /* * Act on rocker gestures immediately and collect movement data for evaluation. * The decision when to consume and when to relay an event is quite tricky. * I decided to only consume clicks that belong to completed rocker gestures. * A user might e.g. go back in a browser several times using rocker gestures, * thus changing what's under the cursor every time. This might lead to * unintended clicks on links where there was free space before. */ bool KGestureMap::eventFilter(QObject *obj, QEvent *e) { //disable until it does not interfere with other input any more return false; Q_UNUSED(obj); int type = e->type(); //catch right-clicks disguised as context menu events. if we ignore a //context menu event caused by a right-click, it should get resent //as a right-click event, according to documentation. //### this is preliminary if (type == QEvent::ContextMenu) { QContextMenuEvent *cme = static_cast(e); if (cme->reason() == QContextMenuEvent::Mouse) { cme->ignore(); return true; } return false; } if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) { return false; } QMouseEvent *me = static_cast(e); if (type == QEvent::MouseButtonPress) { int nButtonsDown = bitCount(me->buttons()); qCDebug(DEBUG_KXMLGUI) << "number of buttons down:" << nButtonsDown; //right button down starts gesture acquisition if (nButtonsDown == 1 && me->button() == Qt::RightButton) { //"startAcquisition()" m_acquiring = true; m_gestureTimeout.start(4000); qCDebug(DEBUG_KXMLGUI) << "========================"; m_points.clear(); m_points.append(me->pos()); return true; } else if (nButtonsDown != 2) { return false; } //rocker gestures. do not trigger any movement gestures from now on. stopAcquisition(); int buttonHeld = me->buttons() ^ me->button(); m_rockerGesture.setButtons(static_cast(buttonHeld), me->button()); QAction *match = m_rockerGestures.value(m_rockerGesture); if (!match) { return false; } handleAction(match); return true; } if (m_acquiring) { if (type == QEvent::MouseMove) { m_points.append(me->pos()); //abort to avoid using too much memory. 1010 points should be enough //for everyone! :) //next reallocation of m_points would happen at 1012 items if (m_points.size() > 1010) { stopAcquisition(); } return true; } else if (type == QEvent::MouseButtonRelease && me->button() == Qt::RightButton) { stopAcquisition(); //TODO: pre-selection of gestures by length (optimization), if necessary //possibly apply other heuristics //then try all remaining gestures for sufficiently small distance int dist = 0; for (int i = 1; i < m_points.size(); i++) { dist += (m_points[i] - m_points[i - 1]).manhattanLength(); if (dist > 40) { matchShapeGesture(); return true; } //this was probably a small glitch while right-clicking if we get here. //TODO: open the context menu or do whatever happens on right-click (how?) } return false; } } return false; } diff --git a/src/khelpmenu.cpp b/src/khelpmenu.cpp index d100982..85c5028 100644 --- a/src/khelpmenu.cpp +++ b/src/khelpmenu.cpp @@ -1,387 +1,384 @@ /* * This file is part of the KDE Libraries * Copyright (C) 1999-2000 Espen Sand (espen@kde.org) * * 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. * */ // I (espen) prefer that header files are included alphabetically #include "khelpmenu.h" #include #include #include #include #include #include #include #include #include -#include -#include #include -#include #include #include #include "kaboutapplicationdialog.h" #include "kaboutkdedialog_p.h" #include "kbugreport.h" #include "kswitchlanguagedialog_p.h" #include #include #include #include #include using namespace KDEPrivate; class KHelpMenuPrivate { public: KHelpMenuPrivate() : mSwitchApplicationLanguage(nullptr), mActionsCreated(false), mSwitchApplicationLanguageAction(nullptr), mAboutData(KAboutData::applicationData()) { mMenu = nullptr; mAboutApp = nullptr; mAboutKDE = nullptr; mBugReport = nullptr; mDonateAction = nullptr; mHandBookAction = nullptr; mWhatsThisAction = nullptr; mReportBugAction = nullptr; mAboutAppAction = nullptr; mAboutKDEAction = nullptr; } ~KHelpMenuPrivate() { delete mMenu; delete mAboutApp; delete mAboutKDE; delete mBugReport; delete mSwitchApplicationLanguage; } void createActions(KHelpMenu *q); QMenu *mMenu; QDialog *mAboutApp; KAboutKdeDialog *mAboutKDE; KBugReport *mBugReport; QAction *mDonateAction; KSwitchLanguageDialog *mSwitchApplicationLanguage; // TODO evaluate if we use static_cast(parent()) instead of mParent to win that bit of memory QWidget *mParent; QString mAboutAppText; bool mShowWhatsThis; bool mActionsCreated; QAction *mHandBookAction, *mWhatsThisAction; QAction *mReportBugAction, *mSwitchApplicationLanguageAction, *mAboutAppAction, *mAboutKDEAction; KAboutData mAboutData; }; KHelpMenu::KHelpMenu(QWidget *parent, const QString &aboutAppText, bool showWhatsThis) : QObject(parent), d(new KHelpMenuPrivate) { d->mAboutAppText = aboutAppText; d->mShowWhatsThis = showWhatsThis; d->mParent = parent; d->createActions(this); } KHelpMenu::KHelpMenu(QWidget *parent, const KAboutData &aboutData, bool showWhatsThis) : QObject(parent), d(new KHelpMenuPrivate) { d->mShowWhatsThis = showWhatsThis; d->mParent = parent; d->mAboutData = aboutData; d->createActions(this); } KHelpMenu::~KHelpMenu() { delete d; } void KHelpMenuPrivate::createActions(KHelpMenu *q) { if (mActionsCreated) { return; } mActionsCreated = true; if (KAuthorized::authorizeAction(QStringLiteral("help_contents"))) { mHandBookAction = KStandardAction::helpContents(q, SLOT(appHelpActivated()), q); } if (mShowWhatsThis && KAuthorized::authorizeAction(QStringLiteral("help_whats_this"))) { mWhatsThisAction = KStandardAction::whatsThis(q, SLOT(contextHelpActivated()), q); } if (KAuthorized::authorizeAction(QStringLiteral("help_report_bug")) && !mAboutData.bugAddress().isEmpty()) { mReportBugAction = KStandardAction::reportBug(q, SLOT(reportBug()), q); } if (KAuthorized::authorizeAction(QStringLiteral("help_donate")) && mAboutData.bugAddress() == QStringLiteral("submit@bugs.kde.org")) { mDonateAction = KStandardAction::donate(q, &KHelpMenu::donate, q); } if (KAuthorized::authorizeAction(QStringLiteral("switch_application_language"))) { mSwitchApplicationLanguageAction = KStandardAction::create(KStandardAction::SwitchApplicationLanguage, q, SLOT(switchApplicationLanguage()), q); } if (KAuthorized::authorizeAction(QStringLiteral("help_about_app"))) { mAboutAppAction = KStandardAction::aboutApp(q, SLOT(aboutApplication()), q); } if (KAuthorized::authorizeAction(QStringLiteral("help_about_kde"))) { mAboutKDEAction = KStandardAction::aboutKDE(q, SLOT(aboutKDE()), q); } } // Used in the non-xml-gui case, like kfind or ksnapshot's help button. QMenu *KHelpMenu::menu() { if (!d->mMenu) { d->mMenu = new QMenu(d->mParent); connect(d->mMenu, &QObject::destroyed, this, &KHelpMenu::menuDestroyed); d->mMenu->setTitle(i18n("&Help")); d->createActions(this); bool need_separator = false; if (d->mHandBookAction) { d->mMenu->addAction(d->mHandBookAction); need_separator = true; } if (d->mWhatsThisAction) { d->mMenu->addAction(d->mWhatsThisAction); need_separator = true; } if (d->mReportBugAction) { if (need_separator) { d->mMenu->addSeparator(); } d->mMenu->addAction(d->mReportBugAction); need_separator = true; } if (d->mDonateAction) { if (need_separator) { d->mMenu->addSeparator(); } d->mMenu->addAction(d->mDonateAction); need_separator = true; } if (d->mSwitchApplicationLanguageAction) { if (need_separator) { d->mMenu->addSeparator(); } d->mMenu->addAction(d->mSwitchApplicationLanguageAction); need_separator = true; } if (need_separator) { d->mMenu->addSeparator(); } if (d->mAboutAppAction) { d->mMenu->addAction(d->mAboutAppAction); } if (d->mAboutKDEAction) { d->mMenu->addAction(d->mAboutKDEAction); } } return d->mMenu; } QAction *KHelpMenu::action(MenuId id) const { switch (id) { case menuHelpContents: return d->mHandBookAction; case menuWhatsThis: return d->mWhatsThisAction; case menuReportBug: return d->mReportBugAction; case menuSwitchLanguage: return d->mSwitchApplicationLanguageAction; case menuAboutApp: return d->mAboutAppAction; case menuAboutKDE: return d->mAboutKDEAction; case menuDonate: return d->mDonateAction; } return nullptr; } void KHelpMenu::appHelpActivated() { QDesktopServices::openUrl(QUrl(QStringLiteral("help:/"))); } void KHelpMenu::aboutApplication() { if (receivers(SIGNAL(showAboutApplication())) > 0) { emit showAboutApplication(); } else { // if (d->mAboutData) if (!d->mAboutApp) { d->mAboutApp = new KAboutApplicationDialog(d->mAboutData, d->mParent); connect(d->mAboutApp, &QDialog::finished, this, &KHelpMenu::dialogFinished); } d->mAboutApp->show(); } #if 0 // KF5: when can this happen? else { if (!d->mAboutApp) { d->mAboutApp = new QDialog(d->mParent, Qt::Dialog); QString caption = QGuiApplication::applicationDisplayName(); if (caption.isEmpty()) { caption = QCoreApplication::applicationName(); } d->mAboutApp->setWindowTitle(i18n("About %1", caption)); d->mAboutApp->setObjectName(QStringLiteral("about")); connect(d->mAboutApp, SIGNAL(finished(int)), this, SLOT(dialogFinished())); const int spacingHint = d->mAboutApp->style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); const int marginHint = d->mAboutApp->style()->pixelMetric(QStyle::PM_DefaultChildMargin); QVBoxLayout *vbox = new QVBoxLayout; d->mAboutApp->setLayout(vbox); QHBoxLayout *hbox = new QHBoxLayout; hbox->setSpacing(spacingHint * 3); hbox->setMargin(marginHint * 1); const int size = IconSize(KIconLoader::Dialog); QLabel *label1 = new QLabel(d->mAboutApp); label1->setPixmap(qApp->windowIcon().pixmap(size, size)); QLabel *label2 = new QLabel(d->mAboutApp); label2->setText(d->mAboutAppText); hbox->addWidget(label1); hbox->addWidget(label2); vbox->addLayout(hbox); QDialogButtonBox *buttonBox = new QDialogButtonBox(d->mAboutApp); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, SIGNAL(accepted()), d->mAboutApp, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), d->mAboutApp, SLOT(reject())); vbox->addWidget(buttonBox); } d->mAboutApp->show(); } #endif } void KHelpMenu::aboutKDE() { if (!d->mAboutKDE) { d->mAboutKDE = new KAboutKdeDialog(d->mParent); connect(d->mAboutKDE, &QDialog::finished, this, &KHelpMenu::dialogFinished); } d->mAboutKDE->show(); } void KHelpMenu::reportBug() { if (!d->mBugReport) { d->mBugReport = new KBugReport(d->mAboutData, d->mParent); connect(d->mBugReport, &QDialog::finished, this, &KHelpMenu::dialogFinished); } d->mBugReport->show(); } void KHelpMenu::switchApplicationLanguage() { if (!d->mSwitchApplicationLanguage) { d->mSwitchApplicationLanguage = new KSwitchLanguageDialog(d->mParent); connect(d->mSwitchApplicationLanguage, &QDialog::finished, this, &KHelpMenu::dialogFinished); } d->mSwitchApplicationLanguage->show(); } void KHelpMenu::donate() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.kde.org/donate?app=%1").arg(d->mAboutData.componentName()))); } void KHelpMenu::dialogFinished() { QTimer::singleShot(0, this, &KHelpMenu::timerExpired); } void KHelpMenu::timerExpired() { if (d->mAboutKDE && !d->mAboutKDE->isVisible()) { delete d->mAboutKDE; d->mAboutKDE = nullptr; } if (d->mBugReport && !d->mBugReport->isVisible()) { delete d->mBugReport; d->mBugReport = nullptr; } if (d->mSwitchApplicationLanguage && !d->mSwitchApplicationLanguage->isVisible()) { delete d->mSwitchApplicationLanguage; d->mSwitchApplicationLanguage = nullptr; } if (d->mAboutApp && !d->mAboutApp->isVisible()) { delete d->mAboutApp; d->mAboutApp = nullptr; } } void KHelpMenu::menuDestroyed() { d->mMenu = nullptr; } void KHelpMenu::contextHelpActivated() { QWhatsThis::enterWhatsThisMode(); } diff --git a/src/kmainwindow.cpp b/src/kmainwindow.cpp index fac00c7..2531aa9 100644 --- a/src/kmainwindow.cpp +++ b/src/kmainwindow.cpp @@ -1,944 +1,942 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) (C) 2000-2008 David Faure (faure@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kmainwindow.h" #include "kmainwindow_p.h" #ifdef QT_DBUS_LIB #include "kmainwindowiface_p.h" #endif #include "ktoolbarhandler_p.h" #include "khelpmenu.h" #include "ktoolbar.h" #include #include #include #include #include -#include #include -#include #include #include #include #include #include #include #ifdef QT_DBUS_LIB #include #endif #include #include #include #include #include #include #include #include #include //#include static const char WINDOW_PROPERTIES[]="WindowProperties"; static QMenuBar *internalMenuBar(KMainWindow *mw) { return mw->findChild(QString(), Qt::FindDirectChildrenOnly); } static QStatusBar *internalStatusBar(KMainWindow *mw) { return mw->findChild(QString(), Qt::FindDirectChildrenOnly); } /** * Listens to resize events from QDockWidgets. The KMainWindow * settings are set as dirty, as soon as at least one resize * event occurred. The listener is attached to the dock widgets * by dock->installEventFilter(dockResizeListener) inside * KMainWindow::event(). */ class DockResizeListener : public QObject { Q_OBJECT public: DockResizeListener(KMainWindow *win); ~DockResizeListener() override; bool eventFilter(QObject *watched, QEvent *event) override; private: KMainWindow *m_win; }; DockResizeListener::DockResizeListener(KMainWindow *win) : QObject(win), m_win(win) { } DockResizeListener::~DockResizeListener() { } bool DockResizeListener::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::Resize: case QEvent::Move: case QEvent::Hide: m_win->k_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls); break; default: break; } return QObject::eventFilter(watched, event); } KMWSessionManager::KMWSessionManager() { connect(qApp, &QGuiApplication::saveStateRequest, this, &KMWSessionManager::saveState); connect(qApp, &QGuiApplication::commitDataRequest, this, &KMWSessionManager::commitData); } KMWSessionManager::~KMWSessionManager() { } void KMWSessionManager::saveState(QSessionManager &sm) { KConfigGui::setSessionConfig(sm.sessionId(), sm.sessionKey()); KConfig *config = KConfigGui::sessionConfig(); if (!KMainWindow::memberList().isEmpty()) { // According to Jochen Wilhelmy , this // hook is useful for better document orientation KMainWindow::memberList().at(0)->saveGlobalProperties(config); } int n = 0; foreach (KMainWindow *mw, KMainWindow::memberList()) { n++; mw->savePropertiesInternal(config, n); } KConfigGroup group(config, "Number"); group.writeEntry("NumberOfWindows", n); // store new status to disk config->sync(); // generate discard command for new file QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); if (QFile::exists(localFilePath)) { QStringList discard; discard << QStringLiteral("rm"); discard << localFilePath; sm.setDiscardCommand(discard); } } void KMWSessionManager::commitData(QSessionManager &sm) { if (!sm.allowsInteraction()) { return; } /* Purpose of this exercise: invoke queryClose() without actually closing the windows, because - queryClose() may contain session management code, so it must be invoked - actually closing windows may quit the application - cf. QGuiApplication::quitOnLastWindowClosed() - quitting the application and thus closing the session manager connection violates the X11 XSMP protocol. The exact requirement of XSMP that would be broken is, in the description of the client's state machine: save-yourself-done: (changing state is forbidden) Closing the session manager connection causes a state change. Worst of all, that is a real problem with ksmserver - it will not save applications that quit on their own in state save-yourself-done. */ foreach (KMainWindow *window, KMainWindow::memberList()) { if (window->testAttribute(Qt::WA_WState_Hidden)) { continue; } QCloseEvent e; QApplication::sendEvent(window, &e); if (!e.isAccepted()) { sm.cancel(); return; } } } Q_GLOBAL_STATIC(KMWSessionManager, ksm) Q_GLOBAL_STATIC(QList, sMemberList) KMainWindow::KMainWindow(QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f), k_ptr(new KMainWindowPrivate) { k_ptr->init(this); } KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f), k_ptr(&dd) { k_ptr->init(this); } void KMainWindowPrivate::init(KMainWindow *_q) { q = _q; QGuiApplication::setFallbackSessionManagementEnabled(false); q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)); q->setAttribute(Qt::WA_DeleteOnClose); helpMenu = nullptr; //actionCollection()->setWidget( this ); #if 0 QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), q, SLOT(_k_slotSettingsChanged(int))); #endif // force KMWSessionManager creation ksm(); sMemberList()->append(q); // If application is translated, load translator information for use in // KAboutApplicationDialog or other getters. The context and messages below // both must be exactly as listed, and are forced to be loaded from the // application's own message catalog instead of kxmlgui's. KAboutData aboutData(KAboutData::applicationData()); if (aboutData.translators().isEmpty()) { aboutData.setTranslator( i18ndc(nullptr, "NAME OF TRANSLATORS", "Your names"), i18ndc(nullptr, "EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); } settingsDirty = false; autoSaveSettings = false; autoSaveWindowSize = true; // for compatibility //d->kaccel = actionCollection()->kaccel(); settingsTimer = nullptr; sizeTimer = nullptr; dockResizeListener = new DockResizeListener(_q); letDirtySettings = true; sizeApplied = false; } static bool endsWithHashNumber(const QString &s) { for (int i = s.length() - 1; i > 0; --i) { if (s[ i ] == QLatin1Char('#') && i != s.length() - 1) { return true; // ok } if (!s[ i ].isDigit()) { break; } } return false; } static inline bool isValidDBusObjectPathCharacter(const QChar &c) { ushort u = c.unicode(); return (u >= QLatin1Char('a') && u <= QLatin1Char('z')) || (u >= QLatin1Char('A') && u <= QLatin1Char('Z')) || (u >= QLatin1Char('0') && u <= QLatin1Char('9')) || (u == QLatin1Char('_')) || (u == QLatin1Char('/')); } void KMainWindowPrivate::polish(KMainWindow *q) { // Set a unique object name. Required by session management, window management, and for the dbus interface. QString objname; QString s; int unusedNumber = 1; const QString name = q->objectName(); bool startNumberingImmediately = true; bool tryReuse = false; if (name.isEmpty()) { // no name given objname = QStringLiteral("MainWindow#"); } else if (name.endsWith(QLatin1Char('#'))) { // trailing # - always add a number - KWin uses this for better grouping objname = name; } else if (endsWithHashNumber(name)) { // trailing # with a number - like above, try to use the given number first objname = name; tryReuse = true; startNumberingImmediately = false; } else { objname = name; startNumberingImmediately = false; } s = objname; if (startNumberingImmediately) { s += QLatin1Char('1'); } for (;;) { const QList list = qApp->topLevelWidgets(); bool found = false; foreach (QWidget *w, list) { if (w != q && w->objectName() == s) { found = true; break; } } if (!found) { break; } if (tryReuse) { objname = name.left(name.length() - 1); // lose the hash unusedNumber = 0; // start from 1 below tryReuse = false; } s.setNum(++unusedNumber); s = objname + s; } q->setObjectName(s); q->winId(); // workaround for setWindowRole() crashing, and set also window role, just in case TT q->setWindowRole(s); // will keep insisting that object name suddenly should not be used for window role dbusName = QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('/'); dbusName += q->objectName().replace(QLatin1Char('/'), QLatin1Char('_')); // Clean up for dbus usage: any non-alphanumeric char should be turned into '_' const int len = dbusName.length(); for (int i = 0; i < len; ++i) { if (!isValidDBusObjectPathCharacter(dbusName[i])) { dbusName[i] = QLatin1Char('_'); } } #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().registerObject(dbusName, q, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportNonScriptableSlots | QDBusConnection::ExportNonScriptableProperties | QDBusConnection::ExportAdaptors); #endif } void KMainWindowPrivate::setSettingsDirty(CallCompression callCompression) { if (!letDirtySettings) { return; } settingsDirty = true; if (autoSaveSettings) { if (callCompression == CompressCalls) { if (!settingsTimer) { settingsTimer = new QTimer(q); settingsTimer->setInterval(500); settingsTimer->setSingleShot(true); QObject::connect(settingsTimer, &QTimer::timeout, q, &KMainWindow::saveAutoSaveSettings); } settingsTimer->start(); } else { q->saveAutoSaveSettings(); } } } void KMainWindowPrivate::setSizeDirty() { if (autoSaveWindowSize) { if (!sizeTimer) { sizeTimer = new QTimer(q); sizeTimer->setInterval(500); sizeTimer->setSingleShot(true); QObject::connect(sizeTimer, SIGNAL(timeout()), q, SLOT(_k_slotSaveAutoSaveSize())); } sizeTimer->start(); } } KMainWindow::~KMainWindow() { sMemberList()->removeAll(this); delete static_cast(k_ptr->dockResizeListener); //so we don't get anymore events after k_ptr is destroyed delete k_ptr; } #ifndef KXMLGUI_NO_DEPRECATED QMenu *KMainWindow::helpMenu(const QString &aboutAppText, bool showWhatsThis) { K_D(KMainWindow); if (!d->helpMenu) { if (aboutAppText.isEmpty()) { d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), showWhatsThis); } else { d->helpMenu = new KHelpMenu(this, aboutAppText, showWhatsThis); } if (!d->helpMenu) { return nullptr; } } return d->helpMenu->menu(); } QMenu *KMainWindow::customHelpMenu(bool showWhatsThis) { K_D(KMainWindow); if (!d->helpMenu) { d->helpMenu = new KHelpMenu(this, QString(), showWhatsThis); connect(d->helpMenu, &KHelpMenu::showAboutApplication, this, &KMainWindow::showAboutApplication); } return d->helpMenu->menu(); } #endif bool KMainWindow::canBeRestored(int number) { if (!qApp->isSessionRestored()) { return false; } KConfig *config = KConfigGui::sessionConfig(); if (!config) { return false; } KConfigGroup group(config, "Number"); const int n = group.readEntry("NumberOfWindows", 1); return number >= 1 && number <= n; } const QString KMainWindow::classNameOfToplevel(int number) { if (!qApp->isSessionRestored()) { return QString(); } KConfig *config = KConfigGui::sessionConfig(); if (!config) { return QString(); } KConfigGroup group(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); if (!group.hasKey("ClassName")) { return QString(); } else { return group.readEntry("ClassName"); } } bool KMainWindow::restore(int number, bool show) { if (!canBeRestored(number)) { return false; } KConfig *config = KConfigGui::sessionConfig(); if (readPropertiesInternal(config, number)) { if (show) { KMainWindow::show(); } return false; } return false; } void KMainWindow::setCaption(const QString &caption) { setPlainCaption(caption); } void KMainWindow::setCaption(const QString &caption, bool modified) { QString title = caption; if (!title.contains(QStringLiteral("[*]")) && !title.isEmpty()) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } setPlainCaption(title); setWindowModified(modified); } void KMainWindow::setPlainCaption(const QString &caption) { setWindowTitle(caption); } void KMainWindow::appHelpActivated() { K_D(KMainWindow); if (!d->helpMenu) { d->helpMenu = new KHelpMenu(this); if (!d->helpMenu) { return; } } d->helpMenu->appHelpActivated(); } void KMainWindow::closeEvent(QCloseEvent *e) { K_D(KMainWindow); // Save settings if auto-save is enabled, and settings have changed if (d->settingsTimer && d->settingsTimer->isActive()) { d->settingsTimer->stop(); saveAutoSaveSettings(); } if (d->sizeTimer && d->sizeTimer->isActive()) { d->sizeTimer->stop(); d->_k_slotSaveAutoSaveSize(); } if (queryClose()) { // widgets will start destroying themselves at this point and we don't // want to save state anymore after this as it might be incorrect d->autoSaveSettings = false; d->letDirtySettings = false; e->accept(); } else { e->ignore(); //if the window should not be closed, don't close it } } bool KMainWindow::queryClose() { return true; } void KMainWindow::saveGlobalProperties(KConfig *) { } void KMainWindow::readGlobalProperties(KConfig *) { } void KMainWindow::savePropertiesInternal(KConfig *config, int number) { K_D(KMainWindow); const bool oldASWS = d->autoSaveWindowSize; d->autoSaveWindowSize = true; // make saveMainWindowSettings save the window size KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); // store objectName, className, Width and Height for later restoring // (Only useful for session management) cg.writeEntry("ObjectName", objectName()); cg.writeEntry("ClassName", metaObject()->className()); saveMainWindowSettings(cg); // Menubar, statusbar and Toolbar settings. cg = KConfigGroup(config, QByteArray::number(number).constData()); saveProperties(cg); d->autoSaveWindowSize = oldASWS; } void KMainWindow::saveMainWindowSettings(KConfigGroup &cg) { K_D(KMainWindow); //qDebug(200) << "KMainWindow::saveMainWindowSettings " << cg.name(); // Called by session management - or if we want to save the window size anyway if (d->autoSaveWindowSize) { KWindowConfig::saveWindowSize(windowHandle(), cg); } // One day will need to save the version number, but for now, assume 0 // Utilise the QMainWindow::saveState() functionality. const QByteArray state = saveState(); cg.writeEntry("State", state.toBase64()); QStatusBar *sb = internalStatusBar(this); if (sb) { if (!cg.hasDefault("StatusBar") && !sb->isHidden()) { cg.revertToDefault("StatusBar"); } else { cg.writeEntry("StatusBar", sb->isHidden() ? "Disabled" : "Enabled"); } } QMenuBar *mb = internalMenuBar(this); if (mb) { if (!cg.hasDefault("MenuBar") && !mb->isHidden()) { cg.revertToDefault("MenuBar"); } else { cg.writeEntry("MenuBar", mb->isHidden() ? "Disabled" : "Enabled"); } } if (!autoSaveSettings() || cg.name() == autoSaveGroup()) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name if (!cg.hasDefault("ToolBarsMovable") && !KToolBar::toolBarsLocked()) { cg.revertToDefault("ToolBarsMovable"); } else { cg.writeEntry("ToolBarsMovable", KToolBar::toolBarsLocked() ? "Disabled" : "Enabled"); } } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar *toolbar, toolBars()) { QByteArray groupName("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8())); KConfigGroup toolbarGroup(&cg, groupName.constData()); toolbar->saveSettings(toolbarGroup); n++; } } bool KMainWindow::readPropertiesInternal(KConfig *config, int number) { K_D(KMainWindow); const bool oldLetDirtySettings = d->letDirtySettings; d->letDirtySettings = false; if (number == 1) { readGlobalProperties(config); } // in order they are in toolbar list KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); // restore the object name (window role) if (cg.hasKey("ObjectName")) { setObjectName(cg.readEntry("ObjectName")); } d->sizeApplied = false; // since we are changing config file, reload the size of the window // if necessary. Do it before the call to applyMainWindowSettings. applyMainWindowSettings(cg); // Menubar, statusbar and toolbar settings. KConfigGroup grp(config, QByteArray::number(number).constData()); readProperties(grp); d->letDirtySettings = oldLetDirtySettings; return true; } void KMainWindow::applyMainWindowSettings(const KConfigGroup &cg) { K_D(KMainWindow); //qDebug(200) << "KMainWindow::applyMainWindowSettings " << cg.name(); QWidget *focusedWidget = QApplication::focusWidget(); const bool oldLetDirtySettings = d->letDirtySettings; d->letDirtySettings = false; if (!d->sizeApplied) { winId(); // ensure there's a window created KWindowConfig::restoreWindowSize(windowHandle(), cg); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); d->sizeApplied = true; } QStatusBar *sb = internalStatusBar(this); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); sb->setVisible( entry != QLatin1String("Disabled") ); } QMenuBar *mb = internalMenuBar(this); if (mb) { QString entry = cg.readEntry("MenuBar", "Enabled"); mb->setVisible( entry != QLatin1String("Disabled") ); } if (!autoSaveSettings() || cg.name() == autoSaveGroup()) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name QString entry = cg.readEntry("ToolBarsMovable", "Disabled"); KToolBar::setToolBarsLocked(entry == QLatin1String("Disabled")); } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar *toolbar, toolBars()) { QByteArray groupName("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8())); KConfigGroup toolbarGroup(&cg, groupName.constData()); toolbar->applySettings(toolbarGroup); n++; } QByteArray state; if (cg.hasKey("State")) { state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } if (focusedWidget) { focusedWidget->setFocus(); } d->settingsDirty = false; d->letDirtySettings = oldLetDirtySettings; } #ifndef KXMLGUI_NO_DEPRECATED void KMainWindow::restoreWindowSize(const KConfigGroup &cg) { KWindowConfig::restoreWindowSize(windowHandle(), cg); } #endif #ifndef KXMLGUI_NO_DEPRECATED void KMainWindow::saveWindowSize(KConfigGroup &cg) const { KWindowConfig::saveWindowSize(windowHandle(), cg); } #endif void KMainWindow::setSettingsDirty() { K_D(KMainWindow); d->setSettingsDirty(); } bool KMainWindow::settingsDirty() const { K_D(const KMainWindow); return d->settingsDirty; } void KMainWindow::setAutoSaveSettings(const QString &groupName, bool saveWindowSize) { setAutoSaveSettings(KConfigGroup(KSharedConfig::openConfig(), groupName), saveWindowSize); } void KMainWindow::setAutoSaveSettings(const KConfigGroup &group, bool saveWindowSize) { K_D(KMainWindow); d->autoSaveSettings = true; d->autoSaveGroup = group; d->autoSaveWindowSize = saveWindowSize; if (!saveWindowSize && d->sizeTimer) { d->sizeTimer->stop(); } // Now read the previously saved settings applyMainWindowSettings(d->autoSaveGroup); } void KMainWindow::resetAutoSaveSettings() { K_D(KMainWindow); d->autoSaveSettings = false; if (d->settingsTimer) { d->settingsTimer->stop(); } } bool KMainWindow::autoSaveSettings() const { K_D(const KMainWindow); return d->autoSaveSettings; } QString KMainWindow::autoSaveGroup() const { K_D(const KMainWindow); return d->autoSaveSettings ? d->autoSaveGroup.name() : QString(); } KConfigGroup KMainWindow::autoSaveConfigGroup() const { K_D(const KMainWindow); return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup(); } void KMainWindow::saveAutoSaveSettings() { K_D(KMainWindow); Q_ASSERT(d->autoSaveSettings); //qDebug(200) << "KMainWindow::saveAutoSaveSettings -> saving settings"; saveMainWindowSettings(d->autoSaveGroup); d->autoSaveGroup.sync(); d->settingsDirty = false; } bool KMainWindow::event(QEvent *ev) { K_D(KMainWindow); switch (ev->type()) { #if defined(Q_OS_WIN) || defined(Q_OS_OSX) case QEvent::Move: #endif case QEvent::Resize: d->setSizeDirty(); break; case QEvent::Polish: d->polish(this); break; case QEvent::ChildPolished: { QChildEvent *event = static_cast(ev); QDockWidget *dock = qobject_cast(event->child()); KToolBar *toolbar = qobject_cast(event->child()); QMenuBar *menubar = qobject_cast(event->child()); if (dock) { connect(dock, &QDockWidget::dockLocationChanged, this, &KMainWindow::setSettingsDirty); connect(dock, &QDockWidget::visibilityChanged, this, &KMainWindow::setSettingsDirty, Qt::QueuedConnection); connect(dock, &QDockWidget::topLevelChanged, this, &KMainWindow::setSettingsDirty); // there is no signal emitted if the size of the dock changes, // hence install an event filter instead dock->installEventFilter(k_ptr->dockResizeListener); } else if (toolbar) { // there is no signal emitted if the size of the toolbar changes, // hence install an event filter instead toolbar->installEventFilter(k_ptr->dockResizeListener); } else if (menubar) { // there is no signal emitted if the size of the menubar changes, // hence install an event filter instead menubar->installEventFilter(k_ptr->dockResizeListener); } } break; case QEvent::ChildRemoved: { QChildEvent *event = static_cast(ev); QDockWidget *dock = qobject_cast(event->child()); KToolBar *toolbar = qobject_cast(event->child()); QMenuBar *menubar = qobject_cast(event->child()); if (dock) { disconnect(dock, &QDockWidget::dockLocationChanged, this, &KMainWindow::setSettingsDirty); disconnect(dock, &QDockWidget::visibilityChanged, this, &KMainWindow::setSettingsDirty); disconnect(dock, &QDockWidget::topLevelChanged, this, &KMainWindow::setSettingsDirty); dock->removeEventFilter(k_ptr->dockResizeListener); } else if (toolbar) { toolbar->removeEventFilter(k_ptr->dockResizeListener); } else if (menubar) { menubar->removeEventFilter(k_ptr->dockResizeListener); } } break; default: break; } return QMainWindow::event(ev); } bool KMainWindow::hasMenuBar() { return internalMenuBar(this); } void KMainWindowPrivate::_k_slotSettingsChanged(int category) { Q_UNUSED(category); // This slot will be called when the style KCM changes settings that need // to be set on the already running applications. // At this level (KMainWindow) the only thing we need to restore is the // animations setting (whether the user wants builtin animations or not). q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)); } void KMainWindowPrivate::_k_slotSaveAutoSaveSize() { if (autoSaveGroup.isValid()) { KWindowConfig::saveWindowSize(q->windowHandle(), autoSaveGroup); } } KToolBar *KMainWindow::toolBar(const QString &name) { QString childName = name; if (childName.isEmpty()) { childName = QStringLiteral("mainToolBar"); } KToolBar *tb = findChild(childName); if (tb) { return tb; } KToolBar *toolbar = new KToolBar(childName, this); // non-XMLGUI toolbar return toolbar; } QList KMainWindow::toolBars() const { QList ret; foreach (QObject *child, children()) if (KToolBar *toolBar = qobject_cast(child)) { ret.append(toolBar); } return ret; } QList KMainWindow::memberList() { return *sMemberList(); } QString KMainWindow::dbusName() const { return k_func()->dbusName; } #include "moc_kmainwindow.cpp" #include "kmainwindow.moc" diff --git a/src/kmainwindow.h b/src/kmainwindow.h index 83afde2..7353f4a 100644 --- a/src/kmainwindow.h +++ b/src/kmainwindow.h @@ -1,740 +1,739 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) (C) 2000-2008 David Faure (faure@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KMAINWINDOW_H #define KMAINWINDOW_H #include #include -#include class QMenu; class KConfig; class KConfigGroup; class KMWSessionManager; class KMainWindowPrivate; class KToolBar; // internal, not public API, may change any time #define XMLGUI_DECLARE_PRIVATE(classname) \ inline classname ## Private *k_func() { return reinterpret_cast(k_ptr); } \ inline const classname ## Private *k_func() const { return reinterpret_cast(k_ptr); } \ friend class classname ## Private; // TODO KF6: remove #define KDE_DEFAULT_WINDOWFLAGS 0 /** * @class KMainWindow kmainwindow.h KMainWindow * * @short Top level main window * * Top level widget that provides toolbars, a status line and a frame. * * It should be used as a top level (parent-less) widget. * It manages the geometry for all its children, including your * main widget. * * Normally, you will inherit from KMainWindow, * then construct (or use some existing) widget as * your main view. You can set only one main view. * * You can add as many toolbars as you like. There can be only one menubar * and only one statusbar. * * The toolbars, menubar, and statusbar can be created by the * KMainWindow and - unlike the old KMainWindow - may, but do not * have to, be deleted by you. KMainWindow will handle that internally. * * Height and width can be operated independently from each other. Simply * define the minimum/maximum height/width of your main widget and * KMainWindow will take this into account. For fixed size windows set * your main widget to a fixed size. * * Fixed aspect ratios (heightForWidth()) and fixed width widgets are * not supported. * * KMainWindow will set icon, mini icon and caption, which it gets * from KApplication. It provides full session management, and * will save its position, geometry and positions of toolbars and * menubar on logout. If you want to save additional data, reimplement * saveProperties() and (to read them again on next login) * readProperties(). To save special data about your data, reimplement * saveGlobalProperties(). To warn user that application or * windows have unsaved data on close or logout, reimplement * queryClose(). * * You have to implement session restoring also in your main() function. * There are also kRestoreMainWindows convenience functions which * can do this for you and restore all your windows on next login. * * Note that KMainWindow uses KGlobal::ref() and KGlobal::deref() so that closing * the last mainwindow will quit the application unless there is still something * that holds a ref in KGlobal - like a KIO job, or a systray icon. * * @author Reginald Stadlbauer (reggie@kde.org) Stephan Kulow (coolo@kde.org), Matthias Ettrich (ettrich@kde.org), Chris Schlaeger (cs@kde.org), Sven Radej (radej@kde.org). Maintained by David Faure (faure@kde.org) */ class KXMLGUI_EXPORT KMainWindow : public QMainWindow { friend class KMWSessionManager; friend class DockResizeListener; XMLGUI_DECLARE_PRIVATE(KMainWindow) Q_OBJECT Q_PROPERTY(bool hasMenuBar READ hasMenuBar) Q_PROPERTY(bool autoSaveSettings READ autoSaveSettings) Q_PROPERTY(QString autoSaveGroup READ autoSaveGroup) public: /** * Construct a main window. * * @param parent The widget parent. This is usually 0 but it may also be the window * group leader. In that case, the KMainWindow becomes sort of a * secondary window. * * @param f Specify the window flags. The default is none. * * Note that a KMainWindow per-default is created with the * Qt::WA_DeleteOnClose attribute set, i.e. it is automatically destroyed * when the window is closed. If you do not want this behavior, call * \code * window->setAttribute(Qt::WA_DeleteOnClose, false); * \endcode * * KMainWindows must be created on the heap with 'new', like: * \code * KMainWindow *kmw = new KMainWindow(...); * kmw->setObjectName(...); * \endcode * * Since the KDE Frameworks 5.16 release, KMainWindow will also enter information regarding * the application's translators by default, using KAboutData::setTranslator(). This only occurs * if no translators are already assigned in KAboutData (see KAboutData::setTranslator() for * details -- the auto-assignment here uses the same translated strings as specified for that * function). * * IMPORTANT: For session management and window management to work * properly, all main windows in the application should have a * different name. If you don't do it, KMainWindow will create * a unique name, but it's recommended to explicitly pass a window name that will * also describe the type of the window. If there can be several windows of the same * type, append '#' (hash) to the name, and KMainWindow will replace it with numbers to make * the names unique. For example, for a mail client which has one main window showing * the mails and folders, and which can also have one or more windows for composing * mails, the name for the folders window should be e.g. "mainwindow" and * for the composer windows "composer#". * */ explicit KMainWindow(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); /** * \brief Destructor. * * Will also destroy the toolbars, and menubar if * needed. */ ~KMainWindow() override; /** * Retrieve the standard help menu. * * It contains entries for the * help system (activated by F1), an optional "What's This?" entry * (activated by Shift F1), an application specific dialog box, * and an "About KDE" dialog box. * * Example (adding a standard help menu to your application): * \code * QMenu *help = helpMenu( ); * menuBar()->addMenu( help ); * \endcode * * @param aboutAppText The string that is used in the application * specific dialog box. If you leave this string empty the * information in the global KAboutData of the * application will be used to make a standard dialog box. * * @param showWhatsThis Set this to false if you do not want to include * the "What's This" menu entry. * * @return A standard help menu. * @deprecated use KHelpMenu directly */ #ifndef KXMLGUI_NO_DEPRECATED KXMLGUI_DEPRECATED QMenu *helpMenu(const QString &aboutAppText = QString(), bool showWhatsThis = true); #endif /** * Returns the help menu. Creates a standard help menu if none exists yet. * * It contains entries for the * help system (activated by F1), an optional "What's This?" entry * (activated by Shift F1), an application specific dialog box, * and an "About KDE" dialog box. You must create the application * specific dialog box yourself. When the "About application" * menu entry is activated, a signal will trigger the * showAboutApplication slot. See showAboutApplication for more * information. * * Example (adding a help menu to your application): * \code * menuBar()->addMenu( customHelpMenu() ); * \endcode * * @param showWhatsThis Set this to @c false if you do not want to include * the "What's This" menu entry. * * @return A standard help menu. * @deprecated use XMLGUI instead, or KHelpMenu directly */ #ifndef KXMLGUI_NO_DEPRECATED KXMLGUI_DEPRECATED QMenu *customHelpMenu(bool showWhatsThis = true); #endif /** * If the session did contain so high a number, @c true is returned, * else @c false. * @see restore() **/ static bool canBeRestored(int number); /** * Returns the className() of the @p number of the toplevel window which * should be restored. * * This is only useful if your application uses * different kinds of toplevel windows. */ static const QString classNameOfToplevel(int number); /** * Try to restore the toplevel widget as defined by @p number (1..X). * * You should call canBeRestored() first. * * If the session did not contain so high a number, the configuration * is not changed and @c false returned. * * That means clients could simply do the following: * \code * if (qApp->isSessionRestored()){ * int n = 1; * while (KMainWindow::canBeRestored(n)){ * (new childMW)->restore(n); * n++; * } * } else { * // create default application as usual * } * \endcode * Note that if @p show is @c true (default), QWidget::show() is called * implicitly in restore. * * With this you can easily restore all toplevel windows of your * application. * * If your application uses different kinds of toplevel * windows, then you can use KMainWindow::classNameOfToplevel(n) * to determine the exact type before calling the childMW * constructor in the example from above. * * @note You don't need to deal with this function. Use the * kRestoreMainWindows() convenience template function instead! * @see kRestoreMainWindows() * @see readProperties() * @see canBeRestored() */ bool restore(int number, bool show = true); /** * Returns true, if there is a menubar */ bool hasMenuBar(); /** * List of members of KMainWindow class. */ static QList memberList(); /** * Returns a pointer to the toolbar with the specified name. * This refers to toolbars created dynamically from the XML UI * framework. If the toolbar does not exist one will be created. * * @param name The internal name of the toolbar. If no name is * specified "mainToolBar" is assumed. * * @return A pointer to the toolbar **/ KToolBar *toolBar(const QString &name = QString()); /** * @return A list of all toolbars for this window */ QList toolBars() const; /** * Call this to enable "auto-save" of toolbar/menubar/statusbar settings * (and optionally window size). * If the *bars were moved around/shown/hidden when the window is closed, * saveMainWindowSettings( KConfigGroup(KSharedConfig::openConfig(), groupName) ) will be called. * * @param groupName a name that identifies this "type of window". * You can have several types of window in the same application. * * @param saveWindowSize set it to true to include the window size * when saving. * * Typically, you will call setAutoSaveSettings() in your * KMainWindow-inherited class constructor, and it will take care * of restoring and saving automatically. Make sure you call this * _after all_ your *bars have been created. * * To make sure that KMainWindow properly obtains the default * size of the window you should do the following: * - Remove hard coded resize() calls in the constructor or main, they * should be removed in favor of letting the automatic resizing * determine the default window size. Hard coded window sizes will * be wrong for users that have big fonts, use different styles, * long/small translations, large toolbars, and other factors. * - Put the setAutoSaveSettings() ( or setupGUI() ) call after all widgets * have been created and placed inside the main window (i.e. for 99% of * apps setCentralWidget()) * - Widgets that inherit from QWidget (like game boards) should overload * "virtual QSize sizeHint() const;" to specify a default size rather * than letting QWidget::adjust use the default size of 0x0. */ void setAutoSaveSettings(const QString &groupName = QStringLiteral("MainWindow"), bool saveWindowSize = true); /** * Overload that lets you specify a KConfigGroup. * This allows the settings to be saved into another file than KSharedConfig::openConfig(). * @since 4.1 */ void setAutoSaveSettings(const KConfigGroup &group, bool saveWindowSize = true); /** * Disable the auto-save-settings feature. * You don't normally need to call this, ever. */ void resetAutoSaveSettings(); /** * @return the current autosave setting, i.e. true if setAutoSaveSettings() was called, * false by default or if resetAutoSaveSettings() was called. */ bool autoSaveSettings() const; /** * @return the group used for setting-autosaving. * Only meaningful if setAutoSaveSettings(const QString&, bool) was called. * This can be useful for forcing a save or an apply, e.g. before and after * using KEditToolBar. * * @note You should rather use saveAutoSaveSettings() for saving or autoSaveConfigGroup() for loading. * This method doesn't make sense if setAutoSaveSettings(const KConfigGroup&, bool) was called. */ QString autoSaveGroup() const; /** * @return the group used for setting-autosaving. * Only meaningful if setAutoSaveSettings() was called. * This can be useful for forcing an apply, e.g. after using KEditToolBar. * @since 4.1 */ KConfigGroup autoSaveConfigGroup() const; /** * Read settings for statusbar, menubar and toolbar from their respective * groups in the config file and apply them. * * @param config Config group to read the settings from. * * KF5 porting note: the unused bool argument was removed, make sure to remove it from your * reimplementations too! And add a override for good measure. */ virtual void applyMainWindowSettings(const KConfigGroup &config); /** * Save settings for statusbar, menubar and toolbar to their respective * groups in the config group @p config. * * @param config Config group to save the settings to. */ void saveMainWindowSettings(KConfigGroup &config); /** * Returns the path under which this window's D-Bus object is exported. * @since 4.0.1 */ QString dbusName() const; /** * @returns Always @c false * @deprecated since 5.0, the functionality got removed **/ #ifndef KXMLGUI_NO_DEPRECATED KXMLGUI_DEPRECATED bool initialGeometrySet() const { return false; } #endif public Q_SLOTS: /** * Makes a KDE compliant caption (window title). * * @param caption Your caption. @em Do @em not include the application name * in this string. It will be added automatically according to the KDE * standard. */ virtual void setCaption(const QString &caption); /** * Makes a KDE compliant caption. * * @param caption Your caption. @em Do @em not include the application name * in this string. It will be added automatically according to the KDE * standard. * @param modified Specify whether the document is modified. This displays * an additional sign in the title bar, usually "**". */ virtual void setCaption(const QString &caption, bool modified); /** * Make a plain caption without any modifications. * * @param caption Your caption. This is the string that will be * displayed in the window title. */ virtual void setPlainCaption(const QString &caption); /** * Open the help page for the application. * * The application name is * used as a key to determine what to display and the system will attempt * to open \/index.html. * * This method is intended for use by a help button in the toolbar or * components outside the regular help menu. Use helpMenu() when you * want to provide access to the help system from the help menu. * * Example (adding a help button to the first toolbar): * * \code * toolBar(0)->addAction(QIcon::fromTheme("help-contents"), i18n("Help"), * this, SLOT(appHelpActivated())); * \endcode * */ void appHelpActivated(); /** * Tell the main window that it should save its settings when being closed. * This is part of the auto-save-settings feature. * For everything related to toolbars this happens automatically, * but you have to call setSettingsDirty() in the slot that toggles * the visibility of the statusbar. */ void setSettingsDirty(); protected: /** * Reimplemented to catch QEvent::Polish in order to adjust the object name * if needed, once all constructor code for the main window has run. * Also reimplemented to catch when a QDockWidget is added or removed. */ bool event(QEvent *event) override; /** * Reimplemented to autosave settings and call queryClose(). * * We recommend that you reimplement queryClose() rather than closeEvent(). * If you do it anyway, ensure to call the base implementation to keep * the feature of auto-saving window settings working. */ void closeEvent(QCloseEvent *) override; /** Called before the window is closed, either by the user or indirectly by the session manager. The purpose of this function is to prepare the window in a way that it is safe to close it, i.e. without the user losing some data. Default implementation returns true. Returning @c false will cancel the closing, and, if KApplication::sessionSaving() is true, it will also cancel KDE logout. Reimplement this function to prevent the user from losing data. Example: \code switch ( KMessageBox::warningYesNoCancel( this, i18n("Save changes to document foo?")) ) { case KMessageBox::Yes : // save document here. If saving fails, return false; return true; case KMessageBox::No : return true; default: // cancel return false; \endcode Note that you should probably @em not actually close the document from within this method, as it may be called by the session manager before the session is saved. If the document is closed before the session save occurs, its location might not be properly saved. In addition, the session shutdown may be canceled, in which case the document should remain open. @see KApplication::sessionSaving() */ virtual bool queryClose(); /** * Save your instance-specific properties. The function is * invoked when the session manager requests your application * to save its state. * * Please reimplement these function in childclasses. * * Note: No user interaction is allowed * in this function! * */ virtual void saveProperties(KConfigGroup &) {} /** * Read your instance-specific properties. * * Is called indirectly by restore(). */ virtual void readProperties(const KConfigGroup &) {} /** * Save your application-wide properties. The function is * invoked when the session manager requests your application * to save its state. * * This function is similar to saveProperties() but is only called for * the very first main window, regardless how many main window are open. * Override it if you need to save other data about your documents on * session end. sessionConfig is a config to which that data should be * saved. Normally, you don't need this function. But if you want to save * data about your documents that are not in opened windows you might need * it. * * Default implementation does nothing. */ virtual void saveGlobalProperties(KConfig *sessionConfig); /** * The counterpart of saveGlobalProperties(). * * Read the application-specific properties in again. */ virtual void readGlobalProperties(KConfig *sessionConfig); void savePropertiesInternal(KConfig *, int); bool readPropertiesInternal(KConfig *, int); /** * For inherited classes */ bool settingsDirty() const; /** * For inherited classes * @deprecated use KWindowConfig::saveWindowSize */ #ifndef KXMLGUI_NO_DEPRECATED KXMLGUI_DEPRECATED void saveWindowSize(KConfigGroup &config) const; #endif /** * For inherited classes * @deprecated use KWindowConfig::restoreWindowSize */ #ifndef KXMLGUI_NO_DEPRECATED KXMLGUI_DEPRECATED void restoreWindowSize(const KConfigGroup &config); #endif protected Q_SLOTS: /** * This slot does nothing. * * It must be reimplemented if you want * to use a custom About Application dialog box. This slot is * connected to the About Application entry in the menu returned * by customHelpMenu. * * Example: * \code * * void MyMainLevel::setupInterface() * { * .. * menuBar()->addMenu( customHelpMenu() ); * .. * } * * void MyMainLevel::showAboutApplication() * { * * } * \endcode * @deprecated use KHelpMenu */ #ifndef KXMLGUI_NO_DEPRECATED virtual KXMLGUI_DEPRECATED void showAboutApplication() {} #endif /** * This slot should only be called in case you reimplement closeEvent() and * if you are using the "auto-save" feature. In all other cases, * setSettingsDirty() should be called instead to benefit from the delayed * saving. * * @see setAutoSaveSettings * @see setSettingsDirty * * Example: * \code * * void MyMainWindow::closeEvent( QCloseEvent *e ) * { * // Save settings if auto-save is enabled, and settings have changed * if ( settingsDirty() && autoSaveSettings() ) * saveAutoSaveSettings(); * .. * } * \endcode */ void saveAutoSaveSettings(); protected: KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f); KMainWindowPrivate *const k_ptr; private: Q_PRIVATE_SLOT(k_func(), void _k_slotSettingsChanged(int)) Q_PRIVATE_SLOT(k_func(), void _k_slotSaveAutoSaveSize()) }; /** * @defgroup KXMLGUI_Session KXMLGUI Session Macros and Functions * * @{ */ #ifndef KXMLGUI_NO_DEPRECATED /** * @def RESTORE * Restores the last session. * * @deprecated since 5.0, use kRestoreMainWindows() instead **/ #define RESTORE(type) { int n = 1;\ while (KMainWindow::canBeRestored(n)){\ (new type)->restore(n);\ n++;}} #endif /** * @def KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS * Returns the maximal number of arguments that are actually * supported by kRestoreMainWindows(). **/ #define KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS 3 /** * Restores the last session. (To be used in your main function). * * These functions work also if you have more than one kind of toplevel * widget (each derived from KMainWindow, of course). * * Imagine you have three kinds of toplevel widgets: the classes @c childMW1, * @c childMW2 and @c childMW3. Then you can just do: * * \code * int main(int argc, char *argv[]) * { * // [...] * if (qApp->isSessionRestored()) * kRestoreMainWindows(); * else { * // create default application as usual * } * // [...] * } * \endcode * * kRestoreMainWindows<>() will create (on the heap) as many instances * of your main windows as have existed in the last session and * call KMainWindow::restore() with the correct arguments. Note that * also QWidget::show() is called implicitly. * * Currently, these functions are provided for up to three * template arguments. If you need more, tell us. To help you in * deciding whether or not you can use kRestoreMainWindows, a * define #KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS is provided. * * @tparam T toplevel widget class * * @see KMainWindow::restore() * @see KMainWindow::classNameOfToplevel() **/ template inline void kRestoreMainWindows() { for (int n = 1; KMainWindow::canBeRestored(n); ++n) { const QString className = KMainWindow::classNameOfToplevel(n); if (className == QLatin1String(T::staticMetaObject.className())) { (new T)->restore(n); } } } /** * Restores the last session. * Overloaded method for usage with multiple different toplevel widget classes. * * @tparam T0 one toplevel widget class * @tparam T1 explicit other toplevel widget class for disambiguation from base template * @tparam Tn Parameter pack to take 0..n further KMainWindows */ template inline void kRestoreMainWindows() { kRestoreMainWindows(); kRestoreMainWindows(); } /** @} */ #endif diff --git a/src/kmainwindowiface_p.h b/src/kmainwindowiface_p.h index aa4f448..df10266 100644 --- a/src/kmainwindowiface_p.h +++ b/src/kmainwindowiface_p.h @@ -1,119 +1,118 @@ /* This file is part of the KDE project Copyright (C) 2001 Ian Reinhart Geiser Copyright (C) 2006 Thiago Macieira This program 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 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 Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMAINWINDOWIFACE_P_H #define KMAINWINDOWIFACE_P_H #include -#include class KXmlGuiWindow; /** * @short D-Bus interface to KMainWindow. * * This is the main interface to the KMainWindow. This will provide a consistent * D-Bus interface to all KDE applications that use it. * * @author Ian Reinhart Geiser */ class KMainWindowInterface : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KMainWindow") public: /** Construct a new interface object. @param mainWindow - The parent KMainWindow object that will provide us with the QAction objects. */ explicit KMainWindowInterface(KXmlGuiWindow *mainWindow); /** Destructor Cleans up the dcop action proxy object. **/ ~KMainWindowInterface(); public Q_SLOTS: /** Return a list of actions available to the application's window. @return A QStringList containing valid names actions. */ QStringList actions(); /** Activates the requested action. @param action The name of the action to activate. The names of valid actions can be found by calling actions(). @return The success of the operation. */ bool activateAction(const QString &action); /** Disables the requested action. @param action The name of the action to disable. The names of valid actions can be found by calling actions(). @return The success of the operation. */ bool disableAction(const QString &action); /** Enables the requested action. @param action The name of the action to enable. The names of valid actions can be found by calling actions(). @return The success of the operation. */ bool enableAction(const QString &action); /** Returns the status of the requested action. @param action The name of the action. The names of valid actions can be found by calling actions(). @returns The state of the action, true - enabled, false - disabled. */ bool actionIsEnabled(const QString &action); /** Returns the tool tip text of the requested action. @param action The name of the action to activate. The names of valid actions can be found by calling actions(). @return A QString containing the text of the action's tool tip. */ QString actionToolTip(const QString &action); /** Returns the ID of the current main window. This is useful for automated screen captures or other evil widget fun. @return A integer value of the main window's ID. **/ qlonglong winId(); /** Copies a pixmap representation of the current main window to the clipboard. **/ void grabWindowToClipBoard(); private: KXmlGuiWindow *m_MainWindow; }; #endif // KMAINWINDOWIFACE_P_H diff --git a/src/kmenumenuhandler_p.cpp b/src/kmenumenuhandler_p.cpp index fcb3309..d73053b 100644 --- a/src/kmenumenuhandler_p.cpp +++ b/src/kmenumenuhandler_p.cpp @@ -1,250 +1,249 @@ /* This file is part of the KDE project Copyright (C) 2006 Olivier Goffart 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 "kmenumenuhandler_p.h" #include "kxmlguibuilder.h" #include "kxmlguiclient.h" #include "kxmlguifactory.h" #include "kactioncollection.h" #include "kmainwindow.h" #include "ktoolbar.h" #include "kshortcutwidget.h" #include "debug.h" #include #include #include #include -#include #include #include #include #include #include #include namespace KDEPrivate { KMenuMenuHandler::KMenuMenuHandler(KXMLGUIBuilder *builder) : QObject(), m_builder(builder), m_popupMenu(nullptr), m_popupAction(nullptr), m_contextMenu(nullptr) { m_toolbarAction = new KSelectAction(i18n("Add to Toolbar"), this); connect(m_toolbarAction, &QAction::triggered, this, &KMenuMenuHandler::slotAddToToolBar); } void KMenuMenuHandler::insertMenu(QMenu *popup) { popup->installEventFilter(this); } bool KMenuMenuHandler::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: if (m_contextMenu && m_contextMenu->isVisible()) { m_contextMenu->hide(); return true; } break; case QEvent::MouseButtonRelease: if (m_contextMenu && m_contextMenu->isVisible()) { return true; } break; case QEvent::ContextMenu: { QContextMenuEvent *e = static_cast(event); QMenu *menu = static_cast(watched); if (e->reason() == QContextMenuEvent::Mouse) { showContextMenu(menu, e->pos()); } else if (menu->activeAction()) { showContextMenu(menu, menu->actionGeometry(menu->activeAction()).center()); } } event->accept(); return true; default: break; } return false; } void KMenuMenuHandler::buildToolbarAction() { KMainWindow *window = qobject_cast(m_builder->widget()); if (!window) { return; } QStringList toolbarlist; foreach (KToolBar *b, window->toolBars()) { toolbarlist << (b->windowTitle().isEmpty() ? b->objectName() : b->windowTitle()); } m_toolbarAction->setItems(toolbarlist); } static KActionCollection *findParentCollection(KXMLGUIFactory *factory, QAction *action) { foreach (KXMLGUIClient *client, factory->clients()) { KActionCollection *collection = client->actionCollection(); // if the call to actions() is too slow, add KActionCollection::contains(QAction*). if (collection->actions().contains(action)) { return collection; } } return nullptr; } void KMenuMenuHandler::slotSetShortcut() { if (!m_popupMenu || !m_popupAction) { return; } QDialog dialog(m_builder->widget()); dialog.setLayout(new QVBoxLayout); KShortcutWidget swidget(&dialog); swidget.setShortcut(m_popupAction->shortcuts()); dialog.layout()->addWidget(&swidget); QDialogButtonBox box(&dialog); box.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(&box, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(&box, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); dialog.layout()->addWidget(&box); KActionCollection *parentCollection = nullptr; if (dynamic_cast(m_builder)) { QList checkCollections; KXMLGUIFactory *factory = dynamic_cast(m_builder)->factory(); parentCollection = findParentCollection(factory, m_popupAction); foreach (KXMLGUIClient *client, factory->clients()) { checkCollections += client->actionCollection(); } swidget.setCheckActionCollections(checkCollections); } if (dialog.exec()) { m_popupAction->setShortcuts(swidget.shortcut()); swidget.applyStealShortcut(); if (parentCollection) { parentCollection->writeSettings(); } } } void KMenuMenuHandler::slotAddToToolBar(int tb) { KMainWindow *window = qobject_cast(m_builder->widget()); if (!window) { return; } if (!m_popupMenu || !m_popupAction) { return; } KXMLGUIFactory *factory = dynamic_cast(m_builder)->factory(); QString actionName = m_popupAction->objectName(); // set by KActionCollection::addAction KActionCollection *collection = nullptr; if (factory) { collection = findParentCollection(factory, m_popupAction); } if (!collection) { qCWarning(DEBUG_KXMLGUI) << "Cannot find the action collection for action " << actionName; return; } KToolBar *toolbar = window->toolBars().at(tb); toolbar->addAction(m_popupAction); const KXMLGUIClient *client = collection->parentGUIClient(); QString xmlFile = client->localXMLFile(); QDomDocument document; document.setContent(KXMLGUIFactory::readConfigFile(client->xmlFile(), client->componentName())); QDomElement elem = document.documentElement().toElement(); const QLatin1String tagToolBar("ToolBar"); const QLatin1String attrNoEdit("noEdit"); const QLatin1String attrName("name"); QDomElement toolbarElem; QDomNode n = elem.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { QDomElement elem = n.toElement(); if (!elem.isNull() && elem.tagName() == tagToolBar && elem.attribute(attrName) == toolbar->objectName()) { if (elem.attribute(attrNoEdit) == QStringLiteral("true")) { qCWarning(DEBUG_KXMLGUI) << "The toolbar is not editable"; return; } toolbarElem = elem; break; } } if (toolbarElem.isNull()) { toolbarElem = document.createElement(tagToolBar); toolbarElem.setAttribute(attrName, toolbar->objectName()); elem.appendChild(toolbarElem); } KXMLGUIFactory::findActionByName(toolbarElem, actionName, true); KXMLGUIFactory::saveConfigFile(document, xmlFile); } void KMenuMenuHandler::showContextMenu(QMenu *menu, const QPoint &pos) { Q_ASSERT(!m_popupMenu); Q_ASSERT(!m_popupAction); Q_ASSERT(!m_contextMenu); auto *action = menu->actionAt(pos); if (!action || action->isSeparator()) { return; } m_popupMenu = menu; m_popupAction = action; m_contextMenu = new QMenu; m_contextMenu->addAction(i18n("Configure Shortcut..."), this, &KMenuMenuHandler::slotSetShortcut); KMainWindow *window = qobject_cast(m_builder->widget()); if (window) { m_contextMenu->addAction(m_toolbarAction); buildToolbarAction(); } m_contextMenu->exec(menu->mapToGlobal(pos)); delete m_contextMenu; m_contextMenu = nullptr; m_popupAction = nullptr; m_popupMenu = nullptr; } } //END namespace KDEPrivate diff --git a/src/ksendbugmail/main.cpp b/src/ksendbugmail/main.cpp index d376ac6..978cb8e 100644 --- a/src/ksendbugmail/main.cpp +++ b/src/ksendbugmail/main.cpp @@ -1,161 +1,160 @@ /* Copyright (c) 2000 Bernd Johannes Wuebben Copyright (c) 2000 Stephan Kulow 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 #include #include #include #include #include -#include #include #include #include #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 = 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) == 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) == 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 + 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 += 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, &SMTP::messageSent, &bm, &BugMailer::slotSend); QObject::connect(sm, &SMTP::error, &bm, &BugMailer::slotError); 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 881c1f2..3400151 100644 --- a/src/ksendbugmail/smtp.cpp +++ b/src/ksendbugmail/smtp.cpp @@ -1,350 +1,349 @@ /* Copyright (c) 2000 Bernd Johannes Wuebben Copyright (c) 2000 Stephan Kulow 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 -#include #include #include SMTP::SMTP(char *serverhost, unsigned short int port, int timeout) { 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, &QTimer::timeout, this, &SMTP::connectTimerTick); connect(&timeOutTimer, &QTimer::timeout, this, &SMTP::connectTimedOut); connect(&interactTimer, &QTimer::timeout, this, &SMTP::interactTimedOut); // some sendmail will give 'duplicate helo' error, quick fix for now connect(this, &SMTP::messageSent, this, &SMTP::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(QLatin1Char('<')); if (index == -1) { return; } senderAddress = senderAddress.mid(index + 1); index = senderAddress.indexOf(QLatin1Char('>')); if (index != -1) { senderAddress = senderAddress.left(index); } senderAddress = senderAddress.simplified(); while (1) { index = senderAddress.indexOf(QLatin1Char(' ')); if (index != -1) { senderAddress = senderAddress.mid(index + 1); // take one side } else { break; } } index = senderAddress.indexOf(QLatin1Char('@')); if (index == -1) { 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() { // qCDebug(DEBUG_KXMLGUI) << "started connect timer"; connectTimer.setSingleShot(true); connectTimer.start(100); } void SMTP::closeConnection() { socketClosed(); } void SMTP::sendMessage() { 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() { 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, &QIODevice::readyRead, this, &SMTP::socketReadyToRead); connect(sock, QOverload::of(&QAbstractSocket::error), this, &SMTP::socketError); connect(sock, &QAbstractSocket::disconnected, this, &SMTP::socketClosed); timeOutTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "connected"; } void SMTP::connectTimedOut() { timeOutTimer.stop(); // qCDebug(DEBUG_KXMLGUI) << "socket connection timed out"; socketClosed(); emit error(ConnectTimeout); } void SMTP::interactTimedOut() { 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 += 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(QByteArray *line) { int i, stat; 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(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/kswitchlanguagedialog_p.cpp b/src/kswitchlanguagedialog_p.cpp index 316e1c3..39b2f78 100644 --- a/src/kswitchlanguagedialog_p.cpp +++ b/src/kswitchlanguagedialog_p.cpp @@ -1,446 +1,444 @@ /* * 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 #include #include -#include #include #include -#include #include #include #include #include #include #include - +#include #include #include #include // 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 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); } // 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; } } 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 languageRows; QList 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(::qobject_cast(signalSender)); if (!removeButton) { qCritical() << "KSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton" << endl; return; } QMap::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 insertedLanguges; const QList 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(); } } diff --git a/src/ktoolbar.cpp b/src/ktoolbar.cpp index 7348c3f..e22b39f 100644 --- a/src/ktoolbar.cpp +++ b/src/ktoolbar.cpp @@ -1,1412 +1,1410 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997, 1998 Stephan Kulow (coolo@kde.org) (C) 1997, 1998 Mark Donohoe (donohoe@kde.org) (C) 1997, 1998 Sven Radej (radej@kde.org) (C) 1997, 1998 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 1999 Kurt Granroth (granroth@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "ktoolbar.h" #include #include #include #include #include #include #include #include #include #include -#include #include #ifdef QT_DBUS_LIB #include #include #endif -#include #include #include #include #include #include #include #include #include #include "kactioncollection.h" #include "kedittoolbar.h" #include "kxmlguifactory.h" #include "kxmlguiwindow.h" #include "ktoolbarhelper_p.h" /* Toolbar settings (e.g. icon size or toolButtonStyle) ===================================================== We have the following stack of settings (in order of priority) : - user-specified settings (loaded/saved in KConfig) - developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime) - KDE-global default (user-configurable; can change at runtime) and when switching between kparts, they are saved as xml in memory, which, in the unlikely case of no-kmainwindow-autosaving, could be different from the user-specified settings saved in KConfig and would have priority over it. So, in summary, without XML: Global config / User settings (loaded/saved in kconfig) and with XML: Global config / App-XML attributes / User settings (loaded/saved in kconfig) And all those settings (except the KDE-global defaults) have to be stored in memory since we cannot retrieve them at random points in time, not knowing the xml document nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays. For instance, if you change the KDE-global default, whether this makes a change on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings. Only if there are no settings at those levels, should the change of KDEDefault make a difference. */ enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings, NSettingLevels }; enum { Unset = -1 }; class Q_DECL_HIDDEN KToolBar::Private { public: Private(KToolBar *qq) : q(qq), isMainToolBar(false), #ifndef KXMLGUI_NO_DEPRECATED enableContext(true), #endif unlockedMovable(true), contextOrient(nullptr), contextMode(nullptr), contextSize(nullptr), contextButtonTitle(nullptr), contextShowText(nullptr), contextButtonAction(nullptr), contextTop(nullptr), contextLeft(nullptr), contextRight(nullptr), contextBottom(nullptr), contextIcons(nullptr), contextTextRight(nullptr), contextText(nullptr), contextTextUnder(nullptr), contextLockAction(nullptr), dropIndicatorAction(nullptr), context(nullptr), dragAction(nullptr) { } void slotAppearanceChanged(); void slotContextAboutToShow(); void slotContextAboutToHide(); void slotContextLeft(); void slotContextRight(); void slotContextShowText(); void slotContextTop(); void slotContextBottom(); void slotContextIcons(); void slotContextText(); void slotContextTextRight(); void slotContextTextUnder(); void slotContextIconSize(); void slotLockToolBars(bool lock); void init(bool readConfig = true, bool isMainToolBar = false); QString getPositionAsString() const; QMenu *contextMenu(const QPoint &globalPos); void setLocked(bool locked); void adjustSeparatorVisibility(); void loadKDESettings(); void applyCurrentSettings(); QAction *findAction(const QString &actionName, KXMLGUIClient **client = nullptr) const; static Qt::ToolButtonStyle toolButtonStyleFromString(const QString &style); static QString toolButtonStyleToString(Qt::ToolButtonStyle); static Qt::ToolBarArea positionFromString(const QString &position); static Qt::ToolButtonStyle toolButtonStyleSetting(); KToolBar *q; bool isMainToolBar : 1; #ifndef KXMLGUI_NO_DEPRECATED bool enableContext : 1; #endif bool unlockedMovable : 1; static bool s_editable; static bool s_locked; QSet xmlguiClients; QMenu *contextOrient; QMenu *contextMode; QMenu *contextSize; QAction *contextButtonTitle; QAction *contextShowText; QAction *contextButtonAction; QAction *contextTop; QAction *contextLeft; QAction *contextRight; QAction *contextBottom; QAction *contextIcons; QAction *contextTextRight; QAction *contextText; QAction *contextTextUnder; KToggleAction *contextLockAction; QMap contextIconSizes; class IntSetting { public: IntSetting() { for (int level = 0; level < NSettingLevels; ++level) { values[level] = Unset; } } int currentValue() const { int val = Unset; for (int level = 0; level < NSettingLevels; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } // Default value as far as the user is concerned is kde-global + app-xml. // If currentValue()==defaultValue() then nothing to write into kconfig. int defaultValue() const { int val = Unset; for (int level = 0; level < Level_UserSettings; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } QString toString() const { QString str; for (int level = 0; level < NSettingLevels; ++level) { str += QString::number(values[level]) + QLatin1Char(' '); } return str; } int &operator[](int index) { return values[index]; } private: int values[NSettingLevels]; }; IntSetting iconSizeSettings; IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int". QList actionsBeingDragged; QAction *dropIndicatorAction; QMenu *context; QAction *dragAction; QPoint dragStartPosition; }; bool KToolBar::Private::s_editable = false; bool KToolBar::Private::s_locked = true; void KToolBar::Private::init(bool readConfig, bool _isMainToolBar) { isMainToolBar = _isMainToolBar; loadKDESettings(); // also read in our configurable settings (for non-xmlgui toolbars) if (readConfig) { KConfigGroup cg(KSharedConfig::openConfig(), QString()); q->applySettings(cg); } if (q->mainWindow()) { // Get notified when settings change connect(q, &QToolBar::allowedAreasChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::iconSizeChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::toolButtonStyleChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::movableChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::orientationChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); } if (!KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { q->setMovable(false); } else { q->setMovable(!KToolBar::toolBarsLocked()); } q->toggleViewAction()->setEnabled(KAuthorized::authorizeAction(QStringLiteral("options_show_toolbar"))); connect(q, &QToolBar::movableChanged, q, &KToolBar::slotMovableChanged); q->setAcceptDrops(true); #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), q, SLOT(slotAppearanceChanged())); #endif connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()), q, SLOT(slotAppearanceChanged())); } QString KToolBar::Private::getPositionAsString() const { // get all of the stuff to save switch (q->mainWindow()->toolBarArea(const_cast(q))) { case Qt::BottomToolBarArea: return QStringLiteral("Bottom"); case Qt::LeftToolBarArea: return QStringLiteral("Left"); case Qt::RightToolBarArea: return QStringLiteral("Right"); case Qt::TopToolBarArea: default: return QStringLiteral("Top"); } } QMenu *KToolBar::Private::contextMenu(const QPoint &globalPos) { if (!context) { context = new QMenu(q); contextButtonTitle = context->addSection(i18nc("@title:menu", "Show Text")); contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText())); context->addSection(i18nc("@title:menu", "Toolbar Settings")); contextOrient = new QMenu(i18nc("Toolbar orientation", "Orientation"), context); contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop())); contextTop->setChecked(true); contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft())); contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight())); contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom())); QActionGroup *positionGroup = new QActionGroup(contextOrient); Q_FOREACH (QAction *action, contextOrient->actions()) { action->setActionGroup(positionGroup); action->setCheckable(true); } contextMode = new QMenu(i18n("Text Position"), context); contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons())); contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText())); contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight())); contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder())); QActionGroup *textGroup = new QActionGroup(contextMode); Q_FOREACH (QAction *action, contextMode->actions()) { action->setActionGroup(textGroup); action->setCheckable(true); } contextSize = new QMenu(i18n("Icon Size"), context); contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())), iconSizeSettings.defaultValue()); // Query the current theme for available sizes KIconTheme *theme = KIconLoader::global()->theme(); QList avSizes; if (theme) { avSizes = theme->querySizes(isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar); } std::sort(avSizes.begin(), avSizes.end()); if (avSizes.count() < 10) { // Fixed or threshold type icons Q_FOREACH (int it, avSizes) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); } } else { // Scalable icons. const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 }; for (uint i = 0; i < 9; i++) { Q_FOREACH (int it, avSizes) { if (it >= progression[ i ]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); break; } } } } QActionGroup *sizeGroup = new QActionGroup(contextSize); Q_FOREACH (QAction *action, contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } if (!q->toolBarsLocked() && !q->isMovable()) { unlockedMovable = false; } delete contextLockAction; contextLockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("system-lock-screen")), i18n("Lock Toolbar Positions"), q); contextLockAction->setChecked(q->toolBarsLocked()); connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool))); // Now add the actions to the menu context->addMenu(contextMode); context->addMenu(contextSize); context->addMenu(contextOrient); context->addSeparator(); connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow())); } contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos)); if (contextButtonAction) { contextShowText->setText(contextButtonAction->text()); contextShowText->setIcon(contextButtonAction->icon()); contextShowText->setCheckable(true); } contextOrient->menuAction()->setVisible(!q->toolBarsLocked()); // Unplugging a submenu from abouttohide leads to the popupmenu floating around // So better simply call that code from after exec() returns (DF) //connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide())); return context; } void KToolBar::Private::setLocked(bool locked) { if (unlockedMovable) { q->setMovable(!locked); } } void KToolBar::Private::adjustSeparatorVisibility() { bool visibleNonSeparator = false; int separatorToShow = -1; for (int index = 0; index < q->actions().count(); ++index) { QAction *action = q->actions().at(index); if (action->isSeparator()) { if (visibleNonSeparator) { separatorToShow = index; visibleNonSeparator = false; } else { action->setVisible(false); } } else if (!visibleNonSeparator) { if (action->isVisible()) { visibleNonSeparator = true; if (separatorToShow != -1) { q->actions().at(separatorToShow)->setVisible(true); separatorToShow = -1; } } } } if (separatorToShow != -1) { q->actions().at(separatorToShow)->setVisible(false); } } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString &_style) { QString style = _style.toLower(); if (style == QStringLiteral("textbesideicon") || style == QLatin1String("icontextright")) { return Qt::ToolButtonTextBesideIcon; } else if (style == QStringLiteral("textundericon") || style == QLatin1String("icontextbottom")) { return Qt::ToolButtonTextUnderIcon; } else if (style == QStringLiteral("textonly")) { return Qt::ToolButtonTextOnly; } else { return Qt::ToolButtonIconOnly; } } QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style) { switch (style) { case Qt::ToolButtonIconOnly: default: return QStringLiteral("IconOnly"); case Qt::ToolButtonTextBesideIcon: return QStringLiteral("TextBesideIcon"); case Qt::ToolButtonTextOnly: return QStringLiteral("TextOnly"); case Qt::ToolButtonTextUnderIcon: return QStringLiteral("TextUnderIcon"); } } Qt::ToolBarArea KToolBar::Private::positionFromString(const QString &position) { Qt::ToolBarArea newposition = Qt::TopToolBarArea; if (position == QStringLiteral("left")) { newposition = Qt::LeftToolBarArea; } else if (position == QStringLiteral("bottom")) { newposition = Qt::BottomToolBarArea; } else if (position == QStringLiteral("right")) { newposition = Qt::RightToolBarArea; } return newposition; } // Global setting was changed void KToolBar::Private::slotAppearanceChanged() { loadKDESettings(); applyCurrentSettings(); } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleSetting() { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString fallback = KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback)); } void KToolBar::Private::loadKDESettings() { iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault(); if (isMainToolBar) { toolButtonStyleSettings[Level_KDEDefault] = toolButtonStyleSetting(); } else { const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); /** TODO: if we get complaints about text beside icons on small screens, try the following code out on such systems - aseigo. // if we are on a small screen with a non-landscape ratio, then // we revert to text under icons since width is probably not our // friend in such cases QDesktopWidget *desktop = QApplication::desktop(); QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen()); qreal ratio = screenGeom.width() / qreal(screenGeom.height()); if (screenGeom.width() < 1024 && ratio <= 1.4) { fallBack = "TextUnderIcon"; } **/ KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack); toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value); } } // Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings void KToolBar::Private::applyCurrentSettings() { //qCDebug(DEBUG_KXMLGUI) << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue(); const int currentIconSize = iconSizeSettings.currentValue(); q->setIconSize(QSize(currentIconSize, currentIconSize)); //qCDebug(DEBUG_KXMLGUI) << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue(); q->setToolButtonStyle(static_cast(toolButtonStyleSettings.currentValue())); // And remember to save the new look later KMainWindow *kmw = q->mainWindow(); if (kmw) { kmw->setSettingsDirty(); } } QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const { Q_FOREACH (KXMLGUIClient *client, xmlguiClients) { QAction *action = client->actionCollection()->action(actionName); if (action) { if (clientOut) { *clientOut = client; } return action; } } return nullptr; } void KToolBar::Private::slotContextAboutToShow() { /** * The idea here is to reuse the "static" part of the menu to save time. * But the "Toolbars" action is dynamic (can be a single action or a submenu) * and ToolBarHandler::setupActions() deletes it, so better not keep it around. * So we currently plug/unplug the last two actions of the menu. * Another way would be to keep around the actions and plug them all into a (new each time) popupmenu. */ KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); // try to find "configure toolbars" action QAction *configureAction = nullptr; const char *actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->addAction(configureAction); } context->addAction(contextLockAction); if (kmw) { kmw->setupToolbarMenuActions(); // Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar) QAction *tbAction = kmw->toolBarMenuAction(); if (!q->toolBarsLocked() && tbAction && !tbAction->associatedWidgets().isEmpty()) { context->addAction(tbAction); } } KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData()); // Check the actions that should be checked switch (q->toolButtonStyle()) { case Qt::ToolButtonIconOnly: default: contextIcons->setChecked(true); break; case Qt::ToolButtonTextBesideIcon: contextTextRight->setChecked(true); break; case Qt::ToolButtonTextOnly: contextText->setChecked(true); break; case Qt::ToolButtonTextUnderIcon: contextTextUnder->setChecked(true); break; } QMapIterator< QAction *, int > it = contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == q->iconSize().width()) { it.key()->setChecked(true); break; } } switch (q->mainWindow()->toolBarArea(q)) { case Qt::BottomToolBarArea: contextBottom->setChecked(true); break; case Qt::LeftToolBarArea: contextLeft->setChecked(true); break; case Qt::RightToolBarArea: contextRight->setChecked(true); break; default: case Qt::TopToolBarArea: contextTop->setChecked(true); break; } const bool showButtonSettings = contextButtonAction && !contextShowText->text().isEmpty() && contextTextRight->isChecked(); contextButtonTitle->setVisible(showButtonSettings); contextShowText->setVisible(showButtonSettings); if (showButtonSettings) { contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority); } } void KToolBar::Private::slotContextAboutToHide() { // We have to unplug whatever slotContextAboutToShow plugged into the menu. // Unplug the toolbar menu action KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); if (kmw && kmw->toolBarMenuAction()) { if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) { context->removeAction(kmw->toolBarMenuAction()); } } // Unplug the configure toolbars action too, since it's afterwards anyway QAction *configureAction = nullptr; const char *actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->removeAction(configureAction); } context->removeAction(contextLockAction); } void KToolBar::Private::slotContextLeft() { q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q); } void KToolBar::Private::slotContextRight() { q->mainWindow()->addToolBar(Qt::RightToolBarArea, q); } void KToolBar::Private::slotContextShowText() { Q_ASSERT(contextButtonAction); const QAction::Priority priority = contextShowText->isChecked() ? QAction::NormalPriority : QAction::LowPriority; contextButtonAction->setPriority(priority); // Find to which xml file and componentData the action belongs to QString componentName; QString filename; KXMLGUIClient *client; if (findAction(contextButtonAction->objectName(), &client)) { componentName = client->componentName(); filename = client->xmlFile(); } if (filename.isEmpty()) { componentName = QCoreApplication::applicationName(); filename = componentName + QStringLiteral("ui.rc"); } // Save the priority state of the action const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentName); QDomDocument document; document.setContent(configFile); QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document); QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true); actionElem.setAttribute(QStringLiteral("priority"), priority); KXMLGUIFactory::saveConfigFile(document, filename, componentName); } void KToolBar::Private::slotContextTop() { q->mainWindow()->addToolBar(Qt::TopToolBarArea, q); } void KToolBar::Private::slotContextBottom() { q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q); } void KToolBar::Private::slotContextIcons() { q->setToolButtonStyle(Qt::ToolButtonIconOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextText() { q->setToolButtonStyle(Qt::ToolButtonTextOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextUnder() { q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextRight() { q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextIconSize() { QAction *action = qobject_cast(q->sender()); if (action && contextIconSizes.contains(action)) { const int iconSize = contextIconSizes.value(action); q->setIconDimensions(iconSize); } } void KToolBar::Private::slotLockToolBars(bool lock) { q->setToolBarsLocked(lock); } KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { d->init(readConfig, isMainToolBar); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QWidget *parent, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); // mainToolBar -> isMainToolBar = true -> buttonStyle is configurable // others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added d->init(readConfig, objectName == QStringLiteral("mainToolBar")); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QMainWindow *parent, Qt::ToolBarArea area, bool newLine, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); d->init(readConfig, isMainToolBar); if (newLine) { mainWindow()->addToolBarBreak(area); } mainWindow()->addToolBar(area, this); if (newLine) { mainWindow()->addToolBarBreak(area); } } KToolBar::~KToolBar() { delete d->contextLockAction; delete d; } #ifndef KXMLGUI_NO_DEPRECATED void KToolBar::setContextMenuEnabled(bool enable) { d->enableContext = enable; } #endif #ifndef KXMLGUI_NO_DEPRECATED bool KToolBar::contextMenuEnabled() const { return d->enableContext; } #endif void KToolBar::saveSettings(KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); const int currentIconSize = iconSize().width(); //qCDebug(DEBUG_KXMLGUI) << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue(); if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) { cg.revertToDefault("IconSize"); d->iconSizeSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("IconSize", currentIconSize); d->iconSizeSettings[Level_UserSettings] = currentIconSize; } const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle(); if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) { cg.revertToDefault("ToolButtonStyle"); d->toolButtonStyleSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle)); d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle; } } #ifndef KXMLGUI_NO_DEPRECATED void KToolBar::setXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.clear(); d->xmlguiClients << client; } #endif void KToolBar::addXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients << client; } void KToolBar::removeXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.remove(client); } void KToolBar::contextMenuEvent(QContextMenuEvent *event) { #ifndef KXMLGUI_NO_DEPRECATED if (mainWindow() && d->enableContext) { QPointer guard(this); const QPoint globalPos = event->globalPos(); d->contextMenu(globalPos)->exec(globalPos); // "Configure Toolbars" recreates toolbars, so we might not exist anymore. if (guard) { d->slotContextAboutToHide(); } return; } #endif QToolBar::contextMenuEvent(event); } void KToolBar::loadState(const QDomElement &element) { QMainWindow *mw = mainWindow(); if (!mw) { return; } { const QString& i18nText = KToolbarHelper::i18nToolBarName(element); if (!i18nText.isEmpty()) { setWindowTitle(i18nText); } } /* This method is called in order to load toolbar settings from XML. However this can be used in two rather different cases: - for the initial loading of the app's XML. In that case the settings are only the defaults (Level_AppXML), the user's KConfig settings will override them - for later re-loading when switching between parts in KXMLGUIFactory. In that case the XML contains the final settings, not the defaults. We do need the defaults, and the toolbar might have been completely deleted and recreated meanwhile. So we store the app-default settings into the XML. */ bool loadingAppDefaults = true; if (element.hasAttribute(QStringLiteral("tempXml"))) { // this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML loadingAppDefaults = false; const QString iconSizeDefault = element.attribute(QStringLiteral("iconSizeDefault")); if (!iconSizeDefault.isEmpty()) { d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt(); } const QString toolButtonStyleDefault = element.attribute(QStringLiteral("toolButtonStyleDefault")); if (!toolButtonStyleDefault.isEmpty()) { d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault); } } else { // loading app defaults bool newLine = false; QString attrNewLine = element.attribute(QStringLiteral("newline")).toLower(); if (!attrNewLine.isEmpty()) { newLine = attrNewLine == QStringLiteral("true"); } if (newLine && mw) { mw->insertToolBarBreak(this); } } int newIconSize = -1; if (element.hasAttribute(QStringLiteral("iconSize"))) { bool ok; newIconSize = element.attribute(QStringLiteral("iconSize")).trimmed().toInt(&ok); if (!ok) { newIconSize = -1; } } if (newIconSize != -1) { d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize; } const QString newToolButtonStyle = element.attribute(QStringLiteral("iconText")); if (!newToolButtonStyle.isEmpty()) { d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle); } bool hidden = false; { QString attrHidden = element.attribute(QStringLiteral("hidden")).toLower(); if (!attrHidden.isEmpty()) { hidden = attrHidden == QStringLiteral("true"); } } Qt::ToolBarArea pos = Qt::NoToolBarArea; { QString attrPosition = element.attribute(QStringLiteral("position")).toLower(); if (!attrPosition.isEmpty()) { pos = KToolBar::Private::positionFromString(attrPosition); } } if (pos != Qt::NoToolBarArea) { mw->addToolBar(pos, this); } setVisible(!hidden); d->applyCurrentSettings(); } // Called when switching between xmlgui clients, in order to find any unsaved settings // again when switching back to the current xmlgui client. void KToolBar::saveState(QDomElement ¤t) const { Q_ASSERT(!current.isNull()); current.setAttribute(QStringLiteral("tempXml"), QStringLiteral("true")); current.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); current.setAttribute(QStringLiteral("position"), d->getPositionAsString().toLower()); current.setAttribute(QStringLiteral("hidden"), isHidden() ? QStringLiteral("true") : QStringLiteral("false")); const int currentIconSize = iconSize().width(); if (currentIconSize == d->iconSizeSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconSize")); } else { current.setAttribute(QStringLiteral("iconSize"), iconSize().width()); } if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconText")); } else { current.setAttribute(QStringLiteral("iconText"), d->toolButtonStyleToString(toolButtonStyle())); } // Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*, // then the stuff below shouldn't always be done. This is not the case currently though. if (d->iconSizeSettings[Level_AppXML] != Unset) { current.setAttribute(QStringLiteral("iconSizeDefault"), d->iconSizeSettings[Level_AppXML]); } if (d->toolButtonStyleSettings[Level_AppXML] != Unset) { const Qt::ToolButtonStyle bs = static_cast(d->toolButtonStyleSettings[Level_AppXML]); current.setAttribute(QStringLiteral("toolButtonStyleDefault"), d->toolButtonStyleToString(bs)); } } // called by KMainWindow::applyMainWindowSettings to read from the user settings void KToolBar::applySettings(const KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); if (cg.hasKey("IconSize")) { d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0); } if (cg.hasKey("ToolButtonStyle")) { d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString())); } d->applyCurrentSettings(); } KMainWindow *KToolBar::mainWindow() const { return qobject_cast(const_cast(parent())); } void KToolBar::setIconDimensions(int size) { QToolBar::setIconSize(QSize(size, size)); d->iconSizeSettings[Level_UserSettings] = size; } int KToolBar::iconSizeDefault() const { return KIconLoader::global()->currentSize(d->isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar); } void KToolBar::slotMovableChanged(bool movable) { if (movable && !KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { setMovable(false); } } void KToolBar::dragEnterEvent(QDragEnterEvent *event) { if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) && event->mimeData()->hasFormat(QStringLiteral("application/x-kde-action-list"))) { QByteArray data = event->mimeData()->data(QStringLiteral("application/x-kde-action-list")); QDataStream stream(data); QStringList actionNames; stream >> actionNames; Q_FOREACH (const QString &actionName, actionNames) { Q_FOREACH (KActionCollection *ac, KActionCollection::allCollections()) { QAction *newAction = ac->action(actionName); if (newAction) { d->actionsBeingDragged.append(newAction); break; } } } if (!d->actionsBeingDragged.isEmpty()) { QAction *overAction = actionAt(event->pos()); QFrame *dropIndicatorWidget = new QFrame(this); dropIndicatorWidget->resize(8, height() - 4); dropIndicatorWidget->setFrameShape(QFrame::VLine); dropIndicatorWidget->setLineWidth(3); d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget); insertAction(overAction, d->dropIndicatorAction); event->acceptProposedAction(); return; } } QToolBar::dragEnterEvent(event); } void KToolBar::dragMoveEvent(QDragMoveEvent *event) { if (toolBarsEditable()) Q_FOREVER { if (d->dropIndicatorAction) { QAction *overAction = nullptr; Q_FOREACH (QAction *action, actions()) { // want to make it feel that half way across an action you're dropping on the other side of it QWidget *widget = widgetForAction(action); if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) { overAction = action; break; } } if (overAction != d->dropIndicatorAction) { // Check to see if the indicator is already in the right spot int dropIndicatorIndex = actions().indexOf(d->dropIndicatorAction); if (dropIndicatorIndex + 1 < actions().count()) { if (actions().at(dropIndicatorIndex + 1) == overAction) { break; } } else if (!overAction) { break; } insertAction(overAction, d->dropIndicatorAction); } event->accept(); return; } break; } QToolBar::dragMoveEvent(event); } void KToolBar::dragLeaveEvent(QDragLeaveEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = nullptr; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dragLeaveEvent(event); } void KToolBar::dropEvent(QDropEvent *event) { if (toolBarsEditable()) { Q_FOREACH (QAction *action, d->actionsBeingDragged) { if (actions().contains(action)) { removeAction(action); } insertAction(d->dropIndicatorAction, action); } } // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = nullptr; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dropEvent(event); } void KToolBar::mousePressEvent(QMouseEvent *event) { if (toolBarsEditable() && event->button() == Qt::LeftButton) { if (QAction *action = actionAt(event->pos())) { d->dragAction = action; d->dragStartPosition = event->pos(); event->accept(); return; } } QToolBar::mousePressEvent(event); } void KToolBar::mouseMoveEvent(QMouseEvent *event) { if (!toolBarsEditable() || !d->dragAction) { QToolBar::mouseMoveEvent(event); return; } if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->accept(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); QStringList actionNames; actionNames << d->dragAction->objectName(); stream << actionNames; } mimeData->setData(QStringLiteral("application/x-kde-action-list"), data); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->start(Qt::MoveAction); if (dropAction == Qt::MoveAction) // Only remove from this toolbar if it was moved to another toolbar // Otherwise the receiver moves it. if (drag->target() != this) { removeAction(d->dragAction); } d->dragAction = nullptr; event->accept(); } void KToolBar::mouseReleaseEvent(QMouseEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) if (d->dragAction) { d->dragAction = nullptr; event->accept(); return; } QToolBar::mouseReleaseEvent(event); } bool KToolBar::eventFilter(QObject *watched, QEvent *event) { // Generate context menu events for disabled buttons too... if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::RightButton) if (QWidget *ww = qobject_cast(watched)) if (ww->parent() == this && !ww->isEnabled()) { QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos())); } } else if (event->type() == QEvent::ParentChange) { // Make sure we're not leaving stale event filters around, // when a child is reparented somewhere else if (QWidget *ww = qobject_cast(watched)) { if (!this->isAncestorOf(ww)) { // New parent is not a subwidget - remove event filter ww->removeEventFilter(this); Q_FOREACH (QWidget *child, ww->findChildren()) { child->removeEventFilter(this); } } } } // Redirect mouse events to the toolbar when drag + drop editing is enabled if (toolBarsEditable()) { if (QWidget *ww = qobject_cast(watched)) { switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mousePressEvent(&newEvent); return true; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseMoveEvent(&newEvent); return true; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseReleaseEvent(&newEvent); return true; } default: break; } } } return QToolBar::eventFilter(watched, event); } void KToolBar::actionEvent(QActionEvent *event) { if (event->type() == QEvent::ActionRemoved) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->removeEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->removeEventFilter(this); } } } QToolBar::actionEvent(event); if (event->type() == QEvent::ActionAdded) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->installEventFilter(this); Q_FOREACH (QWidget *child, widget->findChildren()) { child->installEventFilter(this); } // Center widgets that do not have any use for more space. See bug 165274 if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag) // ... but do not center when using text besides icon in vertical toolbar. See bug 243196 && !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) { const int index = layout()->indexOf(widget); if (index != -1) { layout()->itemAt(index)->setAlignment(Qt::AlignJustify); } } } } d->adjustSeparatorVisibility(); } bool KToolBar::toolBarsEditable() { return KToolBar::Private::s_editable; } void KToolBar::setToolBarsEditable(bool editable) { if (KToolBar::Private::s_editable != editable) { KToolBar::Private::s_editable = editable; } } void KToolBar::setToolBarsLocked(bool locked) { if (KToolBar::Private::s_locked != locked) { KToolBar::Private::s_locked = locked; Q_FOREACH (KMainWindow *mw, KMainWindow::memberList()) { Q_FOREACH (KToolBar *toolbar, mw->findChildren()) { toolbar->d->setLocked(locked); } } } } bool KToolBar::toolBarsLocked() { return KToolBar::Private::s_locked; } void KToolBar::emitToolbarStyleChanged() { #ifdef QT_DBUS_LIB QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged")); QDBusConnection::sessionBus().send(message); #endif } #include "moc_ktoolbar.cpp" diff --git a/src/ktoolbarhandler.cpp b/src/ktoolbarhandler.cpp index c43c519..df0a8c4 100644 --- a/src/ktoolbarhandler.cpp +++ b/src/ktoolbarhandler.cpp @@ -1,273 +1,275 @@ /* This file is part of the KDE libraries Copyright (C) 2002 Simon Hausmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "ktoolbarhandler_p.h" #include #include #include +#include +#include #include #include #include #include "kxmlguiwindow.h" #include "ktoggletoolbaraction.h" #include "ktoolbar.h" #include "kxmlguifactory.h" #include "kactioncollection.h" namespace { const char actionListName[] = "show_menu_and_toolbar_actionlist"; const char guiDescription[] = "" "" "" " " " " " " "" ""; class BarActionBuilder { public: BarActionBuilder(KActionCollection *actionCollection, KXmlGuiWindow *mainWindow, QLinkedList &oldToolBarList) : m_actionCollection(actionCollection), m_mainWindow(mainWindow), m_needsRebuild(false) { QList toolBars = m_mainWindow->findChildren(); Q_FOREACH (KToolBar *toolBar, toolBars) { if (toolBar->mainWindow() != m_mainWindow) { continue; } if (!oldToolBarList.contains(toolBar)) { m_needsRebuild = true; } m_toolBars.append(toolBar); } if (!m_needsRebuild) { m_needsRebuild = (oldToolBarList.count() != m_toolBars.count()); } } bool needsRebuild() const { return m_needsRebuild; } QList create() { QList actions; if (!m_needsRebuild) { return actions; } Q_FOREACH (KToolBar *bar, m_toolBars) { handleToolBar(bar); } if (m_toolBarActions.count() == 0) { return actions; } if (m_toolBarActions.count() == 1) { KToggleToolBarAction *action = static_cast(m_toolBarActions.first()); action->setText(KStandardShortcut::label(KStandardShortcut::ShowToolbar)); return m_toolBarActions; } KActionMenu *menuAction = new KActionMenu(i18n("Toolbars Shown"), m_actionCollection); m_actionCollection->addAction(QStringLiteral("toolbars_submenu_action"), menuAction); Q_FOREACH (QAction *action, m_toolBarActions) { menuAction->menu()->addAction(action); } actions.append(menuAction); return actions; } const QLinkedList &toolBars() const { return m_toolBars; } private: void handleToolBar(KToolBar *toolBar) { KToggleToolBarAction *action = new KToggleToolBarAction( toolBar, toolBar->windowTitle(), m_actionCollection); m_actionCollection->addAction(toolBar->objectName(), action); // ## tooltips, whatsthis? m_toolBarActions.append(action); } KActionCollection *m_actionCollection; KXmlGuiWindow *m_mainWindow; QLinkedList m_toolBars; QList m_toolBarActions; bool m_needsRebuild : 1; }; } using namespace KDEPrivate; class Q_DECL_HIDDEN ToolBarHandler::Private { public: Private(ToolBarHandler *_parent) : parent(_parent) { } void clientAdded(KXMLGUIClient *client) { Q_UNUSED(client) parent->setupActions(); } void init(KXmlGuiWindow *mainWindow); void connectToActionContainers(); void connectToActionContainer(QAction *action); void connectToActionContainer(QWidget *container); ToolBarHandler *parent; QPointer mainWindow; QList actions; QLinkedList toolBars; }; void ToolBarHandler::Private::init(KXmlGuiWindow *mw) { mainWindow = mw; QObject::connect(mainWindow->guiFactory(), &KXMLGUIFactory::clientAdded, parent, &ToolBarHandler::clientAdded); if (parent->domDocument().documentElement().isNull()) { QString completeDescription = QString::fromLatin1(guiDescription) .arg(QLatin1String(actionListName)); parent->setXML(completeDescription, false /*merge*/); } } void ToolBarHandler::Private::connectToActionContainers() { Q_FOREACH (QAction *action, actions) { connectToActionContainer(action); } } void ToolBarHandler::Private::connectToActionContainer(QAction *action) { int containerCount = action->associatedWidgets().count(); for (int i = 0; i < containerCount; ++i) { connectToActionContainer(action->associatedWidgets().value(i)); } } void ToolBarHandler::Private::connectToActionContainer(QWidget *container) { QMenu *popupMenu = qobject_cast(container); if (!popupMenu) { return; } connect(popupMenu, &QMenu::aboutToShow, parent, &ToolBarHandler::setupActions); } ToolBarHandler::ToolBarHandler(KXmlGuiWindow *mainWindow) : QObject(mainWindow), KXMLGUIClient(mainWindow), d(new Private(this)) { d->init(mainWindow); } ToolBarHandler::ToolBarHandler(KXmlGuiWindow *mainWindow, QObject *parent) : QObject(parent), KXMLGUIClient(mainWindow), d(new Private(this)) { d->init(mainWindow); } ToolBarHandler::~ToolBarHandler() { qDeleteAll(d->actions); d->actions.clear(); delete d; } QAction *ToolBarHandler::toolBarMenuAction() { Q_ASSERT(d->actions.count() == 1); return d->actions.first(); } void ToolBarHandler::setupActions() { if (!factory() || !d->mainWindow) { return; } BarActionBuilder builder(actionCollection(), d->mainWindow, d->toolBars); if (!builder.needsRebuild()) { return; } unplugActionList(QLatin1String(actionListName)); qDeleteAll(d->actions); d->actions.clear(); d->actions = builder.create(); d->toolBars = builder.toolBars(); // We have no XML file associated with our action collection, so load settings from KConfig actionCollection()->readSettings(); // #233712 if (KAuthorized::authorizeAction(QStringLiteral("options_show_toolbar"))) { plugActionList(QLatin1String(actionListName), d->actions); } d->connectToActionContainers(); } void ToolBarHandler::clientAdded(KXMLGUIClient* client) { d->clientAdded(client); } diff --git a/src/ktoolbarhandler_p.h b/src/ktoolbarhandler_p.h index e934e4c..c3edc4c 100644 --- a/src/ktoolbarhandler_p.h +++ b/src/ktoolbarhandler_p.h @@ -1,74 +1,72 @@ /* This file is part of the KDE libraries Copyright (C) 2002 Simon Hausmann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KTOOLBARHANDLER_H #define KTOOLBARHANDLER_H -#include #include -#include #include class KXmlGuiWindow; namespace KDEPrivate { class ToolBarHandler : public QObject, public KXMLGUIClient { Q_OBJECT public: /** * Creates a new tool bar handler for the supplied * @param mainWindow. */ explicit ToolBarHandler(KXmlGuiWindow *mainWindow); /** * Creates a new tool bar handler for the supplied * @param mainWindow and with the supplied parent. */ ToolBarHandler(KXmlGuiWindow *mainWindow, QObject *parent); /** * Destroys the tool bar handler. */ virtual ~ToolBarHandler(); /** * Returns the action which is responsible for the tool bar menu. */ QAction *toolBarMenuAction(); public Q_SLOTS: void setupActions(); private Q_SLOTS: void clientAdded(KXMLGUIClient *client); private: class Private; Private *const d; }; } // namespace KDEPrivate #endif // KTOOLBARHANDLER_H diff --git a/src/kundoactions.cpp b/src/kundoactions.cpp index 9c01e56..58c4cb4 100644 --- a/src/kundoactions.cpp +++ b/src/kundoactions.cpp @@ -1,68 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2006 Peter Simonsson 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 "kundoactions.h" #include -#include -#include #include #include #include #include #include QAction *KUndoActions::createRedoAction(QUndoStack *undoStack, KActionCollection *actionCollection, const QString &actionName) { QAction *action = undoStack->createRedoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(QLatin1String(KStandardAction::name(KStandardAction::Redo))); } else { action->setObjectName(actionName); } action->setIcon(QIcon::fromTheme(QStringLiteral("edit-redo"))); action->setIconText(i18n("Redo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo()); actionCollection->addAction(action->objectName(), action); return action; } QAction *KUndoActions::createUndoAction(QUndoStack *undoStack, KActionCollection *actionCollection, const QString &actionName) { QAction *action = undoStack->createUndoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(QLatin1String(KStandardAction::name(KStandardAction::Undo))); } else { action->setObjectName(actionName); } action->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); action->setIconText(i18n("Undo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo()); actionCollection->addAction(action->objectName(), action); return action; } diff --git a/src/kxmlguiclient.cpp b/src/kxmlguiclient.cpp index 0ea6419..6373b7a 100644 --- a/src/kxmlguiclient.cpp +++ b/src/kxmlguiclient.cpp @@ -1,813 +1,811 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Simon Hausmann Copyright (C) 2000 Kurt Granroth This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kxmlguiclient.h" #include "kxmlguiversionhandler_p.h" #include "kxmlguifactory.h" #include "kxmlguibuilder.h" #include "kactioncollection.h" #include "debug.h" #include #include #include #include -#include -#include #include #include #include #include #include #include class KXMLGUIClientPrivate { public: KXMLGUIClientPrivate() : m_componentName(QCoreApplication::applicationName()), m_actionCollection(nullptr), m_parent(nullptr), m_builder(nullptr) { m_textTagNames.append(QStringLiteral("text")); m_textTagNames.append(QStringLiteral("Text")); m_textTagNames.append(QStringLiteral("title")); } ~KXMLGUIClientPrivate() { } bool mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection); bool isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const; QDomElement findMatchingElement(const QDomElement &base, const QDomElement &additive); QString m_componentName; QDomDocument m_doc; KActionCollection *m_actionCollection; QDomDocument m_buildDocument; QPointer m_factory; KXMLGUIClient *m_parent; //QPtrList m_supers; QList m_children; KXMLGUIBuilder *m_builder; QString m_xmlFile; QString m_localXMLFile; QStringList m_textTagNames; // Actions to enable/disable on a state change QMap m_actionsStateMap; }; KXMLGUIClient::KXMLGUIClient() : d(new KXMLGUIClientPrivate) { } KXMLGUIClient::KXMLGUIClient(KXMLGUIClient *parent) : d(new KXMLGUIClientPrivate) { parent->insertChildClient(this); } KXMLGUIClient::~KXMLGUIClient() { if (d->m_parent) { d->m_parent->removeChildClient(this); } if (d->m_factory) { qCWarning(DEBUG_KXMLGUI) << this << "deleted without having been removed from the factory first. This will leak standalone popupmenus and could lead to crashes."; d->m_factory->forgetClient(this); } Q_FOREACH (KXMLGUIClient *client, d->m_children) { if (d->m_factory) { d->m_factory->forgetClient(client); } assert(client->d->m_parent == this); client->d->m_parent = nullptr; } delete d->m_actionCollection; delete d; } QAction *KXMLGUIClient::action(const char *name) const { QAction *act = actionCollection()->action(QLatin1String(name)); if (!act) { Q_FOREACH (KXMLGUIClient *client, d->m_children) { act = client->actionCollection()->action(QLatin1String(name)); if (act) { break; } } } return act; } KActionCollection *KXMLGUIClient::actionCollection() const { if (!d->m_actionCollection) { d->m_actionCollection = new KActionCollection(this); d->m_actionCollection->setObjectName(QStringLiteral("KXMLGUIClient-KActionCollection")); } return d->m_actionCollection; } QAction *KXMLGUIClient::action(const QDomElement &element) const { return actionCollection()->action(element.attribute(QStringLiteral("name"))); } QString KXMLGUIClient::componentName() const { return d->m_componentName; } QDomDocument KXMLGUIClient::domDocument() const { return d->m_doc; } QString KXMLGUIClient::xmlFile() const { return d->m_xmlFile; } QString KXMLGUIClient::localXMLFile() const { if (!d->m_localXMLFile.isEmpty()) { return d->m_localXMLFile; } if (!QDir::isRelativePath(d->m_xmlFile)) { return QString(); // can't save anything here } if (d->m_xmlFile.isEmpty()) { // setXMLFile not called at all, can't save. Use case: ToolBarHandler return QString(); } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kxmlgui5/") + componentName() + QLatin1Char('/') + d->m_xmlFile; } void KXMLGUIClient::reloadXML() { // TODO: this method can't be used for the KXmlGuiWindow, since it doesn't merge in ui_standards.rc! // -> KDE5: load ui_standards_rc in setXMLFile using a flag, and remember that flag? // and then KEditToolBar can use reloadXML. QString file(xmlFile()); if (!file.isEmpty()) { setXMLFile(file); } } void KXMLGUIClient::setComponentName(const QString &componentName, const QString &componentDisplayName) { d->m_componentName = componentName; actionCollection()->setComponentName(componentName); actionCollection()->setComponentDisplayName(componentDisplayName); if (d->m_builder) { d->m_builder->setBuilderClient(this); } } QString KXMLGUIClient::standardsXmlFileLocation() { QString file = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("ui/ui_standards.rc")); if (file.isEmpty()) { // fallback to resource, to allow to use the rc file compiled into this framework, must exist! file = QStringLiteral(":/kxmlgui5/ui_standards.rc"); Q_ASSERT(QFile::exists(file)); } return file; } void KXMLGUIClient::loadStandardsXmlFile() { setXML(KXMLGUIFactory::readConfigFile(standardsXmlFileLocation())); } void KXMLGUIClient::setXMLFile(const QString &_file, bool merge, bool setXMLDoc) { // store our xml file name if (!_file.isNull()) { d->m_xmlFile = _file; } if (!setXMLDoc) { return; } QString file = _file; QStringList allFiles; if (!QDir::isRelativePath(file)) { allFiles.append(file); } else { const QString filter = componentName() + QLatin1Char('/') + _file; // files on filesystem allFiles << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kxmlgui5/") + filter); // KF >= 5.1 // KF >= 5.4 (resource file) const QString qrcFile(QStringLiteral(":/kxmlgui5/") + filter); if (QFile::exists(qrcFile)) { allFiles << qrcFile; } // then compat locations const QStringList compatFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, filter) + // kdelibs4, KF 5.0 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, _file); // kdelibs4, KF 5.0, caller passes component name if (allFiles.isEmpty() && !compatFiles.isEmpty()) { qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << compatFiles << "-- please use ${KXMLGUI_INSTALL_DIR} to install this file instead."; } allFiles += compatFiles; } if (allFiles.isEmpty() && !_file.isEmpty()) { // if a non-empty file gets passed and we can't find it, // inform the developer using some debug output qCWarning(DEBUG_KXMLGUI) << "cannot find .rc file" << _file << "for component" << componentName(); } // make sure to merge the settings from any file specified by setLocalXMLFile() if (!d->m_localXMLFile.isEmpty() && !file.endsWith(QStringLiteral("ui_standards.rc"))) { const bool exists = QDir::isRelativePath(d->m_localXMLFile) || QFile::exists(d->m_localXMLFile); if (exists && !allFiles.contains(d->m_localXMLFile)) { allFiles.prepend(d->m_localXMLFile); } } QString doc; if (!allFiles.isEmpty()) { file = findMostRecentXMLFile(allFiles, doc); } // Always call setXML, even on error, so that we don't keep all ui_standards.rc menus. setXML(doc, merge); } void KXMLGUIClient::setLocalXMLFile(const QString &file) { d->m_localXMLFile = file; } void KXMLGUIClient::replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge) { if (!QDir::isAbsolutePath(xmlfile)) { qCWarning(DEBUG_KXMLGUI) << "xml file" << xmlfile << "is not an absolute path"; } setLocalXMLFile(localxmlfile); setXMLFile(xmlfile, merge); } // The top document element may have translation domain attribute set, // or the translation domain may be implicitly the application domain. // This domain must be used to fetch translations for all text elements // in the document that do not have their own domain attribute. // In order to preserve this semantics through document mergings, // the top or application domain must be propagated to all text elements // lacking their own domain attribute. static void propagateTranslationDomain(QDomDocument &doc, const QStringList &tagNames) { const QLatin1String attrDomain("translationDomain"); QDomElement base = doc.documentElement(); QString domain = base.attribute(attrDomain); if (domain.isEmpty()) { domain = QString::fromUtf8(KLocalizedString::applicationDomain()); if (domain.isEmpty()) { return; } } foreach (const QString &tagName, tagNames) { QDomNodeList textNodes = base.elementsByTagName(tagName); for (int i = 0; i < textNodes.length(); ++i) { QDomElement e = textNodes.item(i).toElement(); QString localDomain = e.attribute(attrDomain); if (localDomain.isEmpty()) { e.setAttribute(attrDomain, domain); } } } } void KXMLGUIClient::setXML(const QString &document, bool merge) { QDomDocument doc; QString errorMsg; int errorLine = 0, errorColumn = 0; // QDomDocument raises a parse error on empty document, but we accept no app-specific document, // in which case you only get ui_standards.rc layout. bool result = document.isEmpty() || doc.setContent(document, &errorMsg, &errorLine, &errorColumn); if (result) { propagateTranslationDomain(doc, d->m_textTagNames); setDOMDocument(doc, merge); } else { #ifdef NDEBUG qCritical() << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn; setDOMDocument(QDomDocument(), merge); // otherwise empty menus from ui_standards.rc stay around #else qCritical() << "Error parsing XML document:" << errorMsg << "at line" << errorLine << "column" << errorColumn; abort(); #endif } } void KXMLGUIClient::setDOMDocument(const QDomDocument &document, bool merge) { if (merge && !d->m_doc.isNull()) { QDomElement base = d->m_doc.documentElement(); QDomElement e = document.documentElement(); // merge our original (global) xml with our new one d->mergeXML(base, e, actionCollection()); // reassign our pointer as mergeXML might have done something // strange to it base = d->m_doc.documentElement(); //qCDebug(DEBUG_KXMLGUI) << "Result of xmlgui merging:" << d->m_doc.toString(); // we want some sort of failsafe.. just in case if (base.isNull()) { d->m_doc = document; } } else { d->m_doc = document; } setXMLGUIBuildDocument(QDomDocument()); } // if (equals(a,b)) is more readable than if (a.compare(b, Qt::CaseInsensitive)==0) static inline bool equalstr(const QString &a, const QString &b) { return a.compare(b, Qt::CaseInsensitive) == 0; } static inline bool equalstr(const QString &a, QLatin1String b) { return a.compare(b, Qt::CaseInsensitive) == 0; } bool KXMLGUIClientPrivate::mergeXML(QDomElement &base, QDomElement &additive, KActionCollection *actionCollection) { const QLatin1String tagAction("Action"); const QLatin1String tagMerge("Merge"); const QLatin1String tagSeparator("Separator"); const QLatin1String tagMergeLocal("MergeLocal"); const QLatin1String tagText("text"); const QLatin1String attrAppend("append"); const QString attrName(QStringLiteral("name")); const QString attrWeakSeparator(QStringLiteral("weakSeparator")); const QString attrAlreadyVisited(QStringLiteral("alreadyVisited")); const QString attrNoMerge(QStringLiteral("noMerge")); const QLatin1String attrOne("1"); // there is a possibility that we don't want to merge in the // additive.. rather, we might want to *replace* the base with the // additive. this can be for any container.. either at a file wide // level or a simple container level. we look for the 'noMerge' // tag, in any event and just replace the old with the new if (additive.attribute(attrNoMerge) == attrOne) { // ### use toInt() instead? (Simon) base.parentNode().replaceChild(additive, base); return true; } else { // Merge attributes { const QDomNamedNodeMap attribs = additive.attributes(); const int attribcount = attribs.count(); for (int i = 0; i < attribcount; ++i) { const QDomNode node = attribs.item(i); base.setAttribute(node.nodeName(), node.nodeValue()); } } // iterate over all elements in the container (of the global DOM tree) QDomNode n = base.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); n = n.nextSibling(); // Advance now so that we can safely delete e if (e.isNull()) { continue; } const QString tag = e.tagName(); // if there's an action tag in the global tree and the action is // not implemented, then we remove the element if (equalstr(tag, tagAction)) { const QString name = e.attribute(attrName); if (!actionCollection->action(name) || !KAuthorized::authorizeAction(name)) { // remove this child as we aren't using it base.removeChild(e); continue; } } // if there's a separator defined in the global tree, then add an // attribute, specifying that this is a "weak" separator else if (equalstr(tag, tagSeparator)) { e.setAttribute(attrWeakSeparator, uint(1)); // okay, hack time. if the last item was a weak separator OR // this is the first item in a container, then we nuke the // current one QDomElement prev = e.previousSibling().toElement(); if (prev.isNull() || (equalstr(prev.tagName(), tagSeparator) && !prev.attribute(attrWeakSeparator).isNull()) || (equalstr(prev.tagName(), tagText))) { // the previous element was a weak separator or didn't exist base.removeChild(e); continue; } } // the MergeLocal tag lets us specify where non-standard elements // of the local tree shall be merged in. After inserting the // elements we delete this element else if (equalstr(tag, tagMergeLocal)) { QDomNode it = additive.firstChild(); while (!it.isNull()) { QDomElement newChild = it.toElement(); it = it.nextSibling(); if (newChild.isNull()) { continue; } if (equalstr(newChild.tagName(), tagText)) { continue; } if (newChild.attribute(attrAlreadyVisited) == attrOne) { continue; } QString itAppend(newChild.attribute(attrAppend)); QString elemName(e.attribute(attrName)); if ((itAppend.isNull() && elemName.isEmpty()) || (itAppend == elemName)) { // first, see if this new element matches a standard one in // the global file. if it does, then we skip it as it will // be merged in, later QDomElement matchingElement = findMatchingElement(newChild, base); if (matchingElement.isNull() || equalstr(newChild.tagName(), tagSeparator)) { base.insertBefore(newChild, e); } } } base.removeChild(e); continue; } else if (equalstr(tag, tagText)) { continue; } else if (equalstr(tag, tagMerge)) { continue; } // in this last case we check for a separator tag and, if not, we // can be sure that it is a container --> proceed with child nodes // recursively and delete the just proceeded container item in // case it is empty (if the recursive call returns true) else { QDomElement matchingElement = findMatchingElement(e, additive); if (!matchingElement.isNull()) { matchingElement.setAttribute(attrAlreadyVisited, uint(1)); if (mergeXML(e, matchingElement, actionCollection)) { base.removeChild(e); additive.removeChild(matchingElement); // make sure we don't append it below continue; } continue; } else { // this is an important case here! We reach this point if the // "local" tree does not contain a container definition for // this container. However we have to call mergeXML recursively // and make it check if there are actions implemented for this // container. *If* none, then we can remove this container now QDomElement dummy; if (mergeXML(e, dummy, actionCollection)) { base.removeChild(e); } continue; } } } //here we append all child elements which were not inserted //previously via the LocalMerge tag n = additive.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); n = n.nextSibling(); // Advance now so that we can safely delete e if (e.isNull()) { continue; } QDomElement matchingElement = findMatchingElement(e, base); if (matchingElement.isNull()) { base.appendChild(e); } } // do one quick check to make sure that the last element was not // a weak separator QDomElement last = base.lastChild().toElement(); if (equalstr(last.tagName(), tagSeparator) && (!last.attribute(attrWeakSeparator).isNull())) { base.removeChild(last); } } return isEmptyContainer(base, actionCollection); } bool KXMLGUIClientPrivate::isEmptyContainer(const QDomElement &base, KActionCollection *actionCollection) const { // now we check if we are empty (in which case we return "true", to // indicate the caller that it can delete "us" (the base element // argument of "this" call) QDomNode n = base.firstChild(); while (!n.isNull()) { const QDomElement e = n.toElement(); n = n.nextSibling(); // Advance now so that we can safely delete e if (e.isNull()) { continue; } const QString tag = e.tagName(); if (equalstr(tag, QLatin1String("Action"))) { // if base contains an implemented action, then we must not get // deleted (note that the actionCollection contains both, // "global" and "local" actions) if (actionCollection->action(e.attribute(QStringLiteral("name")))) { return false; } } else if (equalstr(tag, QLatin1String("Separator"))) { // if we have a separator which has *not* the weak attribute // set, then it must be owned by the "local" tree in which case // we must not get deleted either const QString weakAttr = e.attribute(QStringLiteral("weakSeparator")); if (weakAttr.isEmpty() || weakAttr.toInt() != 1) { return false; } } else if (equalstr(tag, QLatin1String("merge"))) { continue; } // a text tag is NOT enough to spare this container else if (equalstr(tag, QLatin1String("text"))) { continue; } // what's left are non-empty containers! *don't* delete us in this // case (at this position we can be *sure* that the container is // *not* empty, as the recursive call for it was in the first loop // which deleted the element in case the call returned "true" else { return false; } } return true; // I'm empty, please delete me. } QDomElement KXMLGUIClientPrivate::findMatchingElement(const QDomElement &base, const QDomElement &additive) { QDomNode n = additive.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); n = n.nextSibling(); // Advance now so that we can safely delete e -- TODO we don't, so simplify this if (e.isNull()) { continue; } const QString tag = e.tagName(); // skip all action and merge tags as we will never use them if (equalstr(tag, QLatin1String("Action")) || equalstr(tag, QLatin1String("MergeLocal"))) { continue; } // now see if our tags are equivalent if (equalstr(tag, base.tagName()) && e.attribute(QStringLiteral("name")) == base.attribute(QStringLiteral("name"))) { return e; } } // nope, return a (now) null element return QDomElement(); } void KXMLGUIClient::setXMLGUIBuildDocument(const QDomDocument &doc) { d->m_buildDocument = doc; } QDomDocument KXMLGUIClient::xmlguiBuildDocument() const { return d->m_buildDocument; } void KXMLGUIClient::setFactory(KXMLGUIFactory *factory) { d->m_factory = factory; } KXMLGUIFactory *KXMLGUIClient::factory() const { return d->m_factory; } KXMLGUIClient *KXMLGUIClient::parentClient() const { return d->m_parent; } void KXMLGUIClient::insertChildClient(KXMLGUIClient *child) { if (child->d->m_parent) { child->d->m_parent->removeChildClient(child); } d->m_children.append(child); child->d->m_parent = this; } void KXMLGUIClient::removeChildClient(KXMLGUIClient *child) { assert(d->m_children.contains(child)); d->m_children.removeAll(child); child->d->m_parent = nullptr; } /*bool KXMLGUIClient::addSuperClient( KXMLGUIClient *super ) { if ( d->m_supers.contains( super ) ) return false; d->m_supers.append( super ); return true; }*/ QList KXMLGUIClient::childClients() { return d->m_children; } void KXMLGUIClient::setClientBuilder(KXMLGUIBuilder *builder) { d->m_builder = builder; } KXMLGUIBuilder *KXMLGUIClient::clientBuilder() const { return d->m_builder; } void KXMLGUIClient::plugActionList(const QString &name, const QList &actionList) { if (!d->m_factory) { return; } d->m_factory->plugActionList(this, name, actionList); } void KXMLGUIClient::unplugActionList(const QString &name) { if (!d->m_factory) { return; } d->m_factory->unplugActionList(this, name); } QString KXMLGUIClient::findMostRecentXMLFile(const QStringList &files, QString &doc) { KXmlGuiVersionHandler versionHandler(files); doc = versionHandler.finalDocument(); return versionHandler.finalFile(); } void KXMLGUIClient::addStateActionEnabled(const QString &state, const QString &action) { StateChange stateChange = getActionsToChangeForState(state); stateChange.actionsToEnable.append(action); //qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionEnabled( " << state << ", " << action << ")"; d->m_actionsStateMap.insert(state, stateChange); } void KXMLGUIClient::addStateActionDisabled(const QString &state, const QString &action) { StateChange stateChange = getActionsToChangeForState(state); stateChange.actionsToDisable.append(action); //qCDebug(DEBUG_KXMLGUI) << "KXMLGUIClient::addStateActionDisabled( " << state << ", " << action << ")"; d->m_actionsStateMap.insert(state, stateChange); } KXMLGUIClient::StateChange KXMLGUIClient::getActionsToChangeForState(const QString &state) { return d->m_actionsStateMap[state]; } void KXMLGUIClient::stateChanged(const QString &newstate, KXMLGUIClient::ReverseStateChange reverse) { StateChange stateChange = getActionsToChangeForState(newstate); bool setTrue = (reverse == StateNoReverse); bool setFalse = !setTrue; // Enable actions which need to be enabled... // for (QStringList::const_iterator it = stateChange.actionsToEnable.constBegin(); it != stateChange.actionsToEnable.constEnd(); ++it) { QAction *action = actionCollection()->action(*it); if (action) { action->setEnabled(setTrue); } } // and disable actions which need to be disabled... // for (QStringList::const_iterator it = stateChange.actionsToDisable.constBegin(); it != stateChange.actionsToDisable.constEnd(); ++it) { QAction *action = actionCollection()->action(*it); if (action) { action->setEnabled(setFalse); } } } void KXMLGUIClient::beginXMLPlug(QWidget *w) { actionCollection()->addAssociatedWidget(w); foreach (KXMLGUIClient *client, d->m_children) { client->beginXMLPlug(w); } } void KXMLGUIClient::endXMLPlug() { } void KXMLGUIClient::prepareXMLUnplug(QWidget *w) { actionCollection()->removeAssociatedWidget(w); foreach (KXMLGUIClient *client, d->m_children) { client->prepareXMLUnplug(w); } } void KXMLGUIClient::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } diff --git a/src/kxmlguiclient.h b/src/kxmlguiclient.h index 1d9a0c9..66831d5 100644 --- a/src/kxmlguiclient.h +++ b/src/kxmlguiclient.h @@ -1,412 +1,411 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Simon Hausmann Copyright (C) 2000 Kurt Granroth This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KXMLGUICLIENT_H #define KXMLGUICLIENT_H #include -#include #include class QDomDocument; class QDomElement; class QWidget; class QAction; class KActionCollection; class KXMLGUIClientPrivate; class KXMLGUIFactory; class KXMLGUIBuilder; namespace KDEPrivate { class KEditToolBarWidget; } /** * @class KXMLGUIClient kxmlguiclient.h KXMLGUIClient * * A KXMLGUIClient can be used with KXMLGUIFactory to create a * GUI from actions and an XML document, and can be dynamically merged * with other KXMLGUIClients. */ class KXMLGUI_EXPORT KXMLGUIClient { friend class KDEPrivate::KEditToolBarWidget; // for setXMLFile(3 args) public: /** * Constructs a KXMLGUIClient which can be used with a * KXMLGUIFactory to create a GUI from actions and an XML document, and * which can be dynamically merged with other KXMLGUIClients. */ KXMLGUIClient(); /** * Constructs a KXMLGUIClient which can be used with a KXMLGUIFactory * to create a GUI from actions and an XML document, * and which can be dynamically merged with other KXMLGUIClients. * * This constructor takes an additional @p parent argument, which makes * the client a child client of the parent. * * Child clients are automatically added to the GUI if the parent is added. * */ explicit KXMLGUIClient(KXMLGUIClient *parent); /** * Destructs the KXMLGUIClient. * * If the client was in a factory, the factory is NOT informed about the client * being removed. This is a feature, it makes window destruction fast (the xmlgui * is not updated for every client being deleted), but if you want to simply remove * one client and to keep using the window, make sure to call factory->removeClient(client) * before deleting the client. */ virtual ~KXMLGUIClient(); /** * Retrieves an action of the client by name. If not found, it looks in its child clients. * This method is provided for convenience, as it uses actionCollection() * to get the action object. */ QAction *action(const char *name) const; /** * Retrieves an action for a given QDomElement. The default * implementation uses the "name" attribute to query the action * object via the other action() method. */ virtual QAction *action(const QDomElement &element) const; /** * Retrieves the entire action collection for the GUI client. */ virtual KActionCollection *actionCollection() const; /** * @return The component name for this GUI client. */ virtual QString componentName() const; /** * @return The parsed XML in a QDomDocument, set by * setXMLFile() or setXML(). * This document describes the layout of the GUI. */ virtual QDomDocument domDocument() const; /** * This will return the name of the XML file as set by setXMLFile(). * If setXML() is used directly, then this will return an empty string. * * The filename that this returns is obvious for components as each * component has exactly one XML file. In non-components, however, * there are usually two: the global file and the local file. This * function doesn't really care about that, though. It will always * return the last XML file set. This, in almost all cases, will * be the local XML file. * * @return The name of the XML file or QString() */ virtual QString xmlFile() const; virtual QString localXMLFile() const; /** * @internal */ void setXMLGUIBuildDocument(const QDomDocument &doc); /** * @internal */ QDomDocument xmlguiBuildDocument() const; /** * This method is called by the KXMLGUIFactory as soon as the client * is added to the KXMLGUIFactory's GUI. */ void setFactory(KXMLGUIFactory *factory); /** * Retrieves a pointer to the KXMLGUIFactory this client is * associated with (will return nullptr if the client's GUI has not been built * by a KXMLGUIFactory. */ KXMLGUIFactory *factory() const; /** * KXMLGUIClients can form a simple child/parent object tree. This * method returns a pointer to the parent client or nullptr if it has no * parent client assigned. */ KXMLGUIClient *parentClient() const; /** * Use this method to make a client a child client of another client. * Usually you don't need to call this method, as it is called * automatically when using the second constructor, which takes a * parent argument. */ void insertChildClient(KXMLGUIClient *child); /** * Removes the given @p child from the client's children list. */ void removeChildClient(KXMLGUIClient *child); /** * Retrieves a list of all child clients. */ QList childClients(); /** * A client can have an own KXMLGUIBuilder. * Use this method to assign your builder instance to the client (so that the * KXMLGUIFactory can use it when building the client's GUI) * * Client specific guibuilders are useful if you want to create * custom container widgets for your GUI. */ void setClientBuilder(KXMLGUIBuilder *builder); /** * Retrieves the client's GUI builder or nullptr if no client specific * builder has been assigned via setClientBuilder() */ KXMLGUIBuilder *clientBuilder() const; /** * Forces this client to re-read its XML resource file. This is * intended to be used when you know that the resource file has * changed and you will soon be rebuilding the GUI. This will only have * an effect if the client is then removed and re-added to the factory. * * This method is only for child clients, do not call it for a mainwindow! * For a mainwindow, use loadStandardsXmlFile + setXmlFile(xmlFile()) instead. */ void reloadXML(); /** * ActionLists are a way for XMLGUI to support dynamic lists of * actions. E.g. if you are writing a file manager, and there is a * menu file whose contents depend on the mimetype of the file that * is selected, then you can achieve this using ActionLists. It * works as follows: * In your xxxui.rc file ( the one that you set in setXMLFile() / pass to setupGUI() * ), you put a tag \. * * Example: * \code * * * * ... * * ... * * ... * * * \endcode * * This tag will get expanded to a list of actions. In the example * above ( a file manager with a dynamic file menu ), you would call * \code * QList file_actions; * for( ... ) * if( ... ) * file_actions.append( cool_action ); * unplugActionList( "xxx_file_actionlist" ); * plugActionList( "xxx_file_actionlist", file_actions ); * \endcode * every time a file is selected, unselected or ... * * \note You should not call KXmlGuiWindow::createGUI() after calling this * function. In fact, that would remove the newly added * actionlists again... * \note Forgetting to call unplugActionList() before * plugActionList() would leave the previous actions in the * menu too.. * \see unplugActionList() */ void plugActionList(const QString &name, const QList &actionList); /** * Unplugs the action list \p name from the XMLGUI. * Calling this removes the specified action list, i.e. this is the * complement to plugActionList(). See plugActionList() for a more * detailed example. * \see plugActionList() */ void unplugActionList(const QString &name); static QString findMostRecentXMLFile(const QStringList &files, QString &doc); void addStateActionEnabled(const QString &state, const QString &action); void addStateActionDisabled(const QString &state, const QString &action); enum ReverseStateChange { StateNoReverse, StateReverse }; struct StateChange { QStringList actionsToEnable; QStringList actionsToDisable; }; StateChange getActionsToChangeForState(const QString &state); void beginXMLPlug(QWidget *); void endXMLPlug(); void prepareXMLUnplug(QWidget *); /** * Sets a new xmlFile() and localXMLFile(). The purpose of this public * method is to allow non-inherited objects to replace the ui definition * of an embedded client with a customized version. It corresponds to the * usual calls to setXMLFile() and setLocalXMLFile(). * * @param xmlfile The xml file to use. Contrary to setXMLFile(), this * must be an absolute file path. * @param localxmlfile The local xml file to set. This should be the full path * to a writeable file, usually using QStandardPaths::writableLocation. * You can set this to QString(), but no user changes to shortcuts / toolbars * will be possible in this case. * @param merge Whether to merge with the global document * * @note If in any doubt whether you need this or not, use setXMLFile() * and setLocalXMLFile(), instead of this function. * @note Just like setXMLFile(), this function has to be called before * the client is added to a KXMLGUIFactory in order to have an * effect. * * @see setLocalXMLFile() * @since 4.4 */ void replaceXMLFile(const QString &xmlfile, const QString &localxmlfile, bool merge = false); protected: /** * Sets the component name for this part. * * Call this first in the inherited class constructor. * (At least before setXMLFile().) * @param componentName the name of the directory where the XMLGUI files will be found * @param componentDisplayName a user-visible name (e.g. for the toolbar editor) */ virtual void setComponentName(const QString &componentName, const QString &componentDisplayName); /** * Sets the name of the rc file containing the XML for the part. * * Call this in the inherited class constructor, for parts and plugins. * @note For mainwindows, don't call this, pass the name of the xml file * to KXmlGuiWindow::setupGUI() or KXmlGuiWindow::createGUI(). * * @param file Either an absolute path for the file, or simply the * filename. See below for details. * If you pass an absolute path here, make sure to also call * setLocalXMLFile, otherwise toolbar editing won't work. * @param merge Whether to merge with the global document. * @param setXMLDoc Specify whether to call setXML. Default is true. * * The preferred way to call this method is with a simple filename for the @p file argument. * * Since KF 5.1, the file will then be assumed to be installed in DATADIR/kxmlgui5/, under a directory * named after the component name. * You should use ${KXMLGUI_INSTALL_DIR}/componentname in your CMakeLists.txt file, to install * the .rc file(s). * * Since KF 5.4, the file will then be assumed to be installed in a Qt resource in :/kxmlgui5/, * under a directory named after the component name. * * Compatibility notes: * Fallback lookups exist to older locations: DATADIR/componentname/file and DATADIR/file. * The latter was there so that setXMLFile("componentname/filename") worked (but this was * undocumented). Do not do this anymore after porting to KXMLGUI_INSTALL_DIR, use * setComponentName("componentname") and setXMLFile("filename"). **/ virtual void setXMLFile(const QString &file, bool merge = false, bool setXMLDoc = true); /** * Return the full path to the ui_standards.rc, might return a resource path. * @return full path to ui_standards.rc, always non-empty. * @since 5.16 */ static QString standardsXmlFileLocation(); /** * Load the ui_standards.rc file. Usually followed by setXMLFile(xmlFile, true), for merging. * @since 4.6 */ void loadStandardsXmlFile(); /** * Set the full path to the "local" xml file, the one used for saving * toolbar and shortcut changes. You normally don't need to call this, * if you pass a simple filename to setXMLFile. */ virtual void setLocalXMLFile(const QString &file); /** * Sets the XML for the part. * * Call this in the Part-inherited class constructor if you * don't call setXMLFile(). **/ virtual void setXML(const QString &document, bool merge = false); /** * Sets the Document for the part, describing the layout of the GUI. * * Call this in the Part-inherited class constructor if you don't call * setXMLFile() or setXML(). * * @warning Using this method is not recommended. Many code paths * lead to reloading from the XML file on disk. And editing toolbars requires * that the result is written to disk anyway, and loaded from there the next time. * * For application-specific changes to a client's XML, it is a better idea to * save the modified dom document to an app/default-client.xml and define a local-xml-file * to something specific like app/local-client.xml, using replaceXMLFile(). * See kdepimlibs/kontactinterface/plugin.cpp for an example. */ virtual void setDOMDocument(const QDomDocument &document, bool merge = false); /** * Actions can collectively be assigned a "State". To accomplish this * the respective actions are tagged as \ or \ in * a \ \ group of the XMLfile. During program execution the * programmer can call stateChanged() to set actions to a defined state. * * @param newstate Name of a State in the XMLfile. * @param reverse If the flag reverse is set to StateReverse, the State is reversed. * (actions to be enabled will be disabled and action to be disabled will be enabled) * Default is reverse=false. */ virtual void stateChanged(const QString &newstate, ReverseStateChange reverse = StateNoReverse); // KDE5 TODO: virtual void loadActionLists() {}, called when the guiclient is added to the xmlgui factory protected: virtual void virtual_hook(int id, void *data); private: KXMLGUIClientPrivate *const d; }; #endif diff --git a/src/kxmlguifactory.cpp b/src/kxmlguifactory.cpp index cedd5b4..1720f6f 100644 --- a/src/kxmlguifactory.cpp +++ b/src/kxmlguifactory.cpp @@ -1,771 +1,770 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000 Simon Hausmann Copyright (C) 2000 Kurt Granroth 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 "config-xmlgui.h" #include "kxmlguifactory.h" #include "kxmlguifactory_p.h" #include "kshortcutschemeshelper_p.h" #include "kxmlguiclient.h" #include "kxmlguibuilder.h" #include "kshortcutsdialog.h" #include "kactioncollection.h" #include "debug.h" #include #include #include #include #include #include #include -#include #include #include #include #include #include #if HAVE_GLOBALACCEL # include #endif Q_DECLARE_METATYPE(QList) using namespace KXMLGUI; class KXMLGUIFactoryPrivate : public BuildState { public: enum ShortcutOption { SetActiveShortcut = 1, SetDefaultShortcut = 2}; KXMLGUIFactoryPrivate() { m_rootNode = new ContainerNode(nullptr, QString(), QString()); attrName = QStringLiteral("name"); } ~KXMLGUIFactoryPrivate() { delete m_rootNode; } void pushState() { m_stateStack.push(*this); } void popState() { BuildState::operator=(m_stateStack.pop()); } bool emptyState() const { return m_stateStack.isEmpty(); } QWidget *findRecursive(KXMLGUI::ContainerNode *node, bool tag); QList findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName); void applyActionProperties(const QDomElement &element, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList &actions); void refreshActionProperties(KXMLGUIClient *client, const QList &actions, const QDomDocument &doc); void saveDefaultActionProperties(const QList &actions); ContainerNode *m_rootNode; /* * Contains the container which is searched for in ::container . */ QString m_containerName; /* * List of all clients */ QList m_clients; QString attrName; BuildStateStack m_stateStack; }; QString KXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName) { QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName; QString xml_file; if (!QDir::isRelativePath(filename)) { xml_file = filename; } else { // KF >= 5.1 (KXMLGUI_INSTALL_DIR) xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kxmlgui5/") + componentName + QLatin1Char('/') + filename); if (!QFile::exists(xml_file)) { // KF >= 5.4 (resource file) xml_file = QStringLiteral(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename; } bool warn = false; if (!QFile::exists(xml_file)) { // kdelibs4 / KF 5.0 solution xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, componentName + QLatin1Char('/') + filename); warn = true; } if (!QFile::exists(xml_file)) { // kdelibs4 / KF 5.0 solution, and the caller includes the component name // This was broken (lead to component/component/ in kdehome) and unnecessary // (they can specify it with setComponentName instead) xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, filename); warn = true; } if (warn && !xml_file.isEmpty()) { qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << xml_file << "-- please use ${KXMLGUI_INSTALL_DIR} to install these files instead."; } } QFile file(xml_file); if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) { qCritical() << "No such XML file" << filename; return QString(); } QByteArray buffer(file.readAll()); return QString::fromUtf8(buffer.constData(), buffer.size()); } bool KXMLGUIFactory::saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &_componentName) { QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName; QString xml_file(filename); if (QDir::isRelativePath(xml_file)) xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kxmlgui5/") + componentName + QLatin1Char('/') + filename; QFileInfo fileInfo(xml_file); QDir().mkpath(fileInfo.absolutePath()); QFile file(xml_file); if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) { qCritical() << "Could not write to" << filename; return false; } // write out our document QTextStream ts(&file); ts.setCodec(QTextCodec::codecForName("UTF-8")); ts << doc; file.close(); return true; } KXMLGUIFactory::KXMLGUIFactory(KXMLGUIBuilder *builder, QObject *parent) : QObject(parent), d(new KXMLGUIFactoryPrivate) { d->builder = builder; d->guiClient = nullptr; if (d->builder) { d->builderContainerTags = d->builder->containerTags(); d->builderCustomTags = d->builder->customTags(); } } KXMLGUIFactory::~KXMLGUIFactory() { Q_FOREACH (KXMLGUIClient *client, d->m_clients) { client->setFactory(nullptr); } delete d; } void KXMLGUIFactory::addClient(KXMLGUIClient *client) { //qCDebug(DEBUG_KXMLGUI) << client; if (client->factory()) { if (client->factory() == this) { return; } else { client->factory()->removeClient(client); //just in case someone does stupid things ;-) } } if (d->emptyState()) { emit makingChanges(true); } d->pushState(); // QTime dt; dt.start(); d->guiClient = client; // add this client to our client list if (!d->m_clients.contains(client)) { d->m_clients.append(client); } //else //qCDebug(DEBUG_KXMLGUI) << "XMLGUI client already added " << client; // Tell the client that plugging in is process and // let it know what builder widget its mainwindow shortcuts // should be attached to. client->beginXMLPlug(d->builder->widget()); // try to use the build document for building the client's GUI, as the build document // contains the correct container state information (like toolbar positions, sizes, etc.) . // if there is non available, then use the "real" document. QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { doc = client->domDocument(); } QDomElement docElement = doc.documentElement(); d->m_rootNode->index = -1; // cache some variables d->clientName = docElement.attribute(d->attrName); d->clientBuilder = client->clientBuilder(); if (d->clientBuilder) { d->clientBuilderContainerTags = d->clientBuilder->containerTags(); d->clientBuilderCustomTags = d->clientBuilder->customTags(); } else { d->clientBuilderContainerTags.clear(); d->clientBuilderCustomTags.clear(); } // load shortcut schemes, user-defined shortcuts and other action properties d->saveDefaultActionProperties(client->actionCollection()->actions()); if (!doc.isNull()) { d->refreshActionProperties(client, client->actionCollection()->actions(), doc); } BuildHelper(*d, d->m_rootNode).build(docElement); // let the client know that we built its GUI. client->setFactory(this); // call the finalizeGUI method, to fix up the positions of toolbars for example. // Note: the client argument is ignored d->builder->finalizeGUI(d->guiClient); // reset some variables, for safety d->BuildState::reset(); client->endXMLPlug(); d->popState(); emit clientAdded(client); // build child clients Q_FOREACH (KXMLGUIClient *child, client->childClients()) { addClient(child); } if (d->emptyState()) { emit makingChanges(false); } /* QString unaddedActions; Q_FOREACH (KActionCollection* ac, KActionCollection::allCollections()) Q_FOREACH (QAction* action, ac->actions()) if (action->associatedWidgets().isEmpty()) unaddedActions += action->objectName() + ' '; if (!unaddedActions.isEmpty()) qCWarning(DEBUG_KXMLGUI) << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions; */ // qCDebug(DEBUG_KXMLGUI) << "addClient took " << dt.elapsed(); } void KXMLGUIFactory::refreshActionProperties() { Q_FOREACH (KXMLGUIClient *client, d->m_clients) { d->guiClient = client; QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { client->reloadXML(); doc = client->domDocument(); } d->refreshActionProperties(client, client->actionCollection()->actions(), doc); } d->guiClient = nullptr; } static QString currentShortcutScheme() { const KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); return cg.readEntry("Current Scheme", "Default"); } // Find the right ActionProperties element, otherwise return null element static QDomElement findActionPropertiesElement(const QDomDocument &doc) { const QLatin1String tagActionProp("ActionProperties"); const QString schemeName = currentShortcutScheme(); QDomElement e = doc.documentElement().firstChildElement(); for (; !e.isNull(); e = e.nextSiblingElement()) { if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0 && (e.attribute(QStringLiteral("scheme"), QStringLiteral("Default")) == schemeName)) { return e; } } return QDomElement(); } void KXMLGUIFactoryPrivate::refreshActionProperties(KXMLGUIClient *client, const QList &actions, const QDomDocument &doc) { // try to find and apply shortcuts schemes const QString schemeName = KShortcutSchemesHelper::currentShortcutSchemeName(); //qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": applying shortcut scheme" << schemeName; if (schemeName != QLatin1String("Default")) { applyShortcutScheme(schemeName, client, actions); } else { // apply saved default shortcuts Q_FOREACH (QAction *action, actions) { QVariant savedDefaultShortcut = action->property("_k_DefaultShortcut"); if (savedDefaultShortcut.isValid()) { QList shortcut = savedDefaultShortcut.value >(); action->setShortcuts(shortcut); action->setProperty("defaultShortcuts", QVariant::fromValue(shortcut)); //qCDebug(DEBUG_KXMLGUI) << "scheme said" << action->shortcut().toString() << "for action" << action->objectName(); } else { action->setShortcuts(QList()); } } } // try to find and apply user-defined shortcuts const QDomElement actionPropElement = findActionPropertiesElement(doc); if (!actionPropElement.isNull()) { applyActionProperties(actionPropElement); } } void KXMLGUIFactoryPrivate::saveDefaultActionProperties(const QList &actions) { // This method is called every time the user activated a new // kxmlguiclient. We only want to execute the following code only once in // the lifetime of an action. Q_FOREACH (QAction *action, actions) { // Skip nullptr actions or those we have seen already. if (!action || action->property("_k_DefaultShortcut").isValid()) { continue; } // Check if the default shortcut is set QList defaultShortcut = action->property("defaultShortcuts").value >(); QList activeShortcut = action->shortcuts(); //qCDebug(DEBUG_KXMLGUI) << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString(); // Check if we have an empty default shortcut and an non empty // custom shortcut. Print out a warning and correct the mistake. if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) { qCritical() << "Shortcut for action " << action->objectName() << action->text() << "set with QAction::setShortcut()! Use KActionCollection::setDefaultShortcut(s) instead."; action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut)); } else { action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut)); } } } void KXMLGUIFactory::changeShortcutScheme(const QString &scheme) { qCDebug(DEBUG_KXMLGUI) << "Changing shortcut scheme to" << scheme; KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); cg.writeEntry("Current Scheme", scheme); refreshActionProperties(); } void KXMLGUIFactory::forgetClient(KXMLGUIClient *client) { d->m_clients.removeAll(client); } void KXMLGUIFactory::removeClient(KXMLGUIClient *client) { //qCDebug(DEBUG_KXMLGUI) << client; // don't try to remove the client's GUI if we didn't build it if (!client || client->factory() != this) { return; } if (d->emptyState()) { emit makingChanges(true); } // remove this client from our client list d->m_clients.removeAll(client); // remove child clients first (create a copy of the list just in case the // original list is modified directly or indirectly in removeClient()) const QList childClients(client->childClients()); Q_FOREACH (KXMLGUIClient *child, childClients) { removeClient(child); } //qCDebug(DEBUG_KXMLGUI) << "calling removeRecursive"; d->pushState(); // cache some variables d->guiClient = client; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->clientBuilder = client->clientBuilder(); client->setFactory(nullptr); // if we don't have a build document for that client, yet, then create one by // cloning the original document, so that saving container information in the // DOM tree does not touch the original document. QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { doc = client->domDocument().cloneNode(true).toDocument(); client->setXMLGUIBuildDocument(doc); } d->m_rootNode->destruct(doc.documentElement(), *d); // reset some variables d->BuildState::reset(); // This will destruct the KAccel object built around the given widget. client->prepareXMLUnplug(d->builder->widget()); d->popState(); if (d->emptyState()) { emit makingChanges(false); } emit clientRemoved(client); } QList KXMLGUIFactory::clients() const { return d->m_clients; } QWidget *KXMLGUIFactory::container(const QString &containerName, KXMLGUIClient *client, bool useTagName) { d->pushState(); d->m_containerName = containerName; d->guiClient = client; QWidget *result = d->findRecursive(d->m_rootNode, useTagName); d->guiClient = nullptr; d->m_containerName.clear(); d->popState(); return result; } QList KXMLGUIFactory::containers(const QString &tagName) { return d->findRecursive(d->m_rootNode, tagName); } void KXMLGUIFactory::reset() { d->m_rootNode->reset(); d->m_rootNode->clearChildren(); } void KXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName) { if (containerName.isEmpty()) { return; } ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName); if (container && container->parent) { container->parent->removeChild(container); } } QWidget *KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, bool tag) { if (((!tag && node->name == m_containerName) || (tag && node->tagName == m_containerName)) && (!guiClient || node->client == guiClient)) { return node->container; } Q_FOREACH (ContainerNode *child, node->children) { QWidget *cont = findRecursive(child, tag); if (cont) { return cont; } } return nullptr; } // Case insensitive equality without calling toLower which allocates a new string static inline bool equals(const QString &str1, const char *str2) { return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0; } static inline bool equals(const QString &str1, const QString &str2) { return str1.compare(str2, Qt::CaseInsensitive) == 0; } QList KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName) { QList res; if (equals(node->tagName, tagName)) { res.append(node->container); } Q_FOREACH (KXMLGUI::ContainerNode *child, node->children) { res << findRecursive(child, tagName); } return res; } void KXMLGUIFactory::plugActionList(KXMLGUIClient *client, const QString &name, const QList &actionList) { d->pushState(); d->guiClient = client; d->actionListName = name; d->actionList = actionList; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->m_rootNode->plugActionList(*d); // Load shortcuts for these new actions d->saveDefaultActionProperties(actionList); d->refreshActionProperties(client, actionList, client->domDocument()); d->BuildState::reset(); d->popState(); } void KXMLGUIFactory::unplugActionList(KXMLGUIClient *client, const QString &name) { d->pushState(); d->guiClient = client; d->actionListName = name; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->m_rootNode->unplugActionList(*d); d->BuildState::reset(); d->popState(); } void KXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement, ShortcutOption shortcutOption) { for (QDomElement e = actionPropElement.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { if (!equals(e.tagName(), "action")) { continue; } QAction *action = guiClient->action(e); if (!action) { continue; } configureAction(action, e.attributes(), shortcutOption); } } void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption) { for (int i = 0; i < attributes.length(); i++) { QDomAttr attr = attributes.item(i).toAttr(); if (attr.isNull()) { continue; } configureAction(action, attr, shortcutOption); } } void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption) { QString attrName = attribute.name(); // If the attribute is a deprecated "accel", change to "shortcut". if (equals(attrName, "accel")) { attrName = QStringLiteral("shortcut"); } // No need to re-set name, particularly since it's "objectName" in Qt4 if (equals(attrName, "name")) { return; } if (equals(attrName, "icon")) { action->setIcon(QIcon::fromTheme(attribute.value())); return; } QVariant propertyValue; QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type(); bool isShortcut = (propertyType == QVariant::KeySequence); if (propertyType == QVariant::Int) { propertyValue = QVariant(attribute.value().toInt()); } else if (propertyType == QVariant::UInt) { propertyValue = QVariant(attribute.value().toUInt()); } else if (isShortcut) { // Setting the shortcut by property also sets the default shortcut (which is incorrect), so we have to do it directly if (attrName == QStringLiteral("globalShortcut")) { #if HAVE_GLOBALACCEL KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(attribute.value())); #endif } else { action->setShortcuts(QKeySequence::listFromString(attribute.value())); } if (shortcutOption & KXMLGUIFactoryPrivate::SetDefaultShortcut) { action->setProperty("defaultShortcuts", QVariant::fromValue(QKeySequence::listFromString(attribute.value()))); } } else { propertyValue = QVariant(attribute.value()); } if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) { qCWarning(DEBUG_KXMLGUI) << "Error: Unknown action property " << attrName << " will be ignored!"; } } void KXMLGUIFactoryPrivate::applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList &actions) { //First clear all existing shortcuts Q_FOREACH (QAction *action, actions) { action->setShortcuts(QList()); // We clear the default shortcut as well because the shortcut scheme will set its own defaults action->setProperty("defaultShortcuts", QVariant::fromValue(QList())); } // Find the document for the shortcut scheme using the current application path. // This allows to install a single XML file for a shortcut scheme for kdevelop // rather than 10. // Also look for the current xmlguiclient path. // Per component xml files make sense for making kmail shortcuts available in kontact. QString schemeFileName = KShortcutSchemesHelper::shortcutSchemeFileName(client->componentName(), schemeName); if (schemeFileName.isEmpty()) { schemeFileName = KShortcutSchemesHelper::applicationShortcutSchemeFileName(schemeName); } if (schemeFileName.isEmpty()) { qCWarning(DEBUG_KXMLGUI) << client->componentName() << ": shortcut scheme file not found:" << schemeName << "after trying" << QCoreApplication::applicationName() << "and" << client->componentName(); return; } QDomDocument scheme; QFile schemeFile(schemeFileName); if (schemeFile.open(QIODevice::ReadOnly)) { qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": found shortcut scheme XML" << schemeFileName; scheme.setContent(&schemeFile); } if (scheme.isNull()) { return; } QDomElement docElement = scheme.documentElement(); QDomElement actionPropElement = docElement.namedItem(QStringLiteral("ActionProperties")).toElement(); //Check if we really have the shortcut configuration here if (!actionPropElement.isNull()) { //qCDebug(DEBUG_KXMLGUI) << "Applying shortcut scheme for XMLGUI client" << client->componentName(); //Apply all shortcuts we have applyActionProperties(actionPropElement, KXMLGUIFactoryPrivate::SetDefaultShortcut); //} else { //qCDebug(DEBUG_KXMLGUI) << "Invalid shortcut scheme file"; } } int KXMLGUIFactory::configureShortcuts(bool letterCutsOk, bool bSaveSettings) { KShortcutsDialog dlg(KShortcutsEditor::AllActions, letterCutsOk ? KShortcutsEditor::LetterShortcutsAllowed : KShortcutsEditor::LetterShortcutsDisallowed, qobject_cast(parent())); Q_FOREACH (KXMLGUIClient *client, d->m_clients) { if (client) { qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions"; dlg.addCollection(client->actionCollection()); } } return dlg.configure(bSaveSettings); } // Find or create QDomElement KXMLGUIFactory::actionPropertiesElement(QDomDocument &doc) { // first, lets see if we have existing properties QDomElement elem = findActionPropertiesElement(doc); // if there was none, create one if (elem.isNull()) { elem = doc.createElement(QStringLiteral("ActionProperties")); elem.setAttribute(QStringLiteral("scheme"), currentShortcutScheme()); doc.documentElement().appendChild(elem); } return elem; } QDomElement KXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create) { const QLatin1String attrName("name"); for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) { QDomElement e = it.toElement(); if (e.attribute(attrName) == sName) { return e; } } if (create) { QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action")); act_elem.setAttribute(attrName, sName); elem.appendChild(act_elem); return act_elem; } return QDomElement(); } diff --git a/src/kxmlguiwindow.cpp b/src/kxmlguiwindow.cpp index 4bb0a18..1757d5a 100644 --- a/src/kxmlguiwindow.cpp +++ b/src/kxmlguiwindow.cpp @@ -1,451 +1,448 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kxmlguiwindow.h" #include "debug.h" #include "kmainwindow_p.h" #include "kmessagebox.h" #include "kactioncollection.h" #ifdef QT_DBUS_LIB #include "kmainwindowiface_p.h" #endif #include "ktoolbarhandler_p.h" #include "kxmlguifactory.h" #include "kedittoolbar.h" #include "khelpmenu.h" #include "ktoolbar.h" -#include #ifdef QT_DBUS_LIB #include #endif #include -#include #include -#include #include -#include #include #include +#include #include #include #include #include #include #include #include #include #include #include class KXmlGuiWindowPrivate : public KMainWindowPrivate { public: void _k_slotFactoryMakingChanges(bool b) { // While the GUI factory is adding/removing clients, // don't let KMainWindow think those are changes made by the user // #105525 letDirtySettings = !b; } bool showHelpMenu: 1; QSize defaultSize; KDEPrivate::ToolBarHandler *toolBarHandler; KToggleAction *showStatusBarAction; QPointer toolBarEditor; KXMLGUIFactory *factory; }; KXmlGuiWindow::KXmlGuiWindow(QWidget *parent, Qt::WindowFlags f) : KMainWindow(*new KXmlGuiWindowPrivate, parent, f), KXMLGUIBuilder(this) { K_D(KXmlGuiWindow); d->showHelpMenu = true; d->toolBarHandler = nullptr; d->showStatusBarAction = nullptr; d->factory = nullptr; #ifdef QT_DBUS_LIB new KMainWindowInterface(this); #endif } QAction *KXmlGuiWindow::toolBarMenuAction() { K_D(KXmlGuiWindow); if (!d->toolBarHandler) { return nullptr; } return d->toolBarHandler->toolBarMenuAction(); } void KXmlGuiWindow::setupToolbarMenuActions() { K_D(KXmlGuiWindow); if (d->toolBarHandler) { d->toolBarHandler->setupActions(); } } KXmlGuiWindow::~KXmlGuiWindow() { K_D(KXmlGuiWindow); delete d->factory; } bool KXmlGuiWindow::event(QEvent *ev) { bool ret = KMainWindow::event(ev); if (ev->type() == QEvent::Polish) { #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().registerObject(dbusName() + QStringLiteral("/actions"), actionCollection(), QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportNonScriptableSlots | QDBusConnection::ExportNonScriptableProperties | QDBusConnection::ExportChildObjects); #endif } return ret; } void KXmlGuiWindow::setHelpMenuEnabled(bool showHelpMenu) { K_D(KXmlGuiWindow); d->showHelpMenu = showHelpMenu; } bool KXmlGuiWindow::isHelpMenuEnabled() const { K_D(const KXmlGuiWindow); return d->showHelpMenu; } KXMLGUIFactory *KXmlGuiWindow::guiFactory() { K_D(KXmlGuiWindow); if (!d->factory) { d->factory = new KXMLGUIFactory(this, this); connect(d->factory, SIGNAL(makingChanges(bool)), this, SLOT(_k_slotFactoryMakingChanges(bool))); } return d->factory; } void KXmlGuiWindow::configureToolbars() { K_D(KXmlGuiWindow); KConfigGroup cg(KSharedConfig::openConfig(), ""); saveMainWindowSettings(cg); if (!d->toolBarEditor) { d->toolBarEditor = new KEditToolBar(guiFactory(), this); d->toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); connect(d->toolBarEditor, &KEditToolBar::newToolBarConfig, this, &KXmlGuiWindow::saveNewToolbarConfig); } d->toolBarEditor->show(); } void KXmlGuiWindow::saveNewToolbarConfig() { // createGUI(xmlFile()); // this loses any plugged-in guiclients, so we use remove+add instead. guiFactory()->removeClient(this); guiFactory()->addClient(this); KConfigGroup cg(KSharedConfig::openConfig(), ""); applyMainWindowSettings(cg); } void KXmlGuiWindow::setupGUI(StandardWindowOptions options, const QString &xmlfile) { setupGUI(QSize(), options, xmlfile); } void KXmlGuiWindow::setupGUI(const QSize &defaultSize, StandardWindowOptions options, const QString &xmlfile) { K_D(KXmlGuiWindow); if (options & Keys) { KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); } if ((options & StatusBar) && statusBar()) { createStandardStatusBarAction(); } if (options & ToolBar) { setStandardToolBarMenuEnabled(true); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); } d->defaultSize = defaultSize; if (options & Create) { createGUI(xmlfile); } if (d->defaultSize.isValid()) { resize(d->defaultSize); } else if (isHidden()) { adjustSize(); } if (options & Save) { const KConfigGroup cg(autoSaveConfigGroup()); if (cg.isValid()) { setAutoSaveSettings(cg); } else { setAutoSaveSettings(); } } } void KXmlGuiWindow::createGUI(const QString &xmlfile) { K_D(KXmlGuiWindow); // disabling the updates prevents unnecessary redraws //setUpdatesEnabled( false ); // just in case we are rebuilding, let's remove our old client guiFactory()->removeClient(this); // make sure to have an empty GUI QMenuBar *mb = menuBar(); if (mb) { mb->clear(); } qDeleteAll(toolBars()); // delete all toolbars // don't build a help menu unless the user ask for it if (d->showHelpMenu) { delete d->helpMenu; // we always want a help menu d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); KActionCollection *actions = actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); QAction *donateAction = d->helpMenu->action(KHelpMenu::menuDonate); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } if (donateAction) { actions->addAction(donateAction->objectName(), donateAction); } } const QString windowXmlFile = xmlfile.isNull() ? componentName() + QStringLiteral("ui.rc") : xmlfile; // Help beginners who call setXMLFile and then setupGUI... if (!xmlFile().isEmpty() && xmlFile() != windowXmlFile) { qCWarning(DEBUG_KXMLGUI) << "You called setXMLFile(" << xmlFile() << ") and then createGUI or setupGUI," << "which also calls setXMLFile and will overwrite the file you have previously set.\n" << "You should call createGUI(" << xmlFile() << ") or setupGUI(," << xmlFile() << ") instead."; } // we always want to load in our global standards file loadStandardsXmlFile(); // now, merge in our local xml file. setXMLFile(windowXmlFile, true); // make sure we don't have any state saved already setXMLGUIBuildDocument(QDomDocument()); // do the actual GUI building guiFactory()->reset(); guiFactory()->addClient(this); checkAmbiguousShortcuts(); // setUpdatesEnabled( true ); } void KXmlGuiWindow::slotStateChanged(const QString &newstate) { stateChanged(newstate, KXMLGUIClient::StateNoReverse); } void KXmlGuiWindow::slotStateChanged(const QString &newstate, bool reverse) { stateChanged(newstate, reverse ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); } void KXmlGuiWindow::setStandardToolBarMenuEnabled(bool enable) { K_D(KXmlGuiWindow); if (enable) { if (d->toolBarHandler) { return; } d->toolBarHandler = new KDEPrivate::ToolBarHandler(this); if (factory()) { factory()->addClient(d->toolBarHandler); } } else { if (!d->toolBarHandler) { return; } if (factory()) { factory()->removeClient(d->toolBarHandler); } delete d->toolBarHandler; d->toolBarHandler = nullptr; } } bool KXmlGuiWindow::isStandardToolBarMenuEnabled() const { K_D(const KXmlGuiWindow); return (d->toolBarHandler); } void KXmlGuiWindow::createStandardStatusBarAction() { K_D(KXmlGuiWindow); if (!d->showStatusBarAction) { d->showStatusBarAction = KStandardAction::showStatusbar(this, &KMainWindow::setSettingsDirty, actionCollection()); QStatusBar *sb = statusBar(); // Creates statusbar if it doesn't exist already. connect(d->showStatusBarAction, &QAction::toggled, sb, &QWidget::setVisible); d->showStatusBarAction->setChecked(sb->isHidden()); } else { // If the language has changed, we'll need to grab the new text and whatsThis QAction *tmpStatusBar = KStandardAction::showStatusbar(nullptr, nullptr, nullptr); d->showStatusBarAction->setText(tmpStatusBar->text()); d->showStatusBarAction->setWhatsThis(tmpStatusBar->whatsThis()); delete tmpStatusBar; } } void KXmlGuiWindow::finalizeGUI(bool /*force*/) { // FIXME: this really needs to be removed with a code more like the one we had on KDE3. // what we need to do here is to position correctly toolbars so they don't overlap. // Also, take in count plugins could provide their own toolbars and those also need to // be restored. if (autoSaveSettings() && autoSaveConfigGroup().isValid()) { applyMainWindowSettings(autoSaveConfigGroup()); } } void KXmlGuiWindow::applyMainWindowSettings(const KConfigGroup &config) { K_D(KXmlGuiWindow); KMainWindow::applyMainWindowSettings(config); QStatusBar *sb = findChild(); if (sb && d->showStatusBarAction) { d->showStatusBarAction->setChecked(!sb->isHidden()); } } // KDE5 TODO: change it to "using KXMLGUIBuilder::finalizeGUI;" in the header // and remove the reimplementation void KXmlGuiWindow::finalizeGUI(KXMLGUIClient *client) { KXMLGUIBuilder::finalizeGUI(client); } void KXmlGuiWindow::checkAmbiguousShortcuts() { QMap shortcuts; QAction *editCutAction = actionCollection()->action(QStringLiteral("edit_cut")); QAction *deleteFileAction = actionCollection()->action(QStringLiteral("deletefile")); foreach (QAction *action, actionCollection()->actions()) { if (action->isEnabled()) { foreach (const QKeySequence &shortcut, action->shortcuts()) { if (shortcut.isEmpty()) { continue; } const QString portableShortcutText = shortcut.toString(); const QAction *existingShortcutAction = shortcuts.value(portableShortcutText); if (existingShortcutAction) { // If the shortcut is already in use we give a warning, so that hopefully the developer will find it // There is one exception, if the conflicting shortcut is a non primary shortcut of "edit_cut" // and "deleteFileAction" is the other action since Shift+Delete is used for both in our default code bool showWarning = true; if ((action == editCutAction && existingShortcutAction == deleteFileAction) || (action == deleteFileAction && existingShortcutAction == editCutAction)) { QList editCutActionShortcuts = editCutAction->shortcuts(); if (editCutActionShortcuts.indexOf(shortcut) > 0) // alternate shortcut { editCutActionShortcuts.removeAll(shortcut); editCutAction->setShortcuts(editCutActionShortcuts); showWarning = false; } } if (showWarning) { const QString actionName = KLocalizedString::removeAcceleratorMarker(action->text()); const QString existingShortcutActionName = KLocalizedString::removeAcceleratorMarker(existingShortcutAction->text()); QString dontShowAgainString = existingShortcutActionName + actionName + shortcut.toString(); dontShowAgainString.remove(QLatin1Char('\\')); KMessageBox::information(this, i18n("There are two actions (%1, %2) that want to use the same shortcut (%3). This is most probably a bug. Please report it in bugs.kde.org", existingShortcutActionName, actionName, shortcut.toString(QKeySequence::NativeText)), i18n("Ambiguous Shortcuts"), dontShowAgainString, KMessageBox::Notify | KMessageBox::AllowLink); } } else { shortcuts.insert(portableShortcutText, action); } } } } } #include "moc_kxmlguiwindow.cpp" diff --git a/src/kxmlguiwindow.h b/src/kxmlguiwindow.h index 6367dd6..75e34cb 100644 --- a/src/kxmlguiwindow.h +++ b/src/kxmlguiwindow.h @@ -1,361 +1,360 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KXMLGUIWINDOW_H #define KXMLGUIWINDOW_H #include "kxmlguiclient.h" #include "kxmlguibuilder.h" #include "kmainwindow.h" -#include class KMenu; class KXMLGUIFactory; class KConfig; class KConfigGroup; class KToolBar; class KXmlGuiWindowPrivate; // TODO KF6: remove #define KDE_DEFAULT_WINDOWFLAGS 0 /** * @class KXmlGuiWindow kxmlguiwindow.h KXmlGuiWindow * * @short Top level main window with predefined action layout * * Instead of creating a KMainWindow manually and assigning menus, menu entries, * toolbar buttons and actions to it by hand, this class can be used to load an * rc file to manage the main window's actions. * * See https://techbase.kde.org/Development/Tutorials/Using_KActions#XMLGUI * for essential information on the XML file format and usage of this class. * * @see KMainWindow * @author Reginald Stadlbauer (reggie@kde.org) Stephan Kulow (coolo@kde.org), Matthias Ettrich (ettrich@kde.org), Chris Schlaeger (cs@kde.org), Sven Radej (radej@kde.org). Maintained by Sven Radej (radej@kde.org) */ class KXMLGUI_EXPORT KXmlGuiWindow : public KMainWindow, public KXMLGUIBuilder, virtual public KXMLGUIClient { XMLGUI_DECLARE_PRIVATE(KXmlGuiWindow) Q_OBJECT Q_PROPERTY(bool hasMenuBar READ hasMenuBar) Q_PROPERTY(bool autoSaveSettings READ autoSaveSettings) Q_PROPERTY(QString autoSaveGroup READ autoSaveGroup) Q_PROPERTY(bool standardToolBarMenuEnabled READ isStandardToolBarMenuEnabled WRITE setStandardToolBarMenuEnabled) public: /** * Construct a main window. * * @param parent The widget parent. This is usually 0 but it may also be the window * group leader. In that case, the KXmlGuiWindow becomes sort of a * secondary window. * * @param f Specify the window flags. The default is none. * * Note that a KXmlGuiWindow per-default is created with the * Qt::WA_DeleteOnClose attribute set, i.e. it is automatically destroyed * when the window is closed. If you do not want this behavior, call * \code * window->setAttribute(Qt::WA_DeleteOnClose, false); * \endcode * * KXmlGuiWindows must be created on the heap with 'new', like: * \code * KXmlGuiWindow *kmw = new KXmlGuiWindow(...); * kmw->setObjectName(...); * \endcode * * IMPORTANT: For session management and window management to work * properly, all main windows in the application should have a * different name. If you don't do it, the base class KMainWindow will create * a unique name, but it's recommended to explicitly pass a window name that will * also describe the type of the window. If there can be several windows of the same * type, append '#' (hash) to the name, and KMainWindow will replace it with numbers to make * the names unique. For example, for a mail client which has one main window showing * the mails and folders, and which can also have one or more windows for composing * mails, the name for the folders window should be e.g. "mainwindow" and * for the composer windows "composer#". * */ explicit KXmlGuiWindow(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); /** * \brief Destructor. * * Will also destroy the toolbars, and menubar if * needed. */ ~KXmlGuiWindow() override; /** * Enables the build of a standard help menu when calling createGUI() or setupGUI(). * * The default behavior is to build one, you must call this function * to disable it */ void setHelpMenuEnabled(bool showHelpMenu = true); /** * Return @c true when the help menu is enabled */ bool isHelpMenuEnabled() const; virtual KXMLGUIFactory *guiFactory(); /** * Create a GUI given a local XML file. In a regular app you usually want to use * setupGUI() instead of this one since it does more things for free * like setting up the toolbar/shortcut edit actions, etc. * * If @p xmlfile is an empty string, * then it will try to construct a local XML filename like * appnameui.rc where 'appname' is your app's name. If that file * does not exist, then the XML UI code will only use the global * (standard) XML file for the layout purposes. * * @param xmlfile The local xmlfile (relative or absolute) */ void createGUI(const QString &xmlfile = QString()); /** * Sets whether KMainWindow should provide a menu that allows showing/hiding * the available toolbars ( using KToggleToolBarAction ) . In case there * is only one toolbar configured a simple 'Show \' menu item * is shown. * * The menu / menu item is implemented using xmlgui. It will be inserted in your * menu structure in the 'Settings' menu. * * If your application uses a non-standard xmlgui resource file then you can * specify the exact position of the menu / menu item by adding a * <Merge name="StandardToolBarMenuHandler" /> * line to the settings menu section of your resource file ( usually appname.rc ). * * @note You should enable this feature before calling createGUI() ( or similar ). */ void setStandardToolBarMenuEnabled(bool enable); bool isStandardToolBarMenuEnabled() const; /** * Sets whether KMainWindow should provide a menu that allows showing/hiding * of the statusbar ( using KStandardAction::showStatusbar()). * * The menu / menu item is implemented using xmlgui. It will be inserted * in your menu structure in the 'Settings' menu. * * @note You should enable this feature before calling createGUI() ( or similar ). * * If an application maintains the action on its own (i.e. never calls * this function) a connection needs to be made to let KMainWindow * know when that status (hidden/shown) of the statusbar has changed. * For example: * @code * connect(action, &QAction::triggered, * kmainwindow, &KMainWindow::setSettingsDirty); * @endcode * Otherwise the status (hidden/show) of the statusbar might not be saved * by KMainWindow. */ void createStandardStatusBarAction(); /** * @see setupGUI() */ enum StandardWindowOption { /** * adds action to show/hide the toolbar(s) and adds * action to configure the toolbar(s). * @see setStandardToolBarMenuEnabled */ ToolBar = 1, /** * adds action to show the key configure action. */ Keys = 2, /** * adds action to show/hide the statusbar if the * statusbar exists. * @see createStandardStatusBarAction */ StatusBar = 4, /** * auto-saves (and loads) the toolbar/menubar/statusbar settings and * window size using the default name. * * Typically you want to let the default window size be determined by * the widgets size hints. Make sure that setupGUI() is called after * all the widgets are created ( including setCentralWidget ) so the * default size's will be correct. * @see setAutoSaveSettings */ Save = 8, /** * calls createGUI() once ToolBar, Keys and Statusbar have been * taken care of. * * @note When using KParts::MainWindow, remove this flag from the * setupGUI call, since you'll be using createGUI(part) instead. * @code * setupGUI(ToolBar | Keys | StatusBar | Save); * @endcode * @see createGUI */ Create = 16, /** * All the above option * (this is the default) */ Default = ToolBar | Keys | StatusBar | Save | Create }; Q_FLAG(StandardWindowOption) Q_DECLARE_FLAGS(StandardWindowOptions, StandardWindowOption) /** * Configures the current windows and its actions in the typical KDE * fashion. The options are all enabled by default but can be turned * off if desired through the params or if the prereqs don't exists. * * Typically this function replaces createGUI(). * * @see StandardWindowOptions * @note Since this method will restore the state of the application (toolbar, dockwindows * positions...), you need to have added all your actions to your toolbars etc before * calling to this method. (This note is only applicable if you are using the @c Default or * @c Save flag). * @warning If you are calling createGUI yourself, remember to remove the @c Create flag from * the @p options parameter. * */ void setupGUI(StandardWindowOptions options = Default, const QString &xmlfile = QString()); /** * Configures the current windows and its actions in the typical KDE * fashion. The options are all enabled by default but can be turned * off if desired through the params or if the prereqs don't exists. * * @param defaultSize The default size of the window * * Typically this function replaces createGUI(). * * @see StandardWindowOptions * @note Since this method will restore the state of the application (toolbar, dockwindows * positions...), you need to have added all your actions to your toolbars etc before * calling to this method. (This note is only applicable if you are using the @c Default or * @c Save flag). * @warning If you are calling createGUI yourself, remember to remove the @c Create flag from * the @p options parameter. Also, call setupGUI always after you call createGUI. */ void setupGUI(const QSize &defaultSize, StandardWindowOptions options = Default, const QString &xmlfile = QString()); /** * Returns a pointer to the mainwindows action responsible for the toolbars menu */ QAction *toolBarMenuAction(); /** * @internal for KToolBar */ void setupToolbarMenuActions(); // KDE5 TODO: change it to "using KXMLGUIBuilder::finalizeGUI;" void finalizeGUI(KXMLGUIClient *client) override; /** * @internal */ void finalizeGUI(bool force); // reimplemented for internal reasons void applyMainWindowSettings(const KConfigGroup &config) override; public Q_SLOTS: /** * Show a standard configure toolbar dialog. * * This slot can be connected directly to the action to configure toolbar. * This is very simple to do that by adding a single line * \code * KStandardAction::configureToolbars( this, SLOT( configureToolbars() ), * actionCollection() ); * \endcode */ virtual void configureToolbars(); /** * Apply a state change * * Enable and disable actions as defined in the XML rc file */ virtual void slotStateChanged(const QString &newstate); /** * Apply a state change * * Enable and disable actions as defined in the XML rc file, * can "reverse" the state (disable the actions which should be * enabled, and vice-versa) if specified. */ void slotStateChanged(const QString &newstate, bool reverse); protected: /** * Reimplemented to catch QEvent::Polish in order to adjust the object name * if needed, once all constructor code for the main window has run. * Also reimplemented to catch when a QDockWidget is added or removed. */ bool event(QEvent *event) override; /** * Checks if there are actions using the same shortcut. This is called * automatically from createGUI. * @since 5.30 */ void checkAmbiguousShortcuts(); protected Q_SLOTS: /** * Rebuilds the GUI after KEditToolBar changed the toolbar layout. * @see configureToolbars() */ virtual void saveNewToolbarConfig(); private: Q_PRIVATE_SLOT(k_func(), void _k_slotFactoryMakingChanges(bool)) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KXmlGuiWindow::StandardWindowOptions) #endif