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 <QMimeDatabase>
35#include <QTemporaryFile>
36
37static QUrl getDestinationUrl(const QUrl &srcUrl, const QUrl &destUrl, QWidget *widget)
38{
41 job->setSide(KIO::StatJob::DestinationSide);
42 KJobWidgets::setWindow(job, widget);
43
44 // Check for existing destination file.
45 // When we were using CopyJob, we couldn't let it do that (would expose
46 // an ugly tempfile name as the source URL)
47 // And now we're using a put job anyway, no destination checking included.
48 if (job->exec()) {
49 KIO::RenameDialog dlg(widget, i18n("File Already Exists"), srcUrl, destUrl, KIO::RenameDialog_Overwrite);
50 KIO::RenameDialog_Result res = static_cast<KIO::RenameDialog_Result>(dlg.exec());
51
52 if (res == KIO::Result_Rename) {
53 return dlg.newDestUrl();
54 } else if (res == KIO::Result_Cancel) {
55 return QUrl();
56 } else if (res == KIO::Result_Overwrite) {
57 return destUrl;
58 }
59 }
60
61 return destUrl;
62}
63
64static QUrl getNewFileName(const QUrl &u, const QString &text, const QString &suggestedFileName, QWidget *widget)
65{
66 bool ok;
67 QString dialogText(text);
68 if (dialogText.isEmpty()) {
69 dialogText = i18n("Filename for clipboard content:");
70 }
71 QString file = QInputDialog::getText(widget, QString(), dialogText, QLineEdit::Normal, suggestedFileName, &ok);
72 if (!ok) {
73 return QUrl();
74 }
75
76 QUrl myurl(u);
77 myurl.setPath(Utils::concatPaths(myurl.path(), file));
78
79 return getDestinationUrl(u, myurl, widget);
80}
81
82static KIO::Job *putDataAsyncTo(const QUrl &url, const QByteArray &data, QWidget *widget, KIO::JobFlags flags)
83{
84 KIO::Job *job = KIO::storedPut(data, url, -1, flags);
85 QObject::connect(job, &KIO::Job::result, [url](KJob *job) {
86 if (job->error() == KJob::NoError) {
87#ifdef WITH_QTDBUS
88 org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
89#endif
90 }
91 });
92 KJobWidgets::setWindow(job, widget);
93 return job;
94}
95
96static QByteArray chooseFormatAndUrl(const QUrl &u,
97 const QMimeData *mimeData,
98 const QStringList &formats,
99 const QString &text,
100 const QString &suggestedFileName,
101 QWidget *widget,
102 bool clipboard,
103 QUrl *newUrl)
104{
105 QMimeDatabase db;
106 QStringList formatLabels;
107 formatLabels.reserve(formats.size());
108 for (int i = 0; i < formats.size(); ++i) {
109 const QString &fmt = formats[i];
110 QMimeType mime = db.mimeTypeForName(fmt);
111 if (mime.isValid()) {
112 formatLabels.append(i18n("%1 (%2)", mime.comment(), fmt));
113 } else {
114 formatLabels.append(fmt);
115 }
116 }
117
118 QString dialogText(text);
119 if (dialogText.isEmpty()) {
120 dialogText = i18n("Filename for clipboard content:");
121 }
122
123 KIO::PasteDialog dlg(QString(), dialogText, suggestedFileName, formatLabels, widget);
124
125 if (dlg.exec() != QDialog::Accepted) {
126 return QByteArray();
127 }
128
129 const QString chosenFormat = formats[dlg.comboItem()];
130 if (clipboard && !qApp->clipboard()->mimeData()->hasFormat(chosenFormat)) {
132 i18n("The clipboard has changed since you used 'paste': "
133 "the chosen data format is no longer applicable. "
134 "Please copy again what you wanted to paste."));
135 return QByteArray();
136 }
137
138 const QString result = dlg.lineEditText();
139
140 // qDebug() << " result=" << result << " chosenFormat=" << chosenFormat;
141 *newUrl = u;
142 newUrl->setPath(Utils::concatPaths(newUrl->path(), result));
143
144 const QUrl destUrl = getDestinationUrl(u, *newUrl, widget);
145 *newUrl = destUrl;
146
147 // In Qt3, the result of clipboard()->mimeData() only existed until the next
148 // event loop run (see dlg.exec() above), so we re-fetched it.
149 // TODO: This should not be necessary with Qt5; remove this conditional
150 // and test that it still works.
151 if (clipboard) {
152 mimeData = QApplication::clipboard()->mimeData();
153 }
154 const QByteArray ba = mimeData->data(chosenFormat);
155 return ba;
156}
157
158static QStringList extractFormats(const QMimeData *mimeData)
159{
160 QStringList formats;
161 const QStringList allFormats = mimeData->formats();
162 for (const QString &format : allFormats) {
163 if (format == QLatin1String("application/x-qiconlist")) { // Q3IconView and kde4's libkonq
164 continue;
165 }
166 if (format == QLatin1String("application/x-kde-cutselection")) { // see isClipboardDataCut
167 continue;
168 }
169 if (format == QLatin1String("application/x-kde-suggestedfilename")) {
170 continue;
171 }
172 if (format == QLatin1String("application/x-kde-onlyReplaceEmpty")) { // Prevents emptying Klipper via selection
173 continue;
174 }
175 if (format.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal
176 continue;
177 }
178 if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal
179 continue;
180 }
181 if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP
182 continue;
183 }
184 formats.append(format);
185 }
186 return formats;
187}
188
189KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data)
190{
191 return data->hasText() || !extractFormats(data).isEmpty();
192}
193
194KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard)
195{
196 QByteArray ba;
197 const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename")));
198
199 // Now check for plain text
200 // We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly.
201 if (mimeData->hasText()) {
202 ba = mimeData->text().toLocal8Bit(); // encoding OK?
203 } else {
204 auto formats = extractFormats(mimeData);
205 const auto firstFormat = formats.value(0);
206 // Remove formats that shouldn't be exposed to the user
207 erase_if(formats, [](const QString &string) -> bool {
208 return string.startsWith(u"application/x-kde-");
209 });
210 if (formats.isEmpty() && firstFormat.isEmpty()) {
211 return nullptr;
212 } else if (formats.size() > 1) {
213 QUrl newUrl;
214 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
215 if (ba.isEmpty() || newUrl.isEmpty()) {
216 return nullptr;
217 }
218 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
219 }
220 ba = mimeData->data(firstFormat);
221 }
222 if (ba.isEmpty()) {
223 return nullptr;
224 }
225
226 const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget);
227 if (newUrl.isEmpty()) {
228 return nullptr;
229 }
230
231 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
232}
233
234KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
235{
236 bool canPasteData = false;
237 QList<QUrl> urls;
238
239 // mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053
240 if (mimeData) {
241 canPasteData = KIO::canPasteMimeData(mimeData);
242 urls = KUrlMimeData::urlsFromMimeData(mimeData);
243 } else {
244 qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!";
245 }
246
247 QString text;
248 if (!urls.isEmpty() || canPasteData) {
249 // disable the paste action if no writing is supported
250 if (!destItem.isNull()) {
251 if (destItem.url().isEmpty()) {
252 *enable = false;
253 } else {
254 *enable = destItem.isWritable();
255 }
256 } else {
257 *enable = false;
258 }
259
260 if (urls.count() == 1 && urls.first().isLocalFile()) {
261 const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir();
262 text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File");
263 } else if (!urls.isEmpty()) {
264 text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count());
265 } else {
266 text = i18nc("@action:inmenu", "Paste Clipboard Contents…");
267 }
268 } else {
269 *enable = false;
270 text = i18nc("@action:inmenu", "Paste");
271 }
272 return text;
273}
274
275KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut)
276{
277 const QByteArray cutSelectionData = cut ? "1" : "0";
278 mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData);
279}
280
281KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData)
282{
283 const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection"));
284 return (!a.isEmpty() && a.at(0) == '1');
285}
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.
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.
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:275
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:189
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:234
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: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
void reserve(qsizetype size)
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
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
bool isValid() 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-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.