From 05f3fbea05629a3fef0316cb3b98625f88d52866 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Tue, 4 Aug 2020 13:45:36 +0200 Subject: [PATCH 01/29] [locale] Apply SPDX headers --- src/modules/locale/LocalePage.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/locale/LocalePage.cpp b/src/modules/locale/LocalePage.cpp index 406f27a6e..c10b2dee9 100644 --- a/src/modules/locale/LocalePage.cpp +++ b/src/modules/locale/LocalePage.cpp @@ -1,7 +1,8 @@ /* === This file is part of Calamares - === * - * Copyright 2014-2016, Teo Mrnjavac - * Copyright 2017-2019, Adriaan de Groot + * SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac + * SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From fce05acf1efd696dfaf0207a539a39a6cc6e3843 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 15:11:52 +0200 Subject: [PATCH 02/29] [libcalamares] Rip out all the TZ models - The models are overly complicated: **overall** there is just one list of timezones, and we need various views on that list. Start over with an empty model of regions. --- src/libcalamares/locale/Tests.cpp | 38 ----- src/libcalamares/locale/TimeZone.cpp | 242 ++++----------------------- src/libcalamares/locale/TimeZone.h | 166 ++---------------- 3 files changed, 38 insertions(+), 408 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 10b4ad056..16b7d11a7 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -249,32 +249,6 @@ LocaleTests::testSimpleZones() { using namespace CalamaresUtils::Locale; - { - TZRegion r; - QVERIFY( r.tr().isEmpty() ); - } - { - TZZone n; - QVERIFY( n.tr().isEmpty() ); - } - { - TZZone r0( "xAmsterdam" ); - QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) ); - TZZone r1( r0 ); - QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) ); - QCOMPARE( r1.tr(), QStringLiteral( "xAmsterdam" ) ); - TZZone r2( std::move( r0 ) ); - QCOMPARE( r2.tr(), QStringLiteral( "xAmsterdam" ) ); - QCOMPARE( r0.tr(), QString() ); - } - { - TZZone r0( nullptr ); - QVERIFY( r0.tr().isEmpty() ); - TZZone r1( r0 ); - QVERIFY( r1.tr().isEmpty() ); - TZZone r2( std::move( r0 ) ); - QVERIFY( r2.tr().isEmpty() ); - } } void @@ -282,18 +256,6 @@ LocaleTests::testComplexZones() { using namespace CalamaresUtils::Locale; - { - TZZone r0( "America/New_York" ); - TZZone r1( "America/New York" ); - - QCOMPARE( r0.tr(), r1.tr() ); - QCOMPARE( r0.tr(), QStringLiteral( "America/New York" ) ); - } - { - TZZone r( "zxc,;*_vm" ); - QVERIFY( !r.tr().isEmpty() ); - QCOMPARE( r.tr(), QStringLiteral( "zxc,;* vm" ) ); // Only _ is special - } } QTEST_GUILESS_MAIN( LocaleTests ) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 772a242fb..37f94b6f2 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -23,16 +23,14 @@ #include "TimeZone.h" #include "utils/Logger.h" -#include "utils/String.h" - -#include -#include -#include - -#include static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab"; +/** @brief Turns a string longitude or latitude notation into a double + * + * This handles strings like "+4230+00131" from zone.tab, + * which is degrees-and-minutes notation, and + means north or east. + */ static double getRightGeoLocation( QString str ) { @@ -61,28 +59,6 @@ getRightGeoLocation( QString str ) return sign * num; } - -namespace CalamaresUtils -{ -namespace Locale -{ - - -CStringPair::CStringPair( CStringPair&& t ) - : m_human( nullptr ) - , m_key() -{ - // My pointers are initialized to nullptr - std::swap( m_human, t.m_human ); - std::swap( m_key, t.m_key ); -} - -CStringPair::CStringPair( const CStringPair& t ) - : m_human( t.m_human ? strdup( t.m_human ) : nullptr ) - , m_key( t.m_key ) -{ -} - /** @brief Massage an identifier into a human-readable form * * Makes a copy of @p s, caller must free() it. @@ -110,204 +86,42 @@ munge( const char* s ) return t; } -CStringPair::CStringPair( const char* s1 ) - : m_human( s1 ? munge( s1 ) : nullptr ) - , m_key( s1 ? QString( s1 ) : QString() ) + +namespace CalamaresUtils +{ +namespace Locale +{ + +struct Private { +}; + +static Private* privateInstance() +{ + static Private* s_p = new Private; + return s_p; +} + +RegionsModel::RegionsModel() +: QAbstractListModel() +, m_private( privateInstance() ) { } - -CStringPair::~CStringPair() -{ - free( m_human ); -} - - -QString -TZRegion::tr() const -{ - // NOTE: context name must match what's used in zone-extractor.py - return QObject::tr( m_human, "tz_regions" ); -} - -TZRegion::~TZRegion() -{ - qDeleteAll( m_zones ); -} - -const CStringPairList& -TZRegion::fromZoneTab() -{ - static CStringPairList zoneTab = TZRegion::fromFile( TZ_DATA_FILE ); - return zoneTab; -} - -CStringPairList -TZRegion::fromFile( const char* fileName ) -{ - CStringPairList model; - - QFile file( fileName ); - if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) - { - return model; - } - - TZRegion* thisRegion = nullptr; - QTextStream in( &file ); - while ( !in.atEnd() ) - { - QString line = in.readLine().trimmed().split( '#', SplitKeepEmptyParts ).first().trimmed(); - if ( line.isEmpty() ) - { - continue; - } - - QStringList list = line.split( QRegExp( "[\t ]" ), SplitSkipEmptyParts ); - if ( list.size() < 3 ) - { - continue; - } - - QStringList timezoneParts = list.at( 2 ).split( '/', SplitSkipEmptyParts ); - if ( timezoneParts.size() < 2 ) - { - continue; - } - - QString region = timezoneParts.first().trimmed(); - if ( region.isEmpty() ) - { - continue; - } - - auto keyMatch = [®ion]( const CStringPair* r ) { return r->key() == region; }; - auto it = std::find_if( model.begin(), model.end(), keyMatch ); - if ( it != model.end() ) - { - thisRegion = dynamic_cast< TZRegion* >( *it ); - } - else - { - thisRegion = new TZRegion( region.toUtf8().data() ); - model.append( thisRegion ); - } - - QString countryCode = list.at( 0 ).trimmed(); - if ( countryCode.size() != 2 ) - { - continue; - } - - timezoneParts.removeFirst(); - thisRegion->m_zones.append( - new TZZone( region, timezoneParts.join( '/' ).toUtf8().constData(), countryCode, list.at( 1 ) ) ); - } - - auto sorter = []( const CStringPair* l, const CStringPair* r ) { return *l < *r; }; - std::sort( model.begin(), model.end(), sorter ); - for ( auto& it : model ) - { - TZRegion* r = dynamic_cast< TZRegion* >( it ); - if ( r ) - { - std::sort( r->m_zones.begin(), r->m_zones.end(), sorter ); - } - } - - return model; -} - -TZZone::TZZone( const QString& region, const char* zoneName, const QString& country, QString position ) - : CStringPair( zoneName ) - , m_region( region ) - , m_country( country ) -{ - int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 ); - if ( cooSplitPos > 0 ) - { - m_latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) ); - m_longitude = getRightGeoLocation( position.mid( cooSplitPos ) ); - } -} - -QString -TZZone::tr() const -{ - // NOTE: context name must match what's used in zone-extractor.py - return QObject::tr( m_human, "tz_names" ); -} - - -CStringListModel::CStringListModel( CStringPairList l ) - : m_list( l ) +RegionsModel::~RegionsModel() { } -void -CStringListModel::setList( CalamaresUtils::Locale::CStringPairList l ) +int RegionsModel::rowCount(const QModelIndex& parent) const { - beginResetModel(); - m_list = l; - endResetModel(); + return 0; } -int -CStringListModel::rowCount( const QModelIndex& ) const +QVariant RegionsModel::data(const QModelIndex& index, int role) const { - return m_list.count(); + return QVariant(); } -QVariant -CStringListModel::data( const QModelIndex& index, int role ) const -{ - if ( ( role != Qt::DisplayRole ) && ( role != Qt::UserRole ) ) - { - return QVariant(); - } - if ( !index.isValid() ) - { - return QVariant(); - } - - const auto* item = m_list.at( index.row() ); - return item ? ( role == Qt::DisplayRole ? item->tr() : item->key() ) : QVariant(); -} - -void -CStringListModel::setCurrentIndex( int index ) -{ - if ( ( index < 0 ) || ( index >= m_list.count() ) ) - { - return; - } - - m_currentIndex = index; - emit currentIndexChanged(); -} - -int -CStringListModel::currentIndex() const -{ - return m_currentIndex; -} - -QHash< int, QByteArray > -CStringListModel::roleNames() const -{ - return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } }; -} - -const CStringPair* -CStringListModel::item( int index ) const -{ - if ( ( index < 0 ) || ( index >= m_list.count() ) ) - { - return nullptr; - } - return m_list[ index ]; -} } // namespace Locale } // namespace CalamaresUtils diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 05820817a..a34e03248 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -24,182 +24,36 @@ #include "DllMacro.h" -#include "utils/Logger.h" - #include #include -#include - -#include +#include namespace CalamaresUtils { namespace Locale { +struct Private; -/** @brief A pair of strings, one human-readable, one a key +/** @brief The list of timezone regions * - * Given an identifier-like string (e.g. "New_York"), makes - * a human-readable version of that and keeps a copy of the - * identifier itself. - * - * This explicitly uses const char* instead of just being - * QPair because there is API that needs - * C-style strings. + * The regions are a short list of global areas (Africa, America, India ..) + * which contain zones. */ -class CStringPair : public QObject +class DLLEXPORT RegionsModel : public QAbstractListModel { Q_OBJECT -public: - /// @brief An empty pair - CStringPair() {} - /// @brief Given an identifier, create the pair - explicit CStringPair( const char* s1 ); - CStringPair( CStringPair&& t ); - CStringPair( const CStringPair& ); - virtual ~CStringPair(); - /// @brief Give the localized human-readable form - virtual QString tr() const = 0; - QString key() const { return m_key; } - - bool operator<( const CStringPair& other ) const { return m_key < other.m_key; } - -protected: - char* m_human = nullptr; - QString m_key; -}; - -class CStringPairList : public QList< CStringPair* > -{ -public: - template < typename T > - T* find( const QString& key ) const - { - for ( auto* p : *this ) - { - if ( p->key() == key ) - { - return dynamic_cast< T* >( p ); - } - } - return nullptr; - } -}; - -/** @brief Timezone regions (e.g. "America") - * - * A region has a key and a human-readable name, but also - * a collection of associated timezone zones (TZZone, below). - * This class is not usually constructed, but uses fromFile() - * to load a complete tree structure of timezones. - */ -class TZRegion : public CStringPair -{ - Q_OBJECT -public: - using CStringPair::CStringPair; - virtual ~TZRegion() override; - TZRegion( const TZRegion& ) = delete; - QString tr() const override; - - QString region() const { return key(); } - - /** @brief Create list from a zone.tab-like file - * - * Returns a list of all the regions; each region has a list - * of zones within that region. Dyamically, the items in the - * returned list are TZRegions; their zones dynamically are - * TZZones even though all those lists have type CStringPairList. - * - * The list owns the regions, and the regions own their own list of zones. - * When getting rid of the list, remember to qDeleteAll() on it. - */ - static CStringPairList fromFile( const char* fileName ); - /// @brief Calls fromFile with the standard zone.tab name - static const CStringPairList& fromZoneTab(); - - const CStringPairList& zones() const { return m_zones; } - -private: - CStringPairList m_zones; -}; - -/** @brief Specific timezone zones (e.g. "New_York", "New York") - * - * A timezone zone lives in a region, and has some associated - * data like the country (used to map likely languages) and latitude - * and longitude information. - */ -class TZZone : public CStringPair -{ - Q_OBJECT -public: - using CStringPair::CStringPair; - QString tr() const override; - - TZZone( const QString& region, const char* zoneName, const QString& country, QString position ); - - QString region() const { return m_region; } - QString zone() const { return key(); } - QString country() const { return m_country; } - double latitude() const { return m_latitude; } - double longitude() const { return m_longitude; } - -protected: - QString m_region; - QString m_country; - double m_latitude = 0.0, m_longitude = 0.0; -}; - -class CStringListModel : public QAbstractListModel -{ - Q_OBJECT - Q_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged ) + RegionsModel(); public: - /// @brief Create empty model - CStringListModel() {} - /// @brief Create model from list (non-owning) - CStringListModel( CStringPairList ); + virtual ~RegionsModel() override; + static RegionsModel* instance(); int rowCount( const QModelIndex& parent ) const override; - QVariant data( const QModelIndex& index, int role ) const override; - const CStringPair* item( int index ) const; - QHash< int, QByteArray > roleNames() const override; - - void setCurrentIndex( int index ); - int currentIndex() const; - - void setList( CStringPairList ); - - inline int indexOf( const QString& key ) - { - const auto it = std::find_if( - m_list.constBegin(), m_list.constEnd(), [&]( const CalamaresUtils::Locale::CStringPair* item ) -> bool { - return item->key() == key; - } ); - - if ( it != m_list.constEnd() ) - { - // distance() is usually a long long - return int( std::distance( m_list.constBegin(), it ) ); - } - else - { - return -1; - } - } - - private: - CStringPairList m_list; - int m_currentIndex = -1; - -signals: - void currentIndexChanged(); + Private *m_private; }; } // namespace Locale From ca40d2e2d9134797cdf3ad909578382f79c38635 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 15:17:09 +0200 Subject: [PATCH 03/29] [libcalamares] Introduce a failing test for the number of regions --- src/libcalamares/locale/Tests.cpp | 12 ++++++++++-- src/libcalamares/locale/TimeZone.cpp | 23 ++++++++++++----------- src/libcalamares/locale/TimeZone.h | 5 ++--- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 16b7d11a7..54e6cd848 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -47,6 +47,7 @@ private Q_SLOTS: void testInterlingue(); // TimeZone testing + void testRegions(); void testSimpleZones(); void testComplexZones(); }; @@ -244,18 +245,25 @@ LocaleTests::testTranslatableConfig2() QCOMPARE( ts3.count(), 1 ); // The empty string } +void +LocaleTests::testRegions() +{ + CalamaresUtils::Locale::RegionsModel regions; + + QVERIFY( regions.rowCount( QModelIndex() ) > 3 ); // Africa, America, Asia +} + + void LocaleTests::testSimpleZones() { using namespace CalamaresUtils::Locale; - } void LocaleTests::testComplexZones() { using namespace CalamaresUtils::Locale; - } QTEST_GUILESS_MAIN( LocaleTests ) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 37f94b6f2..0dba52a2a 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -92,36 +92,37 @@ namespace CalamaresUtils namespace Locale { -struct Private { +struct Private +{ }; -static Private* privateInstance() +static Private* +privateInstance() { static Private* s_p = new Private; return s_p; } -RegionsModel::RegionsModel() -: QAbstractListModel() -, m_private( privateInstance() ) +RegionsModel::RegionsModel( QObject* parent ) + : QAbstractListModel( parent ) + , m_private( privateInstance() ) { } -RegionsModel::~RegionsModel() -{ -} +RegionsModel::~RegionsModel() {} -int RegionsModel::rowCount(const QModelIndex& parent) const +int +RegionsModel::rowCount( const QModelIndex& parent ) const { return 0; } -QVariant RegionsModel::data(const QModelIndex& index, int role) const +QVariant +RegionsModel::data( const QModelIndex& index, int role ) const { return QVariant(); } - } // namespace Locale } // namespace CalamaresUtils diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index a34e03248..b3d57c4fd 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -43,17 +43,16 @@ class DLLEXPORT RegionsModel : public QAbstractListModel { Q_OBJECT - RegionsModel(); public: + RegionsModel( QObject* parent = nullptr ); virtual ~RegionsModel() override; - static RegionsModel* instance(); int rowCount( const QModelIndex& parent ) const override; QVariant data( const QModelIndex& index, int role ) const override; private: - Private *m_private; + Private* m_private; }; } // namespace Locale From 82cc652f55200ac3b0a06fa7517bbb867604a3cb Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 16:21:05 +0200 Subject: [PATCH 04/29] [libcalamares] Re-done zones loading - just make one big list of zones, one short list of regions - the models are non-functional right now --- src/libcalamares/locale/Tests.cpp | 4 +- src/libcalamares/locale/TimeZone.cpp | 232 ++++++++++++++++++++++++++- src/libcalamares/locale/TimeZone.h | 16 ++ 3 files changed, 250 insertions(+), 2 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 54e6cd848..4690a50a3 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -257,7 +257,9 @@ LocaleTests::testRegions() void LocaleTests::testSimpleZones() { - using namespace CalamaresUtils::Locale; + CalamaresUtils::Locale::ZonesModel zones; + + QVERIFY( zones.rowCount( QModelIndex() ) > 24 ); } void diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 0dba52a2a..e12f404a5 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -23,6 +23,10 @@ #include "TimeZone.h" #include "utils/Logger.h" +#include "utils/String.h" + +#include +#include static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab"; @@ -59,6 +63,42 @@ getRightGeoLocation( QString str ) return sign * num; } +/** @brief A pair of strings, one human-readable, one a key + * + * Given an identifier-like string (e.g. "New_York"), makes + * a human-readable version of that and keeps a copy of the + * identifier itself. + * + * This explicitly uses const char* instead of just being + * QPair because the human-readable part + * may need to be translated through tr(), and that takes a char*. + * C-style strings. + */ +class CStringPair : public QObject +{ + Q_OBJECT +public: + /// @brief An empty pair + CStringPair() {} + /// @brief Given an identifier, create the pair + explicit CStringPair( const char* s1 ); + explicit CStringPair( const QString& s ); + CStringPair( CStringPair&& t ); + CStringPair( const CStringPair& ); + virtual ~CStringPair(); + + /// @brief Give the localized human-readable form + virtual QString tr() const = 0; + QString key() const { return m_key; } + + bool operator==( const CStringPair& other ) const { return m_key == other.m_key; } + bool operator<( const CStringPair& other ) const { return m_key < other.m_key; } + +protected: + char* m_human = nullptr; + QString m_key; +}; + /** @brief Massage an identifier into a human-readable form * * Makes a copy of @p s, caller must free() it. @@ -86,6 +126,162 @@ munge( const char* s ) return t; } +CStringPair::CStringPair( CStringPair&& t ) + : m_human( nullptr ) + , m_key() +{ + // My pointers are initialized to nullptr + std::swap( m_human, t.m_human ); + std::swap( m_key, t.m_key ); +} + +CStringPair::CStringPair( const CStringPair& t ) + : m_human( t.m_human ? strdup( t.m_human ) : nullptr ) + , m_key( t.m_key ) +{ +} + +CStringPair::CStringPair( const char* s1 ) + : m_human( s1 ? munge( s1 ) : nullptr ) + , m_key( s1 ? QString( s1 ) : QString() ) +{ +} + +CStringPair::CStringPair( const QString& s ) + : m_human( strdup( s.toUtf8().constData() ) ) + , m_key( s ) +{ +} + + +CStringPair::~CStringPair() +{ + free( m_human ); +} + + +class TimeZoneData : public CStringPair +{ +public: + TimeZoneData( const QString& region, + const QString& zone, + const QString& country, + double latitude, + double longitude ); + QString tr() const override; + +private: + QString m_region; + QString m_country; + double m_latitude; + double m_longitude; +}; + +TimeZoneData::TimeZoneData( const QString& region, + const QString& zone, + const QString& country, + double latitude, + double longitude ) + : CStringPair( zone ) + , m_region( region ) + , m_country( country ) + , m_latitude( latitude ) + , m_longitude( longitude ) +{ +} + +QString +TimeZoneData::tr() const +{ + // NOTE: context name must match what's used in zone-extractor.py + return QObject::tr( m_human, "tz_names" ); +} + + +class RegionData : public CStringPair +{ +public: + using CStringPair::CStringPair; + QString tr() const override; +}; + +QString +RegionData::tr() const +{ + // NOTE: context name must match what's used in zone-extractor.py + return QObject::tr( m_human, "tz_regions" ); +} + +static void +loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData >& zones ) +{ + QFile file( TZ_DATA_FILE ); + if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) ) + { + QTextStream in( &file ); + while ( !in.atEnd() ) + { + QString line = in.readLine().trimmed().split( '#', SplitKeepEmptyParts ).first().trimmed(); + if ( line.isEmpty() ) + { + continue; + } + + QStringList list = line.split( QRegExp( "[\t ]" ), SplitSkipEmptyParts ); + if ( list.size() < 3 ) + { + continue; + } + + QStringList timezoneParts = list.at( 2 ).split( '/', SplitSkipEmptyParts ); + if ( timezoneParts.size() < 2 ) + { + continue; + } + + QString region = timezoneParts.first().trimmed(); + if ( region.isEmpty() ) + { + continue; + } + + QString countryCode = list.at( 0 ).trimmed(); + if ( countryCode.size() != 2 ) + { + continue; + } + + timezoneParts.removeFirst(); + QString zone = timezoneParts.join( '/' ); + if ( zone.length() < 2 ) + { + continue; + } + + QString position = list.at( 1 ); + int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 ); + double latitude; + double longitude; + if ( cooSplitPos > 0 ) + { + latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) ); + longitude = getRightGeoLocation( position.mid( cooSplitPos ) ); + } + else + { + continue; + } + + // Now we have region, zone, country, lat and longitude + RegionData r( region ); + if ( regions.indexOf( r ) < 0 ) + { + regions.append( std::move( r ) ); + } + zones.append( TimeZoneData( region, zone, countryCode, latitude, longitude ) ); + } + } +} namespace CalamaresUtils { @@ -94,6 +290,16 @@ namespace Locale struct Private { + QVector< RegionData > m_regions; + QVector< TimeZoneData > m_zones; + + Private() + { + m_regions.reserve( 12 ); // reasonable guess + m_zones.reserve( 452 ); // wc -l /usr/share/zoneinfo/zone.tab + + loadTZData( m_regions, m_zones ); + } }; static Private* @@ -114,7 +320,7 @@ RegionsModel::~RegionsModel() {} int RegionsModel::rowCount( const QModelIndex& parent ) const { - return 0; + return m_private->m_regions.count(); } QVariant @@ -123,6 +329,30 @@ RegionsModel::data( const QModelIndex& index, int role ) const return QVariant(); } +ZonesModel::ZonesModel( QObject* parent ) + : QAbstractListModel( parent ) + , m_private( privateInstance() ) +{ +} + +ZonesModel::~ZonesModel() {} + +int +ZonesModel::rowCount( const QModelIndex& parent ) const +{ + return m_private->m_zones.count(); +} + +QVariant +ZonesModel::data( const QModelIndex& index, int role ) const +{ + return QVariant(); +} + } // namespace Locale } // namespace CalamaresUtils + +#include "utils/moc-warnings.h" + +#include "TimeZone.moc" diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index b3d57c4fd..4ded57d8f 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -55,6 +55,22 @@ private: Private* m_private; }; +class DLLEXPORT ZonesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ZonesModel( QObject* parent = nullptr ); + virtual ~ZonesModel() override; + + int rowCount( const QModelIndex& parent ) const override; + QVariant data( const QModelIndex& index, int role ) const override; + +private: + Private* m_private; +}; + + } // namespace Locale } // namespace CalamaresUtils From 609ea8350c947ad492e0a0a4995a3519b695f4da Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 16:25:45 +0200 Subject: [PATCH 05/29] [libcalamares] Failing test: there is data in the regions model --- src/libcalamares/locale/Tests.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 4690a50a3..5ac70d3d0 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -251,6 +251,17 @@ LocaleTests::testRegions() CalamaresUtils::Locale::RegionsModel regions; QVERIFY( regions.rowCount( QModelIndex() ) > 3 ); // Africa, America, Asia + + QStringList names; + for ( int i = 0; i < regions.rowCount( QModelIndex() ); ++i ) + { + QVariant name = regions.data( regions.index( i ), Qt::UserRole ); + QVERIFY( name.isValid() ); + QVERIFY( !name.toString().isEmpty() ); + names.append( name.toString() ); + } + + QVERIFY( names.contains( "America" ) ); } From 33e39b92fb9f623256f4ead751b53d058284e59b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 16:36:00 +0200 Subject: [PATCH 06/29] [libcalamares] Satisfy test, return region names --- src/libcalamares/locale/Tests.cpp | 3 ++- src/libcalamares/locale/TimeZone.cpp | 20 ++++++++++++++++++++ src/libcalamares/locale/TimeZone.h | 8 +++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 5ac70d3d0..e41e3a30b 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -255,13 +255,14 @@ LocaleTests::testRegions() QStringList names; for ( int i = 0; i < regions.rowCount( QModelIndex() ); ++i ) { - QVariant name = regions.data( regions.index( i ), Qt::UserRole ); + QVariant name = regions.data( regions.index( i ), Qt::DisplayRole ); QVERIFY( name.isValid() ); QVERIFY( !name.toString().isEmpty() ); names.append( name.toString() ); } QVERIFY( names.contains( "America" ) ); + QVERIFY( !names.contains( "UTC" ) ); } diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index e12f404a5..9f356d958 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -326,9 +326,29 @@ RegionsModel::rowCount( const QModelIndex& parent ) const QVariant RegionsModel::data( const QModelIndex& index, int role ) const { + if ( !index.isValid() || index.row() < 0 || index.row() >= m_private->m_regions.count() ) + { + return QVariant(); + } + + if ( role == Qt::DisplayRole ) + { + return m_private->m_regions[ index.row() ].tr(); + } + if ( role == KeyRole ) + { + return m_private->m_regions[ index.row() ].key(); + } return QVariant(); } +QHash< int, QByteArray > +RegionsModel::roleNames() const +{ + return { { Qt::DisplayRole, "name" }, { KeyRole, "key" } }; +} + + ZonesModel::ZonesModel( QObject* parent ) : QAbstractListModel( parent ) , m_private( privateInstance() ) diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 4ded57d8f..0a5fbf7f6 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -43,14 +43,20 @@ class DLLEXPORT RegionsModel : public QAbstractListModel { Q_OBJECT - public: + enum Roles + { + KeyRole = Qt::UserRole + 1 + }; + RegionsModel( QObject* parent = nullptr ); virtual ~RegionsModel() override; int rowCount( const QModelIndex& parent ) const override; QVariant data( const QModelIndex& index, int role ) const override; + QHash< int, QByteArray > roleNames() const override; + private: Private* m_private; }; From 1afdcc9c822a75152fca46cf982f936990849a06 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 16:45:41 +0200 Subject: [PATCH 07/29] [libcalamares] Give zones data, too - while here, fix bug in TimeZoneData that didn't munge names (so it reported "New_York") --- src/libcalamares/locale/Tests.cpp | 32 +++++++++++++++++++++++++--- src/libcalamares/locale/TimeZone.cpp | 31 ++++++++++++++++++++++----- src/libcalamares/locale/TimeZone.h | 9 ++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index e41e3a30b..c660ccbfb 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -248,14 +248,15 @@ LocaleTests::testTranslatableConfig2() void LocaleTests::testRegions() { - CalamaresUtils::Locale::RegionsModel regions; + using namespace CalamaresUtils::Locale; + RegionsModel regions; QVERIFY( regions.rowCount( QModelIndex() ) > 3 ); // Africa, America, Asia QStringList names; for ( int i = 0; i < regions.rowCount( QModelIndex() ); ++i ) { - QVariant name = regions.data( regions.index( i ), Qt::DisplayRole ); + QVariant name = regions.data( regions.index( i ), RegionsModel::NameRole ); QVERIFY( name.isValid() ); QVERIFY( !name.toString().isEmpty() ); names.append( name.toString() ); @@ -269,9 +270,34 @@ LocaleTests::testRegions() void LocaleTests::testSimpleZones() { - CalamaresUtils::Locale::ZonesModel zones; + using namespace CalamaresUtils::Locale; + ZonesModel zones; QVERIFY( zones.rowCount( QModelIndex() ) > 24 ); + + QStringList names; + for ( int i = 0; i < zones.rowCount( QModelIndex() ); ++i ) + { + QVariant name = zones.data( zones.index( i ), ZonesModel::NameRole ); + QVERIFY( name.isValid() ); + QVERIFY( !name.toString().isEmpty() ); + names.append( name.toString() ); + } + + QVERIFY( names.contains( "Amsterdam" ) ); + if ( !names.contains( "New York" ) ) + { + for ( const auto& s : names ) + { + if ( s.startsWith( 'N' ) ) + { + cDebug() << s; + } + } + } + QVERIFY( names.contains( "New York" ) ); + QVERIFY( !names.contains( "America" ) ); + QVERIFY( !names.contains( "New_York" ) ); } void diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 9f356d958..05c082738 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -148,7 +148,7 @@ CStringPair::CStringPair( const char* s1 ) } CStringPair::CStringPair( const QString& s ) - : m_human( strdup( s.toUtf8().constData() ) ) + : m_human( munge( s.toUtf8().constData() ) ) , m_key( s ) { } @@ -331,13 +331,14 @@ RegionsModel::data( const QModelIndex& index, int role ) const return QVariant(); } - if ( role == Qt::DisplayRole ) + const auto& region = m_private->m_regions[ index.row() ]; + if ( role == NameRole ) { - return m_private->m_regions[ index.row() ].tr(); + return region.tr(); } if ( role == KeyRole ) { - return m_private->m_regions[ index.row() ].key(); + return region.key(); } return QVariant(); } @@ -345,7 +346,7 @@ RegionsModel::data( const QModelIndex& index, int role ) const QHash< int, QByteArray > RegionsModel::roleNames() const { - return { { Qt::DisplayRole, "name" }, { KeyRole, "key" } }; + return { { NameRole, "name" }, { KeyRole, "key" } }; } @@ -366,9 +367,29 @@ ZonesModel::rowCount( const QModelIndex& parent ) const QVariant ZonesModel::data( const QModelIndex& index, int role ) const { + if ( !index.isValid() || index.row() < 0 || index.row() >= m_private->m_zones.count() ) + { + return QVariant(); + } + + const auto& zone = m_private->m_zones[ index.row() ]; + if ( role == NameRole ) + { + return zone.tr(); + } + if ( role == KeyRole ) + { + return zone.key(); + } return QVariant(); } +QHash< int, QByteArray > +ZonesModel::roleNames() const +{ + return { { NameRole, "name" }, { KeyRole, "key" } }; +} + } // namespace Locale } // namespace CalamaresUtils diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 0a5fbf7f6..d87a5a57a 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -46,6 +46,7 @@ class DLLEXPORT RegionsModel : public QAbstractListModel public: enum Roles { + NameRole = Qt::DisplayRole, KeyRole = Qt::UserRole + 1 }; @@ -66,12 +67,20 @@ class DLLEXPORT ZonesModel : public QAbstractListModel Q_OBJECT public: + enum Roles + { + NameRole = Qt::DisplayRole, + KeyRole = Qt::UserRole + 1 + }; + ZonesModel( QObject* parent = nullptr ); virtual ~ZonesModel() override; int rowCount( const QModelIndex& parent ) const override; QVariant data( const QModelIndex& index, int role ) const override; + QHash< int, QByteArray > roleNames() const override; + private: Private* m_private; }; From 3e32335511a8989fe78abe986d02d6005b23b8b8 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 17:14:13 +0200 Subject: [PATCH 08/29] [libcalamares] Introduce a filtering model per-region --- src/libcalamares/locale/Tests.cpp | 55 ++++++++++++++++++++++++---- src/libcalamares/locale/TimeZone.cpp | 42 +++++++++++++++++++-- src/libcalamares/locale/TimeZone.h | 25 +++++++++++++ 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index c660ccbfb..8a6a6ad84 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -267,6 +267,19 @@ LocaleTests::testRegions() } +static void +displayedNames( QAbstractItemModel& model, QStringList& names ) +{ + names.clear(); + for ( int i = 0; i < model.rowCount( QModelIndex() ); ++i ) + { + QVariant name = model.data( model.index( i, 0 ), Qt::DisplayRole ); + QVERIFY( name.isValid() ); + QVERIFY( !name.toString().isEmpty() ); + names.append( name.toString() ); + } +} + void LocaleTests::testSimpleZones() { @@ -276,14 +289,7 @@ LocaleTests::testSimpleZones() QVERIFY( zones.rowCount( QModelIndex() ) > 24 ); QStringList names; - for ( int i = 0; i < zones.rowCount( QModelIndex() ); ++i ) - { - QVariant name = zones.data( zones.index( i ), ZonesModel::NameRole ); - QVERIFY( name.isValid() ); - QVERIFY( !name.toString().isEmpty() ); - names.append( name.toString() ); - } - + displayedNames( zones, names ); QVERIFY( names.contains( "Amsterdam" ) ); if ( !names.contains( "New York" ) ) { @@ -304,6 +310,39 @@ void LocaleTests::testComplexZones() { using namespace CalamaresUtils::Locale; + ZonesModel zones; + RegionalZonesModel europe( &zones ); + + QStringList names; + displayedNames( zones, names ); + QVERIFY( names.contains( "New York" ) ); + QVERIFY( names.contains( "Prague" ) ); + QVERIFY( names.contains( "Abidjan" ) ); + + // No region set + displayedNames( europe, names ); + QVERIFY( names.contains( "New York" ) ); + QVERIFY( names.contains( "Prague" ) ); + QVERIFY( names.contains( "Abidjan" ) ); + + // Now filter + europe.setRegion( "Europe" ); + displayedNames( europe, names ); + QVERIFY( !names.contains( "New York" ) ); + QVERIFY( names.contains( "Prague" ) ); + QVERIFY( !names.contains( "Abidjan" ) ); + + europe.setRegion( "America" ); + displayedNames( europe, names ); + QVERIFY( names.contains( "New York" ) ); + QVERIFY( !names.contains( "Prague" ) ); + QVERIFY( !names.contains( "Abidjan" ) ); + + europe.setRegion( "Africa" ); + displayedNames( europe, names ); + QVERIFY( !names.contains( "New York" ) ); + QVERIFY( !names.contains( "Prague" ) ); + QVERIFY( names.contains( "Abidjan" ) ); } QTEST_GUILESS_MAIN( LocaleTests ) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 05c082738..3d517940a 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -170,7 +170,6 @@ public: double longitude ); QString tr() const override; -private: QString m_region; QString m_country; double m_latitude; @@ -318,7 +317,7 @@ RegionsModel::RegionsModel( QObject* parent ) RegionsModel::~RegionsModel() {} int -RegionsModel::rowCount( const QModelIndex& parent ) const +RegionsModel::rowCount( const QModelIndex& ) const { return m_private->m_regions.count(); } @@ -359,7 +358,7 @@ ZonesModel::ZonesModel( QObject* parent ) ZonesModel::~ZonesModel() {} int -ZonesModel::rowCount( const QModelIndex& parent ) const +ZonesModel::rowCount( const QModelIndex& ) const { return m_private->m_zones.count(); } @@ -390,6 +389,43 @@ ZonesModel::roleNames() const return { { NameRole, "name" }, { KeyRole, "key" } }; } +RegionalZonesModel::RegionalZonesModel( CalamaresUtils::Locale::ZonesModel* source, QObject* parent ) + : QSortFilterProxyModel( parent ) + , m_private( privateInstance() ) +{ + setSourceModel( source ); +} + +RegionalZonesModel::~RegionalZonesModel() {} + +void +RegionalZonesModel::setRegion( const QString& r ) +{ + if ( r != m_region ) + { + m_region = r; + invalidateFilter(); + emit regionChanged( r ); + } +} + +bool +RegionalZonesModel::filterAcceptsRow( int sourceRow, const QModelIndex& ) const +{ + if ( m_region.isEmpty() ) + { + return true; + } + + if ( sourceRow < 0 || sourceRow >= m_private->m_zones.count() ) + { + return false; + } + + const auto& zone = m_private->m_zones[ sourceRow ]; + return ( zone.m_region == m_region ); +} + } // namespace Locale } // namespace CalamaresUtils diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index d87a5a57a..cb91a8361 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -26,6 +26,7 @@ #include #include +#include #include namespace CalamaresUtils @@ -85,6 +86,30 @@ private: Private* m_private; }; +class DLLEXPORT RegionalZonesModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY( QString region READ region WRITE setRegion NOTIFY regionChanged ) + +public: + RegionalZonesModel( ZonesModel* source, QObject* parent = nullptr ); + ~RegionalZonesModel() override; + + bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const override; + + QString region() const { return m_region; } + +public Q_SLOTS: + void setRegion( const QString& r ); + +signals: + void regionChanged( const QString& ); + +private: + Private* m_private; + QString m_region; +}; + } // namespace Locale } // namespace CalamaresUtils From 10fb5b95c7ca681c9d4f633255b70ffc31ed8657 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 17:52:41 +0200 Subject: [PATCH 09/29] [libcalamares] Split out CStringPair into TranslatableString The (renamed) class TranslatableString keeps a key value (e.g. New_York) and a human-readable version around; the human-readable one is passed through QObject::tr() for translation on-the-fly. --- src/libcalamares/CMakeLists.txt | 1 + src/libcalamares/locale/TimeZone.cpp | 114 ++---------------- .../locale/TranslatableString.cpp | 89 ++++++++++++++ src/libcalamares/locale/TranslatableString.h | 68 +++++++++++ 4 files changed, 168 insertions(+), 104 deletions(-) create mode 100644 src/libcalamares/locale/TranslatableString.cpp create mode 100644 src/libcalamares/locale/TranslatableString.h diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 8e209f8a3..d38aa1124 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -56,6 +56,7 @@ set( libSources locale/Lookup.cpp locale/TimeZone.cpp locale/TranslatableConfiguration.cpp + locale/TranslatableString.cpp # Modules modulesystem/InstanceKey.cpp diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 3d517940a..b19ad7e1e 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -22,6 +22,7 @@ #include "TimeZone.h" +#include "locale/TranslatableString.h" #include "utils/Logger.h" #include "utils/String.h" @@ -30,6 +31,11 @@ static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab"; +namespace CalamaresUtils +{ +namespace Locale +{ + /** @brief Turns a string longitude or latitude notation into a double * * This handles strings like "+4230+00131" from zone.tab, @@ -63,104 +69,8 @@ getRightGeoLocation( QString str ) return sign * num; } -/** @brief A pair of strings, one human-readable, one a key - * - * Given an identifier-like string (e.g. "New_York"), makes - * a human-readable version of that and keeps a copy of the - * identifier itself. - * - * This explicitly uses const char* instead of just being - * QPair because the human-readable part - * may need to be translated through tr(), and that takes a char*. - * C-style strings. - */ -class CStringPair : public QObject -{ - Q_OBJECT -public: - /// @brief An empty pair - CStringPair() {} - /// @brief Given an identifier, create the pair - explicit CStringPair( const char* s1 ); - explicit CStringPair( const QString& s ); - CStringPair( CStringPair&& t ); - CStringPair( const CStringPair& ); - virtual ~CStringPair(); - /// @brief Give the localized human-readable form - virtual QString tr() const = 0; - QString key() const { return m_key; } - - bool operator==( const CStringPair& other ) const { return m_key == other.m_key; } - bool operator<( const CStringPair& other ) const { return m_key < other.m_key; } - -protected: - char* m_human = nullptr; - QString m_key; -}; - -/** @brief Massage an identifier into a human-readable form - * - * Makes a copy of @p s, caller must free() it. - */ -static char* -munge( const char* s ) -{ - char* t = strdup( s ); - if ( !t ) - { - return nullptr; - } - - // replace("_"," ") in the Python script - char* p = t; - while ( *p ) - { - if ( ( *p ) == '_' ) - { - *p = ' '; - } - ++p; - } - - return t; -} - -CStringPair::CStringPair( CStringPair&& t ) - : m_human( nullptr ) - , m_key() -{ - // My pointers are initialized to nullptr - std::swap( m_human, t.m_human ); - std::swap( m_key, t.m_key ); -} - -CStringPair::CStringPair( const CStringPair& t ) - : m_human( t.m_human ? strdup( t.m_human ) : nullptr ) - , m_key( t.m_key ) -{ -} - -CStringPair::CStringPair( const char* s1 ) - : m_human( s1 ? munge( s1 ) : nullptr ) - , m_key( s1 ? QString( s1 ) : QString() ) -{ -} - -CStringPair::CStringPair( const QString& s ) - : m_human( munge( s.toUtf8().constData() ) ) - , m_key( s ) -{ -} - - -CStringPair::~CStringPair() -{ - free( m_human ); -} - - -class TimeZoneData : public CStringPair +class TimeZoneData : public TranslatableString { public: TimeZoneData( const QString& region, @@ -181,7 +91,7 @@ TimeZoneData::TimeZoneData( const QString& region, const QString& country, double latitude, double longitude ) - : CStringPair( zone ) + : TranslatableString( zone ) , m_region( region ) , m_country( country ) , m_latitude( latitude ) @@ -197,10 +107,10 @@ TimeZoneData::tr() const } -class RegionData : public CStringPair +class RegionData : public TranslatableString { public: - using CStringPair::CStringPair; + using TranslatableString::TranslatableString; QString tr() const override; }; @@ -282,10 +192,6 @@ loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData >& zones ) } } -namespace CalamaresUtils -{ -namespace Locale -{ struct Private { diff --git a/src/libcalamares/locale/TranslatableString.cpp b/src/libcalamares/locale/TranslatableString.cpp new file mode 100644 index 000000000..9200c8d65 --- /dev/null +++ b/src/libcalamares/locale/TranslatableString.cpp @@ -0,0 +1,89 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2019 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + * + */ +#include "TranslatableString.h" + + +/** @brief Massage an identifier into a human-readable form + * + * Makes a copy of @p s, caller must free() it. + */ +static char* +munge( const char* s ) +{ + char* t = strdup( s ); + if ( !t ) + { + return nullptr; + } + + // replace("_"," ") in the Python script + char* p = t; + while ( *p ) + { + if ( ( *p ) == '_' ) + { + *p = ' '; + } + ++p; + } + + return t; +} + +namespace CalamaresUtils +{ +namespace Locale +{ + +TranslatableString::TranslatableString( TranslatableString&& t ) + : m_human( nullptr ) + , m_key() +{ + // My pointers are initialized to nullptr + std::swap( m_human, t.m_human ); + std::swap( m_key, t.m_key ); +} + +TranslatableString::TranslatableString( const TranslatableString& t ) + : m_human( t.m_human ? strdup( t.m_human ) : nullptr ) + , m_key( t.m_key ) +{ +} + +TranslatableString::TranslatableString( const char* s1 ) + : m_human( s1 ? munge( s1 ) : nullptr ) + , m_key( s1 ? QString( s1 ) : QString() ) +{ +} + +TranslatableString::TranslatableString( const QString& s ) + : m_human( munge( s.toUtf8().constData() ) ) + , m_key( s ) +{ +} + + +TranslatableString::~TranslatableString() +{ + free( m_human ); +} + +} // namespace Locale +} // namespace CalamaresUtils diff --git a/src/libcalamares/locale/TranslatableString.h b/src/libcalamares/locale/TranslatableString.h new file mode 100644 index 000000000..8347488f1 --- /dev/null +++ b/src/libcalamares/locale/TranslatableString.h @@ -0,0 +1,68 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2019 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + * + */ +#ifndef LOCALE_TRANSLATABLESTRING_H +#define LOCALE_TRANSLATABLESTRING_H + +#include + +namespace CalamaresUtils +{ +namespace Locale +{ + +/** @brief A pair of strings, one human-readable, one a key + * + * Given an identifier-like string (e.g. "New_York"), makes + * a human-readable version of that and keeps a copy of the + * identifier itself. + * + * This explicitly uses const char* instead of just being + * QPair because the human-readable part + * may need to be translated through tr(), and that takes a char* + * C-style strings. + */ +class TranslatableString +{ +public: + /// @brief An empty pair + TranslatableString() {} + /// @brief Given an identifier, create the pair + explicit TranslatableString( const char* s1 ); + explicit TranslatableString( const QString& s ); + TranslatableString( TranslatableString&& t ); + TranslatableString( const TranslatableString& ); + virtual ~TranslatableString(); + + /// @brief Give the localized human-readable form + virtual QString tr() const = 0; + QString key() const { return m_key; } + + bool operator==( const TranslatableString& other ) const { return m_key == other.m_key; } + bool operator<( const TranslatableString& other ) const { return m_key < other.m_key; } + +protected: + char* m_human = nullptr; + QString m_key; +}; + +} // namespace Locale +} // namespace CalamaresUtils + +#endif From 478a27576435f9f67499bd85cf5a15519973a2d8 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 18:01:24 +0200 Subject: [PATCH 10/29] [libcalamares] Make TimeZoneData public - Also make it a QObject so we can add properties and make it useful for QML consumption. --- src/libcalamares/locale/TimeZone.cpp | 34 ++++++---------------------- src/libcalamares/locale/TimeZone.h | 31 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index b19ad7e1e..73ac328d6 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -70,22 +70,6 @@ getRightGeoLocation( QString str ) } -class TimeZoneData : public TranslatableString -{ -public: - TimeZoneData( const QString& region, - const QString& zone, - const QString& country, - double latitude, - double longitude ); - QString tr() const override; - - QString m_region; - QString m_country; - double m_latitude; - double m_longitude; -}; - TimeZoneData::TimeZoneData( const QString& region, const QString& zone, const QString& country, @@ -122,7 +106,7 @@ RegionData::tr() const } static void -loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData >& zones ) +loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData* >& zones ) { QFile file( TZ_DATA_FILE ); if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) ) @@ -187,7 +171,7 @@ loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData >& zones ) { regions.append( std::move( r ) ); } - zones.append( TimeZoneData( region, zone, countryCode, latitude, longitude ) ); + zones.append( new TimeZoneData( region, zone, countryCode, latitude, longitude ) ); } } } @@ -196,7 +180,7 @@ loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData >& zones ) struct Private { QVector< RegionData > m_regions; - QVector< TimeZoneData > m_zones; + QVector< TimeZoneData* > m_zones; Private() { @@ -277,14 +261,14 @@ ZonesModel::data( const QModelIndex& index, int role ) const return QVariant(); } - const auto& zone = m_private->m_zones[ index.row() ]; + const auto* zone = m_private->m_zones[ index.row() ]; if ( role == NameRole ) { - return zone.tr(); + return zone->tr(); } if ( role == KeyRole ) { - return zone.key(); + return zone->key(); } return QVariant(); } @@ -329,13 +313,9 @@ RegionalZonesModel::filterAcceptsRow( int sourceRow, const QModelIndex& ) const } const auto& zone = m_private->m_zones[ sourceRow ]; - return ( zone.m_region == m_region ); + return ( zone->m_region == m_region ); } } // namespace Locale } // namespace CalamaresUtils - -#include "utils/moc-warnings.h" - -#include "TimeZone.moc" diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index cb91a8361..c1bcc96b9 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -24,6 +24,8 @@ #include "DllMacro.h" +#include "locale/TranslatableString.h" + #include #include #include @@ -34,6 +36,35 @@ namespace CalamaresUtils namespace Locale { struct Private; +class RegionalZonesModel; +class ZonesModel; + +class TimeZoneData : public QObject, TranslatableString +{ + friend class RegionalZonesModel; + friend class ZonesModel; + + Q_OBJECT + + Q_PROPERTY( QString region READ region CONSTANT ) + +public: + TimeZoneData( const QString& region, + const QString& zone, + const QString& country, + double latitude, + double longitude ); + QString tr() const override; + + QString region() const { return m_region; } + +private: + QString m_region; + QString m_country; + double m_latitude; + double m_longitude; +}; + /** @brief The list of timezone regions * From 245d4a8ef77207a102be41dc8cbe0e9d1ce4b957 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 22:30:04 +0200 Subject: [PATCH 11/29] [libcalamares] Add a find() to ZonesModel - Look up TZ data by region and zone name. --- src/libcalamares/locale/Tests.cpp | 16 ++++++++++++++++ src/libcalamares/locale/TimeZone.cpp | 13 +++++++++++++ src/libcalamares/locale/TimeZone.h | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 8a6a6ad84..db2ed2fe2 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -50,6 +50,7 @@ private Q_SLOTS: void testRegions(); void testSimpleZones(); void testComplexZones(); + void testTZLookup(); }; LocaleTests::LocaleTests() {} @@ -345,6 +346,21 @@ LocaleTests::testComplexZones() QVERIFY( names.contains( "Abidjan" ) ); } +void +LocaleTests::testTZLookup() +{ + using namespace CalamaresUtils::Locale; + ZonesModel zones; + + QVERIFY( zones.find( "America", "New_York" ) ); + QCOMPARE( zones.find( "America", "New_York" )->zone(), QStringLiteral( "New_York" ) ); + QCOMPARE( zones.find( "America", "New_York" )->tr(), QStringLiteral( "New York" ) ); + + QVERIFY( !zones.find( "Europe", "New_York" ) ); + QVERIFY( !zones.find( "America", "New York" ) ); +} + + QTEST_GUILESS_MAIN( LocaleTests ) #include "utils/moc-warnings.h" diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 73ac328d6..4e56b5af1 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -279,6 +279,19 @@ ZonesModel::roleNames() const return { { NameRole, "name" }, { KeyRole, "key" } }; } +const TimeZoneData* +ZonesModel::find( const QString& region, const QString& zone ) +{ + for ( const auto* p : m_private->m_zones ) + { + if ( p->region() == region && p->zone() == zone ) + { + return p; + } + } + return nullptr; +} + RegionalZonesModel::RegionalZonesModel( CalamaresUtils::Locale::ZonesModel* source, QObject* parent ) : QSortFilterProxyModel( parent ) , m_private( privateInstance() ) diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index c1bcc96b9..de23f07d2 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -57,6 +57,7 @@ public: QString tr() const override; QString region() const { return m_region; } + QString zone() const { return key(); } private: QString m_region; @@ -113,6 +114,12 @@ public: QHash< int, QByteArray > roleNames() const override; + /** @brief Look up TZ data based on its name. + * + * Returns @c nullptr if not found. + */ + const TimeZoneData* find( const QString& region, const QString& zone ); + private: Private* m_private; }; From 7ea2ad7dc6443611f34d291d449c8ae742e48255 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 23:26:17 +0200 Subject: [PATCH 12/29] [libcalamares] Add accessors for TZ data and region in the model It's convenient when e.g. QComboBox::currentData() gets the key "automatically", and the default role for that method is UserRole, so let the value of KeyRole overlap. --- src/libcalamares/locale/TimeZone.cpp | 12 +++++++----- src/libcalamares/locale/TimeZone.h | 12 ++++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 4e56b5af1..d5d4938a8 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -262,15 +262,17 @@ ZonesModel::data( const QModelIndex& index, int role ) const } const auto* zone = m_private->m_zones[ index.row() ]; - if ( role == NameRole ) + switch ( role ) { + case NameRole: return zone->tr(); - } - if ( role == KeyRole ) - { + case KeyRole: return zone->key(); + case RegionRole: + return zone->region(); + default: + return QVariant(); } - return QVariant(); } QHash< int, QByteArray > diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index de23f07d2..afe32963c 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -54,11 +54,18 @@ public: const QString& country, double latitude, double longitude ); + TimeZoneData( const TimeZoneData& ) = delete; + TimeZoneData( TimeZoneData&& ) = delete; + QString tr() const override; QString region() const { return m_region; } QString zone() const { return key(); } + QString country() const { return m_country; } + double latitude() const { return m_latitude; } + double longitude() const { return m_longitude; } + private: QString m_region; QString m_country; @@ -80,7 +87,7 @@ public: enum Roles { NameRole = Qt::DisplayRole, - KeyRole = Qt::UserRole + 1 + KeyRole = Qt::UserRole // So that currentData() will get the key }; RegionsModel( QObject* parent = nullptr ); @@ -103,7 +110,8 @@ public: enum Roles { NameRole = Qt::DisplayRole, - KeyRole = Qt::UserRole + 1 + KeyRole = Qt::UserRole, // So that currentData() will get the key + RegionRole = Qt::UserRole + 1 }; ZonesModel( QObject* parent = nullptr ); From 37c211fd14d8a6a35d02296e774be7da2e9c42be Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 5 Aug 2020 23:46:14 +0200 Subject: [PATCH 13/29] [libcalamares] Add an iterator for the full zones model --- src/libcalamares/locale/Tests.cpp | 26 +++++++++++++++++++ src/libcalamares/locale/TimeZone.cpp | 16 +++++++++++- src/libcalamares/locale/TimeZone.h | 39 +++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index db2ed2fe2..bb5365ee0 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -51,6 +51,7 @@ private Q_SLOTS: void testSimpleZones(); void testComplexZones(); void testTZLookup(); + void testTZIterator(); }; LocaleTests::LocaleTests() {} @@ -360,6 +361,31 @@ LocaleTests::testTZLookup() QVERIFY( !zones.find( "America", "New York" ) ); } +void +LocaleTests::testTZIterator() +{ + using namespace CalamaresUtils::Locale; + const ZonesModel zones; + + QVERIFY( zones.find( "Europe", "Rome" ) ); + + int count = 0; + bool seenRome = false; + bool seenGnome = false; + for ( auto it = zones.begin(); it; ++it ) + { + QVERIFY( *it ); + QVERIFY( !( *it )->zone().isEmpty() ); + seenRome |= ( *it )->zone() == QStringLiteral( "Rome" ); + seenGnome |= ( *it )->zone() == QStringLiteral( "Gnome" ); + count++; + } + + QVERIFY( seenRome ); + QVERIFY( !seenGnome ); + QCOMPARE( count, zones.rowCount( QModelIndex() ) ); +} + QTEST_GUILESS_MAIN( LocaleTests ) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index d5d4938a8..e8b861d69 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -282,7 +282,7 @@ ZonesModel::roleNames() const } const TimeZoneData* -ZonesModel::find( const QString& region, const QString& zone ) +ZonesModel::find( const QString& region, const QString& zone ) const { for ( const auto* p : m_private->m_zones ) { @@ -294,6 +294,20 @@ ZonesModel::find( const QString& region, const QString& zone ) return nullptr; } +ZonesModel::Iterator::operator bool() const +{ + return 0 <= m_index && m_index < m_p->m_zones.count(); +} + +const TimeZoneData* ZonesModel::Iterator::operator*() const +{ + if ( *this ) + { + return m_p->m_zones[ m_index ]; + } + return nullptr; +} + RegionalZonesModel::RegionalZonesModel( CalamaresUtils::Locale::ZonesModel* source, QObject* parent ) : QSortFilterProxyModel( parent ) , m_private( privateInstance() ) diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index afe32963c..9b8569b21 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -126,7 +126,44 @@ public: * * Returns @c nullptr if not found. */ - const TimeZoneData* find( const QString& region, const QString& zone ); + const TimeZoneData* find( const QString& region, const QString& zone ) const; + + /** @brief Iterator for testing purposes + * + * This is primarily for testing, but who knows, it might be useful + * elsewhere, and it's convenient when it can access Private. + * + * Iterates over all the zones in the model. Operator * may return + * a @c nullptr when the iterator is not valid. Typical usage: + * + * ``` + * for( auto it = model.begin(); it; ++it ) + * { + * const auto* zonedata = *it; + * ... + * } + */ + class Iterator + { + friend class ZonesModel; + Iterator( const Private* m ) + : m_index( 0 ) + , m_p( m ) + { + } + + public: + operator bool() const; + void operator++() { ++m_index; } + const TimeZoneData* operator*() const; + int index() const { return m_index; } + + private: + int m_index; + const Private* m_p; + }; + + Iterator begin() const { return Iterator( m_private ); } private: Private* m_private; From d814a3dba8479c2395d422694415611492d0cea9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 00:56:13 +0200 Subject: [PATCH 14/29] [libcalamares] Sort the models before use - zones and regions alphabetically by key --- src/libcalamares/locale/Tests.cpp | 3 +++ src/libcalamares/locale/TimeZone.cpp | 38 ++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index bb5365ee0..dde199a7f 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -384,6 +384,9 @@ LocaleTests::testTZIterator() QVERIFY( seenRome ); QVERIFY( !seenGnome ); QCOMPARE( count, zones.rowCount( QModelIndex() ) ); + + QCOMPARE( zones.data( zones.index( 0 ), ZonesModel::RegionRole ).toString(), QStringLiteral( "Africa" ) ); + QCOMPARE( ( *zones.begin() )->zone(), QStringLiteral( "Abidjan" ) ); } diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index e8b861d69..974b73bfb 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -105,8 +105,11 @@ RegionData::tr() const return QObject::tr( m_human, "tz_regions" ); } +using RegionVector = QVector< RegionData* >; +using ZoneVector = QVector< TimeZoneData* >; + static void -loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData* >& zones ) +loadTZData( RegionVector& regions, ZoneVector& zones ) { QFile file( TZ_DATA_FILE ); if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) ) @@ -166,10 +169,18 @@ loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData* >& zones ) } // Now we have region, zone, country, lat and longitude - RegionData r( region ); - if ( regions.indexOf( r ) < 0 ) + const RegionData* existingRegion = nullptr; + for ( const auto* p : regions ) { - regions.append( std::move( r ) ); + if ( p->key() == region ) + { + existingRegion = p; + break; + } + } + if ( !existingRegion ) + { + regions.append( new RegionData( region ) ); } zones.append( new TimeZoneData( region, zone, countryCode, latitude, longitude ) ); } @@ -179,8 +190,8 @@ loadTZData( QVector< RegionData >& regions, QVector< TimeZoneData* >& zones ) struct Private { - QVector< RegionData > m_regions; - QVector< TimeZoneData* > m_zones; + RegionVector m_regions; + ZoneVector m_zones; Private() { @@ -188,6 +199,17 @@ struct Private m_zones.reserve( 452 ); // wc -l /usr/share/zoneinfo/zone.tab loadTZData( m_regions, m_zones ); + + std::sort( m_regions.begin(), m_regions.end(), []( const RegionData* lhs, const RegionData* rhs ) { + return lhs->key() < rhs->key(); + } ); + std::sort( m_zones.begin(), m_zones.end(), []( const TimeZoneData* lhs, const TimeZoneData* rhs ) { + if ( lhs->region() == rhs->region() ) + { + return lhs->zone() < rhs->zone(); + } + return lhs->region() < rhs->region(); + } ); } }; @@ -223,11 +245,11 @@ RegionsModel::data( const QModelIndex& index, int role ) const const auto& region = m_private->m_regions[ index.row() ]; if ( role == NameRole ) { - return region.tr(); + return region->tr(); } if ( role == KeyRole ) { - return region.key(); + return region->key(); } return QVariant(); } From 626dd038da7bb305d513a2957c7178356f04fb50 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 01:27:03 +0200 Subject: [PATCH 15/29] [locale] Re-do locale module with new TZ data - The Config object now uses the re-done models and timezone data - most of the properties of the locale Config are unchanged - much less complication in extracting data from the zones model --- src/modules/locale/Config.cpp | 28 ++-- src/modules/locale/Config.h | 52 ++++--- src/modules/locale/LocalePage.cpp | 28 ++-- src/modules/locale/LocalePage.h | 2 +- src/modules/locale/Tests.cpp | 130 +++++++----------- .../locale/timezonewidget/timezonewidget.cpp | 34 ++--- .../locale/timezonewidget/timezonewidget.h | 12 +- 7 files changed, 108 insertions(+), 178 deletions(-) diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index 7a49525f2..96d279477 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -148,17 +148,11 @@ loadLocales( const QString& localeGenPath ) 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 >() ) + , m_regionModel( std::make_unique< CalamaresUtils::Locale::RegionsModel >() ) + , m_zonesModel( std::make_unique< CalamaresUtils::Locale::ZonesModel >() ) + , m_regionalZonesModel( std::make_unique< CalamaresUtils::Locale::RegionalZonesModel >( m_zonesModel.get() ) ) { // Slightly unusual: connect to our *own* signals. Wherever the language // or the location is changed, these signals are emitted, so hook up to @@ -208,12 +202,6 @@ Config::Config( QObject* parent ) Config::~Config() {} -const CalamaresUtils::Locale::CStringPairList& -Config::timezoneData() const -{ - return ::timezoneData(); -} - void Config::setCurrentLocation() { @@ -223,7 +211,8 @@ Config::setCurrentLocation() } } -void Config::setCurrentLocation(const QString& regionzone) +void +Config::setCurrentLocation( const QString& regionzone ) { auto r = CalamaresUtils::GeoIP::splitTZString( regionzone ); if ( r.isValid() ) @@ -236,8 +225,7 @@ 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; + auto* zone = m_zonesModel->find( regionName, zoneName ); if ( zone ) { setCurrentLocation( zone ); @@ -250,7 +238,7 @@ Config::setCurrentLocation( const QString& regionName, const QString& zoneName ) } void -Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ) +Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location ) { if ( location != m_currentLocation ) { @@ -459,7 +447,7 @@ Calamares::JobList Config::createJobs() { Calamares::JobList list; - const CalamaresUtils::Locale::TZZone* location = currentLocation(); + const auto* location = currentLocation(); if ( location ) { diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index e9a8e6373..fccd3822e 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -37,18 +37,20 @@ 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( CalamaresUtils::Locale::RegionsModel* regionModel READ regionModel CONSTANT FINAL ) + Q_PROPERTY( CalamaresUtils::Locale::ZonesModel* zonesModel READ zonesModel CONSTANT FINAL ) + Q_PROPERTY( QAbstractItemModel* regionalZonesModel READ regionalZonesModel CONSTANT FINAL ) - Q_PROPERTY( const CalamaresUtils::Locale::TZZone* currentLocation READ currentLocation WRITE setCurrentLocation - NOTIFY currentLocationChanged ) + Q_PROPERTY( + const CalamaresUtils::Locale::TimeZoneData* currentLocation READ currentLocation 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 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 @@ -61,15 +63,6 @@ public: void setConfigurationMap( const QVariantMap& ); Calamares::JobList createJobs(); - // 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 @@ -85,9 +78,16 @@ public: /// The human-readable summary of what the module will do QString prettyStatus() const; + // A long list of locale codes (e.g. en_US.UTF-8) 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(); } + // All the regions (Africa, America, ...) + CalamaresUtils::Locale::RegionsModel* regionModel() const { return m_regionModel.get(); } + // All of the timezones in the world, according to zone.tab + CalamaresUtils::Locale::ZonesModel* zonesModel() const { return m_zonesModel.get(); } + // This model can be filtered by region + CalamaresUtils::Locale::RegionalZonesModel* regionalZonesModel() const { return m_regionalZonesModel.get(); } + + const CalamaresUtils::Locale::TimeZoneData* currentLocation() const { return m_currentLocation; } /// Special case, set location from starting timezone if not already set void setCurrentLocation(); @@ -111,20 +111,17 @@ public Q_SLOTS: * names a zone within that region. */ void setCurrentLocation( const QString& region, const QString& zone ); - /** @brief Sets a location by pointer + + /** @brief Sets a location by pointer to zone data. * - * 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 ); + void setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* tz ); QString currentLanguageCode() const { return localeConfiguration().language(); } QString currentLCCode() const { return localeConfiguration().lc_numeric; } signals: - void currentLocationChanged( const CalamaresUtils::Locale::TZZone* location ) const; + void currentLocationChanged( const CalamaresUtils::Locale::TimeZoneData* location ) const; void currentLocationStatusChanged( const QString& ) const; void currentLanguageStatusChanged( const QString& ) const; void currentLCStatusChanged( const QString& ) const; @@ -137,12 +134,11 @@ private: QStringList m_localeGenLines; /// 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; + std::unique_ptr< CalamaresUtils::Locale::RegionsModel > m_regionModel; + std::unique_ptr< CalamaresUtils::Locale::ZonesModel > m_zonesModel; + std::unique_ptr< CalamaresUtils::Locale::RegionalZonesModel > m_regionalZonesModel; - /// The location, points into the timezone data - const CalamaresUtils::Locale::TZZone* m_currentLocation = nullptr; + const CalamaresUtils::Locale::TimeZoneData* m_currentLocation = nullptr; /** @brief Specific locale configurations * diff --git a/src/modules/locale/LocalePage.cpp b/src/modules/locale/LocalePage.cpp index c10b2dee9..d4ad6854e 100644 --- a/src/modules/locale/LocalePage.cpp +++ b/src/modules/locale/LocalePage.cpp @@ -43,7 +43,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent ) QBoxLayout* mainLayout = new QVBoxLayout; QBoxLayout* tzwLayout = new QHBoxLayout; - m_tzWidget = new TimeZoneWidget( config->timezoneData(), this ); + m_tzWidget = new TimeZoneWidget( m_config->zonesModel(), this ); tzwLayout->addStretch(); tzwLayout->addWidget( m_tzWidget ); tzwLayout->addStretch(); @@ -102,6 +102,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent ) // Set up the location before connecting signals, to avoid a signal // storm as various parts interact. m_regionCombo->setModel( m_config->regionModel() ); + m_zoneCombo->setModel( m_config->regionalZonesModel() ); locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget m_tzWidget->setCurrentLocation( m_config->currentLocation() ); @@ -112,7 +113,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent ) connect( m_tzWidget, &TimeZoneWidget::locationChanged, config, - QOverload< const CalamaresUtils::Locale::TZZone* >::of( &Config::setCurrentLocation ) ); + QOverload< const CalamaresUtils::Locale::TimeZoneData* >::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 ); @@ -152,35 +153,26 @@ LocalePage::regionChanged( int currentIndex ) { using namespace CalamaresUtils::Locale; - Q_UNUSED( currentIndex ) - QString selectedRegion = m_regionCombo->currentData().toString(); - - TZRegion* region = m_config->timezoneData().find< TZRegion >( selectedRegion ); - if ( !region ) + QString selectedRegion = m_regionCombo->itemData( currentIndex ).toString(); { - return; + cSignalBlocker z( m_zoneCombo ); + m_config->regionalZonesModel()->setRegion( selectedRegion ); } - - { - cSignalBlocker b( m_zoneCombo ); - m_zoneCombo->setModel( new CStringListModel( region->zones() ) ); - } - - m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() ); + m_zoneCombo->currentIndexChanged( 0 ); } void LocalePage::zoneChanged( int currentIndex ) { - Q_UNUSED( currentIndex ) if ( !m_blockTzWidgetSet ) { - m_config->setCurrentLocation( m_regionCombo->currentData().toString(), m_zoneCombo->currentData().toString() ); + m_config->setCurrentLocation( m_regionCombo->currentData().toString(), + m_zoneCombo->itemData( currentIndex ).toString() ); } } void -LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location ) +LocalePage::locationChanged( const CalamaresUtils::Locale::TimeZoneData* location ) { if ( !location ) { diff --git a/src/modules/locale/LocalePage.h b/src/modules/locale/LocalePage.h index bf41f8f69..4f2d321b5 100644 --- a/src/modules/locale/LocalePage.h +++ b/src/modules/locale/LocalePage.h @@ -53,7 +53,7 @@ private: void regionChanged( int currentIndex ); void zoneChanged( int currentIndex ); - void locationChanged( const CalamaresUtils::Locale::TZZone* location ); + void locationChanged( const CalamaresUtils::Locale::TimeZoneData* location ); void changeLocale(); void changeFormats(); diff --git a/src/modules/locale/Tests.cpp b/src/modules/locale/Tests.cpp index af37a664b..e7fbb10f2 100644 --- a/src/modules/locale/Tests.cpp +++ b/src/modules/locale/Tests.cpp @@ -22,6 +22,7 @@ #include "timezonewidget/TimeZoneImage.h" #include "locale/TimeZone.h" +#include "utils/Logger.h" #include @@ -115,37 +116,35 @@ LocaleTests::testTZImages() // // using namespace CalamaresUtils::Locale; - const CStringPairList& regions = TZRegion::fromZoneTab(); + const ZonesModel m; int overlapcount = 0; - for ( const auto* pr : regions ) + for ( auto it = m.begin(); it; ++it ) { - const TZRegion* region = dynamic_cast< const TZRegion* >( pr ); - QVERIFY( region ); + QString region = m.data( m.index( it.index() ), ZonesModel::RegionRole ).toString(); + QString zoneName = m.data( m.index( it.index() ), ZonesModel::KeyRole ).toString(); + QVERIFY( !region.isEmpty() ); + QVERIFY( !zoneName.isEmpty() ); + const auto* zone = m.find( region, zoneName ); + const auto* iterzone = *it; - Logger::setupLogLevel( Logger::LOGDEBUG ); - cDebug() << "Region" << region->region() << "zones #" << region->zones().count(); - Logger::setupLogLevel( Logger::LOGERROR ); + QVERIFY( iterzone ); + QVERIFY( zone ); + QCOMPARE( zone, iterzone ); + QCOMPARE( zone->zone(), zoneName ); + QCOMPARE( zone->region(), region ); - const auto zones = region->zones(); - QVERIFY( zones.count() > 0 ); - for ( const auto* pz : zones ) + int overlap = 0; + auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() ); + QVERIFY( images.index( pos, overlap ) >= 0 ); + QVERIFY( overlap > 0 ); // At least one image contains the spot + if ( overlap > 1 ) { - const TZZone* zone = dynamic_cast< const TZZone* >( pz ); - QVERIFY( zone ); - - int overlap = 0; - auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() ); - QVERIFY( images.index( pos, overlap ) >= 0 ); - QVERIFY( overlap > 0 ); // At least one image contains the spot - if ( overlap > 1 ) - { - Logger::setupLogLevel( Logger::LOGDEBUG ); - cDebug() << Logger::SubEntry << "Zone" << zone->zone() << pos; - (void)images.index( pos, overlap ); - Logger::setupLogLevel( Logger::LOGERROR ); - overlapcount++; - } + Logger::setupLogLevel( Logger::LOGDEBUG ); + cDebug() << Logger::SubEntry << "Zone" << zone->zone() << pos; + (void)images.index( pos, overlap ); + Logger::setupLogLevel( Logger::LOGERROR ); + overlapcount++; } } @@ -168,12 +167,17 @@ operator<( const QPoint& l, const QPoint& r ) } void -listAll( const QPoint& p, const CalamaresUtils::Locale::CStringPairList& zones ) +listAll( const QPoint& p, const CalamaresUtils::Locale::ZonesModel& zones ) { using namespace CalamaresUtils::Locale; - for ( const auto* pz : zones ) + for ( auto it = zones.begin(); it; ++it ) { - const TZZone* zone = dynamic_cast< const TZZone* >( pz ); + const auto* zone = *it; + if ( !zone ) + { + cError() << Logger::SubEntry << "NULL zone"; + return; + } if ( p == TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ) ) { cError() << Logger::SubEntry << zone->zone(); @@ -185,78 +189,36 @@ void LocaleTests::testTZLocations() { using namespace CalamaresUtils::Locale; - const CStringPairList& regions = TZRegion::fromZoneTab(); + ZonesModel zones; int overlapcount = 0; - for ( const auto* pr : regions ) + for ( auto it = zones.begin(); it; ++it ) { - const TZRegion* region = dynamic_cast< const TZRegion* >( pr ); - QVERIFY( region ); - - Logger::setupLogLevel( Logger::LOGDEBUG ); - cDebug() << "Region" << region->region() << "zones #" << region->zones().count(); - Logger::setupLogLevel( Logger::LOGERROR ); - std::set< QPoint > occupied; - const auto zones = region->zones(); - QVERIFY( zones.count() > 0 ); - for ( const auto* pz : zones ) - { - const TZZone* zone = dynamic_cast< const TZZone* >( pz ); - QVERIFY( zone ); + const auto* zone = *it; + QVERIFY( zone ); - auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ); - if ( occupied.find( pos ) != occupied.end() ) - { - cError() << "Zone" << zone->zone() << "occupies same spot as .."; - listAll( pos, zones ); - overlapcount++; - } - occupied.insert( pos ); + auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ); + if ( occupied.find( pos ) != occupied.end() ) + { + cError() << "Zone" << zone->zone() << "occupies same spot as .."; + listAll( pos, zones ); + overlapcount++; } + occupied.insert( pos ); } QEXPECT_FAIL( "", "TZ Images contain pixel-overlaps", Continue ); QCOMPARE( overlapcount, 0 ); } -const CalamaresUtils::Locale::TZZone* -findZone( const QString& name ) -{ - using namespace CalamaresUtils::Locale; - const CStringPairList& regions = TZRegion::fromZoneTab(); - - for ( const auto* pr : regions ) - { - const TZRegion* region = dynamic_cast< const TZRegion* >( pr ); - if ( !region ) - { - continue; - } - const auto zones = region->zones(); - for ( const auto* pz : zones ) - { - const TZZone* zone = dynamic_cast< const TZZone* >( pz ); - if ( !zone ) - { - continue; - } - - if ( zone->zone() == name ) - { - return zone; - } - } - } - return nullptr; -} - void LocaleTests::testSpecificLocations() { - const auto* gibraltar = findZone( "Gibraltar" ); - const auto* ceuta = findZone( "Ceuta" ); + CalamaresUtils::Locale::ZonesModel zones; + const auto* gibraltar = zones.find( "Europe", "Gibraltar" ); + const auto* ceuta = zones.find( "Africa", "Ceuta" ); QVERIFY( gibraltar ); QVERIFY( ceuta ); diff --git a/src/modules/locale/timezonewidget/timezonewidget.cpp b/src/modules/locale/timezonewidget/timezonewidget.cpp index 0972e3296..b1d3cfeaa 100644 --- a/src/modules/locale/timezonewidget/timezonewidget.cpp +++ b/src/modules/locale/timezonewidget/timezonewidget.cpp @@ -35,13 +35,13 @@ #endif static QPoint -getLocationPosition( const CalamaresUtils::Locale::TZZone* l ) +getLocationPosition( const CalamaresUtils::Locale::TimeZoneData* l ) { return TimeZoneImageList::getLocationPosition( l->longitude(), l->latitude() ); } -TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent ) +TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::ZonesModel* zones, QWidget* parent ) : QWidget( parent ) , timeZoneImages( TimeZoneImageList::fromQRC() ) , m_zonesData( zones ) @@ -65,7 +65,7 @@ TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& z void -TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location ) +TimeZoneWidget::setCurrentLocation( const TimeZoneData* location ) { if ( location == m_currentLocation ) { @@ -190,32 +190,24 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event ) { return; } - // Set nearest location int nX = 999999, mX = event->pos().x(); int nY = 999999, mY = event->pos().y(); using namespace CalamaresUtils::Locale; - const TZZone* closest = nullptr; - for ( const auto* region_p : m_zonesData ) + const TimeZoneData* closest = nullptr; + for ( auto it = m_zonesData->begin(); it; ++it ) { - const auto* region = dynamic_cast< const TZRegion* >( region_p ); - if ( region ) + const auto* zone = *it; + if ( zone ) { - for ( const auto* zone_p : region->zones() ) - { - const auto* zone = dynamic_cast< const TZZone* >( zone_p ); - if ( zone ) - { - QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ); + QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ); - if ( ( abs( mX - locPos.x() ) + abs( mY - locPos.y() ) < abs( mX - nX ) + abs( mY - nY ) ) ) - { - closest = zone; - nX = locPos.x(); - nY = locPos.y(); - } - } + if ( ( abs( mX - locPos.x() ) + abs( mY - locPos.y() ) < abs( mX - nX ) + abs( mY - nY ) ) ) + { + closest = zone; + nX = locPos.x(); + nY = locPos.y(); } } } diff --git a/src/modules/locale/timezonewidget/timezonewidget.h b/src/modules/locale/timezonewidget/timezonewidget.h index 6bb94c0dd..c15570b52 100644 --- a/src/modules/locale/timezonewidget/timezonewidget.h +++ b/src/modules/locale/timezonewidget/timezonewidget.h @@ -51,28 +51,28 @@ class TimeZoneWidget : public QWidget { Q_OBJECT public: - using TZZone = CalamaresUtils::Locale::TZZone; + using TimeZoneData = CalamaresUtils::Locale::TimeZoneData; - explicit TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent = nullptr ); + explicit TimeZoneWidget( const CalamaresUtils::Locale::ZonesModel* zones, QWidget* parent = nullptr ); 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 ); + void setCurrentLocation( const TimeZoneData* location ); signals: /** @brief The location has changed by mouse click */ - void locationChanged( const TZZone* location ); + void locationChanged( const TimeZoneData* location ); private: QFont font; QImage background, pin, currentZoneImage; TimeZoneImageList timeZoneImages; - const CalamaresUtils::Locale::CStringPairList& m_zonesData; - const TZZone* m_currentLocation = nullptr; // Not owned by me + const CalamaresUtils::Locale::ZonesModel* m_zonesData; + const TimeZoneData* m_currentLocation = nullptr; // Not owned by me void paintEvent( QPaintEvent* event ); void mousePressEvent( QMouseEvent* event ); From ab69e7c83a35e729a356139ca34210847765c26d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 01:52:50 +0200 Subject: [PATCH 16/29] [libcalamares] Add API for geographical lookup - find a zone given lat, lon -- with a failing test and a bogus implementation. --- src/libcalamares/locale/Tests.cpp | 10 ++++++++++ src/libcalamares/locale/TimeZone.cpp | 6 ++++++ src/libcalamares/locale/TimeZone.h | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index dde199a7f..081d0ced5 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -52,6 +52,7 @@ private Q_SLOTS: void testComplexZones(); void testTZLookup(); void testTZIterator(); + void testLocationLookup(); }; LocaleTests::LocaleTests() {} @@ -389,6 +390,15 @@ LocaleTests::testTZIterator() QCOMPARE( ( *zones.begin() )->zone(), QStringLiteral( "Abidjan" ) ); } +void +LocaleTests::testLocationLookup() +{ + const CalamaresUtils::Locale::ZonesModel zones; + + QVERIFY( zones.find( 50.0, 0.0 ) ); + QCOMPARE( zones.find( 50.0, 0.0 )->zone(), QStringLiteral( "London" ) ); +} + QTEST_GUILESS_MAIN( LocaleTests ) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 974b73bfb..eaf7698ea 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -316,6 +316,12 @@ ZonesModel::find( const QString& region, const QString& zone ) const return nullptr; } +const TimeZoneData* +ZonesModel::find( double latitude, double longitude ) const +{ + return nullptr; +} + ZonesModel::Iterator::operator bool() const { return 0 <= m_index && m_index < m_p->m_zones.count(); diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 9b8569b21..55fdda60a 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -128,6 +128,12 @@ public: */ const TimeZoneData* find( const QString& region, const QString& zone ) const; + /** @brief Look up TZ data based on the location. + * + * Returns the nearest zone to the given lat and lon. + */ + const TimeZoneData* find( double latitude, double longitude ) const; + /** @brief Iterator for testing purposes * * This is primarily for testing, but who knows, it might be useful From 9e274aac07b4781a178b6428dbef72f82c8fe75a Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 10:47:47 +0200 Subject: [PATCH 17/29] [libcalamares] Make ZonesModel more QML-friendly - expose TZ lookup (as a QObject*, which QML needs) - C++ code should use find(), which is safer --- src/libcalamares/locale/TimeZone.cpp | 36 ++++++++++++++++++++++--- src/libcalamares/locale/TimeZone.h | 39 ++++++++++++++++------------ 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index eaf7698ea..7143d7d33 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -35,6 +35,9 @@ namespace CalamaresUtils { namespace Locale { +class RegionData; +using RegionVector = QVector< RegionData* >; +using ZoneVector = QVector< TimeZoneData* >; /** @brief Turns a string longitude or latitude notation into a double * @@ -81,6 +84,7 @@ TimeZoneData::TimeZoneData( const QString& region, , m_latitude( latitude ) , m_longitude( longitude ) { + setObjectName( region + '/' + zone ); } QString @@ -105,9 +109,6 @@ RegionData::tr() const return QObject::tr( m_human, "tz_regions" ); } -using RegionVector = QVector< RegionData* >; -using ZoneVector = QVector< TimeZoneData* >; - static void loadTZData( RegionVector& regions, ZoneVector& zones ) { @@ -188,8 +189,10 @@ loadTZData( RegionVector& regions, ZoneVector& zones ) } -struct Private +class Private : public QObject { + Q_OBJECT +public: RegionVector m_regions; ZoneVector m_zones; @@ -210,6 +213,11 @@ struct Private } return lhs->region() < rhs->region(); } ); + + for ( auto* z : m_zones ) + { + z->setParent( this ); + } } }; @@ -322,6 +330,22 @@ ZonesModel::find( double latitude, double longitude ) const return nullptr; } +QObject* +ZonesModel::lookup( double latitude, double longitude ) const +{ + const auto* p = find( latitude, longitude ); + if ( !p ) + { + p = find( "America", "New_York" ); + } + if ( !p ) + { + cWarning() << "No zone (not even New York) found, expect crashes."; + } + return const_cast< QObject* >( reinterpret_cast< const QObject* >( p ) ); +} + + ZonesModel::Iterator::operator bool() const { return 0 <= m_index && m_index < m_p->m_zones.count(); @@ -376,3 +400,7 @@ RegionalZonesModel::filterAcceptsRow( int sourceRow, const QModelIndex& ) const } // namespace Locale } // namespace CalamaresUtils + +#include "utils/moc-warnings.h" + +#include "TimeZone.moc" diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 55fdda60a..1a4ee03bf 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -35,7 +35,7 @@ namespace CalamaresUtils { namespace Locale { -struct Private; +class Private; class RegionalZonesModel; class ZonesModel; @@ -122,22 +122,7 @@ public: QHash< int, QByteArray > roleNames() const override; - /** @brief Look up TZ data based on its name. - * - * Returns @c nullptr if not found. - */ - const TimeZoneData* find( const QString& region, const QString& zone ) const; - - /** @brief Look up TZ data based on the location. - * - * Returns the nearest zone to the given lat and lon. - */ - const TimeZoneData* find( double latitude, double longitude ) const; - - /** @brief Iterator for testing purposes - * - * This is primarily for testing, but who knows, it might be useful - * elsewhere, and it's convenient when it can access Private. + /** @brief Iterator for the underlying list of zones * * Iterates over all the zones in the model. Operator * may return * a @c nullptr when the iterator is not valid. Typical usage: @@ -171,6 +156,26 @@ public: Iterator begin() const { return Iterator( m_private ); } +public Q_SLOTS: + /** @brief Look up TZ data based on its name. + * + * Returns @c nullptr if not found. + */ + const TimeZoneData* find( const QString& region, const QString& zone ) const; + + /** @brief Look up TZ data based on the location. + * + * Returns the nearest zone to the given lat and lon. + */ + const TimeZoneData* find( double latitude, double longitude ) const; + + /** @brief Look up TZ data based on the location. + * + * Returns the nearest zone, or New York. This is non-const for QML + * purposes, but the object should be considered const anyway. + */ + QObject* lookup( double latitude, double longitude ) const; + private: Private* m_private; }; From 296337d45dc44a61decf92f00e602af846cb2490 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 11:57:05 +0200 Subject: [PATCH 18/29] [libcalamares] Implement nearest-TZ lookup - This version, based on lat+lon lookup, handles wrap-around the globe at -180 W (which is very close to +180 E) - Test wrap-around-the-globe lookups --- src/libcalamares/locale/Tests.cpp | 15 +++++++++-- src/libcalamares/locale/TimeZone.cpp | 37 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index 081d0ced5..e498ac039 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -395,8 +395,19 @@ LocaleTests::testLocationLookup() { const CalamaresUtils::Locale::ZonesModel zones; - QVERIFY( zones.find( 50.0, 0.0 ) ); - QCOMPARE( zones.find( 50.0, 0.0 )->zone(), QStringLiteral( "London" ) ); + const auto* zone = zones.find( 50.0, 0.0 ); + QVERIFY( zone ); + QCOMPARE( zone->zone(), QStringLiteral( "London" ) ); + + + // Tarawa is close to "the other side of the world" from London + zone = zones.find( 0.0, 179.0 ); + QVERIFY( zone ); + QCOMPARE( zone->zone(), QStringLiteral( "Tarawa" ) ); + + zone = zones.find( 0.0, -179.0 ); + QVERIFY( zone ); + QCOMPARE( zone->zone(), QStringLiteral( "Tarawa" ) ); } diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 7143d7d33..364564232 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -327,7 +327,42 @@ ZonesModel::find( const QString& region, const QString& zone ) const const TimeZoneData* ZonesModel::find( double latitude, double longitude ) const { - return nullptr; + /* This is a somewhat derpy way of finding "closest", + * in that it considers one degree of separation + * either N/S or E/W equal to any other; this obviously + * falls apart at the poles. + */ + + double largestDifference = 720.0; + const TimeZoneData* closest = nullptr; + + for ( const auto* zone : m_private->m_zones ) + { + // Latitude doesn't wrap around: there is nothing north of 90 + double latitudeDifference = abs( zone->latitude() - latitude ); + + // Longitude **does** wrap around, so consider the case of -178 and 178 + // which differ by 4 degrees. + double westerly = qMin( zone->longitude(), longitude ); + double easterly = qMax( zone->longitude(), longitude ); + double longitudeDifference = 0.0; + if ( westerly < 0.0 && !( easterly < 0.0 ) ) + { + // Only if they're different signs can we have wrap-around. + longitudeDifference = qMin( abs( westerly - easterly ), abs( 360.0 + westerly - easterly ) ); + } + else + { + longitudeDifference = abs( westerly - easterly ); + } + + if ( latitudeDifference + longitudeDifference < largestDifference ) + { + largestDifference = latitudeDifference + longitudeDifference; + closest = zone; + } + } + return closest; } QObject* From ad3c0de936854cfda3396af0f400dff2a372dc1e Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 12:43:49 +0200 Subject: [PATCH 19/29] [libcalamares] Reduce logging in POD manipulation --- src/libcalamares/geoip/Interface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libcalamares/geoip/Interface.cpp b/src/libcalamares/geoip/Interface.cpp index f8649f37a..47c826e1a 100644 --- a/src/libcalamares/geoip/Interface.cpp +++ b/src/libcalamares/geoip/Interface.cpp @@ -47,7 +47,6 @@ splitTZString( const QString& tz ) QStringList tzParts = timezoneString.split( '/', SplitSkipEmptyParts ); if ( tzParts.size() >= 2 ) { - cDebug() << "GeoIP reporting" << timezoneString; QString region = tzParts.takeFirst(); QString zone = tzParts.join( '/' ); return RegionZonePair( region, zone ); From 21f97db8fd95265ad9eada8f807673428b3350ce Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 14:19:27 +0200 Subject: [PATCH 20/29] [libcalamares] Offer translation lookup of regions --- src/libcalamares/locale/TimeZone.cpp | 12 ++++++++++++ src/libcalamares/locale/TimeZone.h | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/libcalamares/locale/TimeZone.cpp b/src/libcalamares/locale/TimeZone.cpp index 364564232..e2779281f 100644 --- a/src/libcalamares/locale/TimeZone.cpp +++ b/src/libcalamares/locale/TimeZone.cpp @@ -268,6 +268,18 @@ RegionsModel::roleNames() const return { { NameRole, "name" }, { KeyRole, "key" } }; } +QString +RegionsModel::tr( const QString& region ) const +{ + for ( const auto* p : m_private->m_regions ) + { + if ( p->key() == region ) + { + return p->tr(); + } + } + return region; +} ZonesModel::ZonesModel( QObject* parent ) : QAbstractListModel( parent ) diff --git a/src/libcalamares/locale/TimeZone.h b/src/libcalamares/locale/TimeZone.h index 1a4ee03bf..1d4b4a8ff 100644 --- a/src/libcalamares/locale/TimeZone.h +++ b/src/libcalamares/locale/TimeZone.h @@ -47,6 +47,9 @@ class TimeZoneData : public QObject, TranslatableString Q_OBJECT Q_PROPERTY( QString region READ region CONSTANT ) + Q_PROPERTY( QString zone READ zone CONSTANT ) + Q_PROPERTY( QString name READ tr CONSTANT ) + Q_PROPERTY( QString countryCode READ country CONSTANT ) public: TimeZoneData( const QString& region, @@ -98,6 +101,14 @@ public: QHash< int, QByteArray > roleNames() const override; +public Q_SLOTS: + /** @brief Provides a human-readable version of the region + * + * Returns @p region unchanged if there is no such region + * or no translation for the region's name. + */ + QString tr( const QString& region ) const; + private: Private* m_private; }; From 04e53be934d13c08a63e4a5a12d8e0100ad15188 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 12:14:15 +0200 Subject: [PATCH 21/29] [locale] Repair test: don't re-init te occupied-pixels set each loop - while here, merge Tests.h to the cpp file - Fix build when debugging timezones (missed during earlier refactor) --- src/modules/locale/Config.cpp | 2 +- src/modules/locale/Tests.cpp | 30 ++++++++++++++++++++--- src/modules/locale/Tests.h | 45 ----------------------------------- 3 files changed, 28 insertions(+), 49 deletions(-) delete mode 100644 src/modules/locale/Tests.h diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index 96d279477..b8a6df437 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -368,7 +368,7 @@ getAdjustLiveTimezone( const QVariantMap& configurationMap, bool& adjustLiveTime adjustLiveTimezone = CalamaresUtils::getBool( configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() ); #ifdef DEBUG_TIMEZONES - if ( m_adjustLiveTimezone ) + if ( adjustLiveTimezone ) { cWarning() << "Turning off live-timezone adjustments because debugging is on."; adjustLiveTimezone = false; diff --git a/src/modules/locale/Tests.cpp b/src/modules/locale/Tests.cpp index e7fbb10f2..de0be57c4 100644 --- a/src/modules/locale/Tests.cpp +++ b/src/modules/locale/Tests.cpp @@ -17,7 +17,6 @@ */ -#include "Tests.h" #include "LocaleConfiguration.h" #include "timezonewidget/TimeZoneImage.h" @@ -28,6 +27,26 @@ #include +class LocaleTests : public QObject +{ + Q_OBJECT +public: + LocaleTests(); + ~LocaleTests() override; + +private Q_SLOTS: + void initTestCase(); + // Check the sample config file is processed correctly + void testEmptyLocaleConfiguration(); + void testDefaultLocaleConfiguration(); + void testSplitLocaleConfiguration(); + + // Check the TZ images for consistency + void testTZImages(); // No overlaps in images + void testTZLocations(); // No overlaps in locations + void testSpecificLocations(); +}; + QTEST_MAIN( LocaleTests ) @@ -191,11 +210,12 @@ LocaleTests::testTZLocations() using namespace CalamaresUtils::Locale; ZonesModel zones; + QVERIFY( zones.rowCount( QModelIndex() ) > 100 ); + int overlapcount = 0; + std::set< QPoint > occupied; for ( auto it = zones.begin(); it; ++it ) { - std::set< QPoint > occupied; - const auto* zone = *it; QVERIFY( zone ); @@ -230,3 +250,7 @@ LocaleTests::testSpecificLocations() QEXPECT_FAIL( "", "Gibraltar and Ceuta are really close", Continue ); QVERIFY( gpos.y() < cpos.y() ); // Gibraltar is north of Ceuta } + +#include "utils/moc-warnings.h" + +#include "Tests.moc" diff --git a/src/modules/locale/Tests.h b/src/modules/locale/Tests.h deleted file mode 100644 index e01b1a25c..000000000 --- a/src/modules/locale/Tests.h +++ /dev/null @@ -1,45 +0,0 @@ -/* === This file is part of Calamares - === - * - * Copyright 2019-2020, Adriaan de Groot - * - * Calamares is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Calamares is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Calamares. If not, see . - */ - - -#ifndef TESTS_H -#define TESTS_H - -#include - -class LocaleTests : public QObject -{ - Q_OBJECT -public: - LocaleTests(); - ~LocaleTests() override; - -private Q_SLOTS: - void initTestCase(); - // Check the sample config file is processed correctly - void testEmptyLocaleConfiguration(); - void testDefaultLocaleConfiguration(); - void testSplitLocaleConfiguration(); - - // Check the TZ images for consistency - void testTZImages(); // No overlaps in images - void testTZLocations(); // No overlaps in locations - void testSpecificLocations(); -}; - -#endif From b36ad4c7f4c7e96718e67303690b27777d56da35 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 12:32:48 +0200 Subject: [PATCH 22/29] [locale] Add test for Config initialization - needs some massaging because Config otherwise depends on ModuleManager which is a UI class (for the Reasons), but we already have a BUILD_AS_TEST define for that purpose. - demonstrate a nullptr deref. --- src/modules/locale/CMakeLists.txt | 2 ++ src/modules/locale/Config.cpp | 2 ++ src/modules/locale/Tests.cpp | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/modules/locale/CMakeLists.txt b/src/modules/locale/CMakeLists.txt index a09bde282..6f965f041 100644 --- a/src/modules/locale/CMakeLists.txt +++ b/src/modules/locale/CMakeLists.txt @@ -36,7 +36,9 @@ calamares_add_test( localetest SOURCES Tests.cpp + Config.cpp LocaleConfiguration.cpp + SetTimezoneJob.cpp timezonewidget/TimeZoneImage.cpp DEFINITIONS SOURCE_DIR="${CMAKE_CURRENT_LIST_DIR}/images" diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index b8a6df437..ecd704186 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -436,11 +436,13 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) getStartingTimezone( configurationMap, m_startingTimezone ); getGeoIP( configurationMap, m_geoip ); +#ifndef BUILD_AS_TEST if ( m_geoip && m_geoip->isValid() ) { connect( Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP ); } +#endif } Calamares::JobList diff --git a/src/modules/locale/Tests.cpp b/src/modules/locale/Tests.cpp index de0be57c4..52d4882a2 100644 --- a/src/modules/locale/Tests.cpp +++ b/src/modules/locale/Tests.cpp @@ -16,7 +16,7 @@ * along with Calamares. If not, see . */ - +#include "Config.h" #include "LocaleConfiguration.h" #include "timezonewidget/TimeZoneImage.h" @@ -45,6 +45,9 @@ private Q_SLOTS: void testTZImages(); // No overlaps in images void testTZLocations(); // No overlaps in locations void testSpecificLocations(); + + // Check the Config loading + void testConfigInitialization(); }; QTEST_MAIN( LocaleTests ) @@ -251,6 +254,16 @@ LocaleTests::testSpecificLocations() QVERIFY( gpos.y() < cpos.y() ); // Gibraltar is north of Ceuta } +void +LocaleTests::testConfigInitialization() +{ + Config c; + + QVERIFY( !c.currentLocation() ); + QVERIFY( !c.currentLocationStatus().isEmpty() ); +} + + #include "utils/moc-warnings.h" #include "Tests.moc" From eda14ce548b2fd0a0d397c6956c64b50dd0d38f5 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 12:38:55 +0200 Subject: [PATCH 23/29] [locale] Avoid nullptr deref - when no location has been set at all, there's no sensible TZ to report; just leave it blank. In *practice* you won't hit this code from the Calamares UI before a location has been set, because the Config object is instantiated and then immediately configured, but from tests or unusual UIs it could be. --- src/modules/locale/Config.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index ecd704186..a6d3e5222 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -318,7 +318,9 @@ Config::setLCLocaleExplicitly( const QString& locale ) QString Config::currentLocationStatus() const { - return tr( "Set timezone to %1/%2." ).arg( m_currentLocation->region(), m_currentLocation->zone() ); + return tr( "Set timezone to %1/%2." ) + .arg( m_currentLocation ? m_currentLocation->region() : QString(), + m_currentLocation ? m_currentLocation->zone() : QString() ); } static inline QString From 15a8d629864d3de473b02b59fd6dec30b14a88db Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 12:40:24 +0200 Subject: [PATCH 24/29] [locale] Add a 'current timezone' strings to Config - status is a longer phrase - name is a short human-readable name - code is the internal code Code that writes its own "Timezone set to" messages can use the name, rather than the status. --- src/modules/locale/Config.cpp | 25 +++++++++++++++++++++++++ src/modules/locale/Config.h | 9 +++++++++ 2 files changed, 34 insertions(+) diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index a6d3e5222..228f863e5 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -192,6 +192,9 @@ Config::Config( QObject* parent ) QProcess::execute( "timedatectl", // depends on systemd { "set-timezone", location->region() + '/' + location->zone() } ); } + + emit currentTimezoneCodeChanged( currentTimezoneCode() ); + emit currentTimezoneNameChanged( currentTimezoneName() ); } ); auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); }; @@ -265,6 +268,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location emit currentLCStatusChanged( currentLCStatus() ); } emit currentLocationChanged( m_currentLocation ); + // Other signals come from the LocationChanged signal } } @@ -323,6 +327,27 @@ Config::currentLocationStatus() const m_currentLocation ? m_currentLocation->zone() : QString() ); } +QString +Config::currentTimezoneCode() const +{ + if ( m_currentLocation ) + { + return m_currentLocation->region() + '/' + m_currentLocation->zone(); + } + return QString(); +} + +QString +Config::currentTimezoneName() const +{ + if ( m_currentLocation ) + { + return m_regionModel->tr( m_currentLocation->region() ) + '/' + m_currentLocation->tr(); + } + return QString(); +} + + static inline QString localeLabel( const QString& s ) { diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index fccd3822e..5754cde8d 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -48,7 +48,12 @@ class Config : public QObject Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged ) Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged ) Q_PROPERTY( QString currentLCStatus READ currentLCStatus NOTIFY currentLCStatusChanged ) + // Name are shorter human-readable names + // .. main difference is that status is a full sentence, like "Timezone is America/New York" + // while name is just "America/New York" (and the code, below, is "America/New_York") + Q_PROPERTY( QString currentTimezoneName READ currentTimezoneName NOTIFY currentTimezoneNameChanged ) // Code are internal identifiers, like "en_US.UTF-8" + Q_PROPERTY( QString currentTimezoneCode READ currentTimezoneCode NOTIFY currentTimezoneCodeChanged ) Q_PROPERTY( QString currentLanguageCode READ currentLanguageCode WRITE setLanguageExplicitly NOTIFY currentLanguageCodeChanged ) Q_PROPERTY( QString currentLCCode READ currentLCCode WRITE setLCLocaleExplicitly NOTIFY currentLCCodeChanged ) @@ -119,6 +124,8 @@ public Q_SLOTS: QString currentLanguageCode() const { return localeConfiguration().language(); } QString currentLCCode() const { return localeConfiguration().lc_numeric; } + QString currentTimezoneName() const; // human-readable + QString currentTimezoneCode() const; signals: void currentLocationChanged( const CalamaresUtils::Locale::TimeZoneData* location ) const; @@ -128,6 +135,8 @@ signals: void prettyStatusChanged( const QString& ) const; void currentLanguageCodeChanged( const QString& ) const; void currentLCCodeChanged( const QString& ) const; + void currentTimezoneCodeChanged( const QString& ) const; + void currentTimezoneNameChanged( const QString& ) const; private: /// A list of supported locale identifiers (e.g. "en_US.UTF-8") From 91cc5a2b4225d8a3a4f35dc90bebbd213ee217c9 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 14:47:55 +0200 Subject: [PATCH 25/29] [locale] Update the map-QML implementation - Config has suitable strings for displaying TZ information. Use them and automatic bindings. Don't update the strings manually. - Suggest online or offline TZ lookups based on what the distro wants. Edit the QML to pick online lookups (needs access to the geonames service, though). - Drop the variables that point at config and geoip: the Config object has a currentLocation, which is filled in by both the configuration and any GeoIP lookup -- it doesn't have city or country information though. --- src/modules/localeq/Map.qml | 53 ++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/modules/localeq/Map.qml b/src/modules/localeq/Map.qml index 080d7388a..06b4f7a1f 100644 --- a/src/modules/localeq/Map.qml +++ b/src/modules/localeq/Map.qml @@ -29,17 +29,12 @@ import QtPositioning 5.14 Column { width: parent.width - //Needs to come from .conf/geoip - property var configCity: "New York" - property var configCountry: "USA" - property var configTimezone: "America/New York" - property var geoipCity: "" //"Amsterdam" - property var geoipCountry: "" //"Netherlands" - property var geoipTimezone: "" //"Europe/Amsterdam" - // vars that will stay once connected - property var cityName: (geoipCity != "") ? geoipCity : configCity - property var countryName: (geoipCountry != "") ? geoipCountry : configCountry - property var timeZone: (geoipTimezone != "") ? geoipTimezone : configTimezone + // These are used by the map query to initially center the + // map on the user's likely location. They are updated by + // getIp() which does a more accurate GeoIP lookup than + // the default one in Calamares + property var cityName: "" + property var countryName: "" function getIp() { var xhr = new XMLHttpRequest @@ -51,9 +46,10 @@ Column { var ct = responseJSON.city var cy = responseJSON.country - tzText.text = "Timezone: " + tz cityName = ct countryName = cy + + config.setCurrentLocation(tz) } } @@ -63,7 +59,15 @@ Column { xhr.send() } - function getTz() { + /* This is an **accurate** TZ lookup method: it queries an + * online service for the TZ at the given coordinates. It + * requires an internet connection, though, and the distribution + * will need to have an account with geonames to not hit the + * daily query limit. + * + * See below, in MouseArea, for calling the right method. + */ + function getTzOnline() { var xhr = new XMLHttpRequest var latC = map.center.latitude var lonC = map.center.longitude @@ -73,16 +77,29 @@ Column { var responseJSON = JSON.parse(xhr.responseText) var tz2 = responseJSON.timezoneId - tzText.text = "Timezone: " + tz2 config.setCurrentLocation(tz2) } } + console.log("Online lookup", latC, lonC) // Needs to move to localeq.conf, each distribution will need their own account xhr.open("GET", "http://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME") xhr.send() } + /* This is a quick TZ lookup method: it uses the existing + * Calamares "closest TZ" code, which has lots of caveats. + * + * See below, in MouseArea, for calling the right method. + */ + function getTzOffline() { + var latC = map.center.latitude + var lonC = map.center.longitude + var tz = config.zonesModel.lookup(latC, lonC) + console.log("Offline lookup", latC, lonC) + config.setCurrentLocation(tz.region, tz.zone) + } + Rectangle { width: parent.width height: parent.height / 1.28 @@ -156,9 +173,8 @@ Column { map.center.latitude = coordinate.latitude map.center.longitude = coordinate.longitude - getTz(); - - console.log(coordinate.latitude, coordinate.longitude) + // Pick a TZ lookup method here (quick:offline, accurate:online) + getTzOffline(); } } } @@ -218,8 +234,7 @@ Column { Text { id: tzText - text: tzText.text - //text: qsTr("Timezone: %1").arg(timeZone) + text: qsTr("Timezone: %1").arg(config.currentTimezoneName) color: Kirigami.Theme.textColor anchors.centerIn: parent } From 32c8338a9ce540b9306a6610b3ff9843f6adb8bd Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 15:58:11 +0200 Subject: [PATCH 26/29] [locale] QML doesn't like const --- src/modules/locale/Config.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index 5754cde8d..2a44f4d24 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -42,7 +42,7 @@ class Config : public QObject Q_PROPERTY( QAbstractItemModel* regionalZonesModel READ regionalZonesModel CONSTANT FINAL ) Q_PROPERTY( - const CalamaresUtils::Locale::TimeZoneData* currentLocation READ currentLocation NOTIFY currentLocationChanged ) + CalamaresUtils::Locale::TimeZoneData* currentLocation READ currentLocation_c NOTIFY currentLocationChanged ) // Status are complete, human-readable, messages Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged ) @@ -94,9 +94,16 @@ public: const CalamaresUtils::Locale::TimeZoneData* currentLocation() const { return m_currentLocation; } + /// Special case, set location from starting timezone if not already set void setCurrentLocation(); +private: + CalamaresUtils::Locale::TimeZoneData* currentLocation_c() const + { + return const_cast< CalamaresUtils::Locale::TimeZoneData* >( m_currentLocation ); + } + public Q_SLOTS: /// Set a language by user-choice, overriding future location changes void setLanguageExplicitly( const QString& language ); From 71ca1e154438b62deddeb5505509c9321d3eb288 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 15:49:55 +0200 Subject: [PATCH 27/29] [localeq] Pick up Config changes before showing the module --- src/modules/localeq/LocaleQmlViewStep.cpp | 9 ++++++++- src/modules/localeq/LocaleQmlViewStep.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/modules/localeq/LocaleQmlViewStep.cpp b/src/modules/localeq/LocaleQmlViewStep.cpp index ead2e2673..cbcc5f78e 100644 --- a/src/modules/localeq/LocaleQmlViewStep.cpp +++ b/src/modules/localeq/LocaleQmlViewStep.cpp @@ -79,9 +79,16 @@ LocaleQmlViewStep::jobs() const return m_config->createJobs(); } +void +LocaleQmlViewStep::onActivate() +{ + m_config->setCurrentLocation(); // Finalize the location + QmlViewStep::onActivate(); +} + void LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { m_config->setConfigurationMap( configurationMap ); - Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last + QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last } diff --git a/src/modules/localeq/LocaleQmlViewStep.h b/src/modules/localeq/LocaleQmlViewStep.h index 3d73c6f79..e2b8a9e13 100644 --- a/src/modules/localeq/LocaleQmlViewStep.h +++ b/src/modules/localeq/LocaleQmlViewStep.h @@ -43,6 +43,8 @@ public: bool isAtBeginning() const override; bool isAtEnd() const override; + virtual void onActivate() override; + Calamares::JobList jobs() const override; void setConfigurationMap( const QVariantMap& configurationMap ) override; From c69bd972e9f9b96fe1028f164e2a507d7c0b8f99 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 16:05:58 +0200 Subject: [PATCH 28/29] [localeq] Demonstrate "offline" lookups - we can do GeoIP and GeoNames lookups, **or** - use Calamares's internal GeoIP lookup and country / city hints. The online version is much more accurate, but costs more lookups; in these examples, set it all to "offline" and document what needs to change (code edit) to use the online version. It's probably a good beginner job to introduce a bool in localeq.qml to switch the behaviors. --- src/modules/localeq/Map.qml | 23 +++++++++++++++++++++-- src/modules/localeq/localeq.qml | 8 ++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/modules/localeq/Map.qml b/src/modules/localeq/Map.qml index 06b4f7a1f..d414825dd 100644 --- a/src/modules/localeq/Map.qml +++ b/src/modules/localeq/Map.qml @@ -36,7 +36,12 @@ Column { property var cityName: "" property var countryName: "" - function getIp() { + /* This is an extra GeoIP lookup, which will find better-accuracy + * location data for the user's IP, and then sets the current timezone + * and map location. Call it from Component.onCompleted so that + * it happens "on time" before the page is shown. + */ + function getIpOnline() { var xhr = new XMLHttpRequest xhr.onreadystatechange = function() { @@ -59,6 +64,16 @@ Column { xhr.send() } + /* This is an "offline" GeoIP lookup -- it just follows what + * Calamares itself has figured out with its GeoIP or configuration. + * Call it from the **Component** onActivate() -- in localeq.qml -- + * so it happens as the page is shown. + */ + function getIpOffline() { + cityName = config.currentLocation.zone + countryName = config.currentLocation.countryCode + } + /* This is an **accurate** TZ lookup method: it queries an * online service for the TZ at the given coordinates. It * requires an internet connection, though, and the distribution @@ -239,7 +254,11 @@ Column { anchors.centerIn: parent } - Component.onCompleted: getIp(); + /* If you want an extra (and accurate) GeoIP lookup, + * enable this one and disable the offline lookup in + * onActivate(). + Component.onCompleted: getIpOnline(); + */ } } diff --git a/src/modules/localeq/localeq.qml b/src/modules/localeq/localeq.qml index 49719db65..1a250f5c1 100644 --- a/src/modules/localeq/localeq.qml +++ b/src/modules/localeq/localeq.qml @@ -29,6 +29,14 @@ Page { width: 800 height: 550 + function onActivate() { + /* If you want the map to follow Calamares's GeoIP + * lookup or configuration, call the update function + * here, and disable the one at onCompleted in Map.qml. + */ + if (Network.hasInternet) { image.item.getIpOffline() } + } + Loader { id: image anchors.horizontalCenter: parent.horizontalCenter From 52d1c8f88a077170f7f7e11d0f5b61b82e68e0b6 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 6 Aug 2020 18:32:51 +0200 Subject: [PATCH 29/29] [locale] Explicitly update GS from the locale step - refactor into some free functions (out of the lambda's for connecting) - introduce new method to call from onLeave(), matching previous widget behavior --- src/modules/locale/Config.cpp | 70 +++++++++++++++++------ src/modules/locale/Config.h | 1 + src/modules/locale/LocaleViewStep.cpp | 2 + src/modules/localeq/LocaleQmlViewStep.cpp | 6 ++ src/modules/localeq/LocaleQmlViewStep.h | 1 + 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index 228f863e5..5bbe20038 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -148,6 +148,45 @@ loadLocales( const QString& localeGenPath ) return localeGenLines; } +static bool +updateGSLocation( Calamares::GlobalStorage* gs, const CalamaresUtils::Locale::TimeZoneData* location ) +{ + const QString regionKey = QStringLiteral( "locationRegion" ); + const QString zoneKey = QStringLiteral( "locationZone" ); + + if ( !location ) + { + if ( gs->contains( regionKey ) || gs->contains( zoneKey ) ) + { + gs->remove( regionKey ); + gs->remove( zoneKey ); + return true; + } + return false; + } + + // Update the GS region and zone (and possibly the live timezone) + bool locationChanged + = ( location->region() != gs->value( regionKey ) ) || ( location->zone() != gs->value( zoneKey ) ); + + gs->insert( regionKey, location->region() ); + gs->insert( zoneKey, location->zone() ); + + return locationChanged; +} + +static void +updateGSLocale( Calamares::GlobalStorage* gs, const LocaleConfiguration& locale ) +{ + auto map = locale.toMap(); + QVariantMap vm; + for ( auto it = map.constBegin(); it != map.constEnd(); ++it ) + { + vm.insert( it.key(), it.value() ); + } + gs->insert( "localeConf", vm ); +} + Config::Config( QObject* parent ) : QObject( parent ) , m_regionModel( std::make_unique< CalamaresUtils::Locale::RegionsModel >() ) @@ -166,31 +205,17 @@ Config::Config( QObject* parent ) } ); 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 ) - { - vm.insert( it.key(), it.value() ); - } - gs->insert( "localeConf", vm ); + updateGSLocale( Calamares::JobQueue::instance()->globalStorage(), localeConfiguration() ); } ); connect( this, &Config::currentLocationChanged, [&]() { - auto* gs = Calamares::JobQueue::instance()->globalStorage(); + const bool locationChanged + = updateGSLocation( Calamares::JobQueue::instance()->globalStorage(), currentLocation() ); - // 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 ) { QProcess::execute( "timedatectl", // depends on systemd - { "set-timezone", location->region() + '/' + location->zone() } ); + { "set-timezone", currentTimezoneCode() } ); } emit currentTimezoneCodeChanged( currentTimezoneCode() ); @@ -487,6 +512,15 @@ Config::createJobs() return list; } +void +Config::finalizeGlobalStorage() const +{ + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + updateGSLocale( gs, localeConfiguration() ); + updateGSLocation( gs, currentLocation() ); +} + + void Config::startGeoIP() { diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index 2a44f4d24..d95606d99 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -66,6 +66,7 @@ public: ~Config(); void setConfigurationMap( const QVariantMap& ); + void finalizeGlobalStorage() const; Calamares::JobList createJobs(); /// locale configuration (LC_* and LANG) based solely on the current location. diff --git a/src/modules/locale/LocaleViewStep.cpp b/src/modules/locale/LocaleViewStep.cpp index a85c87e4f..9ffb96c25 100644 --- a/src/modules/locale/LocaleViewStep.cpp +++ b/src/modules/locale/LocaleViewStep.cpp @@ -138,6 +138,7 @@ LocaleViewStep::jobs() const void LocaleViewStep::onActivate() { + m_config->setCurrentLocation(); // Finalize the location if ( !m_actualWidget ) { setUpPage(); @@ -149,6 +150,7 @@ LocaleViewStep::onActivate() void LocaleViewStep::onLeave() { + m_config->finalizeGlobalStorage(); } diff --git a/src/modules/localeq/LocaleQmlViewStep.cpp b/src/modules/localeq/LocaleQmlViewStep.cpp index cbcc5f78e..d60664a8f 100644 --- a/src/modules/localeq/LocaleQmlViewStep.cpp +++ b/src/modules/localeq/LocaleQmlViewStep.cpp @@ -86,6 +86,12 @@ LocaleQmlViewStep::onActivate() QmlViewStep::onActivate(); } +void +LocaleQmlViewStep::onLeave() +{ + m_config->finalizeGlobalStorage(); +} + void LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { diff --git a/src/modules/localeq/LocaleQmlViewStep.h b/src/modules/localeq/LocaleQmlViewStep.h index e2b8a9e13..bbafd5abb 100644 --- a/src/modules/localeq/LocaleQmlViewStep.h +++ b/src/modules/localeq/LocaleQmlViewStep.h @@ -44,6 +44,7 @@ public: bool isAtEnd() const override; virtual void onActivate() override; + virtual void onLeave() override; Calamares::JobList jobs() const override;