File indexing completed on 2024-04-28 16:44:26

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0003    SPDX-FileCopyrightText: 2003 David Faure <faure@kde.org>
0004    SPDX-FileCopyrightText: 2002 Daniel Molkentin <molkentin@kde.org>
0005 
0006    SPDX-License-Identifier: GPL-2.0-only
0007 */
0008 
0009 // Own
0010 #include "kservicelistwidget.h"
0011 
0012 // Qt
0013 #include <QHBoxLayout>
0014 #include <QPushButton>
0015 #include <QStandardPaths>
0016 #include <QVBoxLayout>
0017 
0018 // KDE
0019 #include <klocalizedstring.h>
0020 #include <kmessagebox.h>
0021 #include <kopenwithdialog.h>
0022 #include <kpropertiesdialog.h>
0023 
0024 // Local
0025 #include "kserviceselectdlg.h"
0026 #include "mimetypedata.h"
0027 
0028 KServiceListItem::KServiceListItem(const KService::Ptr &pService, int kind)
0029     : QListWidgetItem()
0030     , storageId(pService->storageId())
0031     , desktopPath(pService->entryPath())
0032 {
0033     if (kind == KServiceListWidget::SERVICELIST_APPLICATIONS) {
0034         setText(pService->name());
0035     } else {
0036         setText(i18n("%1 (%2)", pService->name(), pService->desktopEntryName()));
0037     }
0038 
0039     setIcon(QIcon::fromTheme(pService->icon()));
0040 
0041     if (!pService->isApplication()) {
0042         localPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kservices" QT_STRINGIFY(QT_VERSION_MAJOR))
0043             + QLatin1Char('/') + desktopPath;
0044     } else {
0045         localPath = pService->locateLocal();
0046     }
0047 }
0048 
0049 PluginListItem::PluginListItem(const KPluginMetaData &metaData)
0050     : QListWidgetItem()
0051 {
0052     setText(i18n("%1 (%2)", metaData.name(), metaData.pluginId()));
0053     setIcon(QIcon::fromTheme(metaData.iconName()));
0054 }
0055 
0056 KServiceListWidget::KServiceListWidget(int kind, QWidget *parent)
0057     : QGroupBox(kind == SERVICELIST_APPLICATIONS ? i18n("Application Preference Order") : i18n("Services Preference Order"), parent)
0058     , m_kind(kind)
0059     , m_mimeTypeData(nullptr)
0060 {
0061     QHBoxLayout *lay = new QHBoxLayout(this);
0062 
0063     servicesLB = new QListWidget(this);
0064     connect(servicesLB, &QListWidget::itemSelectionChanged, this, &KServiceListWidget::enableMoveButtons);
0065     lay->addWidget(servicesLB);
0066     connect(servicesLB, &QListWidget::itemDoubleClicked, this, &KServiceListWidget::editService);
0067 
0068     QString wtstr = (kind == SERVICELIST_APPLICATIONS ? i18n("This is a list of applications associated with files of the selected"
0069                                                              " file type. This list is shown in Konqueror's context menus when you select"
0070                                                              " \"Open With...\". If more than one application is associated with this file type,"
0071                                                              " then the list is ordered by priority with the uppermost item taking precedence"
0072                                                              " over the others.")
0073                                                       : i18n("This is a list of services associated with files of the selected"
0074                                                              " file type. This list is shown in Konqueror's context menus when you select"
0075                                                              " a \"Preview with...\" option. If more than one service is associated with this file type,"
0076                                                              " then the list is ordered by priority with the uppermost item taking precedence"
0077                                                              " over the others."));
0078 
0079     setWhatsThis(wtstr);
0080     servicesLB->setWhatsThis(wtstr);
0081 
0082     QVBoxLayout *btnsLay = new QVBoxLayout();
0083     lay->addLayout(btnsLay);
0084 
0085     servUpButton = new QPushButton(i18n("Move &Up"), this);
0086     servUpButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0087     servUpButton->setEnabled(false);
0088     connect(servUpButton, &QAbstractButton::clicked, this, &KServiceListWidget::promoteService);
0089     btnsLay->addWidget(servUpButton);
0090 
0091     servUpButton->setWhatsThis(kind == SERVICELIST_APPLICATIONS ? i18n("Assigns a higher priority to the selected\n"
0092                                                                        "application, moving it up in the list. Note:  This\n"
0093                                                                        "only affects the selected application if the file type is\n"
0094                                                                        "associated with more than one application.")
0095                                                                 : i18n("Assigns a higher priority to the selected\n"
0096                                                                        "service, moving it up in the list."));
0097 
0098     servDownButton = new QPushButton(i18n("Move &Down"), this);
0099     servDownButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
0100     servDownButton->setEnabled(false);
0101     connect(servDownButton, &QAbstractButton::clicked, this, &KServiceListWidget::demoteService);
0102     btnsLay->addWidget(servDownButton);
0103     servDownButton->setWhatsThis(kind == SERVICELIST_APPLICATIONS ? i18n("Assigns a lower priority to the selected\n"
0104                                                                          "application, moving it down in the list. Note: This \n"
0105                                                                          "only affects the selected application if the file type is\n"
0106                                                                          "associated with more than one application.")
0107                                                                   : i18n("Assigns a lower priority to the selected\n"
0108                                                                          "service, moving it down in the list."));
0109 
0110     servNewButton = new QPushButton(i18n("Add..."), this);
0111     servNewButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0112     servNewButton->setEnabled(false);
0113     connect(servNewButton, &QAbstractButton::clicked, this, &KServiceListWidget::addService);
0114     btnsLay->addWidget(servNewButton);
0115     servNewButton->setWhatsThis(i18n("Add a new application for this file type."));
0116 
0117     servEditButton = new QPushButton(i18n("Edit..."), this);
0118     servEditButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0119     servEditButton->setEnabled(false);
0120     connect(servEditButton, &QAbstractButton::clicked, this, &KServiceListWidget::editService);
0121     btnsLay->addWidget(servEditButton);
0122     servEditButton->setWhatsThis(i18n("Edit command line of the selected application."));
0123 
0124     servRemoveButton = new QPushButton(i18n("Remove"), this);
0125     servRemoveButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0126     servRemoveButton->setEnabled(false);
0127     connect(servRemoveButton, &QAbstractButton::clicked, this, &KServiceListWidget::removeService);
0128     btnsLay->addWidget(servRemoveButton);
0129     servRemoveButton->setWhatsThis(i18n("Remove the selected application from the list."));
0130 
0131     btnsLay->addStretch(1);
0132 }
0133 
0134 void KServiceListWidget::setMimeTypeData(MimeTypeData *mimeTypeData)
0135 {
0136     m_mimeTypeData = mimeTypeData;
0137     if (servNewButton) {
0138         servNewButton->setEnabled(true);
0139     }
0140     // will need a selection
0141     servUpButton->setEnabled(false);
0142     servDownButton->setEnabled(false);
0143 
0144     servicesLB->clear();
0145     servicesLB->setEnabled(false);
0146 
0147     if (m_mimeTypeData) {
0148         const QStringList services = (m_kind == SERVICELIST_APPLICATIONS) ? m_mimeTypeData->appServices() : m_mimeTypeData->embedServices();
0149 
0150         if (services.isEmpty()) {
0151             if (m_kind == SERVICELIST_APPLICATIONS) {
0152                 servicesLB->addItem(i18nc("No applications associated with this file type", "None"));
0153             } else {
0154                 servicesLB->addItem(i18nc("No components associated with this file type", "None"));
0155             }
0156         } else {
0157             for (const QString &service : services) {
0158                 KService::Ptr pService = KService::serviceByStorageId(service);
0159                 if (pService) {
0160                     servicesLB->addItem(new KServiceListItem(pService, m_kind));
0161                 }
0162             }
0163             servicesLB->setEnabled(true);
0164         }
0165     }
0166 
0167     if (servRemoveButton) {
0168         servRemoveButton->setEnabled(servicesLB->currentRow() > -1);
0169     }
0170     if (servEditButton) {
0171         servEditButton->setEnabled(servicesLB->currentRow() > -1);
0172     }
0173 }
0174 
0175 void KServiceListWidget::promoteService()
0176 {
0177     if (!servicesLB->isEnabled()) {
0178         return;
0179     }
0180 
0181     int selIndex = servicesLB->currentRow();
0182     if (selIndex == 0) {
0183         return;
0184     }
0185 
0186     QListWidgetItem *selItem = servicesLB->item(selIndex);
0187     servicesLB->takeItem(selIndex);
0188     servicesLB->insertItem(selIndex - 1, selItem);
0189     servicesLB->setCurrentRow(selIndex - 1);
0190 
0191     updatePreferredServices();
0192 
0193     Q_EMIT changed(true);
0194 }
0195 
0196 void KServiceListWidget::demoteService()
0197 {
0198     if (!servicesLB->isEnabled()) {
0199         return;
0200     }
0201 
0202     int selIndex = servicesLB->currentRow();
0203     if (selIndex == servicesLB->count() - 1) {
0204         return;
0205     }
0206 
0207     QListWidgetItem *selItem = servicesLB->item(selIndex);
0208     servicesLB->takeItem(selIndex);
0209     servicesLB->insertItem(selIndex + 1, selItem);
0210     servicesLB->setCurrentRow(selIndex + 1);
0211 
0212     updatePreferredServices();
0213 
0214     Q_EMIT changed(true);
0215 }
0216 
0217 void KServiceListWidget::addService()
0218 {
0219     if (!m_mimeTypeData) {
0220         return;
0221     }
0222 
0223     KService::Ptr service;
0224     if (m_kind == SERVICELIST_APPLICATIONS) {
0225         KOpenWithDialog dlg(m_mimeTypeData->name(), QString(), this);
0226         dlg.setSaveNewApplications(true);
0227         if (dlg.exec() != QDialog::Accepted) {
0228             return;
0229         }
0230 
0231         service = dlg.service();
0232 
0233         Q_ASSERT(service);
0234         if (!service) {
0235             return; // Don't crash if KOpenWith wasn't able to create service.
0236         }
0237     } else {
0238         KServiceSelectDlg dlg(m_mimeTypeData->name(), QString(), this);
0239         if (dlg.exec() != QDialog::Accepted) {
0240             return;
0241         }
0242         service = dlg.service();
0243         Q_ASSERT(service);
0244         if (!service) {
0245             return;
0246         }
0247     }
0248 
0249     // Did the list simply show "None"?
0250     const bool hadDummyEntry = (m_kind == SERVICELIST_APPLICATIONS) ? m_mimeTypeData->appServices().isEmpty() : m_mimeTypeData->embedServices().isEmpty();
0251 
0252     if (hadDummyEntry) {
0253         delete servicesLB->takeItem(0); // Remove the "None" item.
0254         servicesLB->setEnabled(true);
0255     } else {
0256         // check if it is a duplicate entry
0257         for (int index = 0; index < servicesLB->count(); index++) {
0258             if (static_cast<KServiceListItem *>(servicesLB->item(index))->desktopPath == service->entryPath()) {
0259                 // ##### shouldn't we make the existing entry the default one?
0260                 return;
0261             }
0262         }
0263     }
0264 
0265     servicesLB->insertItem(0, new KServiceListItem(service, m_kind));
0266     servicesLB->setCurrentItem(nullptr);
0267 
0268     updatePreferredServices();
0269 
0270     Q_EMIT changed(true);
0271 }
0272 
0273 void KServiceListWidget::editService()
0274 {
0275     if (!m_mimeTypeData) {
0276         return;
0277     }
0278     const int selected = servicesLB->currentRow();
0279     if (selected < 0) {
0280         return;
0281     }
0282 
0283     // Only edit applications, not services as
0284     // they don't have any parameters
0285     if (m_kind != SERVICELIST_APPLICATIONS) {
0286         return;
0287     }
0288 
0289     // Just like popping up an add dialog except that we
0290     // pass the current command line as a default
0291     KServiceListItem *selItem = (KServiceListItem *)servicesLB->item(selected);
0292     const QString desktopPath = selItem->desktopPath;
0293 
0294     KService::Ptr service = KService::serviceByDesktopPath(desktopPath);
0295     if (!service) {
0296         return;
0297     }
0298 
0299     QString path = service->entryPath();
0300     {
0301         // If the path to the desktop file is relative, try to get the full
0302         // path from QStandardPaths.
0303         QString fullPath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, path);
0304         if (!fullPath.isEmpty()) {
0305             path = fullPath;
0306         }
0307     }
0308 
0309     KFileItem item(QUrl::fromLocalFile(path), QStringLiteral("application/x-desktop"), KFileItem::Unknown);
0310     KPropertiesDialog dlg(item, this);
0311     if (dlg.exec() != QDialog::Accepted) {
0312         return;
0313     }
0314 
0315     // Note that at this point, ksycoca has been updated,
0316     // and setMimeTypeData has been called again, so all the items have been recreated.
0317 
0318     // Reload service
0319     service = KService::serviceByDesktopPath(desktopPath);
0320     if (!service) {
0321         return;
0322     }
0323 
0324     // Remove the old one...
0325     delete servicesLB->takeItem(selected);
0326 
0327     // ...check that it's not a duplicate entry...
0328     bool addIt = true;
0329     for (int index = 0; index < servicesLB->count(); index++) {
0330         if (static_cast<KServiceListItem *>(servicesLB->item(index))->desktopPath == service->entryPath()) {
0331             addIt = false;
0332             break;
0333         }
0334     }
0335 
0336     // ...and add it in the same place as the old one:
0337     if (addIt) {
0338         servicesLB->insertItem(selected, new KServiceListItem(service, m_kind));
0339         servicesLB->setCurrentRow(selected);
0340     }
0341 
0342     updatePreferredServices();
0343 
0344     Q_EMIT changed(true);
0345 }
0346 
0347 void KServiceListWidget::removeService()
0348 {
0349     if (!m_mimeTypeData) {
0350         return;
0351     }
0352 
0353     int selected = servicesLB->currentRow();
0354 
0355     if (selected >= 0) {
0356         delete servicesLB->takeItem(selected);
0357         updatePreferredServices();
0358 
0359         Q_EMIT changed(true);
0360     }
0361 
0362     // Update buttons and service list again (e.g. to re-add "None")
0363     setMimeTypeData(m_mimeTypeData);
0364 }
0365 
0366 void KServiceListWidget::updatePreferredServices()
0367 {
0368     if (!m_mimeTypeData) {
0369         return;
0370     }
0371     QStringList sl;
0372     unsigned int count = servicesLB->count();
0373 
0374     for (unsigned int i = 0; i < count; i++) {
0375         KServiceListItem *sli = (KServiceListItem *)servicesLB->item(i);
0376         sl.append(sli->storageId);
0377     }
0378     sl.removeDuplicates();
0379     if (m_kind == SERVICELIST_APPLICATIONS) {
0380         m_mimeTypeData->setAppServices(sl);
0381     } else {
0382         m_mimeTypeData->setEmbedServices(sl);
0383     }
0384 }
0385 
0386 void KServiceListWidget::enableMoveButtons()
0387 {
0388     int idx = servicesLB->currentRow();
0389     if (servicesLB->model()->rowCount() <= 1) {
0390         servUpButton->setEnabled(false);
0391         servDownButton->setEnabled(false);
0392     } else if (idx == (servicesLB->model()->rowCount() - 1)) {
0393         servUpButton->setEnabled(true);
0394         servDownButton->setEnabled(false);
0395     } else if (idx == 0) {
0396         servUpButton->setEnabled(false);
0397         servDownButton->setEnabled(true);
0398     } else {
0399         servUpButton->setEnabled(true);
0400         servDownButton->setEnabled(true);
0401     }
0402 
0403     if (servRemoveButton) {
0404         servRemoveButton->setEnabled(true);
0405     }
0406 
0407     if (servEditButton) {
0408         servEditButton->setEnabled(m_kind == SERVICELIST_APPLICATIONS);
0409     }
0410 }