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

IJHack / QtPass / 25576477093

08 May 2026 07:56PM UTC coverage: 28.048% (+0.03%) from 28.015%
25576477093

push

github

web-flow
refactor: clang-tidy cleanup (1 real bug + bulk modernize/performance fixes) (#1432)

* fix(trayicon): remove dead misleading setVisible method

TrayIcon::setVisible(bool) was a name collision with its base
class QWidget::setVisible(bool) — same signature, but the
implementation didn't toggle the tray icon's visibility. It
shows/hides the parent window:

  void TrayIcon::setVisible(bool visible) {
    if (visible) parentwin->show();
    else         parentwin->hide();
  }

Two problems:
- Misleading name: doesn't do what the QWidget API contract
  promises.
- Virtual shadowing without override: calling through a QWidget*
  pointer would call the base implementation, not this one,
  silently breaking caller intent.

Searched for callers — there are none. The method is dead code.
Removed the declaration and implementation together. Show/hide
of the parent window already happens elsewhere (via showAction
/ hideAction signals in createActions).

Caught by clang-tidy modernize-use-override.

* chore(clang-tidy): drop noisy stylistic checks

Two clang-tidy checks were producing high-volume findings that
the project doesn't actually want to fix:

- modernize-use-trailing-return-type (~55 in QtPass code, ~11K
  total counting Qt headers): suggests "auto foo() -> int"
  style. Project codebase mixes both; not enforcing the trailing
  form is a deliberate stylistic choice.

- modernize-use-nodiscard (~5 in QtPass code, ~3.7K total):
  suggests [[nodiscard]] on every getter. Project is selective
  about which getters need it; auto-applying everywhere creates
  noise and doesn't reflect intent.

Disabled both. Keeps the rest of modernize-* / performance-*
active so genuine improvements (default-member-init, override,
braced-init-list, enum-size, etc.) keep getting flagged.

Also added HeaderFilterRegex to keep findings from vendored
qprogressindicator out of the diagnostic stream.

* refactor: apply clang-tidy auto-fixes (modernize-* + performance-*)

Bulk pass o... (continued)

5 of 18 new or added lines in 12 files covered. (27.78%)

9 existing lines in 1 file now uncovered.

1854 of 6610 relevant lines covered (28.05%)

27.13 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 "importkeydialog.h"
5
#include "qtpasssettings.h"
6
#include "ui_usersdialog.h"
7
#include <QApplication>
8
#include <QCloseEvent>
9
#include <QDateTime>
10
#include <QKeyEvent>
11
#include <QLineEdit>
12
#include <QListWidget>
13
#include <QMessageBox>
14
#include <QRegularExpression>
15
#include <QSet>
16
#include <QSignalBlocker>
17
#include <QWidget>
18
#include <utility>
19

20
#ifdef QT_DEBUG
21
#include "debughelper.h"
22
#endif
23
/**
24
 * @brief UsersDialog::UsersDialog basic constructor
25
 * @param dir Password directory
26
 * @param parent
27
 */
NEW
28
UsersDialog::UsersDialog(QString dir, QWidget *parent)
×
NEW
29
    : QDialog(parent), ui(new Ui::UsersDialog), m_dir(std::move(dir)) {
×
30

31
  ui->setupUi(this);
×
32

33
  restoreDialogState();
×
34
  if (!loadGpgKeys()) {
×
35
    connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
36
    return;
×
37
  }
38

39
  loadRecipients();
×
40
  populateList();
×
41

42
  connectSignals();
×
43
}
×
44

45
void UsersDialog::connectSignals() {
×
46
  connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
×
47
          &UsersDialog::accept);
×
48
  connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
49
  connect(ui->listWidget, &QListWidget::itemChanged, this,
×
50
          &UsersDialog::itemChange);
×
51
  // importKeyButton is wired via Qt's automatic on_<name>_<signal> mechanism
52
  // already triggered by setupUi().
53

54
  ui->lineEdit->setClearButtonEnabled(true);
×
55
}
×
56

57
/**
58
 * @brief Restore dialog geometry from settings.
59
 */
60
void UsersDialog::restoreDialogState() {
×
61
  QByteArray savedGeometry = QtPassSettings::getDialogGeometry("usersDialog");
×
62
  bool hasSavedGeometry = !savedGeometry.isEmpty();
63
  if (hasSavedGeometry) {
×
64
    restoreGeometry(savedGeometry);
×
65
  }
66
  if (QtPassSettings::isDialogMaximized("usersDialog")) {
×
67
    showMaximized();
×
68
  } else if (hasSavedGeometry) {
×
69
    move(QtPassSettings::getDialogPos("usersDialog"));
×
70
    resize(QtPassSettings::getDialogSize("usersDialog"));
×
71
  }
72
}
×
73

74
auto UsersDialog::loadGpgKeys() -> bool {
×
75
  QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
×
76
  if (users.isEmpty()) {
×
77
    QMessageBox::critical(parentWidget(), tr("Keylist missing"),
×
78
                          tr("Could not fetch list of available GPG keys"));
×
79
    reject();
×
80
    return false;
81
  }
82

83
  markSecretKeys(users);
×
84

85
  m_userList = users;
86
  return true;
×
87
}
88

89
void UsersDialog::markSecretKeys(QList<UserInfo> &users) {
×
90
  QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
×
91
  QSet<QString> secretKeyIds;
92
  for (const UserInfo &sec : secret_keys) {
×
93
    secretKeyIds.insert(sec.key_id);
×
94
  }
95
  for (auto &user : users) {
×
96
    if (secretKeyIds.contains(user.key_id)) {
×
97
      user.have_secret = true;
×
98
    }
99
  }
100
}
×
101

102
void UsersDialog::loadRecipients() {
×
103
  int count = 0;
×
104
  QStringList recipients = QtPassSettings::getPass()->getRecipientString(
×
105
      m_dir.isEmpty() ? "" : m_dir, " ", &count);
×
106

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

119
  if (count > selectedUsers.size()) {
×
120
    QStringList allRecipients = QtPassSettings::getPass()->getRecipientList(
×
121
        m_dir.isEmpty() ? "" : m_dir);
×
122

123
    // Use bulk lookup to resolve all recipients at once (single gpg call)
124
    // This preserves the original email/UID resolution behavior
125
    QList<UserInfo> resolvedKeys =
126
        QtPassSettings::getPass()->listKeys(allRecipients);
×
127
    // Track resolved recipients by their resolved key_id
128
    QSet<QString> resolvedKeyIds;
129
    for (const UserInfo &key : resolvedKeys) {
×
130
      resolvedKeyIds.insert(key.key_id);
×
131
    }
132

133
    // Accept a recipient as resolved if GPG returned a key for it
134
    // (either its exact key_id, or GPG resolved email/UID/fingerprint to it)
135
    QSet<QString> resolvedRecipients;
136
    for (const UserInfo &key : resolvedKeys) {
×
137
      resolvedRecipients.insert(key.key_id);
×
138
      // Also add the name (email/UID) of resolved keys as valid resolved tokens
139
      // since GPG matched them to this key
140
      if (!key.name.isEmpty()) {
×
141
        resolvedRecipients.insert(
×
142
            key.name.section('@', 0, 0));    // email local part
×
143
        resolvedRecipients.insert(key.name); // full email/UID
×
144
      }
145
    }
146

147
    for (const QString &recipient : allRecipients) {
×
148
      if (!resolvedKeyIds.contains(recipient) &&
×
149
          !resolvedRecipients.contains(recipient) &&
×
150
          !selectedKeyIds.contains(recipient)) {
151
        UserInfo i;
×
152
        i.enabled = true;
×
153
        i.key_id = recipient;
×
154
        i.name = " ?? " + tr("Key not found in keyring");
×
155
        m_userList.append(i);
156
      }
×
157
    }
158
  }
159
}
×
160

161
/**
162
 * @brief UsersDialog::~UsersDialog basic destructor.
163
 */
164
UsersDialog::~UsersDialog() { delete ui; }
×
165

166
/**
167
 * @brief UsersDialog::accept
168
 */
169
void UsersDialog::accept() {
×
170
  QtPassSettings::getPass()->Init(m_dir, m_userList);
×
171

172
  QDialog::accept();
×
173
}
×
174

175
/**
176
 * @brief UsersDialog::closeEvent save window state on close.
177
 * @param event
178
 */
179
void UsersDialog::closeEvent(QCloseEvent *event) {
×
180
  QtPassSettings::setDialogGeometry("usersDialog", saveGeometry());
×
181
  if (!isMaximized()) {
×
182
    QtPassSettings::setDialogPos("usersDialog", pos());
×
183
    QtPassSettings::setDialogSize("usersDialog", size());
×
184
  }
185
  QtPassSettings::setDialogMaximized("usersDialog", isMaximized());
×
186
  event->accept();
187
}
×
188

189
/**
190
 * @brief UsersDialog::keyPressEvent clear the lineEdit when escape is pressed.
191
 * No action for Enter currently.
192
 * @param event
193
 */
194
void UsersDialog::keyPressEvent(QKeyEvent *event) {
×
195
  switch (event->key()) {
×
196
  case Qt::Key_Escape:
×
197
    ui->lineEdit->clear();
×
198
    break;
×
199
  default:
200
    break;
201
  }
202
}
×
203

204
/**
205
 * @brief UsersDialog::itemChange update the item information.
206
 * @param item
207
 */
208
void UsersDialog::itemChange(QListWidgetItem *item) {
×
209
  if (!item) {
×
210
    return;
×
211
  }
212
  bool ok = false;
×
213
  const int index = item->data(Qt::UserRole).toInt(&ok);
×
214
  if (!ok) {
×
215
#ifdef QT_DEBUG
216
    qWarning() << "UsersDialog::itemChange: invalid user index data for item";
217
#endif
218
    return;
219
  }
220
  if (index < 0 || index >= m_userList.size()) {
×
221
#ifdef QT_DEBUG
222
    qWarning() << "UsersDialog::itemChange: user index out of range:" << index
223
               << "valid range is [0," << (m_userList.size() - 1) << "]";
224
#endif
225
    return;
226
  }
227
  m_userList[index].enabled = item->checkState() == Qt::Checked;
×
228
}
229

230
/**
231
 * @brief UsersDialog::populateList update the view based on filter options
232
 * (such as searching).
233
 * @param filter
234
 */
235
void UsersDialog::populateList(const QString &filter) {
×
236
  // Invalidate cached datetime so expiry checks use fresh current time
237
  m_cachedDateTimeValid = false;
×
238

239
  QString patternString = "*" + filter + "*";
×
240
  if (m_cachedPatternString != patternString) {
×
241
    QRegularExpression re(
242
        QRegularExpression::wildcardToRegularExpression(patternString),
×
243
        QRegularExpression::CaseInsensitiveOption);
×
244
    if (re.isValid()) {
×
245
      m_cachedNameFilter = re;
×
246
      m_cachedPatternString = patternString;
×
247
    }
248
  }
×
249
  const QRegularExpression &nameFilter = m_cachedNameFilter;
×
250
  ui->listWidget->clear();
×
251

252
  for (int i = 0; i < m_userList.size(); ++i) {
×
253
    const auto &user = m_userList.at(i);
254
    if (!passesFilter(user, filter, nameFilter)) {
×
255
      continue;
×
256
    }
257

258
    auto *item = new QListWidgetItem(buildUserText(user), ui->listWidget);
×
259
    applyUserStyling(item, user);
×
260
    item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
×
261
    item->setData(Qt::UserRole, QVariant::fromValue(i));
×
262
    ui->listWidget->addItem(item);
×
263
  }
264
}
×
265

266
/**
267
 * @brief Checks if a user passes the filter criteria.
268
 * @param user User to check
269
 * @param filter Filter string
270
 * @param nameFilter Compiled name filter regex
271
 * @return true if user passes filter
272
 */
273
bool UsersDialog::passesFilter(const UserInfo &user, const QString &filter,
×
274
                               const QRegularExpression &nameFilter) const {
275
  if (!filter.isEmpty() && !nameFilter.match(user.name).hasMatch()) {
×
276
    return false;
277
  }
278
  if (!user.isValid() && !ui->checkBox->isChecked()) {
×
279
    return false;
280
  }
281
  const bool expired = isUserExpired(user);
×
282
  return !(expired && !ui->checkBox->isChecked());
×
283
}
284

285
/**
286
 * @brief Checks if a user's key has expired.
287
 * @param user User to check
288
 * @return true if user's key is expired
289
 */
290
auto UsersDialog::isUserExpired(const UserInfo &user) const -> bool {
×
291
  if (!m_cachedDateTimeValid) {
×
292
    m_cachedCurrentDateTime = QDateTime::currentDateTime();
×
293
    m_cachedDateTimeValid = true;
×
294
  }
295
  return user.expiry.toSecsSinceEpoch() > 0 &&
×
296
         m_cachedCurrentDateTime > user.expiry;
×
297
}
298

299
/**
300
 * @brief Builds display text for a user.
301
 * @param user User to format
302
 * @return Formatted user text
303
 */
304
QString UsersDialog::buildUserText(const UserInfo &user) const {
×
305
  QString text = user.name + "\n" + user.key_id;
×
306
  if (user.created.toSecsSinceEpoch() > 0) {
×
307
    text += " " + tr("created") + " " +
×
308
            QLocale::system().toString(user.created, QLocale::ShortFormat);
×
309
  }
310
  if (user.expiry.toSecsSinceEpoch() > 0) {
×
311
    text += " " + tr("expires") + " " +
×
312
            QLocale::system().toString(user.expiry, QLocale::ShortFormat);
×
313
  }
314
  return text;
×
315
}
316

317
/**
318
 * @brief Applies visual styling to a user list item based on key status.
319
 * @param item List widget item to style
320
 * @param user User whose status determines styling
321
 */
322
void UsersDialog::applyUserStyling(QListWidgetItem *item,
×
323
                                   const UserInfo &user) const {
324
  const QString originalText = item->text();
×
325
  if (user.have_secret) {
×
326
    const QPalette palette = QApplication::palette();
×
327
    item->setForeground(palette.color(QPalette::Link));
×
328
    QFont font = item->font();
×
329
    font.setBold(true);
330
    item->setFont(font);
×
331
  } else if (!user.isValid()) {
×
332
    item->setBackground(Qt::darkRed);
×
333
    item->setForeground(Qt::white);
×
334
    item->setText(tr("[INVALID] ") + originalText);
×
335
  } else if (isUserExpired(user)) {
×
336
    item->setForeground(Qt::darkRed);
×
337
    item->setText(tr("[EXPIRED] ") + originalText);
×
338
  } else if (!user.fullyValid()) {
×
339
    item->setBackground(Qt::darkYellow);
×
340
    item->setForeground(Qt::white);
×
341
    item->setText(tr("[PARTIAL] ") + originalText);
×
342
  } else {
343
    item->setText(originalText);
×
344
  }
345
}
×
346

347
/**
348
 * @brief UsersDialog::on_lineEdit_textChanged typing in the searchbox.
349
 * @param filter
350
 */
351
void UsersDialog::on_lineEdit_textChanged(const QString &filter) {
×
352
  populateList(filter);
×
353
}
×
354

355
/**
356
 * @brief UsersDialog::on_checkBox_clicked filtering.
357
 */
358
void UsersDialog::on_checkBox_clicked() { populateList(ui->lineEdit->text()); }
×
359

360
void UsersDialog::on_importKeyButton_clicked() {
×
361
  ImportKeyDialog dialog(this);
×
362
  if (dialog.exec() != QDialog::Accepted) {
×
363
    return;
364
  }
365

366
  // dialog.exec() == Accepted is only reachable after a successful import
367
  // (see ImportKeyDialog::importFromString), so importedKeyId() is non-empty
368
  // by construction. Guard anyway: an empty value would make the
369
  // bidirectional endsWith() below match the first listed key.
370
  const QString importedKey = dialog.importedKeyId();
×
371
  if (importedKey.isEmpty()) {
×
372
    return;
373
  }
374

375
  if (!loadGpgKeys()) {
×
376
    return;
377
  }
378

379
  // Clear the filter so the just-imported key is visible. setText("") still
380
  // emits textChanged once, so don't double-populate.
381
  {
382
    const QSignalBlocker blocker(ui->lineEdit);
×
383
    ui->lineEdit->clear();
×
384
  }
×
385
  populateList(QString());
×
386

387
  // Match against the user's stored key_id, not the visible item text.
388
  // gpg can return a 16-char long key id (IMPORTED status line) or a
389
  // 40-char fingerprint (IMPORT_OK status line); compare both directions
390
  // so a long-id match works against a fingerprint hit and vice versa.
391
  for (int i = 0; i < ui->listWidget->count(); ++i) {
×
392
    QListWidgetItem *item = ui->listWidget->item(i);
×
393
    if (item == nullptr) {
×
394
      continue;
×
395
    }
396
    bool ok = false;
×
397
    const int idx = item->data(Qt::UserRole).toInt(&ok);
×
398
    if (!ok || idx < 0 || idx >= m_userList.size()) {
×
399
      continue;
×
400
    }
401
    const QString &keyId = m_userList[idx].key_id;
×
402
    if (keyId.isEmpty()) {
×
403
      continue;
×
404
    }
405
    // Only perform endsWith checks when the suffix being matched is at least
406
    // 16 chars to avoid false positives with short IDs.
407
    if ((keyId.length() >= 16 &&
×
408
         importedKey.endsWith(keyId, Qt::CaseInsensitive)) ||
×
409
        (importedKey.length() >= 16 &&
×
410
         keyId.endsWith(importedKey, Qt::CaseInsensitive))) {
×
411
      ui->listWidget->setCurrentItem(item);
×
412
      break;
×
413
    }
414
  }
415
}
×
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