Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_clipboard.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "kis_clipboard.h"
9
10#include <QApplication>
11#include <QBuffer>
12#include <QClipboard>
13#include <QScreen>
14#include <QFileInfo>
15#include <QMessageBox>
16#include <QMimeData>
17#include <QScopedPointer>
18#include <QTemporaryFile>
19
20// kritaglobal
21#include <algorithm>
22#include <kis_assert.h>
23#include <kis_debug.h>
24
25// kritastore
26#include <KoStore.h>
27
28// kritaimage
29#include <kis_annotation.h>
30#include <kis_default_bounds.h>
31#include <kis_image.h>
32#include <kis_layer_utils.h>
33#include <kis_paint_device.h>
34#include <kis_paint_layer.h>
35#include <kis_time_span.h>
36
37// local
38#include "KisDocument.h"
40#include "KisMainWindow.h"
41#include "KisMimeDatabase.h"
42#include "KisPart.h"
46#include "kis_mimedata.h"
48#include "KisDisplayConfig.h"
49
50
52
54 QSet<QString> mimeTypes;
55 QString format;
56};
57
58class Q_DECL_HIDDEN KisClipboardPrivate
59{
60public:
62 : clipboard(QApplication::clipboard())
63 {
64 }
65
66 bool hasClip {};
67 const KoColorSpace *referenceColorSpace = nullptr;
68 QClipboard *clipboard;
69};
70
72 : d(new KisClipboardPrivate)
73{
74 // Check that we don't already have a clip ready
76
77 // Make sure we are notified when clipboard changes
78 connect(d->clipboard, &QClipboard::dataChanged, this, &KisClipboard::clipboardDataChanged, Qt::UniqueConnection);
79}
80
82{
83 dbgRegistry << "deleting KisClipBoard";
84 delete d;
85}
86
88{
89 return s_instance;
90}
91
93 bool showPopup,
94 int overridePasteBehaviour) const
95{
96 const QMimeData *cbData = d->clipboard->mimeData();
97
98 if (!cbData) {
99 return nullptr;
100 }
101
102 dbgUI << Q_FUNC_INFO;
103 dbgUI << "\tFormats: " << cbData->formats();
104 dbgUI << "\tUrls: " << cbData->urls();
105 dbgUI << "\tHas images: " << cbData->hasImage();
106
107 KisPaintDeviceSP clip = clipFromKritaLayers(nullptr, defaultBounds);
108
109 if (!clip) {
110 KisImageSP clipImage = clipImageFromBoardContents(showPopup, overridePasteBehaviour);
111 if (clipImage) {
113 clip->setDefaultBounds(defaultBounds);
114 }
115 }
116
117 return clip;
118}
119
120KisImageSP KisClipboard::clipImage(bool showPopup, int overridePasteBehaviour) const
121{
122 const QMimeData *cbData = d->clipboard->mimeData();
123
124 if (!cbData) {
125 return nullptr;
126 }
127
128 dbgUI << Q_FUNC_INFO;
129 dbgUI << "\tFormats: " << cbData->formats();
130 dbgUI << "\tUrls: " << cbData->urls();
131 dbgUI << "\tHas images: " << cbData->hasImage();
132
134
135 if (!clipImage) {
136 clipImage = clipImageFromBoardContents(showPopup, overridePasteBehaviour);
137 }
138
139 return clipImage;
140}
141
142KisImageSP KisClipboard::clipImageFromBoardContents(bool showPopup, int overridePasteBehaviour, QPair<bool, PasteFormatBehaviour> source) const
143{
144 const QMimeData *cbData = d->clipboard->mimeData();
145
146 if (!cbData) {
147 return nullptr;
148 }
149
150 // On wayland opening a dialog invalidates the clipboard data so we cache all the data beforehand
151 const auto &urls = cbData->urls();
152 const QImage qimage = getImageWithFallback(cbData, true);
153
154 return clipFromBoardContentsWithData(qimage, urls, showPopup, overridePasteBehaviour, source);
155}
156
158{
159 KisPaintDeviceSP result;
160
161 const QMimeData *data = KisClipboard::instance()->layersMimeData();
162 if (!data) return result;
163
164 const KisMimeData *mimedata = qobject_cast<const KisMimeData *>(data);
165 if (!mimedata) return result;
166
167 return mimedata->clipFromKritaLayers(cs, defaultBounds);
168}
169
171{
172 KisImageSP resultImage;
173
174 const QMimeData *data = KisClipboard::instance()->layersMimeData();
175 if (!data) return resultImage;
176
177 const KisMimeData *mimedata = qobject_cast<const KisMimeData *>(data);
178 if (!mimedata) return resultImage;
179
180 return mimedata->clipImageFromKritaLayers(cs);
181}
182
183QPair<bool, KisClipboard::PasteFormatBehaviour>
184KisClipboard::askUserForSource(const QMimeData *cbData, bool useClipboardFallback) const
185{
186 if (!cbData) {
187 return {false, PASTE_FORMAT_ASK};
188 }
189 const QImage qimage = getImageWithFallback(cbData, useClipboardFallback);
190 QList<QUrl> urls = cbData->urls();
191
192 return askUserForSourceWithData(qimage, urls, useClipboardFallback);
193}
194
195QPair<bool, KisClipboard::PasteFormatBehaviour>
196KisClipboard::askUserForSourceWithData(QImage qimage, const QList<QUrl> urls, bool useClipboardFallback) const
197{
198 KisConfig cfg(true);
199
200 bool saveSourceSetting = false;
201
202 auto choice = (PasteFormatBehaviour)cfg.pasteFormat(false);
203
204 if (!qimage.isNull() || !urls.isEmpty()) {
205 bool local = false;
206 bool remote = false;
207 bool isURI = false;
208
209 std::for_each(urls.constBegin(), urls.constEnd(), [&](const QUrl &url) {
210 local |= url.isLocalFile();
211 remote |= !url.isLocalFile();
212 isURI |= url.scheme() == "data";
213 });
214
215 const bool hasMultipleFormatsAvailable = (remote && local)
216 || (remote && !qimage.isNull()) || (local && !qimage.isNull());
217
218 const bool defaultOptionUnavailable =
219 (!remote && choice == PASTE_FORMAT_DOWNLOAD)
220 || (!local && choice == PASTE_FORMAT_LOCAL)
221 || (qimage.isNull() && choice == PASTE_FORMAT_CLIP);
222
223 dbgUI << "Incoming paste event:";
224 dbgUI << "\tHas attached bitmap:" << !qimage.isNull();
225 dbgUI << "\tHas local images:" << local;
226 dbgUI << "\tHas remote images:" << remote;
227 dbgUI << "\tHas multiple formats:" << hasMultipleFormatsAvailable;
228 dbgUI << "\tDefault source preference" << choice;
229 dbgUI << "\tDefault source available:" << !defaultOptionUnavailable;
230 dbgUI << "\tIs data URI:" << isURI;
231
232 if (hasMultipleFormatsAvailable && choice == PASTE_FORMAT_ASK && !isURI) {
233 KisDlgPasteFormat dlg(qApp->activeWindow());
234
237 dlg.setSourceAvailable(PASTE_FORMAT_CLIP, !qimage.isNull());
238
239 if (dlg.exec() != KoDialog::Accepted) {
240 return {false, PASTE_FORMAT_ASK};
241 };
242
243 choice = dlg.source();
244
245 saveSourceSetting = dlg.remember();
246 } else if (defaultOptionUnavailable || choice == PASTE_FORMAT_ASK) {
247 if (remote) {
248 choice = PASTE_FORMAT_DOWNLOAD;
249 } else if (local) {
250 choice = PASTE_FORMAT_LOCAL;
251 } else if (!qimage.isNull()) {
252 choice = PASTE_FORMAT_CLIP;
253 } else {
254 return {false, PASTE_FORMAT_ASK};
255 }
256 } else if (isURI) {
257 choice = PASTE_FORMAT_DOWNLOAD;
258 }
259 }
260
261 if (saveSourceSetting) {
262 cfg.setPasteFormat(choice);
263 }
264
265 dbgUI << "Selected source for the paste:" << choice;
266
267 return {true, choice};
268}
269
271 QImage qimage,
272 const QList<QUrl> urls,
273 bool showPopup,
274 int overridePasteBehaviour,
275 QPair<bool, PasteFormatBehaviour> source) const
276{
279
280 if (!source.first) {
281 choice = askUserForSourceWithData(qimage , urls).second;
282 } else {
283 choice = source.second;
284 }
285
286 if (choice == PASTE_FORMAT_CLIP) {
287 KIS_SAFE_ASSERT_RECOVER(!qimage.isNull())
288 {
289 warnKrita << "Clipboard was cleared before loading image";
290 return nullptr;
291 }
292
293 int behaviour = overridePasteBehaviour;
294 bool saveColorSetting = false;
295
296 KisConfig cfg(true);
297
298 if (overridePasteBehaviour == -1) {
299 behaviour = cfg.pasteBehaviour();
300 }
301
302 if (behaviour == PASTE_ASK && showPopup) {
303 // Ask user each time.
304 KisDlgMissingColorProfile dlg(qApp->activeWindow());
305
306 if (dlg.exec() != QDialog::Accepted) {
307 return nullptr;
308 }
309
310 behaviour = dlg.source();
311
312 saveColorSetting = dlg.remember(); // should we save this option to the config for next time?
313 }
314
315 const KoColorSpace *cs = nullptr;
316 const KoColorProfile *profile = nullptr;
317 if (!profile && behaviour == PASTE_ASSUME_MONITOR)
319
320 cs = KoColorSpaceRegistry::instance()->rgb8(profile);
321 if (!cs) {
323 profile = cs->profile();
324 }
325
326 KisImageSP image = new KisImage(nullptr, qimage.width(), qimage.height(), cs, "clip");
327 if (qimage.dotsPerMeterX() > 0 && qimage.dotsPerMeterY() > 0) {
328 image->setResolution(qimage.dotsPerMeterX() / 72.0 * 0.0254, qimage.dotsPerMeterY() / 72.0 * 0.0254);
329 }
330 KisPaintLayerSP layer = new KisPaintLayer(image, "clip", OPACITY_OPAQUE_U8);
331 image->addNode(layer);
332 layer->paintDevice()->convertFromQImage(qimage, profile);
333
334 clipImage = image;
335
336 // save the user's selection to the configuration if the option is checked
337 if (saveColorSetting) {
338 cfg.setPasteBehaviour(behaviour);
339 }
340 } else {
341 const auto url = std::find_if(urls.constBegin(), urls.constEnd(), [&](const QUrl &url) {
342 if (choice == PASTE_FORMAT_DOWNLOAD) {
343 return !url.isLocalFile();
344 } else if (choice == PASTE_FORMAT_LOCAL) {
345 return url.isLocalFile();
346 } else {
347 return false;
348 }
349 });
350
351 if (url != urls.constEnd()) {
353 }
354 }
355
356 return clipImage;
357}
358
360{
361 d->hasClip = d->clipboard->mimeData()->hasImage();
362
363 const KisMimeData *mimedata = qobject_cast<const KisMimeData *>(d->clipboard->mimeData());
364 if (mimedata) {
366 } else {
367 d->referenceColorSpace = nullptr;
368 }
369
370 Q_EMIT clipChanged();
371}
372
373QImage KisClipboard::getImageWithFallback(const QMimeData *cbData, bool useClipboardFallback) const
374{
375 QImage qimage = getImageFromMimeData(cbData);
376
377 if (qimage.isNull() && useClipboardFallback) {
378 qimage = d->clipboard->image();
379 }
380
381 return qimage;
382}
383
385{
386 return d->hasClip;
387}
388
393
394void KisClipboard::setLayers(KisNodeList nodes, KisImageSP image, const QRect referenceRect, bool forceCopy)
395{
399 QMimeData *data = KisMimeData::mimeForLayersDeepCopy(nodes, image, referenceRect, forceCopy);
400 if (!data)
401 return;
402
403 d->clipboard->setMimeData(data);
404}
405
407{
408 const QByteArray mimeType = QByteArrayLiteral("application/x-krita-node-internal-pointer");
409 return d->clipboard->mimeData()->hasFormat(mimeType);
410}
411
413{
414 // NOTE: please don't disable the paste action based on the
415 // result of this function, because we allow pasting
416 // of the layer styles as 'text/plain'
417
418 return d->clipboard->mimeData()->hasFormat("application/x-krita-layer-style");
419}
420
421const QMimeData *KisClipboard::layersMimeData() const
422{
423 const QMimeData *cbData = d->clipboard->mimeData();
424 return cbData->hasFormat("application/x-krita-node-internal-pointer") ? cbData : 0;
425}
426
428{
429 return d->clipboard->mimeData()->hasUrls();
430}
431
432
434{
435 return d->clipboard->mimeData()->hasImage();
436}
437
438QImage KisClipboard::getImageFromMimeData(const QMimeData *cbData) const
439{
440 static const QList<ClipboardImageFormat> supportedFormats = {
441 {{"image/png"}, "PNG"},
442 {{"image/tiff"}, "TIFF"},
443 {{"image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"}, "BMP"}};
444
445 QImage image;
446 QSet<QString> clipboardMimeTypes;
447
448 Q_FOREACH (const QString &format, cbData->formats()) {
449 clipboardMimeTypes << format;
450 }
451
452 Q_FOREACH (const ClipboardImageFormat &item, supportedFormats) {
453 const QSet<QString> &intersection = item.mimeTypes & clipboardMimeTypes;
454 if (intersection.isEmpty()) {
455 continue;
456 }
457
458 const QString &format = *intersection.constBegin();
459 const QByteArray &imageData = cbData->data(format);
460 if (imageData.isEmpty()) {
461 continue;
462 }
463
464 if (image.loadFromData(imageData, item.format.toLatin1())) {
465 break;
466 }
467 }
468
469 if (image.isNull() && cbData->hasImage()) {
470 image = qvariant_cast<QImage>(cbData->imageData());
471 }
472
473 return image;
474}
475
476KisImageSP KisClipboard::fetchImageByURL(const QUrl &originalUrl) const
477{
479 KisPaintDeviceSP result;
480 QUrl url(originalUrl);
481 QScopedPointer<QTemporaryFile> tmp;
482
483 if (!originalUrl.isLocalFile()) {
484 tmp.reset(new QTemporaryFile());
485 tmp->setAutoRemove(true);
486
487 // download the file and substitute the url
488 KisRemoteFileFetcher fetcher;
489
490 if (!fetcher.fetchFile(originalUrl, tmp.data())) {
491 qWarning() << "Fetching" << originalUrl << "failed";
492 return clipImage;
493 }
494 url = QUrl::fromLocalFile(tmp->fileName());
495 }
496
497 if (url.isLocalFile()) {
498 QFileInfo fileInfo(url.toLocalFile());
499
500 QString type = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
502
503 if (!mimes.contains(type)) {
505 QMessageBox::warning(KisPart::instance()->currentMainwindow(),
506 i18nc("@title:window", "Krita"),
507 i18n("Could not open %2.\nReason: %1.", msg, url.toDisplayString()));
508 return clipImage;
509 }
510
511 QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
512
513 if (doc->openPath(url.toLocalFile(), KisDocument::DontAddToRecent)) {
514 // Wait for required updates, if any. BUG: 448256
515 KisLayerUtils::forceAllDelayedNodesUpdate(doc->image()->root());
516 doc->image()->waitForDone();
517
518 KisPaintDeviceSP clonedProjection(new KisPaintDevice(*doc->image()->projection()));
519
520 KisImageSP image = new KisImage(nullptr, doc->image()->width(), doc->image()->height(), doc->image()->projection()->colorSpace(), "clip");
521 image->setResolution(doc->image()->xRes(), doc->image()->yRes());
522
523 KisPaintLayerSP layer = new KisPaintLayer(image, "clip", OPACITY_OPAQUE_U8, clonedProjection);
524 image->addNode(layer);
525
526 clipImage = image;
527 } else {
528 qWarning() << "Failed to import file" << url.toLocalFile();
529 }
530 }
531
532 return clipImage;
533}
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
const quint8 OPACITY_OPAQUE_U8
QClipboard * clipboard
const KoColorSpace * referenceColorSpace
void clipboardDataChanged()
QPair< bool, PasteFormatBehaviour > askUserForSourceWithData(QImage qimage, const QList< QUrl > urls, bool useClipboardFallback=false) const
static KisClipboard * instance()
bool hasClip() const
void clipChanged()
QPair< bool, PasteFormatBehaviour > askUserForSource(const QMimeData *data, bool useClipboardFallback=false) const
KisPaintDeviceSP clipFromKritaLayers(const KoColorSpace *cs, KisDefaultBoundsBaseSP defaultBounds) const
KisImageSP clipImageFromBoardContents(bool showPopup, int overridePasteBehaviour, QPair< bool, PasteFormatBehaviour > source={false, PasteFormatBehaviour::PASTE_FORMAT_ASK}) const
QImage getImageWithFallback(const QMimeData *cbData, bool useClipboardFallback=false) const
void setLayers(KisNodeList nodes, KisImageSP image, const QRect referenceRect, bool forceCopy=false)
KisImageSP clipFromBoardContentsWithData(QImage image, const QList< QUrl > urls, bool showPopup, int overridePasteBehaviour=-1, QPair< bool, PasteFormatBehaviour > source={false, PasteFormatBehaviour::PASTE_FORMAT_ASK}) const
KisImageSP clipImage(bool showPopup, int overridePasteBehaviour=-1) const
KisImageSP clipImageFromKritaLayers(const KoColorSpace *cs=nullptr) const
const KoColorSpace * referenceColorSpace() const
const QMimeData * layersMimeData() const
QImage getImageFromMimeData(const QMimeData *cbData) const
bool hasLayerStyles() const
KisImageSP fetchImageByURL(const QUrl &originalUrl) const
bool hasLayers() const
bool hasImage() const
KisClipboardPrivate *const d
~KisClipboard() override
bool hasUrls() const
void setPasteFormat(qint32 format)
qint32 pasteFormat(bool defaultValue) const
qint32 pasteBehaviour(bool defaultValue=false) const
void setPasteBehaviour(qint32 behaviour) const
const KoColorProfile * profile
KisClipboard::PasteBehaviour source() const
KisClipboard::PasteFormatBehaviour source() const
void setSourceAvailable(KisClipboard::PasteFormatBehaviour id, bool value)
KisPaintDeviceSP projection() const
void setResolution(double xres, double yres)
static QStringList supportedMimeTypes(Direction direction)
static KisDisplayConfig displayConfigForMimePastes()
const KoColorSpace * referenceColorSpace() const
KisPaintDeviceSP clipFromKritaLayers(const KoColorSpace *cs, KisDefaultBoundsBaseSP defaultBounds) const
KisImageSP clipImageFromKritaLayers(const KoColorSpace *cs) const
static QMimeData * mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, const QRect referenceRect, bool forceCopy)
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
void convertFromQImage(const QImage &image, const KoColorProfile *profile, qint32 offsetX=0, qint32 offsetY=0)
static KisPart * instance()
Definition KisPart.cpp:130
The KisRemoteFileFetcher class can fetch a remote file and blocks until the file is downloaded.
bool fetchFile(const QUrl &remote, QIODevice *io)
fetch the image. Shows a progress dialog
virtual const KoColorProfile * profile() const =0
This file is part of the Krita application in calligra.
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define warnKrita
Definition kis_debug.h:94
#define dbgUI
Definition kis_debug.h:56
#define dbgRegistry
Definition kis_debug.h:51
void forceAllDelayedNodesUpdate(KisNodeSP root)
QSet< QString > mimeTypes
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
KisPaintDeviceSP paintDevice
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())