MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - MailView.h (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 100.0 % 2 2
Test Date: 2026-06-21 21:10:19 Functions: 100.0 % 2 2
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <QWebEngineView>
       4              : 
       5              : #include "data/Models.h"
       6              : #include "ui/HtmlSanitizer.h"
       7              : 
       8              : class QClipboard;
       9              : class QContextMenuEvent;
      10              : class QFrame;
      11              : class QLabel;
      12              : class QMenu;
      13              : class QPushButton;
      14              : class QStackedWidget;
      15              : class QTextBrowser;
      16              : class QVBoxLayout;
      17              : class QHBoxLayout;
      18              : class QWebEngineProfile;
      19              : class AttachmentBar;
      20              : class ExternalContentInterceptor;
      21              : class MailCache;
      22              : class PdfViewerWidget;
      23              : 
      24              : // T-351: QWebEngineView subclass that overrides contextMenuEvent to suppress
      25              : // Chromium's built-in context menu and emit our own signal instead.
      26              : class MailWebEngineView : public QWebEngineView {
      27              :   Q_OBJECT
      28              : public:
      29              :   using QWebEngineView::QWebEngineView;
      30              : signals:
      31              :   void mailContextMenuRequested(const QPoint &globalPos);
      32              : protected:
      33              :   void contextMenuEvent(QContextMenuEvent *event) override;
      34              : };
      35              : 
      36              : // Composite email viewer widget.
      37              : //
      38              : // Layout (top to bottom):
      39              : //   1. Header frame (background block):
      40              : //      a. Subject label (bold heading)
      41              : //      b. Meta label (Von/An/Datum on separate lines)
      42              : //   2. AttachmentBar: clickable attachment chips
      43              : //   3. Info bar: external content warning + view mode toggle
      44              : //   4. QStackedWidget: page 0 = QTextBrowser (plain), page 1 = QWebEngineView
      45              : class MailView : public QWidget {
      46          805 :   Q_OBJECT
      47              : #ifdef MAILJD_UNIT_TEST
      48              :   friend class TestMailView;
      49              : #endif
      50              : 
      51              : public:
      52              :   explicit MailView(QWidget *parent = nullptr);
      53              : 
      54              :   void displayMail(const MailHeader &header, const MailBody &body);
      55              :   void refreshLabels(const QStringList &labels);
      56              :   void clear();
      57              :   void toggleViewMode();
      58              :   void loadExternalContent();
      59              :   void showSource();
      60              : 
      61              :   // T-122: Inject MailCache for whitelist access
      62              :   void setCache(MailCache *cache);
      63              : 
      64              :   // T-544: Reload whitelist from MailCache into ExternalContentInterceptor
      65              :   void reloadWhitelist();
      66              : 
      67              : 
      68              : 
      69              : 
      70          119 :   AttachmentBar *attachmentBar() const { return m_attachmentBar; }
      71              : 
      72              : signals:
      73              :   void externalContentLoadRequested();
      74              :   void whitelistChanged();  // Emitted after addWhitelistEntry()
      75              : 
      76              :   // T-351: Context menu actions (connected by MainWindow)
      77              :   void replyRequested();
      78              :   void replyAllRequested();
      79              :   void forwardRequested();
      80              :   void moveRequested();
      81              :   void archiveRequested();
      82              :   void deleteRequested();
      83              : 
      84              : private:
      85              :   void ensureWebEngine();
      86              :   void renderCurrentBody();
      87              :   bool applySanitizedHtml(const QString &sanitizedHtml, const QString &csp,
      88              :                           quint64 generation);
      89              :   void retranslateUi();
      90              : 
      91              :   // Sprint 75: named handler for the plain-text viewer's
      92              :   // customContextMenuRequested signal. The signal delivers viewport-
      93              :   // local coordinates; this maps them to global screen coordinates via
      94              :   // m_textBrowser->viewport()->mapToGlobal() before forwarding to
      95              :   // showMailContextMenu(). showMailContextMenu() then receives global
      96              :   // coordinates from BOTH callers (the WebEngine path already emits
      97              :   // event->globalPos()), so its body no longer mixes coordinate
      98              :   // systems.
      99              :   void onPlainTextContextMenuRequested(const QPoint &localPos);
     100              : 
     101              :   // T-351: Context menu. Both callers normalize to global screen
     102              :   // coordinates before invoking this (QTextBrowser via
     103              :   // onPlainTextContextMenuRequested, WebEngine via contextMenuEvent).
     104              :   void showMailContextMenu(const QPoint &globalPos);
     105              : 
     106              :   // T-71.6: Open an attachment inline in the PDF viewer (page 2 of the
     107              :   // stack). Validates the MIME type, lazy-loads the BLOB via MailCache, and
     108              :   // switches the stack. leavePdfViewer() restores the previous page.
     109              :   void showAttachmentInline(qint64 attachmentId);
     110              :   void leavePdfViewer();
     111              : 
     112              :   // T-608/SEC-04: DOMPurify-based HTML sanitizer (replaces old regex sanitizer)
     113              :   HtmlSanitizer *m_htmlSanitizer = nullptr;
     114              : 
     115              : protected:
     116              :   void changeEvent(QEvent *event) override;
     117              : 
     118              :   // Components
     119              :   QVBoxLayout *m_mainLayout;
     120              : 
     121              :   // Header container (subject + meta with background)
     122              :   QFrame *m_headerFrame;
     123              :   QLabel *m_avatarLabel;   // 67.B4: sender initials avatar
     124              :   QLabel *m_subjectLabel;
     125              :   QLabel *m_metaLabel;
     126              :   QHBoxLayout *m_labelsLayout = nullptr;
     127              : 
     128              :   // Info bar
     129              :   QFrame *m_infoBar;
     130              :   QLabel *m_infoLabel;
     131              :   QPushButton *m_loadExternalBtn;
     132              :   QMenu *m_externalMenu = nullptr;
     133              :   QPushButton *m_toggleBtn;
     134              :   QPushButton *m_sourceBtn;
     135              : 
     136              :   // Content
     137              :   AttachmentBar *m_attachmentBar;
     138              :   QStackedWidget *m_stack;
     139              :   QTextBrowser *m_textBrowser;
     140              :   MailWebEngineView *m_webView = nullptr;
     141              :   QWebEngineProfile *m_webProfile = nullptr;
     142              :   ExternalContentInterceptor *m_interceptor = nullptr;
     143              :   PdfViewerWidget *m_pdfView = nullptr;   // T-71.6: inline PDF (stack page 2)
     144              :   int m_previousStackIndex = 0;           // T-71.6: page to return to from PDF
     145              : 
     146              :   // State
     147              :   MailHeader m_currentHeader;
     148              :   MailBody m_currentBody;
     149              :   bool m_showHtml = false;
     150              :   bool m_externalContentOverride = false; // T-073: user clicked "Load"
     151              :   quint64 m_renderGeneration = 0;
     152              : 
     153              :   // Sprint 75: opt-in exec-seam for unit tests. When m_interceptMenuExec
     154              :   // is true, showMailContextMenu() stores the received global position
     155              :   // in m_lastMenuExecGlobalPos and returns WITHOUT calling
     156              :   // QMenu::exec() — so a unit test can deterministically assert the
     157              :   // exec coordinate without blocking in the popup event loop.
     158              :   // Production and the default test path keep the real exec().
     159              :   bool m_interceptMenuExec = false;
     160              :   QPoint m_lastMenuExecGlobalPos;
     161              : 
     162              :   // T-122: Whitelist integration
     163              :   MailCache *m_cache = nullptr;
     164              :   QString m_currentSenderEmail;
     165              :   static QString extractEmail(const QString &fromField);
     166              : 
     167              :   // 67.B4: up to two initials from a From field (protected — tests
     168              :   // reach it through a subclass, like extractEmail)
     169              :   static QString initialsForSender(const QString &fromField);
     170              : 
     171              :   // 67.B4: 40x40 initials disc, deterministic color per sender
     172              :   QPixmap renderAvatar(const QString &fromField) const;
     173              :   void buildExternalContentMenu();
     174              : };
        

Generated by: LCOV version 2.0-1