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 : }
|