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.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal
173 continue;
174 }
175 if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal
176 continue;
177 }
178 if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP
179 continue;
180 }
181 formats.append(format);
182 }
183 return formats;
184}
185
186KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data)
187{
188 return data->hasText() || !extractFormats(data).isEmpty();
189}
190
191KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard)
192{
193 QByteArray ba;
194 const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename")));
195
196 // Now check for plain text
197 // We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly.
198 if (mimeData->hasText()) {
199 ba = mimeData->text().toLocal8Bit(); // encoding OK?
200 } else {
201 const QStringList formats = extractFormats(mimeData);
202 if (formats.isEmpty()) {
203 return nullptr;
204 } else if (formats.size() > 1) {
205 QUrl newUrl;
206 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
207 if (ba.isEmpty() || newUrl.isEmpty()) {
208 return nullptr;
209 }
210 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
211 }
212 ba = mimeData->data(formats.first());
213 }
214 if (ba.isEmpty()) {
215 return nullptr;
216 }
217
218 const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget);
219 if (newUrl.isEmpty()) {
220 return nullptr;
221 }
222
223 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
224}
225
226KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
227{
228 bool canPasteData = false;
229 QList<QUrl> urls;
230
231 // mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053
232 if (mimeData) {
233 canPasteData = KIO::canPasteMimeData(mimeData);
234 urls = KUrlMimeData::urlsFromMimeData(mimeData);
235 } else {
236 qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!";
237 }
238
239 QString text;
240 if (!urls.isEmpty() || canPasteData) {
241 // disable the paste action if no writing is supported
242 if (!destItem.isNull()) {
243 if (destItem.url().isEmpty()) {
244 *enable = false;
245 } else {
246 *enable = destItem.isWritable();
247 }
248 } else {
249 *enable = false;
250 }
251
252 if (urls.count() == 1 && urls.first().isLocalFile()) {
253 const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir();
254 text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File");
255 } else if (!urls.isEmpty()) {
256 text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count());
257 } else {
258 text = i18nc("@action:inmenu", "Paste Clipboard Contents…");
259 }
260 } else {
261 *enable = false;
262 text = i18nc("@action:inmenu", "Paste");
263 }
264 return text;
265}
266
267KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut)
268{
269 const QByteArray cutSelectionData = cut ? "1" : "0";
270 mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData);
271}
272
273KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData)
274{
275 const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection"));
276 return (!a.isEmpty() && a.at(0) == '1');
277}
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...)
A namespace for KIO globals.
KIOWIDGETS_EXPORT void setClipboardDataCut(QMimeData *mimeData, bool cut)
Add the information whether the files were cut, into the mimedata.
Definition paste.cpp:267
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:186
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:226
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:273
@ 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:251
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
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
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 Fri Jun 14 2024 11:51:27 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.