kis_clipboard.cc 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 *  Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
16
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
 */
Boudewijn Rempt's avatar
Boudewijn Rempt committed
18

19 20
#include "kis_clipboard.h"

21 22
#include <QApplication>
#include <QClipboard>
23
#include <QDesktopWidget>
David Faure's avatar
David Faure committed
24
#include <QMimeData>
25 26 27
#include <QObject>
#include <QImage>
#include <QMessageBox>
28
#include <QCheckBox>
29
#include <QBuffer>
30
#include <QGlobalStatic>
Adrian Page's avatar
Adrian Page committed
31

32
#include <klocalizedstring.h>
33

34
#include "KoColorSpace.h"
35
#include "KoStore.h"
36
#include <KoColorSpaceRegistry.h>
37
#include <KoColorProfile.h>
38 39 40 41

// kritaimage
#include <kis_types.h>
#include <kis_paint_device.h>
Boudewijn Rempt's avatar
Boudewijn Rempt committed
42
#include <kis_debug.h>
43
#include <kis_annotation.h>
44
#include <kis_node.h>
45 46 47

// local
#include "kis_config.h"
48
#include "kis_store_paintdevice_writer.h"
49
#include "kis_mimedata.h"
50

51 52
Q_GLOBAL_STATIC(KisClipboard, s_instance)

53 54
KisClipboard::KisClipboard()
{
55
    m_pushedClipboard = false;
56
    m_hasClip = false;
57

58 59 60 61
    // Check that we don't already have a clip ready
    clipboardDataChanged();

    // Make sure we are notified when clipboard changes
Boudewijn Rempt's avatar
Boudewijn Rempt committed
62 63
    connect(QApplication::clipboard(), SIGNAL(dataChanged()),
            this, SLOT(clipboardDataChanged()));
64 65


66 67 68 69
}

KisClipboard::~KisClipboard()
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
70
    dbgRegistry << "deleting KisClipBoard";
71 72 73 74
}

KisClipboard* KisClipboard::instance()
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
75
    return s_instance;
76 77
}

78
void KisClipboard::setClip(KisPaintDeviceSP dev, const QPoint& topLeft)
79
{
80
    if (!dev)
81 82 83 84 85 86
        return;

    m_hasClip = true;

    // We'll create a store (ZIP format) in memory
    QBuffer buffer;
Laurent Montel's avatar
Laurent Montel committed
87
    QByteArray mimeType("application/x-krita-selection");
Boudewijn Rempt's avatar
Boudewijn Rempt committed
88
    KoStore* store = KoStore::createStore(&buffer, KoStore::Write, mimeType);
89
    KisStorePaintDeviceWriter writer(store);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
90 91
    Q_ASSERT(store);
    Q_ASSERT(!store->bad());
92
    
93 94

    // Layer data
95
    if (store->open("layerdata")) {
96
        if (!dev->write(writer)) {
97
            dev->disconnect();
98
            store->close();
99
            delete store;
100
            return;
101
        }
102
        store->close();
103
    }
104

105 106
    // Coordinates
    if (store->open("topLeft")) {
107
        store->write(QString("%1 %2").arg(topLeft.x()).arg(topLeft.y()).toLatin1());
108 109
        store->close();
    }
110
    // ColorSpace id of layer data
111
    if (store->open("colormodel")) {
112
        QString csName = dev->colorSpace()->colorModelId().id();
113
        store->write(csName.toLatin1());
114 115 116
        store->close();
    }
    if (store->open("colordepth")) {
117
        QString csName = dev->colorSpace()->colorDepthId().id();
118
        store->write(csName.toLatin1());
119
        store->close();
120
    }
121

122 123
    if (dev->colorSpace()->profile()) {
        const KoColorProfile *profile = dev->colorSpace()->profile();
124
        KisAnnotationSP annotation;
125

Adrian Page's avatar
Adrian Page committed
126
        if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
127 128 129 130 131 132 133 134
            annotation = new  KisAnnotation("icc", profile->name(), profile->rawData());

            if (annotation) {
                // save layer profile
                if (store->open("profile.icc")) {
                    store->write(annotation->annotation());
                    store->close();
                }
135 136 137 138 139 140
            }
        }
    }

    delete store;

141 142
    QMimeData *mimeData = new QMimeData;
    Q_CHECK_PTR(mimeData);
143

144
    if (mimeData) {
145 146 147 148
        mimeData->setData(mimeType, buffer.buffer());
    }

    // We also create a QImage so we can interchange with other applications
149 150
    QImage qimage;
    KisConfig cfg;
151
    const KoColorProfile *monitorProfile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
152
    qimage = dev->convertToQImage(monitorProfile, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
153 154
    if (!qimage.isNull() && mimeData) {
        mimeData->setImageData(qimage);
155
    }
156

157
    if (mimeData) {
158 159 160 161
        m_pushedClipboard = true;
        QClipboard *cb = QApplication::clipboard();
        cb->setMimeData(mimeData);
    }
162

163 164
}

165
KisPaintDeviceSP KisClipboard::clip(const QRect &imageBounds, bool showPopup)
166
{
Laurent Montel's avatar
Laurent Montel committed
167
    QByteArray mimeType("application/x-krita-selection");
168 169

    QClipboard *cb = QApplication::clipboard();
170
    const QMimeData *cbData = cb->mimeData();
171

172
    KisPaintDeviceSP clip;
173

Boudewijn Rempt's avatar
Boudewijn Rempt committed
174
    if (cbData && cbData->hasFormat(mimeType)) {
175
        QByteArray encodedData = cbData->data(mimeType);
Adrian Page's avatar
Adrian Page committed
176
        QBuffer buffer(&encodedData);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
177
        KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType);
178
        
179
        const KoColorProfile *profile = 0;
180

181
        QString csDepth, csModel;
182

183
        // ColorSpace id of layer data
184 185 186
        if (store->hasFile("colormodel")) {
            store->open("colormodel");
            csModel = QString(store->read(store->size()));
187
            store->close();
188 189
        }

190 191 192 193 194
        if (store->hasFile("colordepth")) {
            store->open("colordepth");
            csDepth = QString(store->read(store->size()));
            store->close();
        }
195 196 197 198 199 200 201 202 203 204

        if (store->hasFile("profile.icc")) {
            QByteArray data;
            store->open("profile.icc");
            data = store->read(store->size());
            store->close();
            profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);

        }

205
        const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
206
        if (cs) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
207
            clip = new KisPaintDevice(cs);
208

Boudewijn Rempt's avatar
Boudewijn Rempt committed
209 210
            if (store->hasFile("layerdata")) {
                store->open("layerdata");
211 212 213
                if (!clip->read(store->device())) {
                    clip = 0;
                }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
214 215
                store->close();
            }
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241

            if (clip && !imageBounds.isEmpty()) {

                // load topLeft
                if (store->hasFile("topLeft")) {
                    store->open("topLeft");
                    QString str = store->read(store->size());
                    store->close();
                    QStringList list = str.split(' ');
                    if (list.size() == 2) {
                        QPoint topLeft(list[0].toInt(), list[1].toInt());
                        clip->setX(topLeft.x());
                        clip->setY(topLeft.y());
                    }
                }

                QRect clipBounds = clip->exactBounds();

                if (!imageBounds.contains(clipBounds) &&
                    !imageBounds.intersects(clipBounds)) {

                    QPoint diff = imageBounds.center() - clipBounds.center();
                    clip->setX(clip->x() + diff.x());
                    clip->setY(clip->y() + diff.y());
                }
            }
242
        }
243

244
        delete store;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
245
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
246

247
    if (!clip) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
248
        QImage qimage = cb->image();
249

Boudewijn Rempt's avatar
Boudewijn Rempt committed
250
        if (qimage.isNull())
Adrian Page's avatar
Adrian Page committed
251
            return KisPaintDeviceSP(0);
252 253

        KisConfig cfg;
Laurent Montel's avatar
Laurent Montel committed
254
        quint32 behaviour = cfg.pasteBehaviour();
255 256
        bool saveColorSetting = false;

257

258
        if (behaviour == PASTE_ASK && showPopup) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
259
            // Ask user each time.
260
            QMessageBox mb(qApp->activeWindow());
261 262 263 264 265 266 267 268 269 270
            QCheckBox dontPrompt(i18n("Remember"), &mb);

            dontPrompt.blockSignals(true);



            mb.setWindowTitle(i18nc("@title:window", "Missing Color Profile"));
            mb.setText(i18n("The image data you are trying to paste has no color profile information. How do you want to interpret these data? \n\n As Web (sRGB) -  Use standard colors that are displayed from computer monitors.  This is the most common way that images are stored. \n\nAs on Monitor - If you know a bit about color management and want to use your monitor to determine the color profile.\n\n"));

            // the order of how you add these buttons matters as it determines the index.
271 272 273
            mb.addButton(i18n("As &Web"), QMessageBox::AcceptRole);
            mb.addButton(i18n("As on &Monitor"), QMessageBox::AcceptRole);
            mb.addButton(i18n("Cancel"), QMessageBox::RejectRole);
274 275 276
            mb.addButton(&dontPrompt, QMessageBox::ActionRole);


277 278 279 280 281 282 283

            behaviour = mb.exec();

            if (behaviour > 1) {
                return 0;
            }

284 285 286
            saveColorSetting = dontPrompt.isChecked(); // should we save this option to the config for next time?


287 288
        }

289
        const KoColorSpace * cs;
290
        const KoColorProfile *profile = 0;
291
        if (behaviour == PASTE_ASSUME_MONITOR)
292
            profile = cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow()));
293

294
        cs = KoColorSpaceRegistry::instance()->rgb8(profile);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
295
        if (!cs) {
296
            cs = KoColorSpaceRegistry::instance()->rgb8();
297
            profile = cs->profile();
298 299
        }

300 301
        clip = new KisPaintDevice(cs);
        Q_CHECK_PTR(clip);
302
        clip->convertFromQImage(qimage, profile);
303 304 305 306 307

        QRect clipBounds = clip->exactBounds();
        QPoint diff = imageBounds.center() - clipBounds.center();
        clip->setX(diff.x());
        clip->setY(diff.y());
308 309 310 311 312 313

        // save the persion's selection to the configuration if the option is checked
        if (saveColorSetting) {
            cfg.setPasteBehaviour(behaviour);
        }

314
    }
315

316
    return clip;
317 318 319 320
}

void KisClipboard::clipboardDataChanged()
{
321
    if (!m_pushedClipboard) {
322
        m_hasClip = false;
323
        QClipboard *cb = QApplication::clipboard();
324 325 326 327
        if (cb->mimeData()->hasImage()) {
            QImage qimage = cb->image();
            const QMimeData *cbData = cb->mimeData();
            QByteArray mimeType("application/x-krita-selection");
328

329 330
            if (cbData && cbData->hasFormat(mimeType))
                m_hasClip = true;
331

332 333 334
            if (!qimage.isNull())
                m_hasClip = true;
        }
335
    }
336 337 338
    if (m_hasClip) {
        emit clipCreated();
    }
339
    m_pushedClipboard = false;
340
    emit clipChanged();
341 342 343
}


344
bool KisClipboard::hasClip() const
345
{
346
    return m_hasClip;
347 348
}

349
QSize KisClipboard::clipSize() const
Boudewijn Rempt's avatar
Boudewijn Rempt committed
350 351 352
{

    QClipboard *cb = QApplication::clipboard();
Laurent Montel's avatar
Laurent Montel committed
353
    QByteArray mimeType("application/x-krita-selection");
354
    const QMimeData *cbData = cb->mimeData();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
355 356

    KisPaintDeviceSP clip;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
357

358 359
    if (cbData && cbData->hasFormat(mimeType)) {
        QByteArray encodedData = cbData->data(mimeType);
Adrian Page's avatar
Adrian Page committed
360
        QBuffer buffer(&encodedData);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
361
        KoStore* store = KoStore::createStore(&buffer, KoStore::Read, mimeType);
362
        const KoColorProfile *profile = 0;
363
        QString csDepth, csModel;
364

Boudewijn Rempt's avatar
Boudewijn Rempt committed
365
        // ColorSpace id of layer data
366 367 368
        if (store->hasFile("colormodel")) {
            store->open("colormodel");
            csModel = QString(store->read(store->size()));
369
            store->close();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
370 371
        }

372 373 374 375 376
        if (store->hasFile("colordepth")) {
            store->open("colordepth");
            csDepth = QString(store->read(store->size()));
            store->close();
        }
377 378 379 380 381 382 383 384 385 386

        if (store->hasFile("profile.icc")) {
            QByteArray data;
            store->open("profile.icc");
            data = store->read(store->size());
            store->close();
            profile = KoColorSpaceRegistry::instance()->createColorProfile(csModel, csDepth, data);

        }

387
        const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(csModel, csDepth, profile);
388 389 390
        if (!cs) {
            cs = KoColorSpaceRegistry::instance()->rgb8();
        }
391
        clip = new KisPaintDevice(cs);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
392

393 394
        if (store->hasFile("layerdata")) {
            store->open("layerdata");
395
            clip->read(store->device());
396
            store->close();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
397 398 399 400
        }
        delete store;

        return clip->exactBounds().size();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
401
    } else {
402 403 404 405
        if (cb->mimeData()->hasImage()) {
            QImage qimage = cb->image();
            return qimage.size();
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
406
    }
407
    return QSize();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
408 409
}

410 411
void KisClipboard::setLayers(KisNodeList nodes, KisNodeSP imageRoot, bool forceCopy)
{
412 413 414 415
    /**
     * See a comment in KisMimeData::deepCopyNodes()
     */
    QMimeData *data = KisMimeData::mimeForLayersDeepCopy(nodes, imageRoot, forceCopy);
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    if (!data) return;

    QClipboard *cb = QApplication::clipboard();
    cb->setMimeData(data);
}

bool KisClipboard::hasLayers() const
{
    QClipboard *cb = QApplication::clipboard();
    const QMimeData *cbData = cb->mimeData();
    return cbData->hasFormat("application/x-krita-node");
}

const QMimeData* KisClipboard::layersMimeData() const
{
    QClipboard *cb = QApplication::clipboard();
    const QMimeData *cbData = cb->mimeData();
    return cbData->hasFormat("application/x-krita-node") ? cbData : 0;
}