File indexing completed on 2024-05-12 05:35:38
0001 /* 0002 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org> 0003 SPDX-FileCopyrightText: 2020 Méven Car <meven.car@kdemail.net> 0004 SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de> 0005 SPDX-FileCopyrightText: 2022 Méven Car <meven@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "componentchooser.h" 0011 0012 #include <QApplication> 0013 #include <QDBusConnection> 0014 #include <QDBusMessage> 0015 #include <QMimeDatabase> 0016 0017 #include <KApplicationTrader> 0018 #include <KBuildSycocaProgressDialog> 0019 #include <KConfigGroup> 0020 #include <KOpenWithDialog> 0021 #include <KQuickConfigModule> 0022 #include <KService> 0023 #include <KSharedConfig> 0024 0025 extern KSERVICE_EXPORT int ksycoca_ms_between_checks; 0026 0027 ComponentChooser::ComponentChooser(QObject *parent, 0028 const QString &mimeType, 0029 const QString &applicationCategory, 0030 const QString &defaultApplication, 0031 const QString &dialogText) 0032 : QObject(parent) 0033 , m_mimeType(mimeType) 0034 , m_applicationCategory(applicationCategory) 0035 , m_defaultApplication(defaultApplication) 0036 , m_dialogText(dialogText) 0037 { 0038 qRegisterMetaType<QList<PairQml>>("QList<PairQml>"); 0039 0040 m_model = new ApplicationModel(this); 0041 connect(m_model, &QAbstractItemModel::modelReset, this, &ComponentChooser::modelChanged); 0042 } 0043 0044 void ComponentChooser::defaults() 0045 { 0046 const auto defaultIndex = m_model->defaultIndex(); 0047 if (defaultIndex) { 0048 select(*defaultIndex); 0049 } 0050 } 0051 0052 void ComponentChooser::load() 0053 { 0054 m_model->load(m_mimeType, m_applicationCategory, m_defaultApplication, KApplicationTrader::preferredService(m_mimeType)); 0055 0056 m_index = m_model->currentIndex(); 0057 0058 m_currentApplication = currentStorageId(); 0059 0060 Q_EMIT indexChanged(); 0061 Q_EMIT isDefaultsChanged(); 0062 } 0063 0064 void ComponentChooser::select(int index) 0065 { 0066 // Other selection 0067 if (index == m_model->rowCount() - 1) { 0068 KOpenWithDialog *dialog = new KOpenWithDialog(QList<QUrl>(), m_mimeType, m_dialogText, QString(), QApplication::activeWindow()); 0069 dialog->setSaveNewApplications(true); 0070 dialog->setAttribute(Qt::WA_DeleteOnClose); 0071 connect(dialog, &KOpenWithDialog::finished, this, [this, dialog](int result) { 0072 if (result == QDialog::Rejected) { 0073 Q_EMIT indexChanged(); 0074 Q_EMIT isDefaultsChanged(); 0075 return; 0076 } 0077 0078 const auto serviceStorageId = dialog->service()->storageId(); 0079 0080 // Check if the selected application is already in the list 0081 QModelIndex modelIndex = m_model->findByStorageId(serviceStorageId); 0082 if (modelIndex.isValid()) { 0083 select(modelIndex.row()); 0084 return; 0085 } 0086 0087 auto newIndex = m_model->addApplicationBeforeLast(dialog->service()); 0088 select(newIndex); 0089 }); 0090 dialog->open(); 0091 } else { 0092 m_index = index; 0093 0094 const auto selectedIndex = m_model->index(index, 0); 0095 m_model->setData(selectedIndex, true, ApplicationModel::Selected); 0096 0097 Q_EMIT indexChanged(); 0098 Q_EMIT isDefaultsChanged(); 0099 } 0100 } 0101 0102 void ComponentChooser::saveMimeTypeAssociations(const QString &storageId, const QStringList &mimeTypes, bool forceUnsupportedMimeType) 0103 { 0104 if (storageId.isEmpty()) { 0105 return; 0106 } 0107 0108 // This grabs the configuration from mimeapps.list, which is DE agnostic and part of the XDG standard. 0109 KSharedConfig::Ptr mimeAppsList = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); 0110 0111 if (mimeAppsList->isConfigWritable(true)) { 0112 const auto appService = KService::serviceByStorageId(storageId); 0113 0114 for (const QString &mimeType : mimeTypes) { 0115 if (!forceUnsupportedMimeType && appService && !serviceSupportsMimeType(appService, mimeType)) { 0116 // skip mimetype association if the app does not support it at all 0117 continue; 0118 } 0119 0120 KApplicationTrader::setPreferredService(mimeType, appService); 0121 } 0122 0123 m_currentApplication = storageId; 0124 } 0125 } 0126 0127 void ComponentChooser::onSaved() 0128 { 0129 Q_EMIT indexChanged(); 0130 Q_EMIT isDefaultsChanged(); 0131 } 0132 0133 QStringList ComponentChooser::unsupportedMimeTypes() const 0134 { 0135 const auto preferredApp = currentStorageId(); 0136 0137 if (preferredApp.isEmpty()) { 0138 return QStringList{}; 0139 } 0140 0141 const auto db = QMimeDatabase(); 0142 0143 QStringList unsupportedMimeTypes; 0144 0145 const auto appService = KService::serviceByStorageId(preferredApp); 0146 const auto componentMimeTypes = mimeTypes(); 0147 for (const QString &mimeType : componentMimeTypes) { 0148 if (!serviceSupportsMimeType(appService, mimeType)) { 0149 const auto preferredService = KApplicationTrader::preferredService(mimeType); 0150 if (!preferredService || (preferredService->storageId() != appService->storageId())) { 0151 unsupportedMimeTypes << mimeType; 0152 } 0153 } 0154 } 0155 0156 return unsupportedMimeTypes; 0157 } 0158 0159 void ComponentChooser::saveAssociationUnsuportedMimeTypes() 0160 { 0161 const auto storageId = currentStorageId(); 0162 0163 saveMimeTypeAssociations(storageId, unsupportedMimeTypes(), true); 0164 forceReloadServiceCache(); 0165 load(); 0166 } 0167 0168 QList<PairQml> ComponentChooser::mimeTypesNotAssociated() const 0169 { 0170 auto ret = QList<PairQml>(); 0171 0172 const auto db = QMimeDatabase(); 0173 const auto storageId = m_currentApplication; 0174 0175 const auto appService = KService::serviceByStorageId(storageId); 0176 0177 if (!appService) { 0178 return ret; 0179 } 0180 0181 const auto mimes = mimeTypes(); 0182 0183 for (const QString &mimeType : mimes) { 0184 const auto service = KApplicationTrader::preferredService(mimeType); 0185 0186 if (service && service->storageId() != storageId && 0187 // only explicitly supported mimetype 0188 serviceSupportsMimeType(appService, mimeType)) { 0189 ret.append(PairQml(service->name(), mimeType)); 0190 } 0191 } 0192 0193 return ret; 0194 } 0195 0196 void ComponentChooser::saveMimeTypesNotAssociated() 0197 { 0198 const auto mimeTypeNotAssociated = mimeTypesNotAssociated(); 0199 0200 auto mimeTypeList = QList<QString>(); 0201 for (const auto &pair : mimeTypeNotAssociated) { 0202 mimeTypeList.append(pair.second.toString()); 0203 } 0204 0205 const auto storageId = currentStorageId(); 0206 saveMimeTypeAssociations(storageId, mimeTypeList); 0207 0208 forceReloadServiceCache(); 0209 load(); 0210 } 0211 0212 QString ComponentChooser::currentStorageId() const 0213 { 0214 return m_model->data(m_index, ApplicationModel::StorageId).toString(); 0215 } 0216 0217 QString ComponentChooser::applicationName() const 0218 { 0219 return m_model->data(m_index, Qt::DisplayRole).toString(); 0220 } 0221 0222 QString ComponentChooser::applicationIcon() const 0223 { 0224 return m_model->data(m_index, ApplicationModel::Icon).toString(); 0225 } 0226 0227 QStringList ComponentChooser::mimeTypes() const 0228 { 0229 return QStringList{}; 0230 } 0231 0232 void ComponentChooser::forceReloadServiceCache() 0233 { 0234 KBuildSycocaProgressDialog::rebuildKSycoca(QApplication::activeWindow()); 0235 0236 // HACK to ensure mime cache is updated right away 0237 int previous_delay = ksycoca_ms_between_checks; 0238 ksycoca_ms_between_checks = 0; 0239 KService::allServices(); 0240 ksycoca_ms_between_checks = previous_delay; 0241 } 0242 0243 void ComponentChooser::save() 0244 { 0245 // default impl for simple application kinds 0246 const auto storageId = currentStorageId(); 0247 0248 saveMimeTypeAssociations(storageId, mimeTypes()); 0249 } 0250 0251 bool ComponentChooser::isDefaults() const 0252 { 0253 const auto defaultIndex = m_model->defaultIndex(); 0254 return !defaultIndex.has_value() || *defaultIndex == m_index; 0255 } 0256 0257 bool ComponentChooser::isSaveNeeded() const 0258 { 0259 const auto storageId = currentStorageId(); 0260 return m_model->rowCount() > 1 && (m_currentApplication != storageId) && storageId != ""; 0261 } 0262 0263 bool ComponentChooser::serviceSupportsMimeType(KService::Ptr service, const QString &targetMimeType) 0264 { 0265 static QMimeDatabase db; 0266 const QStringList mimeTypes = service->mimeTypes(); 0267 for (const QString &supportedMimeType : mimeTypes) { 0268 if (supportedMimeType == targetMimeType) { 0269 return true; 0270 } 0271 0272 if (db.mimeTypeForName(targetMimeType).inherits(supportedMimeType)) { 0273 return true; 0274 } 0275 } 0276 0277 if (targetMimeType.startsWith(QLatin1String("x-scheme-handler/")) && service->schemeHandlers().contains(targetMimeType.mid(17))) { 0278 return true; 0279 } 0280 0281 return false; 0282 }