File indexing completed on 2024-11-17 05:01:36

0001 /*  This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 2013 Alejandro Fiestas Olivares <afiestas@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "../src/platformtheme/kdeplatformtheme.h"
0008 #include "../src/platformtheme/khintssettings.h"
0009 #include "kdeplatformtheme_config.h"
0010 #include <config-platformtheme.h>
0011 #undef HAVE_X11
0012 #define HAVE_X11 0
0013 
0014 #include <QApplication>
0015 #include <QDialogButtonBox>
0016 #include <QDir>
0017 #include <QDrag>
0018 #include <QFile>
0019 #include <QIconEngine>
0020 #include <QMimeData>
0021 #include <QPalette>
0022 #include <QStandardPaths>
0023 #include <QString>
0024 #include <QTest>
0025 #include <QToolButton>
0026 #include <Qt>
0027 
0028 #include <KWindowSystem>
0029 #include <QDBusConnection>
0030 #include <QDBusMessage>
0031 #include <QTimer>
0032 
0033 #include <KIconTheme>
0034 #include <KWindowInfo>
0035 #include <kiconloader.h>
0036 
0037 static void prepareEnvironment()
0038 {
0039     QStandardPaths::setTestModeEnabled(true);
0040 
0041     QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0042 
0043     if (!QDir(configPath).mkpath(QStringLiteral("."))) {
0044         qFatal("Failed to create test configuration directory.");
0045     }
0046 
0047     configPath.append("/kdeglobals");
0048 
0049     QFile::remove(configPath);
0050     if (!QFile::copy(CONFIGFILE, configPath)) {
0051         qFatal("Failed to copy kdeglobals required for tests.");
0052     }
0053 }
0054 
0055 Q_CONSTRUCTOR_FUNCTION(prepareEnvironment)
0056 
0057 class EventTest : public QObject
0058 {
0059 public:
0060     EventTest(QObject *tested, QEvent::Type type)
0061         : QObject()
0062         , gotEvent(false)
0063         , m_type(type)
0064     {
0065         tested->installEventFilter(this);
0066     }
0067 
0068     bool eventFilter(QObject *, QEvent *e) override
0069     {
0070         if (e->type() == m_type) {
0071             gotEvent = true;
0072         }
0073         return false;
0074     }
0075 
0076     bool gotEvent;
0077     QEvent::Type m_type;
0078 };
0079 
0080 class KdePlatformTheme_UnitTest : public QObject
0081 {
0082     Q_OBJECT
0083 public:
0084     KdePlatformTheme_UnitTest()
0085     {
0086     }
0087 
0088 private:
0089     void sendNotifyChange(KHintsSettings::ChangeType type, int arg = -1)
0090     {
0091         QDBusMessage message =
0092             QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange"));
0093         QList<QVariant> args;
0094         args.append(static_cast<int>(type));
0095         if (arg >= 0) {
0096             args.append(arg);
0097         }
0098         message.setArguments(args);
0099         QDBusConnection::sessionBus().send(message);
0100     }
0101 
0102     QEventLoop m_loop;
0103     QToolButton m_toolBtn;
0104     KdePlatformTheme *m_qpa;
0105 private Q_SLOTS:
0106     void initTestCase()
0107     {
0108         m_qpa = new KdePlatformTheme();
0109         QDBusConnection::sessionBus().connect(QString(),
0110                                               QStringLiteral("/KGlobalSettings"),
0111                                               QStringLiteral("org.kde.KGlobalSettings"),
0112                                               QStringLiteral("notifyChange"),
0113                                               &m_loop,
0114                                               SLOT(quit()));
0115     }
0116 
0117     void cleanupTestCase()
0118     {
0119         QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0120         configPath.append("/kdeglobals");
0121 
0122         QFile::remove(configPath);
0123     }
0124 
0125     void testPlatformHints()
0126     {
0127         QCOMPARE(qApp->cursorFlashTime(), 1042);
0128         QCOMPARE(qApp->doubleClickInterval(), 4343);
0129         QCOMPARE(qApp->startDragDistance(), 15);
0130         QCOMPARE(qApp->startDragTime(), 555);
0131         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ToolButtonStyle).toInt(), (int)Qt::ToolButtonTextOnly);
0132         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ToolBarIconSize).toInt(), 2);
0133         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ItemViewActivateItemOnSingleClick).toBool(), false);
0134         QCOMPARE(m_qpa->themeHint(QPlatformTheme::SystemIconThemeName).toString(), QLatin1String("non-existent-icon-theme"));
0135         QCOMPARE(m_qpa->themeHint(QPlatformTheme::SystemIconFallbackThemeName).toString(), QLatin1String("hicolor"));
0136 
0137         QStringList iconThemeSearchPaths = m_qpa->themeHint(QPlatformTheme::IconThemeSearchPaths).toStringList();
0138         foreach (const QString &iconPath, iconThemeSearchPaths) {
0139             QVERIFY(iconPath.endsWith(QLatin1String("/icons")) || iconPath.endsWith(QLatin1String("/.icons")));
0140             QVERIFY(QFile::exists(iconPath));
0141         }
0142         // there must be *some* icons in XDG_DATA_DIRS, right?
0143         QVERIFY(!iconThemeSearchPaths.isEmpty());
0144 
0145         QStringList styles;
0146         styles << QStringLiteral("non-existent-widget-style") << QStringLiteral("breeze") << QStringLiteral("oxygen") << QStringLiteral("fusion")
0147                << QStringLiteral("windows");
0148         QCOMPARE(m_qpa->themeHint(QPlatformTheme::StyleNames).toStringList(), styles);
0149         QCOMPARE(m_qpa->themeHint(QPlatformTheme::DialogButtonBoxLayout).toInt(), (int)QDialogButtonBox::KdeLayout);
0150         QCOMPARE(m_qpa->themeHint(QPlatformTheme::DialogButtonBoxButtonsHaveIcons).toBool(), false);
0151         QCOMPARE(m_qpa->themeHint(QPlatformTheme::UseFullScreenForPopupMenu).toBool(), true);
0152         QCOMPARE(m_qpa->themeHint(QPlatformTheme::KeyboardScheme).toInt(), (int)QPlatformTheme::KdeKeyboardScheme);
0153         QCOMPARE(m_qpa->themeHint(QPlatformTheme::UiEffects).toInt(), 0);
0154         QCOMPARE(m_qpa->themeHint(QPlatformTheme::IconPixmapSizes).value<QList<int>>(), QList<int>() << 512 << 256 << 128 << 64 << 32 << 22 << 16 << 8);
0155 
0156         QCOMPARE(qApp->wheelScrollLines(), 1234);
0157         QCOMPARE(qApp->testAttribute(Qt::AA_DontShowIconsInMenus), false);
0158     }
0159 
0160     void testPlatformPalette()
0161     {
0162         const QPalette palette = qApp->palette();
0163         QPalette::ColorGroup states[3] = {QPalette::Active, QPalette::Inactive, QPalette::Disabled};
0164         QColor greenColor(QColor(0, 128, 0));
0165         QBrush greenBrush(greenColor);
0166         for (int i = 0; i < 3; i++) {
0167             QCOMPARE(palette.brush(states[i], QPalette::ButtonText), greenBrush);
0168             QCOMPARE(palette.brush(states[i], QPalette::WindowText), greenBrush);
0169             QCOMPARE(palette.brush(states[i], QPalette::Window), greenBrush);
0170             QCOMPARE(palette.brush(states[i], QPalette::Base), greenBrush);
0171             QCOMPARE(palette.brush(states[i], QPalette::Text), greenBrush);
0172             QCOMPARE(palette.brush(states[i], QPalette::Button), greenBrush);
0173             QCOMPARE(palette.brush(states[i], QPalette::ButtonText), greenBrush);
0174             QCOMPARE(palette.brush(states[i], QPalette::Highlight), greenBrush);
0175             QCOMPARE(palette.brush(states[i], QPalette::HighlightedText), greenBrush);
0176             QCOMPARE(palette.brush(states[i], QPalette::ToolTipBase), greenBrush);
0177             QCOMPARE(palette.brush(states[i], QPalette::ToolTipText), greenBrush);
0178 
0179             // KColorScheme applies modifications and we can't disable them, so I extracted
0180             // the values and blindly compare them.
0181             QCOMPARE(palette.color(states[i], QPalette::Light).green(), 162);
0182             QCOMPARE(palette.color(states[i], QPalette::Midlight).green(), 144);
0183             QCOMPARE(palette.color(states[i], QPalette::Mid).green(), 109);
0184             QCOMPARE(palette.color(states[i], QPalette::Dark).green(), 62);
0185             QCOMPARE(palette.color(states[i], QPalette::Shadow).green(), 43);
0186 
0187             QCOMPARE(palette.brush(states[i], QPalette::AlternateBase), greenBrush);
0188             QCOMPARE(palette.brush(states[i], QPalette::Link), greenBrush);
0189             QCOMPARE(palette.brush(states[i], QPalette::LinkVisited), greenBrush);
0190         }
0191     }
0192 
0193     void testPlatformIconEngine()
0194     {
0195         QIconEngine *engine = m_qpa->createIconEngine(QStringLiteral("test-icon"));
0196         QCOMPARE(engine->key(), QStringLiteral("KIconEngine"));
0197     }
0198 
0199     void testPlatformIconEngineTheme()
0200     {
0201         // The current theme should be what we defined.
0202         KdePlatformTheme().createIconEngine(QStringLiteral("test-icon"));
0203         QCOMPARE(KIconLoader::global()->theme()->current(), QStringLiteral("non-existent-icon-theme"));
0204     }
0205 
0206     void testPlatformIconChanges()
0207     {
0208         QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0209         configPath.append("/kdeglobals");
0210         QFile::remove(configPath);
0211         QFile::copy(CHANGED_CONFIGFILE, configPath);
0212 
0213         QDBusConnection::sessionBus()
0214             .connect(QString(), QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"), &m_loop, SLOT(quit()));
0215 
0216         QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
0217         message.setArguments(QList<QVariant>() << int(KIconLoader::MainToolbar));
0218         QDBusConnection::sessionBus().send(message);
0219         m_loop.exec();
0220 
0221         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ToolBarIconSize).toInt(), 11);
0222     }
0223 
0224     void testPlatformHintChanges()
0225     {
0226         EventTest tester(&m_toolBtn, QEvent::StyleChange);
0227         sendNotifyChange(KHintsSettings::SettingsChanged, KHintsSettings::SETTINGS_QT);
0228         m_loop.exec();
0229 
0230         QCOMPARE(qApp->cursorFlashTime(), 1022);
0231 
0232         sendNotifyChange(KHintsSettings::SettingsChanged, KHintsSettings::SETTINGS_MOUSE);
0233         m_loop.exec();
0234 
0235         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ItemViewActivateItemOnSingleClick).toBool(), true);
0236         QCOMPARE(qApp->doubleClickInterval(), 401);
0237         QCOMPARE(qApp->startDragDistance(), 35);
0238         QCOMPARE(qApp->startDragTime(), 501);
0239 
0240         QCOMPARE(qApp->wheelScrollLines(), 122);
0241         QCOMPARE(qApp->testAttribute(Qt::AA_DontShowIconsInMenus), true);
0242 
0243         sendNotifyChange(KHintsSettings::ToolbarStyleChanged, 2);
0244         m_loop.exec();
0245 
0246         QCOMPARE(m_qpa->themeHint(QPlatformTheme::ToolButtonStyle).toInt(), (int)Qt::ToolButtonTextUnderIcon);
0247         QCOMPARE(tester.gotEvent, true);
0248 
0249         sendNotifyChange(KHintsSettings::StyleChanged, 2);
0250         m_loop.exec();
0251 
0252         QStringList styles;
0253         styles << QStringLiteral("another-non-existent-widget-style") << QStringLiteral("breeze") << QStringLiteral("oxygen") << QStringLiteral("fusion")
0254                << QStringLiteral("windows");
0255         QCOMPARE(m_qpa->themeHint(QPlatformTheme::StyleNames).toStringList(), styles);
0256 
0257         sendNotifyChange(KHintsSettings::SettingsChanged, KHintsSettings::SETTINGS_STYLE);
0258         m_loop.exec();
0259 
0260         QCOMPARE(m_qpa->themeHint(QPlatformTheme::DialogButtonBoxButtonsHaveIcons).toBool(), true);
0261 
0262         sendNotifyChange(KHintsSettings::IconChanged, 4);
0263         m_loop.exec();
0264 
0265         QCOMPARE(m_qpa->themeHint(QPlatformTheme::SystemIconThemeName).toString(), QLatin1String("other-non-existent"));
0266     }
0267 
0268     void testPlatformPaletteChanges()
0269     {
0270         EventTest tester(QGuiApplication::instance(), QEvent::ApplicationPaletteChange);
0271         sendNotifyChange(KHintsSettings::PaletteChanged, 0);
0272         m_loop.exec();
0273         QCOMPARE(tester.gotEvent, true);
0274 
0275         const QPalette *palette = m_qpa->palette();
0276         QPalette::ColorGroup states[3] = {QPalette::Active, QPalette::Inactive, QPalette::Disabled};
0277         QColor redColor(QColor(174, 11, 11));
0278         QBrush redBrush(redColor);
0279         for (int i = 0; i < 3; i++) {
0280             QCOMPARE(palette->brush(states[i], QPalette::ButtonText), redBrush);
0281             QCOMPARE(palette->brush(states[i], QPalette::WindowText), redBrush);
0282             QCOMPARE(palette->brush(states[i], QPalette::Window), redBrush);
0283             QCOMPARE(palette->brush(states[i], QPalette::Base), redBrush);
0284             QCOMPARE(palette->brush(states[i], QPalette::Text), redBrush);
0285             QCOMPARE(palette->brush(states[i], QPalette::Button), redBrush);
0286             QCOMPARE(palette->brush(states[i], QPalette::ButtonText), redBrush);
0287             QCOMPARE(palette->brush(states[i], QPalette::Highlight), redBrush);
0288             QCOMPARE(palette->brush(states[i], QPalette::HighlightedText), redBrush);
0289             QCOMPARE(palette->brush(states[i], QPalette::ToolTipBase), redBrush);
0290             QCOMPARE(palette->brush(states[i], QPalette::ToolTipText), redBrush);
0291 
0292             // KColorScheme applies modifications and we can't disable them, so I extracted
0293             // the values and blindly compare them.
0294             QCOMPARE(palette->color(states[i], QPalette::Light).red(), 230);
0295             QCOMPARE(palette->color(states[i], QPalette::Midlight).red(), 203);
0296             QCOMPARE(palette->color(states[i], QPalette::Mid).red(), 149);
0297             QCOMPARE(palette->color(states[i], QPalette::Dark).red(), 84);
0298             QCOMPARE(palette->color(states[i], QPalette::Shadow).red(), 60);
0299 
0300             QCOMPARE(palette->brush(states[i], QPalette::AlternateBase), redBrush);
0301             QCOMPARE(palette->brush(states[i], QPalette::Link), redBrush);
0302             QCOMPARE(palette->brush(states[i], QPalette::LinkVisited), redBrush);
0303         }
0304     }
0305 
0306     void dndWindowFlagsTest()
0307     {
0308         if (!KWindowSystem::isPlatformX11()) {
0309             QSKIP("This test requires xcb platform.");
0310         }
0311 
0312         QDrag *drag = new QDrag(this);
0313         QMimeData *data = new QMimeData;
0314         data->setData("Text/Plain", "drag data");
0315         drag->setMimeData(data);
0316         bool succeeded = false;
0317         QTimer::singleShot(1000, [&succeeded] {
0318             auto windows = QGuiApplication::allWindows();
0319             auto it = std::find_if(windows.constBegin(), windows.constEnd(), [](QWindow *w) {
0320                 return w->inherits("QShapedPixmapWindow");
0321             });
0322             if (it != windows.constEnd()) {
0323                 KWindowInfo info((*it)->winId(), NET::WMWindowType);
0324                 succeeded = info.windowType(NET::DNDIconMask) == NET::DNDIcon;
0325             }
0326             QTest::keyClick(windows.first(), Qt::Key_Escape);
0327         });
0328         drag->exec();
0329         QVERIFY(succeeded);
0330     }
0331 };
0332 
0333 QTEST_MAIN(KdePlatformTheme_UnitTest)
0334 
0335 #include "kdeplatformtheme_unittest.moc"