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 }