File indexing completed on 2024-04-28 05:49:29

0001 /* This file is part of the KDE project
0002  *
0003  * SPDX-FileCopyrightText: 2018 Gregor Mi <codestruct@posteo.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  **/
0007 
0008 #include "katefileactions.h"
0009 
0010 #include "hostprocess.h"
0011 
0012 #include <ktexteditor/application.h>
0013 #include <ktexteditor/document.h>
0014 #include <ktexteditor/editor.h>
0015 
0016 #include <KApplicationTrader>
0017 #include <KIO/ApplicationLauncherJob>
0018 #include <KIO/CopyJob>
0019 #include <KIO/DeleteJob>
0020 #include <KIO/JobUiDelegateFactory>
0021 #include <KIO/OpenFileManagerWindowJob>
0022 #include <KJobWidgets>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <KPropertiesDialog>
0026 #include <KService>
0027 #include <kio_version.h>
0028 
0029 #include <QAction>
0030 #include <QApplication>
0031 #include <QClipboard>
0032 #include <QDir>
0033 #include <QInputDialog>
0034 #include <QMenu>
0035 #include <QMimeDatabase>
0036 #include <QMimeType>
0037 #include <QProcess>
0038 #include <QStandardPaths>
0039 #include <QUrl>
0040 
0041 void KateFileActions::copyFilePathToClipboard(KTextEditor::Document *doc)
0042 {
0043     const QUrl url = doc->url();
0044     // ensure we prefer native separators, bug 381052
0045     QApplication::clipboard()->setText(url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.url());
0046 }
0047 
0048 void KateFileActions::openContainingFolder(KTextEditor::Document *doc)
0049 {
0050     KIO::highlightInFileManager({doc->url()});
0051 }
0052 
0053 void KateFileActions::openFilePropertiesDialog(QWidget *parent, KTextEditor::Document *doc)
0054 {
0055     KFileItem fileItem(doc->url());
0056     QDialog *dlg = new KPropertiesDialog(fileItem, parent);
0057     dlg->setAttribute(Qt::WA_DeleteOnClose);
0058     dlg->show();
0059 }
0060 
0061 void KateFileActions::renameDocumentFile(QWidget *parent, KTextEditor::Document *doc)
0062 {
0063     if (!doc) {
0064         return;
0065     }
0066 
0067     const QUrl oldFileUrl = doc->url();
0068     if (oldFileUrl.isEmpty()) {
0069         return;
0070     }
0071 
0072     const QString oldFileName = doc->url().fileName();
0073     bool ok = false;
0074     QString newFileName = QInputDialog::getText(parent, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok);
0075     if (!ok || (newFileName == oldFileName)) {
0076         return;
0077     }
0078 
0079     QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0080     newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName);
0081 
0082     if (!newFileUrl.isValid()) {
0083         return;
0084     }
0085 
0086     if (!doc->closeUrl()) {
0087         return;
0088     }
0089 
0090     doc->waitSaveComplete();
0091 
0092     KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl);
0093     parent->connect(parent, &QObject::destroyed, job, [job]() {
0094         job->kill();
0095     });
0096 
0097     // Associate the job with the parent widget, in case of renaming conflicts the ask-user-dialog
0098     // is window-modal by default
0099     KJobWidgets::setWindow(job, parent);
0100 
0101     parent->connect(job, &KJob::result, parent, [parent, doc, oldFileUrl](KJob *job) {
0102         auto *copyJob = static_cast<KIO::CopyJob *>(job);
0103         if (!copyJob->error()) {
0104             doc->openUrl(copyJob->destUrl());
0105             doc->documentSavedOrUploaded(doc, true);
0106         } else {
0107             KMessageBox::error(parent,
0108                                i18n("File \"%1\" could not be moved to \"%2\"",
0109                                     oldFileUrl.toDisplayString(QUrl::PreferLocalFile),
0110                                     copyJob->destUrl().toDisplayString(QUrl::PreferLocalFile)));
0111             doc->openUrl(oldFileUrl);
0112         }
0113     });
0114 }
0115 
0116 void KateFileActions::deleteDocumentFile(QWidget *parent, KTextEditor::Document *doc)
0117 {
0118     if (!doc) {
0119         return;
0120     }
0121 
0122     const auto &&url = doc->url();
0123 
0124     if (url.isEmpty()) {
0125         return;
0126     }
0127 
0128     bool go = (KMessageBox::warningContinueCancel(parent,
0129                                                   i18n("Do you really want to delete file \"%1\"?", url.toDisplayString()),
0130                                                   i18n("Delete file"),
0131                                                   KStandardGuiItem::del(),
0132                                                   KStandardGuiItem::cancel(),
0133                                                   QStringLiteral("filetreedeletefile"))
0134                == KMessageBox::Continue);
0135 
0136     if (!go) {
0137         return;
0138     }
0139 
0140     // If the document is modified, user will be asked if he wants to save it.
0141     // This confirmation is useless when deleting, so we mark the document as unmodified.
0142     doc->setModified(false);
0143 
0144     if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) {
0145         return; // no extra message, the internals of ktexteditor should take care of that.
0146     }
0147 
0148     if (url.isValid()) {
0149         KIO::DeleteJob *job = KIO::del(url);
0150         if (!job->exec()) {
0151             KMessageBox::error(parent, i18n("File \"%1\" could not be deleted.", url.toDisplayString()));
0152         }
0153     }
0154 }
0155 
0156 QList<std::pair<QString, QString>> KateFileActions::supportedDiffTools()
0157 {
0158     // query once if the tools are there in the path and store that
0159     // we will disable the actions for the tools not found
0160     static QList<std::pair<QString, QString>> resultList{{QStringLiteral("kdiff3"), safeExecutableName(QStringLiteral("kdiff3"))},
0161                                                          {QStringLiteral("kompare"), safeExecutableName(QStringLiteral("kompare"))},
0162                                                          {QStringLiteral("meld"), safeExecutableName(QStringLiteral("meld"))}};
0163     return resultList;
0164 }
0165 
0166 bool KateFileActions::compareWithExternalProgram(KTextEditor::Document *documentA, KTextEditor::Document *documentB, const QString &diffExecutable)
0167 {
0168     Q_ASSERT(documentA);
0169     Q_ASSERT(documentB);
0170 
0171     QProcess process;
0172     QStringList arguments;
0173     arguments << documentA->url().toLocalFile() << documentB->url().toLocalFile();
0174     return process.startDetached(diffExecutable, arguments);
0175 }
0176 
0177 void KateFileActions::prepareOpenWithMenu(const QUrl &url, QMenu *menu)
0178 {
0179     // dh: in bug #307699, this slot is called when launching the Kate application
0180     // unfortunately, no one ever could reproduce except users.
0181 
0182     QMimeDatabase db;
0183     QMimeType mime = db.mimeTypeForUrl(url);
0184 
0185     menu->clear();
0186 
0187     // get a list of appropriate services.
0188     const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
0189     QAction *a = nullptr;
0190 
0191     // add all default open-with-actions except "Kate"
0192     for (const auto &service : offers) {
0193         if (service->name() == QLatin1String("Kate")) {
0194             continue;
0195         }
0196         a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
0197         a->setData(service->entryPath());
0198     }
0199 
0200     // append "Other..." to call the KDE "open with" dialog.
0201     menu->addSeparator();
0202     QAction *other = menu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("&Other Application..."));
0203     other->setData(QString());
0204 }
0205 
0206 void KateFileActions::showOpenWithMenu(QWidget *parent, const QUrl &url, QAction *action)
0207 {
0208     KService::Ptr app = KService::serviceByDesktopPath(action->data().toString());
0209     // If app is null, ApplicationLauncherJob will invoke the open-with dialog
0210     auto *job = new KIO::ApplicationLauncherJob(app);
0211     job->setUrls({url});
0212     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parent));
0213     job->start();
0214 }