viewer_p.cpp 110 KB
Newer Older
1
/*
2
3
4
5
  SPDX-FileCopyrightText: 1997 Markus Wuebben <markus.wuebben@kde.org>
  SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
  SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
  SPDX-FileCopyrightText: 2010 Torgny Nyblom <nyblom@kde.org>
Laurent Montel's avatar
Laurent Montel committed
6
  SPDX-FileCopyrightText: 2011-2021 Laurent Montel <montel@kde.org>
7

8
  SPDX-License-Identifier: GPL-2.0-or-later
9
10
*/
#include "viewer_p.h"
Laurent Montel's avatar
Laurent Montel committed
11
#include "viewerpurposemenuwidget.h"
12

Laurent Montel's avatar
Laurent Montel committed
13
#include "messagedisplayformatattribute.h"
14
#include "messageviewer_debug.h"
Laurent Montel's avatar
Laurent Montel committed
15
16
17
#include "scamdetection/scamattribute.h"
#include "scamdetection/scamdetectionwarningwidget.h"
#include "utils/iconnamecache.h"
Sandro Knauß's avatar
Sandro Knauß committed
18
#include "utils/mimetype.h"
Laurent Montel's avatar
Laurent Montel committed
19
#include "viewer/mimeparttree/mimeparttreeview.h"
20
21
#include "viewer/objecttreeemptysource.h"
#include "viewer/objecttreeviewersource.h"
Laurent Montel's avatar
Laurent Montel committed
22
#include "viewer/renderer/messageviewerrenderer.h"
23

24
#include "messageviewer/headerstrategy.h"
Laurent Montel's avatar
Laurent Montel committed
25
#include "messageviewer/headerstyle.h"
Laurent Montel's avatar
Laurent Montel committed
26
#include <KPIMTextEdit/SlideContainer>
Laurent Montel's avatar
Laurent Montel committed
27

28
#include "job/modifymessagedisplayformatjob.h"
Laurent Montel's avatar
Laurent Montel committed
29

Laurent Montel's avatar
Laurent Montel committed
30
#include "htmlwriter/webengineembedpart.h"
31
#include "viewerplugins/viewerplugintoolmanager.h"
Laurent Montel's avatar
Laurent Montel committed
32
#include <KContacts/VCardConverter>
Laurent Montel's avatar
Laurent Montel committed
33
// KDE includes
34
35
36
#include <KActionCollection>
#include <KActionMenu>
#include <KCharsets>
Laurent Montel's avatar
Laurent Montel committed
37
38
#include <QAction>
#include <QHBoxLayout>
Laurent Montel's avatar
Laurent Montel committed
39
#include <QPrintPreviewDialog>
Laurent Montel's avatar
Laurent Montel committed
40
#include <QVBoxLayout>
Laurent Montel's avatar
Laurent Montel committed
41

Laurent Montel's avatar
Laurent Montel committed
42
43
44
#include <Akonadi/KMime/SpecialMailCollections>
#include <AkonadiCore/ItemCreateJob>
#include <AkonadiCore/ItemModifyJob>
Nicolas Fella's avatar
Nicolas Fella committed
45
#include <KApplicationTrader>
Laurent Montel's avatar
Laurent Montel committed
46
47
48
#include <KEmailAddress>
#include <KFileItemActions>
#include <KIO/ApplicationLauncherJob>
Laurent Montel's avatar
Laurent Montel committed
49
50
#include <KIO/JobUiDelegate>
#include <KIO/OpenUrlJob>
Laurent Montel's avatar
Laurent Montel committed
51
52
53
#include <KLocalizedString>
#include <KMessageBox>
#include <KMimeTypeChooser>
54
#include <KSelectAction>
55
#include <KSharedConfig>
56
57
#include <KStandardGuiItem>
#include <KToggleAction>
Laurent Montel's avatar
Laurent Montel committed
58
59
#include <MailTransportAkonadi/ErrorAttribute>
#include <MessageCore/Util>
60
#include <QIcon>
Laurent Montel's avatar
Laurent Montel committed
61
#include <QMenu>
62
#include <QMimeData>
Laurent Montel's avatar
Laurent Montel committed
63
#include <QTemporaryDir>
64
#include <messageflags.h>
65

Laurent Montel's avatar
Laurent Montel committed
66
// Qt includes
67
68
#include <QClipboard>
#include <QItemSelectionModel>
Laurent Montel's avatar
Laurent Montel committed
69
70
71
#include <QMimeDatabase>
#include <QPrintDialog>
#include <QPrinter>
72
73
#include <QSplitter>
#include <QTreeView>
Laurent Montel's avatar
Laurent Montel committed
74
#include <QWheelEvent>
Laurent Montel's avatar
Laurent Montel committed
75
#include <WebEngineViewer/WebEngineExportHtmlPageJob>
Laurent Montel's avatar
Laurent Montel committed
76
// libkdepim
77
#include <MessageCore/AttachmentPropertiesDialog>
Laurent Montel's avatar
Laurent Montel committed
78
#include <PimCommon/BroadcastStatus>
79

Laurent Montel's avatar
Laurent Montel committed
80
81
82
#include <Akonadi/KMime/MessageParts>
#include <Akonadi/KMime/MessageStatus>
#include <AkonadiCore/attributefactory.h>
83
84
85
86
#include <AkonadiCore/collection.h>
#include <AkonadiCore/itemfetchjob.h>
#include <AkonadiCore/itemfetchscope.h>

87
#include <MessageCore/AutocryptUtils>
88
89
90
#include <KIdentityManagement/Identity>
#include <KIdentityManagement/IdentityManager>

Laurent Montel's avatar
Laurent Montel committed
91
// own includes
92
#include "csshelper.h"
Laurent Montel's avatar
Laurent Montel committed
93
#include "messageviewer/messageviewerutil.h"
94
#include "settings/messageviewersettings.h"
Laurent Montel's avatar
Laurent Montel committed
95
#include "utils/messageviewerutil_p.h"
96
#include "viewer/attachmentstrategy.h"
97
98
#include "viewer/mimeparttree/mimetreemodel.h"
#include "viewer/urlhandlermanager.h"
Laurent Montel's avatar
Laurent Montel committed
99
100
#include "widgets/attachmentdialog.h"
#include "widgets/htmlstatusbar.h"
101
#include "widgets/shownextmessagewidget.h"
Laurent Montel's avatar
Laurent Montel committed
102
#include "widgets/vcardviewer.h"
103

104
#include "header/headerstylemenumanager.h"
Laurent Montel's avatar
Laurent Montel committed
105
106
107
#include "htmlwriter/webengineparthtmlwriter.h"
#include "viewer/webengine/mailwebengineview.h"
#include <WebEngineViewer/FindBarWebEngineView>
Laurent Montel's avatar
Laurent Montel committed
108
#include <WebEngineViewer/LocalDataBaseManager>
109
#include <WebEngineViewer/SubmittedFormWarningWidget>
110
#include <WebEngineViewer/WebEngineExportPdfPageJob>
Laurent Montel's avatar
Laurent Montel committed
111
112
#include <WebEngineViewer/WebHitTestResult>
#include <widgets/mailsourcewebengineviewer.h>
113

114
#include "interfaces/htmlwriter.h"
Laurent Montel's avatar
Laurent Montel committed
115
#include <MimeTreeParser/BodyPart>
116
117
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/ObjectTreeParser>
118
119
120

#include <MessageCore/StringUtil>

121
#include <MessageCore/MessageCoreSettings>
Laurent Montel's avatar
Laurent Montel committed
122
#include <MessageCore/NodeHelper>
123

Laurent Montel's avatar
Laurent Montel committed
124
#include <AkonadiCore/CollectionFetchJob>
125
126
127
128
129
#include <AkonadiCore/agentinstance.h>
#include <AkonadiCore/agentmanager.h>
#include <AkonadiCore/collectionfetchscope.h>

#include <KJobWidgets/KJobWidgets>
Laurent Montel's avatar
Laurent Montel committed
130
#include <KPIMTextEdit/TextToSpeechWidget>
131
132
#include <QApplication>
#include <QStandardPaths>
133
#include <QWebEngineSettings>
134
#include <WebEngineViewer/DeveloperToolDialog>
135
#include <WebEngineViewer/TrackingWarningWidget>
Laurent Montel's avatar
Laurent Montel committed
136
137
#include <WebEngineViewer/ZoomActionMenu>
#include <boost/bind.hpp>
138
139
140
#include <header/headerstyleplugin.h>
#include <viewerplugins/viewerplugininterface.h>

Laurent Montel's avatar
Laurent Montel committed
141
#include <GrantleeTheme/GrantleeTheme>
Laurent Montel's avatar
Laurent Montel committed
142
#include <GrantleeTheme/GrantleeThemeManager>
143

144
#include "dkim-verify/dkimmanager.h"
Laurent Montel's avatar
Laurent Montel committed
145
#include "dkim-verify/dkimmanagerulesdialog.h"
Laurent Montel's avatar
Laurent Montel committed
146
#include "dkim-verify/dkimresultattribute.h"
Laurent Montel's avatar
Laurent Montel committed
147
#include "dkim-verify/dkimviewermenu.h"
Laurent Montel's avatar
Laurent Montel committed
148
#include "dkim-verify/dkimwidgetinfo.h"
149

Laurent Montel's avatar
Laurent Montel committed
150
151
#include "remote-content/remotecontentmenu.h"

152
153
154
155
156
157
158
using namespace boost;
using namespace MailTransport;
using namespace MessageViewer;
using namespace MessageCore;

static QAtomicInt _k_attributeInitialized;

Laurent Montel's avatar
Laurent Montel committed
159
template<typename Arg, typename R, typename C> struct InvokeWrapper {
160
161
    R *receiver;
    void (C::*memberFun)(Arg);
Laurent Montel's avatar
Laurent Montel committed
162
163
    void operator()(Arg result)
    {
164
165
166
167
        (receiver->*memberFun)(result);
    }
};

Laurent Montel's avatar
Laurent Montel committed
168
template<typename Arg, typename R, typename C> InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
169
170
171
172
173
{
    InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
    return wrapper;
}

Laurent Montel's avatar
Laurent Montel committed
174
ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection)
Laurent Montel's avatar
Laurent Montel committed
175
176
177
178
179
180
    : QObject(aParent)
    , mNodeHelper(new MimeTreeParser::NodeHelper)
    , mOldGlobalOverrideEncoding(QStringLiteral("---"))
    , mMainWindow(mainWindow)
    , mActionCollection(actionCollection)
    , q(aParent)
Laurent Montel's avatar
Laurent Montel committed
181
    , mSession(new Akonadi::Session("MessageViewer-" + QByteArray::number(reinterpret_cast<quintptr>(this)), this))
182
183
184
185
{
    if (!mainWindow) {
        mMainWindow = aParent;
    }
186
187
    mMessageViewerRenderer = new MessageViewerRenderer;

Laurent Montel's avatar
Laurent Montel committed
188
    mRemoteContentMenu = new MessageViewer::RemoteContentMenu(mMainWindow);
Laurent Montel's avatar
Laurent Montel committed
189
    connect(mRemoteContentMenu, &MessageViewer::RemoteContentMenu::updateEmail, this, &ViewerPrivate::updateReaderWin);
Laurent Montel's avatar
Laurent Montel committed
190

191
    mDkimWidgetInfo = new MessageViewer::DKIMWidgetInfo(mMainWindow);
192
193
194
195
    if (_k_attributeInitialized.testAndSetAcquire(0, 1)) {
        Akonadi::AttributeFactory::registerAttribute<MessageViewer::MessageDisplayFormatAttribute>();
        Akonadi::AttributeFactory::registerAttribute<MessageViewer::ScamAttribute>();
    }
196
197
    mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this);
    mPhishingDatabase->initialize();
Laurent Montel's avatar
Laurent Montel committed
198
    connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ViewerPrivate::slotCheckedUrlFinished);
Laurent Montel's avatar
Laurent Montel committed
199

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    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);
Laurent Montel's avatar
Laurent Montel committed
215
    connect(&mResizeTimer, &QTimer::timeout, this, &ViewerPrivate::slotDelayedResize);
216
217

    mUpdateReaderWinTimer.setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
218
    connect(&mUpdateReaderWinTimer, &QTimer::timeout, this, &ViewerPrivate::updateReaderWin);
219

Laurent Montel's avatar
Laurent Montel committed
220
    connect(mNodeHelper, &MimeTreeParser::NodeHelper::update, this, &ViewerPrivate::update);
221

Laurent Montel's avatar
Laurent Montel committed
222
    connect(mColorBar, &HtmlStatusBar::clicked, this, &ViewerPrivate::slotToggleHtmlMode);
223
224
225

    // FIXME: Don't use the full payload here when attachment loading on demand is used, just
    //        like in KMMainWidget::slotMessageActivated().
226
    mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor"));
227
    mMonitor.setSession(mSession);
228
229
230
231
232
    Akonadi::ItemFetchScope fs;
    fs.fetchFullPayload();
    fs.fetchAttribute<MailTransport::ErrorAttribute>();
    fs.fetchAttribute<MessageViewer::MessageDisplayFormatAttribute>();
    fs.fetchAttribute<MessageViewer::ScamAttribute>();
Laurent Montel's avatar
Laurent Montel committed
233
    fs.fetchAttribute<MessageViewer::DKIMResultAttribute>();
234
    mMonitor.setItemFetchScope(fs);
Laurent Montel's avatar
Laurent Montel committed
235
236
237
    connect(&mMonitor, &Akonadi::Monitor::itemChanged, this, &ViewerPrivate::slotItemChanged);
    connect(&mMonitor, &Akonadi::Monitor::itemRemoved, this, &ViewerPrivate::slotClear);
    connect(&mMonitor, &Akonadi::Monitor::itemMoved, this, &ViewerPrivate::slotItemMoved);
238
239
240
241
}

ViewerPrivate::~ViewerPrivate()
{
242
    delete mDeveloperToolDialog;
Laurent Montel's avatar
Laurent Montel committed
243
    delete mMessageViewerRenderer;
244
    MessageViewer::MessageViewerSettings::self()->save();
Laurent Montel's avatar
Laurent Montel committed
245
246
247
248
    delete mHtmlWriter;
    mHtmlWriter = nullptr;
    delete mViewer;
    mViewer = nullptr;
249
    mNodeHelper->forceCleanTempFiles();
250
    qDeleteAll(mListMailSourceViewer);
Laurent Montel's avatar
Laurent Montel committed
251
    mMessage.clear();
252
253
254
255
256
257
258
259
260
    delete mNodeHelper;
}

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

261
void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url)
262
263
264
265
266
{
    if (!node) {
        return;
    }

267
268
    if (auto ct = node->contentType(false)) {
        if (ct->mimeType() == "text/x-moz-deleted") {
269
270
            return;
        }
271
        if (ct->mimeType() == "message/external-body") {
Laurent Montel's avatar
Laurent Montel committed
272
            if (ct->hasParameter(QStringLiteral("url"))) {
Laurent Montel's avatar
Laurent Montel committed
273
                auto job = new KIO::OpenUrlJob(url, QStringLiteral("text/html"));
Laurent Montel's avatar
Laurent Montel committed
274
275
                job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, q));
                job->start();
276
277
278
279
280
281
282
283
284
                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
285
        KMime::Message::Ptr m(new KMime::Message);
286
287
        m->setContent(node->parent()->bodyAsMessage()->encodedContent());
        m->parse();
Laurent Montel's avatar
Laurent Montel committed
288
        attachmentViewMessage(m);
289
290
291
292
293
        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
294
    auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower()));
295
296
297
298
299
300
    if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) {
        showVCard(node);
        return;
    }

    // special case treatment on mac and windows
301
302
303
    QUrl atmUrl = url;
    if (url.isEmpty()) {
        atmUrl = mNodeHelper->tempFileUrlFromNode(node);
304
    }
305
    if (Util::handleUrlWithQDesktopServices(atmUrl)) {
306
307
308
309
        return;
    }

    if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
Laurent Montel's avatar
Laurent Montel committed
310
        mimetype = MimeTreeParser::Util::mimetype(url.isLocalFile() ? url.toLocalFile() : url.fileName());
311
    }
Laurent Montel's avatar
Laurent Montel committed
312
    KService::Ptr offer = KApplicationTrader::preferredService(mimetype.name());
313

314
    const QString filenameText = MimeTreeParser::NodeHelper::fileName(node);
315

Laurent Montel's avatar
Laurent Montel committed
316
    QPointer<AttachmentDialog> dialog = new AttachmentDialog(mMainWindow, filenameText, offer, QLatin1String("askSave_") + mimetype.name());
Laurent Montel's avatar
Laurent Montel committed
317
318
    const int choice = dialog->exec();
    delete dialog;
319
    if (choice == AttachmentDialog::Save) {
320
        QList<QUrl> urlList;
Laurent Montel's avatar
Laurent Montel committed
321
        if (Util::saveContents(mMainWindow, KMime::Content::List() << node, urlList)) {
322
            showSavedFileFolderWidget(urlList, MessageViewer::OpenSavedFileFolderWidget::FileType::Attachment);
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
        }
    } 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
347
    const QVector<KMime::Content *> extraNodes = mNodeHelper->extraContents(mMessage.data());
348
349
    if (extraNodes.contains(node->topLevel())) {
        KMessageBox::error(mMainWindow,
Laurent Montel's avatar
Laurent Montel committed
350
                           i18n("Deleting an attachment from an encrypted or old-style mailman message is not supported."),
351
                           i18n("Delete Attachment"));
Laurent Montel's avatar
Laurent Montel committed
352
        return true; // cancelled
353
354
    }

Laurent Montel's avatar
Laurent Montel committed
355
356
357
358
359
360
361
362
363
    if (showWarning
        && KMessageBox::warningContinueCancel(mMainWindow,
                                              i18n("Deleting an attachment might invalidate any digital signature on this message."),
                                              i18n("Delete Attachment"),
                                              KStandardGuiItem::del(),
                                              KStandardGuiItem::cancel(),
                                              QStringLiteral("DeleteAttachmentSignatureWarning"))
            != KMessageBox::Continue) {
        return false; // cancelled
364
    }
Laurent Montel's avatar
Laurent Montel committed
365
    // don't confuse the model
366
367
368
369
    mMimePartTree->clearModel();
    QString filename;
    QString name;
    QByteArray mimetype;
Laurent Montel's avatar
Laurent Montel committed
370
371
    if (auto cd = node->contentDisposition(false)) {
        filename = cd->filename();
372
373
    }

374
375
376
    if (auto ct = node->contentType(false)) {
        name = ct->name();
        mimetype = ct->mimeType();
377
378
379
    }

    // text/plain part:
Laurent Montel's avatar
Laurent Montel committed
380
    auto deletePart = new KMime::Content(parent);
Laurent Montel's avatar
Laurent Montel committed
381
382
383
    auto deleteCt = deletePart->contentType(true);
    deleteCt->setMimeType("text/x-moz-deleted");
    deleteCt->setName(QStringLiteral("Deleted: %1").arg(name), "utf8");
Laurent Montel's avatar
Laurent Montel committed
384
385
    deletePart->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
    deletePart->contentDisposition(false)->setFilename(QStringLiteral("Deleted: %1").arg(name));
386

Laurent Montel's avatar
Laurent Montel committed
387
    deleteCt->setCharset("utf-8");
388
    deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
Laurent Montel's avatar
Laurent Montel committed
389
    QByteArray bodyMessage = QByteArrayLiteral("\nYou deleted an attachment from this message. The original MIME headers for the attachment were:");
390
391
392
393
394
395
396
397
398
399
    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());
    mMimePartTree->mimePartModel()->setRoot(modifiedMessage);
Ingo Klöcker's avatar
Ingo Klöcker committed
400
    mMessageItem.setPayloadFromData(mMessage->encodedContent());
Laurent Montel's avatar
Laurent Montel committed
401
    auto job = new Akonadi::ItemModifyJob(mMessageItem, mSession);
402
403
404
405
406
407
408
409
410
411
    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 {
412
        setMessageItem(mMessageItem, MimeTreeParser::Force);
413
414
415
    }
}

Laurent Montel's avatar
Laurent Montel committed
416
void ViewerPrivate::scrollToAnchor(const QString &anchor)
417
{
Laurent Montel's avatar
Laurent Montel committed
418
    mViewer->scrollToAnchor(anchor);
419
420
}

Laurent Montel's avatar
Laurent Montel committed
421
void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent)
422
{
Laurent Montel's avatar
Laurent Montel committed
423
    const KService::List offers = KFileItemActions::associatedApplications(QStringList() << contentTypeStr, QString());
424
425
    if (!offers.isEmpty()) {
        QMenu *menu = topMenu;
Laurent Montel's avatar
Laurent Montel committed
426
        auto actionGroup = new QActionGroup(menu);
427
428

        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
429
            connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithActionCurrentContent);
430
        } else {
Laurent Montel's avatar
Laurent Montel committed
431
            connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithAction);
432
433
434
435
436
437
438
        }

        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
439
        // qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu;
440

Laurent Montel's avatar
Laurent Montel committed
441
442
        for (const KService::Ptr &ser : offers) {
            QAction *act = MessageViewer::Util::createAppAction(ser,
Laurent Montel's avatar
Laurent Montel committed
443
                                                                // no submenu -> prefix single offer
Laurent Montel's avatar
Laurent Montel committed
444
445
446
                                                                menu == topMenu,
                                                                actionGroup,
                                                                menu);
447
448
449
450
451
452
453
454
455
456
            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...");
        }
Laurent Montel's avatar
Laurent Montel committed
457
        auto openWithAct = new QAction(menu);
458
459
        openWithAct->setText(openWithActionName);
        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
460
            connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent);
461
462
463
464
465
466
        } else {
            connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog);
        }

        menu->addAction(openWithAct);
    } else { // no app offers -> Open With...
Laurent Montel's avatar
Laurent Montel committed
467
        auto act = new QAction(topMenu);
468
469
        act->setText(i18nc("@title:menu", "&Open With..."));
        if (fromCurrentContent) {
Laurent Montel's avatar
Laurent Montel committed
470
            connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent);
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
        } 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
488
    const auto contents = selectedContents();
489
490
491
492
493
494
495
496
497
498
    if (contents.count() == 1) {
        attachmentOpenWith(contents.first());
    }
}

void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act)
{
    if (!mCurrentContent) {
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
499
    const KService::Ptr app = act->data().value<KService::Ptr>();
500
501
502
503
504
    attachmentOpenWith(mCurrentContent, app);
}

void ViewerPrivate::slotOpenWithAction(QAction *act)
{
Laurent Montel's avatar
Laurent Montel committed
505
506
    const KService::Ptr app = act->data().value<KService::Ptr>();
    const auto contents = selectedContents();
507
508
509
510
511
    if (contents.count() == 1) {
        attachmentOpenWith(contents.first(), app);
    }
}

Laurent Montel's avatar
Laurent Montel committed
512
void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos)
513
{
Laurent Montel's avatar
Laurent Montel committed
514
    Q_UNUSED(name)
Laurent Montel's avatar
Laurent Montel committed
515
    prepareHandleAttachment(node);
516
    bool deletedAttachment = false;
Laurent Montel's avatar
Laurent Montel committed
517
518
519
    QString contentTypeStr;
    if (auto contentType = node->contentType(false)) {
        contentTypeStr = QLatin1String(contentType->mimeType());
520
    }
521
522
523
    if (contentTypeStr == QStringLiteral("message/global")) { // Not registred in mimetype => it's a message/rfc822
        contentTypeStr = QStringLiteral("message/rfc822");
    }
Laurent Montel's avatar
Laurent Montel committed
524
    deletedAttachment = (contentTypeStr == QStringLiteral("text/x-moz-deleted"));
Laurent Montel's avatar
Laurent Montel committed
525
    // Not necessary to show popup menu when attachment was removed
526
527
528
529
530
    if (deletedAttachment) {
        return;
    }

    QMenu menu;
531

Laurent Montel's avatar
Laurent Montel committed
532
    QAction *action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open"));
533
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
534
535
536
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Open);
    });
537
    if (!deletedAttachment) {
Laurent Montel's avatar
Laurent Montel committed
538
        createOpenWithMenu(&menu, contentTypeStr, true);
539
540
541
542
543
544
    }

    QMimeDatabase mimeDb;
    auto mimetype = mimeDb.mimeTypeForName(contentTypeStr);
    if (mimetype.isValid()) {
        const QStringList parentMimeType = mimetype.parentMimeTypes();
Laurent Montel's avatar
Laurent Montel committed
545
546
547
        if ((contentTypeStr == QLatin1String("text/plain")) || (contentTypeStr == QLatin1String("image/png")) || (contentTypeStr == QLatin1String("image/jpeg"))
            || parentMimeType.contains(QLatin1String("text/plain")) || parentMimeType.contains(QLatin1String("image/png"))
            || parentMimeType.contains(QLatin1String("image/jpeg"))) {
Laurent Montel's avatar
Laurent Montel committed
548
            action = menu.addAction(i18nc("to view something", "View"));
549
            action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
550
551
552
            connect(action, &QAction::triggered, this, [this]() {
                slotHandleAttachment(Viewer::View);
            });
553
554
555
        }
    }

Laurent Montel's avatar
Laurent Montel committed
556
557
558
559
    action = menu.addAction(i18n("Scroll To"));
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ScrollTo);
    });
560

Laurent Montel's avatar
Laurent Montel committed
561
    action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As..."));
562
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
563
564
565
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Save);
    });
566

Laurent Montel's avatar
Laurent Montel committed
567
    action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"));
568
    action->setEnabled(!deletedAttachment);
Laurent Montel's avatar
Laurent Montel committed
569
570
571
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Copy);
    });
572
573

    const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
Laurent Montel's avatar
Laurent Montel committed
574
    const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid()
Laurent Montel's avatar
Laurent Montel committed
575
        && (mMessageItem.parentCollection().rights() != Akonadi::Collection::ReadOnly) && !isEncapsulatedMessage;
576

Laurent Montel's avatar
Laurent Montel committed
577
    menu.addSeparator();
Laurent Montel's avatar
Laurent Montel committed
578
    action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Attachment"));
Laurent Montel's avatar
Laurent Montel committed
579
580
581
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Delete);
    });
Laurent Montel's avatar
Laurent Montel committed
582

583
    action->setEnabled(canChange && !deletedAttachment);
584
585
586
#if 0
    menu->addSeparator();

Laurent Montel's avatar
Laurent Montel committed
587
588
589
    action
        = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")),
                          i18n("Reply To Author"));
Laurent Montel's avatar
Laurent Montel committed
590
591
592
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ReplyMessageToAuthor);
    });
Laurent Montel's avatar
Laurent Montel committed
593
594
595

    menu->addSeparator();

Laurent Montel's avatar
Laurent Montel committed
596
597
    action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n(
                                 "Reply To All"));
Laurent Montel's avatar
Laurent Montel committed
598
599
600
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::ReplyMessageToAll);
    });
601
#endif
Laurent Montel's avatar
Laurent Montel committed
602
603
    menu.addSeparator();
    action = menu.addAction(i18n("Properties"));
Laurent Montel's avatar
Laurent Montel committed
604
605
606
    connect(action, &QAction::triggered, this, [this]() {
        slotHandleAttachment(Viewer::Properties);
    });
Laurent Montel's avatar
Laurent Montel committed
607
    menu.exec(globalPos);
608
609
}

Laurent Montel's avatar
Laurent Montel committed
610
void ViewerPrivate::prepareHandleAttachment(KMime::Content *node)
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
{
    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
628
        return KService::Ptr(nullptr);
629
630
631
632
    }

    if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
        /*TODO(Andris) port when on-demand loading is done   && msgPart.isComplete() */
633
        mimetype = MimeTreeParser::Util::mimetype(fileName);
634
    }
Nicolas Fella's avatar
Nicolas Fella committed
635
    return KApplicationTrader::preferredService(mimetype.name());
636
637
}

Laurent Montel's avatar
Laurent Montel committed
638
KMime::Content::List ViewerPrivate::selectedContents() const
639
640
641
642
643
644
645
646
{
    return mMimePartTree->selectedContents();
}

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

647
    // Make sure that it will not deleted when we switch from message.
Laurent Montel's avatar
Laurent Montel committed
648
    auto tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX"));
649
650
651
652
653
654
655
656
657
    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
658
            qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name << " to " << path;
659
660
661
662
        } else {
            name = newPath;
        }
        f.close();
663
    } else {
664
        delete tmpDir;
665
666
    }

667
668
669
670
    QList<QUrl> lst;
    const QFileDevice::Permissions perms = QFile::permissions(name);
    QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser);
    const QUrl url = QUrl::fromLocalFile(name);
671
    lst.append(url);
Laurent Montel's avatar
Laurent Montel committed
672

Laurent Montel's avatar
Laurent Montel committed
673
    auto job = new KIO::ApplicationLauncherJob(offer);
Laurent Montel's avatar
Laurent Montel committed
674
675
676
677
678
    job->setUrls({url});
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, mMainWindow));
    job->start();
    connect(job, &KJob::result, this, [url, job]() {
        if (job->error()) {
679
680
            QFile::remove(url.toLocalFile());
        }
Laurent Montel's avatar
Laurent Montel committed
681
    });
682
683
684
685
}

void ViewerPrivate::attachmentOpen(KMime::Content *node)
{
Laurent Montel's avatar
Laurent Montel committed
686
    const KService::Ptr offer = getServiceOffer(node);
687
688
689
690
691
692
693
    if (!offer) {
        qCDebug(MESSAGEVIEWER_LOG) << "got no offer";
        return;
    }
    attachmentOpenWith(node, offer);
}

694
695
bool ViewerPrivate::showEmoticons() const
{
696
    return mForceEmoticons;
697
698
}

699
HtmlWriter *ViewerPrivate::htmlWriter() const
700
701
702
703
704
705
{
    return mHtmlWriter;
}

CSSHelper *ViewerPrivate::cssHelper() const
{
706
    return mMessageViewerRenderer->cssHelper();
707
708
}

709
MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
{
    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
731
    if (MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) {
732
        return true;
Laurent Montel's avatar
Laurent Montel committed
733
734
    } else {
        return mDecrytMessageOverwrite;
735
736
737
738
739
    }
}

void ViewerPrivate::displaySplashPage(const QString &message)
{
Laurent Montel's avatar
Laurent Montel committed
740
741
742
743
744
    displaySplashPage(QStringLiteral("status.html"),
                      {{QStringLiteral("icon"), QStringLiteral("kmail")},
                       {QStringLiteral("name"), i18n("KMail")},
                       {QStringLiteral("subtitle"), i18n("The KDE Mail Client")},
                       {QStringLiteral("message"), message}});
745
746
}

Laurent Montel's avatar
Laurent Montel committed
747
void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain)
748
{
Laurent Montel's avatar
Laurent Montel committed
749
750
751
752
    if (mViewer) {
        mMsgDisplay = false;
        adjustLayout();

Laurent Montel's avatar
Laurent Montel committed
753
        GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/"));
Laurent Montel's avatar
Laurent Montel committed
754
755
        GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
        if (theme.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
756
            mViewer->setHtml(theme.render(templateName, data, domain), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
Laurent Montel's avatar
Laurent Montel committed
757
758
759
760
        } else {
            qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme";
        }
        mViewer->show();
761
762
763
764
765
    }
}

void ViewerPrivate::enableMessageDisplay()
{
766
767
768
    if (mMsgDisplay) {
        return;
    }
769
770
771
772
773
774
775
776
777
778
779
    mMsgDisplay = true;
    adjustLayout();
}

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

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

    if (mMessageItem.hasAttribute<MessageViewer::MessageDisplayFormatAttribute>()) {
Laurent Montel's avatar
Laurent Montel committed
780
        const MessageViewer::MessageDisplayFormatAttribute *const attr = mMessageItem.attribute<MessageViewer::MessageDisplayFormatAttribute>();
781
782
783
784
        setHtmlLoadExtOverride(attr->remoteContent());
        setDisplayFormatMessageOverwrite(attr->messageFormat());
    }

Volker Krause's avatar
Volker Krause committed
785
    htmlWriter()->begin();
Laurent Montel's avatar
Laurent Montel committed
786
    htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont));
787
788
789
790
791
792
793

    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.
794
    mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate);
795
796

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

Laurent Montel's avatar
Laurent Montel committed
802
803
        htmlWriter()->write(QStringLiteral("<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
804
        htmlWriter()->write(QStringLiteral("<p></p>"));
805
806
807
808
809
810
    }

    parseContent(mMessage.data());
    mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data()));
    mColorBar->update();

Laurent Montel's avatar
Laurent Montel committed
811
    htmlWriter()->write(cssHelper()->endBodyHtml());
Laurent Montel's avatar
Laurent Montel committed
812
813
    connect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection);
    connect(mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered, Qt::UniqueConnection);
814

Volker Krause's avatar
Volker Krause committed
815
    htmlWriter()->end();
816
817
818
819
}

void ViewerPrivate::parseContent(KMime::Content *content)
{
Laurent Montel's avatar
Laurent Montel committed
820
    Q_ASSERT(content != nullptr);
821
    mNodeHelper->removeTempFiles();
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841

    // 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);
        }
    }

Laurent Montel's avatar
Laurent Montel committed
842
    auto *message = dynamic_cast<KMime::Message *>(content);
843
    bool onlySingleNode = mMessage.data() != content;
844
845
846
847

    // Pass control to the OTP now, which does the real work
    mNodeHelper->setNodeUnprocessed(mMessage.data(), true);
    MailViewerSource otpSource(this);
848
    MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper);
849

850
    otp.setAllowAsync(!mPrinting);
851
    otp.parseObjectTree(content, onlySingleNode);
852
    htmlWriter()->setCodec(otp.plainTextContentCharset());
853
854
855
856
    if (message) {
        htmlWriter()->write(writeMessageHeader(message, hasVCard ? vCardContent : nullptr, true));
    }

857
    otpSource.render(otp.parsedPart(), onlySingleNode);
858

859
860
861
862
863
    // 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
864
865
    MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState(content);
    MimeTreeParser::KMMsgSignatureState signatureState = mNodeHelper->overallSignatureState(content);
866
867
868
    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
869
    if (signatureState != MimeTreeParser::KMMsgNotSigned || mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) {
870
871
872
        mNodeHelper->setSignatureState(content, signatureState);
    }

873
874
875
876
877
    if (!onlySingleNode && isAutocryptEnabled(message)) {
        auto mixup = HeaderMixupNodeHelper(mNodeHelper, message);
        processAutocryptfromMail(mixup);
    }

878
879
880
    showHideMimeTree();
}

Laurent Montel's avatar
Laurent Montel committed
881
QString ViewerPrivate::writeMessageHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel)
882
883
{
    if (!headerStylePlugin()) {
Laurent Montel's avatar
Laurent Montel committed
884
        qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMessageHeader() without a header style set!";
885
        return {};
886
    }
Laurent Montel's avatar
Laurent Montel committed
887
    HeaderStyle *style = headerStylePlugin()->headerStyle();
888
    if (vCardNode) {
Laurent Montel's avatar
Laurent Montel committed
889
        style->setVCardName(mNodeHelper->asHREF(vCardNode, QStringLiteral("body")));
890
891
    } else {
        style->setVCardName(QString());
Laurent Montel's avatar
Laurent Montel committed
892
893
894
895
896
897
898
899
    }
    style->setHeaderStrategy(headerStylePlugin()->headerStrategy());
    style->setPrinting(mPrinting);
    style->setTopLevel(topLevel);
    style->setAllowAsync(true);
    style->setSourceObject(this);
    style->setNodeHelper(mNodeHelper);
    style->setAttachmentHtml(attachmentHtml());
900
901
902
903
    if (mMessageItem.isValid()) {
        Akonadi::MessageStatus status;
        status.setStatusFromFlags(mMessageItem.flags());

Laurent Montel's avatar
Laurent Montel committed
904
        style->setMessageStatus(status);
Laurent Montel's avatar
Laurent Montel committed
905
    } else {
Laurent Montel's avatar
Laurent Montel committed
906
        style->setReadOnlyMessage(true);
907
908
    }

Laurent Montel's avatar
Laurent Montel committed
909
    return style->format(aMsg);
910
911
912
913
914
915
}

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

Laurent Montel's avatar
Laurent Montel committed
916
    auto vcv = new VCardViewer(mMainWindow, vCard);
917
918
919
920
921
922
923
    vcv->setAttribute(Qt::WA_DeleteOnClose);
    vcv->show();
}

void ViewerPrivate::initHtmlWidget()
{
    if (!htmlWriter()) {
Laurent Montel's avatar
Laurent Montel committed
924
        mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr);
925
926
        mHtmlWriter = mPartHtmlWriter;
    }
Laurent Montel's avatar
Laurent Montel committed
927
928
929
930
931
    connect(mViewer->page(), &QWebEnginePage::linkHovered, this, &ViewerPrivate::slotUrlOn);
    connect(mViewer, &MailWebEngineView::openUrl, this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection);
    connect(mViewer, &MailWebEngineView::popupMenu, this, &ViewerPrivate::slotUrlPopup);
    connect(mViewer, &MailWebEngineView::wheelZoomChanged, this, &ViewerPrivate::slotWheelZoomChanged);
    connect(mViewer, &MailWebEngineView::messageMayBeAScam, this, &ViewerPrivate::slotMessageMayBeAScam);
932
    connect(mViewer, &MailWebEngineView::formSubmittedForbidden, mSubmittedFormWarning, &WebEngineViewer::SubmittedFormWarningWidget::showWarning);
933
    connect(mViewer, &MailWebEngineView::mailTrackingFound, mMailTrackingWarning, &WebEngineViewer::TrackingWarningWidget::addTracker);
Laurent Montel's avatar
Laurent Montel committed
934
935
936
937
938
    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);
Laurent Montel's avatar
Laurent Montel committed
939
940
941
942
943
    connect(mViewer, &MailWebEngineView::urlBlocked, this, &ViewerPrivate::slotUrlBlocked);
}

void ViewerPrivate::slotUrlBlocked(const QUrl &url)
{
Laurent Montel's avatar
Laurent Montel committed
944
    mRemoteContentMenu->appendUrl(url.adjusted(QUrl::RemovePath | QUrl::RemovePort | QUrl::RemoveQuery).toString());
Laurent Montel's avatar
Laurent Montel committed
945
946
947
948
949
}

RemoteContentMenu *ViewerPrivate::remoteContentMenu() const
{
    return mRemoteContentMenu;
950
951
}

Laurent Montel's avatar
Laurent Montel committed
952
void ViewerPrivate::applyZoomValue(qreal factor, bool saveConfig)
953
{
Laurent Montel's avatar
Laurent Montel committed
954
955
    if (mZoomActionMenu) {
        if (factor >= 10 && factor <= 300) {
Laurent Montel's avatar
Laurent Montel committed
956
            if (!qFuzzyCompare(mZoomActionMenu->zoomFactor(), factor)) {
Laurent Montel's avatar
Laurent Montel committed
957
958
959
960
961
962
                mZoomActionMenu->setZoomFactor(factor);
                mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0);
                if (saveConfig) {
                    MessageViewer::MessageViewerSettings::self()->setZoomFactor(factor);
                }
            }
Laurent Montel's avatar
Laurent Montel committed
963
        }
964
965
966
    }
}

967
968
void ViewerPrivate::setWebViewZoomFactor(qreal factor)
{
Laurent Montel's avatar
Laurent Montel committed
969
    applyZoomValue(factor, false);
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
}

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);
}

987
988
void ViewerPrivate::readConfig()
{
989
    mMessageViewerRenderer->setCurrentWidget(mViewer);
Laurent Montel's avatar
Laurent Montel committed
990
    recreateCssHelper();
991

992
993
994
995
    mForceEmoticons = MessageViewer::MessageViewerSettings::self()->showEmoticons();
    if (mDisableEmoticonAction) {
        mDisableEmoticonAction->setChecked(!mForceEmoticons);
    }
996
997
998
999
    if (headerStylePlugin()) {
        headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons);
    }

1000
1001
1002
1003
1004
1005
1006
    mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont();
    if (mToggleFixFontAction) {
        mToggleFixFontAction->setChecked(mUseFixedFont);
    }

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

Laurent Montel's avatar
Laurent Montel committed
1007
    MessageViewer::Util::readGravatarConfig();
1008
1009
1010
1011
    if (mHeaderStyleMenuManager) {
        mHeaderStyleMenuManager->readConfig();
    }

Laurent Montel's avatar
Laurent Montel committed
1012
    setAttachmentStrategy(AttachmentStrategy::create(MessageViewer::MessageViewerSettings::self()->attachmentStrategy()));
1013
1014
1015
1016
1017
1018
1019
1020
    KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy());
    if (raction) {
        raction->setChecked(true);
    }

    adjustLayout();

    readGlobalOverrideCodec();
1021
    mViewer->readConfig();
Laurent Montel's avatar
Laurent Montel committed
1022
1023
    mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize());
    mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize());
1024
1025
1026
1027
    if (mMessage) {
        update();
    }
    mColorBar->update();
Laurent Montel's avatar
Laurent Montel committed
1028
    applyZoomValue(MessageViewer::MessageViewerSettings::self()->zoomFactor(), false);
1029
1030
}

Laurent Montel's avatar
Laurent Montel committed
1031 </