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

IJHack / QtPass / 27851479774

19 Jun 2026 10:35PM UTC coverage: 55.155%. First build
27851479774

Pull #1586

github

web-flow
Merge e7c4a8c20 into b0e76f340
Pull Request #1586: fix: null-check widget/layout in clear(); cache compiled regex

3 of 5 new or added lines in 1 file covered. (60.0%)

3691 of 6692 relevant lines covered (55.16%)

29.29 hits per line

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

74.04
/src/passworddisplaypanel.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3

4
/**
5
 * @class PasswordDisplayPanel
6
 * @brief Password-field rendering implementation.
7
 *
8
 * @see passworddisplaypanel.h
9
 */
10

11
#include "passworddisplaypanel.h"
12
#include "appsettings.h"
13
#include "qpushbuttonasqrcode.h"
14
#include "qpushbuttonshowpassword.h"
15
#include "qpushbuttonwithclipboard.h"
16
#include "util.h"
17

18
#include <QBoxLayout>
19
#include <QDesktopServices>
20
#include <QFrame>
21
#include <QGridLayout>
22
#include <QHBoxLayout>
23
#include <QIcon>
24
#include <QLabel>
25
#include <QLineEdit>
26
#include <QPalette>
27
#include <QPushButton>
28
#include <QRegularExpressionMatchIterator>
29
#include <QTextBrowser>
30
#include <QUrl>
31

32
PasswordDisplayPanel::PasswordDisplayPanel(QGridLayout *grid,
16✔
33
                                           QBoxLayout *container,
34
                                           QWidget *widgetParent,
35
                                           QObject *parent)
16✔
36
    : QObject(parent), m_grid(grid), m_container(container),
16✔
37
      m_widgetParent(widgetParent) {}
16✔
38

39
void PasswordDisplayPanel::clear() {
2✔
40
  while (m_grid->count() > 0) {
4✔
41
    QLayoutItem *item = m_grid->takeAt(0);
2✔
42
    if (item->widget()) {
2✔
43
      delete item->widget();
2✔
NEW
44
    } else if (item->layout()) {
×
NEW
45
      delete item->layout();
×
46
    }
47
    delete item;
2✔
48
  }
49
  m_container->setSpacing(0);
2✔
50
}
2✔
51

52
void PasswordDisplayPanel::displayFields(const QString &password,
3✔
53
                                         const NamedValues &namedValues,
54
                                         const AppSettings &s) {
55
  if (!password.isEmpty()) {
3✔
56
    // The password is hidden in addField when needed.
57
    addField(0, QObject::tr("Password"), password, s);
4✔
58
  }
59
  int position = 1;
60
  for (const NamedValue &nv : namedValues) {
4✔
61
    addField(position, nv.name, nv.value, s);
1✔
62
    ++position;
1✔
63
  }
64
  m_container->setSpacing(m_grid->count() == 0 ? 0 : 6);
5✔
65
}
3✔
66

67
void PasswordDisplayPanel::appendField(const QString &field,
1✔
68
                                       const QString &value,
69
                                       const AppSettings &s) {
70
  // Each row is two grid items (label + value frame), so the next free row is
71
  // count() / 2 — the same sequential scheme displayFields() uses.
72
  addField(m_grid->count() / 2, field, value, s);
1✔
73
}
1✔
74

75
void PasswordDisplayPanel::addField(int position, const QString &field,
4✔
76
                                    const QString &value,
77
                                    const AppSettings &s) {
78
  QString trimmedField = field.trimmed();
79
  QString trimmedValue = value.trimmed();
80

81
  // Scope every rule to the widget type so the transparent background does not
82
  // cascade into the field's standard context menu (a child QMenu), which would
83
  // otherwise render transparent too.
84
  const QString buttonStyle =
85
      "QPushButton { border-style: none; background: transparent; padding: 0; "
86
      "margin: 0; icon-size: 16px; color: inherit; }";
4✔
87

88
  // Combine the Copy button and the line edit in one widget
89
  auto *frame = new QFrame();
4✔
90
  QHBoxLayout *frameLayout = new QHBoxLayout();
4✔
91
  frameLayout->setContentsMargins(5, 2, 2, 2);
4✔
92
  frameLayout->setSpacing(0);
4✔
93
  frame->setLayout(frameLayout);
4✔
94
  if (s.clipBoardType != Enums::CLIPBOARD_NEVER) {
4✔
95
    auto *fieldLabel =
96
        new QPushButtonWithClipboard(trimmedValue, m_widgetParent);
×
97
    connect(fieldLabel, &QPushButtonWithClipboard::clicked, this,
×
98
            &PasswordDisplayPanel::copyRequested);
×
99

100
    fieldLabel->setStyleSheet(buttonStyle);
×
101
    frame->layout()->addWidget(fieldLabel);
×
102
  }
103

104
  if (s.useQrencode) {
4✔
105
    auto *qrbutton = new QPushButtonAsQRCode(trimmedValue, m_widgetParent);
×
106
    connect(qrbutton, &QPushButtonAsQRCode::clicked, this,
×
107
            &PasswordDisplayPanel::qrRequested);
×
108
    qrbutton->setStyleSheet(buttonStyle);
×
109
    frame->layout()->addWidget(qrbutton);
×
110
  }
111

112
  // Show an explicit "open in browser" button when the value is a safe
113
  // http(s) URL. The inline clickable link still works for URLs embedded in
114
  // prose; this button is the discoverable affordance for url fields.
115
  // Never on the password field: its value is a secret and must not be
116
  // surfaced in a tooltip or handed to the browser.
117
  if (trimmedField != QObject::tr("Password") &&
10✔
118
      Util::isLaunchableWebUrl(trimmedValue)) {
2✔
119
    auto *urlButton = new QPushButton(m_widgetParent);
1✔
120
    urlButton->setIcon(QIcon::fromTheme(QStringLiteral("applications-internet"),
2✔
121
                                        QIcon(":/icons/open-url.svg")));
2✔
122
    // Escape only for tooltip rendering (rich-text safe display). The launched
123
    // URL must remain the original validated value; HTML escaping would change
124
    // it.
125
    urlButton->setToolTip(
2✔
126
        QObject::tr("Open %1 in browser").arg(trimmedValue.toHtmlEscaped()));
2✔
127
    urlButton->setStyleSheet(buttonStyle);
1✔
128
    urlButton->setCursor(Qt::PointingHandCursor);
1✔
129
    connect(urlButton, &QPushButton::clicked, this, [trimmedValue]() {
1✔
130
      // Re-validate before launching (defence in depth: the value is
131
      // immutable here, but never hand an unvalidated string to the OS
132
      // URL handler).
133
      if (Util::isLaunchableWebUrl(trimmedValue)) {
×
134
        QDesktopServices::openUrl(QUrl(trimmedValue));
×
135
      }
136
    });
×
137
    frame->layout()->addWidget(urlButton);
1✔
138
  }
139

140
  // set the echo mode to password, if the field is "password"
141
  const QString lineStyle =
142
      s.useMonospace
4✔
143
          ? "QLineEdit, QTextBrowser { border-style: none; background: "
144
            "transparent; font-family: monospace; }"
145
          : "QLineEdit, QTextBrowser { border-style: none; background: "
146
            "transparent; }";
8✔
147

148
  // Fixed control height keeps line edits and action buttons aligned.
149
  constexpr int fieldHeight = 26;
150
  if (s.hidePassword && trimmedField == QObject::tr("Password")) {
4✔
151
    auto *passwordLineEdit = new QLineEdit();
×
152
    passwordLineEdit->setObjectName(trimmedField);
×
153
    passwordLineEdit->setText(trimmedValue);
×
154
    passwordLineEdit->setReadOnly(true);
×
155
    passwordLineEdit->setStyleSheet(lineStyle);
×
156
    passwordLineEdit->setContentsMargins(0, 0, 0, 0);
×
157
    passwordLineEdit->setEchoMode(QLineEdit::Password);
×
158
    auto *showButton =
159
        new QPushButtonShowPassword(passwordLineEdit, m_widgetParent);
×
160
    showButton->setStyleSheet(buttonStyle);
×
161
    showButton->setContentsMargins(0, 0, 0, 0);
×
162
    frame->layout()->addWidget(showButton);
×
163
    frame->layout()->addWidget(passwordLineEdit);
×
164
  } else {
165
    auto *contentTextBrowser = new QTextBrowser();
4✔
166
    contentTextBrowser->setOpenExternalLinks(true);
4✔
167
    contentTextBrowser->setOpenLinks(true);
4✔
168
    contentTextBrowser->setMaximumHeight(fieldHeight);
4✔
169
    contentTextBrowser->setMinimumHeight(fieldHeight);
4✔
170
    contentTextBrowser->setSizePolicy(
4✔
171
        QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
172
    contentTextBrowser->setObjectName(trimmedField);
4✔
173
    {
174
      QString linkedText;
4✔
175
      int lastIndex = 0;
176
      static const QRegularExpression re = Util::protocolRegex();
4✔
177
      QRegularExpressionMatchIterator it = re.globalMatch(trimmedValue);
4✔
178
      while (it.hasNext()) {
5✔
179
        const QRegularExpressionMatch match = it.next();
1✔
180
        const int start = match.capturedStart(0);
1✔
181
        const int end = match.capturedEnd(0);
1✔
182
        linkedText +=
183
            trimmedValue.mid(lastIndex, start - lastIndex).toHtmlEscaped();
1✔
184
        const QString escapedUrl = match.captured(0).toHtmlEscaped();
2✔
185
        linkedText += QStringLiteral("<a href=\"%1\">%1</a>").arg(escapedUrl);
2✔
186
        lastIndex = end;
187
      }
1✔
188
      linkedText += trimmedValue.mid(lastIndex).toHtmlEscaped();
4✔
189
      contentTextBrowser->setText(linkedText);
4✔
190
    }
4✔
191
    contentTextBrowser->setReadOnly(true);
4✔
192
    contentTextBrowser->setStyleSheet(lineStyle);
4✔
193
    contentTextBrowser->setContentsMargins(0, 0, 0, 0);
4✔
194
    frame->layout()->addWidget(contentTextBrowser);
4✔
195
  }
196

197
  // Derive the border colour from the palette so it adapts to light/dark
198
  // themes instead of a hardcoded light grey.
199
  const QString borderColor =
200
      m_widgetParent->palette().color(QPalette::Mid).name();
8✔
201
  frame->setStyleSheet(QStringLiteral(".QFrame{border: 1px solid %1; "
8✔
202
                                      "border-radius: 5px;}")
203
                           .arg(borderColor));
8✔
204

205
  // set into the layout
206
  m_grid->addWidget(new QLabel(trimmedField), position, 0);
4✔
207
  m_grid->addWidget(frame, position, 1);
4✔
208
}
4✔
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