composer.cpp 19.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
  Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
  Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This library 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 Library General Public
  License for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; see the file COPYING.LIB.  If not, write to the
  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  02110-1301, USA.
*/

#include "composer.h"
23

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include "job/attachmentjob.h"
#include "part/globalpart.h"
#include "part/infopart.h"
#include "job/jobbase_p.h"
#include "part/textpart.h"
#include "job/maintextjob.h"
#include "job/multipartjob.h"
#include "job/signjob.h"
#include "job/encryptjob.h"
#include "job/signencryptjob.h"
#include "job/skeletonmessagejob.h"
#include "job/transparentjob.h"
#include "imagescaling/imagescaling.h"
#include "imagescaling/imagescalingutils.h"
#include "settings/messagecomposersettings.h"

#include "messagecomposer_debug.h"
41
#include <KLocalizedString>
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

using namespace MessageComposer;
using MessageCore::AttachmentPart;

class MessageComposer::ComposerPrivate : public JobBasePrivate
{
public:
    ComposerPrivate(Composer *qq)
        : JobBasePrivate(qq)
    {
    }

    void init();
    void doStart(); // slot
    void composeStep1();
    void composeStep2();
58
    Q_REQUIRED_RESULT QList<ContentJobBase *> createEncryptJobs(ContentJobBase *contentJob, bool sign);
59
    void contentJobFinished(KJob *job);   // slot
Laurent Montel's avatar
Laurent Montel committed
60
    void composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector<GpgME::Key> &keys, const QStringList &recipients);
61 62 63 64
    void attachmentsFinished(KJob *job);   // slot

    void composeFinalStep(KMime::Content *headers, KMime::Content *content);

Laurent Montel's avatar
Laurent Montel committed
65
    QVector<QPair<QStringList, std::vector<GpgME::Key> > > encData;
66 67 68 69 70
    std::vector<GpgME::Key> signers;
    AttachmentPart::List attachmentParts;
    // attachments with different sign/encrypt settings from
    // main message body. added at the end of the process
    AttachmentPart::List lateAttachmentParts;
Laurent Montel's avatar
Laurent Montel committed
71
    QVector<KMime::Message::Ptr> resultMessages;
72

73 74
    Kleo::CryptoMessageFormat format;

75
    // Stuff that the application plays with.
Laurent Montel's avatar
Laurent Montel committed
76 77 78
    GlobalPart *globalPart = nullptr;
    InfoPart *infoPart = nullptr;
    TextPart *textPart = nullptr;
79 80

    // Stuff that we play with.
Laurent Montel's avatar
Laurent Montel committed
81
    KMime::Message *skeletonMessage = nullptr;
82

83 84 85 86 87 88
    bool started = false;
    bool finished = false;
    bool sign = false;
    bool encrypt = false;
    bool noCrypto = false;
    bool autoSaving = false;
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    Q_DECLARE_PUBLIC(Composer)
};

void ComposerPrivate::init()
{
    Q_Q(Composer);

    // We cannot create these in ComposerPrivate's constructor, because
    // their parent q is not fully constructed at that time.
    globalPart = new GlobalPart(q);
    infoPart = new InfoPart(q);
    textPart = new TextPart(q);
}

void ComposerPrivate::doStart()
{
    Q_ASSERT(!started);
    started = true;
    composeStep1();
}

void ComposerPrivate::composeStep1()
{
    Q_Q(Composer);

    // Create skeleton message (containing headers only; no content).
    SkeletonMessageJob *skeletonJob = new SkeletonMessageJob(infoPart, globalPart, q);
116 117 118 119 120 121 122 123 124 125 126 127
    QObject::connect(skeletonJob, &SkeletonMessageJob::finished, q, [this, skeletonJob](KJob *job) {
        if (job->error()) {
            return; // KCompositeJob takes care of the error.
        }

        // SkeletonMessageJob is a special job creating a Message instead of a Content.
        Q_ASSERT(skeletonMessage == nullptr);
        skeletonMessage = skeletonJob->message();
        Q_ASSERT(skeletonMessage);
        skeletonMessage->assemble();

        composeStep2();
Laurent Montel's avatar
Laurent Montel committed
128
    });
129 130 131 132 133 134 135 136
    q->addSubjob(skeletonJob);
    skeletonJob->start();
}

void ComposerPrivate::composeStep2()
{
    Q_Q(Composer);

Laurent Montel's avatar
Laurent Montel committed
137
    ContentJobBase *mainJob = nullptr;
138 139 140 141 142 143 144 145
    MainTextJob *mainTextJob = new MainTextJob(textPart, q);

    if ((sign || encrypt) && format & Kleo::InlineOpenPGPFormat) {    // needs custom handling --- one SignEncryptJob by itself
        qCDebug(MESSAGECOMPOSER_LOG) << "sending to sign/enc inline job!";

        if (encrypt) {
            //TODO: fix Inline PGP with encrypted attachments

Laurent Montel's avatar
Laurent Montel committed
146 147
            const QList<ContentJobBase *> jobs = createEncryptJobs(mainTextJob, sign);
            for (ContentJobBase *subJob : jobs) {
148 149 150 151 152 153 154
                if (attachmentParts.isEmpty()) {
                    // We have no attachments.  Use the content given by the MainTextJob.
                    mainJob = subJob;
                } else {
                    MultipartJob *multipartJob = new MultipartJob(q);
                    multipartJob->setMultipartSubtype("mixed");
                    multipartJob->appendSubjob(subJob);
Laurent Montel's avatar
Laurent Montel committed
155
                    for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
156 157 158 159 160
                        multipartJob->appendSubjob(new AttachmentJob(part));
                    }
                    mainJob = multipartJob;
                }

Laurent Montel's avatar
Laurent Montel committed
161
                QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
                q->addSubjob(mainJob);
            }
        } else {
            SignJob *subJob = new SignJob(q);
            subJob->setSigningKeys(signers);
            subJob->setCryptoMessageFormat(format);
            subJob->appendSubjob(mainTextJob);

            if (attachmentParts.isEmpty()) {
                // We have no attachments.  Use the content given by the MainTextJob.
                mainJob = subJob;
            } else {
                MultipartJob *multipartJob = new MultipartJob(q);
                multipartJob->setMultipartSubtype("mixed");
                multipartJob->appendSubjob(subJob);
Laurent Montel's avatar
Laurent Montel committed
177
                for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
178 179 180 181
                    multipartJob->appendSubjob(new AttachmentJob(part));
                }
                mainJob = multipartJob;
            }
Laurent Montel's avatar
Laurent Montel committed
182
            QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
183 184 185
            q->addSubjob(mainJob);
        }

186 187 188 189 190
        if (mainJob) {
            mainJob->start();
        } else {
            qCDebug(MESSAGECOMPOSER_LOG) << "main job is null";
        }
191 192 193 194 195 196 197 198 199 200 201
        return;
    }

    if (attachmentParts.isEmpty()) {
        // We have no attachments.  Use the content given by the MainTextJob.
        mainJob = mainTextJob;
    } else {
        // We have attachments.  Create a multipart/mixed content.
        QMutableListIterator<AttachmentPart::Ptr> iter(attachmentParts);
        while (iter.hasNext()) {
            AttachmentPart::Ptr part = iter.next();
202
            qCDebug(MESSAGECOMPOSER_LOG) << "Checking attachment crypto policy... signed: " << part->isSigned() << " isEncrypted : " << part->isEncrypted();
203 204 205 206 207 208 209 210 211
            if (!noCrypto && !autoSaving && (sign != part->isSigned() || encrypt != part->isEncrypted())) {    // different policy
                qCDebug(MESSAGECOMPOSER_LOG) << "got attachment with different crypto policy!";
                lateAttachmentParts.append(part);
                iter.remove();
            }
        }
        MultipartJob *multipartJob = new MultipartJob(q);
        multipartJob->setMultipartSubtype("mixed");
        multipartJob->appendSubjob(mainTextJob);
Laurent Montel's avatar
Laurent Montel committed
212
        for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
213 214 215 216 217 218 219 220 221 222
            multipartJob->appendSubjob(new AttachmentJob(part));
        }
        mainJob = multipartJob;
    }

    if (sign) {
        SignJob *sJob = new SignJob(q);
        sJob->setCryptoMessageFormat(format);
        sJob->setSigningKeys(signers);
        sJob->appendSubjob(mainJob);
223 224 225
        if (!encrypt) {
            sJob->setSkeletonMessage(skeletonMessage);
        }
226 227 228 229
        mainJob = sJob;
    }

    if (encrypt) {
230 231
        const auto lstJob = createEncryptJobs(mainJob, false);
        for (ContentJobBase *eJob : lstJob) {
Laurent Montel's avatar
Laurent Montel committed
232
            QObject::connect(eJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
233 234 235 236
            q->addSubjob(eJob);
            mainJob = eJob;         //start only last EncryptJob
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
237
        QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
        q->addSubjob(mainJob);
    }

    mainJob->start();
}

QList<ContentJobBase *> ComposerPrivate::createEncryptJobs(ContentJobBase *contentJob, bool sign)
{
    Q_Q(Composer);

    QList<ContentJobBase *> jobs;

    // each SplitInfo holds a list of recipients/keys, if there is more than
    // one item in it then it means there are secondary recipients that need
    // different messages w/ clean headers
    qCDebug(MESSAGECOMPOSER_LOG) << "starting enc jobs";
    qCDebug(MESSAGECOMPOSER_LOG) << "format:" << format;
    qCDebug(MESSAGECOMPOSER_LOG) << "enc data:" << encData.size();

    if (encData.isEmpty()) {  // no key data! bail!
        q->setErrorText(i18n("No key data for recipients found."));
        q->setError(Composer::IncompleteError);
        q->emitResult();
        return jobs;
    }

    const int encDataSize = encData.size();
    jobs.reserve(encDataSize);
    for (int i = 0; i < encDataSize; ++i) {
        QPair<QStringList, std::vector<GpgME::Key> > recipients = encData[ i ];
        qCDebug(MESSAGECOMPOSER_LOG) << "got first list of recipients:" << recipients.first;
Laurent Montel's avatar
Laurent Montel committed
269
        ContentJobBase *subJob = nullptr;
270 271 272 273 274 275 276
        if (sign) {
            SignEncryptJob *seJob = new SignEncryptJob(q);

            seJob->setCryptoMessageFormat(format);
            seJob->setSigningKeys(signers);
            seJob->setEncryptionKeys(recipients.second);
            seJob->setRecipients(recipients.first);
277
            seJob->setSkeletonMessage(skeletonMessage);
278 279 280 281 282 283 284

            subJob = seJob;
        } else {
            EncryptJob *eJob = new EncryptJob(q);
            eJob->setCryptoMessageFormat(format);
            eJob->setEncryptionKeys(recipients.second);
            eJob->setRecipients(recipients.first);
285
            eJob->setSkeletonMessage(skeletonMessage);
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
            subJob = eJob;
        }
        qCDebug(MESSAGECOMPOSER_LOG) << "subJob" << subJob;
        subJob->appendSubjob(contentJob);
        jobs.append(subJob);
    }
    qCDebug(MESSAGECOMPOSER_LOG) << jobs.size();
    return jobs;
}

void ComposerPrivate::contentJobFinished(KJob *job)
{
    if (job->error()) {
        return; // KCompositeJob takes care of the error.
    }
    qCDebug(MESSAGECOMPOSER_LOG) << "composing final message";

Laurent Montel's avatar
Laurent Montel committed
303 304
    KMime::Message *headers = nullptr;
    KMime::Content *resultContent = nullptr;
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
    std::vector<GpgME::Key> keys;
    QStringList recipients;

    Q_ASSERT(dynamic_cast<ContentJobBase *>(job) == static_cast<ContentJobBase *>(job));
    ContentJobBase *contentJob = static_cast<ContentJobBase *>(job);

    // create the final headers and body,
    // taking into account secondary recipients for encryption
    if (encData.size() > 1) {  // crypto job with secondary recipients..
        Q_ASSERT(dynamic_cast<MessageComposer::AbstractEncryptJob *>(job));    // we need to get the recipients for this job
        MessageComposer::AbstractEncryptJob *eJob = dynamic_cast<MessageComposer::AbstractEncryptJob *>(job);

        keys = eJob->encryptionKeys();
        recipients = eJob->recipients();

        resultContent = contentJob->content(); // content() comes from superclass
        headers = new KMime::Message;
        headers->setHeader(skeletonMessage->from());
        headers->setHeader(skeletonMessage->to());
        headers->setHeader(skeletonMessage->cc());
        headers->setHeader(skeletonMessage->subject());
        headers->setHeader(skeletonMessage->date());
        headers->setHeader(skeletonMessage->messageID());

        KMime::Headers::Generic *realTo = new KMime::Headers::Generic("X-KMail-EncBccRecipients");
Laurent Montel's avatar
Laurent Montel committed
330
        realTo->fromUnicodeString(eJob->recipients().join(QLatin1Char('%')), "utf-8");
331 332 333 334 335 336

        qCDebug(MESSAGECOMPOSER_LOG) << "got one of multiple messages sending to:" << realTo->asUnicodeString();
        qCDebug(MESSAGECOMPOSER_LOG) << "sending to recipients:" << recipients;
        headers->setHeader(realTo);
        headers->assemble();
    } else { // just use the saved headers from before
Laurent Montel's avatar
Laurent Montel committed
337
        if (!encData.isEmpty()) {
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
            qCDebug(MESSAGECOMPOSER_LOG) << "setting enc data:" << encData[ 0 ].first << "with num keys:" << encData[ 0 ].second.size();
            keys = encData[ 0 ].second;
            recipients = encData[ 0 ].first;
        }

        headers = skeletonMessage;
        resultContent = contentJob->content();
    }

    if (lateAttachmentParts.isEmpty()) {
        composeFinalStep(headers, resultContent);
    } else {
        composeWithLateAttachments(headers, resultContent, lateAttachmentParts, keys, recipients);
    }
}

Laurent Montel's avatar
Laurent Montel committed
354
void ComposerPrivate::composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector<GpgME::Key> &keys, const QStringList &recipients)
355 356 357 358 359 360 361 362 363 364 365 366
{
    Q_Q(Composer);

    MultipartJob *multiJob = new MultipartJob(q);
    multiJob->setMultipartSubtype("mixed");

    // wrap the content into a job for the multijob to handle it
    MessageComposer::TransparentJob *tJob = new MessageComposer::TransparentJob(q);
    tJob->setContent(content);
    multiJob->appendSubjob(tJob);
    multiJob->setExtraContent(headers);

367
    qCDebug(MESSAGECOMPOSER_LOG) << "attachment encr key size:" << keys.size() << " recipients: " << recipients;
368 369

    // operate correctly on each attachment that has a different crypto policy than body.
Laurent Montel's avatar
Laurent Montel committed
370
    for (const AttachmentPart::Ptr &attachment : qAsConst(parts)) {
371 372 373
        AttachmentJob *attachJob = new AttachmentJob(attachment, q);

        qCDebug(MESSAGECOMPOSER_LOG) << "got a late attachment";
374
        if (attachment->isSigned() && format) {
375 376
            qCDebug(MESSAGECOMPOSER_LOG) << "adding signjob for late attachment";
            SignJob *sJob = new SignJob(q);
Laurent Montel's avatar
Laurent Montel committed
377
            sJob->setContent(nullptr);
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
            sJob->setCryptoMessageFormat(format);
            sJob->setSigningKeys(signers);

            sJob->appendSubjob(attachJob);
            if (attachment->isEncrypted()) {
                qCDebug(MESSAGECOMPOSER_LOG) << "adding sign + encrypt job for late attachment";
                EncryptJob *eJob = new EncryptJob(q);
                eJob->setCryptoMessageFormat(format);
                eJob->setEncryptionKeys(keys);
                eJob->setRecipients(recipients);

                eJob->appendSubjob(sJob);

                multiJob->appendSubjob(eJob);
            } else {
                qCDebug(MESSAGECOMPOSER_LOG) << "Just signing late attachment";
                multiJob->appendSubjob(sJob);
            }
396
        } else if (attachment->isEncrypted() && format) {  // only encryption
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
            qCDebug(MESSAGECOMPOSER_LOG) << "just encrypting late attachment";
            EncryptJob *eJob = new EncryptJob(q);
            eJob->setCryptoMessageFormat(format);
            eJob->setEncryptionKeys(keys);
            eJob->setRecipients(recipients);

            eJob->appendSubjob(attachJob);
            multiJob->appendSubjob(eJob);
        } else {
            qCDebug(MESSAGECOMPOSER_LOG) << "attaching plain non-crypto attachment";
            AttachmentJob *attachJob = new AttachmentJob(attachment, q);
            multiJob->appendSubjob(attachJob);
        }
    }

Laurent Montel's avatar
Laurent Montel committed
412
    QObject::connect(multiJob, SIGNAL(finished(KJob*)), q, SLOT(attachmentsFinished(KJob*)));
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437

    q->addSubjob(multiJob);
    multiJob->start();
}

void ComposerPrivate::attachmentsFinished(KJob *job)
{
    if (job->error()) {
        return; // KCompositeJob takes care of the error.
    }
    qCDebug(MESSAGECOMPOSER_LOG) << "composing final message with late attachments";

    Q_ASSERT(dynamic_cast<ContentJobBase *>(job));
    ContentJobBase *contentJob = static_cast<ContentJobBase *>(job);

    KMime::Content *content = contentJob->content();
    KMime::Content *headers = contentJob->extraContent();

    composeFinalStep(headers, content);
}

void ComposerPrivate::composeFinalStep(KMime::Content *headers, KMime::Content *content)
{
    content->assemble();

Laurent Montel's avatar
Laurent Montel committed
438
    const QByteArray allData = headers->head() + content->encodedContent();
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
    KMime::Message::Ptr resultMessage(new KMime::Message);
    resultMessage->setContent(allData);
    resultMessage->parse(); // Not strictly necessary.
    resultMessages.append(resultMessage);
}

Composer::Composer(QObject *parent)
    : JobBase(*new ComposerPrivate(this), parent)
{
    Q_D(Composer);
    d->init();
}

Composer::~Composer()
{
}

Laurent Montel's avatar
Laurent Montel committed
456
QVector<KMime::Message::Ptr> Composer::resultMessages() const
457 458 459 460
{
    Q_D(const Composer);
    Q_ASSERT(d->finished);
    Q_ASSERT(!error());
Laurent Montel's avatar
Laurent Montel committed
461
    QVector<KMime::Message::Ptr> results = d->resultMessages;
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
    return results;
}

GlobalPart *Composer::globalPart() const
{
    Q_D(const Composer);
    return d->globalPart;
}

InfoPart *Composer::infoPart() const
{
    Q_D(const Composer);
    return d->infoPart;
}

TextPart *Composer::textPart() const
{
    Q_D(const Composer);
    return d->textPart;
}

AttachmentPart::List Composer::attachmentParts() const
{
    Q_D(const Composer);
    return d->attachmentParts;
}

void Composer::addAttachmentPart(AttachmentPart::Ptr part, bool autoresizeImage)
{
    Q_D(Composer);
    Q_ASSERT(!d->started);
    Q_ASSERT(!d->attachmentParts.contains(part));
    if (autoresizeImage) {
        MessageComposer::Utils resizeUtils;
        if (resizeUtils.resizeImage(part)) {
            MessageComposer::ImageScaling autoResizeJob;
            autoResizeJob.setName(part->name());
            autoResizeJob.setMimetype(part->mimeType());
            if (autoResizeJob.loadImageFromData(part->data())) {
                if (autoResizeJob.resizeImage()) {
                    part->setData(autoResizeJob.imageArray());
                    part->setMimeType(autoResizeJob.mimetype());
                    part->setName(autoResizeJob.generateNewName());
                    resizeUtils.changeFileName(part);
                }
            }
        }
    }
    d->attachmentParts.append(part);
}

void Composer::addAttachmentParts(const AttachmentPart::List &parts, bool autoresizeImage)
{
Laurent Montel's avatar
Laurent Montel committed
515
    for (const AttachmentPart::Ptr &part : parts) {
516 517 518 519 520 521 522 523
        addAttachmentPart(part, autoresizeImage);
    }
}

void Composer::removeAttachmentPart(AttachmentPart::Ptr part)
{
    Q_D(Composer);
    Q_ASSERT(!d->started);
Laurent Montel's avatar
Laurent Montel committed
524
    const int numberOfElements = d->attachmentParts.removeAll(part);
Laurent Montel's avatar
Laurent Montel committed
525
    if (numberOfElements <= 0) {
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
        qCCritical(MESSAGECOMPOSER_LOG) << "Unknown attachment part" << part.data();
        Q_ASSERT(false);
        return;
    }
}

void Composer::setSignAndEncrypt(const bool doSign, const bool doEncrypt)
{
    Q_D(Composer);
    d->sign = doSign;
    d->encrypt = doEncrypt;
}

void Composer::setMessageCryptoFormat(Kleo::CryptoMessageFormat format)
{
    Q_D(Composer);

    d->format = format;
}

void Composer::setSigningKeys(std::vector<GpgME::Key> &signers)
{
    Q_D(Composer);

    d->signers = signers;
}

Laurent Montel's avatar
Laurent Montel committed
553
void Composer::setEncryptionKeys(const QVector<QPair<QStringList, std::vector<GpgME::Key> > > &encData)
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
{
    Q_D(Composer);

    d->encData = encData;
}

void Composer::setNoCrypto(bool noCrypto)
{
    Q_D(Composer);

    d->noCrypto = noCrypto;
}

bool Composer::finished() const
{
    Q_D(const Composer);

Laurent Montel's avatar
Laurent Montel committed
571
    return d->finished;
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
}

bool Composer::autoSave() const
{
    Q_D(const Composer);

    return d->autoSaving;
}

void Composer::setAutoSave(bool isAutoSave)
{
    Q_D(Composer);

    d->autoSaving = isAutoSave;
}

void Composer::start()
{
    Q_D(Composer);
    d->doStart();
}

void Composer::slotResult(KJob *job)
{
    Q_D(Composer);
    JobBase::slotResult(job);

    if (!hasSubjobs()) {
        d->finished = true;
        emitResult();
    }
}

#include "moc_composer.cpp"