viewer_p.cpp 112 KB
Newer Older
1
2
3
4
5
/*
  Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
  Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  Copyright (c) 2009 Andras Mantia <andras@kdab.net>
  Copyright (c) 2010 Torgny Nyblom <nyblom@kde.org>
Laurent Montel's avatar
Laurent Montel committed
6
  Copyright (C) 2011-2020 Laurent Montel <montel@kde.org>
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

  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 Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "viewer_p.h"
#include "viewer.h"
24

25
#include "messageviewer_debug.h"
Sandro Knauß's avatar
Sandro Knauß committed
26
#include "utils/mimetype.h"
27
#include "viewer/renderer/messageviewerrenderer.h"
28
29
30
#include "viewer/objecttreeemptysource.h"
#include "viewer/objecttreeviewersource.h"
#include "messagedisplayformatattribute.h"
31
#include "utils/iconnamecache.h"
32
33
34
#include "scamdetection/scamdetectionwarningwidget.h"
#include "scamdetection/scamattribute.h"
#include "viewer/mimeparttree/mimeparttreeview.h"
35

36
37
#include "messageviewer/headerstyle.h"
#include "messageviewer/headerstrategy.h"
Laurent Montel's avatar
Laurent Montel committed
38
#include "widgets/developertooldialog.h"
Laurent Montel's avatar
Laurent Montel committed
39
#include <KPIMTextEdit/SlideContainer>
Laurent Montel's avatar
Laurent Montel committed
40

41
#include "job/modifymessagedisplayformatjob.h"
Laurent Montel's avatar
Laurent Montel committed
42

43
#include "viewerplugins/viewerplugintoolmanager.h"
Laurent Montel's avatar
Laurent Montel committed
44
#include <KContacts/VCardConverter>
45
#include "htmlwriter/webengineembedpart.h"
46
47
48
49
50
51
52
53
#include <unistd.h> // link()
//KDE includes
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QAction>
#include <KActionCollection>
#include <KActionMenu>
#include <KCharsets>
Laurent Montel's avatar
Laurent Montel committed
54
#include <QPrintPreviewDialog>
Laurent Montel's avatar
Laurent Montel committed
55

56
57
58
59
#include <QMenu>
#include <KMessageBox>
#include <KMimeTypeChooser>
#include <KMimeTypeTrader>
Laurent Montel's avatar
Laurent Montel committed
60
61
62
#include <kio_version.h>
#include <KIO/JobUiDelegate>
#include <KIO/OpenUrlJob>
Laurent Montel's avatar
Laurent Montel committed
63
#include <KIO/ApplicationLauncherJob>
64
#include <KSelectAction>
65
#include <KSharedConfig>
66
67
68
69
#include <KStandardGuiItem>
#include <QTemporaryDir>
#include <KToggleAction>
#include <QIcon>
70
#include <KFileItemActions>
71
72
73
74
75
#include <KLocalizedString>
#include <QMimeData>
#include <KEmailAddress>
#include <AkonadiCore/ItemModifyJob>
#include <AkonadiCore/ItemCreateJob>
76
#include <messageflags.h>
77
#include <Akonadi/KMime/SpecialMailCollections>
78
#include <MailTransportAkonadi/ErrorAttribute>
79
80
81
82
83
84
85
86
87

//Qt includes
#include <QClipboard>
#include <QItemSelectionModel>
#include <QSplitter>
#include <QTreeView>
#include <QPrinter>
#include <QPrintDialog>
#include <QMimeDatabase>
Laurent Montel's avatar
Laurent Montel committed
88
#include <QWheelEvent>
Laurent Montel's avatar
Laurent Montel committed
89
#include <QPointer>
Laurent Montel's avatar
Laurent Montel committed
90
#include <WebEngineViewer/WebEngineExportHtmlPageJob>
91
//libkdepim
92
#include <PimCommon/BroadcastStatus>
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <MessageCore/AttachmentPropertiesDialog>

#include <AkonadiCore/collection.h>
#include <AkonadiCore/itemfetchjob.h>
#include <AkonadiCore/itemfetchscope.h>
#include <Akonadi/KMime/MessageStatus>
#include <AkonadiCore/attributefactory.h>
#include <Akonadi/KMime/MessageParts>

//own includes
#include "widgets/attachmentdialog.h"
#include "csshelper.h"
#include "settings/messageviewersettings.h"
#include "widgets/htmlstatusbar.h"
107
#include "viewer/attachmentstrategy.h"
108
109
110
#include "viewer/mimeparttree/mimetreemodel.h"
#include "viewer/urlhandlermanager.h"
#include "messageviewer/messageviewerutil.h"
111
#include "utils/messageviewerutil_p.h"
112
#include "widgets/vcardviewer.h"
113
#include "widgets/shownextmessagewidget.h"
114

Laurent Montel's avatar
Laurent Montel committed
115
#include <WebEngineViewer/FindBarWebEngineView>
116
#include "viewer/webengine/mailwebengineview.h"
Laurent Montel's avatar
Laurent Montel committed
117
118
#include "htmlwriter/webengineparthtmlwriter.h"
#include <widgets/mailsourcewebengineviewer.h>
Laurent Montel's avatar
Laurent Montel committed
119
#include <WebEngineViewer/WebHitTestResult>
120
#include "header/headerstylemenumanager.h"
121
#include "widgets/submittedformwarningwidget.h"
Laurent Montel's avatar
Laurent Montel committed
122
#include <WebEngineViewer/LocalDataBaseManager>
123
#include <WebEngineViewer/WebEngineExportPdfPageJob>
124

125
#include <MimeTreeParser/BodyPart>
126
#include "interfaces/htmlwriter.h"
127
128
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/ObjectTreeParser>
129
130
131
132

#include <MessageCore/StringUtil>

#include <MessageCore/NodeHelper>
133
134
#include <MessageCore/MessageCoreSettings>

135
136
137
138
139
140
141
142
143
#include <AkonadiCore/agentinstance.h>
#include <AkonadiCore/agentmanager.h>
#include <AkonadiCore/CollectionFetchJob>
#include <AkonadiCore/collectionfetchscope.h>

#include <boost/bind.hpp>
#include <KJobWidgets/KJobWidgets>
#include <QApplication>
#include <QStandardPaths>
144
#include <QWebEngineSettings>
145
146
#include <header/headerstyleplugin.h>
#include <viewerplugins/viewerplugininterface.h>
Laurent Montel's avatar
Laurent Montel committed
147
#include <WebEngineViewer/ZoomActionMenu>
Laurent Montel's avatar
Laurent Montel committed
148
#include <KPIMTextEdit/TextToSpeechWidget>
149
#include "widgets/mailtrackingwarningwidget.h"
150

151
#include <GrantleeTheme/GrantleeThemeManager>
Laurent Montel's avatar
Laurent Montel committed
152
#include <GrantleeTheme/GrantleeTheme>
153

154
#include "dkim-verify/dkimwidgetinfo.h"
155
#include "dkim-verify/dkimmanager.h"
Laurent Montel's avatar
Laurent Montel committed
156
#include "dkim-verify/dkimresultattribute.h"
Laurent Montel's avatar
Laurent Montel committed
157
#include "dkim-verify/dkimviewermenu.h"
Laurent Montel's avatar
Laurent Montel committed
158
#include "dkim-verify/dkimmanagerulesdialog.h"
159

160
161
162
163
164
165
166
using namespace boost;
using namespace MailTransport;
using namespace MessageViewer;
using namespace MessageCore;

static QAtomicInt _k_attributeInitialized;

167
168
169
170
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
    R *receiver;
    void (C::*memberFun)(Arg);
Laurent Montel's avatar
Laurent Montel committed
171
172
    void operator()(Arg result)
    {
173
174
175
176
177
178
179
180
181
182
183
        (receiver->*memberFun)(result);
    }
};

template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
{
    InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
    return wrapper;
}

Laurent Montel's avatar
Laurent Montel committed
184
ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection)
Laurent Montel's avatar
Laurent Montel committed
185
186
187
188
189
190
    : QObject(aParent)
    , mNodeHelper(new MimeTreeParser::NodeHelper)
    , mOldGlobalOverrideEncoding(QStringLiteral("---"))
    , mMainWindow(mainWindow)
    , mActionCollection(actionCollection)
    , q(aParent)
Laurent Montel's avatar
Laurent Montel committed
191
192
    , mSession(new Akonadi::Session("MessageViewer-"
                                    + QByteArray::number(reinterpret_cast<quintptr>(this)), this))
Laurent Montel's avatar
Laurent Montel committed
193
    , mPreviouslyViewedItemId(-1)
194
{
Laurent Montel's avatar
Laurent Montel committed
195

196
197
198
    if (!mainWindow) {
        mMainWindow = aParent;
    }
199
200
    mMessageViewerRenderer = new MessageViewerRenderer;

201
    mDkimWidgetInfo = new MessageViewer::DKIMWidgetInfo(mMainWindow);
202
    if (_k_attributeInitialized.testAndSetAcquire(0, 1)) {
Laurent Montel's avatar
Laurent Montel committed
203
        MailWebEngineView::initializeCustomScheme();
204
205
206
        Akonadi::AttributeFactory::registerAttribute<MessageViewer::MessageDisplayFormatAttribute>();
        Akonadi::AttributeFactory::registerAttribute<MessageViewer::ScamAttribute>();
    }
207
208
    mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this);
    mPhishingDatabase->initialize();
Laurent Montel's avatar
Laurent Montel committed
209
    connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished,
210
            this, &ViewerPrivate::slotCheckedUrlFinished);
Laurent Montel's avatar
Laurent Montel committed
211

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    mShareServiceManager = new PimCommon::ShareServiceUrlManager(this);

    mDisplayFormatMessageOverwrite = MessageViewer::Viewer::UseGlobalSetting;

    mUpdateReaderWinTimer.setObjectName(QStringLiteral("mUpdateReaderWinTimer"));
    mResizeTimer.setObjectName(QStringLiteral("mResizeTimer"));

    createWidgets();
    createActions();
    initHtmlWidget();
    readConfig();

    mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1;

    mResizeTimer.setSingleShot(true);
    connect(&mResizeTimer, &QTimer::timeout,
            this, &ViewerPrivate::slotDelayedResize);

    mUpdateReaderWinTimer.setSingleShot(true);
    connect(&mUpdateReaderWinTimer, &QTimer::timeout,
            this, &ViewerPrivate::updateReaderWin);

234
235
236
    connect(mNodeHelper, &MimeTreeParser::NodeHelper::update,
            this, &ViewerPrivate::update);

237
238
239
240
241
    connect(mColorBar, &HtmlStatusBar::clicked,
            this, &ViewerPrivate::slotToggleHtmlMode);

    // FIXME: Don't use the full payload here when attachment loading on demand is used, just
    //        like in KMMainWidget::slotMessageActivated().
242
    mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor"));
243
    mMonitor.setSession(mSession);
244
245
246
247
248
    Akonadi::ItemFetchScope fs;
    fs.fetchFullPayload();
    fs.fetchAttribute<MailTransport::ErrorAttribute>();
    fs.fetchAttribute<MessageViewer::MessageDisplayFormatAttribute>();
    fs.fetchAttribute<MessageViewer::ScamAttribute>();
Laurent Montel's avatar
Laurent Montel committed
249
    fs.fetchAttribute<MessageViewer::DKIMResultAttribute>();
250
251
252
253
254
255
256
257
258
259
260
    mMonitor.setItemFetchScope(fs);
    connect(&mMonitor, &Akonadi::Monitor::itemChanged,
            this, &ViewerPrivate::slotItemChanged);
    connect(&mMonitor, &Akonadi::Monitor::itemRemoved,
            this, &ViewerPrivate::slotClear);
    connect(&mMonitor, &Akonadi::Monitor::itemMoved,
            this, &ViewerPrivate::slotItemMoved);
}

ViewerPrivate::~ViewerPrivate()
{
261
    delete mDeveloperToolDialog;
Laurent Montel's avatar
Laurent Montel committed
262
    delete mMessageViewerRenderer;
263
    MessageViewer::MessageViewerSettings::self()->save();
Laurent Montel's avatar
Laurent Montel committed
264
265
266
267
    delete mHtmlWriter;
    mHtmlWriter = nullptr;
    delete mViewer;
    mViewer = nullptr;
268
    mNodeHelper->forceCleanTempFiles();
269
    qDeleteAll(mListMailSourceViewer);
Laurent Montel's avatar
Laurent Montel committed
270
    mMessage.clear();
271
272
273
274
275
276
277
278
279
    delete mNodeHelper;
}

//-----------------------------------------------------------------------------
KMime::Content *ViewerPrivate::nodeFromUrl(const QUrl &url) const
{
    return mNodeHelper->fromHREF(mMessage, url);
}

280
void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url)
281
282
283
284
285
286
287
288
289
290
291
{
    if (!node) {
        return;
    }

    if (node->contentType(false)) {
        if (node->contentType()->mimeType() == "text/x-moz-deleted") {
            return;
        }
        if (node->contentType()->mimeType() == "message/external-body") {
            if (node->contentType()->hasParameter(QStringLiteral("url"))) {
Laurent Montel's avatar
Laurent Montel committed
292
293
294
295
                KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url, QStringLiteral("text/html"));
                job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, q));
                job->setRunExecutables(true);
                job->start();
296
297
298
299
300
301
302
303
304
                return;
            }
        }
    }

    const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
    if (isEncapsulatedMessage) {
        // the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation
        // in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree.
Laurent Montel's avatar
Minor    
Laurent Montel committed
305
        KMime::Message::Ptr m(new KMime::Message);
306
307
        m->setContent(node->parent()->bodyAsMessage()->encodedContent());
        m->parse();
Laurent Montel's avatar
Laurent Montel committed
308
        attachmentViewMessage(m);
309
310
311
312
313
        return;
    }
    // determine the MIME type of the attachment
    // prefer the value of the Content-Type header
    QMimeDatabase mimeDb;
Laurent Montel's avatar
Laurent Montel committed
314
315
    auto mimetype
        = mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower()));
316
317
318
319
320
321
    if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) {
        showVCard(node);
        return;
    }

    // special case treatment on mac and windows
322
323
324
    QUrl atmUrl = url;
    if (url.isEmpty()) {
        atmUrl = mNodeHelper->tempFileUrlFromNode(node);
325
    }
326
    if (Util::handleUrlWithQDesktopServices(atmUrl)) {
327
328
329
330
        return;
    }

    if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
Laurent Montel's avatar
Laurent Montel committed
331
332
        mimetype = MimeTreeParser::Util::mimetype(
            url.isLocalFile() ? url.toLocalFile() : url.fileName());
333
    }
Laurent Montel's avatar
Laurent Montel committed
334
335
    KService::Ptr offer
        = KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application"));
336

337
    const QString filenameText = MimeTreeParser::NodeHelper::fileName(node);
338

Laurent Montel's avatar
Laurent Montel committed
339
    QPointer<AttachmentDialog> dialog = new AttachmentDialog(mMainWindow, filenameText,
340
                                                             offer,
Laurent Montel's avatar
Laurent Montel committed
341
342
                                                             QLatin1String(
                                                                 "askSave_") + mimetype.name());
Laurent Montel's avatar
Laurent Montel committed
343
344
    const int choice = dialog->exec();
    delete dialog;
345
    if (choice == AttachmentDialog::Save) {
346
        QList<QUrl> urlList;
Laurent Montel's avatar
Laurent Montel committed
347
        if (Util::saveContents(mMainWindow, KMime::Content::List() << node, urlList)) {
348
            showSavedFileFolderWidget(urlList, MessageViewer::OpenSavedFileFolderWidget::FileType::Attachment);
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
        }
    } else if (choice == AttachmentDialog::Open) { // Open
        if (offer) {
            attachmentOpenWith(node, offer);
        } else {
            attachmentOpen(node);
        }
    } else if (choice == AttachmentDialog::OpenWith) {
        attachmentOpenWith(node);
    } else { // Cancel
        qCDebug(MESSAGEVIEWER_LOG) << "Canceled opening attachment";
    }
}

bool ViewerPrivate::deleteAttachment(KMime::Content *node, bool showWarning)
{
    if (!node) {
        return true;
    }
    KMime::Content *parent = node->parent();
    if (!parent) {
        return true;
    }

Laurent Montel's avatar
Laurent Montel committed
373
    const QVector<KMime::Content *> extraNodes = mNodeHelper->extraContents(mMessage.data());
374
375
    if (extraNodes.contains(node->topLevel())) {
        KMessageBox::error(mMainWindow,
Laurent Montel's avatar
Laurent Montel committed
376
377
                           i18n(
                               "Deleting an attachment from an encrypted or old-style mailman message is not supported."),
378
379
380
381
382
                           i18n("Delete Attachment"));
        return true; //cancelled
    }

    if (showWarning && KMessageBox::warningContinueCancel(mMainWindow,
Laurent Montel's avatar
Laurent Montel committed
383
384
385
386
387
388
389
390
                                                          i18n(
                                                              "Deleting an attachment might invalidate any digital signature on this message."),
                                                          i18n("Delete Attachment"),
                                                          KStandardGuiItem::del(),
                                                          KStandardGuiItem::cancel(),
                                                          QStringLiteral(
                                                              "DeleteAttachmentSignatureWarning"))
        != KMessageBox::Continue) {
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
        return false; //cancelled
    }
    //don't confuse the model
#ifndef QT_NO_TREEVIEW
    mMimePartTree->clearModel();
#endif
    QString filename;
    QString name;
    QByteArray mimetype;
    if (node->contentDisposition(false)) {
        filename = node->contentDisposition()->filename();
    }

    if (node->contentType(false)) {
        name = node->contentType()->name();
        mimetype = node->contentType()->mimeType();
    }

    // text/plain part:
    KMime::Content *deletePart = new KMime::Content(parent);
    deletePart->contentType()->setMimeType("text/x-moz-deleted");
    deletePart->contentType()->setName(QStringLiteral("Deleted: %1").arg(name), "utf8");
    deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
    deletePart->contentDisposition()->setFilename(QStringLiteral("Deleted: %1").arg(name));

    deletePart->contentType()->setCharset("utf-8");
    deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
Laurent Montel's avatar
Laurent Montel committed
418
419
    QByteArray bodyMessage = QByteArrayLiteral(
        "\nYou deleted an attachment from this message. The original MIME headers for the attachment were:");
420
421
422
423
424
425
426
427
428
429
430
431
432
    bodyMessage += ("\nContent-Type: ") + mimetype;
    bodyMessage += ("\nname=\"") + name.toUtf8() + "\"";
    bodyMessage += ("\nfilename=\"") + filename.toUtf8() + "\"";
    deletePart->setBody(bodyMessage);
    parent->replaceContent(node, deletePart);

    parent->assemble();

    KMime::Message *modifiedMessage = mNodeHelper->messageWithExtraContent(mMessage.data());
#ifndef QT_NO_TREEVIEW
    mMimePartTree->mimePartModel()->setRoot(modifiedMessage);
#endif
    mMessageItem.setPayloadFromData(modifiedMessage->encodedContent());
433
    Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession);
434
435
436
437
438
439
440
441
442
443
    job->disableRevisionCheck();
    connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult);
    return true;
}

void ViewerPrivate::itemModifiedResult(KJob *job)
{
    if (job->error()) {
        qCDebug(MESSAGEVIEWER_LOG) << "Item update failed:" << job->errorString();
    } else {
444
        setMessageItem(mMessageItem, MimeTreeParser::Force);
445
446
447
    }
}

Laurent Montel's avatar
Laurent Montel committed
448
void ViewerPrivate::scrollToAnchor(const QString &anchor)
449
{
Laurent Montel's avatar
Laurent Montel committed
450
    mViewer->scrollToAnchor(anchor);
451
452
}

Laurent Montel's avatar
Laurent Montel committed
453
void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent)
454
{
Laurent Montel's avatar
Laurent Montel committed
455
456
    const KService::List offers = KFileItemActions::associatedApplications(
        QStringList() << contentTypeStr, QString());
457
458
459
460
461
    if (!offers.isEmpty()) {
        QMenu *menu = topMenu;
        QActionGroup *actionGroup = new QActionGroup(menu);

        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
462
463
            connect(actionGroup, &QActionGroup::triggered, this,
                    &ViewerPrivate::slotOpenWithActionCurrentContent);
464
        } else {
Laurent Montel's avatar
Laurent Montel committed
465
466
            connect(actionGroup, &QActionGroup::triggered, this,
                    &ViewerPrivate::slotOpenWithAction);
467
468
469
470
471
472
473
        }

        if (offers.count() > 1) { // submenu 'open with'
            menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
            menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest
            topMenu->addMenu(menu);
        }
Laurent Montel's avatar
Laurent Montel committed
474
        //qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu;
475

Laurent Montel's avatar
Laurent Montel committed
476
477
        for (const KService::Ptr &ser : offers) {
            QAction *act = MessageViewer::Util::createAppAction(ser,
Laurent Montel's avatar
Laurent Montel committed
478
479
                                                                // no submenu -> prefix single offer
                                                                menu == topMenu, actionGroup, menu);
480
481
482
483
484
485
486
487
488
489
490
491
492
            menu->addAction(act);
        }

        QString openWithActionName;
        if (menu != topMenu) { // submenu
            menu->addSeparator();
            openWithActionName = i18nc("@action:inmenu Open With", "&Other...");
        } else {
            openWithActionName = i18nc("@title:menu", "&Open With...");
        }
        QAction *openWithAct = new QAction(menu);
        openWithAct->setText(openWithActionName);
        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
493
494
            connect(openWithAct, &QAction::triggered, this,
                    &ViewerPrivate::slotOpenWithDialogCurrentContent);
495
496
497
498
499
500
501
502
503
        } else {
            connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog);
        }

        menu->addAction(openWithAct);
    } else { // no app offers -> Open With...
        QAction *act = new QAction(topMenu);
        act->setText(i18nc("@title:menu", "&Open With..."));
        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
504
505
            connect(act, &QAction::triggered, this,
                    &ViewerPrivate::slotOpenWithDialogCurrentContent);
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
        } else {
            connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog);
        }
        topMenu->addAction(act);
    }
}

void ViewerPrivate::slotOpenWithDialogCurrentContent()
{
    if (!mCurrentContent) {
        return;
    }
    attachmentOpenWith(mCurrentContent);
}

void ViewerPrivate::slotOpenWithDialog()
{
Laurent Montel's avatar
Laurent Montel committed
523
    const auto contents = selectedContents();
524
525
526
527
528
529
530
531
532
533
    if (contents.count() == 1) {
        attachmentOpenWith(contents.first());
    }
}

void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act)
{
    if (!mCurrentContent) {
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
534
    const KService::Ptr app = act->data().value<KService::Ptr>();
535
536
537
538
539
    attachmentOpenWith(mCurrentContent, app);
}

void ViewerPrivate::slotOpenWithAction(QAction *act)
{
Laurent Montel's avatar
Laurent Montel committed
540
541
    const KService::Ptr app = act->data().value<KService::Ptr>();
    const auto contents = selectedContents();
542
543
544
545
546
    if (contents.count() == 1) {
        attachmentOpenWith(contents.first(), app);
    }
}

Laurent Montel's avatar
Laurent Montel committed
547
void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos)
548
{
Laurent Montel's avatar
Laurent Montel committed
549
    Q_UNUSED(name)
Laurent Montel's avatar
Laurent Montel committed
550
    prepareHandleAttachment(node);
551
552
553
554
    bool deletedAttachment = false;
    if (node->contentType(false)) {
        deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted");
    }
555
556
557
558
559
560
    //Not necessary to show popup menu when attachment was removed
    if (deletedAttachment) {
        return;
    }

    QMenu menu;
561
562
    const QString contentTypeStr = QLatin1String(node->contentType()->mimeType());

Laurent Montel's avatar
Laurent Montel committed
563
    QAction *action
Laurent Montel's avatar
Laurent Montel committed
564
        = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open",
Laurent Montel's avatar
Laurent Montel committed
565
                                                                                  "Open"));
566
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
567
568
569
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Open);
    });
570
    if (!deletedAttachment) {
Laurent Montel's avatar
Laurent Montel committed
571
        createOpenWithMenu(&menu, contentTypeStr, true);
572
573
574
575
576
577
    }

    QMimeDatabase mimeDb;
    auto mimetype = mimeDb.mimeTypeForName(contentTypeStr);
    if (mimetype.isValid()) {
        const QStringList parentMimeType = mimetype.parentMimeTypes();
Laurent Montel's avatar
Laurent Montel committed
578
579
580
        if ((contentTypeStr == QLatin1String("text/plain"))
            || (contentTypeStr == QLatin1String("image/png"))
            || (contentTypeStr == QLatin1String("image/jpeg"))
Laurent Montel's avatar
Laurent Montel committed
581
582
583
            || parentMimeType.contains(QLatin1String("text/plain"))
            || parentMimeType.contains(QLatin1String("image/png"))
            || parentMimeType.contains(QLatin1String("image/jpeg"))
Laurent Montel's avatar
Laurent Montel committed
584
            ) {
Laurent Montel's avatar
Laurent Montel committed
585
            action = menu.addAction(i18nc("to view something", "View"));
586
            action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
587
588
589
            connect(action, &QAction::triggered, this, [this]() {
                slotHandleAttachment(Viewer::View);
            });
590
591
592
        }
    }

Laurent Montel's avatar
Laurent Montel committed
593
594
595
596
    action = menu.addAction(i18n("Scroll To"));
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ScrollTo);
    });
597

Laurent Montel's avatar
Laurent Montel committed
598
    action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n(
Laurent Montel's avatar
Laurent Montel committed
599
                                "Save As..."));
600
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
601
602
603
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Save);
    });
604

Laurent Montel's avatar
Laurent Montel committed
605
    action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"));
606
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
607
608
609
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Copy);
    });
610
611

    const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
Laurent Montel's avatar
Laurent Montel committed
612
613
614
615
    const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid()
                           && (mMessageItem.parentCollection().rights()
                               != Akonadi::Collection::ReadOnly)
                           && !isEncapsulatedMessage;
616

Laurent Montel's avatar
Laurent Montel committed
617
    action
Laurent Montel's avatar
Laurent Montel committed
618
        = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
Laurent Montel's avatar
Laurent Montel committed
619
                         i18n("Delete Attachment"));
Laurent Montel's avatar
Laurent Montel committed
620
621
622
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Delete);
    });
Laurent Montel's avatar
Laurent Montel committed
623

624
    action->setEnabled(canChange && !deletedAttachment);
625
626
627
#if 0
    menu->addSeparator();

Laurent Montel's avatar
Laurent Montel committed
628
629
630
    action
        = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")),
                          i18n("Reply To Author"));
Laurent Montel's avatar
Laurent Montel committed
631
632
633
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ReplyMessageToAuthor);
    });
Laurent Montel's avatar
Laurent Montel committed
634
635
636

    menu->addSeparator();

Laurent Montel's avatar
Laurent Montel committed
637
638
    action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n(
                                 "Reply To All"));
Laurent Montel's avatar
Laurent Montel committed
639
640
641
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ReplyMessageToAll);
    });
642
#endif
Laurent Montel's avatar
Laurent Montel committed
643
644
    menu.addSeparator();
    action = menu.addAction(i18n("Properties"));
Laurent Montel's avatar
Laurent Montel committed
645
646
647
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Properties);
    });
Laurent Montel's avatar
Laurent Montel committed
648
    menu.exec(globalPos);
649
650
}

Laurent Montel's avatar
Laurent Montel committed
651
void ViewerPrivate::prepareHandleAttachment(KMime::Content *node)
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
{
    mCurrentContent = node;
}

KService::Ptr ViewerPrivate::getServiceOffer(KMime::Content *content)
{
    const QString fileName = mNodeHelper->writeNodeToTempFile(content);

    const QString contentTypeStr = QLatin1String(content->contentType()->mimeType());

    // determine the MIME type of the attachment
    // prefer the value of the Content-Type header
    QMimeDatabase mimeDb;
    auto mimetype = mimeDb.mimeTypeForName(contentTypeStr);

    if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) {
        attachmentView(content);
Laurent Montel's avatar
Laurent Montel committed
669
        return KService::Ptr(nullptr);
670
671
672
673
    }

    if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
        /*TODO(Andris) port when on-demand loading is done   && msgPart.isComplete() */
674
        mimetype = MimeTreeParser::Util::mimetype(fileName);
675
    }
Laurent Montel's avatar
Laurent Montel committed
676
677
    return KMimeTypeTrader::self()->preferredService(mimetype.name(),
                                                     QStringLiteral("Application"));
678
679
}

Laurent Montel's avatar
Laurent Montel committed
680
KMime::Content::List ViewerPrivate::selectedContents() const
681
682
683
684
685
686
687
688
{
    return mMimePartTree->selectedContents();
}

void ViewerPrivate::attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer)
{
    QString name = mNodeHelper->writeNodeToTempFile(node);

689
    // Make sure that it will not deleted when we switch from message.
Laurent Montel's avatar
Laurent Montel committed
690
691
    QTemporaryDir *tmpDir
        = new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX"));
692
693
694
695
696
697
698
699
700
    if (tmpDir->isValid()) {
        tmpDir->setAutoRemove(false);
        const QString path = tmpDir->path();
        delete tmpDir;
        QFile f(name);
        const QUrl tmpFileName = QUrl::fromLocalFile(name);
        const QString newPath = path + QLatin1Char('/') + tmpFileName.fileName();

        if (!f.copy(newPath)) {
Laurent Montel's avatar
Laurent Montel committed
701
            qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name
Laurent Montel's avatar
Laurent Montel committed
702
                                       << " to " << path;
703
704
705
706
        } else {
            name = newPath;
        }
        f.close();
707
    } else {
708
        delete tmpDir;
709
710
    }

711
712
713
714
    QList<QUrl> lst;
    const QFileDevice::Permissions perms = QFile::permissions(name);
    QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser);
    const QUrl url = QUrl::fromLocalFile(name);
715
    lst.append(url);
Laurent Montel's avatar
Laurent Montel committed
716
717
718
719
720
721
722

    KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(offer);
    job->setUrls({url});
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mMainWindow));
    job->start();
    connect(job, &KJob::result, this, [url, job]() {
        if (job->error()) {
723
724
            QFile::remove(url.toLocalFile());
        }
Laurent Montel's avatar
Laurent Montel committed
725
    });
726
727
728
729
}

void ViewerPrivate::attachmentOpen(KMime::Content *node)
{
Laurent Montel's avatar
Laurent Montel committed
730
    const KService::Ptr offer = getServiceOffer(node);
731
732
733
734
735
736
737
    if (!offer) {
        qCDebug(MESSAGEVIEWER_LOG) << "got no offer";
        return;
    }
    attachmentOpenWith(node, offer);
}

738
739
bool ViewerPrivate::showEmoticons() const
{
740
    return mForceEmoticons;
741
742
}

743
HtmlWriter *ViewerPrivate::htmlWriter() const
744
745
746
747
748
749
{
    return mHtmlWriter;
}

CSSHelper *ViewerPrivate::cssHelper() const
{
750
    return mMessageViewerRenderer->cssHelper();
751
752
}

753
MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
{
    return mNodeHelper;
}

Viewer *ViewerPrivate::viewer() const
{
    return q;
}

Akonadi::Item ViewerPrivate::messageItem() const
{
    return mMessageItem;
}

KMime::Message::Ptr ViewerPrivate::message() const
{
    return mMessage;
}

bool ViewerPrivate::decryptMessage() const
{
Laurent Montel's avatar
Laurent Montel committed
775
    if (MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) {
776
        return true;
Laurent Montel's avatar
Laurent Montel committed
777
778
    } else {
        return mDecrytMessageOverwrite;
779
780
781
782
783
784
785
786
787
788
789
790
791
    }
}

void ViewerPrivate::displaySplashPage(const QString &message)
{
    displaySplashPage(QStringLiteral("status.html"), {
        { QStringLiteral("icon"), QStringLiteral("kmail") },
        { QStringLiteral("name"), i18n("KMail") },
        { QStringLiteral("subtitle"), i18n("The KDE Mail Client") },
        { QStringLiteral("message"), message }
    });
}

Laurent Montel's avatar
Laurent Montel committed
792
void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain)
793
{
Laurent Montel's avatar
Laurent Montel committed
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
    if (mViewer) {
        mMsgDisplay = false;
        adjustLayout();

        GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"),
                                            QStringLiteral("splash.theme"),
                                            nullptr,
                                            QStringLiteral("messageviewer/about/"));
        GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
        if (theme.isValid()) {
            mViewer->setHtml(theme.render(templateName, data, domain),
                             QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
        } else {
            qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme";
        }
        mViewer->show();
810
811
812
813
814
    }
}

void ViewerPrivate::enableMessageDisplay()
{
815
816
817
    if (mMsgDisplay) {
        return;
    }
818
819
820
821
822
823
824
825
826
827
828
    mMsgDisplay = true;
    adjustLayout();
}

void ViewerPrivate::displayMessage()
{
    showHideMimeTree();

    mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec());

    if (mMessageItem.hasAttribute<MessageViewer::MessageDisplayFormatAttribute>()) {
Laurent Montel's avatar
Laurent Montel committed
829
830
        const MessageViewer::MessageDisplayFormatAttribute *const attr
            = mMessageItem.attribute<MessageViewer::MessageDisplayFormatAttribute>();
831
832
833
834
        setHtmlLoadExtOverride(attr->remoteContent());
        setDisplayFormatMessageOverwrite(attr->messageFormat());
    }

Volker Krause's avatar
Volker Krause committed
835
    htmlWriter()->begin();
Laurent Montel's avatar
Laurent Montel committed
836
    htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont));
837
838
839
840
841
842
843

    if (!mMainWindow) {
        q->setWindowTitle(mMessage->subject()->asUnicodeString());
    }

    // Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker.
    // It is updated right after parseMsg() instead.
844
    mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate);
845
846
847
848
849

    if (mMessageItem.hasAttribute<ErrorAttribute>()) {
        //TODO: Insert link to clear error so that message might be resent
        const ErrorAttribute *const attr = mMessageItem.attribute<ErrorAttribute>();
        Q_ASSERT(attr);
Laurent Montel's avatar
Laurent Montel committed
850
        initializeColorFromScheme();
851

Volker Krause's avatar
Volker Krause committed
852
        htmlWriter()->write(QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
853
854
855
                                "<div style=\"background:%1;color:%2;border:1px solid %2\">%3</div>").arg(
                                mBackgroundError.name(),
                                mForegroundError.name(), attr->message().toHtmlEscaped()));
Volker Krause's avatar
Volker Krause committed
856
        htmlWriter()->write(QStringLiteral("<p></p>"));
857
858
859
860
861
862
863
864
    }

    parseContent(mMessage.data());
#ifndef QT_NO_TREEVIEW
    mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data()));
#endif
    mColorBar->update();

Laurent Montel's avatar
Laurent Montel committed
865
    htmlWriter()->write(cssHelper()->endBodyHtml());
Laurent Montel's avatar
Laurent Montel committed
866
867
868
869
870
    connect(mViewer, &MailWebEngineView::loadFinished, this,
            &ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection);
    connect(
        mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered,
        Qt::UniqueConnection);
871

Volker Krause's avatar
Volker Krause committed
872
    htmlWriter()->end();
873
874
875
876
}

void ViewerPrivate::parseContent(KMime::Content *content)
{
Laurent Montel's avatar
Laurent Montel committed
877
    assert(content != nullptr);
878
    mNodeHelper->removeTempFiles();
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903

    // Check if any part of this message is a v-card
    // v-cards can be either text/x-vcard or text/directory, so we need to check
    // both.
    KMime::Content *vCardContent = findContentByType(content, "text/x-vcard");
    if (!vCardContent) {
        vCardContent = findContentByType(content, "text/directory");
    }
    bool hasVCard = false;
    if (vCardContent) {
        // ### FIXME: We should only do this if the vCard belongs to the sender,
        // ### i.e. if the sender's email address is contained in the vCard.
        const QByteArray vCard = vCardContent->decodedContent();
        KContacts::VCardConverter t;
        if (!t.parseVCards(vCard).isEmpty()) {
            hasVCard = true;
            mNodeHelper->writeNodeToTempFile(vCardContent);
        }
    }

    KMime::Message *message = dynamic_cast<KMime::Message *>(content);

    // Pass control to the OTP now, which does the real work
    mNodeHelper->setNodeUnprocessed(mMessage.data(), true);
    MailViewerSource otpSource(this);
904
    MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper);
Laurent Montel's avatar
Laurent Montel committed
905
    //TODO: needs to end up in renderer: mMessage.data() != content /* show only single node */);
906
    otp.setAllowAsync(!mPrinting);
907
    otp.parseObjectTree(content, mMessage.data() != content /* parse/show only single node */);
908
    htmlWriter()->setCodec(otp.plainTextContentCharset());
909
910
911
912
913
914
    if (message) {
        htmlWriter()->write(writeMessageHeader(message, hasVCard ? vCardContent : nullptr, true));
    }

    otpSource.render(otp.parsedPart(), mMessage.data() != content /* parse/show only single node */);

915
916
917
918
919
    // TODO: Setting the signature state to nodehelper is not enough, it should actually
    // be added to the store, so that the message list correctly displays the signature state
    // of messages that were parsed at least once
    // store encrypted/signed status information in the KMMessage
    //  - this can only be done *after* calling parseObjectTree()
Laurent Montel's avatar
Laurent Montel committed
920
921
922
923
    MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState(
        content);
    MimeTreeParser::KMMsgSignatureState signatureState
        = mNodeHelper->overallSignatureState(content);
924
925
926
    mNodeHelper->setEncryptionState(content, encryptionState);
    // Don't reset the signature state to "not signed" (e.g. if one canceled the
    // decryption of a signed messages which has already been decrypted before).
Laurent Montel's avatar
Laurent Montel committed
927
928
    if (signatureState != MimeTreeParser::KMMsgNotSigned
        || mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) {
929
930
931
932
933
934
        mNodeHelper->setSignatureState(content, signatureState);
    }

    showHideMimeTree();
}

Laurent Montel's avatar
Laurent Montel committed
935
QString ViewerPrivate::writeMessageHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel)
936
937
{
    if (!headerStylePlugin()) {
Laurent Montel's avatar
Laurent Montel committed
938
        qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMessageHeader() without a header style set!";
939
        return {};
940
    }
Laurent Montel's avatar
Laurent Montel committed
941
    HeaderStyle *style = headerStylePlugin()->headerStyle();
942
    if (vCardNode) {
Laurent Montel's avatar
Laurent Montel committed
943
        style->setVCardName(mNodeHelper->asHREF(vCardNode, QStringLiteral("body")));
944
945
    } else {
        style->setVCardName(QString());
Laurent Montel's avatar
Laurent Montel committed
946
947
948
949
950
951
952
953
    }
    style->setHeaderStrategy(headerStylePlugin()->headerStrategy());
    style->setPrinting(mPrinting);
    style->setTopLevel(topLevel);
    style->setAllowAsync(true);
    style->setSourceObject(this);
    style->setNodeHelper(mNodeHelper);
    style->setAttachmentHtml(attachmentHtml());
954
955
956
957
    if (mMessageItem.isValid()) {
        Akonadi::MessageStatus status;
        status.setStatusFromFlags(mMessageItem.flags());

Laurent Montel's avatar
Laurent Montel committed
958
        style->setMessageStatus(status);
Laurent Montel's avatar
Laurent Montel committed
959
    } else {
Laurent Montel's avatar
Laurent Montel committed
960
        style->setReadOnlyMessage(true);
961
962
    }

Laurent Montel's avatar
Laurent Montel committed
963
    return style->format(aMsg);
964
965
966
967
968
969
970
971
972
973
974
975
976
977
}

void ViewerPrivate::showVCard(KMime::Content *msgPart)
{
    const QByteArray vCard = msgPart->decodedContent();

    VCardViewer *vcv = new VCardViewer(mMainWindow, vCard);
    vcv->setAttribute(Qt::WA_DeleteOnClose);
    vcv->show();
}

void ViewerPrivate::initHtmlWidget()
{
    if (!htmlWriter()) {
Laurent Montel's avatar
Laurent Montel committed
978
        mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr);
979
980
        mHtmlWriter = mPartHtmlWriter;
    }
Laurent Montel's avatar
Laurent Montel committed
981
    connect(mViewer->page(), &QWebEnginePage::linkHovered,
982
            this, &ViewerPrivate::slotUrlOn);
Laurent Montel's avatar
Laurent Montel committed
983
    connect(mViewer, &MailWebEngineView::openUrl,
984
985
986
            this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection);
    connect(mViewer, &MailWebEngineView::popupMenu,
            this, &ViewerPrivate::slotUrlPopup);
987
988
    connect(mViewer, &MailWebEngineView::wheelZoomChanged,
            this, &ViewerPrivate::slotWheelZoomChanged);
Laurent Montel's avatar
Laurent Montel committed
989
990
    connect(mViewer, &MailWebEngineView::messageMayBeAScam, this,
            &ViewerPrivate::slotMessageMayBeAScam);
991
992
    connect(mViewer, &MailWebEngineView::formSubmittedForbidden, mSubmittedFormWarning, &SubmittedFormWarningWidget::showWarning);
    connect(mViewer, &MailWebEngineView::mailTrackingFound, mMailTrackingWarning, &MailTrackingWarningWidget::addTracker);
Laurent Montel's avatar
Laurent Montel committed
993
994
995
996
997
998
999
1000
1001
1002
    connect(mScamDetectionWarning, &ScamDetectionWarningWidget::showDetails, mViewer,
            &MailWebEngineView::slotShowDetails);
    connect(mScamDetectionWarning, &ScamDetectionWarningWidget::moveMessageToTrash, this,
            &ViewerPrivate::moveMessageToTrash);
    connect(mScamDetectionWarning, &ScamDetectionWarningWidget::messageIsNotAScam, this,
            &ViewerPrivate::slotMessageIsNotAScam);
    connect(mScamDetectionWarning, &ScamDetectionWarningWidget::addToWhiteList, this,
            &ViewerPrivate::slotAddToWhiteList);
    connect(mViewer, &MailWebEngineView::pageIsScrolledToBottom, this,
            &ViewerPrivate::pageIsScrolledToBottom);
1003
1004
}

Laurent Montel's avatar
Laurent Montel committed
1005
void ViewerPrivate::applyZoomValue(qreal factor, bool saveConfig)
1006
{
Laurent Montel's avatar
Laurent Montel committed
1007
1008
    if (mZoomActionMenu) {
        if (factor >= 10 && factor <= 300) {
Laurent Montel's avatar
Laurent Montel committed
1009
1010
1011
1012
1013
1014
1015
            if (mZoomActionMenu->zoomFactor() != factor) {
                mZoomActionMenu->setZoomFactor(factor);
                mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0);
                if (saveConfig) {
                    MessageViewer::MessageViewerSettings::self()->setZoomFactor(factor);
                }
            }
Laurent Montel's avatar
Laurent Montel committed
1016
        }
1017
1018
1019
    }
}

1020
1021
void ViewerPrivate::setWebViewZoomFactor(qreal factor)
{
Laurent Montel's avatar
Laurent Montel committed
1022
    applyZoomValue(factor, false);
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
}

qreal ViewerPrivate::webViewZoomFactor() const
{
    qreal zoomFactor = -1;
    if (mZoomActionMenu) {
        zoomFactor = mZoomActionMenu->zoomFactor();
    }
    return zoomFactor;
}

void ViewerPrivate::slotWheelZoomChanged(int numSteps)
{
    const qreal factor = mZoomActionMenu->zoomFactor() + numSteps * 10;
    applyZoomValue(factor);
}

1040
1041
void ViewerPrivate::readConfig()
{
1042
    mMessageViewerRenderer->setCurrentWidget(mViewer);
Laurent Montel's avatar
Laurent Montel committed
1043
    recreateCssHelper();
1044

1045
1046
1047
1048
    mForceEmoticons = MessageViewer::MessageViewerSettings::self()->showEmoticons();
    if (mDisableEmoticonAction) {
        mDisableEmoticonAction->setChecked(!mForceEmoticons);
    }
1049
1050
1051
1052
    if (headerStylePlugin()) {
        headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons);
    }

1053
1054
1055
1056
1057
1058
1059
    mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont();
    if (mToggleFixFontAction) {
        mToggleFixFontAction->setChecked(mUseFixedFont);
    }

    mHtmlMailGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail();

Laurent Montel's avatar
Laurent Montel committed
1060
    MessageViewer::Util::readGravatarConfig();
1061
1062
1063
1064
    if (mHeaderStyleMenuManager) {
        mHeaderStyleMenuManager->readConfig();
    }

1065
    setAttachmentStrategy(AttachmentStrategy::create(MessageViewer::
Laurent Montel's avatar
Laurent Montel committed
1066
1067
                                                     MessageViewerSettings::self()->
                                                     attachmentStrategy()));
1068
1069
1070
1071
1072
1073
1074
1075
    KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy());
    if (raction) {
        raction->setChecked(true);
    }

    adjustLayout();

    readGlobalOverrideCodec();
1076
    mViewer->readConfig();
Laurent Montel's avatar
Laurent Montel committed
1077
1078
1079
1080
    mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize,
                                     MessageViewer::MessageViewerSettings::self()->minimumFontSize());
    mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize,
                                     MessageViewer::MessageViewerSettings::self()->minimumFontSize());
1081
1082
1083
1084
    if (mMessage) {
        update();
    }
    mColorBar->update();
Laurent Montel's avatar
Laurent Montel committed
1085
    applyZoomValue(MessageViewer::MessageViewerSettings::self()->zoomFactor(), false);
1086
1087
}

Laurent Montel's avatar
Laurent Montel committed
1088
void ViewerPrivate::recreateCssHelper()
1089
{
1090
    mMessageViewerRenderer->recreateCssHelper();
Laurent Montel's avatar
Laurent Montel committed
1091
1092
}

1093
1094
1095
1096
1097
void ViewerPrivate::hasMultiMessages(bool messages)
{
    mShowNextMessageWidget->setVisible(messages);
}

Laurent Montel's avatar
Laurent Montel committed
1098
1099
1100
void ViewerPrivate::slotGeneralFontChanged()
{
    recreateCssHelper();
1101
1102
1103
1104
1105
1106
1107
    if (mMessage) {
        update();
    }
}

void ViewerPrivate::writeConfig(bool sync)
{
1108
    MessageViewer::MessageViewerSettings::self()->setShowEmoticons(mForceEmoticons);
1109
1110
    MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mUseFixedFont);
    if (attachmentStrategy()) {
Laurent Montel's avatar
Laurent Montel committed
1111
1112
1113
        MessageViewer::MessageViewerSettings::self()->setAttachmentStrategy(QLatin1String(
                                                                                attachmentStrategy()
                                                                                ->name()));
1114
1115
1116
1117
1118
1119
1120
    }
    saveSplitterSizes();
    if (sync) {
        Q_EMIT requestConfigSync();
    }
}

1121
const AttachmentStrategy *ViewerPrivate::attachmentStrategy() const
1122
1123
1124
1125
{
    return mAttachmentStrategy;
}

1126
void ViewerPrivate::setAttachmentStrategy(const AttachmentStrategy *strategy)
1127
1128
1129
1130
{
    if (mAttachmentStrategy == strategy) {
        return;
    }
1131
    mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart();
1132
    update(MimeTreeParser::Force);
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
}

QString ViewerPrivate::overrideEncoding() const
{
    return mOverrideEncoding;
}

void ViewerPrivate::setOverrideEncoding(const QString &encoding)
{
    if (encoding == mOverrideEncoding) {
        return;
    }

    mOverrideEncoding = encoding;
    if (mSelectEncodingAction) {
        if (encoding.isEmpty()) {
            mSelectEncodingAction->setCurrentItem(0);
        } else {
            const QStringList encodings = mSelectEncodingAction->items();
            int i = 0;
Laurent Montel's avatar
Laurent Montel committed
1153
1154
1155
            for (QStringList::const_iterator it = encodings.constBegin(),
                 end = encodings.constEnd();
                 it != end; ++it, ++i) {
1156
                if (MimeTreeParser::NodeHelper::encodingForName(*it) == encoding) {
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
                    mSelectEncodingAction->setCurrentItem(i);
                    break;
                }
            }
            if (i == encodings.size()) {
                // the value of encoding is unknown => use Auto
                qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding" << encoding
                                             << ". Using Auto instead.";
                mSelectEncodingAction->setCurrentItem(0);
                mOverrideEncoding.clear();
            }
        }
    }
1170
    update(MimeTreeParser::Force);
1171
1172
1173
1174
1175
1176
1177
}

void ViewerPrivate::setPrinting(bool enable)
{
    mPrinting = enable;
}

1178
1179
1180
1181
1182
bool ViewerPrivate::printingMode() const
{
    return mPrinting;
}

1183
1184
void ViewerPrivate::printMessage(const Akonadi::Item &message)
{
Laurent Montel's avatar
Laurent Montel committed
1185
1186
1187
1188
1189
1190
    disconnect(
        mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
        &ViewerPrivate::slotPrintMessage);
    connect(
        mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
        &ViewerPrivate::slotPrintMessage);
1191
    setMessageItem(message, MimeTreeParser::Force);
1192
1193
1194
1195
}

void ViewerPrivate::printPreviewMessage(const Akonadi::Item &message)
{
Laurent Montel's avatar
Laurent Montel committed
1196
1197
1198
1199
1200
1201
    disconnect(
        mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
        &ViewerPrivate::slotPrintPreview);
    connect(
        mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
        &ViewerPrivate::slotPrintPreview);
1202
    setMessageItem(message, MimeTreeParser::Force);
1203
1204
1205
1206
}

void ViewerPrivate::resetStateForNewMessage()
{
Laurent Montel's avatar
Laurent Montel committed
1207
    mDkimWidgetInfo->clear();
1208
    mHtmlLoadExtOverride = false;
1209
1210
1211
1212
1213
    mClickedUrl.clear();
    mImageUrl.clear();
    enableMessageDisplay(); // just to make sure it's on
    mMessage.reset();
    mNodeHelper->clear();
Laurent Montel's avatar
Laurent Montel committed
1214
    mMessagePartNode = nullptr;
1215
1216
1217
#ifndef QT_NO_TREEVIEW
    mMimePartTree->clearModel();
#endif
Laurent Montel's avatar
Laurent Montel committed
1218
1219
1220
1221
1222
    if (mViewer) {
        mViewer->clearRelativePosition();
        mViewer->hideAccessKeys();
        mFindBar->closeBar();
    }
1223
1224
1225
    if (!mPrinting) {
        setShowSignatureDetails(false);
    }
1226
1227
    mViewerPluginToolManager->closeAllTools();
    mScamDetectionWarning->setVisible(false);
1228
    mOpenSavedFileFolderWidget->setVisible(false);
1229
    mSubmittedFormWarning->setVisible(false);
Laurent Montel's avatar
Laurent Montel committed
1230
    mMailTrackingWarning->hideAndClear();
1231
1232
1233
1234

    if (mPrinting) {
        if (MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettings()) {
            if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) {
Laurent Montel's avatar
Laurent Montel committed
1235
1236
                mLevelQuote
                    = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1;
1237
1238
1239
1240
1241
1242
            } else {
                mLevelQuote = -1;
            }
        } else {
            mLevelQuote = -1;
        }
1243
    } else {
1244
1245
1246
1247
1248
//        mDisplayFormatMessageOverwrite
//            = (mDisplayFormatMessageOverwrite
//               == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting
//              :
//              MessageViewer::Viewer::Unknown;
1249
1250
1251
    }
}

Laurent Montel's avatar
Laurent Montel committed
1252
void ViewerPrivate::setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode</