1/* This file is part of the KDE libraries
2 Copyright (C) 2000 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*/
18
19#include "paste.h"
20#include "pastedialog.h"
21
22#include "kio/job.h"
23#include "kio/copyjob.h"
24#include "kio/deletejob.h"
25#include "kio/global.h"
26#include "kio/netaccess.h"
27#include "kio/renamedialog.h"
28#include "kio/kprotocolmanager.h"
29#include "jobuidelegate.h"
30
31#include <kurl.h>
32#include <kdebug.h>
33#include <klocale.h>
34#include <kinputdialog.h>
35#include <kmessagebox.h>
36#include <kmimetype.h>
37#include <ktemporaryfile.h>
38
39#include <QtGui/QApplication>
40#include <QtGui/QClipboard>
41#include <QMimeData>
42
43static bool decodeIsCutSelection(const QMimeData *mimeData)
44{
45 const QByteArray data = mimeData->data("application/x-kde-cutselection");
46 return data.isEmpty() ? false : data.at(0) == '1';
47}
48
49// This could be made a public method, if there's a need for pasting only urls
50// and not random data.
51/**
52 * Pastes URLs from the clipboard. This results in a copy or move job,
53 * depending on whether the user has copied or cut the items.
54 *
55 * @param mimeData the mimeData to paste, usually QApplication::clipboard()->mimeData()
56 * @param destDir Destination directory where the items will be copied/moved.
57 * @param flags the flags are passed to KIO::copy or KIO::move.
58 * @return the copy or move job handling the operation, or 0 if there is nothing to do
59 * @since ...
60 */
61//KIO_EXPORT Job *pasteClipboardUrls(const KUrl& destDir, JobFlags flags = DefaultFlags);
62static KIO::Job *pasteClipboardUrls(const QMimeData* mimeData, const KUrl& destDir, KIO::JobFlags flags = KIO::DefaultFlags)
63{
64 const KUrl::List urls = KUrl::List::fromMimeData(mimeData, KUrl::List::PreferLocalUrls);
65 if (!urls.isEmpty()) {
66 const bool move = decodeIsCutSelection(mimeData);
67 KIO::Job *job = 0;
68 if (move) {
69 job = KIO::move(urls, destDir, flags);
70 } else {
71 job = KIO::copy(urls, destDir, flags);
72 }
73 return job;
74 }
75 return 0;
76}
77
78static KUrl getNewFileName( const KUrl &u, const QString& text, const QString& suggestedFileName, QWidget *widget, bool delIfOverwrite )
79{
80 bool ok;
81 QString dialogText( text );
82 if ( dialogText.isEmpty() )
83 dialogText = i18n( "Filename for clipboard content:" );
84 QString file = KInputDialog::getText( QString(), dialogText, suggestedFileName, &ok, widget );
85 if ( !ok )
86 return KUrl();
87
88 KUrl myurl(u);
89 myurl.addPath( file );
90
91 // Check for existing destination file.
92 // When we were using CopyJob, we couldn't let it do that (would expose
93 // an ugly tempfile name as the source URL)
94 // And now we're using a put job anyway, no destination checking included.
95 if (KIO::NetAccess::exists(myurl, KIO::NetAccess::DestinationSide, widget))
96 {
97 kDebug(7007) << "Paste will overwrite file. Prompting...";
98 KIO::RenameDialog_Result res = KIO::R_OVERWRITE;
99
100 KIO::RenameDialog dlg( widget,
101 i18n("File Already Exists"),
102 u.pathOrUrl(),
103 myurl.pathOrUrl(),
104 (KIO::RenameDialog_Mode) (KIO::M_OVERWRITE | KIO::M_SINGLE) );
105 res = static_cast<KIO::RenameDialog_Result>(dlg.exec());
106
107 if ( res == KIO::R_RENAME )
108 {
109 myurl = dlg.newDestUrl();
110 }
111 else if ( res == KIO::R_CANCEL )
112 {
113 return KUrl();
114 } else if (res == KIO::R_OVERWRITE)
115 {
116 // Old hack. With the put job we just pass Overwrite.
117 if (delIfOverwrite) {
118 // Ideally we would just pass KIO::Overwrite to the job in pasteDataAsyncTo.
119 // But 1) CopyJob doesn't support that (it wouldn't really apply to multiple files) [not true anymore]
120 // 2) we can't use file_move because CopyJob* is everywhere in the API (see TODO)
121 // But well the simpler is really to delete the dest:
122 KIO::Job* delJob = KIO::del(myurl);
123 delJob->exec();
124 }
125 }
126 }
127
128 return myurl;
129}
130
131// Old solution
132// The final step: write _data to tempfile and move it to newUrl
133static KIO::CopyJob* pasteDataAsyncTo( const KUrl& newUrl, const QByteArray& _data )
134{
135 // ### Bug: because we move from a tempfile to the destination,
136 // if the user does "Undo" then we won't ask for confirmation, and we'll
137 // move back to a tempfile, instead of just deleting.
138 // A KIO::storedPut would be better but FileUndoManager would need to support it first.
139 KTemporaryFile tempFile;
140 tempFile.setAutoRemove(false);
141 tempFile.open();
142 tempFile.write(_data.data(), _data.size());
143 tempFile.flush();
144 KUrl origUrl(tempFile.fileName());
145 return KIO::move(origUrl, newUrl);
146}
147
148// New solution
149static KIO::Job* putDataAsyncTo(const KUrl& url, const QByteArray& data, QWidget* widget, KIO::JobFlags flags)
150{
151 KIO::Job* job = KIO::storedPut(data, url, -1, flags);
152 job->ui()->setWindow(widget);
153 return job;
154}
155
156static QByteArray chooseFormatAndUrl(const KUrl& u, const QMimeData* mimeData,
157 const QStringList& formats,
158 const QString& text,
159 const QString& suggestedFileName,
160 QWidget* widget,
161 bool clipboard,
162 KUrl* newUrl)
163{
164 QStringList formatLabels;
165 for ( int i = 0; i < formats.size(); ++i ) {
166 const QString& fmt = formats[i];
167 KMimeType::Ptr mime = KMimeType::mimeType(fmt, KMimeType::ResolveAliases);
168 if (mime)
169 formatLabels.append( i18n("%1 (%2)", mime->comment(), fmt) );
170 else
171 formatLabels.append( fmt );
172 }
173
174 QString dialogText( text );
175 if ( dialogText.isEmpty() )
176 dialogText = i18n( "Filename for clipboard content:" );
177 //using QString() instead of QString::null didn't compile (with gcc 3.2.3), because the ctor was mistaken as a function declaration, Alex //krazy:exclude=nullstrassign
178 KIO::PasteDialog dlg( QString::null, dialogText, suggestedFileName, formatLabels, widget, clipboard ); //krazy:exclude=nullstrassign
179
180 if ( dlg.exec() != KDialog::Accepted )
181 return QByteArray();
182
183 if ( clipboard && dlg.clipboardChanged() ) {
184 KMessageBox::sorry( widget,
185 i18n( "The clipboard has changed since you used 'paste': "
186 "the chosen data format is no longer applicable. "
187 "Please copy again what you wanted to paste." ) );
188 return QByteArray();
189 }
190
191 const QString result = dlg.lineEditText();
192 const QString chosenFormat = formats[ dlg.comboItem() ];
193
194 kDebug() << " result=" << result << " chosenFormat=" << chosenFormat;
195 *newUrl = KUrl( u );
196 newUrl->addPath( result );
197 // if "data" came from QClipboard, then it was deleted already - by a nice 0-seconds timer
198 // In that case, get it again. Let's hope the user didn't copy something else meanwhile :/
199 // #### QT4/KDE4 TODO: check that this is still the case
200 if ( clipboard ) {
201 mimeData = QApplication::clipboard()->mimeData();
202 }
203 const QByteArray ba = mimeData->data( chosenFormat );
204 return ba;
205}
206
207static QStringList extractFormats(const QMimeData* mimeData)
208{
209 QStringList formats;
210 const QStringList allFormats = mimeData->formats();
211 Q_FOREACH(const QString& format, allFormats) {
212 if (format == QLatin1String("application/x-qiconlist")) // see QIconDrag
213 continue;
214 if (format == QLatin1String("application/x-kde-cutselection")) // see KonqDrag
215 continue;
216 if (format == QLatin1String("application/x-kde-suggestedfilename"))
217 continue;
218 if (format.startsWith(QLatin1String("application/x-qt-"))) // Qt-internal
219 continue;
220 if (format.startsWith(QLatin1String("x-kmail-drag/"))) // app-internal
221 continue;
222 if (!format.contains(QLatin1Char('/'))) // e.g. TARGETS, MULTIPLE, TIMESTAMP
223 continue;
224 formats.append(format);
225 }
226 return formats;
227}
228
229// The [old] main method for dropping
230KIO::CopyJob* KIO::pasteMimeSource( const QMimeData* mimeData, const KUrl& destUrl,
231 const QString& dialogText, QWidget* widget, bool clipboard )
232{
233 QByteArray ba;
234
235 const QString suggestedFilename = QString::fromUtf8(mimeData->data("application/x-kde-suggestedfilename"));
236
237 // Now check for plain text
238 // We don't want to display a mimetype choice for a QTextDrag, those mimetypes look ugly.
239 if ( mimeData->hasText() )
240 {
241 ba = mimeData->text().toLocal8Bit(); // encoding OK?
242 }
243 else
244 {
245 const QStringList formats = extractFormats(mimeData);
246 if ( formats.size() == 0 )
247 return 0;
248
249 if ( formats.size() > 1 ) {
250 KUrl newUrl;
251 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
252 KIO::CopyJob* job = pasteDataAsyncTo(newUrl, ba);
253 job->ui()->setWindow(widget);
254 return job;
255 }
256 ba = mimeData->data( formats.first() );
257 }
258 if ( ba.isEmpty() )
259 {
260 KMessageBox::sorry( widget, i18n("The clipboard is empty") );
261 return 0;
262 }
263
264 const KUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget, true);
265 if (newUrl.isEmpty())
266 return 0;
267
268 KIO::CopyJob* job = pasteDataAsyncTo(newUrl, ba);
269 job->ui()->setWindow(widget);
270 return job;
271}
272
273KIO_EXPORT bool KIO::canPasteMimeSource(const QMimeData* data)
274{
275 return data->hasText() || !extractFormats(data).isEmpty();
276}
277
278KIO::Job* pasteMimeDataImpl(const QMimeData* mimeData, const KUrl& destUrl,
279 const QString& dialogText, QWidget* widget,
280 bool clipboard)
281{
282 QByteArray ba;
283 const QString suggestedFilename = QString::fromUtf8(mimeData->data("application/x-kde-suggestedfilename"));
284
285 // Now check for plain text
286 // We don't want to display a mimetype choice for a QTextDrag, those mimetypes look ugly.
287 if (mimeData->hasText()) {
288 ba = mimeData->text().toLocal8Bit(); // encoding OK?
289 } else {
290 const QStringList formats = extractFormats(mimeData);
291 if (formats.isEmpty()) {
292 return 0;
293 } else if (formats.size() > 1) {
294 KUrl newUrl;
295 ba = chooseFormatAndUrl(destUrl, mimeData, formats, dialogText, suggestedFilename, widget, clipboard, &newUrl);
296 if (ba.isEmpty()) {
297 return 0;
298 }
299 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
300 }
301 ba = mimeData->data(formats.first());
302 }
303 if (ba.isEmpty()) {
304 return 0;
305 }
306
307 const KUrl newUrl = getNewFileName(destUrl, dialogText, suggestedFilename, widget, false);
308 if (newUrl.isEmpty())
309 return 0;
310
311 return putDataAsyncTo(newUrl, ba, widget, KIO::Overwrite);
312}
313
314// The main method for pasting
315KIO_EXPORT KIO::Job *KIO::pasteClipboard( const KUrl& destUrl, QWidget* widget, bool move )
316{
317 Q_UNUSED(move);
318
319 if ( !destUrl.isValid() ) {
320 KMessageBox::error( widget, i18n( "Malformed URL\n%1", destUrl.prettyUrl() ) );
321 return 0;
322 }
323
324 // TODO: if we passed mimeData as argument, we could write unittests that don't
325 // mess up the clipboard and that don't need QtGui.
326 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
327
328 if (KUrl::List::canDecode(mimeData)) {
329 // We can ignore the bool move, KIO::paste decodes it
330 KIO::Job* job = pasteClipboardUrls(mimeData, destUrl);
331 if (job) {
332 job->ui()->setWindow(widget);
333 return job;
334 }
335 }
336
337 return pasteMimeDataImpl(mimeData, destUrl, QString(), widget, true /*clipboard*/);
338}
339
340
341KIO_EXPORT void KIO::pasteData(const KUrl& u, const QByteArray& data, QWidget* widget)
342{
343 const KUrl newUrl = getNewFileName(u, QString(), QString(), widget, false);
344 if (newUrl.isEmpty())
345 return;
346
347 KIO::Job* job = putDataAsyncTo(newUrl, data, widget, KIO::Overwrite);
348 KIO::NetAccess::synchronousRun(job, widget);
349}
350
351// KDE5: remove
352KIO_EXPORT KIO::CopyJob* KIO::pasteDataAsync( const KUrl& u, const QByteArray& _data, QWidget *widget, const QString& text )
353{
354 KUrl newUrl = getNewFileName(u, text, QString(), widget, true);
355
356 if (newUrl.isEmpty())
357 return 0;
358
359 KIO::CopyJob* job = pasteDataAsyncTo( newUrl, _data );
360 job->ui()->setWindow(widget);
361 return job;
362}
363
364// NOTE: DolphinView::pasteInfo() has a better version of this
365// (but which requires KonqFileItemCapabilities)
366KIO_EXPORT QString KIO::pasteActionText()
367{
368 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
369 const KUrl::List urls = KUrl::List::fromMimeData( mimeData );
370 if ( !urls.isEmpty() ) {
371 if ( urls.first().isLocalFile() )
372 return i18np( "&Paste File", "&Paste %1 Files", urls.count() );
373 else
374 return i18np( "&Paste URL", "&Paste %1 URLs", urls.count() );
375 } else if ( !mimeData->formats().isEmpty() ) {
376 return i18n( "&Paste Clipboard Contents" );
377 } else {
378 return QString();
379 }
380}
381
382// The [new] main method for dropping
383KIO_EXPORT KIO::Job* KIO::pasteMimeData(const QMimeData* mimeData, const KUrl& destUrl,
384 const QString& dialogText, QWidget* widget)
385{
386 return pasteMimeDataImpl(mimeData, destUrl, dialogText, widget, false /*not clipboard*/);
387}
388