File indexing completed on 2025-02-16 03:42:39
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "paste.h" 0009 #include "kio_widgets_debug.h" 0010 0011 #include "../utils_p.h" 0012 #include "kio/copyjob.h" 0013 #include "kio/deletejob.h" 0014 #include "kio/global.h" 0015 #include "kio/renamedialog.h" 0016 #include "kio/statjob.h" 0017 #include "pastedialog_p.h" 0018 #include <kdirnotify.h> 0019 #include <kfileitem.h> 0020 #include <kfileitemlistproperties.h> 0021 #include <kio/storedtransferjob.h> 0022 0023 #include <KJobWidgets> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <KUrlMimeData> 0027 0028 #include <QApplication> 0029 #include <QClipboard> 0030 #include <QDebug> 0031 #include <QFileInfo> 0032 #include <QInputDialog> 0033 #include <QMimeData> 0034 #include <QMimeDatabase> 0035 #include <QTemporaryFile> 0036 0037 static QUrl getDestinationUrl(const QUrl &srcUrl, const QUrl &destUrl, QWidget *widget) 0038 { 0039 KIO::StatJob *job = KIO::stat(destUrl, destUrl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); 0040 job->setDetails(KIO::StatBasic); 0041 job->setSide(KIO::StatJob::DestinationSide); 0042 KJobWidgets::setWindow(job, widget); 0043 0044 // Check for existing destination file. 0045 // When we were using CopyJob, we couldn't let it do that (would expose 0046 // an ugly tempfile name as the source URL) 0047 // And now we're using a put job anyway, no destination checking included. 0048 if (job->exec()) { 0049 KIO::RenameDialog dlg(widget, i18n("File Already Exists"), srcUrl, destUrl, KIO::RenameDialog_Overwrite); 0050 KIO::RenameDialog_Result res = static_cast<KIO::RenameDialog_Result>(dlg.exec()); 0051 0052 if (res == KIO::Result_Rename) { 0053 return dlg.newDestUrl(); 0054 } else if (res == KIO::Result_Cancel) { 0055 return QUrl(); 0056 } else if (res == KIO::Result_Overwrite) { 0057 return destUrl; 0058 } 0059 } 0060 0061 return destUrl; 0062 } 0063 0064 static QUrl getNewFileName(const QUrl &u, const QString &text, const QString &suggestedFileName, QWidget *widget) 0065 { 0066 bool ok; 0067 QString dialogText(text); 0068 if (dialogText.isEmpty()) { 0069 dialogText = i18n("Filename for clipboard content:"); 0070 } 0071 QString file = QInputDialog::getText(widget, QString(), dialogText, QLineEdit::Normal, suggestedFileName, &ok); 0072 if (!ok) { 0073 return QUrl(); 0074 } 0075 0076 QUrl myurl(u); 0077 myurl.setPath(Utils::concatPaths(myurl.path(), file)); 0078 0079 return getDestinationUrl(u, myurl, widget); 0080 } 0081 0082 static KIO::Job *putDataAsyncTo(const QUrl &url, const QByteArray &data, QWidget *widget, KIO::JobFlags flags) 0083 { 0084 KIO::Job *job = KIO::storedPut(data, url, -1, flags); 0085 QObject::connect(job, &KIO::Job::result, [url](KJob *job) { 0086 if (job->error() == KJob::NoError) { 0087 org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 0088 } 0089 }); 0090 KJobWidgets::setWindow(job, widget); 0091 return job; 0092 } 0093 0094 static QByteArray chooseFormatAndUrl(const QUrl &u, 0095 const QMimeData *mimeData, 0096 const QStringList &formats, 0097 const QString &text, 0098 const QString &suggestedFileName, 0099 QWidget *widget, 0100 bool clipboard, 0101 QUrl *newUrl) 0102 { 0103 QMimeDatabase db; 0104 QStringList formatLabels; 0105 formatLabels.reserve(formats.size()); 0106 for (int i = 0; i < formats.size(); ++i) { 0107 const QString &fmt = formats[i]; 0108 QMimeType mime = db.mimeTypeForName(fmt); 0109 if (mime.isValid()) { 0110 formatLabels.append(i18n("%1 (%2)", mime.comment(), fmt)); 0111 } else { 0112 formatLabels.append(fmt); 0113 } 0114 } 0115 0116 QString dialogText(text); 0117 if (dialogText.isEmpty()) { 0118 dialogText = i18n("Filename for clipboard content:"); 0119 } 0120 0121 KIO::PasteDialog dlg(QString(), dialogText, suggestedFileName, formatLabels, widget); 0122 0123 if (dlg.exec() != QDialog::Accepted) { 0124 return QByteArray(); 0125 } 0126 0127 const QString chosenFormat = formats[dlg.comboItem()]; 0128 if (clipboard && !qApp->clipboard()->mimeData()->hasFormat(chosenFormat)) { 0129 KMessageBox::information(widget, 0130 i18n("The clipboard has changed since you used 'paste': " 0131 "the chosen data format is no longer applicable. " 0132 "Please copy again what you wanted to paste.")); 0133 return QByteArray(); 0134 } 0135 0136 const QString result = dlg.lineEditText(); 0137 0138 // qDebug() << " result=" << result << " chosenFormat=" << chosenFormat; 0139 *newUrl = u; 0140 newUrl->setPath(Utils::concatPaths(newUrl->path(), result)); 0141 0142 const QUrl destUrl = getDestinationUrl(u, *newUrl, widget); 0143 *newUrl = destUrl; 0144 0145 // In Qt3, the result of clipboard()->mimeData() only existed until the next 0146 // event loop run (see dlg.exec() above), so we re-fetched it. 0147 // TODO: This should not be necessary with Qt5; remove this conditional 0148 // and test that it still works. 0149 if (clipboard) { 0150 mimeData = QApplication::clipboard()->mimeData(); 0151 } 0152 const QByteArray ba = mimeData->data(chosenFormat); 0153 return ba; 0154 } 0155 0156 static QStringList extractFormats(const QMimeData *mimeData) 0157 { 0158 QStringList formats; 0159 const QStringList allFormats = mimeData->formats(); 0160 for (const QString &format : allFormats) { 0161 if (format == QLatin1String("application/x-qiconlist")) { // Q3IconView and kde4's libkonq 0162 continue; 0163 } 0164 if (format == QLatin1String("application/x-kde-cutselection")) { // see isClipboardDataCut 0165 continue; 0166 } 0167 if (format == QLatin1String("application/x-kde-suggestedfilename")) { 0168 continue; 0169 } 0170 if (format.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal 0171 continue; 0172 } 0173 if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal 0174 continue; 0175 } 0176 if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP 0177 continue; 0178 } 0179 formats.append(format); 0180 } 0181 return formats; 0182 } 0183 0184 KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data) 0185 { 0186 return data->hasText() || !extractFormats(data).isEmpty(); 0187 } 0188 0189 KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard) 0190 { 0191 QByteArray ba; 0192 const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename"))); 0193 0194 // Now check for plain text 0195 // We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly. 0196 if (mimeData->hasText()) { 0197 ba = mimeData->text().toLocal8Bit(); // encoding OK? 0198 } else { 0199 const QStringList formats = extractFormats(mimeData); 0200 if (formats.isEmpty()) { 0201 return nullptr; 0202 } else if (formats.size() > 1) { 0203 QUrl newUrl; 0204 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl); 0205 if (ba.isEmpty() || newUrl.isEmpty()) { 0206 return nullptr; 0207 } 0208 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite); 0209 } 0210 ba = mimeData->data(formats.first()); 0211 } 0212 if (ba.isEmpty()) { 0213 return nullptr; 0214 } 0215 0216 const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget); 0217 if (newUrl.isEmpty()) { 0218 return nullptr; 0219 } 0220 0221 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite); 0222 } 0223 0224 KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem) 0225 { 0226 bool canPasteData = false; 0227 QList<QUrl> urls; 0228 0229 // mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053 0230 if (mimeData) { 0231 canPasteData = KIO::canPasteMimeData(mimeData); 0232 urls = KUrlMimeData::urlsFromMimeData(mimeData); 0233 } else { 0234 qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!"; 0235 } 0236 0237 QString text; 0238 if (!urls.isEmpty() || canPasteData) { 0239 // disable the paste action if no writing is supported 0240 if (!destItem.isNull()) { 0241 if (destItem.url().isEmpty()) { 0242 *enable = false; 0243 } else { 0244 *enable = destItem.isWritable(); 0245 } 0246 } else { 0247 *enable = false; 0248 } 0249 0250 if (urls.count() == 1 && urls.first().isLocalFile()) { 0251 const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir(); 0252 text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File"); 0253 } else if (!urls.isEmpty()) { 0254 text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count()); 0255 } else { 0256 text = i18nc("@action:inmenu", "Paste Clipboard Contents..."); 0257 } 0258 } else { 0259 *enable = false; 0260 text = i18nc("@action:inmenu", "Paste"); 0261 } 0262 return text; 0263 } 0264 0265 KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut) 0266 { 0267 const QByteArray cutSelectionData = cut ? "1" : "0"; 0268 mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData); 0269 } 0270 0271 KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData) 0272 { 0273 const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection")); 0274 return (!a.isEmpty() && a.at(0) == '1'); 0275 }