File indexing completed on 2024-04-28 05:27:03

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