File indexing completed on 2024-05-05 17:42:24

0001 /*  This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 2013 Kevin Ottens <ervin+bluesystems@kde.org>
0003     SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0004     SPDX-FileCopyrightText: 2014 Lukáš Tinkl <ltinkl@redhat.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include <config-platformtheme.h>
0010 
0011 #include "kdeplatformfiledialoghelper.h"
0012 #include "kdeplatformsystemtrayicon.h"
0013 #include "kdeplatformtheme.h"
0014 #include "kfontsettingsdata.h"
0015 #include "khintssettings.h"
0016 #include "kwaylandintegration.h"
0017 #include "x11integration.h"
0018 
0019 #include <QApplication>
0020 #include <QDBusConnection>
0021 #include <QDBusConnectionInterface>
0022 #include <QDebug>
0023 #include <QFont>
0024 #include <QPalette>
0025 #include <QString>
0026 #include <QVariant>
0027 #include <QtQuickControls2/QQuickStyle>
0028 
0029 #include <KIO/Global>
0030 #include <KLocalizedString>
0031 #include <KStandardGuiItem>
0032 #include <KWindowSystem>
0033 #include <kiconengine.h>
0034 #include <kiconloader.h>
0035 #include <kstandardshortcut.h>
0036 
0037 #include "qdbusmenubar_p.h"
0038 #include "qxdgdesktopportalfiledialog_p.h"
0039 
0040 static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
0041 static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
0042 
0043 static bool checkDBusGlobalMenuAvailable()
0044 {
0045     if (qEnvironmentVariableIsSet("KDE_NO_GLOBAL_MENU")) {
0046         return false;
0047     }
0048 
0049     QDBusConnection connection = QDBusConnection::sessionBus();
0050     QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
0051     return connection.interface()->isServiceRegistered(registrarService);
0052 }
0053 
0054 static bool isDBusGlobalMenuAvailable()
0055 {
0056     static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
0057     return dbusGlobalMenuAvailable;
0058 }
0059 
0060 KdePlatformTheme::KdePlatformTheme()
0061 {
0062     loadSettings();
0063 
0064     // explicitly not KWindowSystem::isPlatformWayland to not include the kwin process
0065     if (QGuiApplication::platformName() == QLatin1String("wayland")) {
0066         m_kwaylandIntegration.reset(new KWaylandIntegration(this));
0067     }
0068 
0069 #if HAVE_X11
0070     if (KWindowSystem::isPlatformX11()) {
0071         m_x11Integration.reset(new X11Integration(this));
0072         m_x11Integration->init();
0073     }
0074 #endif
0075 
0076     // Don't show the titlebar "What's This?" help button for dialogs that
0077     // have any UI elements with help text, unless the window specifically
0078     // requests it. This is because KDE apps with help text will use the nice
0079     // contextual tooltips instead; we only want to ever see the titlebar button
0080     // to invoke the "What's This?" feature in 3rd-party Qt apps that have set
0081     // "What's This" help text and requested the button
0082 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0083     QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton, true);
0084 #endif
0085 
0086     QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false);
0087     setQtQuickControlsTheme();
0088 }
0089 
0090 KdePlatformTheme::~KdePlatformTheme()
0091 {
0092     delete m_fontsData;
0093     delete m_hints;
0094 }
0095 
0096 QVariant KdePlatformTheme::themeHint(QPlatformTheme::ThemeHint hintType) const
0097 {
0098     QVariant hint = m_hints->hint(hintType);
0099     if (hint.isValid()) {
0100         return hint;
0101     } else {
0102         return QPlatformTheme::themeHint(hintType);
0103     }
0104 }
0105 
0106 QIcon KdePlatformTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
0107 {
0108     if (iconOptions.testFlag(DontUseCustomDirectoryIcons) && fileInfo.isDir()) {
0109         return QIcon::fromTheme(QLatin1String("inode-directory"));
0110     }
0111 
0112     return QIcon::fromTheme(KIO::iconNameForUrl(QUrl::fromLocalFile(fileInfo.absoluteFilePath())));
0113 }
0114 
0115 const QPalette *KdePlatformTheme::palette(Palette type) const
0116 {
0117     QPalette *palette = m_hints->palette(type);
0118     if (palette) {
0119         return palette;
0120     } else {
0121         return QPlatformTheme::palette(type);
0122     }
0123 }
0124 
0125 const QFont *KdePlatformTheme::font(Font type) const
0126 {
0127     KFontSettingsData::FontTypes fdtype;
0128     switch (type) {
0129     case SystemFont:
0130         fdtype = KFontSettingsData::GeneralFont;
0131         break;
0132     case MenuFont:
0133     case MenuBarFont:
0134     case MenuItemFont:
0135         fdtype = KFontSettingsData::MenuFont;
0136         break;
0137     case MessageBoxFont:
0138     case LabelFont:
0139     case TipLabelFont:
0140     case StatusBarFont:
0141     case PushButtonFont:
0142     case ItemViewFont:
0143     case ListViewFont:
0144     case HeaderViewFont:
0145     case ListBoxFont:
0146     case ComboMenuItemFont:
0147     case ComboLineEditFont:
0148         fdtype = KFontSettingsData::GeneralFont;
0149         break;
0150     case TitleBarFont:
0151     case MdiSubWindowTitleFont:
0152     case DockWidgetTitleFont:
0153         fdtype = KFontSettingsData::WindowTitleFont;
0154         break;
0155     case SmallFont:
0156     case MiniFont:
0157         fdtype = KFontSettingsData::SmallestReadableFont;
0158         break;
0159     case FixedFont:
0160         fdtype = KFontSettingsData::FixedFont;
0161         break;
0162     case ToolButtonFont:
0163         fdtype = KFontSettingsData::ToolbarFont;
0164         break;
0165     default:
0166         fdtype = KFontSettingsData::GeneralFont;
0167         break;
0168     }
0169 
0170     return m_fontsData->font(fdtype);
0171 }
0172 
0173 QIconEngine *KdePlatformTheme::createIconEngine(const QString &iconName) const
0174 {
0175     return new KIconEngine(iconName, KIconLoader::global());
0176 }
0177 
0178 void KdePlatformTheme::loadSettings()
0179 {
0180     m_fontsData = new KFontSettingsData;
0181     m_hints = new KHintsSettings;
0182 }
0183 
0184 QList<QKeySequence> KdePlatformTheme::keyBindings(QKeySequence::StandardKey key) const
0185 {
0186     switch (key) {
0187     case QKeySequence::HelpContents:
0188         return KStandardShortcut::shortcut(KStandardShortcut::Help);
0189     case QKeySequence::WhatsThis:
0190         return KStandardShortcut::shortcut(KStandardShortcut::WhatsThis);
0191     case QKeySequence::Open:
0192         return KStandardShortcut::shortcut(KStandardShortcut::Open);
0193     case QKeySequence::Close:
0194         return KStandardShortcut::shortcut(KStandardShortcut::Close);
0195     case QKeySequence::Save:
0196         return KStandardShortcut::shortcut(KStandardShortcut::Save);
0197     case QKeySequence::New:
0198         return KStandardShortcut::shortcut(KStandardShortcut::New);
0199     case QKeySequence::Cut:
0200         return KStandardShortcut::shortcut(KStandardShortcut::Cut);
0201     case QKeySequence::Copy:
0202         return KStandardShortcut::shortcut(KStandardShortcut::Copy);
0203     case QKeySequence::Paste:
0204         return KStandardShortcut::shortcut(KStandardShortcut::Paste);
0205     case QKeySequence::Undo:
0206         return KStandardShortcut::shortcut(KStandardShortcut::Undo);
0207     case QKeySequence::Redo:
0208         return KStandardShortcut::shortcut(KStandardShortcut::Redo);
0209     case QKeySequence::Back:
0210         return KStandardShortcut::shortcut(KStandardShortcut::Back);
0211     case QKeySequence::Forward:
0212         return KStandardShortcut::shortcut(KStandardShortcut::Forward);
0213     case QKeySequence::Refresh:
0214         return KStandardShortcut::shortcut(KStandardShortcut::Reload);
0215     case QKeySequence::ZoomIn:
0216         return KStandardShortcut::shortcut(KStandardShortcut::ZoomIn);
0217     case QKeySequence::ZoomOut:
0218         return KStandardShortcut::shortcut(KStandardShortcut::ZoomOut);
0219     case QKeySequence::Print:
0220         return KStandardShortcut::shortcut(KStandardShortcut::Print);
0221     case QKeySequence::Find:
0222         return KStandardShortcut::shortcut(KStandardShortcut::Find);
0223     case QKeySequence::FindNext:
0224         return KStandardShortcut::shortcut(KStandardShortcut::FindNext);
0225     case QKeySequence::FindPrevious:
0226         return KStandardShortcut::shortcut(KStandardShortcut::FindPrev);
0227     case QKeySequence::Replace:
0228         return KStandardShortcut::shortcut(KStandardShortcut::Replace);
0229     case QKeySequence::SelectAll:
0230         return KStandardShortcut::shortcut(KStandardShortcut::SelectAll);
0231     case QKeySequence::MoveToNextWord:
0232         return KStandardShortcut::shortcut(KStandardShortcut::ForwardWord);
0233     case QKeySequence::MoveToPreviousWord:
0234         return KStandardShortcut::shortcut(KStandardShortcut::BackwardWord);
0235     case QKeySequence::MoveToNextPage:
0236         return KStandardShortcut::shortcut(KStandardShortcut::Next);
0237     case QKeySequence::MoveToPreviousPage:
0238         return KStandardShortcut::shortcut(KStandardShortcut::Prior);
0239     case QKeySequence::MoveToStartOfLine:
0240         return KStandardShortcut::shortcut(KStandardShortcut::BeginningOfLine);
0241     case QKeySequence::MoveToEndOfLine:
0242         return KStandardShortcut::shortcut(KStandardShortcut::EndOfLine);
0243     case QKeySequence::MoveToStartOfDocument:
0244         return KStandardShortcut::shortcut(KStandardShortcut::Begin);
0245     case QKeySequence::MoveToEndOfDocument:
0246         return KStandardShortcut::shortcut(KStandardShortcut::End);
0247     case QKeySequence::SaveAs:
0248         return KStandardShortcut::shortcut(KStandardShortcut::SaveAs);
0249     case QKeySequence::Preferences:
0250         return KStandardShortcut::shortcut(KStandardShortcut::Preferences);
0251     case QKeySequence::Quit:
0252         return KStandardShortcut::shortcut(KStandardShortcut::Quit);
0253     case QKeySequence::FullScreen:
0254         return KStandardShortcut::shortcut(KStandardShortcut::FullScreen);
0255     case QKeySequence::Deselect:
0256         return KStandardShortcut::shortcut(KStandardShortcut::Deselect);
0257     case QKeySequence::DeleteStartOfWord:
0258         return KStandardShortcut::shortcut(KStandardShortcut::DeleteWordBack);
0259     case QKeySequence::DeleteEndOfWord:
0260         return KStandardShortcut::shortcut(KStandardShortcut::DeleteWordForward);
0261     case QKeySequence::NextChild:
0262         return KStandardShortcut::shortcut(KStandardShortcut::TabNext);
0263     case QKeySequence::PreviousChild:
0264         return KStandardShortcut::shortcut(KStandardShortcut::TabPrev);
0265     case QKeySequence::Delete:
0266         return KStandardShortcut::shortcut(KStandardShortcut::MoveToTrash);
0267     default:
0268         return QPlatformTheme::keyBindings(key);
0269     }
0270 }
0271 
0272 bool KdePlatformTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const
0273 {
0274     return type == QPlatformTheme::FileDialog && qobject_cast<QApplication *>(QCoreApplication::instance());
0275 }
0276 
0277 QString KdePlatformTheme::standardButtonText(int button) const
0278 {
0279     switch (static_cast<QPlatformDialogHelper::StandardButton>(button)) {
0280     case QPlatformDialogHelper::NoButton:
0281         qWarning() << Q_FUNC_INFO << "Unsupported standard button:" << button;
0282         return QString();
0283     case QPlatformDialogHelper::Ok:
0284         return KStandardGuiItem::ok().text();
0285     case QPlatformDialogHelper::Save:
0286         return KStandardGuiItem::save().text();
0287     case QPlatformDialogHelper::SaveAll:
0288         return i18nc("@action:button", "Save All");
0289     case QPlatformDialogHelper::Open:
0290         return KStandardGuiItem::open().text();
0291     case QPlatformDialogHelper::Yes:
0292         return i18nc("@action:button", "&Yes");
0293     case QPlatformDialogHelper::YesToAll:
0294         return i18nc("@action:button", "Yes to All");
0295     case QPlatformDialogHelper::No:
0296         return i18nc("@action:button", "&No");
0297     case QPlatformDialogHelper::NoToAll:
0298         return i18nc("@action:button", "No to All");
0299     case QPlatformDialogHelper::Abort:
0300         // FIXME KStandardGuiItem::stop() doesn't seem right here
0301         return i18nc("@action:button", "Abort");
0302     case QPlatformDialogHelper::Retry:
0303         return i18nc("@action:button", "Retry");
0304     case QPlatformDialogHelper::Ignore:
0305         return i18nc("@action:button", "Ignore");
0306     case QPlatformDialogHelper::Close:
0307         return KStandardGuiItem::close().text();
0308     case QPlatformDialogHelper::Cancel:
0309         return KStandardGuiItem::cancel().text();
0310     case QPlatformDialogHelper::Discard:
0311         return KStandardGuiItem::discard().text();
0312     case QPlatformDialogHelper::Help:
0313         return KStandardGuiItem::help().text();
0314     case QPlatformDialogHelper::Apply:
0315         return KStandardGuiItem::apply().text();
0316     case QPlatformDialogHelper::Reset:
0317         return KStandardGuiItem::reset().text();
0318     case QPlatformDialogHelper::RestoreDefaults:
0319         return KStandardGuiItem::defaults().text();
0320     default:
0321         return QPlatformTheme::defaultStandardButtonText(button);
0322     }
0323 }
0324 
0325 QPlatformDialogHelper *KdePlatformTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const
0326 {
0327     switch (type) {
0328     case QPlatformTheme::FileDialog:
0329         if (useXdgDesktopPortal()) {
0330             return new QXdgDesktopPortalFileDialog;
0331         }
0332         return new KDEPlatformFileDialogHelper;
0333     case QPlatformTheme::FontDialog:
0334     case QPlatformTheme::ColorDialog:
0335     case QPlatformTheme::MessageDialog:
0336     default:
0337         return nullptr;
0338     }
0339 }
0340 
0341 QPlatformSystemTrayIcon *KdePlatformTheme::createPlatformSystemTrayIcon() const
0342 {
0343     return new KDEPlatformSystemTrayIcon;
0344 }
0345 
0346 QPlatformMenuBar *KdePlatformTheme::createPlatformMenuBar() const
0347 {
0348     if (isDBusGlobalMenuAvailable()) {
0349 #ifndef KF6_TODO_DBUS_MENUBAR
0350         auto *menu = new QDBusMenuBar(const_cast<KdePlatformTheme *>(this));
0351 
0352         QObject::connect(menu, &QDBusMenuBar::windowChanged, menu, [this, menu](QWindow *newWindow, QWindow *oldWindow) {
0353             const QString &serviceName = QDBusConnection::sessionBus().baseService();
0354             const QString &objectPath = menu->objectPath();
0355 
0356             setMenuBarForWindow(oldWindow, {}, {});
0357             setMenuBarForWindow(newWindow, serviceName, objectPath);
0358         });
0359 
0360         return menu;
0361 #endif
0362     }
0363 
0364     return nullptr;
0365 }
0366 
0367 // force QtQuickControls2 to use the desktop theme as default
0368 void KdePlatformTheme::setQtQuickControlsTheme()
0369 {
0370     // if the user is running only a QGuiApplication, explicitly unset the QQC1 desktop style and abort
0371     // as this style is all about QWidgets and we know setting this will make it crash
0372     if (!qobject_cast<QApplication *>(qApp)) {
0373         if (qgetenv("QT_QUICK_CONTROLS_1_STYLE").right(7) == "Desktop") {
0374             qunsetenv("QT_QUICK_CONTROLS_1_STYLE");
0375         }
0376         return;
0377     }
0378     // if the user has explicitly set something else, don't meddle
0379     if (!QQuickStyle::name().isEmpty()) {
0380         return;
0381     }
0382     QQuickStyle::setStyle(QLatin1String("org.kde.desktop"));
0383 }
0384 
0385 bool KdePlatformTheme::useXdgDesktopPortal()
0386 {
0387     static bool usePortal = qEnvironmentVariableIntValue("PLASMA_INTEGRATION_USE_PORTAL") == 1;
0388     return usePortal;
0389 }
0390 
0391 inline bool windowRelevantForGlobalMenu(QWindow *window)
0392 {
0393     return !(window->type() & Qt::WindowType::Popup);
0394 }
0395 
0396 void KdePlatformTheme::globalMenuBarExistsNow()
0397 {
0398 #ifndef KF6_TODO_DBUS_MENUBAR
0399     const QString &serviceName = QDBusConnection::sessionBus().baseService();
0400     const QString &objectPath = QDBusMenuBar::globalMenuBar()->objectPath();
0401 
0402     for (auto *window : qApp->topLevelWindows()) {
0403         if (QDBusMenuBar::menuBarForWindow(window))
0404             continue;
0405         if (!windowRelevantForGlobalMenu(window))
0406             return;
0407 
0408         setMenuBarForWindow(window, serviceName, objectPath);
0409     }
0410 #endif
0411 }
0412 
0413 void KdePlatformTheme::windowCreated(QWindow *window)
0414 {
0415 #ifndef KF6_TODO_DBUS_MENUBAR
0416     if (!QDBusMenuBar::globalMenuBar())
0417         return;
0418 
0419     if (QDBusMenuBar::menuBarForWindow(window))
0420         return;
0421 
0422     const QString &serviceName = QDBusConnection::sessionBus().baseService();
0423     const QString &objectPath = QDBusMenuBar::globalMenuBar()->objectPath();
0424 
0425     setMenuBarForWindow(window, serviceName, objectPath);
0426 #endif
0427 }
0428 
0429 void KdePlatformTheme::globalMenuBarNoLongerExists()
0430 {
0431 #ifndef KF6_TODO_DBUS_MENUBAR
0432     for (auto *window : qApp->topLevelWindows()) {
0433         if (QDBusMenuBar::menuBarForWindow(window))
0434             continue;
0435         if (!windowRelevantForGlobalMenu(window))
0436             return;
0437 
0438         setMenuBarForWindow(window, {}, {});
0439     }
0440 #endif
0441 }
0442 
0443 void KdePlatformTheme::setMenuBarForWindow(QWindow *window, const QString &serviceName, const QString &objectPath) const
0444 {
0445     if (!window)
0446         return;
0447 
0448     if (m_x11Integration) {
0449         m_x11Integration->setWindowProperty(window, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8());
0450         m_x11Integration->setWindowProperty(window, s_x11AppMenuObjectPathPropertyName, objectPath.toUtf8());
0451     }
0452 
0453     if (m_kwaylandIntegration) {
0454         m_kwaylandIntegration->setAppMenu(window, serviceName, objectPath);
0455     }
0456 }