/* * Copyright (C) 2014 Felix Geyer * Copyright (C) 2020 KeePassXC Team * * 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 . */ #include "PasswordEdit.h" #include "core/Config.h" #include "gui/Font.h" #include "gui/Icons.h" #include "gui/PasswordGeneratorWidget.h" #include "gui/osutils/OSUtils.h" #include "gui/styles/StateColorPalette.h" #include #include #include PasswordEdit::PasswordEdit(QWidget* parent) : QLineEdit(parent) { const QIcon errorIcon = icons()->icon("dialog-error"); m_errorAction = addAction(errorIcon, QLineEdit::TrailingPosition); m_errorAction->setVisible(false); m_errorAction->setToolTip(tr("Passwords do not match")); const QIcon correctIcon = icons()->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 constexpr auto modifier = Qt::MetaModifier; #else constexpr auto modifier = Qt::ControlModifier; #endif m_toggleVisibleAction = new QAction( icons()->onOffIcon("password-show", false), tr("Toggle Password (%1)").arg(QKeySequence(modifier + Qt::Key_H).toString(QKeySequence::NativeText)), this); 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( icons()->icon("password-generator"), tr("Generate Password (%1)").arg(QKeySequence(modifier + Qt::Key_G).toString(QKeySequence::NativeText)), this); m_passwordGeneratorAction->setShortcut(modifier + Qt::Key_G); m_passwordGeneratorAction->setShortcutContext(Qt::WidgetShortcut); addAction(m_passwordGeneratorAction, QLineEdit::TrailingPosition); m_passwordGeneratorAction->setVisible(false); m_capslockAction = new QAction(icons()->icon("dialog-warning", true, StateColorPalette().color(StateColorPalette::Error)), tr("Warning: Caps Lock enabled!"), this); 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); } 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(icons()->onOffIcon("password-show", show)); 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()); } 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); } } bool PasswordEdit::event(QEvent* event) { if (isVisible() && (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease || event->type() == QEvent::FocusIn)) { 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()); }); } else if (QToolTip::isVisible()) { QToolTip::hideText(); } } }