diff --git a/src/libcalamares/GlobalStorage.cpp b/src/libcalamares/GlobalStorage.cpp index d58a3b0c6..341fc3892 100644 --- a/src/libcalamares/GlobalStorage.cpp +++ b/src/libcalamares/GlobalStorage.cpp @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac * SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot * @@ -36,8 +36,8 @@ using CalamaresUtils::operator""_MiB; namespace Calamares { -GlobalStorage::GlobalStorage() - : QObject( nullptr ) +GlobalStorage::GlobalStorage( QObject* parent ) + : QObject( parent ) { } diff --git a/src/libcalamares/GlobalStorage.h b/src/libcalamares/GlobalStorage.h index e9ba1da8a..a2848f888 100644 --- a/src/libcalamares/GlobalStorage.h +++ b/src/libcalamares/GlobalStorage.h @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac * SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot * @@ -39,7 +39,7 @@ class GlobalStorage : public QObject { Q_OBJECT public: - explicit GlobalStorage(); + explicit GlobalStorage( QObject* parent = nullptr ); //NOTE: thread safety is guaranteed by JobQueue, which executes jobs one by one. // If at any time jobs become concurrent, this class must be made thread-safe. diff --git a/src/libcalamares/JobQueue.cpp b/src/libcalamares/JobQueue.cpp index adff9464b..64cc4794d 100644 --- a/src/libcalamares/JobQueue.cpp +++ b/src/libcalamares/JobQueue.cpp @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac * SPDX-FileCopyrightText: 2018 Adriaan de Groot * @@ -170,7 +170,7 @@ JobQueue::globalStorage() const JobQueue::JobQueue( QObject* parent ) : QObject( parent ) , m_thread( new JobThread( this ) ) - , m_storage( new GlobalStorage() ) + , m_storage( new GlobalStorage( this ) ) { Q_ASSERT( !s_instance ); s_instance = this; diff --git a/src/libcalamares/network/Manager.cpp b/src/libcalamares/network/Manager.cpp index d70988a0a..9d7534e99 100644 --- a/src/libcalamares/network/Manager.cpp +++ b/src/libcalamares/network/Manager.cpp @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2019 Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify @@ -168,7 +168,11 @@ Manager::checkHasInternet() { hasInternet = synchronousPing( d->m_hasInternetUrl ); } - d->m_hasInternet = hasInternet; + if ( hasInternet != d->m_hasInternet ) + { + d->m_hasInternet = hasInternet; + emit hasInternetChanged( hasInternet ); + } return hasInternet; } diff --git a/src/libcalamares/network/Manager.h b/src/libcalamares/network/Manager.h index 1ba3eb411..8673d340b 100644 --- a/src/libcalamares/network/Manager.h +++ b/src/libcalamares/network/Manager.h @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2019 Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify @@ -98,9 +98,10 @@ struct RequestStatus QDebug& operator<<( QDebug& s, const RequestStatus& e ); -class DLLEXPORT Manager : QObject +class DLLEXPORT Manager : public QObject { Q_OBJECT + Q_PROPERTY( bool hasInternet READ hasInternet NOTIFY hasInternetChanged FINAL ) Manager(); @@ -133,6 +134,16 @@ public: /// @brief Set the URL which is used for the general "is there internet" check. void setCheckHasInternetUrl( const QUrl& url ); + + /** @brief Do a network request asynchronously. + * + * Returns a pointer to the reply-from-the-request. + * This may be a nullptr if an error occurs immediately. + * The caller is responsible for cleaning up the reply (eventually). + */ + QNetworkReply* asynchronousGet( const QUrl& url, const RequestOptions& options = RequestOptions() ); + +public Q_SLOTS: /** @brief Do an explicit check for internet connectivity. * * This **may** do a ping to the configured check URL, but can also @@ -148,13 +159,13 @@ public: */ bool hasInternet(); - /** @brief Do a network request asynchronously. +signals: + /** @brief Indicates that internet connectivity status has changed * - * Returns a pointer to the reply-from-the-request. - * This may be a nullptr if an error occurs immediately. - * The caller is responsible for cleaning up the reply (eventually). + * The value is that returned from hasInternet() -- @c true when there + * is connectivity, @c false otherwise. */ - QNetworkReply* asynchronousGet( const QUrl& url, const RequestOptions& options = RequestOptions() ); + void hasInternetChanged( bool ); private: class Private; diff --git a/src/libcalamares/utils/RAII.h b/src/libcalamares/utils/RAII.h index dae85e84a..28e57ff9e 100644 --- a/src/libcalamares/utils/RAII.h +++ b/src/libcalamares/utils/RAII.h @@ -1,5 +1,5 @@ /* === This file is part of Calamares - === - * + * * SPDX-FileCopyrightText: 2020 Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ #define UTILS_RAII_H #include +#include #include @@ -44,4 +45,21 @@ struct cqDeleter } }; +/// @brief Sets a bool to @p value and resets to !value on destruction +template < bool value > +struct cBoolSetter +{ + bool& m_b; + + cBoolSetter( bool& b ) + : m_b( b ) + { + m_b = value; + } + ~cBoolSetter() { m_b = !value; } +}; + +/// @brief Blocks signals on a QObject until destruction +using cSignalBlocker = QSignalBlocker; + #endif diff --git a/src/libcalamaresui/modulesystem/ModuleManager.h b/src/libcalamaresui/modulesystem/ModuleManager.h index 2c51e70f7..2bac78af6 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.h +++ b/src/libcalamaresui/modulesystem/ModuleManager.h @@ -103,10 +103,36 @@ public: RequirementsModel* requirementsModel() { return m_requirementsModel; } signals: + /** @brief Emitted when all the module **configuration** has been read + * + * This indicates that all of the module.desc files have been + * loaded; bad ones are silently skipped, so this just indicates + * that the module manager is ready for the next phase (loading). + */ void initDone(); - void modulesLoaded(); /// All of the modules were loaded successfully - void modulesFailed( QStringList ); /// .. or not - // Below, see RequirementsChecker documentation + /** @brief Emitted when all the modules are loaded successfully + * + * Each module listed in the settings is loaded. Modules are loaded + * only once, even when instantiated multiple times. If all of + * the listed modules are successfully loaded, this signal is + * emitted (otherwise, it isn't, so you need to wait for **both** + * of the signals). + * + * If this is emitted (i.e. all modules have loaded) then the next + * phase, requirements checking, can be started. + */ + void modulesLoaded(); + /** @brief Emitted if any modules failed to load + * + * Modules that failed to load (for any reason) are listed by + * instance key (e.g. "welcome@welcome", "shellprocess@mycustomthing"). + */ + void modulesFailed( QStringList ); + /** @brief Emitted after all requirements have been checked + * + * The bool value indicates if all of the **mandatory** requirements + * are satisfied (e.g. whether installation can continue). + */ void requirementsComplete( bool ); private slots: diff --git a/src/libcalamaresui/utils/Qml.cpp b/src/libcalamaresui/utils/Qml.cpp index 4f53aa317..1f1152fa2 100644 --- a/src/libcalamaresui/utils/Qml.cpp +++ b/src/libcalamaresui/utils/Qml.cpp @@ -23,6 +23,7 @@ #include "JobQueue.h" #include "Settings.h" #include "ViewManager.h" +#include "network/Manager.h" #include "utils/Dirs.h" #include "utils/Logger.h" @@ -242,6 +243,10 @@ registerQmlModels() "io.calamares.core", 1, 0, "Global", []( QQmlEngine*, QJSEngine* ) -> QObject* { return Calamares::JobQueue::instance()->globalStorage(); } ); + qmlRegisterSingletonType< CalamaresUtils::Network::Manager >( + "io.calamares.core", 1, 0, "Network", []( QQmlEngine*, QJSEngine* ) -> QObject* { + return &CalamaresUtils::Network::Manager::instance(); + } ); } } diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index cde0a5e09..7a49525f2 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -1,7 +1,8 @@ /* === This file is part of Calamares - === * - * Copyright 2019-2020, Adriaan de Groot - * Copyright 2020, Camilo Higuita + * SPDX-FileCopyrightText: 2020 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * License-Filename: LICENSE * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,85 +20,43 @@ #include "Config.h" -#include "LCLocaleDialog.h" #include "SetTimezoneJob.h" -#include "timezonewidget/timezonewidget.h" #include "GlobalStorage.h" #include "JobQueue.h" #include "Settings.h" - #include "locale/Label.h" -#include "locale/TimeZone.h" -#include "utils/CalamaresUtilsGui.h" +#include "modulesystem/ModuleManager.h" +#include "network/Manager.h" #include "utils/Logger.h" -#include "utils/Retranslator.h" +#include "utils/Variant.h" -#include #include #include +#include -Config::Config( QObject* parent ) - : QObject( parent ) - , m_regionList( CalamaresUtils::Locale::TZRegion::fromZoneTab() ) - , m_regionModel( new CalamaresUtils::Locale::CStringListModel( m_regionList ) ) - , m_zonesModel( new CalamaresUtils::Locale::CStringListModel() ) - , m_blockTzWidgetSet( false ) -{ - connect( m_regionModel, &CalamaresUtils::Locale::CStringListModel::currentIndexChanged, [&]() { - m_zonesModel->setList( static_cast< const CalamaresUtils::Locale::TZRegion* >( - m_regionModel->item( m_regionModel->currentIndex() ) ) - ->zones() ); - updateLocaleLabels(); - } ); - - connect( - m_zonesModel, &CalamaresUtils::Locale::CStringListModel::currentIndexChanged, [&]() { updateLocaleLabels(); } ); -} - -Config::~Config() -{ - qDeleteAll( m_regionList ); -} - -CalamaresUtils::Locale::CStringListModel* -Config::zonesModel() const -{ - return m_zonesModel; -} - -CalamaresUtils::Locale::CStringListModel* -Config::regionModel() const +/** @brief Load supported locale keys + * + * If i18n/SUPPORTED exists, read the lines from that and return those + * as supported locales; otherwise, try the file at @p localeGenPath + * and get lines from that. Failing both, try the output of `locale -a`. + * + * This gives us a list of locale identifiers (e.g. en_US.UTF-8), which + * are not particularly human-readable. + * + * Only UTF-8 locales are returned (even if the system claims to support + * other, non-UTF-8, locales). + */ +static QStringList +loadLocales( const QString& localeGenPath ) { - return m_regionModel; -} - -void -Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath ) -{ - using namespace CalamaresUtils::Locale; - - cDebug() << "REGION MODEL SIZE" << initialRegion << initialZone; - auto* region = m_regionList.find< TZRegion >( initialRegion ); - if ( region && region->zones().find< TZZone >( initialZone ) ) - { - m_regionModel->setCurrentIndex( m_regionModel->indexOf( initialRegion ) ); - m_zonesModel->setList( region->zones() ); - m_zonesModel->setCurrentIndex( m_zonesModel->indexOf( initialZone ) ); - } - else - { - m_regionModel->setCurrentIndex( m_regionModel->indexOf( "America" ) ); - m_zonesModel->setList( - static_cast< const TZRegion* >( m_regionModel->item( m_regionModel->currentIndex() ) )->zones() ); - m_zonesModel->setCurrentIndex( m_zonesModel->indexOf( "New_York" ) ); - } + QStringList localeGenLines; // Some distros come with a meaningfully commented and easy to parse locale.gen, // and others ship a separate file /usr/share/i18n/SUPPORTED with a clean list of // supported locales. We first try that one, and if it doesn't exist, we fall back // to parsing the lines from locale.gen - m_localeGenLines.clear(); + localeGenLines.clear(); QFile supported( "/usr/share/i18n/SUPPORTED" ); QByteArray ba; @@ -109,7 +68,7 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone, const auto lines = ba.split( '\n' ); for ( const QByteArray& line : lines ) { - m_localeGenLines.append( QString::fromLatin1( line.simplified() ) ); + localeGenLines.append( QString::fromLatin1( line.simplified() ) ); } } else @@ -150,11 +109,11 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone, continue; } - m_localeGenLines.append( lineString ); + localeGenLines.append( lineString ); } } - if ( m_localeGenLines.isEmpty() ) + if ( localeGenLines.isEmpty() ) { cWarning() << "cannot acquire a list of available locales." << "The locale and localecfg modules will be broken as long as this " @@ -164,168 +123,388 @@ Config::setLocaleInfo( const QString& initialRegion, const QString& initialZone, << "* a well-formed" << ( localeGenPath.isEmpty() ? QLatin1String( "/etc/locale.gen" ) : localeGenPath ) << "\n\tOR" << "* a complete pre-compiled locale-gen database which allows complete locale -a output."; - return; // something went wrong and there's nothing we can do about it. + return localeGenLines; // something went wrong and there's nothing we can do about it. } // Assuming we have a list of supported locales, we usually only want UTF-8 ones // because it's not 1995. - for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); ) - { - if ( !it->contains( "UTF-8", Qt::CaseInsensitive ) && !it->contains( "utf8", Qt::CaseInsensitive ) ) + auto notUtf8 = []( const QString& s ) { + return !s.contains( "UTF-8", Qt::CaseInsensitive ) && !s.contains( "utf8", Qt::CaseInsensitive ); + }; + auto it = std::remove_if( localeGenLines.begin(), localeGenLines.end(), notUtf8 ); + localeGenLines.erase( it, localeGenLines.end() ); + + // We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant. + // Also simplify whitespace. + auto unredundant = []( QString& s ) { + if ( s.endsWith( " UTF-8" ) ) { - it = m_localeGenLines.erase( it ); + s.chop( 6 ); } - else + s = s.simplified(); + }; + std::for_each( localeGenLines.begin(), localeGenLines.end(), unredundant ); + + return localeGenLines; +} + +static inline const CalamaresUtils::Locale::CStringPairList& +timezoneData() +{ + return CalamaresUtils::Locale::TZRegion::fromZoneTab(); +} + + +Config::Config( QObject* parent ) + : QObject( parent ) + , m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( ::timezoneData() ) ) + , m_zonesModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >() ) +{ + // Slightly unusual: connect to our *own* signals. Wherever the language + // or the location is changed, these signals are emitted, so hook up to + // them to update global storage accordingly. This simplifies code: + // we don't need to call an update-GS method, or introduce an intermediate + // update-thing-and-GS method. And everywhere where we **do** change + // language or location, we already emit the signal. + connect( this, &Config::currentLanguageCodeChanged, [&]() { + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + gs->insert( "locale", m_selectedLocaleConfiguration.toBcp47() ); + } ); + + connect( this, &Config::currentLCCodeChanged, [&]() { + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + // Update GS localeConf (the LC_ variables) + auto map = localeConfiguration().toMap(); + QVariantMap vm; + for ( auto it = map.constBegin(); it != map.constEnd(); ++it ) { - ++it; + vm.insert( it.key(), it.value() ); } - } + gs->insert( "localeConf", vm ); + } ); - // We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant. - for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); ++it ) - { - if ( it->endsWith( " UTF-8" ) ) + connect( this, &Config::currentLocationChanged, [&]() { + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + + // Update the GS region and zone (and possibly the live timezone) + const auto* location = currentLocation(); + bool locationChanged = ( location->region() != gs->value( "locationRegion" ) ) + || ( location->zone() != gs->value( "locationZone" ) ); + + gs->insert( "locationRegion", location->region() ); + gs->insert( "locationZone", location->zone() ); + if ( locationChanged && m_adjustLiveTimezone ) { - it->chop( 6 ); + QProcess::execute( "timedatectl", // depends on systemd + { "set-timezone", location->region() + '/' + location->zone() } ); } - *it = it->simplified(); - } - updateGlobalStorage(); - updateLocaleLabels(); + } ); + + auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); }; + connect( this, &Config::currentLanguageStatusChanged, prettyStatusNotify ); + connect( this, &Config::currentLCStatusChanged, prettyStatusNotify ); + connect( this, &Config::currentLocationStatusChanged, prettyStatusNotify ); } -void -Config::updateGlobalLocale() +Config::~Config() {} + +const CalamaresUtils::Locale::CStringPairList& +Config::timezoneData() const { - auto* gs = Calamares::JobQueue::instance()->globalStorage(); - const QString bcp47 = m_selectedLocaleConfiguration.toBcp47(); - gs->insert( "locale", bcp47 ); + return ::timezoneData(); } void -Config::updateGlobalStorage() +Config::setCurrentLocation() { - auto* gs = Calamares::JobQueue::instance()->globalStorage(); - - const auto* location = currentLocation(); - bool locationChanged = ( location->region() != gs->value( "locationRegion" ) ) - || ( location->zone() != gs->value( "locationZone" ) ); -#ifdef DEBUG_TIMEZONES - if ( locationChanged ) + if ( !m_currentLocation && m_startingTimezone.isValid() ) { - cDebug() << "Location changed" << gs->value( "locationRegion" ) << ',' << gs->value( "locationZone" ) << "to" - << location->region() << ',' << location->zone(); + setCurrentLocation( m_startingTimezone.first, m_startingTimezone.second ); } -#endif - gs->insert( "locationRegion", location->region() ); - gs->insert( "locationZone", location->zone() ); +} - updateGlobalLocale(); +void Config::setCurrentLocation(const QString& regionzone) +{ + auto r = CalamaresUtils::GeoIP::splitTZString( regionzone ); + if ( r.isValid() ) + { + setCurrentLocation( r.first, r.second ); + } +} - // If we're in chroot mode (normal install mode), then we immediately set the - // timezone on the live system. When debugging timezones, don't bother. -#ifndef DEBUG_TIMEZONES - if ( locationChanged && Calamares::Settings::instance()->doChroot() ) +void +Config::setCurrentLocation( const QString& regionName, const QString& zoneName ) +{ + using namespace CalamaresUtils::Locale; + auto* region = timezoneData().find< TZRegion >( regionName ); + auto* zone = region ? region->zones().find< TZZone >( zoneName ) : nullptr; + if ( zone ) { - QProcess::execute( "timedatectl", // depends on systemd - { "set-timezone", location->region() + '/' + location->zone() } ); + setCurrentLocation( zone ); } -#endif + else + { + // Recursive, but America/New_York always exists. + setCurrentLocation( QStringLiteral( "America" ), QStringLiteral( "New_York" ) ); + } +} - // Preserve those settings that have been made explicit. - auto newLocale = guessLocaleConfiguration(); - if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lang ) +void +Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ) +{ + if ( location != m_currentLocation ) { - newLocale.setLanguage( m_selectedLocaleConfiguration.language() ); + m_currentLocation = location; + // Overwrite those settings that have not been made explicit. + auto newLocale = automaticLocaleConfiguration(); + if ( !m_selectedLocaleConfiguration.explicit_lang ) + { + m_selectedLocaleConfiguration.setLanguage( newLocale.language() ); + emit currentLanguageStatusChanged( currentLanguageStatus() ); + } + if ( !m_selectedLocaleConfiguration.explicit_lc ) + { + m_selectedLocaleConfiguration.lc_numeric = newLocale.lc_numeric; + m_selectedLocaleConfiguration.lc_time = newLocale.lc_time; + m_selectedLocaleConfiguration.lc_monetary = newLocale.lc_monetary; + m_selectedLocaleConfiguration.lc_paper = newLocale.lc_paper; + m_selectedLocaleConfiguration.lc_name = newLocale.lc_name; + m_selectedLocaleConfiguration.lc_address = newLocale.lc_address; + m_selectedLocaleConfiguration.lc_telephone = newLocale.lc_telephone; + m_selectedLocaleConfiguration.lc_measurement = newLocale.lc_measurement; + m_selectedLocaleConfiguration.lc_identification = newLocale.lc_identification; + + emit currentLCStatusChanged( currentLCStatus() ); + } + emit currentLocationChanged( m_currentLocation ); } - if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lc ) +} + +LocaleConfiguration +Config::automaticLocaleConfiguration() const +{ + // Special case: no location has been set at **all** + if ( !currentLocation() ) { - newLocale.lc_numeric = m_selectedLocaleConfiguration.lc_numeric; - newLocale.lc_time = m_selectedLocaleConfiguration.lc_time; - newLocale.lc_monetary = m_selectedLocaleConfiguration.lc_monetary; - newLocale.lc_paper = m_selectedLocaleConfiguration.lc_paper; - newLocale.lc_name = m_selectedLocaleConfiguration.lc_name; - newLocale.lc_address = m_selectedLocaleConfiguration.lc_address; - newLocale.lc_telephone = m_selectedLocaleConfiguration.lc_telephone; - newLocale.lc_measurement = m_selectedLocaleConfiguration.lc_measurement; - newLocale.lc_identification = m_selectedLocaleConfiguration.lc_identification; + return LocaleConfiguration(); } - newLocale.explicit_lang = m_selectedLocaleConfiguration.explicit_lang; - newLocale.explicit_lc = m_selectedLocaleConfiguration.explicit_lc; + return LocaleConfiguration::fromLanguageAndLocation( + QLocale().name(), supportedLocales(), currentLocation()->country() ); +} + +LocaleConfiguration +Config::localeConfiguration() const +{ + return m_selectedLocaleConfiguration.isEmpty() ? automaticLocaleConfiguration() : m_selectedLocaleConfiguration; +} + +void +Config::setLanguageExplicitly( const QString& language ) +{ + m_selectedLocaleConfiguration.setLanguage( language ); + m_selectedLocaleConfiguration.explicit_lang = true; - m_selectedLocaleConfiguration = newLocale; - updateLocaleLabels(); + emit currentLanguageStatusChanged( currentLanguageStatus() ); + emit currentLanguageCodeChanged( currentLanguageCode() ); } void -Config::updateLocaleLabels() +Config::setLCLocaleExplicitly( const QString& locale ) { - LocaleConfiguration lc - = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration; - auto labels = prettyLocaleStatus( lc ); - emit prettyStatusChanged(); + // TODO: improve the granularity of this setting. + m_selectedLocaleConfiguration.lc_numeric = locale; + m_selectedLocaleConfiguration.lc_time = locale; + m_selectedLocaleConfiguration.lc_monetary = locale; + m_selectedLocaleConfiguration.lc_paper = locale; + m_selectedLocaleConfiguration.lc_name = locale; + m_selectedLocaleConfiguration.lc_address = locale; + m_selectedLocaleConfiguration.lc_telephone = locale; + m_selectedLocaleConfiguration.lc_measurement = locale; + m_selectedLocaleConfiguration.lc_identification = locale; + m_selectedLocaleConfiguration.explicit_lc = true; + + emit currentLCStatusChanged( currentLCStatus() ); + emit currentLCCodeChanged( currentLCCode() ); } +QString +Config::currentLocationStatus() const +{ + return tr( "Set timezone to %1/%2." ).arg( m_currentLocation->region(), m_currentLocation->zone() ); +} -std::pair< QString, QString > -Config::prettyLocaleStatus( const LocaleConfiguration& lc ) const +static inline QString +localeLabel( const QString& s ) { using CalamaresUtils::Locale::Label; - Label lang( lc.language(), Label::LabelFormat::AlwaysWithCountry ); - Label num( lc.lc_numeric, Label::LabelFormat::AlwaysWithCountry ); + Label lang( s, Label::LabelFormat::AlwaysWithCountry ); + return lang.label(); +} - return std::make_pair< QString, QString >( - tr( "The system language will be set to %1." ).arg( lang.label() ), - tr( "The numbers and dates locale will be set to %1." ).arg( num.label() ) ); +QString +Config::currentLanguageStatus() const +{ + return tr( "The system language will be set to %1." ) + .arg( localeLabel( m_selectedLocaleConfiguration.language() ) ); } -Calamares::JobList -Config::createJobs() +QString +Config::currentLCStatus() const { - QList< Calamares::job_ptr > list; - const CalamaresUtils::Locale::TZZone* location = currentLocation(); + return tr( "The numbers and dates locale will be set to %1." ) + .arg( localeLabel( m_selectedLocaleConfiguration.lc_numeric ) ); +} - Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() ); - list.append( Calamares::job_ptr( j ) ); +QString +Config::prettyStatus() const +{ + QStringList l { currentLocationStatus(), currentLanguageStatus(), currentLCStatus() }; + return l.join( QStringLiteral( "
" ) ); +} - return list; +static inline void +getLocaleGenLines( const QVariantMap& configurationMap, QStringList& localeGenLines ) +{ + QString localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" ); + if ( localeGenPath.isEmpty() ) + { + localeGenPath = QStringLiteral( "/etc/locale.gen" ); + } + localeGenLines = loadLocales( localeGenPath ); } -LocaleConfiguration -Config::guessLocaleConfiguration() const +static inline void +getAdjustLiveTimezone( const QVariantMap& configurationMap, bool& adjustLiveTimezone ) { - return LocaleConfiguration::fromLanguageAndLocation( - QLocale().name(), m_localeGenLines, currentLocation() ? currentLocation()->country() : "" ); + adjustLiveTimezone = CalamaresUtils::getBool( + configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() ); +#ifdef DEBUG_TIMEZONES + if ( m_adjustLiveTimezone ) + { + cWarning() << "Turning off live-timezone adjustments because debugging is on."; + adjustLiveTimezone = false; + } +#endif +#ifdef __FreeBSD__ + if ( adjustLiveTimezone ) + { + cWarning() << "Turning off live-timezone adjustments on FreeBSD."; + adjustLiveTimezone = false; + } +#endif +} + +static inline void +getStartingTimezone( const QVariantMap& configurationMap, CalamaresUtils::GeoIP::RegionZonePair& startingTimezone ) +{ + QString region = CalamaresUtils::getString( configurationMap, "region" ); + QString zone = CalamaresUtils::getString( configurationMap, "zone" ); + if ( !region.isEmpty() && !zone.isEmpty() ) + { + startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone ); + } + else + { + startingTimezone + = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) ); + } + + if ( CalamaresUtils::getBool( configurationMap, "useSystemTimezone", false ) ) + { + auto systemtz = CalamaresUtils::GeoIP::splitTZString( QTimeZone::systemTimeZoneId() ); + if ( systemtz.isValid() ) + { + cDebug() << "Overriding configured timezone" << startingTimezone << "with system timezone" << systemtz; + startingTimezone = systemtz; + } + } } -QMap< QString, QString > -Config::localesMap() +static inline void +getGeoIP( const QVariantMap& configurationMap, std::unique_ptr< CalamaresUtils::GeoIP::Handler >& geoip ) { - return m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().toMap() - : m_selectedLocaleConfiguration.toMap(); + bool ok = false; + QVariantMap map = CalamaresUtils::getSubMap( configurationMap, "geoip", ok ); + if ( ok ) + { + QString url = CalamaresUtils::getString( map, "url" ); + QString style = CalamaresUtils::getString( map, "style" ); + QString selector = CalamaresUtils::getString( map, "selector" ); + + geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector ); + if ( !geoip->isValid() ) + { + cWarning() << "GeoIP Style" << style << "is not recognized."; + } + } } -QString -Config::prettyStatus() const +void +Config::setConfigurationMap( const QVariantMap& configurationMap ) { - QString status; - status += tr( "Set timezone to %1/%2.
" ) - .arg( m_regionModel->item( m_regionModel->currentIndex() )->tr() ) - .arg( m_zonesModel->item( m_zonesModel->currentIndex() )->tr() ); - - LocaleConfiguration lc - = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration; - auto labels = prettyLocaleStatus( lc ); - status += labels.first + "
"; - status += labels.second + "
"; - - return status; + getLocaleGenLines( configurationMap, m_localeGenLines ); + getAdjustLiveTimezone( configurationMap, m_adjustLiveTimezone ); + getStartingTimezone( configurationMap, m_startingTimezone ); + getGeoIP( configurationMap, m_geoip ); + + if ( m_geoip && m_geoip->isValid() ) + { + connect( + Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP ); + } } +Calamares::JobList +Config::createJobs() +{ + Calamares::JobList list; + const CalamaresUtils::Locale::TZZone* location = currentLocation(); -const CalamaresUtils::Locale::TZZone* -Config::currentLocation() const + if ( location ) + { + Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() ); + list.append( Calamares::job_ptr( j ) ); + } + + return list; +} + +void +Config::startGeoIP() +{ + if ( m_geoip && m_geoip->isValid() ) + { + auto& network = CalamaresUtils::Network::Manager::instance(); + if ( network.hasInternet() || network.synchronousPing( m_geoip->url() ) ) + { + using Watcher = QFutureWatcher< CalamaresUtils::GeoIP::RegionZonePair >; + m_geoipWatcher = std::make_unique< Watcher >(); + m_geoipWatcher->setFuture( m_geoip->query() ); + connect( m_geoipWatcher.get(), &Watcher::finished, this, &Config::completeGeoIP ); + } + } +} + +void +Config::completeGeoIP() { - return static_cast< const CalamaresUtils::Locale::TZZone* >( m_zonesModel->item( m_zonesModel->currentIndex() ) ); + if ( !currentLocation() ) + { + auto r = m_geoipWatcher->result(); + if ( r.isValid() ) + { + m_startingTimezone = r; + } + else + { + cWarning() << "GeoIP returned invalid result."; + } + } + else + { + cWarning() << "GeoIP result ignored because a location is already set."; + } + m_geoipWatcher.reset(); + m_geoip.reset(); } diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index cfbed7bae..e9a8e6373 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -1,7 +1,8 @@ /* === This file is part of Calamares - === * - * Copyright 2019-2020, Adriaan de Groot - * Copyright 2020, Camilo Higuita + * SPDX-FileCopyrightText: 2020 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * License-Filename: LICENSE * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,9 +24,11 @@ #include "LocaleConfiguration.h" #include "Job.h" +#include "geoip/Handler.h" +#include "geoip/Interface.h" #include "locale/TimeZone.h" -#include +#include #include #include @@ -33,53 +36,150 @@ class Config : public QObject { Q_OBJECT + Q_PROPERTY( const QStringList& supportedLocales READ supportedLocales CONSTANT FINAL ) Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* zonesModel READ zonesModel CONSTANT FINAL ) Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* regionModel READ regionModel CONSTANT FINAL ) + + Q_PROPERTY( const CalamaresUtils::Locale::TZZone* currentLocation READ currentLocation WRITE setCurrentLocation + NOTIFY currentLocationChanged ) + + // Status are complete, human-readable, messages + Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged ) + Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged ) + Q_PROPERTY( QString currentLCStatus READ currentLCStatus NOTIFY currentLCStatusChanged ) + // Code are internal identifiers, like "en_US.UTF-8" + Q_PROPERTY( QString currentLanguageCode READ currentLanguageCode WRITE setLanguageExplicitly NOTIFY currentLanguageCodeChanged ) + Q_PROPERTY( QString currentLCCode READ currentLCCode WRITE setLCLocaleExplicitly NOTIFY currentLCCodeChanged ) + + // This is a long human-readable string with all three statuses Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL ) public: Config( QObject* parent = nullptr ); ~Config(); - CalamaresUtils::Locale::CStringListModel* regionModel() const; - CalamaresUtils::Locale::CStringListModel* zonesModel() const; - - void setLocaleInfo( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath ); + void setConfigurationMap( const QVariantMap& ); Calamares::JobList createJobs(); - QMap< QString, QString > localesMap(); + + // Underlying data for the models + const CalamaresUtils::Locale::CStringPairList& timezoneData() const; + + /** @brief The currently selected location (timezone) + * + * The location is a pointer into the date that timezoneData() returns. + */ + const CalamaresUtils::Locale::TZZone* currentLocation() const { return m_currentLocation; } + + /// locale configuration (LC_* and LANG) based solely on the current location. + LocaleConfiguration automaticLocaleConfiguration() const; + /// locale configuration that takes explicit settings into account + LocaleConfiguration localeConfiguration() const; + + /// The human-readable description of what timezone is used + QString currentLocationStatus() const; + /// The human-readable description of what language is used + QString currentLanguageStatus() const; + /// The human-readable description of what locale (LC_*) is used + QString currentLCStatus() const; + + /// The human-readable summary of what the module will do QString prettyStatus() const; -private: - CalamaresUtils::Locale::CStringPairList m_regionList; - CalamaresUtils::Locale::CStringListModel* m_regionModel; - CalamaresUtils::Locale::CStringListModel* m_zonesModel; + const QStringList& supportedLocales() const { return m_localeGenLines; } + CalamaresUtils::Locale::CStringListModel* regionModel() const { return m_regionModel.get(); } + CalamaresUtils::Locale::CStringListModel* zonesModel() const { return m_zonesModel.get(); } - LocaleConfiguration m_selectedLocaleConfiguration; + /// Special case, set location from starting timezone if not already set + void setCurrentLocation(); + +public Q_SLOTS: + /// Set a language by user-choice, overriding future location changes + void setLanguageExplicitly( const QString& language ); + /// Set LC (formats) by user-choice, overriding future location changes + void setLCLocaleExplicitly( const QString& locale ); + + /** @brief Sets a location by full name + * + * @p regionzone should be an identifier from zone.tab, e.g. "Africa/Abidjan", + * which is split into regon and zone. Invalid names will **not** + * change the actual location. + */ + void setCurrentLocation( const QString& regionzone ); + /** @brief Sets a location by split name + * + * @p region should be "America" or the like, while @p zone + * names a zone within that region. + */ + void setCurrentLocation( const QString& region, const QString& zone ); + /** @brief Sets a location by pointer + * + * Pointer should be within the same model as the widget uses. + * This can update the locale configuration -- the automatic one + * follows the current location, and otherwise only explicitly-set + * values will ignore changes to the location. + */ + void setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ); + + QString currentLanguageCode() const { return localeConfiguration().language(); } + QString currentLCCode() const { return localeConfiguration().lc_numeric; } +signals: + void currentLocationChanged( const CalamaresUtils::Locale::TZZone* location ) const; + void currentLocationStatusChanged( const QString& ) const; + void currentLanguageStatusChanged( const QString& ) const; + void currentLCStatusChanged( const QString& ) const; + void prettyStatusChanged( const QString& ) const; + void currentLanguageCodeChanged( const QString& ) const; + void currentLCCodeChanged( const QString& ) const; + +private: + /// A list of supported locale identifiers (e.g. "en_US.UTF-8") QStringList m_localeGenLines; - int m_currentRegion = -1; - bool m_blockTzWidgetSet; + /// The regions (America, Asia, Europe ..) + std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel; + /// The zones for the current region (e.g. America/New_York) + std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_zonesModel; - LocaleConfiguration guessLocaleConfiguration() const; + /// The location, points into the timezone data + const CalamaresUtils::Locale::TZZone* m_currentLocation = nullptr; - // For the given locale config, return two strings describing - // the settings for language and numbers. - std::pair< QString, QString > prettyLocaleStatus( const LocaleConfiguration& ) const; + /** @brief Specific locale configurations + * + * "Automatic" locale configuration based on the location (e.g. + * Europe/Amsterdam means Dutch language and Dutch locale) leave + * this empty; if the user explicitly sets something, then + * this configuration is non-empty and takes precedence over the + * automatic location settings (so a user in Amsterdam can still + * pick Ukranian settings, for instance). + */ + LocaleConfiguration m_selectedLocaleConfiguration; + + /** @brief Should we adjust the "live" timezone when the location changes? + * + * In the Widgets UI, clicking around on the world map adjusts the + * timezone, and the live system can be made to follow that. + */ + bool m_adjustLiveTimezone; - /** @brief Update the GS *locale* key with the selected system language. + /** @brief The initial timezone (region, zone) specified in the config. * - * This uses whatever is set in m_selectedLocaleConfiguration as the language, - * and writes it to GS *locale* key (as a string, in BCP47 format). + * This may be overridden by setting *useSystemTimezone* or by + * GeoIP settings. */ - void updateGlobalLocale(); - void updateGlobalStorage(); - void updateLocaleLabels(); + CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone; - const CalamaresUtils::Locale::TZZone* currentLocation() const; + /** @brief Handler for GeoIP lookup (if configured) + * + * The GeoIP lookup needs to be started at some suitable time, + * by explicitly calling *TODO* + */ + std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip; -signals: - void prettyStatusChanged(); + // Implementation details for doing GeoIP lookup + void startGeoIP(); + void completeGeoIP(); + std::unique_ptr< QFutureWatcher< CalamaresUtils::GeoIP::RegionZonePair > > m_geoipWatcher; }; diff --git a/src/modules/locale/LocaleConfiguration.cpp b/src/modules/locale/LocaleConfiguration.cpp index f1810fb83..055907228 100644 --- a/src/modules/locale/LocaleConfiguration.cpp +++ b/src/modules/locale/LocaleConfiguration.cpp @@ -37,7 +37,7 @@ LocaleConfiguration::LocaleConfiguration( const QString& localeName, const QStri lc_numeric = lc_time = lc_monetary = lc_paper = lc_name = lc_address = lc_telephone = lc_measurement = lc_identification = formatsName; - (void)setLanguage( localeName ); + setLanguage( localeName ); } @@ -83,7 +83,7 @@ LocaleConfiguration::fromLanguageAndLocation( const QString& languageLocale, if ( language == "pt" || language == "zh" ) { QString proposedLocale = QString( "%1_%2" ).arg( language ).arg( countryCode ); - foreach ( QString line, linesForLanguage ) + for ( const QString& line : linesForLanguage ) { if ( line.contains( proposedLocale ) ) { diff --git a/src/modules/locale/LocaleConfiguration.h b/src/modules/locale/LocaleConfiguration.h index 4f4fc6b21..2e02bc02a 100644 --- a/src/modules/locale/LocaleConfiguration.h +++ b/src/modules/locale/LocaleConfiguration.h @@ -26,36 +26,53 @@ class LocaleConfiguration { -public: - /// @brief Create an empty locale, with nothing set - explicit LocaleConfiguration(); - /// @brief Create a locale with everything set to the given @p localeName +public: // TODO: private (but need to be public for tests) + /** @brief Create a locale with everything set to the given @p localeName + * + * Consumers should use fromLanguageAndLocation() instead. + */ explicit LocaleConfiguration( const QString& localeName /* "en_US.UTF-8" */ ) : LocaleConfiguration( localeName, localeName ) { } - /// @brief Create a locale with language and formats separate + /** @brief Create a locale with language and formats separate + * + * Consumers should use fromLanguageAndLocation() instead. + */ explicit LocaleConfiguration( const QString& localeName, const QString& formatsName ); + /// @brief Create an empty locale, with nothing set + explicit LocaleConfiguration(); + + /** @brief Create a "sensible" locale configuration for @p language and @p countryCode + * + * This method applies some heuristics to pick a good locale (from the list + * @p availableLocales), along with a good language (for instance, in + * large countries with many languages, picking a generally used one). + */ static LocaleConfiguration fromLanguageAndLocation( const QString& language, const QStringList& availableLocales, const QString& countryCode ); + /// Is this an empty (default-constructed and not modified) configuration? bool isEmpty() const; - /** @brief sets lang and the BCP47 representation + /** @brief sets language to @p localeName * - * Note that the documentation how this works is in packages.conf + * The language may be regionalized, e.g. "nl_BE". Both the language + * (with region) and BCP47 representation (without region, lowercase) + * are updated. The BCP47 representation is used by the packages module. + * See also `packages.conf` for a discussion of how this is used. */ void setLanguage( const QString& localeName ); + /// Current language (including region) QString language() const { return m_lang; } - - // Note that the documentation how this works is in packages.conf + /// Current language (lowercase, BCP47 format, no region) QString toBcp47() const { return m_languageLocaleBcp47; } QMap< QString, QString > toMap() const; // These become all uppercase in locale.conf, but we keep them lowercase here to - // avoid confusion with locale.h. + // avoid confusion with , which defines (e.g.) LC_NUMERIC macro. QString lc_numeric, lc_time, lc_monetary, lc_paper, lc_name, lc_address, lc_telephone, lc_measurement, lc_identification; diff --git a/src/modules/locale/LocalePage.cpp b/src/modules/locale/LocalePage.cpp index 53d97ea02..406f27a6e 100644 --- a/src/modules/locale/LocalePage.cpp +++ b/src/modules/locale/LocalePage.cpp @@ -19,37 +19,30 @@ #include "LocalePage.h" -#include "SetTimezoneJob.h" -#include "timezonewidget/timezonewidget.h" - -#include "GlobalStorage.h" -#include "JobQueue.h" +#include "Config.h" #include "LCLocaleDialog.h" -#include "Settings.h" +#include "timezonewidget/timezonewidget.h" -#include "locale/Label.h" -#include "locale/TimeZone.h" #include "utils/CalamaresUtilsGui.h" #include "utils/Logger.h" +#include "utils/RAII.h" #include "utils/Retranslator.h" #include #include -#include #include -#include +#include #include -LocalePage::LocalePage( QWidget* parent ) +LocalePage::LocalePage( Config* config, QWidget* parent ) : QWidget( parent ) - , m_regionList( CalamaresUtils::Locale::TZRegion::fromZoneTab() ) - , m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( m_regionList ) ) + , m_config( config ) , m_blockTzWidgetSet( false ) { QBoxLayout* mainLayout = new QVBoxLayout; QBoxLayout* tzwLayout = new QHBoxLayout; - m_tzWidget = new TimeZoneWidget( this ); + m_tzWidget = new TimeZoneWidget( config->timezoneData(), this ); tzwLayout->addStretch(); tzwLayout->addWidget( m_tzWidget ); tzwLayout->addStretch(); @@ -105,9 +98,24 @@ LocalePage::LocalePage( QWidget* parent ) setMinimumWidth( m_tzWidget->width() ); setLayout( mainLayout ); + // Set up the location before connecting signals, to avoid a signal + // storm as various parts interact. + m_regionCombo->setModel( m_config->regionModel() ); + locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget + m_tzWidget->setCurrentLocation( m_config->currentLocation() ); + + connect( config, &Config::currentLCStatusChanged, m_formatsLabel, &QLabel::setText ); + connect( config, &Config::currentLanguageStatusChanged, m_localeLabel, &QLabel::setText ); + connect( config, &Config::currentLocationChanged, m_tzWidget, &TimeZoneWidget::setCurrentLocation ); + connect( config, &Config::currentLocationChanged, this, &LocalePage::locationChanged ); + connect( m_tzWidget, + &TimeZoneWidget::locationChanged, + config, + QOverload< const CalamaresUtils::Locale::TZZone* >::of( &Config::setCurrentLocation ) ); + connect( m_regionCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::regionChanged ); connect( m_zoneCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::zoneChanged ); - connect( m_tzWidget, &TimeZoneWidget::locationChanged, this, &LocalePage::locationChanged ); + connect( m_localeChangeButton, &QPushButton::clicked, this, &LocalePage::changeLocale ); connect( m_formatsChangeButton, &QPushButton::clicked, this, &LocalePage::changeFormats ); @@ -115,10 +123,7 @@ LocalePage::LocalePage( QWidget* parent ) } -LocalePage::~LocalePage() -{ - qDeleteAll( m_regionList ); -} +LocalePage::~LocalePage() {} void @@ -128,175 +133,8 @@ LocalePage::updateLocaleLabels() m_zoneLabel->setText( tr( "Zone:" ) ); m_localeChangeButton->setText( tr( "&Change..." ) ); m_formatsChangeButton->setText( tr( "&Change..." ) ); - - LocaleConfiguration lc - = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration; - auto labels = prettyLocaleStatus( lc ); - m_localeLabel->setText( labels.first ); - m_formatsLabel->setText( labels.second ); -} - -void -LocalePage::init( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath ) -{ - using namespace CalamaresUtils::Locale; - - m_regionCombo->setModel( m_regionModel.get() ); - m_regionCombo->currentIndexChanged( m_regionCombo->currentIndex() ); - - auto* region = m_regionList.find< TZRegion >( initialRegion ); - if ( region && region->zones().find< TZZone >( initialZone ) ) - { - m_tzWidget->setCurrentLocation( initialRegion, initialZone ); - } - else - { - m_tzWidget->setCurrentLocation( "America", "New_York" ); - } - - // Some distros come with a meaningfully commented and easy to parse locale.gen, - // and others ship a separate file /usr/share/i18n/SUPPORTED with a clean list of - // supported locales. We first try that one, and if it doesn't exist, we fall back - // to parsing the lines from locale.gen - m_localeGenLines.clear(); - QFile supported( "/usr/share/i18n/SUPPORTED" ); - QByteArray ba; - - if ( supported.exists() && supported.open( QIODevice::ReadOnly | QIODevice::Text ) ) - { - ba = supported.readAll(); - supported.close(); - - const auto lines = ba.split( '\n' ); - for ( const QByteArray& line : lines ) - { - m_localeGenLines.append( QString::fromLatin1( line.simplified() ) ); - } - } - else - { - QFile localeGen( localeGenPath ); - if ( localeGen.open( QIODevice::ReadOnly | QIODevice::Text ) ) - { - ba = localeGen.readAll(); - localeGen.close(); - } - else - { - cWarning() << "Cannot open file" << localeGenPath - << ". Assuming the supported languages are already built into " - "the locale archive."; - QProcess localeA; - localeA.start( "locale", QStringList() << "-a" ); - localeA.waitForFinished(); - ba = localeA.readAllStandardOutput(); - } - const auto lines = ba.split( '\n' ); - for ( const QByteArray& line : lines ) - { - if ( line.startsWith( "## " ) || line.startsWith( "# " ) || line.simplified() == "#" ) - { - continue; - } - - QString lineString = QString::fromLatin1( line.simplified() ); - if ( lineString.startsWith( "#" ) ) - { - lineString.remove( '#' ); - } - lineString = lineString.simplified(); - - if ( lineString.isEmpty() ) - { - continue; - } - - m_localeGenLines.append( lineString ); - } - } - - if ( m_localeGenLines.isEmpty() ) - { - cWarning() << "cannot acquire a list of available locales." - << "The locale and localecfg modules will be broken as long as this " - "system does not provide" - << "\n\t " - << "* a well-formed" << supported.fileName() << "\n\tOR" - << "* a well-formed" - << ( localeGenPath.isEmpty() ? QLatin1String( "/etc/locale.gen" ) : localeGenPath ) << "\n\tOR" - << "* a complete pre-compiled locale-gen database which allows complete locale -a output."; - return; // something went wrong and there's nothing we can do about it. - } - - // Assuming we have a list of supported locales, we usually only want UTF-8 ones - // because it's not 1995. - auto notUtf8 = []( const QString& s ) { - return !s.contains( "UTF-8", Qt::CaseInsensitive ) && !s.contains( "utf8", Qt::CaseInsensitive ); - }; - auto it = std::remove_if( m_localeGenLines.begin(), m_localeGenLines.end(), notUtf8 ); - m_localeGenLines.erase( it, m_localeGenLines.end() ); - - // We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant. - // Also simplify whitespace. - auto unredundant = []( QString& s ) { - if ( s.endsWith( " UTF-8" ) ) - { - s.chop( 6 ); - } - s = s.simplified(); - }; - std::for_each( m_localeGenLines.begin(), m_localeGenLines.end(), unredundant ); - - updateGlobalStorage(); -} - -std::pair< QString, QString > -LocalePage::prettyLocaleStatus( const LocaleConfiguration& lc ) const -{ - using CalamaresUtils::Locale::Label; - - Label lang( lc.language(), Label::LabelFormat::AlwaysWithCountry ); - Label num( lc.lc_numeric, Label::LabelFormat::AlwaysWithCountry ); - - return std::make_pair< QString, QString >( - tr( "The system language will be set to %1." ).arg( lang.label() ), - tr( "The numbers and dates locale will be set to %1." ).arg( num.label() ) ); -} - -QString -LocalePage::prettyStatus() const -{ - QString status; - status += tr( "Set timezone to %1/%2.
" ).arg( m_regionCombo->currentText() ).arg( m_zoneCombo->currentText() ); - - LocaleConfiguration lc - = m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration() : m_selectedLocaleConfiguration; - auto labels = prettyLocaleStatus( lc ); - status += labels.first + "
"; - status += labels.second + "
"; - - return status; -} - - -Calamares::JobList -LocalePage::createJobs() -{ - QList< Calamares::job_ptr > list; - const CalamaresUtils::Locale::TZZone* location = m_tzWidget->currentLocation(); - - Calamares::Job* j = new SetTimezoneJob( location->region(), location->zone() ); - list.append( Calamares::job_ptr( j ) ); - - return list; -} - - -QMap< QString, QString > -LocalePage::localesMap() -{ - return m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().toMap() - : m_selectedLocaleConfiguration.toMap(); + m_localeLabel->setText( m_config->currentLanguageStatus() ); + m_formatsLabel->setText( m_config->currentLCStatus() ); } @@ -304,82 +142,10 @@ void LocalePage::onActivate() { m_regionCombo->setFocus(); - if ( m_selectedLocaleConfiguration.isEmpty() || !m_selectedLocaleConfiguration.explicit_lang ) - { - auto newLocale = guessLocaleConfiguration(); - m_selectedLocaleConfiguration.setLanguage( newLocale.language() ); - updateGlobalLocale(); - updateLocaleLabels(); - } -} - - -LocaleConfiguration -LocalePage::guessLocaleConfiguration() const -{ - return LocaleConfiguration::fromLanguageAndLocation( - QLocale().name(), m_localeGenLines, m_tzWidget->currentLocation()->country() ); -} - - -void -LocalePage::updateGlobalLocale() -{ - auto* gs = Calamares::JobQueue::instance()->globalStorage(); - const QString bcp47 = m_selectedLocaleConfiguration.toBcp47(); - gs->insert( "locale", bcp47 ); -} - - -void -LocalePage::updateGlobalStorage() -{ - auto* gs = Calamares::JobQueue::instance()->globalStorage(); - - const auto* location = m_tzWidget->currentLocation(); - bool locationChanged = ( location->region() != gs->value( "locationRegion" ) ) - || ( location->zone() != gs->value( "locationZone" ) ); - - gs->insert( "locationRegion", location->region() ); - gs->insert( "locationZone", location->zone() ); - - updateGlobalLocale(); - - // If we're in chroot mode (normal install mode), then we immediately set the - // timezone on the live system. When debugging timezones, don't bother. -#ifndef DEBUG_TIMEZONES - if ( locationChanged && Calamares::Settings::instance()->doChroot() ) - { - QProcess::execute( "timedatectl", // depends on systemd - { "set-timezone", location->region() + '/' + location->zone() } ); - } -#endif - - // Preserve those settings that have been made explicit. - auto newLocale = guessLocaleConfiguration(); - if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lang ) - { - newLocale.setLanguage( m_selectedLocaleConfiguration.language() ); - } - if ( !m_selectedLocaleConfiguration.isEmpty() && m_selectedLocaleConfiguration.explicit_lc ) - { - newLocale.lc_numeric = m_selectedLocaleConfiguration.lc_numeric; - newLocale.lc_time = m_selectedLocaleConfiguration.lc_time; - newLocale.lc_monetary = m_selectedLocaleConfiguration.lc_monetary; - newLocale.lc_paper = m_selectedLocaleConfiguration.lc_paper; - newLocale.lc_name = m_selectedLocaleConfiguration.lc_name; - newLocale.lc_address = m_selectedLocaleConfiguration.lc_address; - newLocale.lc_telephone = m_selectedLocaleConfiguration.lc_telephone; - newLocale.lc_measurement = m_selectedLocaleConfiguration.lc_measurement; - newLocale.lc_identification = m_selectedLocaleConfiguration.lc_identification; - } - newLocale.explicit_lang = m_selectedLocaleConfiguration.explicit_lang; - newLocale.explicit_lc = m_selectedLocaleConfiguration.explicit_lc; - - m_selectedLocaleConfiguration = newLocale; updateLocaleLabels(); } + void LocalePage::regionChanged( int currentIndex ) { @@ -388,15 +154,17 @@ LocalePage::regionChanged( int currentIndex ) Q_UNUSED( currentIndex ) QString selectedRegion = m_regionCombo->currentData().toString(); - TZRegion* region = m_regionList.find< TZRegion >( selectedRegion ); + TZRegion* region = m_config->timezoneData().find< TZRegion >( selectedRegion ); if ( !region ) { return; } - m_zoneCombo->blockSignals( true ); - m_zoneCombo->setModel( new CStringListModel( region->zones() ) ); - m_zoneCombo->blockSignals( false ); + { + cSignalBlocker b( m_zoneCombo ); + m_zoneCombo->setModel( new CStringListModel( region->zones() ) ); + } + m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() ); } @@ -405,16 +173,19 @@ LocalePage::zoneChanged( int currentIndex ) { Q_UNUSED( currentIndex ) if ( !m_blockTzWidgetSet ) - m_tzWidget->setCurrentLocation( m_regionCombo->currentData().toString(), - m_zoneCombo->currentData().toString() ); - - updateGlobalStorage(); + { + m_config->setCurrentLocation( m_regionCombo->currentData().toString(), m_zoneCombo->currentData().toString() ); + } } void LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location ) { - m_blockTzWidgetSet = true; + if ( !location ) + { + return; + } + cBoolSetter< true > b( m_blockTzWidgetSet ); // Set region index int index = m_regionCombo->findData( location->region() ); @@ -433,58 +204,35 @@ LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location ) } m_zoneCombo->setCurrentIndex( index ); - - m_blockTzWidgetSet = false; - - updateGlobalStorage(); } void LocalePage::changeLocale() { - LCLocaleDialog* dlg - = new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().language() - : m_selectedLocaleConfiguration.language(), - m_localeGenLines, - this ); + QPointer< LCLocaleDialog > dlg( + new LCLocaleDialog( m_config->localeConfiguration().language(), m_config->supportedLocales(), this ) ); dlg->exec(); - if ( dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() ) + if ( dlg && dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() ) { - m_selectedLocaleConfiguration.setLanguage( dlg->selectedLCLocale() ); - m_selectedLocaleConfiguration.explicit_lang = true; - this->updateGlobalLocale(); - this->updateLocaleLabels(); + m_config->setLanguageExplicitly( dlg->selectedLCLocale() ); + updateLocaleLabels(); } - dlg->deleteLater(); + delete dlg; } void LocalePage::changeFormats() { - LCLocaleDialog* dlg - = new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ? guessLocaleConfiguration().lc_numeric - : m_selectedLocaleConfiguration.lc_numeric, - m_localeGenLines, - this ); + QPointer< LCLocaleDialog > dlg( + new LCLocaleDialog( m_config->localeConfiguration().lc_numeric, m_config->supportedLocales(), this ) ); dlg->exec(); - if ( dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() ) + if ( dlg && dlg->result() == QDialog::Accepted && !dlg->selectedLCLocale().isEmpty() ) { - // TODO: improve the granularity of this setting. - m_selectedLocaleConfiguration.lc_numeric = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_time = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_monetary = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_paper = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_name = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_address = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_telephone = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_measurement = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.lc_identification = dlg->selectedLCLocale(); - m_selectedLocaleConfiguration.explicit_lc = true; - - this->updateLocaleLabels(); + m_config->setLCLocaleExplicitly( dlg->selectedLCLocale() ); + updateLocaleLabels(); } - dlg->deleteLater(); + delete dlg; } diff --git a/src/modules/locale/LocalePage.h b/src/modules/locale/LocalePage.h index f31d81fb6..bf41f8f69 100644 --- a/src/modules/locale/LocalePage.h +++ b/src/modules/locale/LocalePage.h @@ -32,39 +32,23 @@ class QComboBox; class QLabel; class QPushButton; + +class Config; class TimeZoneWidget; class LocalePage : public QWidget { Q_OBJECT public: - explicit LocalePage( QWidget* parent = nullptr ); + explicit LocalePage( class Config* config, QWidget* parent = nullptr ); virtual ~LocalePage(); - void init( const QString& initialRegion, const QString& initialZone, const QString& localeGenPath ); - - QString prettyStatus() const; - - Calamares::JobList createJobs(); - - QMap< QString, QString > localesMap(); - void onActivate(); private: - LocaleConfiguration guessLocaleConfiguration() const; - - // For the given locale config, return two strings describing - // the settings for language and numbers. - std::pair< QString, QString > prettyLocaleStatus( const LocaleConfiguration& ) const; - - /** @brief Update the GS *locale* key with the selected system language. - * - * This uses whatever is set in m_selectedLocaleConfiguration as the language, - * and writes it to GS *locale* key (as a string, in BCP47 format). - */ - void updateGlobalLocale(); - void updateGlobalStorage(); + /// @brief Non-owning pointer to the ViewStep's config + Config* m_config; + void updateLocaleLabels(); void regionChanged( int currentIndex ); @@ -73,10 +57,6 @@ private: void changeLocale(); void changeFormats(); - // Dynamically, QList< TZRegion* > - CalamaresUtils::Locale::CStringPairList m_regionList; - std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel; - TimeZoneWidget* m_tzWidget; QComboBox* m_regionCombo; QComboBox* m_zoneCombo; @@ -88,9 +68,6 @@ private: QLabel* m_formatsLabel; QPushButton* m_formatsChangeButton; - LocaleConfiguration m_selectedLocaleConfiguration; - - QStringList m_localeGenLines; bool m_blockTzWidgetSet; }; diff --git a/src/modules/locale/LocaleViewStep.cpp b/src/modules/locale/LocaleViewStep.cpp index 3a8c37673..a85c87e4f 100644 --- a/src/modules/locale/LocaleViewStep.cpp +++ b/src/modules/locale/LocaleViewStep.cpp @@ -34,7 +34,6 @@ #include #include -#include CALAMARES_PLUGIN_FACTORY_DEFINITION( LocaleViewStepFactory, registerPlugin< LocaleViewStep >(); ) @@ -44,7 +43,7 @@ LocaleViewStep::LocaleViewStep( QObject* parent ) , m_widget( new QWidget() ) , m_actualWidget( nullptr ) , m_nextEnabled( false ) - , m_geoip( nullptr ) + , m_config( std::make_unique< Config >() ) { QBoxLayout* mainLayout = new QHBoxLayout; m_widget->setLayout( mainLayout ); @@ -66,11 +65,11 @@ LocaleViewStep::~LocaleViewStep() void LocaleViewStep::setUpPage() { + m_config->setCurrentLocation(); if ( !m_actualWidget ) { - m_actualWidget = new LocalePage(); + m_actualWidget = new LocalePage( m_config.get() ); } - m_actualWidget->init( m_startingTimezone.first, m_startingTimezone.second, m_localeGenPath ); m_widget->layout()->addWidget( m_actualWidget ); ensureSize( m_actualWidget->sizeHint() ); @@ -80,20 +79,6 @@ LocaleViewStep::setUpPage() } -void -LocaleViewStep::fetchGeoIpTimezone() -{ - if ( m_geoip && m_geoip->isValid() ) - { - m_startingTimezone = m_geoip->get(); - if ( !m_startingTimezone.isValid() ) - { - cWarning() << "GeoIP lookup at" << m_geoip->url() << "failed."; - } - } -} - - QString LocaleViewStep::prettyName() const { @@ -104,7 +89,7 @@ LocaleViewStep::prettyName() const QString LocaleViewStep::prettyStatus() const { - return m_prettyStatus; + return m_config->prettyStatus(); } @@ -146,7 +131,7 @@ LocaleViewStep::isAtEnd() const Calamares::JobList LocaleViewStep::jobs() const { - return m_jobs; + return m_config->createJobs(); } @@ -164,83 +149,11 @@ LocaleViewStep::onActivate() void LocaleViewStep::onLeave() { - if ( m_actualWidget ) - { - m_jobs = m_actualWidget->createJobs(); - m_prettyStatus = m_actualWidget->prettyStatus(); - - auto map = m_actualWidget->localesMap(); - QVariantMap vm; - for ( auto it = map.constBegin(); it != map.constEnd(); ++it ) - { - vm.insert( it.key(), it.value() ); - } - - Calamares::JobQueue::instance()->globalStorage()->insert( "localeConf", vm ); - } - else - { - m_jobs.clear(); - Calamares::JobQueue::instance()->globalStorage()->remove( "localeConf" ); - } } void LocaleViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { - QString region = CalamaresUtils::getString( configurationMap, "region" ); - QString zone = CalamaresUtils::getString( configurationMap, "zone" ); - if ( !region.isEmpty() && !zone.isEmpty() ) - { - m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone ); - } - else - { - m_startingTimezone - = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) ); - } - - m_localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" ); - if ( m_localeGenPath.isEmpty() ) - { - m_localeGenPath = QStringLiteral( "/etc/locale.gen" ); - } - - bool ok = false; - QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok ); - if ( ok ) - { - QString url = CalamaresUtils::getString( geoip, "url" ); - QString style = CalamaresUtils::getString( geoip, "style" ); - QString selector = CalamaresUtils::getString( geoip, "selector" ); - - m_geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector ); - if ( !m_geoip->isValid() ) - { - cWarning() << "GeoIP Style" << style << "is not recognized."; - } - } -} - -Calamares::RequirementsList -LocaleViewStep::checkRequirements() -{ - if ( m_geoip && m_geoip->isValid() ) - { - auto& network = CalamaresUtils::Network::Manager::instance(); - if ( network.hasInternet() ) - { - fetchGeoIpTimezone(); - } - else - { - if ( network.synchronousPing( m_geoip->url() ) ) - { - fetchGeoIpTimezone(); - } - } - } - - return Calamares::RequirementsList(); + m_config->setConfigurationMap( configurationMap ); } diff --git a/src/modules/locale/LocaleViewStep.h b/src/modules/locale/LocaleViewStep.h index 841aba97f..0a40da861 100644 --- a/src/modules/locale/LocaleViewStep.h +++ b/src/modules/locale/LocaleViewStep.h @@ -20,15 +20,11 @@ #ifndef LOCALEVIEWSTEP_H #define LOCALEVIEWSTEP_H -#include "geoip/Handler.h" -#include "geoip/Interface.h" -#include "utils/PluginFactory.h" -#include "viewpages/ViewStep.h" +#include "Config.h" #include "DllMacro.h" - -#include -#include +#include "utils/PluginFactory.h" +#include "viewpages/ViewStep.h" #include @@ -60,25 +56,16 @@ public: void setConfigurationMap( const QVariantMap& configurationMap ) override; - /// @brief Do setup (returns empty list) asynchronously - virtual Calamares::RequirementsList checkRequirements() override; - private slots: void setUpPage(); private: - void fetchGeoIpTimezone(); QWidget* m_widget; LocalePage* m_actualWidget; bool m_nextEnabled; - QString m_prettyStatus; - - CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone; - QString m_localeGenPath; - Calamares::JobList m_jobs; - std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip; + std::unique_ptr< Config > m_config; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( LocaleViewStepFactory ) diff --git a/src/modules/locale/locale.conf b/src/modules/locale/locale.conf index dc68a050f..8236a879b 100644 --- a/src/modules/locale/locale.conf +++ b/src/modules/locale/locale.conf @@ -1,5 +1,5 @@ --- -# This settings are used to set your default system time zone. +# These settings are used to set your default system time zone. # Time zones are usually located under /usr/share/zoneinfo and # provided by the 'tzdata' package of your Distribution. # @@ -11,20 +11,41 @@ # the locale page can be set through keys *region* and *zone*. # If either is not set, defaults to America/New_York. # +# Note that useSystemTimezone and GeoIP settings can change the +# starting time zone. +# region: "America" zone: "New_York" +# Instead of using *region* and *zone* specified above, +# you can use the system's notion of the timezone, instead. +# This can help if your system is automatically configured with +# a sensible TZ rather than chasing a fixed default. +# +# The default is false. +# +# useSystemTimezone: true + +# Should changing the system location (e.g. clicking around on the timezone +# map) immediately reflect the changed timezone in the live system? +# By default, installers (with a target system) do, and setup (e.g. OEM +# configuration) does not, but you can switch it on here (or off, if +# you think it's annoying in the installer). +# +# Note that not all systems support live adjustment. +# +# adjustLiveTimezone: true # System locales are detected in the following order: # # - /usr/share/i18n/SUPPORTED # - localeGenPath (defaults to /etc/locale.gen if not set) -# - 'locale -a' output +# - `locale -a` output # -# Enable only when your Distribution is using an +# Enable only when your Distribution is using a # custom path for locale.gen # -#localeGenPath: "PATH_TO/locale.gen" +#localeGenPath: "/etc/locale.gen" # GeoIP based Language settings: Leave commented out to disable GeoIP. # diff --git a/src/modules/locale/locale.schema.yaml b/src/modules/locale/locale.schema.yaml index 41c3ad487..d6c35020f 100644 --- a/src/modules/locale/locale.schema.yaml +++ b/src/modules/locale/locale.schema.yaml @@ -4,7 +4,35 @@ $id: https://calamares.io/schemas/locale additionalProperties: false type: object properties: - "region": { type: str } - "zone": { type: str } - "localeGenPath": { type: string, required: true } - "geoipUrl": { type: str } + region: { type: string, + enum: [ + Africa, + America, + Antarctica, + Arctic, + Asia, + Atlantic, + Australia, + Europe, + Indian, + Pacific + ] + } + zone: { type: string } + useSystemTimezone: { type: boolean, default: false } + + adjustLiveTimezone: { type: boolean, default: true } + + localeGenPath: { type: string } + + # TODO: refactor, this is reused in welcome + geoip: + additionalProperties: false + type: object + properties: + style: { type: string, enum: [ none, fixed, xml, json ] } + url: { type: string } + selector: { type: string } + required: [ style, url, selector ] + +required: [ region, zone ] diff --git a/src/modules/locale/timezonewidget/TimeZoneImage.cpp b/src/modules/locale/timezonewidget/TimeZoneImage.cpp index a60c9b7d7..22bd263d6 100644 --- a/src/modules/locale/timezonewidget/TimeZoneImage.cpp +++ b/src/modules/locale/timezonewidget/TimeZoneImage.cpp @@ -25,9 +25,9 @@ #include static const char* zoneNames[] - = { "0.0", "1.0", "2.0", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "5.75", "6.0", "6.5", "7.0", - "8.0", "9.0", "9.5", "10.0", "10.5", "11.0", "12.0", "12.75", "13.0", "-1.0", "-2.0", "-3.0", - "-3.5", "-4.0", "-4.5", "-5.0", "-5.5", "-6.0", "-7.0", "-8.0", "-9.0", "-9.5", "-10.0", "-11.0" }; + = { "0.0", "1.0", "2.0", "3.0", "3.5", "4.0", "4.5", "5.0", "5.5", "5.75", "6.0", "6.5", "7.0", + "8.0", "9.0", "9.5", "10.0", "10.5", "11.0", "12.0", "12.75", "13.0", "-1.0", "-2.0", "-3.0", "-3.5", + "-4.0", "-4.5", "-5.0", "-5.5", "-6.0", "-7.0", "-8.0", "-9.0", "-9.5", "-10.0", "-11.0" }; static_assert( TimeZoneImageList::zoneCount == ( sizeof( zoneNames ) / sizeof( zoneNames[ 0 ] ) ), "Incorrect number of zones" ); diff --git a/src/modules/locale/timezonewidget/timezonewidget.cpp b/src/modules/locale/timezonewidget/timezonewidget.cpp index b81e0414c..0972e3296 100644 --- a/src/modules/locale/timezonewidget/timezonewidget.cpp +++ b/src/modules/locale/timezonewidget/timezonewidget.cpp @@ -34,9 +34,17 @@ #define ZONE_NAME QStringLiteral( "zone" ) #endif -TimeZoneWidget::TimeZoneWidget( QWidget* parent ) +static QPoint +getLocationPosition( const CalamaresUtils::Locale::TZZone* l ) +{ + return TimeZoneImageList::getLocationPosition( l->longitude(), l->latitude() ); +} + + +TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent ) : QWidget( parent ) , timeZoneImages( TimeZoneImageList::fromQRC() ) + , m_zonesData( zones ) { setMouseTracking( false ); setCursor( Qt::PointingHandCursor ); @@ -57,27 +65,13 @@ TimeZoneWidget::TimeZoneWidget( QWidget* parent ) void -TimeZoneWidget::setCurrentLocation( QString regionName, QString zoneName ) +TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ) { - using namespace CalamaresUtils::Locale; - const auto& regions = TZRegion::fromZoneTab(); - auto* region = regions.find< TZRegion >( regionName ); - if ( !region ) + if ( location == m_currentLocation ) { return; } - auto* zone = region->zones().find< TZZone >( zoneName ); - if ( zone ) - { - setCurrentLocation( zone ); - } -} - - -void -TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ) -{ m_currentLocation = location; // Set zone @@ -93,7 +87,6 @@ TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* locati // Repaint widget repaint(); - emit locationChanged( m_currentLocation ); } @@ -101,11 +94,18 @@ TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* locati //### Private //### +struct PainterEnder +{ + QPainter& p; + ~PainterEnder() { p.end(); } +}; + void TimeZoneWidget::paintEvent( QPaintEvent* ) { QFontMetrics fontMetrics( font ); QPainter painter( this ); + PainterEnder painter_end { painter }; painter.setRenderHint( QPainter::Antialiasing ); painter.setFont( font ); @@ -116,6 +116,11 @@ TimeZoneWidget::paintEvent( QPaintEvent* ) // Draw zone image painter.drawImage( 0, 0, currentZoneImage ); + if ( !m_currentLocation ) + { + return; + } + #ifdef DEBUG_TIMEZONES QPoint point = getLocationPosition( m_currentLocation ); // Draw latitude lines @@ -175,8 +180,6 @@ TimeZoneWidget::paintEvent( QPaintEvent* ) painter.setPen( Qt::white ); painter.drawText( rect.x() + 5, rect.bottom() - 4, m_currentLocation ? m_currentLocation->tr() : QString() ); #endif - - painter.end(); } @@ -194,7 +197,7 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event ) using namespace CalamaresUtils::Locale; const TZZone* closest = nullptr; - for ( const auto* region_p : TZRegion::fromZoneTab() ) + for ( const auto* region_p : m_zonesData ) { const auto* region = dynamic_cast< const TZRegion* >( region_p ); if ( region ) @@ -222,6 +225,6 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event ) // Set zone image and repaint widget setCurrentLocation( closest ); // Emit signal - emit locationChanged( m_currentLocation ); + emit locationChanged( closest ); } } diff --git a/src/modules/locale/timezonewidget/timezonewidget.h b/src/modules/locale/timezonewidget/timezonewidget.h index afebbfd7b..6bb94c0dd 100644 --- a/src/modules/locale/timezonewidget/timezonewidget.h +++ b/src/modules/locale/timezonewidget/timezonewidget.h @@ -31,32 +31,48 @@ #include #include +/** @brief The TimeZoneWidget shows a map and reports where clicks happen + * + * This widget shows a map (unspecified whether it's a whole world map + * or can show regionsvia some kind of internal state). Mouse clicks are + * translated into timezone locations (e.g. the zone for America/New_York). + * + * The current location can be changed programmatically, by name + * or through a pointer to a location. If a pointer is used, take care + * that the pointer is to a zone in the same model as used by the + * widget. + * + * When a location is chosen -- by mouse click or programmatically -- + * the locationChanged() signal is emitted with the new location. + * + * NOTE: the widget currently uses the globally cached TZRegion::fromZoneTab() + */ class TimeZoneWidget : public QWidget { Q_OBJECT public: using TZZone = CalamaresUtils::Locale::TZZone; - explicit TimeZoneWidget( QWidget* parent = nullptr ); + explicit TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent = nullptr ); - void setCurrentLocation( QString region, QString zone ); +public Q_SLOTS: + /** @brief Sets a location by pointer + * + * Pointer should be within the same model as the widget uses. + */ void setCurrentLocation( const TZZone* location ); - const TZZone* currentLocation() { return m_currentLocation; } - signals: + /** @brief The location has changed by mouse click */ void locationChanged( const TZZone* location ); private: QFont font; QImage background, pin, currentZoneImage; TimeZoneImageList timeZoneImages; - const TZZone* m_currentLocation = nullptr; // Not owned by me - QPoint getLocationPosition( const TZZone* l ) - { - return timeZoneImages.getLocationPosition( l->longitude(), l->latitude() ); - } + const CalamaresUtils::Locale::CStringPairList& m_zonesData; + const TZZone* m_currentLocation = nullptr; // Not owned by me void paintEvent( QPaintEvent* event ); void mousePressEvent( QMouseEvent* event ); diff --git a/src/modules/localeq/LocaleQmlViewStep.cpp b/src/modules/localeq/LocaleQmlViewStep.cpp index fd5e72734..ead2e2673 100644 --- a/src/modules/localeq/LocaleQmlViewStep.cpp +++ b/src/modules/localeq/LocaleQmlViewStep.cpp @@ -19,93 +19,43 @@ #include "LocaleQmlViewStep.h" -#include "GlobalStorage.h" -#include "JobQueue.h" - -#include "geoip/Handler.h" -#include "network/Manager.h" -#include "utils/CalamaresUtilsGui.h" #include "utils/Logger.h" -#include "utils/Variant.h" -#include "utils/Yaml.h" - -#include "Branding.h" -#include "modulesystem/ModuleManager.h" -#include -#include -#include -#include CALAMARES_PLUGIN_FACTORY_DEFINITION( LocaleQmlViewStepFactory, registerPlugin< LocaleQmlViewStep >(); ) LocaleQmlViewStep::LocaleQmlViewStep( QObject* parent ) -: Calamares::QmlViewStep( parent ) -, m_config( new Config( this ) ) -, m_nextEnabled( false ) -, m_geoip( nullptr ) + : Calamares::QmlViewStep( parent ) + , m_config( std::make_unique< Config >( this ) ) { - emit nextStatusChanged( m_nextEnabled ); } QObject* LocaleQmlViewStep::getConfig() { - return m_config; -} - -void -LocaleQmlViewStep::fetchGeoIpTimezone() -{ - if ( m_geoip && m_geoip->isValid() ) - { - m_startingTimezone = m_geoip->get(); - if ( !m_startingTimezone.isValid() ) - { - cWarning() << "GeoIP lookup at" << m_geoip->url() << "failed."; - } - } - - m_config->setLocaleInfo(m_startingTimezone.first, m_startingTimezone.second, m_localeGenPath); + return m_config.get(); } -Calamares::RequirementsList LocaleQmlViewStep::checkRequirements() +QString +LocaleQmlViewStep::prettyName() const { - if ( m_geoip && m_geoip->isValid() ) - { - auto& network = CalamaresUtils::Network::Manager::instance(); - if ( network.hasInternet() ) - { - fetchGeoIpTimezone(); - } - else - { - if ( network.synchronousPing( m_geoip->url() ) ) - { - fetchGeoIpTimezone(); - } - } - } - - return Calamares::RequirementsList(); + return tr( "Location" ); } QString -LocaleQmlViewStep::prettyName() const +LocaleQmlViewStep::prettyStatus() const { - return tr( "Location" ); + return m_config->prettyStatus(); } bool LocaleQmlViewStep::isNextEnabled() const { - // TODO: should return true return true; } bool LocaleQmlViewStep::isBackEnabled() const { - // TODO: should return true (it's weird that you are not allowed to have welcome *after* anything return true; } @@ -113,7 +63,6 @@ LocaleQmlViewStep::isBackEnabled() const bool LocaleQmlViewStep::isAtBeginning() const { - // TODO: adjust to "pages" in the QML return true; } @@ -121,79 +70,18 @@ LocaleQmlViewStep::isAtBeginning() const bool LocaleQmlViewStep::isAtEnd() const { - // TODO: adjust to "pages" in the QML return true; } Calamares::JobList LocaleQmlViewStep::jobs() const { - return m_jobs; + return m_config->createJobs(); } -void LocaleQmlViewStep::onActivate() -{ - // TODO no sure if it is needed at all or for the abstract class to start something -} - -void LocaleQmlViewStep::onLeave() -{ - if ( true ) - { - m_jobs = m_config->createJobs(); -// m_prettyStatus = m_actualWidget->prettyStatus(); - - auto map = m_config->localesMap(); - QVariantMap vm; - for ( auto it = map.constBegin(); it != map.constEnd(); ++it ) - { - vm.insert( it.key(), it.value() ); - } - - Calamares::JobQueue::instance()->globalStorage()->insert( "localeConf", vm ); - } - else - { - m_jobs.clear(); - Calamares::JobQueue::instance()->globalStorage()->remove( "localeConf" ); - } -} - -void LocaleQmlViewStep::setConfigurationMap(const QVariantMap& configurationMap) +void +LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { - QString region = CalamaresUtils::getString( configurationMap, "region" ); - QString zone = CalamaresUtils::getString( configurationMap, "zone" ); - if ( !region.isEmpty() && !zone.isEmpty() ) - { - m_startingTimezone = CalamaresUtils::GeoIP::RegionZonePair( region, zone ); - } - else - { - m_startingTimezone - = CalamaresUtils::GeoIP::RegionZonePair( QStringLiteral( "America" ), QStringLiteral( "New_York" ) ); - } - - m_localeGenPath = CalamaresUtils::getString( configurationMap, "localeGenPath" ); - if ( m_localeGenPath.isEmpty() ) - { - m_localeGenPath = QStringLiteral( "/etc/locale.gen" ); - } - - bool ok = false; - QVariantMap geoip = CalamaresUtils::getSubMap( configurationMap, "geoip", ok ); - if ( ok ) - { - QString url = CalamaresUtils::getString( geoip, "url" ); - QString style = CalamaresUtils::getString( geoip, "style" ); - QString selector = CalamaresUtils::getString( geoip, "selector" ); - - m_geoip = std::make_unique< CalamaresUtils::GeoIP::Handler >( style, url, selector ); - if ( !m_geoip->isValid() ) - { - cWarning() << "GeoIP Style" << style << "is not recognized."; - } - } - - checkRequirements(); - Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last + m_config->setConfigurationMap( configurationMap ); + Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last } diff --git a/src/modules/localeq/LocaleQmlViewStep.h b/src/modules/localeq/LocaleQmlViewStep.h index 0639274d6..3d73c6f79 100644 --- a/src/modules/localeq/LocaleQmlViewStep.h +++ b/src/modules/localeq/LocaleQmlViewStep.h @@ -20,14 +20,10 @@ #define LOCALE_QMLVIEWSTEP_H #include "Config.h" -#include "geoip/Handler.h" -#include "geoip/Interface.h" + +#include "DllMacro.h" #include "utils/PluginFactory.h" #include "viewpages/QmlViewStep.h" -#include - -#include -#include #include @@ -39,6 +35,7 @@ public: explicit LocaleQmlViewStep( QObject* parent = nullptr ); QString prettyName() const override; + QString prettyStatus() const override; bool isNextEnabled() const override; bool isBackEnabled() const override; @@ -47,28 +44,12 @@ public: bool isAtEnd() const override; Calamares::JobList jobs() const override; - void onActivate() override; - void onLeave() override; void setConfigurationMap( const QVariantMap& configurationMap ) override; QObject* getConfig() override; - virtual Calamares::RequirementsList checkRequirements() override; - private: - // TODO: a generic QML viewstep should return a config object from a method - Config *m_config; - - bool m_nextEnabled; - QString m_prettyStatus; - - CalamaresUtils::GeoIP::RegionZonePair m_startingTimezone; - QString m_localeGenPath; - - Calamares::JobList m_jobs; - std::unique_ptr< CalamaresUtils::GeoIP::Handler > m_geoip; - - void fetchGeoIpTimezone(); + std::unique_ptr< Config > m_config; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( LocaleQmlViewStepFactory ) diff --git a/src/modules/localeq/Map.qml b/src/modules/localeq/Map.qml index 023de6d1b..080d7388a 100644 --- a/src/modules/localeq/Map.qml +++ b/src/modules/localeq/Map.qml @@ -74,6 +74,7 @@ Column { var tz2 = responseJSON.timezoneId tzText.text = "Timezone: " + tz2 + config.setCurrentLocation(tz2) } } @@ -126,7 +127,7 @@ Column { anchorPoint.x: image.width/4 anchorPoint.y: image.height coordinate: QtPositioning.coordinate( - map.center.latitude, + map.center.latitude, map.center.longitude) //coordinate: QtPositioning.coordinate(40.730610, -73.935242) // New York @@ -156,7 +157,7 @@ Column { map.center.longitude = coordinate.longitude getTz(); - + console.log(coordinate.latitude, coordinate.longitude) } } @@ -199,7 +200,7 @@ Column { } Rectangle { - width: parent.width + width: parent.width height: 100 anchors.horizontalCenter: parent.horizontalCenter diff --git a/src/modules/localeq/i18n.qml b/src/modules/localeq/i18n.qml index a806d0174..eea9864a3 100644 --- a/src/modules/localeq/i18n.qml +++ b/src/modules/localeq/i18n.qml @@ -32,10 +32,6 @@ Item { anchors.fill: parent } - //Needs to come from Locale config - property var confLang: "en_US.UTF8" - property var confLocale: "nl_NL.UTF8" - Rectangle { id: textArea x: 28 @@ -57,7 +53,7 @@ Item { width: 240 wrapMode: Text.WordWrap text: qsTr("

Languages


- The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(confLang) + The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(config.currentLanguageCode) font.pointSize: 10 } } @@ -76,8 +72,7 @@ Item { id: list1 focus: true - // bogus entries, need to come from Locale config - model: ["en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8"] + model: config.supportedLocales currentIndex: 1 highlight: Rectangle { @@ -95,17 +90,17 @@ Item { } onClicked: { list1.currentIndex = index - confLang = list1.currentIndex } } } + onCurrentItemChanged: { config.currentLanguageCode = model[currentIndex] } /* This works because model is a stringlist */ } } } } Column { - id: i18n + id: lc_numeric x: 430 y: 40 @@ -118,7 +113,7 @@ Item { width: 240 wrapMode: Text.WordWrap text: qsTr("

Locales


- The system locale setting affects the language and character set for some command line user interface elements. The current setting is %1.").arg(confLocale) + The system locale setting affects the numbers and dates format. The current setting is %1.").arg(config.currentLCCode) font.pointSize: 10 } } @@ -139,7 +134,7 @@ Item { focus: true // bogus entries, need to come from Locale config - model: ["en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8", "en_GB.UTF-8 UTF-8", "en_US.UTF-8 UTF-8 ", "nl_NL.UTF-8 UTF-8"] + model: config.supportedLocales currentIndex: 2 highlight: Rectangle { @@ -154,11 +149,10 @@ Item { cursorShape: Qt.PointingHandCursor onClicked: { list2.currentIndex = index - confLocale = list1.currentIndex } } } - onCurrentItemChanged: console.debug(currentIndex) + onCurrentItemChanged: { config.currentLCCode = model[currentIndex]; } /* This works because model is a stringlist */ } } } diff --git a/src/modules/localeq/localeq.qml b/src/modules/localeq/localeq.qml index ffd87f5b5..c48140d12 100644 --- a/src/modules/localeq/localeq.qml +++ b/src/modules/localeq/localeq.qml @@ -31,41 +31,14 @@ Page { property var confLang: "American English" property var confLocale: "Nederland" - //Needs to come from .conf/geoip - property var hasInternet: true - - function getInt(format) { - var requestURL = "https://example.org/"; - var xhr = new XMLHttpRequest; - - xhr.onreadystatechange = function() { - if (xhr.readyState === XMLHttpRequest.DONE) { - - if (xhr.status !== 200) { - console.log("Disconnected!!"); - var connected = false - hasInternet = connected - return; - } - - else { - console.log("Connected!!"); - } - } - } - xhr.open("GET", requestURL, true); - xhr.send(); - } - Component.onCompleted: { - getInt(); - } Loader { id: image anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: parent.height / 1.28 - source: (hasInternet) ? "Map.qml" : "Offline.qml" + // Network is in io.calamares.core + source: Network.hasInternet ? "Map.qml" : "Offline.qml" } RowLayout { @@ -95,7 +68,7 @@ Page { Label { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("System language set to %1").arg(confLang) + text: config.currentLanguageStatus } Kirigami.Separator { Layout.fillWidth: true @@ -103,7 +76,7 @@ Page { Label { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Numbers and dates locale set to %1").arg(confLocale) + text: config.currentLCStatus } } Button {