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 org::kde::KDirNotify::emitFilesAdded(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
88 }
89 });
90 KJobWidgets::setWindow(job, widget);
91 return job;
92}
93
94static QByteArray chooseFormatAndUrl(const QUrl &u,
95 const QMimeData *mimeData,
96 const QStringList &formats,
97 const QString &text,
98 const QString &suggestedFileName,
99 QWidget *widget,
100 bool clipboard,
101 QUrl *newUrl)
102{
103 QMimeDatabase db;
104 QStringList formatLabels;
105 formatLabels.reserve(formats.size());
106 for (int i = 0; i < formats.size(); ++i) {
107 const QString &fmt = formats[i];
108 QMimeType mime = db.mimeTypeForName(fmt);
109 if (mime.isValid()) {
110 formatLabels.append(i18n("%1 (%2)", mime.comment(), fmt));
111 } else {
112 formatLabels.append(fmt);
113 }
114 }
115
116 QString dialogText(text);
117 if (dialogText.isEmpty()) {
118 dialogText = i18n("Filename for clipboard content:");
119 }
120
121 KIO::PasteDialog dlg(QString(), dialogText, suggestedFileName, formatLabels, widget);
122
123 if (dlg.exec() != QDialog::Accepted) {
124 return QByteArray();
125 }
126
127 const QString chosenFormat = formats[dlg.comboItem()];
128 if (clipboard && !qApp->clipboard()->mimeData()->hasFormat(chosenFormat)) {
130 i18n("The clipboard has changed since you used 'paste': "
131 "the chosen data format is no longer applicable. "
132 "Please copy again what you wanted to paste."));
133 return QByteArray();
134 }
135
136 const QString result = dlg.lineEditText();
137
138 // qDebug() << " result=" << result << " chosenFormat=" << chosenFormat;
139 *newUrl = u;
140 newUrl->setPath(Utils::concatPaths(newUrl->path(), result));
141
142 const QUrl destUrl = getDestinationUrl(u, *newUrl, widget);
143 *newUrl = destUrl;
144
145 // In Qt3, the result of clipboard()->mimeData() only existed until the next
146 // event loop run (see dlg.exec() above), so we re-fetched it.
147 // TODO: This should not be necessary with Qt5; remove this conditional
148 // and test that it still works.
149 if (clipboard) {
150 mimeData = QApplication::clipboard()->mimeData();
151 }
152 const QByteArray ba = mimeData->data(chosenFormat);
153 return ba;
154}
155
156static QStringList extractFormats(const QMimeData *mimeData)
157{
158 QStringList formats;
159 const QStringList allFormats = mimeData->formats();
160 for (const QString &format : allFormats) {
161 if (format == QLatin1String("application/x-qiconlist")) { // Q3IconView and kde4's libkonq
162 continue;
163 }
164 if (format == QLatin1String("application/x-kde-cutselection")) { // see isClipboardDataCut
165 continue;
166 }
167 if (format == QLatin1String("application/x-kde-suggestedfilename")) {
168 continue;
169 }
170 if (format.startsWith(QLatin1String("application/x-qt-"))) { // Qt-internal
171 continue;
172 }
173 if (format.startsWith(QLatin1String("x-kmail-drag/"))) { // app-internal
174 continue;
175 }
176 if (!format.contains(QLatin1Char('/'))) { // e.g. TARGETS, MULTIPLE, TIMESTAMP
177 continue;
178 }
179 formats.append(format);
180 }
181 return formats;
182}
183
184KIOWIDGETS_EXPORT bool KIO::canPasteMimeData(const QMimeData *data)
185{
186 return data->hasText() || !extractFormats(data).isEmpty();
187}
188
189KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl, const QString &dialogText, QWidget *widget, bool clipboard)
190{
191 QByteArray ba;
192 const QString suggestedFilename = QString::fromUtf8(mimeData->data(QStringLiteral("application/x-kde-suggestedfilename")));
193
194 // Now check for plain text
195 // We don't want to display a MIME type choice for a QTextDrag, those MIME type look ugly.
196 if (mimeData->hasText()) {
197 ba = mimeData->text().toLocal8Bit(); // encoding OK?
198 } else {
199 const QStringList formats = extractFormats(mimeData);
200 if (formats.isEmpty()) {
201 return nullptr;
202 } else if (formats.size() > 1) {
203 QUrl newUrl;
204 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
205 if (ba.isEmpty() || newUrl.isEmpty()) {
206 return nullptr;
207 }
208 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
209 }
210 ba = mimeData->data(formats.first());
211 }
212 if (ba.isEmpty()) {
213 return nullptr;
214 }
215
216 const QUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget);
217 if (newUrl.isEmpty()) {
218 return nullptr;
219 }
220
221 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
222}
223
224KIOWIDGETS_EXPORT QString KIO::pasteActionText(const QMimeData *mimeData, bool *enable, const KFileItem &destItem)
225{
226 bool canPasteData = false;
227 QList<QUrl> urls;
228
229 // mimeData can be 0 according to https://bugs.kde.org/show_bug.cgi?id=335053
230 if (mimeData) {
231 canPasteData = KIO::canPasteMimeData(mimeData);
232 urls = KUrlMimeData::urlsFromMimeData(mimeData);
233 } else {
234 qCWarning(KIO_WIDGETS) << "QApplication::clipboard()->mimeData() is 0!";
235 }
236
237 QString text;
238 if (!urls.isEmpty() || canPasteData) {
239 // disable the paste action if no writing is supported
240 if (!destItem.isNull()) {
241 if (destItem.url().isEmpty()) {
242 *enable = false;
243 } else {
244 *enable = destItem.isWritable();
245 }
246 } else {
247 *enable = false;
248 }
249
250 if (urls.count() == 1 && urls.first().isLocalFile()) {
251 const bool isDir = QFileInfo(urls.first().toLocalFile()).isDir();
252 text = isDir ? i18nc("@action:inmenu", "Paste One Folder") : i18nc("@action:inmenu", "Paste One File");
253 } else if (!urls.isEmpty()) {
254 text = i18ncp("@action:inmenu", "Paste One Item", "Paste %1 Items", urls.count());
255 } else {
256 text = i18nc("@action:inmenu", "Paste Clipboard Contents...");
257 }
258 } else {
259 *enable = false;
260 text = i18nc("@action:inmenu", "Paste");
261 }
262 return text;
263}
264
265KIOWIDGETS_EXPORT void KIO::setClipboardDataCut(QMimeData *mimeData, bool cut)
266{
267 const QByteArray cutSelectionData = cut ? "1" : "0";
268 mimeData->setData(QStringLiteral("application/x-kde-cutselection"), cutSelectionData);
269}
270
271KIOWIDGETS_EXPORT bool KIO::isClipboardDataCut(const QMimeData *mimeData)
272{
273 const QByteArray a = mimeData->data(QStringLiteral("application/x-kde-cutselection"));
274 return (!a.isEmpty() && a.at(0) == '1');
275}
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:265
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:184
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:224
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:271
@ 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 Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.