/* SPDX-FileCopyrightText: 2002 Joseph Wenninger SPDX-FileCopyrightText: 2020 Méven Car SPDX-FileCopyrightText: 2020 Tobias Fella SPDX-FileCopyrightText: 2022 Méven Car SPDX-License-Identifier: GPL-2.0-or-later */ #include "componentchooser.h" #include #include #include #include #include #include #include #include #include #include #include extern KSERVICE_EXPORT int ksycoca_ms_between_checks; ComponentChooser::ComponentChooser(QObject *parent, const QString &mimeType, const QString &applicationCategory, const QString &defaultApplication, const QString &dialogText) : QObject(parent) , m_mimeType(mimeType) , m_applicationCategory(applicationCategory) , m_defaultApplication(defaultApplication) , m_dialogText(dialogText) { qRegisterMetaType>("QList"); m_model = new ApplicationModel(this); connect(m_model, &QAbstractItemModel::modelReset, this, &ComponentChooser::modelChanged); } void ComponentChooser::defaults() { const auto defaultIndex = m_model->defaultIndex(); if (defaultIndex) { select(*defaultIndex); } } void ComponentChooser::load() { m_model->load(m_mimeType, m_applicationCategory, m_defaultApplication, KApplicationTrader::preferredService(m_mimeType)); m_index = m_model->currentIndex(); m_currentApplication = currentStorageId(); Q_EMIT indexChanged(); Q_EMIT isDefaultsChanged(); } void ComponentChooser::select(int index) { // Other selection if (index == m_model->rowCount() - 1) { KOpenWithDialog *dialog = new KOpenWithDialog(QList(), m_mimeType, m_dialogText, QString(), QApplication::activeWindow()); dialog->setSaveNewApplications(true); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &KOpenWithDialog::finished, this, [this, dialog](int result) { if (result == QDialog::Rejected) { Q_EMIT indexChanged(); Q_EMIT isDefaultsChanged(); return; } const auto serviceStorageId = dialog->service()->storageId(); // Check if the selected application is already in the list QModelIndex modelIndex = m_model->findByStorageId(serviceStorageId); if (modelIndex.isValid()) { select(modelIndex.row()); return; } auto newIndex = m_model->addApplicationBeforeLast(dialog->service()); select(newIndex); }); dialog->open(); } else { m_index = index; const auto selectedIndex = m_model->index(index, 0); m_model->setData(selectedIndex, true, ApplicationModel::Selected); Q_EMIT indexChanged(); Q_EMIT isDefaultsChanged(); Q_EMIT modelChanged(); } } void ComponentChooser::saveMimeTypeAssociations(const QString &storageId, const QStringList &mimeTypes, bool forceUnsupportedMimeType) { if (storageId.isEmpty()) { return; } // This grabs the configuration from mimeapps.list, which is DE agnostic and part of the XDG standard. KSharedConfig::Ptr mimeAppsList = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); if (mimeAppsList->isConfigWritable(true)) { const auto appService = KService::serviceByStorageId(storageId); for (const QString &mimeType : mimeTypes) { if (!forceUnsupportedMimeType && appService && !serviceSupportsMimeType(appService, mimeType)) { // skip mimetype association if the app does not support it at all continue; } KApplicationTrader::setPreferredService(mimeType, appService); } m_currentApplication = storageId; } } void ComponentChooser::onSaved() { Q_EMIT indexChanged(); Q_EMIT isDefaultsChanged(); } QStringList ComponentChooser::unsupportedMimeTypes() const { const auto preferredApp = currentStorageId(); if (preferredApp.isEmpty()) { return QStringList{}; } const auto db = QMimeDatabase(); QStringList unsupportedMimeTypes; const auto appService = KService::serviceByStorageId(preferredApp); const auto componentMimeTypes = mimeTypes(); for (const QString &mimeType : componentMimeTypes) { if (!serviceSupportsMimeType(appService, mimeType)) { const auto preferredService = KApplicationTrader::preferredService(mimeType); if (!preferredService || (preferredService->storageId() != appService->storageId())) { unsupportedMimeTypes << mimeType; } } } return unsupportedMimeTypes; } void ComponentChooser::saveAssociationUnsuportedMimeTypes() { const auto storageId = currentStorageId(); saveMimeTypeAssociations(storageId, unsupportedMimeTypes(), true); forceReloadServiceCache(); load(); } QList ComponentChooser::mimeTypesNotAssociated() const { auto ret = QList(); const auto db = QMimeDatabase(); const auto storageId = m_currentApplication; const auto appService = KService::serviceByStorageId(storageId); if (!appService) { return ret; } const auto mimes = mimeTypes(); for (const QString &mimeType : mimes) { const auto service = KApplicationTrader::preferredService(mimeType); if (service && service->storageId() != storageId && // only explicitly supported mimetype serviceSupportsMimeType(appService, mimeType)) { ret.append(PairQml(service->name(), mimeType)); } } return ret; } void ComponentChooser::saveMimeTypesNotAssociated() { const auto mimeTypeNotAssociated = mimeTypesNotAssociated(); auto mimeTypeList = QList(); for (const auto &pair : mimeTypeNotAssociated) { mimeTypeList.append(pair.second.toString()); } const auto storageId = currentStorageId(); saveMimeTypeAssociations(storageId, mimeTypeList); forceReloadServiceCache(); load(); } QString ComponentChooser::currentStorageId() const { return m_model->data(m_index, ApplicationModel::StorageId).toString(); } QString ComponentChooser::applicationName() const { return m_model->data(m_index, Qt::DisplayRole).toString(); } QString ComponentChooser::applicationIcon() const { return m_model->data(m_index, ApplicationModel::Icon).toString(); } QStringList ComponentChooser::mimeTypes() const { return QStringList{}; } void ComponentChooser::forceReloadServiceCache() { KBuildSycocaProgressDialog::rebuildKSycoca(QApplication::activeWindow()); // HACK to ensure mime cache is updated right away int previous_delay = ksycoca_ms_between_checks; ksycoca_ms_between_checks = 0; KService::allServices(); ksycoca_ms_between_checks = previous_delay; } void ComponentChooser::save() { // default impl for simple application kinds const auto storageId = currentStorageId(); saveMimeTypeAssociations(storageId, mimeTypes()); } bool ComponentChooser::isDefaults() const { const auto defaultIndex = m_model->defaultIndex(); return !defaultIndex.has_value() || *defaultIndex == m_index; } bool ComponentChooser::isSaveNeeded() const { const auto storageId = currentStorageId(); return m_model->rowCount() > 1 && (m_currentApplication != storageId) && storageId != ""; } bool ComponentChooser::serviceSupportsMimeType(KService::Ptr service, const QString &targetMimeType) { static QMimeDatabase db; const QStringList mimeTypes = service->mimeTypes(); for (const QString &supportedMimeType : mimeTypes) { if (supportedMimeType == targetMimeType) { return true; } if (db.mimeTypeForName(targetMimeType).inherits(supportedMimeType)) { return true; } } if (targetMimeType.startsWith(QLatin1String("x-scheme-handler/")) && service->schemeHandlers().contains(targetMimeType.mid(17))) { return true; } return false; }