MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - AttachmentBar.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 90.8 % 87 79
Test Date: 2026-07-27 17:53:44 Functions: 100.0 % 9 9
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 50.6 % 174 88

             Branch data     Line data    Source code
       1                 :             : #include "AttachmentBar.h"
       2                 :             : 
       3                 :             : #include <QEvent>
       4                 :             : #include <QHBoxLayout>
       5                 :             : #include <QIcon>
       6                 :             : #include <QToolButton>
       7                 :             : 
       8         [ +  - ]:          98 : AttachmentBar::AttachmentBar(QWidget *parent) : QFrame(parent) {
       9         [ +  - ]:         196 :   setObjectName(QStringLiteral("attachmentBar"));
      10   [ +  -  +  -  :          98 :   m_layout = new QHBoxLayout(this);
             -  +  -  - ]
      11         [ +  - ]:          98 :   m_layout->setContentsMargins(4, 2, 4, 2);
      12         [ +  - ]:          98 :   m_layout->setSpacing(4);
      13         [ +  - ]:          98 :   setFrameShape(QFrame::NoFrame);
      14         [ +  - ]:          98 :   setVisible(false);
      15                 :          98 : }
      16                 :             : 
      17                 :          86 : void AttachmentBar::setAttachments(const QList<Attachment> &attachments) {
      18                 :          86 :   clear();
      19                 :          86 :   m_attachments = attachments;
      20                 :             : 
      21         [ +  + ]:          86 :   if (attachments.isEmpty()) {
      22                 :          72 :     return;
      23                 :             :   }
      24                 :             : 
      25         [ +  + ]:          33 :   for (const auto &att : attachments) {
      26   [ +  -  +  -  :          19 :     auto *btn = new QToolButton(this);
             -  +  -  - ]
      27                 :             :     QString label =
      28   [ -  +  -  - ]:          19 :         att.filename.isEmpty() ? tr("(unnamed)") : att.filename;
      29   [ +  -  +  -  :          38 :     label += QStringLiteral(" (%1)").arg(formatSize(att.size));
                   +  - ]
      30         [ +  - ]:          19 :     btn->setText(label);
      31         [ +  - ]:          19 :     btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
      32   [ +  -  +  - ]:          38 :     btn->setIcon(QIcon::fromTheme(QStringLiteral("mail-attachment")));
      33         [ +  - ]:          19 :     btn->setAutoRaise(true);
      34                 :             : 
      35                 :          19 :     const qint64 id = att.id;
      36                 :          19 :     const QString filename = att.filename;
      37                 :             :     // T-71.6: PDFs open inline on click; everything else downloads.
      38                 :             :     // Detection covers both a proper MIME type and a .pdf extension so
      39                 :             :     // mis-typed attachments still work.
      40                 :             :     const bool isPdf =
      41   [ +  -  -  -  :          38 :         att.contentType.startsWith(QStringLiteral("application/pdf"),
                   -  - ]
      42   [ +  -  +  + ]:          51 :                                    Qt::CaseInsensitive) ||
      43   [ +  -  +  +  :          45 :         att.filename.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive);
          +  +  +  +  +  
             -  -  -  -  
                      - ]
      44                 :             : 
      45         [ +  + ]:          19 :     if (isPdf) {
      46                 :           8 :       connect(btn, &QToolButton::clicked, this,
      47         [ +  - ]:          19 :               [this, id]() { emit viewInlineRequested(id); });
      48                 :             :     } else {
      49         [ +  - ]:          11 :       connect(btn, &QToolButton::clicked, this,
      50                 :          22 :               [this, id, filename]() {
      51                 :           1 :                 emit downloadRequested(id, filename);
      52                 :           1 :               });
      53                 :             :     }
      54                 :             : 
      55         [ +  - ]:          19 :     m_layout->addWidget(btn);
      56                 :             : 
      57                 :             :     // T-71.6: PDF chips get a small dedicated download button so users can
      58                 :             :     // still save the file (the main chip now opens the inline viewer).
      59         [ +  + ]:          19 :     if (isPdf) {
      60   [ +  -  +  -  :           8 :       auto *dlBtn = new QToolButton(this);
             -  +  -  - ]
      61   [ +  -  +  - ]:           8 :       dlBtn->setText(tr("Save"));
      62   [ +  -  +  -  :          16 :       dlBtn->setToolTip(tr("Save %1 to disk").arg(
                   +  - ]
      63   [ -  +  -  - ]:          16 :           (att.filename.isEmpty() ? tr("attachment") : att.filename)
      64         [ +  - ]:          24 :               .toHtmlEscaped()));
      65         [ +  - ]:           8 :       dlBtn->setAutoRaise(true);
      66         [ +  - ]:           8 :       connect(dlBtn, &QToolButton::clicked, this,
      67                 :          16 :               [this, id, filename]() {
      68                 :           0 :                 emit downloadRequested(id, filename);
      69                 :           0 :               });
      70         [ +  - ]:           8 :       m_layout->addWidget(dlBtn);
      71                 :             :     }
      72                 :          19 :   }
      73                 :             : 
      74                 :             :   // "Save All" button if multiple attachments
      75         [ +  + ]:          14 :   if (attachments.size() > 1) {
      76                 :           3 :     m_layout->addStretch();
      77   [ +  -  -  +  :           3 :     auto *allBtn = new QToolButton(this);
                   -  - ]
      78   [ +  -  +  - ]:           3 :     allBtn->setText(tr("Save All"));
      79   [ +  -  +  - ]:           6 :     allBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
      80                 :           3 :     allBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
      81                 :           3 :     allBtn->setAutoRaise(true);
      82                 :           3 :     connect(allBtn, &QToolButton::clicked, this,
      83         [ +  - ]:           3 :             &AttachmentBar::downloadAllRequested);
      84         [ +  - ]:           3 :     m_layout->addWidget(allBtn);
      85                 :             :   } else {
      86                 :          11 :     m_layout->addStretch();
      87                 :             :   }
      88                 :             : 
      89                 :          14 :   setVisible(true);
      90                 :             : }
      91                 :             : 
      92                 :         122 : void AttachmentBar::clear() {
      93                 :         122 :   m_attachments.clear();
      94                 :             : 
      95                 :             :   // Remove all widgets from layout
      96                 :             :   QLayoutItem *item;
      97         [ +  + ]:         150 :   while ((item = m_layout->takeAt(0)) != nullptr) {
      98         [ +  + ]:          28 :     delete item->widget();
      99         [ +  - ]:          28 :     delete item;
     100                 :             :   }
     101                 :         122 :   setVisible(false);
     102                 :         122 : }
     103                 :             : 
     104                 :          19 : QString AttachmentBar::formatSize(qint64 bytes) {
     105         [ +  + ]:          19 :   if (bytes < 1024) {
     106   [ +  -  +  - ]:          22 :     return QString("%1 B").arg(bytes);
     107                 :             :   }
     108         [ +  + ]:           8 :   if (bytes < 1024 * 1024) {
     109   [ +  -  +  - ]:          12 :     return QString("%1 KB").arg(bytes / 1024.0, 0, 'f', 1);
     110                 :             :   }
     111         [ +  - ]:           2 :   if (bytes < 1024LL * 1024 * 1024) {
     112   [ +  -  +  - ]:           4 :     return QString("%1 MB").arg(bytes / (1024.0 * 1024.0), 0, 'f', 1);
     113                 :             :   }
     114   [ #  #  #  # ]:           0 :   return QString("%1 GB").arg(bytes / (1024.0 * 1024.0 * 1024.0), 0, 'f', 1);
     115                 :             : }
     116                 :             : 
     117                 :             : // T-76.B3: Runtime language switching.
     118                 :             : // The chips are built dynamically in setAttachments() (one Save button per
     119                 :             : // PDF, a Save All button, "(unnamed)" fallback), so re-applying tr() means
     120                 :             : // rebuilding. A local copy is made first — setAttachments() takes a const
     121                 :             : // ref and clear() empties m_attachments, which would otherwise dangle it.
     122                 :        1578 : void AttachmentBar::changeEvent(QEvent *event) {
     123         [ +  + ]:        1578 :   if (event->type() == QEvent::LanguageChange)
     124                 :           8 :     retranslateUi();
     125                 :        1578 :   QFrame::changeEvent(event);
     126                 :        1578 : }
     127                 :             : 
     128                 :           8 : void AttachmentBar::retranslateUi() {
     129                 :             :   // T-76.B3: setAttachments() rebuilds the widget tree (deletes/creates child
     130                 :             :   // widgets). Calling it from within changeEvent can re-enter if a child
     131                 :             :   // removal posts further events; guard against that.
     132   [ +  -  +  -  :           8 :   if (m_retranslating || m_attachments.isEmpty())
                   +  - ]
     133                 :           8 :     return;
     134                 :           0 :   m_retranslating = true;
     135                 :           0 :   QList<Attachment> copy = m_attachments;
     136         [ #  # ]:           0 :   setAttachments(copy);
     137                 :           0 :   m_retranslating = false;
     138                 :           0 : }
        

Generated by: LCOV version 2.0-1