KIO

paste.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "paste.h"
9#include "kio_widgets_debug.h"
10
11#include "../utils_p.h"
12#include "kio/copyjob.h"
13#include "kio/deletejob.h"
14#include "kio/global.h"
15#include "kio/renamedialog.h"
16#include "kio/statjob.h"
17#include "pastedialog_p.h"
18#include <kdirnotify.h>
19#include <kfileitem.h>
20#include <kfileitemlistproperties.h>
21#include <kio/storedtransferjob.h>
22
23#include <KJobWidgets>
24#include <KLocalizedString>
25#include <KMessageBox>
26#include <KUrlMimeData>
27
28#include <QApplication>
29#include <QClipboard>
30#include <QDebug>
31#include <QFileInfo>
32#include <QInputDialog>
33#include <QMimeData>
34#include <QTemporaryFile>
35
36static QUrl getDestinationUrl(const QUrl &srcUrl, const QUrl &destUrl, QWidget *widget)
37{
40 job->setSide(KIO::StatJob::DestinationSide);
41 KJobWidgets::setWindow(job, widget);
42
43 // Check for existing destination file.
44 // When we were using CopyJob, we couldn't let it do that (would expose
45 // an ugly tempfile name as the source URL)
46 // And now we're using a put job anyway, no destination checking included.
47 if (job->exec()) {
48 KIO::RenameDialog dlg(widget, i18n("File Already Exists"), srcUrl, destUrl, KIO::RenameDialog_Overwrite);
49 KIO::RenameDialog_Result res = static_cast<KIO::RenameDialog_Result>(dlg.exec());
50
51 if (res == KIO::Result_Rename) {
52 return dlg.newDestUrl();
53 } else if (res == KIO::Result_Cancel) {
54 return QUrl();
55 } else if (res == KIO::Result_Overwrite) {
56 return destUrl;
57 }
58 }
59
60 return destUrl;
61}
62
63static QUrl getNewFileName(const QUrl &u, const QString &text, const QString &suggestedFileName, QWidget *widget)
64{
65 bool ok;
66 QString dialogText(text);
67 if (dialogText.isEmpty()) {
68 dialogText = i18n("Filename for clipboard content:");
69 }
70 QString file = QInputDialog::getText(widget, QString(), dialogText, QLineEdit::Normal, suggestedFileName, &ok);
71 if (!ok) {
72 return QUrl();
73 }
74
75 QUrl myurl(u);
76 myurl.setPath(Utils::concatPaths(myurl.path(), file));
77
78 return getDestinationUrl(u, myurl, widget);
79}
80
81static KIO::Job *putDataAsyncTo(const QUrl &url, const QByteArray &data, QWidget *widget, KIO::JobFlags flags)
82{
83 KIO::Job *job = KIO::storedPut(data, url, -1, flags);
84 QObject::connect(job, &KIO::Job::result, [url](KJob *job) {
85 if (job->error() == KJob::NoError) {
86#ifdef WITH_QTDBUS
87 org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
88#endif
89 }
90 });
91 KJobWidgets::setWindow(job, widget);
92 return job;
93}
94
95static QByteArray chooseFormatAndUrl(const QUrl &u,
96 const QMimeData *mimeData,
97 const QStringList &formats,
98 const QString &text,
99 const QString &suggestedFileName,
100 QWidget *widget,
101 bool clipboard,
102 QUrl *newUrl)
103{
104 QString dialogText(text);
105 if (dialogText.isEmpty()) {
106 dialogText = i18n("Filename for clipboard content:");
107 }
108
109 auto defaultFilename = suggestedFileName;
110 if (defaultFilename.isEmpty()) {
111 defaultFilename = i18nc("A default file name excluding extension for some pasted content", "pasted file");
112 }
113
114 KIO::PasteDialog dlg(QString(), dialogText, defaultFilename, formats, widget);
115
116 if (dlg.exec() != QDialog::Accepted) {
117 return QByteArray();
118 }
119
120 const QString chosenFormat = formats[dlg.comboItem()];
121 if (clipboard && !qApp->clipboard()->mimeData()->hasFormat(chosenFormat)) {
123 i18n("The clipboard has changed since you used 'paste': "
124 "the chosen data format is no longer applicable. "
125 "Please copy again what you wanted to paste."));
126 return QByteArray();
127 }
128
129 const QString result = dlg.lineEditText();
130
131 // qDebug() << " result=" << result << " chosenFormat=" << chosenFormat;
132 *newUrl = u;
133 newUrl->setPath(Utils::concatPaths(newUrl->path(), result));
134
135 const QUrl destUrl = getDestinationUrl(u, *newUrl, widget);
136 *newUrl = destUrl;
137
138 // In Qt3, the result of clipboard()->mimeData() only existed until the next
139 // event loop run (see dlg.exec() above), so we re-fetched it.
140 // TODO: This should not be necessary with Qt5; remove this conditional
141 // and test that it still works.
142 if (clipboard) {
143 mimeData = QApplication::clipboard()->mimeData();
144 }
145 const QByteArray ba = mimeData->data(chosenFormat);
146 return ba;
147}
148
149static QStringList extractFormats(const QMimeData *mimeData)
150{
151 QStringList formats;
152 const QStringList allFormats = mimeData->formats();
153 for (const QString &format : allFormats) {
154 if (format == QLatin1String("application/x-qiconlist")) { // Q3IconView and kde4's libkonq
155 continue;
156 }
157 if (format == QLatin1String("application/x-kde-cutselection")) { // see isClipboardDataCut
158 continue;
159 }
160 if (format == QLatin1String("application/x-kde-suggestedfilename")) {
161 continue;
162 }
163 if (format == QLatin1String("application/x-kde-onlyReplaceEmpty")) { // Prevents emptying Klipper via selection
164 continue;
165 }
166 if (format.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal
167 continue;
168 }
169 if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal
170 continue;
171 }
172 if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP
173 continue;
174 }
175 formats.append(format);
176 }
177 return formats;
178}
179
180KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data)
181{
182 return data->hasText() || !extractFormats(data).isEmpty();
183}
184
185KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard)
186{
187 QByteArray ba;
188 const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename")));
189
190 // Now check for plain text
191 // We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly.
192 if (mimeData->hasText()) {
193 ba = mimeData->text().toLocal8Bit(); // encoding OK?
194 } else {
195 auto formats = extractFormats(mimeData);
196 const auto firstFormat = formats.value(0);
197 // Remove formats that shouldn't be exposed to the user
198 erase_if(formats, [](const QString &string) -> bool {
199 return string.startsWith(u"application/x-kde-");
200 });
201 if (formats.isEmpty() && firstFormat.isEmpty()) {
202 return nullptr;
203 } else if (formats.size() > 1) {
204 QUrl newUrl;
205 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
206 if (ba.isEmpty() || newUrl.isEmpty()) {
207 return nullptr;
208 }
209 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
210 }
211 ba = mimeData->data(firstFormat);
212 }
213 if (ba.isEmpty()) {
214 return nullptr;
215 }
216
217 const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget);
218 if (newUrl.isEmpty()) {
219 return nullptr;
220 }
221
222 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
223}
224
225KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
226{
227 bool canPasteData = false;
228 QList<QUrl> urls;
229
230 // mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053
231 if (mimeData) {
232 canPasteData = KIO::canPasteMimeData(mimeData);
233 urls = KUrlMimeData::urlsFromMimeData(mimeData);
234 } else {
235 qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!";
236 }
237
238 QString text;
239 if (!urls.isEmpty() || canPasteData) {
240 // disable the paste action if no writing is supported
241 if (!destItem.isNull()) {
242 if (destItem.url().isEmpty()) {
243 *enable = false;
244 } else {
245 *enable = destItem.isWritable();
246 }
247 } else {
248 *enable = false;
249 }
250
251 if (urls.count() == 1 && urls.first().isLocalFile()) {
252 const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir();
253 text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File");
254 } else if (!urls.isEmpty()) {
255 text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count());
256 } else {
257 text = i18nc("@action:inmenu", "Paste Clipboard Contents…");
258 }
259 } else {
260 *enable = false;
261 text = i18nc("@action:inmenu", "Paste");
262 }
263 return text;
264}
265
266KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut)
267{
268 const QByteArray cutSelectionData = cut ? "1" : "0";
269 mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData);
270}
271
272KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData)
273{
274 const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection"));
275 return (!a.isEmpty() && a.at(0) == '1');
276}
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
bool isNull() const
Return true if default-constructed.
The base class for all jobs.
Definition job_base.h:45
The dialog shown when a CopyJob realizes that a destination file already exists, and wants to offer t...
A KIO job that retrieves information about a file or directory.
Definition statjob.h:26
void setDetails(KIO::StatDetails details)
Selects the level of details we want.
Definition statjob.cpp:75
void setSide(StatSide side)
A stat() can have two meanings.
Definition statjob.cpp:70
bool exec()
int error() const
void result(KJob *job)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
KIOWIDGETS_EXPORT void setClipboardDataCut(QMimeData *mimeData, bool cut)
Add the information whether the files were cut, into the mimedata.
Definition paste.cpp:266
KIOCORE_EXPORT StoredTransferJob * storedPut(QIODevice *input, const QUrl &url, int permissions, JobFlags flags=DefaultFlags)
Put (means: write) data from a QIODevice.
@ RenameDialog_Overwrite
We have an existing destination, show details about it and offer to overwrite it.
KIOWIDGETS_EXPORT bool canPasteMimeData(const QMimeData *data)
Returns true if pasteMimeData will find any interesting format in data.
Definition paste.cpp:180
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOWIDGETS_EXPORT QString pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
Returns the text to use for the Paste action, when the application supports pasting files,...
Definition paste.cpp:225
RenameDialog_Result
The result of a rename or skip dialog.
KIOWIDGETS_EXPORT bool isClipboardDataCut(const QMimeData *mimeData)
Returns true if the URLs in mimeData were cut by the user.
Definition paste.cpp:272
QFlags< JobFlag > JobFlags
Stores a combination of JobFlag values.
Definition job_base.h:281
@ DefaultFlags
Show the progress info GUI, no Resume and no Overwrite.
Definition job_base.h:246
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ Overwrite
When set, automatically overwrite the destination if it exists already.
Definition job_base.h:267
@ StatBasic
Filename, access, type, size, linkdest.
Definition global.h:255
void setWindow(QObject *job, QWidget *widget)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
KCOREADDONS_EXPORT QList< QUrl > urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions=PreferKdeUrls, MetaDataMap *metaData=nullptr)
char at(qsizetype i) const const
bool isEmpty() const const
const QMimeData * mimeData(Mode mode) const const
bool isDir() const const
QClipboard * clipboard()
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
void append(QList< T > &&value)
qsizetype count() const const
T & first()
bool isEmpty() const const
qsizetype size() const const
T value(qsizetype i) const const
QByteArray data(const QString &mimeType) const const
virtual QStringList formats() const const
bool hasText() const const
void setData(const QString &mimeType, const QByteArray &data)
QString text() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromUtf8(QByteArrayView str)
QByteArray toLocal8Bit() const const
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
bool isEmpty() const const
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
void setPath(const QString &path, ParsingMode mode)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:51:43 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.