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

IJHack / QtPass / 24161188444

08 Apr 2026 10:06PM UTC coverage: 20.992% (-0.05%) from 21.04%
24161188444

push

github

web-flow
fix: let window manager handle dialog positioning (#947)

* fix: let window manager handle dialog positioning

- Config dialog: Only restore position/size if previously saved (not first launch)
- Close event: Only save position/size when not maximized

* fix: use getDialogGeometry() as reliable discriminator for saved state

Replaces savedPos.isNull() check with getDialogGeometry() non-empty check
to properly handle cases where position QPoint(0,0) was legitimately saved.

* fix: avoid saving maximized dialog position/size in all dialogs

Apply same guard pattern as ConfigDialog to KeygenDialog and UsersDialog:
only save pos()/size() when not maximized.

* fix: guard dialog position/size restore with geometry check in all dialogs

Apply same pattern as ConfigDialog to KeygenDialog and UsersDialog:
only restore pos()/size() if saved geometry exists.

0 of 21 new or added lines in 3 files covered. (0.0%)

4 existing lines in 3 files now uncovered.

1109 of 5283 relevant lines covered (20.99%)

7.78 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

27
  // Restore dialog state
NEW
28
  QByteArray savedGeometry = QtPassSettings::getDialogGeometry("usersDialog");
×
29
  bool hasSavedGeometry = !savedGeometry.isEmpty();
NEW
30
  if (hasSavedGeometry) {
×
NEW
31
    restoreGeometry(savedGeometry);
×
32
  }
33
  if (QtPassSettings::isDialogMaximized("usersDialog")) {
×
34
    showMaximized();
×
NEW
35
  } else if (hasSavedGeometry) {
×
36
    move(QtPassSettings::getDialogPos("usersDialog"));
×
37
    resize(QtPassSettings::getDialogSize("usersDialog"));
×
38
  } else {
39
    // Let window manager handle positioning for first launch
40
  }
41

42
  QList<UserInfo> users = QtPassSettings::getPass()->listKeys();
×
43
  if (users.isEmpty()) {
×
44
    QMessageBox::critical(parent, tr("Keylist missing"),
×
45
                          tr("Could not fetch list of available GPG keys"));
×
46
    reject();
×
47
    return;
48
  }
49

50
  QList<UserInfo> secret_keys = QtPassSettings::getPass()->listKeys("", true);
×
51
  QSet<QString> secretKeyIds;
52
  for (const UserInfo &sec : secret_keys) {
×
53
    secretKeyIds.insert(sec.key_id);
×
54
  }
55
  for (auto &user : users) {
×
56
    if (secretKeyIds.contains(user.key_id)) {
×
57
      user.have_secret = true;
×
58
    }
59
  }
60

61
  QList<UserInfo> selected_users;
×
62
  int count = 0;
×
63

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

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

97
  m_userList = users;
98
  populateList();
×
99

100
  connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
×
101
          &UsersDialog::accept);
×
102
  connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
103
  connect(ui->listWidget, &QListWidget::itemChanged, this,
×
104
          &UsersDialog::itemChange);
×
105

106
  ui->lineEdit->setClearButtonEnabled(true);
×
107
}
×
108

109
/**
110
 * @brief UsersDialog::~UsersDialog basic destructor.
111
 */
112
UsersDialog::~UsersDialog() { delete ui; }
×
113

114
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
115
Q_DECLARE_METATYPE(UserInfo *)
116
Q_DECLARE_METATYPE(const UserInfo *)
117
#endif
118

119
/**
120
 * @brief UsersDialog::accept
121
 */
122
void UsersDialog::accept() {
×
123
  QtPassSettings::getPass()->Init(m_dir, m_userList);
×
124

125
  QDialog::accept();
×
126
}
×
127

128
/**
129
 * @brief UsersDialog::closeEvent save window state on close.
130
 * @param event
131
 */
132
void UsersDialog::closeEvent(QCloseEvent *event) {
×
133
  QtPassSettings::setDialogGeometry("usersDialog", saveGeometry());
×
NEW
134
  if (!isMaximized()) {
×
NEW
135
    QtPassSettings::setDialogPos("usersDialog", pos());
×
NEW
136
    QtPassSettings::setDialogSize("usersDialog", size());
×
137
  }
UNCOV
138
  QtPassSettings::setDialogMaximized("usersDialog", isMaximized());
×
139
  event->accept();
140
}
×
141

142
/**
143
 * @brief UsersDialog::keyPressEvent clear the lineEdit when escape is pressed.
144
 * No action for Enter currently.
145
 * @param event
146
 */
147
void UsersDialog::keyPressEvent(QKeyEvent *event) {
×
148
  switch (event->key()) {
×
149
  case Qt::Key_Escape:
×
150
    ui->lineEdit->clear();
×
151
    break;
×
152
  default:
153
    break;
154
  }
155
}
×
156

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

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

200
  for (int i = 0; i < m_userList.size(); ++i) {
×
201
    const auto &user = m_userList.at(i);
202
    if (!passesFilter(user, filter, nameFilter)) {
×
203
      continue;
×
204
    }
205

206
    auto *item = new QListWidgetItem(buildUserText(user), ui->listWidget);
×
207
    applyUserStyling(item, user);
×
208
    item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
×
209
    item->setData(Qt::UserRole, QVariant::fromValue(i));
×
210
    ui->listWidget->addItem(item);
×
211
  }
212
}
×
213

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

233
/**
234
 * @brief Checks if a user's key has expired.
235
 * @param user User to check
236
 * @return true if user's key is expired
237
 */
238
auto UsersDialog::isUserExpired(const UserInfo &user) const -> bool {
×
239
  return user.expiry.toSecsSinceEpoch() > 0 &&
×
240
         QDateTime::currentDateTime() > user.expiry;
×
241
}
242

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

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

291
/**
292
 * @brief UsersDialog::on_lineEdit_textChanged typing in the searchbox.
293
 * @param filter
294
 */
295
void UsersDialog::on_lineEdit_textChanged(const QString &filter) {
×
296
  populateList(filter);
×
297
}
×
298

299
/**
300
 * @brief UsersDialog::on_checkBox_clicked filtering.
301
 */
302
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