Page MenuHome GnuPG

No OneTemporary

diff --git a/src/utils/accessibility.cpp b/src/utils/accessibility.cpp
index 51234100d..84852f22d 100644
--- a/src/utils/accessibility.cpp
+++ b/src/utils/accessibility.cpp
@@ -1,46 +1,53 @@
/* utils/accessibility.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "accessibility.h"
#include <KLocalizedString>
#include <QAccessible>
#include <QObject>
namespace
{
QString getAccessibleText(QObject *object, QAccessible::Text t)
{
QString name;
if (const auto *const iface = QAccessible::queryAccessibleInterface(object)) {
name = iface->text(t);
}
return name;
}
}
QString Kleo::getAccessibleName(QObject *object)
{
return getAccessibleText(object, QAccessible::Name);
}
QString Kleo::getAccessibleDescription(QObject *object)
{
return getAccessibleText(object, QAccessible::Description);
}
QString Kleo::invalidEntryText()
{
return i18nc("text for screen readers to indicate that the associated object, "
"such as a form field, has an error",
"invalid entry");
}
+
+QString Kleo::requiredText()
+{
+ return i18nc("text for screen readers to indicate that the associated object, "
+ "such as a form field must be filled out",
+ "required");
+}
diff --git a/src/utils/accessibility.h b/src/utils/accessibility.h
index 54fcac62d..55980a5b0 100644
--- a/src/utils/accessibility.h
+++ b/src/utils/accessibility.h
@@ -1,19 +1,20 @@
/* utils/accessibility.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
class QObject;
class QString;
namespace Kleo
{
QString getAccessibleName(QObject *object);
QString getAccessibleDescription(QObject *object);
QString invalidEntryText();
+ QString requiredText();
}
diff --git a/src/view/formtextinput.cpp b/src/view/formtextinput.cpp
index dad113d09..946e77bab 100644
--- a/src/view/formtextinput.cpp
+++ b/src/view/formtextinput.cpp
@@ -1,237 +1,309 @@
/* view/formtextinput.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "formtextinput.h"
#include "errorlabel.h"
#include "utils/accessibility.h"
#include <KLocalizedString>
#include <QLabel>
#include <QLineEdit>
#include <QPointer>
#include <QValidator>
#include "kleopatra_debug.h"
+namespace
+{
+auto defaultValueRequiredErrorMessage()
+{
+ return i18n("Error: A value is required.");
+}
+
+auto defaultInvalidEntryErrorMessage()
+{
+ return i18n("Error: The entered text is not valid.");
+}
+}
+
namespace Kleo::_detail
{
class FormTextInputBase::Private
{
FormTextInputBase *q;
public:
+ enum Error
+ {
+ EntryOK,
+ EntryMissing, // a required entry is missing
+ InvalidEntry // the validator doesn't accept the entry
+ };
+
Private(FormTextInputBase *q)
: q{q}
- , mErrorMessage{i18n("Error: The entered text is not valid.")}
+ , mValueRequiredErrorMessage{defaultValueRequiredErrorMessage()}
+ , mInvalidEntryErrorMessage{defaultInvalidEntryErrorMessage()}
{}
- QString errorMessage() const;
+ QString errorMessage(Error error) const;
void updateError();
void updateAccessibleNameAndDescription();
QPointer<QLabel> mLabel;
QPointer<QWidget> mWidget;
QPointer<ErrorLabel> mErrorLabel;
QPointer<const QValidator> mValidator;
QString mAccessibleName;
QString mAccessibleDescription;
- QString mErrorMessage;
+ QString mValueRequiredErrorMessage;
+ QString mInvalidEntryErrorMessage;
+ Error mError = EntryOK;
+ bool mRequired = false;
bool mEditingInProgress = false;
};
-QString FormTextInputBase::Private::errorMessage() const
+QString FormTextInputBase::Private::errorMessage(Error error) const
{
- return q->hasAcceptableInput() ? QString{} : mErrorMessage;
+ switch (error) {
+ case EntryOK:
+ return {};
+ case EntryMissing:
+ return mValueRequiredErrorMessage;
+ case InvalidEntry:
+ return mInvalidEntryErrorMessage;
+ }
+ return {};
}
void FormTextInputBase::Private::updateError()
{
if (!mErrorLabel) {
return;
}
+
+ if (mRequired && !q->hasValue()) {
+ mError = EntryMissing;
+ } else if (!q->hasAcceptableInput()) {
+ mError = InvalidEntry;
+ } else {
+ mError = EntryOK;
+ }
+
const auto currentErrorMessage = mErrorLabel->text();
- const auto newErrorMessage = errorMessage();
+ const auto newErrorMessage = errorMessage(mError);
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && mEditingInProgress) {
// delay showing the error message until editing is finished, so that we
// do not annoy the user with an error message while they are still
// entering the recipient;
// on the other hand, we clear the error message immediately if it does
// not apply anymore and we update the error message immediately if it
// changed
return;
}
mErrorLabel->setVisible(!newErrorMessage.isEmpty());
mErrorLabel->setText(newErrorMessage);
updateAccessibleNameAndDescription();
}
void FormTextInputBase::Private::updateAccessibleNameAndDescription()
{
// fall back to default accessible name/description if accessible name/description wasn't set explicitly
if (mAccessibleName.isEmpty()) {
mAccessibleName = getAccessibleName(mWidget);
}
if (mAccessibleDescription.isEmpty()) {
mAccessibleDescription = getAccessibleDescription(mWidget);
}
const bool errorShown = mErrorLabel && mErrorLabel->isVisible();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by adding the error message to the accessible description of the input field
const auto description = errorShown ? mAccessibleDescription + QLatin1String{" "} + mErrorLabel->text()
: mAccessibleDescription;
if (mWidget && mWidget->accessibleDescription() != description) {
mWidget->setAccessibleDescription(description);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid entry" if this state is set;
// emulate this by adding "invalid entry" to the accessible name of the input field
// and its label
- const auto name = errorShown ? mAccessibleName + QLatin1String{", "} + invalidEntryText()
- : mAccessibleName;
+ QString name = mAccessibleName;
+ if (mRequired) {
+ name += QLatin1String{", "} + requiredText();
+ }
+ if (errorShown) {
+ name += QLatin1String{", "} + invalidEntryText();
+ };
if (mLabel && mLabel->accessibleName() != name) {
mLabel->setAccessibleName(name);
}
if (mWidget && mWidget->accessibleName() != name) {
mWidget->setAccessibleName(name);
}
}
FormTextInputBase::FormTextInputBase()
: d{new Private{this}}
{
}
FormTextInputBase::~FormTextInputBase() = default;
QWidget *FormTextInputBase::widget() const
{
return d->mWidget;
}
QLabel *FormTextInputBase::label() const
{
return d->mLabel;
}
ErrorLabel *FormTextInputBase::errorLabel() const
{
return d->mErrorLabel;
}
+void FormTextInputBase::setIsRequired(bool required)
+{
+ d->mRequired = required;
+}
+
+bool FormTextInputBase::isRequired() const
+{
+ return d->mRequired;
+}
+
void FormTextInputBase::setValidator(const QValidator *validator)
{
d->mValidator = validator;
}
-void FormTextInputBase::setErrorMessage(const QString &text)
+void FormTextInputBase::setValueRequiredErrorMessage(const QString &text)
+{
+ if (text.isEmpty()) {
+ d->mValueRequiredErrorMessage = defaultValueRequiredErrorMessage();
+ } else {
+ d->mValueRequiredErrorMessage = text;
+ }
+}
+
+void FormTextInputBase::setInvalidEntryErrorMessage(const QString &text)
{
if (text.isEmpty()) {
- d->mErrorMessage = i18n("Error: The entered text is not valid.");
+ d->mInvalidEntryErrorMessage = defaultInvalidEntryErrorMessage();
} else {
- d->mErrorMessage = text;
+ d->mInvalidEntryErrorMessage = text;
}
}
void FormTextInputBase::setToolTip(const QString &toolTip)
{
if (d->mLabel) {
d->mLabel->setToolTip(toolTip);
}
if (d->mWidget) {
d->mWidget->setToolTip(toolTip);
}
}
void FormTextInputBase::setAccessibleName(const QString &name)
{
d->mAccessibleName = name;
d->updateAccessibleNameAndDescription();
}
void FormTextInputBase::setAccessibleDescription(const QString &description)
{
d->mAccessibleDescription = description;
d->updateAccessibleNameAndDescription();
}
void FormTextInputBase::setWidget(QWidget *widget)
{
auto parent = widget ? widget->parentWidget() : nullptr;
d->mWidget = widget;
d->mLabel = new QLabel{parent};
d->mErrorLabel = new ErrorLabel{parent};
if (d->mLabel) {
d->mLabel->setBuddy(d->mWidget);
}
if (d->mErrorLabel) {
d->mErrorLabel->setVisible(false);
}
connectWidget();
}
void FormTextInputBase::setEnabled(bool enabled)
{
if (d->mLabel) {
d->mLabel->setEnabled(enabled);
}
if (d->mWidget) {
d->mWidget->setEnabled(enabled);
}
if (d->mErrorLabel) {
d->mErrorLabel->setVisible(enabled && !d->mErrorLabel->text().isEmpty());
}
}
bool FormTextInputBase::validate(const QString &text, int pos) const
{
QString textCopy = text;
if (d->mValidator && d->mValidator->validate(textCopy, pos) != QValidator::Acceptable)
{
return false;
}
return true;
}
void FormTextInputBase::onTextChanged()
{
d->mEditingInProgress = true;
d->updateError();
}
void FormTextInputBase::onEditingFinished()
{
d->mEditingInProgress = false;
d->updateError();
}
}
+template<>
+bool Kleo::FormTextInput<QLineEdit>::hasValue() const
+{
+ const auto w = widget();
+ return w && !w->text().trimmed().isEmpty();
+}
+
template<>
bool Kleo::FormTextInput<QLineEdit>::hasAcceptableInput() const
{
const auto w = widget();
return w && validate(w->text(), w->cursorPosition());
}
template<>
void Kleo::FormTextInput<QLineEdit>::connectWidget()
{
const auto w = widget();
QObject::connect(w, &QLineEdit::editingFinished,
w, [this]() { onEditingFinished(); });
QObject::connect(w, &QLineEdit::textChanged,
w, [this]() { onTextChanged(); });
}
diff --git a/src/view/formtextinput.h b/src/view/formtextinput.h
index 69810fa2a..1a75a65bb 100644
--- a/src/view/formtextinput.h
+++ b/src/view/formtextinput.h
@@ -1,190 +1,220 @@
/* view/formtextinput.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <memory>
class QLabel;
class QLineEdit;
class QString;
class QValidator;
class QWidget;
namespace Kleo
{
class ErrorLabel;
namespace _detail
{
class FormTextInputBase
{
protected:
FormTextInputBase();
public:
virtual ~FormTextInputBase();
FormTextInputBase(const FormTextInputBase&) = delete;
FormTextInputBase& operator=(const FormTextInputBase&) = delete;
FormTextInputBase(FormTextInputBase&&) = delete;
FormTextInputBase& operator=(FormTextInputBase&&) = delete;
/**
* Returns the label associated to the controlled widget.
*/
QLabel *label() const;
/**
* Returns the error label associated to the controlled widget.
*/
ErrorLabel *errorLabel() const;
+ /**
+ * Marks this input field as required.
+ */
+ void setIsRequired(bool required);
+
+ /**
+ * Returns \c true, if this field needs to be filled out.
+ */
+ bool isRequired() const;
+
/**
* Sets the validator to use for validating the input.
*
* Note: If you wrap a QLineEdit, then do not set a validator (or an input mask)
* on it because this will break the correct displaying of the error message.
*/
void setValidator(const QValidator *validator);
/**
- * Sets the error message to display. If \p text is empty, then the default
- * error message will be used.
+ * Sets the error message to display if a value is required for this input field,
+ * but if no value has been entered. If \p text is empty, then a default
+ * message will be used.
+ */
+ void setValueRequiredErrorMessage(const QString &text);
+
+ /**
+ * Sets the error message to display if the entered value is not accepted
+ * by the validator. If \p text is empty, then a default message will be used.
*/
- void setErrorMessage(const QString &text);
+ void setInvalidEntryErrorMessage(const QString &text);
/**
* Sets the tool tip of the controlled widget and its associated label.
*/
void setToolTip(const QString &toolTip);
/**
* Sets the accessible name of the controlled widget. Use this function
* instead of setting the accessible name directly on the controlled widget.
*/
void setAccessibleName(const QString &name);
/**
* Sets the accessible description of the controlled widget. Use this
* function instead of setting the accessible description directly on
* the controlled widget.
*/
void setAccessibleDescription(const QString &description);
/**
* Enables or disables the controlled widget and its associated label.
* If the widget is disables, then the error label is hidden. Otherwise,
* the error label is shown if there is an error.
*/
void setEnabled(bool enabled);
+ /**
+ * Returns \c true, if the input has a value. This function is used to
+ * check required input fields for non-empty user input.
+ * Needs to be implemented for concrete widget classes.
+ * \sa validate
+ */
+ virtual bool hasValue() const = 0;
+
/**
* Returns \c true, if the input satisfies the validator.
* Needs to be implemented for concrete widget classes.
* \sa validate
*/
virtual bool hasAcceptableInput() const = 0;
protected:
/**
* Connects the slots \ref onTextChanged and \ref onEditingFinished to the
* corresponding signal of the controlled widget.
* Needs to be implemented for concrete widget classes.
*/
virtual void connectWidget() = 0;
/**
* Sets the controlled widget and creates the associated labels.
*/
void setWidget(QWidget *widget);
/**
* Returns the controlled widget.
*/
QWidget *widget() const;
/**
* Validates \p text with the validator. Should be used when implementing
* \ref hasAcceptableInput.
*/
bool validate(const QString &text, int pos) const;
/**
* This slot needs to be connected to a signal of the controlled widget
* that is emitted when the text changes like \ref QLineEdit::textChanged.
* \sa connectWidget
*/
void onTextChanged();
/**
* This slot needs to be connected to a signal of the controlled widget
* that is emitted when the widget loses focus (or some user interaction
* signals that they want to commit the entered text) like
* \ref QLineEdit::editingFinished.
* \sa connectWidget
*/
void onEditingFinished();
private:
class Private;
const std::unique_ptr<Private> d;
};
}
/**
* FormTextInput is a class for simplifying the management of text input widgets
* like QLineEdit or QTextEdit with associated label and error message for usage
* in form-like dialogs.
*
* Usage hints:
* * If you wrap a QLineEdit, then do not set a validator (or an input mask)
* on it. Instead set the validator on this class.
* If you set a validator on the QLineEdit, then showing the error message
* when editing is finished does not work because QLineEdit doesn't emit the
* editingFinished() signal if the input is not acceptable.
*/
template<class Widget>
class FormTextInput : public _detail::FormTextInputBase
{
/**
* Use \ref create to create a new instance.
*/
FormTextInput() = default;
public:
/**
* Creates a new instance of this class with a new instance of \p Widget.
*/
static auto create(QWidget *parent)
{
std::unique_ptr<FormTextInput> self{new FormTextInput};
self->setWidget(new Widget{parent});
return self;
}
/**
* Returns the controlled widget.
*/
Widget *widget() const
{
return static_cast<Widget *>(FormTextInputBase::widget());
}
+ bool hasValue() const override;
+
bool hasAcceptableInput() const override;
private:
void connectWidget() override;
};
+template<>
+bool FormTextInput<QLineEdit>::hasValue() const;
+
template<>
bool FormTextInput<QLineEdit>::hasAcceptableInput() const;
template<>
void FormTextInput<QLineEdit>::connectWidget();
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Dec 6, 10:53 PM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
d3/49/bb37ba84f3ba84ee03a5549b22fb

Event Timeline