keepassxc/src/gui/PasswordEdit.cpp

211 lines
7.3 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
2017-06-09 21:40:36 +00:00
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PasswordEdit.h"
#include "core/Config.h"
#include "core/Resources.h"
#include "gui/Font.h"
#include "gui/PasswordGeneratorWidget.h"
2019-10-21 08:49:09 +00:00
#include "gui/osutils/OSUtils.h"
#include "gui/styles/StateColorPalette.h"
#include <QDialog>
2019-10-21 08:49:09 +00:00
#include <QTimer>
#include <QToolTip>
#include <QVBoxLayout>
PasswordEdit::PasswordEdit(QWidget* parent)
: QLineEdit(parent)
{
const QIcon errorIcon = resources()->icon("dialog-error");
m_errorAction = addAction(errorIcon, QLineEdit::TrailingPosition);
m_errorAction->setVisible(false);
m_errorAction->setToolTip(tr("Passwords do not match"));
const QIcon correctIcon = resources()->icon("dialog-ok");
m_correctAction = addAction(correctIcon, QLineEdit::TrailingPosition);
m_correctAction->setVisible(false);
m_correctAction->setToolTip(tr("Passwords match so far"));
setEchoMode(QLineEdit::Password);
// use a monospace font for the password field
QFont passwordFont = Font::fixedFont();
passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110);
setFont(passwordFont);
// Prevent conflicts with global Mac shortcuts (force Control on all platforms)
#ifdef Q_OS_MAC
auto modifier = Qt::META;
#else
auto modifier = Qt::CTRL;
#endif
m_toggleVisibleAction = new QAction(
resources()->icon("password-show-off"),
tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)),
nullptr);
m_toggleVisibleAction->setCheckable(true);
m_toggleVisibleAction->setShortcut(modifier + Qt::Key_H);
m_toggleVisibleAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_toggleVisibleAction, QLineEdit::TrailingPosition);
connect(m_toggleVisibleAction, &QAction::triggered, this, &PasswordEdit::setShowPassword);
m_passwordGeneratorAction = new QAction(
resources()->icon("password-generator"),
tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)),
nullptr);
m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G);
m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut);
addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition);
m_passwordGeneratorAction->setVisible(false);
2019-10-21 08:49:09 +00:00
m_capslockAction =
new QAction(resources()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)),
tr("Warning: Caps Lock enabled!"),
nullptr);
addAction(m_capslockAction, QLineEdit::LeadingPosition);
m_capslockAction->setVisible(false);
}
void PasswordEdit::setRepeatPartner(PasswordEdit* repeatEdit)
{
m_repeatPasswordEdit = repeatEdit;
m_repeatPasswordEdit->setParentPasswordEdit(this);
connect(this, SIGNAL(textChanged(QString)), m_repeatPasswordEdit, SLOT(autocompletePassword(QString)));
connect(this, SIGNAL(textChanged(QString)), m_repeatPasswordEdit, SLOT(updateRepeatStatus()));
connect(m_repeatPasswordEdit, SIGNAL(textChanged(QString)), m_repeatPasswordEdit, SLOT(updateRepeatStatus()));
}
void PasswordEdit::setParentPasswordEdit(PasswordEdit* parent)
{
m_parentPasswordEdit = parent;
// Hide actions
m_toggleVisibleAction->setVisible(false);
m_passwordGeneratorAction->setVisible(false);
}
2018-03-31 20:01:30 +00:00
void PasswordEdit::enablePasswordGenerator()
{
if (!m_passwordGeneratorAction->isVisible()) {
m_passwordGeneratorAction->setVisible(true);
connect(m_passwordGeneratorAction, &QAction::triggered, this, &PasswordEdit::popupPasswordGenerator);
}
}
void PasswordEdit::setShowPassword(bool show)
{
setEchoMode(show ? QLineEdit::Normal : QLineEdit::Password);
m_toggleVisibleAction->setIcon(resources()->icon(show ? "password-show-on" : "password-show-off"));
m_toggleVisibleAction->setChecked(show);
if (m_repeatPasswordEdit) {
m_repeatPasswordEdit->setEchoMode(show ? QLineEdit::Normal : QLineEdit::Password);
if (!config()->get(Config::Security_PasswordsRepeatVisible).toBool()) {
m_repeatPasswordEdit->setEnabled(!show);
m_repeatPasswordEdit->setText(text());
2018-03-31 20:01:30 +00:00
} else {
m_repeatPasswordEdit->setEnabled(true);
}
}
}
bool PasswordEdit::isPasswordVisible() const
{
return echoMode() == QLineEdit::Normal;
}
void PasswordEdit::popupPasswordGenerator()
{
auto generator = PasswordGeneratorWidget::popupGenerator(this);
generator->setPasswordVisible(isPasswordVisible());
generator->setPasswordLength(text().length());
connect(generator, SIGNAL(appliedPassword(QString)), SLOT(setText(QString)));
if (m_repeatPasswordEdit) {
connect(generator, SIGNAL(appliedPassword(QString)), m_repeatPasswordEdit, SLOT(setText(QString)));
}
}
void PasswordEdit::updateRepeatStatus()
{
static const auto stylesheetTemplate = QStringLiteral("QLineEdit { background: %1; }");
if (!m_parentPasswordEdit) {
return;
}
const auto otherPassword = m_parentPasswordEdit->text();
const auto password = text();
if (otherPassword != password) {
bool isCorrect = false;
StateColorPalette statePalette;
QColor color = statePalette.color(StateColorPalette::ColorRole::Error);
if (!password.isEmpty() && otherPassword.startsWith(password)) {
color = statePalette.color(StateColorPalette::ColorRole::Incomplete);
isCorrect = true;
}
setStyleSheet(stylesheetTemplate.arg(color.name()));
m_correctAction->setVisible(isCorrect);
m_errorAction->setVisible(!isCorrect);
} else {
m_correctAction->setVisible(false);
m_errorAction->setVisible(false);
setStyleSheet("");
}
}
void PasswordEdit::autocompletePassword(const QString& password)
{
if (!config()->get(Config::Security_PasswordsRepeatVisible).toBool() && echoMode() == QLineEdit::Normal) {
setText(password);
}
}
2019-10-21 08:49:09 +00:00
bool PasswordEdit::event(QEvent* event)
{
if (isVisible()) {
checkCapslockState();
}
return QLineEdit::event(event);
}
void PasswordEdit::checkCapslockState()
{
if (m_parentPasswordEdit) {
return;
}
bool newCapslockState = osUtils->isCapslockEnabled();
if (newCapslockState != m_capslockState) {
m_capslockState = newCapslockState;
m_capslockAction->setVisible(newCapslockState);
// Force repaint to avoid rendering glitches of QLineEdit contents
repaint();
emit capslockToggled(m_capslockState);
if (newCapslockState) {
QTimer::singleShot(
150, [this]() { QToolTip::showText(mapToGlobal(rect().bottomLeft()), m_capslockAction->text()); });
}
}
}