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 | |
43 | static 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); |
62 | static 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 | |
78 | static 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 |
133 | static 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 |
149 | static 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 | |
156 | static 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 | |
207 | static QStringList (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 |
230 | KIO::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 | |
273 | KIO_EXPORT bool KIO::canPasteMimeSource(const QMimeData* data) |
274 | { |
275 | return data->hasText() || !extractFormats(data).isEmpty(); |
276 | } |
277 | |
278 | KIO::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 |
315 | KIO_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 | |
341 | KIO_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 |
352 | KIO_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) |
366 | KIO_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 |
383 | KIO_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 | |