MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - ContactCompleter.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 94.9 % 118 112
Test Date: 2026-06-21 21:10:19 Functions: 100.0 % 11 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 56.7 % 187 106

             Branch data     Line data    Source code
       1                 :             : #include "ContactCompleter.h"
       2                 :             : 
       3                 :             : #include <QEvent>
       4                 :             : #include <QKeyEvent>
       5                 :             : #include <QLineEdit>
       6                 :             : #include <QListWidget>
       7                 :             : #include <QApplication>
       8                 :             : 
       9                 :             : #include "data/ContactStore.h"
      10                 :             : 
      11                 :          69 : ContactCompleter::ContactCompleter(ContactStore *store, QLineEdit *target,
      12                 :          69 :                                    QObject *parent)
      13                 :          69 :     : QObject(parent), m_store(store), m_target(target) {
      14   [ +  -  +  -  :          69 :   m_popup = new QListWidget();
             -  +  -  - ]
      15         [ +  - ]:          69 :   m_popup->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
      16         [ +  - ]:          69 :   m_popup->setFocusPolicy(Qt::NoFocus);
      17         [ +  - ]:          69 :   m_popup->setMaximumHeight(220);
      18         [ +  - ]:          69 :   m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
      19                 :             :   // Styled via main.qss (QListWidget#completerPopup, 67.B3)
      20         [ +  - ]:         138 :   m_popup->setObjectName(QStringLiteral("completerPopup"));
      21         [ +  - ]:          69 :   m_popup->hide();
      22                 :             : 
      23                 :          69 :   connect(m_target, &QLineEdit::textChanged, this,
      24         [ +  - ]:          69 :           &ContactCompleter::onTextChanged);
      25                 :          69 :   connect(m_popup, &QListWidget::itemActivated, this,
      26         [ +  - ]:          69 :           &ContactCompleter::onItemActivated);
      27                 :             : 
      28         [ +  - ]:          69 :   m_target->installEventFilter(this);
      29                 :          69 : }
      30                 :             : 
      31                 :         132 : ContactCompleter::~ContactCompleter() {
      32         [ +  - ]:          69 :   delete m_popup;
      33                 :         132 : }
      34                 :             : 
      35                 :        1018 : bool ContactCompleter::eventFilter(QObject *obj, QEvent *event) {
      36   [ +  -  +  +  :        1018 :   if (obj != m_target || !m_popup->isVisible())
                   +  + ]
      37                 :         942 :     return QObject::eventFilter(obj, event);
      38                 :             : 
      39         [ +  + ]:          76 :   if (event->type() == QEvent::KeyPress) {
      40                 :           9 :     auto *ke = static_cast<QKeyEvent *>(event);
      41   [ +  +  +  +  :           9 :     switch (ke->key()) {
                      + ]
      42                 :           2 :     case Qt::Key_Down:
      43                 :           2 :       navigatePopup(1);
      44                 :           2 :       return true;
      45                 :           1 :     case Qt::Key_Up:
      46                 :           1 :       navigatePopup(-1);
      47                 :           1 :       return true;
      48                 :           1 :     case Qt::Key_Return:
      49                 :             :     case Qt::Key_Enter:
      50                 :             :     case Qt::Key_Tab:
      51         [ +  - ]:           1 :       if (m_popup->currentItem()) {
      52                 :           1 :         onItemActivated(m_popup->currentItem());
      53                 :           1 :         return true;
      54                 :             :       }
      55                 :           0 :       break;
      56                 :           1 :     case Qt::Key_Escape:
      57                 :           1 :       hidePopup();
      58                 :           1 :       return true;
      59                 :           4 :     default:
      60                 :           4 :       break;
      61                 :             :     }
      62                 :             :   }
      63                 :             : 
      64                 :          71 :   return QObject::eventFilter(obj, event);
      65                 :             : }
      66                 :             : 
      67                 :          30 : QString ContactCompleter::currentToken() const {
      68         [ +  - ]:          30 :   QString text = m_target->text();
      69         [ +  - ]:          30 :   int cursorPos = m_target->cursorPosition();
      70                 :             : 
      71                 :             :   // Find the start of the current token (after last comma/semicolon)
      72                 :          30 :   int start = 0;
      73         [ +  + ]:         202 :   for (int i = cursorPos - 1; i >= 0; --i) {
      74   [ +  -  +  +  :         179 :     if (text[i] == ',' || text[i] == ';') {
          +  -  -  +  +  
                      + ]
      75                 :           7 :       start = i + 1;
      76                 :           7 :       break;
      77                 :             :     }
      78                 :             :   }
      79                 :             : 
      80                 :             :   // Skip leading whitespace
      81   [ +  +  +  -  :          36 :   while (start < cursorPos && text[start].isSpace())
             +  +  +  + ]
      82                 :           6 :     ++start;
      83                 :             : 
      84                 :          30 :   m_tokenStart = start;
      85   [ +  -  +  - ]:          60 :   return text.mid(start, cursorPos - start).trimmed();
      86                 :          30 : }
      87                 :             : 
      88                 :          28 : void ContactCompleter::onTextChanged(const QString &) {
      89         [ +  - ]:          28 :   QString token = currentToken();
      90         [ +  + ]:          28 :   if (token.length() < 2) {
      91         [ +  - ]:          10 :     hidePopup();
      92                 :          10 :     return;
      93                 :             :   }
      94         [ +  - ]:          18 :   updateSuggestions(token);
      95         [ +  + ]:          28 : }
      96                 :             : 
      97                 :          18 : void ContactCompleter::updateSuggestions(const QString &query) {
      98   [ +  -  +  -  :          18 :   if (!m_store || !m_store->isOpen()) {
             -  +  -  + ]
      99         [ #  # ]:           0 :     hidePopup();
     100                 :           8 :     return;
     101                 :             :   }
     102                 :             : 
     103         [ +  - ]:          18 :   QList<Contact> results = m_store->search(query, 8);
     104         [ +  + ]:          18 :   if (results.isEmpty()) {
     105         [ +  - ]:           8 :     hidePopup();
     106                 :           8 :     return;
     107                 :             :   }
     108                 :             : 
     109         [ +  - ]:          10 :   m_popup->clear();
     110   [ +  -  +  -  :          23 :   for (const auto &c : results) {
                   +  + ]
     111                 :          13 :     QString display;
     112   [ +  -  +  -  :          13 :     if (!c.displayName.isEmpty() && c.displayName != c.email) {
                   +  - ]
     113         [ +  - ]:          13 :       display = QStringLiteral("%1 <%2>").arg(c.displayName, c.email);
     114                 :             :     } else {
     115                 :           0 :       display = c.email;
     116                 :             :     }
     117   [ +  -  +  -  :          13 :     auto *item = new QListWidgetItem(display, m_popup);
             -  +  -  - ]
     118         [ +  - ]:          13 :     item->setData(Qt::UserRole, display);
     119                 :          13 :   }
     120                 :             : 
     121         [ +  - ]:          10 :   showPopup();
     122         [ +  + ]:          18 : }
     123                 :             : 
     124                 :          10 : void ContactCompleter::showPopup() {
     125   [ +  -  -  + ]:          10 :   if (m_popup->count() == 0) {
     126         [ #  # ]:           0 :     hidePopup();
     127                 :           0 :     return;
     128                 :             :   }
     129                 :             : 
     130                 :             :   // Position below the line edit
     131         [ +  - ]:          10 :   QPoint pos = m_target->mapToGlobal(
     132                 :          10 :       QPoint(0, m_target->height()));
     133         [ +  - ]:          10 :   m_popup->setFixedWidth(m_target->width());
     134         [ +  - ]:          10 :   m_popup->move(pos);
     135         [ +  - ]:          10 :   m_popup->setCurrentRow(0);
     136         [ +  - ]:          10 :   m_popup->show();
     137                 :             : }
     138                 :             : 
     139                 :          21 : void ContactCompleter::hidePopup() {
     140                 :          21 :   m_popup->hide();
     141                 :          21 : }
     142                 :             : 
     143                 :           2 : void ContactCompleter::onItemActivated(QListWidgetItem *item) {
     144         [ -  + ]:           2 :   if (!item)
     145                 :           0 :     return;
     146   [ +  -  +  -  :           2 :   acceptSuggestion(item->data(Qt::UserRole).toString());
                   +  - ]
     147                 :             : }
     148                 :             : 
     149                 :           2 : void ContactCompleter::acceptSuggestion(const QString &formatted) {
     150         [ +  - ]:           2 :   QString text = m_target->text();
     151         [ +  - ]:           2 :   int cursorPos = m_target->cursorPosition();
     152                 :             : 
     153                 :             :   // Find current token boundaries
     154         [ +  - ]:           2 :   currentToken(); // updates m_tokenStart
     155                 :             : 
     156                 :             :   // Replace from token start to cursor with the formatted contact
     157         [ +  - ]:           2 :   QString before = text.left(m_tokenStart);
     158         [ +  - ]:           2 :   QString after = text.mid(cursorPos);
     159                 :             : 
     160                 :             :   // Add comma+space after the inserted address if there's more text
     161                 :           2 :   QString separator;
     162   [ +  -  +  - ]:           2 :   if (after.trimmed().isEmpty()) {
     163                 :           2 :     separator = QStringLiteral(", ");
     164                 :             :   }
     165                 :             : 
     166   [ +  -  +  -  :           2 :   QString newText = before + formatted + separator + after.trimmed();
             +  -  +  - ]
     167         [ +  - ]:           2 :   m_target->setText(newText);
     168         [ +  - ]:           4 :   m_target->setCursorPosition(before.length() + formatted.length() +
     169                 :           2 :                               separator.length());
     170                 :             : 
     171         [ +  - ]:           2 :   hidePopup();
     172                 :           2 : }
     173                 :             : 
     174                 :           3 : void ContactCompleter::navigatePopup(int delta) {
     175         [ +  - ]:           3 :   int row = m_popup->currentRow() + delta;
     176   [ +  -  +  - ]:           3 :   row = qBound(0, row, m_popup->count() - 1);
     177         [ +  - ]:           3 :   m_popup->setCurrentRow(row);
     178                 :           3 : }
        

Generated by: LCOV version 2.0-1