i18n: re-do the whole Retranslator infrastructure

Just have **one** Retranslator object, and install it as event-filter
(this needs to be done manually on a top-level widget) and use
signals / slots to do the actual work, rather than filtering
in multiple places and doing our own mediocre version of binding-
signal-to-lambda.
This commit is contained in:
Adriaan de Groot 2021-07-24 12:01:30 +02:00
parent f32671ebab
commit 613d076a60
5 changed files with 76 additions and 53 deletions

18
CHANGES
View File

@ -7,6 +7,24 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions. website will have to do for older versions.
# 3.2.41 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Anke Boersma
## Core ##
- The (re)translation framework has been internally re-vamped to be
less resource-intensive and to work with all QObjects, not just
widgets. Consumers of the translations framework are expected to
set up the event filter on the top-level widget(s) manually.
## Modules ##
- The *usersq* module has had a fair bit of QML rewritten to make it easier
to customize the colors used by the module in a consistent way.
(Thanks Anke)
# 3.2.40 (2021-07-14) # # 3.2.40 (2021-07-14) #
This release contains contributions from (alphabetically by first name): This release contains contributions from (alphabetically by first name):

View File

@ -357,6 +357,8 @@ CalamaresWindow::CalamaresWindow( QWidget* parent )
, m_debugManager( new Calamares::DebugWindowManager( this ) ) , m_debugManager( new Calamares::DebugWindowManager( this ) )
, m_viewManager( nullptr ) , m_viewManager( nullptr )
{ {
installEventFilter( CalamaresUtils::Retranslator::instance() );
// If we can never cancel, don't show the window-close button // If we can never cancel, don't show the window-close button
if ( Calamares::Settings::instance()->disableCancel() ) if ( Calamares::Settings::instance()->disableCancel() )
{ {

View File

@ -24,6 +24,7 @@
#include "modulesystem/ModuleManager.h" #include "modulesystem/ModuleManager.h"
#include "modulesystem/ViewModule.h" #include "modulesystem/ViewModule.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "utils/Retranslator.h"
#include "utils/Yaml.h" #include "utils/Yaml.h"
#include "viewpages/ExecutionViewStep.h" #include "viewpages/ExecutionViewStep.h"
@ -489,6 +490,10 @@ main( int argc, char* argv[] )
aw = replace_app; aw = replace_app;
} }
mw = module.m_ui ? new QMainWindow() : nullptr; mw = module.m_ui ? new QMainWindow() : nullptr;
if ( mw )
{
mw->installEventFilter( CalamaresUtils::Retranslator::instance() );
}
(void)new Calamares::Branding( module.m_branding ); (void)new Calamares::Branding( module.m_branding );
auto* modulemanager = new Calamares::ModuleManager( QStringList(), nullptr ); auto* modulemanager = new Calamares::ModuleManager( QStringList(), nullptr );

View File

@ -211,55 +211,29 @@ loadTranslator( const QLocale& locale, const QString& prefix, QTranslator* trans
return ::tryLoad( translator, prefix, locale.name() ); return ::tryLoad( translator, prefix, locale.name() );
} }
Retranslator*
Retranslator::retranslatorFor( QObject* parent )
{
Retranslator* r = nullptr;
for ( QObject* child : parent->children() )
{
r = qobject_cast< Retranslator* >( child );
if ( r )
{
return r;
}
}
return new Retranslator( parent );
}
void
Retranslator::attachRetranslator( QObject* parent, std::function< void( void ) > retranslateFunc )
{
retranslatorFor( parent )->m_retranslateFuncList.append( retranslateFunc );
retranslateFunc();
}
Retranslator::Retranslator( QObject* parent ) Retranslator::Retranslator( QObject* parent )
: QObject( parent ) : QObject( parent )
{ {
parent->installEventFilter( this );
} }
bool bool
Retranslator::eventFilter( QObject* obj, QEvent* e ) Retranslator::eventFilter( QObject* obj, QEvent* e )
{ {
if ( obj == parent() ) if ( e->type() == QEvent::LanguageChange )
{ {
if ( e->type() == QEvent::LanguageChange ) emit languageChanged();
{
foreach ( std::function< void() > func, m_retranslateFuncList )
{
func();
}
emit languageChange();
}
} }
// pass the event on to the base // pass the event on to the base
return QObject::eventFilter( obj, e ); return QObject::eventFilter( obj, e );
} }
Retranslator* Retranslator::instance()
{
static Retranslator s_instance(nullptr);
return &s_instance;
}
void void
setAllowLocalTranslation( bool allow ) setAllowLocalTranslation( bool allow )
{ {

View File

@ -64,40 +64,64 @@ DLLEXPORT bool loadTranslator( const QLocale& locale, const QString& prefix, QTr
*/ */
DLLEXPORT void setAllowLocalTranslation( bool allow ); DLLEXPORT void setAllowLocalTranslation( bool allow );
/** @brief Handles change-of-language events
*
* There is one single Retranslator object. Use `instance()` to get it.
* The top-level widget of the application should call
* `installEventFilter( Retranslator::instance() )`
* to set up event-handling for translation events. The Retranslator
* will emit signal `languageChanged()` if there is such an event.
*
* Normal consumers should not have to use the Retranslator directly,
* but use the macros `CALAMARES_RETRANSLATE*` to set things up
* in code -- the macros will connect to the Retranslator's signals.
*/
class Retranslator : public QObject class Retranslator : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
/// @brief Call @p retranslateFunc when the language changes static Retranslator* instance();
static void attachRetranslator( QObject* parent, std::function< void( void ) > retranslateFunc );
/// @brief What retranslator belongs to @p parent (may create one)
static Retranslator* retranslatorFor( QObject* parent );
signals: signals:
void languageChange(); void languageChanged();
protected: protected:
bool eventFilter( QObject* obj, QEvent* e ) override; bool eventFilter( QObject* obj, QEvent* e ) override;
private: private:
explicit Retranslator( QObject* parent ); explicit Retranslator( QObject* parent );
QList< std::function< void( void ) > > m_retranslateFuncList;
}; };
} // namespace CalamaresUtils } // namespace CalamaresUtils
#define CALAMARES_RETRANSLATE( body ) CalamaresUtils::Retranslator::attachRetranslator( this, [=] { body } ) // Implementation detail: connects the retranslator to a slot or function
#define CALAMARES_RETRANSLATE_WIDGET( widget, body ) \ #define CALAMARES_RETRANSLATE_FOR( object, body ) \
CalamaresUtils::Retranslator::attachRetranslator( widget, [=] { body } ) QObject::connect( CalamaresUtils::Retranslator::instance(), &CalamaresUtils::Retranslator::languageChanged, object, body )
#define CALAMARES_RETRANSLATE_SLOT( slotfunc ) \
do \ /** @brief Call code for this object when language changes
{ \ *
this->connect( CalamaresUtils::Retranslator::retranslatorFor( this ), \ * @p body should be a code block (it does not need braces) that can be wrapped
&CalamaresUtils::Retranslator::languageChange, \ * up as a lambda. When the language changes, the lambda is called. Note that
this, \ * this macro should be used in constructors or other code that is run only
slotfunc ); \ * once, since otherwise you will end up with multiple calls to @p body.
} while ( 0 ) */
#define CALAMARES_RETRANSLATE( body ) CALAMARES_RETRANSLATE_FOR( this, [=] { body } )
/** @brief Call code for the given object (widget) when language changes
*
* This is identical to CALAMARES_RETRANSLATE, except the @p body is called
* for the given widget, not this object.
*
* NOTE: this macro is deprecated.
*/
#define CALAMARES_RETRANSLATE_WIDGET( widget, body ) CALAMARES_RETRANSLATE_FOR( widget, [=] { body } )
/** @brief Call a slot in this object when language changes
*
* Given a slot (in method-function-pointer notation), call that slot when the
* language changes. This is shorthand for connecting the Retranslator's
* signal to the given slot.
*/
#define CALAMARES_RETRANSLATE_SLOT( slotfunc ) CALAMARES_RETRANSLATE_FOR( this, slotfunc )
#endif #endif