Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34123547
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
17 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment