• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24292264395

11 Apr 2026 09:43PM UTC coverage: 20.927%. Remained the same
24292264395

push

github

web-flow
fix: pass dir by const ref in UsersDialog (#976)

* fix: replace static variables with member variables in populateList

* refactor: move docstrings to header, add connectSignals declaration

* fix: pass dir by const ref, remove unused Q_DECLARE_METATYPE

0 of 2 new or added lines in 1 file covered. (0.0%)

1111 of 5309 relevant lines covered (20.93%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/usersdialog.cpp
1
// SPDX-FileCopyrightText: 2015 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "usersdialog.h"
4
#include "qtpasssettings.h"
5
#include "ui_usersdialog.h"
6
#include <QApplication>
7
#include <QCloseEvent>
8
#include <QKeyEvent>
9
#include <QMessageBox>
10
#include <QRegularExpression>
11
#include <QSet>
12
#include <QWidget>
13
#include <utility>
14

15
#ifdef QT_DEBUG
16
#include "debughelper.h"
17
#endif
18
/**
19
 * @brief UsersDialog::UsersDialog basic constructor
20
 * @param dir Password directory
21
 * @param parent
22
 */
NEW
23
UsersDialog::UsersDialog(const QString &dir, QWidget *parent)
×
NEW
24
    : QDialog(parent), ui(new Ui::UsersDialog), m_dir(dir) {
×
25

26
  ui->setupUi(this);
×
27

28
  restoreDialogState();
×
29
  if (!loadGpgKeys()) {
×
30
    connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
31
    return;
×
32
  }
33

34
  loadRecipients();
×
35
  populateList();
×
36

37
  connectSignals();
×
38
}
×
39

40
void UsersDialog::connectSignals() {
×
41
  connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
×
42
          &UsersDialog::accept);
×
43
  connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
44
  connect(ui->listWidget, &QListWidget::itemChanged, this,
×
45
          &UsersDialog::itemChange);
×
46

47
  ui->lineEdit->setClearButtonEnabled(true);
×
48
}
×
49

50
/**
51
 * @brief Restore dialog geometry from settings.
52
 */
53
void UsersDialog::restoreDialogState() {
×
54
  QByteArray savedGeometry = QtPassSettings::getDialogGeometry("usersDialog");
×
55
  bool hasSavedGeometry = !savedGeometry.isEmpty();
56
  if (hasSavedGeometry) {
×
57
    restoreGeometry(savedGeometry);
×
58
  }
59
  if (QtPassSettings::isDialogMaximized("usersDialog")) {
×
60
    showMaximized();
×
61
  } else if (hasSavedGeometry) {
×
62
    move(QtPassSettings::getDialogPos("usersDialog"));
×
63
    resize(QtPassSettings::getDialogSize("usersDialog"));
×
64
  }
65
}
×
66

67
auto UsersDialog::loadGpgKeys() -> bool {
×
68
  QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
×
69
  if (users.isEmpty()) {
×
70
    QMessageBox::critical(parentWidget(), tr("Keylist missing"),
×
71
                          tr("Could not fetch list of available GPG keys"));
×
72
    reject();
×
73
    return false;
74
  }
75

76
  markSecretKeys(users);
×
77

78
  m_userList = users;
79
  return true;
×
80
}
81

82
void UsersDialog::markSecretKeys(QList<UserInfo> &users) {
×
83
  QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
×
84
  QSet<QString> secretKeyIds;
85
  for (const UserInfo &sec : secret_keys) {
×
86
    secretKeyIds.insert(sec.key_id);
×
87
  }
88
  for (auto &user : users) {
×
89
    if (secretKeyIds.contains(user.key_id)) {
×
90
      user.have_secret = true;
×
91
    }
92
  }
93
}
×
94

95
void UsersDialog::loadRecipients() {
×
96
  int count = 0;
×
97
  QStringList recipients = QtPassSettings::getPass()->getRecipientString(
×
98
      m_dir.isEmpty() ? "" : m_dir, " ", &count);
×
99

100
  QList<UserInfo> selectedUsers =
101
      QtPassSettings::getPass()->listKeys(recipients);
×
102
  QSet<QString> selectedKeyIds;
103
  for (const UserInfo &sel : selectedUsers) {
×
104
    selectedKeyIds.insert(sel.key_id);
×
105
  }
106
  for (auto &user : m_userList) {
×
107
    if (selectedKeyIds.contains(user.key_id)) {
×
108
      user.enabled = true;
×
109
    }
110
  }
111

112
  if (count > selectedUsers.size()) {
×
113
    QStringList allRecipients = QtPassSettings::getPass()->getRecipientList(
×
114
        m_dir.isEmpty() ? "" : m_dir);
×
115
    QSet<QString> missingKeyRecipients;
116
    for (const QString &recipient : allRecipients) {
×
117
      if (!missingKeyRecipients.contains(recipient) &&
×
118
          QtPassSettings::getPass()->listKeys(recipient).empty()) {
×
119
        missingKeyRecipients.insert(recipient);
×
120
        UserInfo i;
×
121
        i.enabled = true;
×
122
        i.key_id = recipient;
×
123
        i.name = " ?? " + tr("Key not found in keyring");
×
124
        m_userList.append(i);
125
      }
×
126
    }
127
  }
128
}
×
129

130
/**
131
 * @brief UsersDialog::~UsersDialog basic destructor.
132
 */
133
UsersDialog::~UsersDialog() { delete ui; }
×
134

135
/**
136
 * @brief UsersDialog::accept
137
 */
138
void UsersDialog::accept() {
×
139
  QtPassSettings::getPass()->Init(m_dir, m_userList);
×
140

141
  QDialog::accept();
×
142
}
×
143

144
/**
145
 * @brief UsersDialog::closeEvent save window state on close.
146
 * @param event
147
 */
148
void UsersDialog::closeEvent(QCloseEvent *event) {
×
149
  QtPassSettings::setDialogGeometry("usersDialog", saveGeometry());
×
150
  if (!isMaximized()) {
×
151
    QtPassSettings::setDialogPos("usersDialog", pos());
×
152
    QtPassSettings::setDialogSize("usersDialog", size());
×
153
  }
154
  QtPassSettings::setDialogMaximized("usersDialog", isMaximized());
×
155
  event->accept();
156
}
×
157

158
/**
159
 * @brief UsersDialog::keyPressEvent clear the lineEdit when escape is pressed.
160
 * No action for Enter currently.
161
 * @param event
162
 */
163
void UsersDialog::keyPressEvent(QKeyEvent *event) {
×
164
  switch (event->key()) {
×
165
  case Qt::Key_Escape:
×
166
    ui->lineEdit->clear();
×
167
    break;
×
168
  default:
169
    break;
170
  }
171
}
×
172

173
/**
174
 * @brief UsersDialog::itemChange update the item information.
175
 * @param item
176
 */
177
void UsersDialog::itemChange(QListWidgetItem *item) {
×
178
  if (!item) {
×
179
    return;
×
180
  }
181
  bool ok = false;
×
182
  const int index = item->data(Qt::UserRole).toInt(&ok);
×
183
  if (!ok) {
×
184
#ifdef QT_DEBUG
185
    qWarning() << "UsersDialog::itemChange: invalid user index data for item";
186
#endif
187
    return;
188
  }
189
  if (index < 0 || index >= m_userList.size()) {
×
190
#ifdef QT_DEBUG
191
    qWarning() << "UsersDialog::itemChange: user index out of range:" << index
192
               << "valid range is [0," << (m_userList.size() - 1) << "]";
193
#endif
194
    return;
195
  }
196
  m_userList[index].enabled = item->checkState() == Qt::Checked;
×
197
}
198

199
/**
200
 * @brief UsersDialog::populateList update the view based on filter options
201
 * (such as searching).
202
 * @param filter
203
 */
204
void UsersDialog::populateList(const QString &filter) {
×
205
  if (filter != m_lastFilter) {
×
206
    m_lastFilter = filter;
×
207
    m_cachedNameFilter = QRegularExpression(
×
208
        QRegularExpression::wildcardToRegularExpression("*" + filter + "*"),
×
209
        QRegularExpression::CaseInsensitiveOption);
210
  }
211
  const QRegularExpression &nameFilter = m_cachedNameFilter;
×
212
  ui->listWidget->clear();
×
213

214
  for (int i = 0; i < m_userList.size(); ++i) {
×
215
    const auto &user = m_userList.at(i);
216
    if (!passesFilter(user, filter, nameFilter)) {
×
217
      continue;
×
218
    }
219

220
    auto *item = new QListWidgetItem(buildUserText(user), ui->listWidget);
×
221
    applyUserStyling(item, user);
×
222
    item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
×
223
    item->setData(Qt::UserRole, QVariant::fromValue(i));
×
224
    ui->listWidget->addItem(item);
×
225
  }
226
}
×
227

228
/**
229
 * @brief Checks if a user passes the filter criteria.
230
 * @param user User to check
231
 * @param filter Filter string
232
 * @param nameFilter Compiled name filter regex
233
 * @return true if user passes filter
234
 */
235
bool UsersDialog::passesFilter(const UserInfo &user, const QString &filter,
×
236
                               const QRegularExpression &nameFilter) const {
237
  if (!filter.isEmpty() && !nameFilter.match(user.name).hasMatch()) {
×
238
    return false;
239
  }
240
  if (!user.isValid() && !ui->checkBox->isChecked()) {
×
241
    return false;
242
  }
243
  const bool expired = isUserExpired(user);
×
244
  return !(expired && !ui->checkBox->isChecked());
×
245
}
246

247
/**
248
 * @brief Checks if a user's key has expired.
249
 * @param user User to check
250
 * @return true if user's key is expired
251
 */
252
auto UsersDialog::isUserExpired(const UserInfo &user) const -> bool {
×
253
  return user.expiry.toSecsSinceEpoch() > 0 &&
×
254
         QDateTime::currentDateTime() > user.expiry;
×
255
}
256

257
/**
258
 * @brief Builds display text for a user.
259
 * @param user User to format
260
 * @return Formatted user text
261
 */
262
QString UsersDialog::buildUserText(const UserInfo &user) const {
×
263
  QString text = user.name + "\n" + user.key_id;
×
264
  if (user.created.toSecsSinceEpoch() > 0) {
×
265
    text += " " + tr("created") + " " +
×
266
            QLocale::system().toString(user.created, QLocale::ShortFormat);
×
267
  }
268
  if (user.expiry.toSecsSinceEpoch() > 0) {
×
269
    text += " " + tr("expires") + " " +
×
270
            QLocale::system().toString(user.expiry, QLocale::ShortFormat);
×
271
  }
272
  return text;
×
273
}
274

275
/**
276
 * @brief Applies visual styling to a user list item based on key status.
277
 * @param item List widget item to style
278
 * @param user User whose status determines styling
279
 */
280
void UsersDialog::applyUserStyling(QListWidgetItem *item,
×
281
                                   const UserInfo &user) const {
282
  const QString originalText = item->text();
×
283
  if (user.have_secret) {
×
284
    const QPalette palette = QApplication::palette();
×
285
    item->setForeground(palette.color(QPalette::Link));
×
286
    QFont font = item->font();
×
287
    font.setBold(true);
288
    item->setFont(font);
×
289
  } else if (!user.isValid()) {
×
290
    item->setBackground(Qt::darkRed);
×
291
    item->setForeground(Qt::white);
×
292
    item->setText(tr("[INVALID] ") + originalText);
×
293
  } else if (isUserExpired(user)) {
×
294
    item->setForeground(Qt::darkRed);
×
295
    item->setText(tr("[EXPIRED] ") + originalText);
×
296
  } else if (!user.fullyValid()) {
×
297
    item->setBackground(Qt::darkYellow);
×
298
    item->setForeground(Qt::white);
×
299
    item->setText(tr("[PARTIAL] ") + originalText);
×
300
  } else {
301
    item->setText(originalText);
×
302
  }
303
}
×
304

305
/**
306
 * @brief UsersDialog::on_lineEdit_textChanged typing in the searchbox.
307
 * @param filter
308
 */
309
void UsersDialog::on_lineEdit_textChanged(const QString &filter) {
×
310
  populateList(filter);
×
311
}
×
312

313
/**
314
 * @brief UsersDialog::on_checkBox_clicked filtering.
315
 */
316
void UsersDialog::on_checkBox_clicked() { populateList(ui->lineEdit->text()); }
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc