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

IJHack / QtPass / 23825672923

01 Apr 2026 12:21AM UTC coverage: 20.071% (+0.3%) from 19.722%
23825672923

push

github

web-flow
fix: resolve remaining TODOs and save window state (#877)

* fix: resolve remaining TODOs and save window state

- Remove TODO from on_toolButtonStore_clicked (comment was outdated)
- Implement window size/position saving in ConfigDialog::closeEvent
- Implement window size/position saving in KeygenDialog::closeEvent
- Implement window size/position saving in UsersDialog::closeEvent
- Convert TODO comment in qtpass.cpp to a note (existing code is correct)

- Add dialog-specific settings to avoid clobbering mainwindow state:
  * Add QtPassSettings::getDialogGeometry/setDialogGeometry
  * Add QtPassSettings::getDialogPos/setDialogPos
  * Add QtPassSettings::getDialogSize/setDialogSize
  * Add QtPassSettings::isDialogMaximized/setDialogMaximized

- Update ConfigDialog, KeygenDialog, UsersDialog to use dialog-specific
  settings with unique keys (e.g., 'configDialog', 'keygenDialog', 'usersDialog')
- Add SettingsConstants for dialog-specific settings

* docs: add doxygen documentation and unit tests for dialog settings

29 of 59 new or added lines in 5 files covered. (49.15%)

1023 of 5097 relevant lines covered (20.07%)

7.88 hits per line

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

0.0
/src/usersdialog.cpp
1
// SPDX-FileCopyrightText: 2016 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

27
  // Restore dialog state
NEW
28
  restoreGeometry(QtPassSettings::getDialogGeometry("usersDialog"));
×
NEW
29
  if (QtPassSettings::isDialogMaximized("usersDialog")) {
×
NEW
30
    showMaximized();
×
31
  } else {
NEW
32
    move(QtPassSettings::getDialogPos("usersDialog"));
×
NEW
33
    resize(QtPassSettings::getDialogSize("usersDialog"));
×
34
  }
35

36
  QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
×
37
  if (users.isEmpty()) {
×
38
    QMessageBox::critical(parent, tr("Keylist missing"),
×
39
                          tr("Could not fetch list of available GPG keys"));
×
40
    reject();
×
41
    return;
42
  }
43

44
  QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
×
45
  QSet<QString> secretKeyIds;
46
  for (const UserInfo &sec : secret_keys) {
×
47
    secretKeyIds.insert(sec.key_id);
×
48
  }
49
  for (auto &user : users) {
×
50
    if (secretKeyIds.contains(user.key_id)) {
×
51
      user.have_secret = true;
×
52
    }
53
  }
54

55
  QList<UserInfo> selected_users;
×
56
  int count = 0;
×
57

58
  QStringList recipients = QtPassSettings::getPass()->getRecipientString(
×
59
      m_dir.isEmpty() ? "" : m_dir, " ", &count);
×
60
  if (!recipients.isEmpty()) {
×
61
    selected_users = QtPassSettings::getPass()->listKeys(recipients);
×
62
  }
63
  QSet<QString> selectedKeyIds;
64
  for (const UserInfo &sel : selected_users) {
×
65
    selectedKeyIds.insert(sel.key_id);
×
66
  }
67
  for (auto &user : users) {
×
68
    if (selectedKeyIds.contains(user.key_id)) {
×
69
      user.enabled = true;
×
70
    }
71
  }
72

73
  if (count > selected_users.size()) {
×
74
    // Some keys seem missing from keyring, add them separately
75
    QStringList allRecipients = QtPassSettings::getPass()->getRecipientList(
×
76
        m_dir.isEmpty() ? "" : m_dir);
×
77
    QSet<QString> missingKeyRecipients;
78
    for (const QString &recipient : allRecipients) {
×
79
      if (missingKeyRecipients.contains(recipient) ||
×
80
          QtPassSettings::getPass()->listKeys(recipient).empty()) {
×
81
        missingKeyRecipients.insert(recipient);
×
82
        UserInfo i;
×
83
        i.enabled = true;
×
84
        i.key_id = recipient;
×
85
        i.name = " ?? " + tr("Key not found in keyring");
×
86
        users.append(i);
87
      }
×
88
    }
89
  }
90

91
  m_userList = users;
92
  populateList();
×
93

94
  connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
×
95
          &UsersDialog::accept);
×
96
  connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
97
  connect(ui->listWidget, &QListWidget::itemChanged, this,
×
98
          &UsersDialog::itemChange);
×
99

100
  ui->lineEdit->setClearButtonEnabled(true);
×
101
}
×
102

103
/**
104
 * @brief UsersDialog::~UsersDialog basic destructor.
105
 */
106
UsersDialog::~UsersDialog() { delete ui; }
×
107

108
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
109
Q_DECLARE_METATYPE(UserInfo *)
110
Q_DECLARE_METATYPE(const UserInfo *)
111
#endif
112

113
/**
114
 * @brief UsersDialog::accept
115
 */
116
void UsersDialog::accept() {
×
117
  QtPassSettings::getPass()->Init(m_dir, m_userList);
×
118

119
  QDialog::accept();
×
120
}
×
121

122
/**
123
 * @brief UsersDialog::closeEvent might have to store size and location if that
124
 * is wanted.
125
 * @param event
126
 */
127
void UsersDialog::closeEvent(QCloseEvent *event) {
×
NEW
128
  QtPassSettings::setDialogGeometry("usersDialog", saveGeometry());
×
NEW
129
  QtPassSettings::setDialogPos("usersDialog", pos());
×
NEW
130
  QtPassSettings::setDialogSize("usersDialog", size());
×
NEW
131
  QtPassSettings::setDialogMaximized("usersDialog", isMaximized());
×
132
  event->accept();
133
}
×
134

135
/**
136
 * @brief UsersDialog::keyPressEvent clear the lineEdit when escape is pressed.
137
 * No action for Enter currently.
138
 * @param event
139
 */
140
void UsersDialog::keyPressEvent(QKeyEvent *event) {
×
141
  switch (event->key()) {
×
142
  case Qt::Key_Escape:
×
143
    ui->lineEdit->clear();
×
144
    break;
×
145
  default:
146
    break;
147
  }
148
}
×
149

150
/**
151
 * @brief UsersDialog::itemChange update the item information.
152
 * @param item
153
 */
154
void UsersDialog::itemChange(QListWidgetItem *item) {
×
155
  if (!item) {
×
156
    return;
×
157
  }
158
  bool ok = false;
×
159
  const int index = item->data(Qt::UserRole).toInt(&ok);
×
160
  if (!ok) {
×
161
#ifdef QT_DEBUG
162
    qWarning() << "UsersDialog::itemChange: invalid user index data for item";
163
#endif
164
    return;
165
  }
166
  if (index < 0 || index >= m_userList.size()) {
×
167
#ifdef QT_DEBUG
168
    qWarning() << "UsersDialog::itemChange: user index out of range:" << index
169
               << "valid range is [0," << (m_userList.size() - 1) << "]";
170
#endif
171
    return;
172
  }
173
  m_userList[index].enabled = item->checkState() == Qt::Checked;
×
174
}
175

176
/**
177
 * @brief UsersDialog::populateList update the view based on filter options
178
 * (such as searching).
179
 * @param filter
180
 */
181
void UsersDialog::populateList(const QString &filter) {
×
182
  static QString lastFilter;
×
183
  static QRegularExpression cachedNameFilter;
×
184
  if (filter != lastFilter) {
×
185
    lastFilter = filter;
×
186
    cachedNameFilter = QRegularExpression(
×
187
        QRegularExpression::wildcardToRegularExpression("*" + filter + "*"),
×
188
        QRegularExpression::CaseInsensitiveOption);
189
  }
190
  const QRegularExpression &nameFilter = cachedNameFilter;
191
  ui->listWidget->clear();
×
192

193
  for (int i = 0; i < m_userList.size(); ++i) {
×
194
    const auto &user = m_userList.at(i);
195
    if (!passesFilter(user, filter, nameFilter)) {
×
196
      continue;
×
197
    }
198

199
    auto *item = new QListWidgetItem(buildUserText(user), ui->listWidget);
×
200
    applyUserStyling(item, user);
×
201
    item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
×
202
    item->setData(Qt::UserRole, QVariant::fromValue(i));
×
203
    ui->listWidget->addItem(item);
×
204
  }
205
}
×
206

207
/**
208
 * @brief Checks if a user passes the filter criteria.
209
 * @param user User to check
210
 * @param filter Filter string
211
 * @param nameFilter Compiled name filter regex
212
 * @return true if user passes filter
213
 */
214
bool UsersDialog::passesFilter(const UserInfo &user, const QString &filter,
×
215
                               const QRegularExpression &nameFilter) const {
216
  if (!filter.isEmpty() && !nameFilter.match(user.name).hasMatch()) {
×
217
    return false;
218
  }
219
  if (!user.isValid() && !ui->checkBox->isChecked()) {
×
220
    return false;
221
  }
222
  const bool expired = isUserExpired(user);
×
223
  return !(expired && !ui->checkBox->isChecked());
×
224
}
225

226
/**
227
 * @brief Checks if a user's key has expired.
228
 * @param user User to check
229
 * @return true if user's key is expired
230
 */
231
auto UsersDialog::isUserExpired(const UserInfo &user) const -> bool {
×
232
  return user.expiry.toSecsSinceEpoch() > 0 &&
×
233
         QDateTime::currentDateTime() > user.expiry;
×
234
}
235

236
/**
237
 * @brief Builds display text for a user.
238
 * @param user User to format
239
 * @return Formatted user text
240
 */
241
QString UsersDialog::buildUserText(const UserInfo &user) const {
×
242
  QString text = user.name + "\n" + user.key_id;
×
243
  if (user.created.toSecsSinceEpoch() > 0) {
×
244
    text += " " + tr("created") + " " +
×
245
            QLocale::system().toString(user.created, QLocale::ShortFormat);
×
246
  }
247
  if (user.expiry.toSecsSinceEpoch() > 0) {
×
248
    text += " " + tr("expires") + " " +
×
249
            QLocale::system().toString(user.expiry, QLocale::ShortFormat);
×
250
  }
251
  return text;
×
252
}
253

254
/**
255
 * @brief Applies visual styling to a user list item based on key status.
256
 * @param item List widget item to style
257
 * @param user User whose status determines styling
258
 */
259
void UsersDialog::applyUserStyling(QListWidgetItem *item,
×
260
                                   const UserInfo &user) const {
261
  const QString originalText = item->text();
×
262
  if (user.have_secret) {
×
263
    const QPalette palette = QApplication::palette();
×
264
    item->setForeground(palette.color(QPalette::Link));
×
265
    QFont font = item->font();
×
266
    font.setBold(true);
267
    item->setFont(font);
×
268
  } else if (!user.isValid()) {
×
269
    item->setBackground(Qt::darkRed);
×
270
    item->setForeground(Qt::white);
×
271
    item->setText(tr("[INVALID] ") + originalText);
×
272
  } else if (isUserExpired(user)) {
×
273
    item->setForeground(Qt::darkRed);
×
274
    item->setText(tr("[EXPIRED] ") + originalText);
×
275
  } else if (!user.fullyValid()) {
×
276
    item->setBackground(Qt::darkYellow);
×
277
    item->setForeground(Qt::white);
×
278
    item->setText(tr("[PARTIAL] ") + originalText);
×
279
  } else {
280
    item->setText(originalText);
×
281
  }
282
}
×
283

284
/**
285
 * @brief UsersDialog::on_lineEdit_textChanged typing in the searchbox.
286
 * @param filter
287
 */
288
void UsersDialog::on_lineEdit_textChanged(const QString &filter) {
×
289
  populateList(filter);
×
290
}
×
291

292
/**
293
 * @brief UsersDialog::on_checkBox_clicked filtering.
294
 */
295
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