From b4a21d7acacf1f0be2d42cdb41fb7ef6403579e7 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Sun, 14 Mar 2021 13:30:26 +0100 Subject: [PATCH] [libcalamares] Add macro CONFIG_PREVENT_EDITING to handle uneditable fields Boilerplate code for avoiding accidental setting of an internal field when the UI is editable and the underlying data isn't. --- src/libcalamares/modulesystem/Config.h | 40 ++++++++++++++++++++++++++ src/modules/users/Config.cpp | 15 ++-------- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/libcalamares/modulesystem/Config.h b/src/libcalamares/modulesystem/Config.h index c31433e1c..5facae3cd 100644 --- a/src/libcalamares/modulesystem/Config.h +++ b/src/libcalamares/modulesystem/Config.h @@ -54,6 +54,25 @@ public Q_SLOTS: * to not-editable, returns @c false. Otherwise, return @c true. * Calling this with an unknown field (one for which no presets * are accepted) will print a warning and return @c true. + * + * @see CONFIG_PREVENT_EDITING + * + * Most setters will call isEditable() to check if the field should + * be editable. Do not count on the setter not being called: the + * UI might not have set the field to fixed / constant / not-editable + * and then you can have the setter called by changes in the UI. + * + * To prevent the UI from changing **and** to make sure that the UI + * reflects the unchanged value (rather than the changed value it + * sent to the Config object), use CONFIG_PREVENT_EDITING, like so: + * + * CONFIG_PREVENT_EDITING( type, "propertyName" ); + * + * The ; is necessary. is the type of the property; for instance + * QString. The name of the property is a (constant) string. The + * macro will return (out of the setter it is used in) if the field + * is not editable, and will send a notification event with the old + * value as soon as the event loop resumes. */ bool isEditable( const QString& fieldName ) const; @@ -104,4 +123,25 @@ private: } // namespace ModuleSystem } // namespace Calamares +/// @see Config::isEditable() +// +// This needs to be a macro, because Q_ARG() is a macro that stringifies +// the type name. +#define CONFIG_PREVENT_EDITING( type, fieldName ) \ + do \ + { \ + if ( !isEditable( QStringLiteral( fieldName ) ) ) \ + { \ + auto prop = property( fieldName ); \ + const auto& metaobject = metaObject(); \ + auto metaprop = metaobject->property( metaobject->indexOfProperty( fieldName ) ); \ + if ( metaprop.hasNotifySignal() ) \ + { \ + metaprop.notifySignal().invoke( this, Qt::QueuedConnection, Q_ARG( type, prop.value< type >() ) ); \ + } \ + return; \ + } \ + } while ( 0 ) + + #endif diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index 71bd48725..7aa1cb65c 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -184,12 +185,7 @@ Config::setSudoersGroup( const QString& group ) void Config::setLoginName( const QString& login ) { - if ( !isEditable( QStringLiteral( "loginName" ) ) ) - { - // Should not have arrived here anyway - QTimer::singleShot( 0, this, [=]() { emit loginNameChanged( m_loginName ); } ); - return; - } + CONFIG_PREVENT_EDITING( QString, "loginName" ); if ( login != m_loginName ) { m_customLoginName = !login.isEmpty(); @@ -400,12 +396,7 @@ makeHostnameSuggestion( const QStringList& parts ) void Config::setFullName( const QString& name ) { - if ( !isEditable( QStringLiteral( "fullName" ) ) ) - { - // Should not have arrived here anyway - QTimer::singleShot( 0, this, [=]() { emit fullNameChanged( m_fullName ); } ); - return; - } + CONFIG_PREVENT_EDITING( QString, "fullName" ); if ( name.isEmpty() && !m_fullName.isEmpty() ) {