Line data Source code
1 : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2 : // SPDX-License-Identifier: GPL-3.0-or-later
3 :
4 : /**
5 : * @file
6 : * @brief Implements OtpCodeWidget: builds the code label, copy button and
7 : * countdown bar, and owns the one-second refresh timer.
8 : *
9 : * @see otpcodewidget.h
10 : */
11 :
12 : #include "otpcodewidget.h"
13 : #include "qpushbuttonwithclipboard.h"
14 :
15 : #include <QHBoxLayout>
16 : #include <QLabel>
17 : #include <QProgressBar>
18 : #include <QTimer>
19 : #include <utility>
20 :
21 : namespace {
22 :
23 : /// Refresh cadence: the countdown is shown in whole seconds.
24 : constexpr int kRefreshIntervalMs = 1000;
25 : /// Matches the action-button visual height used elsewhere in the field rows.
26 : constexpr int kProgressHeight = 18;
27 : /// Enough for a three-digit countdown without the bar dominating the row.
28 : constexpr int kProgressWidth = 52;
29 :
30 : } // namespace
31 :
32 15 : OtpCodeWidget::OtpCodeWidget(Totp::Settings settings, bool withCopyButton,
33 15 : bool monospace, QWidget *parent)
34 15 : : QWidget(parent), m_settings(std::move(settings)) {
35 : // Same borderless look as the action buttons PasswordDisplayPanel builds.
36 : const QString buttonStyle =
37 : "QPushButton { border-style: none; background: transparent; padding: 0; "
38 15 : "margin: 0; icon-size: 16px; color: inherit; }";
39 :
40 15 : auto *layout = new QHBoxLayout(this);
41 15 : layout->setContentsMargins(0, 0, 0, 0);
42 15 : layout->setSpacing(4);
43 :
44 15 : if (withCopyButton) {
45 6 : m_copyButton = new QPushButtonWithClipboard(QString(), this);
46 6 : m_copyButton->setStyleSheet(buttonStyle);
47 6 : connect(m_copyButton, &QPushButtonWithClipboard::clicked, this,
48 6 : &OtpCodeWidget::copyRequested);
49 6 : layout->addWidget(m_copyButton);
50 : }
51 :
52 15 : m_codeLabel = new QLabel(this);
53 30 : m_codeLabel->setObjectName(QStringLiteral("OTP code"));
54 15 : m_codeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
55 15 : if (monospace) {
56 0 : m_codeLabel->setStyleSheet("QLabel { font-family: monospace; }");
57 : }
58 15 : layout->addWidget(m_codeLabel);
59 15 : layout->addStretch();
60 :
61 15 : m_progress = new QProgressBar(this);
62 30 : m_progress->setObjectName(QStringLiteral("otpCountdown"));
63 15 : m_progress->setRange(0, static_cast<int>(m_settings.step));
64 : // Number-only format: no translatable string, and no unit to mistranslate.
65 30 : m_progress->setFormat(QStringLiteral("%v"));
66 15 : m_progress->setFixedWidth(kProgressWidth);
67 15 : m_progress->setMaximumHeight(kProgressHeight);
68 15 : m_progress->setToolTip(tr("Seconds until the OTP code changes"));
69 15 : layout->addWidget(m_progress);
70 :
71 : // The timer is a child of this widget: deleting the row stops it, so a
72 : // queued timeout can never reach a destroyed label.
73 15 : auto *timer = new QTimer(this);
74 15 : timer->setInterval(kRefreshIntervalMs);
75 15 : connect(timer, &QTimer::timeout, this,
76 15 : [this]() { refresh(Totp::currentUnixTime()); });
77 15 : timer->start();
78 :
79 15 : refresh(Totp::currentUnixTime());
80 15 : }
81 :
82 20 : void OtpCodeWidget::refresh(quint64 unixTime) {
83 20 : const quint64 counter = Totp::counter(m_settings, unixTime);
84 20 : if (!m_hasCode || counter != m_counter) {
85 20 : m_counter = counter;
86 20 : m_hasCode = true;
87 20 : m_code = Totp::generate(m_settings, unixTime);
88 20 : m_codeLabel->setText(m_code);
89 20 : if (m_copyButton != nullptr) {
90 7 : m_copyButton->setTextToCopy(m_code);
91 : }
92 : }
93 :
94 20 : m_secondsRemaining = Totp::secondsRemaining(m_settings, unixTime);
95 20 : m_progress->setValue(static_cast<int>(m_secondsRemaining));
96 20 : }
97 :
98 10 : auto OtpCodeWidget::code() const -> QString { return m_code; }
99 :
100 2 : auto OtpCodeWidget::secondsRemaining() const -> uint {
101 2 : return m_secondsRemaining;
102 : }
|