MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - ui - ContactManagerDialog.cpp (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 91.6 % 166 152
Test Date: 2026-06-21 21:10:19 Functions: 92.3 % 13 12
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 47.8 % 372 178

             Branch data     Line data    Source code
       1                 :             : #include "ContactManagerDialog.h"
       2                 :             : 
       3                 :             : #include <QHBoxLayout>
       4                 :             : #include <QHeaderView>
       5                 :             : #include <QInputDialog>
       6                 :             : #include <QLabel>
       7                 :             : #include <QLineEdit>
       8                 :             : #include <QMenu>
       9                 :             : #include <QMessageBox>
      10                 :             : #include <QPushButton>
      11                 :             : #include <QSortFilterProxyModel>
      12                 :             : #include <QStandardItemModel>
      13                 :             : #include <QTableView>
      14                 :             : #include <QVBoxLayout>
      15                 :             : 
      16                 :             : #include "data/ContactStore.h"
      17                 :             : #include <QEvent>
      18                 :             : 
      19                 :          10 : ContactManagerDialog::ContactManagerDialog(ContactStore *store,
      20                 :          10 :                                            QWidget *parent)
      21         [ +  - ]:          10 :     : QDialog(parent), m_store(store) {
      22                 :             :   // Modal test seams: real dialogs by default (unit tests override).
      23                 :           0 :   m_promptText = [this](const QString &title, const QString &label,
      24                 :             :                         const QString &initial, bool *ok) {
      25                 :           0 :     return QInputDialog::getText(this, title, label, QLineEdit::Normal,
      26         [ #  # ]:           0 :                                  initial, ok);
      27                 :          10 :   };
      28                 :          20 :   m_confirm = [this](const QString &title, const QString &text) {
      29                 :           0 :     return QMessageBox::question(this, title, text,
      30                 :             :                                  QMessageBox::Yes | QMessageBox::No,
      31                 :           0 :                                  QMessageBox::No) == QMessageBox::Yes;
      32                 :          10 :   };
      33                 :             : 
      34         [ +  - ]:          10 :   setupUi();
      35         [ +  - ]:          10 :   reloadContacts();
      36                 :          10 : }
      37                 :             : 
      38                 :          10 : void ContactManagerDialog::setupUi() {
      39   [ +  -  +  - ]:          10 :   setWindowTitle(tr("Manage Contacts"));
      40                 :          10 :   setMinimumSize(650, 450);
      41                 :          10 :   resize(750, 550);
      42                 :             : 
      43   [ +  -  -  +  :          10 :   auto *mainLayout = new QVBoxLayout(this);
                   -  - ]
      44                 :          10 :   mainLayout->setContentsMargins(12, 12, 12, 12);
      45                 :          10 :   mainLayout->setSpacing(8);
      46                 :             : 
      47                 :             :   // --- Top bar: search + add ---
      48   [ +  -  -  +  :          10 :   auto *topBar = new QHBoxLayout();
                   -  - ]
      49                 :             : 
      50   [ +  -  -  +  :          10 :   m_searchEdit = new QLineEdit(this);
                   -  - ]
      51   [ +  -  +  - ]:          10 :   m_searchEdit->setPlaceholderText(tr("Search contacts…"));
      52                 :          10 :   m_searchEdit->setClearButtonEnabled(true);
      53                 :             :   // Styled via main.qss generic QLineEdit rules (67.B3)
      54         [ +  - ]:          10 :   topBar->addWidget(m_searchEdit, 1);
      55                 :             : 
      56   [ +  -  +  -  :          10 :   auto *addBtn = new QPushButton(tr("+ New Contact"), this);
             -  +  -  - ]
      57                 :             :   // Accent-colored primary action (main.qss QPushButton#primaryButton)
      58         [ +  - ]:          20 :   addBtn->setObjectName(QStringLiteral("primaryButton"));
      59                 :          10 :   connect(addBtn, &QPushButton::clicked, this,
      60         [ +  - ]:          10 :           &ContactManagerDialog::onAddContact);
      61         [ +  - ]:          10 :   topBar->addWidget(addBtn);
      62                 :             : 
      63                 :          10 :   mainLayout->addLayout(topBar);
      64                 :             : 
      65                 :             :   // --- Table ---
      66   [ +  -  -  +  :          10 :   m_model = new QStandardItemModel(0, 5, this);
                   -  - ]
      67   [ +  -  +  +  :          60 :   m_model->setHorizontalHeaderLabels(
                   -  - ]
      68                 :             :       {tr("Name"), tr("Email"), tr("Source"), tr("Last Used"),
      69                 :             :        tr("Count")});
      70                 :             : 
      71   [ +  -  -  +  :          10 :   m_proxy = new QSortFilterProxyModel(this);
                   -  - ]
      72                 :          10 :   m_proxy->setSourceModel(m_model);
      73                 :          10 :   m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
      74                 :          10 :   m_proxy->setFilterKeyColumn(-1); // Filter all columns
      75                 :             : 
      76   [ +  -  -  +  :          10 :   m_table = new QTableView(this);
                   -  - ]
      77                 :          10 :   m_table->setModel(m_proxy);
      78                 :          10 :   m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
      79                 :          10 :   m_table->setSelectionMode(QAbstractItemView::SingleSelection);
      80         [ +  - ]:          10 :   m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
      81                 :          10 :   m_table->setSortingEnabled(true);
      82                 :          10 :   m_table->setAlternatingRowColors(true);
      83                 :          10 :   m_table->setShowGrid(false);
      84                 :          10 :   m_table->verticalHeader()->hide();
      85                 :          10 :   m_table->horizontalHeader()->setStretchLastSection(true);
      86                 :             :   // Styled via main.qss generic QTableView rules (67.B3)
      87                 :             : 
      88                 :             :   // Context menu
      89                 :          10 :   m_table->setContextMenuPolicy(Qt::CustomContextMenu);
      90                 :          10 :   connect(m_table, &QTableView::customContextMenuRequested, this,
      91         [ +  - ]:          10 :           [this](const QPoint &pos) {
      92   [ +  -  -  + ]:           1 :             if (selectedContactId() < 0)
      93                 :           0 :               return;
      94         [ +  - ]:           1 :             QMenu menu(this);
      95         [ +  - ]:           1 :             menu.addAction(tr("Edit…"), this,
      96         [ +  - ]:           1 :                            &ContactManagerDialog::onEditContact);
      97         [ +  - ]:           1 :             menu.addAction(tr("Delete"), this,
      98         [ +  - ]:           1 :                            &ContactManagerDialog::onDeleteContact);
      99         [ +  - ]:           1 :             menu.addSeparator();
     100   [ +  -  +  - ]:           1 :             menu.addAction(tr("Email this contact"), this, [this]() {
     101         [ +  - ]:           1 :               int row = m_proxy->mapToSource(
     102         [ +  - ]:           2 :                             m_table->currentIndex()).row();
     103         [ -  + ]:           1 :               if (row < 0) return;
     104   [ +  -  +  - ]:           1 :               QString email = m_model->item(row, 1)->text();
     105   [ +  -  +  - ]:           1 :               QString name = m_model->item(row, 0)->text();
     106         [ +  - ]:           1 :               emit composeToContact(email, name);
     107                 :           1 :             });
     108   [ +  -  +  -  :           1 :             menu.exec(m_table->viewport()->mapToGlobal(pos));
                   +  - ]
     109                 :           1 :           });
     110                 :             : 
     111         [ +  - ]:          10 :   mainLayout->addWidget(m_table, 1);
     112                 :             : 
     113                 :             :   // --- Bottom bar ---
     114   [ +  -  -  +  :          10 :   auto *bottomBar = new QHBoxLayout();
                   -  - ]
     115                 :          10 :   bottomBar->addStretch();
     116   [ +  -  +  -  :          10 :   auto *closeBtn = new QPushButton(tr("Close"), this);
             -  +  -  - ]
     117         [ +  - ]:          10 :   connect(closeBtn, &QPushButton::clicked, this, &QDialog::accept);
     118         [ +  - ]:          10 :   bottomBar->addWidget(closeBtn);
     119                 :          10 :   mainLayout->addLayout(bottomBar);
     120                 :             : 
     121                 :             :   // Filter
     122                 :          10 :   connect(m_searchEdit, &QLineEdit::textChanged, m_proxy,
     123         [ +  - ]:          10 :           &QSortFilterProxyModel::setFilterFixedString);
     124   [ +  -  +  -  :          20 : }
          +  -  +  -  +  
          -  +  -  -  -  
                   -  - ]
     125                 :             : 
     126                 :          18 : void ContactManagerDialog::reloadContacts() {
     127   [ +  -  +  - ]:          18 :   m_model->removeRows(0, m_model->rowCount());
     128         [ -  + ]:          18 :   if (!m_store)
     129                 :           0 :     return;
     130                 :             : 
     131         [ +  - ]:          18 :   QList<Contact> contacts = m_store->allContacts();
     132   [ +  -  +  -  :          37 :   for (const auto &c : contacts) {
                   +  + ]
     133                 :          19 :     QList<QStandardItem *> row;
     134                 :             : 
     135   [ +  -  +  -  :          19 :     auto *nameItem = new QStandardItem(c.displayName);
             -  +  -  - ]
     136         [ +  - ]:          19 :     nameItem->setData(c.id, Qt::UserRole); // Store ID for operations
     137         [ +  - ]:          19 :     row.append(nameItem);
     138                 :             : 
     139   [ +  -  +  -  :          19 :     row.append(new QStandardItem(c.email));
          +  -  -  +  -  
                      - ]
     140                 :             : 
     141                 :             :     // Source badge
     142                 :             :     auto *sourceItem = new QStandardItem(
     143   [ +  -  +  +  :          38 :         c.source == QStringLiteral("carddav") ? tr("CardDAV") : tr("Local"));
          +  -  +  -  +  
             -  -  +  -  
                      - ]
     144         [ +  - ]:          19 :     row.append(sourceItem);
     145                 :             : 
     146                 :             :     // Last used
     147                 :          19 :     QString lastUsed;
     148   [ +  -  +  + ]:          19 :     if (c.lastUsed.isValid()) {
     149         [ +  - ]:          12 :       lastUsed = c.lastUsed.toString(QStringLiteral("dd.MM.yyyy HH:mm"));
     150                 :             :     }
     151   [ +  -  +  -  :          19 :     row.append(new QStandardItem(lastUsed));
          +  -  -  +  -  
                      - ]
     152                 :             : 
     153                 :             :     // Use count
     154   [ +  -  +  -  :          19 :     auto *countItem = new QStandardItem();
             -  +  -  - ]
     155         [ +  - ]:          19 :     countItem->setData(c.useCount, Qt::DisplayRole);
     156         [ +  - ]:          19 :     row.append(countItem);
     157                 :             : 
     158         [ +  - ]:          19 :     m_model->appendRow(row);
     159                 :          19 :   }
     160                 :             : 
     161                 :             :   // Set column widths
     162         [ +  - ]:          18 :   m_table->setColumnWidth(0, 180);
     163         [ +  - ]:          18 :   m_table->setColumnWidth(1, 220);
     164         [ +  - ]:          18 :   m_table->setColumnWidth(2, 70);
     165         [ +  - ]:          18 :   m_table->setColumnWidth(3, 140);
     166                 :          18 : }
     167                 :             : 
     168                 :           9 : int ContactManagerDialog::selectedContactId() const {
     169         [ +  - ]:           9 :   QModelIndex idx = m_table->currentIndex();
     170         [ +  + ]:           9 :   if (!idx.isValid())
     171                 :           2 :     return -1;
     172         [ +  - ]:           7 :   QModelIndex sourceIdx = m_proxy->mapToSource(idx);
     173         [ +  - ]:           7 :   auto *item = m_model->item(sourceIdx.row(), 0);
     174   [ +  -  +  -  :           7 :   return item ? item->data(Qt::UserRole).toInt() : -1;
          +  -  +  -  -  
                      - ]
     175                 :             : }
     176                 :             : 
     177                 :           2 : void ContactManagerDialog::onAddContact() {
     178                 :           2 :   bool ok = false;
     179                 :             :   QString name =
     180   [ +  -  +  -  :           2 :       m_promptText(tr("New Contact"), tr("Name:"), QString(), &ok);
                   +  - ]
     181   [ +  +  +  -  :           2 :   if (!ok || name.trimmed().isEmpty())
          -  +  +  +  +  
                +  -  - ]
     182                 :           1 :     return;
     183                 :             : 
     184                 :             :   QString email =
     185   [ +  -  +  -  :           1 :       m_promptText(tr("New Contact"), tr("Email Address:"), QString(), &ok);
                   +  - ]
     186   [ +  -  +  -  :           1 :   if (!ok || email.trimmed().isEmpty())
          -  +  +  -  -  
                +  -  - ]
     187                 :           0 :     return;
     188                 :             : 
     189                 :           1 :   Contact c;
     190         [ +  - ]:           1 :   c.displayName = name.trimmed();
     191   [ +  -  +  - ]:           1 :   c.email = email.trimmed().toLower();
     192                 :           1 :   c.source = QStringLiteral("local");
     193         [ +  - ]:           1 :   m_store->addContact(c);
     194         [ +  - ]:           1 :   reloadContacts();
     195   [ +  -  +  + ]:           2 : }
     196                 :             : 
     197                 :           4 : void ContactManagerDialog::onDeleteContact() {
     198                 :           4 :   int id = selectedContactId();
     199         [ +  + ]:           4 :   if (id < 0)
     200                 :           1 :     return;
     201                 :             : 
     202   [ +  -  +  - ]:           3 :   if (!m_confirm(tr("Delete Contact"),
     203   [ +  -  +  + ]:           6 :                  tr("Do you really want to delete this contact?")))
     204                 :           2 :     return;
     205                 :             : 
     206                 :           1 :   m_store->removeContact(id);
     207                 :           1 :   reloadContacts();
     208                 :             : }
     209                 :             : 
     210                 :           3 : void ContactManagerDialog::onEditContact() {
     211         [ +  - ]:           3 :   int id = selectedContactId();
     212         [ +  + ]:           3 :   if (id < 0)
     213                 :           2 :     return;
     214                 :             : 
     215         [ +  - ]:           2 :   auto contact = m_store->contactById(id);
     216         [ -  + ]:           2 :   if (!contact)
     217                 :           0 :     return;
     218                 :             : 
     219                 :           2 :   bool ok = false;
     220         [ +  - ]:           4 :   QString name = m_promptText(tr("Edit Contact"), tr("Name:"),
     221   [ +  -  +  - ]:           4 :                               contact->displayName, &ok);
     222         [ +  + ]:           2 :   if (!ok)
     223                 :           1 :     return;
     224                 :             : 
     225         [ +  - ]:           2 :   QString email = m_promptText(tr("Edit Contact"), tr("Email Address:"),
     226   [ +  -  +  - ]:           2 :                                contact->email, &ok);
     227         [ -  + ]:           1 :   if (!ok)
     228                 :           0 :     return;
     229                 :             : 
     230         [ +  - ]:           1 :   contact->displayName = name.trimmed();
     231   [ +  -  +  - ]:           1 :   contact->email = email.trimmed().toLower();
     232         [ +  - ]:           1 :   m_store->updateContact(*contact);
     233         [ +  - ]:           1 :   reloadContacts();
     234   [ +  -  +  +  :           3 : }
                   +  + ]
     235                 :             : 
     236                 :             : // T-304: Runtime language switching
     237                 :          30 : void ContactManagerDialog::changeEvent(QEvent *event) {
     238         [ -  + ]:          30 :   if (event->type() == QEvent::LanguageChange)
     239                 :           0 :     retranslateUi();
     240                 :          30 :   QDialog::changeEvent(event);
     241                 :          30 : }
     242                 :             : 
     243                 :           0 : void ContactManagerDialog::retranslateUi() {
     244   [ #  #  #  # ]:           0 :   setWindowTitle(tr("Manage Contacts"));
     245                 :           0 : }
        

Generated by: LCOV version 2.0-1