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

IJHack / QtPass / 24284720980

11 Apr 2026 02:38PM UTC coverage: 20.856%. First build
24284720980

Pull #968

github

web-flow
Merge ce4467428 into 71fc52f07
Pull Request #968: refactor: split UsersDialog constructor into helper methods

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

1106 of 5303 relevant lines covered (20.86%)

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 parent
21
 */
22
UsersDialog::UsersDialog(QString dir, QWidget *parent)
×
23
    : QDialog(parent), ui(new Ui::UsersDialog), m_dir(std::move(dir)) {
×
24

25
  ui->setupUi(this);
×
26

NEW
27
  restoreDialogState();
×
NEW
28
  if (!loadGpgKeys()) {
×
29
    return;
30
  }
31

NEW
32
  loadRecipients();
×
NEW
33
  populateList();
×
34

NEW
35
  connectSignals();
×
NEW
36
}
×
37

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

NEW
45
  ui->lineEdit->setClearButtonEnabled(true);
×
NEW
46
}
×
47

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

NEW
65
auto UsersDialog::loadGpgKeys() -> bool {
×
66
  /**
67
   * @brief Load GPG keys and determine secret key status.
68
   * @return true if successful, false if keys could not be loaded.
69
   */
70
  QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
×
71
  if (users.isEmpty()) {
×
NEW
72
    QMessageBox::critical(parentWidget(), tr("Keylist missing"),
×
73
                          tr("Could not fetch list of available GPG keys"));
×
74
    reject();
×
75
    return false;
76
  }
77

NEW
78
  markSecretKeys(users);
×
79

80
  m_userList = users;
NEW
81
  return true;
×
82
}
83

NEW
84
void UsersDialog::markSecretKeys(QList<UserInfo> &users) {
×
85
  /**
86
   * @brief Mark which keys have secret counterparts.
87
   * @param users List of users to mark.
88
   */
89
  QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
×
90
  QSet<QString> secretKeyIds;
91
  for (const UserInfo &sec : secret_keys) {
×
92
    secretKeyIds.insert(sec.key_id);
×
93
  }
94
  for (auto &user : users) {
×
95
    if (secretKeyIds.contains(user.key_id)) {
×
96
      user.have_secret = true;
×
97
    }
98
  }
NEW
99
}
×
100

NEW
101
void UsersDialog::loadRecipients() {
×
102
  /**
103
   * @brief Load recipients and handle missing keys.
104
   */
105
  int count = 0;
×
106
  QStringList recipients = QtPassSettings::getPass()->getRecipientString(
×
107
      m_dir.isEmpty() ? "" : m_dir, " ", &count);
×
108

109
  QList<UserInfo> selectedUsers =
NEW
110
      QtPassSettings::getPass()->listKeys(recipients);
×
111
  QSet<QString> selectedKeyIds;
NEW
112
  for (const UserInfo &sel : selectedUsers) {
×
113
    selectedKeyIds.insert(sel.key_id);
×
114
  }
NEW
115
  for (auto &user : m_userList) {
×
116
    if (selectedKeyIds.contains(user.key_id)) {
×
117
      user.enabled = true;
×
118
    }
119
  }
120

NEW
121
  if (count > selectedUsers.size()) {
×
122
    QStringList allRecipients = QtPassSettings::getPass()->getRecipientList(
×
123
        m_dir.isEmpty() ? "" : m_dir);
×
124
    QSet<QString> missingKeyRecipients;
125
    for (const QString &recipient : allRecipients) {
×
NEW
126
      if (!missingKeyRecipients.contains(recipient) &&
×
127
          QtPassSettings::getPass()->listKeys(recipient).empty()) {
×
128
        missingKeyRecipients.insert(recipient);
×
129
        UserInfo i;
×
130
        i.enabled = true;
×
131
        i.key_id = recipient;
×
132
        i.name = " ?? " + tr("Key not found in keyring");
×
133
        m_userList.append(i);
134
      }
×
135
    }
136
  }
137
}
×
138

139
/**
140
 * @brief UsersDialog::~UsersDialog basic destructor.
141
 */
142
UsersDialog::~UsersDialog() { delete ui; }
×
143

144
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
145
Q_DECLARE_METATYPE(UserInfo *)
146
Q_DECLARE_METATYPE(const UserInfo *)
147
#endif
148

149
/**
150
 * @brief UsersDialog::accept
151
 */
152
void UsersDialog::accept() {
×
153
  QtPassSettings::getPass()->Init(m_dir, m_userList);
×
154

155
  QDialog::accept();
×
156
}
×
157

158
/**
159
 * @brief UsersDialog::closeEvent save window state on close.
160
 * @param event
161
 */
162
void UsersDialog::closeEvent(QCloseEvent *event) {
×
163
  QtPassSettings::setDialogGeometry("usersDialog", saveGeometry());
×
164
  if (!isMaximized()) {
×
165
    QtPassSettings::setDialogPos("usersDialog", pos());
×
166
    QtPassSettings::setDialogSize("usersDialog", size());
×
167
  }
168
  QtPassSettings::setDialogMaximized("usersDialog", isMaximized());
×
169
  event->accept();
170
}
×
171

172
/**
173
 * @brief UsersDialog::keyPressEvent clear the lineEdit when escape is pressed.
174
 * No action for Enter currently.
175
 * @param event
176
 */
177
void UsersDialog::keyPressEvent(QKeyEvent *event) {
×
178
  switch (event->key()) {
×
179
  case Qt::Key_Escape:
×
180
    ui->lineEdit->clear();
×
181
    break;
×
182
  default:
183
    break;
184
  }
185
}
×
186

187
/**
188
 * @brief UsersDialog::itemChange update the item information.
189
 * @param item
190
 */
191
void UsersDialog::itemChange(QListWidgetItem *item) {
×
192
  if (!item) {
×
193
    return;
×
194
  }
195
  bool ok = false;
×
196
  const int index = item->data(Qt::UserRole).toInt(&ok);
×
197
  if (!ok) {
×
198
#ifdef QT_DEBUG
199
    qWarning() << "UsersDialog::itemChange: invalid user index data for item";
200
#endif
201
    return;
202
  }
203
  if (index < 0 || index >= m_userList.size()) {
×
204
#ifdef QT_DEBUG
205
    qWarning() << "UsersDialog::itemChange: user index out of range:" << index
206
               << "valid range is [0," << (m_userList.size() - 1) << "]";
207
#endif
208
    return;
209
  }
210
  m_userList[index].enabled = item->checkState() == Qt::Checked;
×
211
}
212

213
/**
214
 * @brief UsersDialog::populateList update the view based on filter options
215
 * (such as searching).
216
 * @param filter
217
 */
218
void UsersDialog::populateList(const QString &filter) {
×
219
  static QString lastFilter;
×
220
  static QRegularExpression cachedNameFilter;
×
221
  if (filter != lastFilter) {
×
222
    lastFilter = filter;
×
223
    cachedNameFilter = QRegularExpression(
×
224
        QRegularExpression::wildcardToRegularExpression("*" + filter + "*"),
×
225
        QRegularExpression::CaseInsensitiveOption);
226
  }
227
  const QRegularExpression &nameFilter = cachedNameFilter;
228
  ui->listWidget->clear();
×
229

230
  for (int i = 0; i < m_userList.size(); ++i) {
×
231
    const auto &user = m_userList.at(i);
232
    if (!passesFilter(user, filter, nameFilter)) {
×
233
      continue;
×
234
    }
235

236
    auto *item = new QListWidgetItem(buildUserText(user), ui->listWidget);
×
237
    applyUserStyling(item, user);
×
238
    item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
×
239
    item->setData(Qt::UserRole, QVariant::fromValue(i));
×
240
    ui->listWidget->addItem(item);
×
241
  }
242
}
×
243

244
/**
245
 * @brief Checks if a user passes the filter criteria.
246
 * @param user User to check
247
 * @param filter Filter string
248
 * @param nameFilter Compiled name filter regex
249
 * @return true if user passes filter
250
 */
251
bool UsersDialog::passesFilter(const UserInfo &user, const QString &filter,
×
252
                               const QRegularExpression &nameFilter) const {
253
  if (!filter.isEmpty() && !nameFilter.match(user.name).hasMatch()) {
×
254
    return false;
255
  }
256
  if (!user.isValid() && !ui->checkBox->isChecked()) {
×
257
    return false;
258
  }
259
  const bool expired = isUserExpired(user);
×
260
  return !(expired && !ui->checkBox->isChecked());
×
261
}
262

263
/**
264
 * @brief Checks if a user's key has expired.
265
 * @param user User to check
266
 * @return true if user's key is expired
267
 */
268
auto UsersDialog::isUserExpired(const UserInfo &user) const -> bool {
×
269
  return user.expiry.toSecsSinceEpoch() > 0 &&
×
270
         QDateTime::currentDateTime() > user.expiry;
×
271
}
272

273
/**
274
 * @brief Builds display text for a user.
275
 * @param user User to format
276
 * @return Formatted user text
277
 */
278
QString UsersDialog::buildUserText(const UserInfo &user) const {
×
279
  QString text = user.name + "\n" + user.key_id;
×
280
  if (user.created.toSecsSinceEpoch() > 0) {
×
281
    text += " " + tr("created") + " " +
×
282
            QLocale::system().toString(user.created, QLocale::ShortFormat);
×
283
  }
284
  if (user.expiry.toSecsSinceEpoch() > 0) {
×
285
    text += " " + tr("expires") + " " +
×
286
            QLocale::system().toString(user.expiry, QLocale::ShortFormat);
×
287
  }
288
  return text;
×
289
}
290

291
/**
292
 * @brief Applies visual styling to a user list item based on key status.
293
 * @param item List widget item to style
294
 * @param user User whose status determines styling
295
 */
296
void UsersDialog::applyUserStyling(QListWidgetItem *item,
×
297
                                   const UserInfo &user) const {
298
  const QString originalText = item->text();
×
299
  if (user.have_secret) {
×
300
    const QPalette palette = QApplication::palette();
×
301
    item->setForeground(palette.color(QPalette::Link));
×
302
    QFont font = item->font();
×
303
    font.setBold(true);
304
    item->setFont(font);
×
305
  } else if (!user.isValid()) {
×
306
    item->setBackground(Qt::darkRed);
×
307
    item->setForeground(Qt::white);
×
308
    item->setText(tr("[INVALID] ") + originalText);
×
309
  } else if (isUserExpired(user)) {
×
310
    item->setForeground(Qt::darkRed);
×
311
    item->setText(tr("[EXPIRED] ") + originalText);
×
312
  } else if (!user.fullyValid()) {
×
313
    item->setBackground(Qt::darkYellow);
×
314
    item->setForeground(Qt::white);
×
315
    item->setText(tr("[PARTIAL] ") + originalText);
×
316
  } else {
317
    item->setText(originalText);
×
318
  }
319
}
×
320

321
/**
322
 * @brief UsersDialog::on_lineEdit_textChanged typing in the searchbox.
323
 * @param filter
324
 */
325
void UsersDialog::on_lineEdit_textChanged(const QString &filter) {
×
326
  populateList(filter);
×
327
}
×
328

329
/**
330
 * @brief UsersDialog::on_checkBox_clicked filtering.
331
 */
332
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