Merge branch 'issue-1476' into calamares
Go over the locale module again: - new models that avoid weird casts and inconvenient iteration - shared timezone data - simple sorting and filtering - simplify the map / QML version FIXES #1476 FIXES #1426
This commit is contained in:
commit
2ce12d5368
@ -57,6 +57,7 @@ set( libSources
|
|||||||
locale/Lookup.cpp
|
locale/Lookup.cpp
|
||||||
locale/TimeZone.cpp
|
locale/TimeZone.cpp
|
||||||
locale/TranslatableConfiguration.cpp
|
locale/TranslatableConfiguration.cpp
|
||||||
|
locale/TranslatableString.cpp
|
||||||
|
|
||||||
# Modules
|
# Modules
|
||||||
modulesystem/InstanceKey.cpp
|
modulesystem/InstanceKey.cpp
|
||||||
|
@ -47,7 +47,6 @@ splitTZString( const QString& tz )
|
|||||||
QStringList tzParts = timezoneString.split( '/', SplitSkipEmptyParts );
|
QStringList tzParts = timezoneString.split( '/', SplitSkipEmptyParts );
|
||||||
if ( tzParts.size() >= 2 )
|
if ( tzParts.size() >= 2 )
|
||||||
{
|
{
|
||||||
cDebug() << "GeoIP reporting" << timezoneString;
|
|
||||||
QString region = tzParts.takeFirst();
|
QString region = tzParts.takeFirst();
|
||||||
QString zone = tzParts.join( '/' );
|
QString zone = tzParts.join( '/' );
|
||||||
return RegionZonePair( region, zone );
|
return RegionZonePair( region, zone );
|
||||||
|
@ -47,8 +47,12 @@ private Q_SLOTS:
|
|||||||
void testInterlingue();
|
void testInterlingue();
|
||||||
|
|
||||||
// TimeZone testing
|
// TimeZone testing
|
||||||
|
void testRegions();
|
||||||
void testSimpleZones();
|
void testSimpleZones();
|
||||||
void testComplexZones();
|
void testComplexZones();
|
||||||
|
void testTZLookup();
|
||||||
|
void testTZIterator();
|
||||||
|
void testLocationLookup();
|
||||||
};
|
};
|
||||||
|
|
||||||
LocaleTests::LocaleTests() {}
|
LocaleTests::LocaleTests() {}
|
||||||
@ -244,58 +248,169 @@ LocaleTests::testTranslatableConfig2()
|
|||||||
QCOMPARE( ts3.count(), 1 ); // The empty string
|
QCOMPARE( ts3.count(), 1 ); // The empty string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LocaleTests::testRegions()
|
||||||
|
{
|
||||||
|
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 ), RegionsModel::NameRole );
|
||||||
|
QVERIFY( name.isValid() );
|
||||||
|
QVERIFY( !name.toString().isEmpty() );
|
||||||
|
names.append( name.toString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
QVERIFY( names.contains( "America" ) );
|
||||||
|
QVERIFY( !names.contains( "UTC" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
void
|
||||||
LocaleTests::testSimpleZones()
|
LocaleTests::testSimpleZones()
|
||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
|
ZonesModel zones;
|
||||||
|
|
||||||
|
QVERIFY( zones.rowCount( QModelIndex() ) > 24 );
|
||||||
|
|
||||||
|
QStringList names;
|
||||||
|
displayedNames( zones, names );
|
||||||
|
QVERIFY( names.contains( "Amsterdam" ) );
|
||||||
|
if ( !names.contains( "New York" ) )
|
||||||
{
|
{
|
||||||
TZRegion r;
|
for ( const auto& s : names )
|
||||||
QVERIFY( r.tr().isEmpty() );
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
TZZone n;
|
if ( s.startsWith( 'N' ) )
|
||||||
QVERIFY( n.tr().isEmpty() );
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
TZZone r0( "xAmsterdam" );
|
cDebug() << s;
|
||||||
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() );
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
QVERIFY( names.contains( "New York" ) );
|
||||||
|
QVERIFY( !names.contains( "America" ) );
|
||||||
|
QVERIFY( !names.contains( "New_York" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LocaleTests::testComplexZones()
|
LocaleTests::testComplexZones()
|
||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
|
ZonesModel zones;
|
||||||
|
RegionalZonesModel europe( &zones );
|
||||||
|
|
||||||
{
|
QStringList names;
|
||||||
TZZone r0( "America/New_York" );
|
displayedNames( zones, names );
|
||||||
TZZone r1( "America/New York" );
|
QVERIFY( names.contains( "New York" ) );
|
||||||
|
QVERIFY( names.contains( "Prague" ) );
|
||||||
|
QVERIFY( names.contains( "Abidjan" ) );
|
||||||
|
|
||||||
QCOMPARE( r0.tr(), r1.tr() );
|
// No region set
|
||||||
QCOMPARE( r0.tr(), QStringLiteral( "America/New York" ) );
|
displayedNames( europe, names );
|
||||||
}
|
QVERIFY( names.contains( "New York" ) );
|
||||||
{
|
QVERIFY( names.contains( "Prague" ) );
|
||||||
TZZone r( "zxc,;*_vm" );
|
QVERIFY( names.contains( "Abidjan" ) );
|
||||||
QVERIFY( !r.tr().isEmpty() );
|
|
||||||
QCOMPARE( r.tr(), QStringLiteral( "zxc,;* vm" ) ); // Only _ is special
|
// 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" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
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() ) );
|
||||||
|
|
||||||
|
QCOMPARE( zones.data( zones.index( 0 ), ZonesModel::RegionRole ).toString(), QStringLiteral( "Africa" ) );
|
||||||
|
QCOMPARE( ( *zones.begin() )->zone(), QStringLiteral( "Abidjan" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LocaleTests::testLocationLookup()
|
||||||
|
{
|
||||||
|
const CalamaresUtils::Locale::ZonesModel zones;
|
||||||
|
|
||||||
|
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" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN( LocaleTests )
|
QTEST_GUILESS_MAIN( LocaleTests )
|
||||||
|
|
||||||
#include "utils/moc-warnings.h"
|
#include "utils/moc-warnings.h"
|
||||||
|
@ -22,17 +22,28 @@
|
|||||||
|
|
||||||
#include "TimeZone.h"
|
#include "TimeZone.h"
|
||||||
|
|
||||||
|
#include "locale/TranslatableString.h"
|
||||||
#include "utils/Logger.h"
|
#include "utils/Logger.h"
|
||||||
#include "utils/String.h"
|
#include "utils/String.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QStringList>
|
#include <QString>
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab";
|
static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab";
|
||||||
|
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* This handles strings like "+4230+00131" from zone.tab,
|
||||||
|
* which is degrees-and-minutes notation, and + means north or east.
|
||||||
|
*/
|
||||||
static double
|
static double
|
||||||
getRightGeoLocation( QString str )
|
getRightGeoLocation( QString str )
|
||||||
{
|
{
|
||||||
@ -62,98 +73,48 @@ getRightGeoLocation( QString str )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace CalamaresUtils
|
TimeZoneData::TimeZoneData( const QString& region,
|
||||||
|
const QString& zone,
|
||||||
|
const QString& country,
|
||||||
|
double latitude,
|
||||||
|
double longitude )
|
||||||
|
: TranslatableString( zone )
|
||||||
|
, m_region( region )
|
||||||
|
, m_country( country )
|
||||||
|
, m_latitude( latitude )
|
||||||
|
, m_longitude( longitude )
|
||||||
{
|
{
|
||||||
namespace Locale
|
setObjectName( region + '/' + zone );
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
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( const char* s1 )
|
|
||||||
: m_human( s1 ? munge( s1 ) : nullptr )
|
|
||||||
, m_key( s1 ? QString( s1 ) : QString() )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CStringPair::~CStringPair()
|
|
||||||
{
|
|
||||||
free( m_human );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QString
|
QString
|
||||||
TZRegion::tr() const
|
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 TranslatableString
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using TranslatableString::TranslatableString;
|
||||||
|
QString tr() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
QString
|
||||||
|
RegionData::tr() const
|
||||||
{
|
{
|
||||||
// NOTE: context name must match what's used in zone-extractor.py
|
// NOTE: context name must match what's used in zone-extractor.py
|
||||||
return QObject::tr( m_human, "tz_regions" );
|
return QObject::tr( m_human, "tz_regions" );
|
||||||
}
|
}
|
||||||
|
|
||||||
TZRegion::~TZRegion()
|
static void
|
||||||
|
loadTZData( RegionVector& regions, ZoneVector& zones )
|
||||||
{
|
{
|
||||||
qDeleteAll( m_zones );
|
QFile file( TZ_DATA_FILE );
|
||||||
}
|
if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
|
||||||
|
|
||||||
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 );
|
QTextStream in( &file );
|
||||||
while ( !in.atEnd() )
|
while ( !in.atEnd() )
|
||||||
{
|
{
|
||||||
@ -181,18 +142,6 @@ TZRegion::fromFile( const char* fileName )
|
|||||||
continue;
|
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();
|
QString countryCode = list.at( 0 ).trimmed();
|
||||||
if ( countryCode.size() != 2 )
|
if ( countryCode.size() != 2 )
|
||||||
{
|
{
|
||||||
@ -200,114 +149,305 @@ TZRegion::fromFile( const char* fileName )
|
|||||||
}
|
}
|
||||||
|
|
||||||
timezoneParts.removeFirst();
|
timezoneParts.removeFirst();
|
||||||
thisRegion->m_zones.append(
|
QString zone = timezoneParts.join( '/' );
|
||||||
new TZZone( region, timezoneParts.join( '/' ).toUtf8().constData(), countryCode, list.at( 1 ) ) );
|
if ( zone.length() < 2 )
|
||||||
}
|
|
||||||
|
|
||||||
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 );
|
continue;
|
||||||
if ( r )
|
|
||||||
{
|
|
||||||
std::sort( r->m_zones.begin(), r->m_zones.end(), sorter );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
QString position = list.at( 1 );
|
||||||
}
|
|
||||||
|
|
||||||
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 );
|
int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 );
|
||||||
|
double latitude;
|
||||||
|
double longitude;
|
||||||
if ( cooSplitPos > 0 )
|
if ( cooSplitPos > 0 )
|
||||||
{
|
{
|
||||||
m_latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
|
latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
|
||||||
m_longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
|
longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have region, zone, country, lat and longitude
|
||||||
|
const RegionData* existingRegion = nullptr;
|
||||||
|
for ( const auto* p : regions )
|
||||||
|
{
|
||||||
|
if ( p->key() == region )
|
||||||
|
{
|
||||||
|
existingRegion = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !existingRegion )
|
||||||
|
{
|
||||||
|
regions.append( new RegionData( region ) );
|
||||||
|
}
|
||||||
|
zones.append( new TimeZoneData( region, zone, countryCode, latitude, longitude ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString
|
|
||||||
TZZone::tr() const
|
class Private : public QObject
|
||||||
{
|
{
|
||||||
// NOTE: context name must match what's used in zone-extractor.py
|
Q_OBJECT
|
||||||
return QObject::tr( m_human, "tz_names" );
|
public:
|
||||||
|
RegionVector m_regions;
|
||||||
|
ZoneVector 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 );
|
||||||
|
|
||||||
|
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();
|
||||||
|
} );
|
||||||
|
|
||||||
|
for ( auto* z : m_zones )
|
||||||
|
{
|
||||||
|
z->setParent( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Private*
|
||||||
|
privateInstance()
|
||||||
|
{
|
||||||
|
static Private* s_p = new Private;
|
||||||
|
return s_p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegionsModel::RegionsModel( QObject* parent )
|
||||||
CStringListModel::CStringListModel( CStringPairList l )
|
: QAbstractListModel( parent )
|
||||||
: m_list( l )
|
, m_private( privateInstance() )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
RegionsModel::~RegionsModel() {}
|
||||||
CStringListModel::setList( CalamaresUtils::Locale::CStringPairList l )
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_list = l;
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
CStringListModel::rowCount( const QModelIndex& ) const
|
RegionsModel::rowCount( const QModelIndex& ) const
|
||||||
{
|
{
|
||||||
return m_list.count();
|
return m_private->m_regions.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant
|
QVariant
|
||||||
CStringListModel::data( const QModelIndex& index, int role ) const
|
RegionsModel::data( const QModelIndex& index, int role ) const
|
||||||
{
|
{
|
||||||
if ( ( role != Qt::DisplayRole ) && ( role != Qt::UserRole ) )
|
if ( !index.isValid() || index.row() < 0 || index.row() >= m_private->m_regions.count() )
|
||||||
{
|
{
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !index.isValid() )
|
const auto& region = m_private->m_regions[ index.row() ];
|
||||||
|
if ( role == NameRole )
|
||||||
{
|
{
|
||||||
|
return region->tr();
|
||||||
|
}
|
||||||
|
if ( role == KeyRole )
|
||||||
|
{
|
||||||
|
return region->key();
|
||||||
|
}
|
||||||
return QVariant();
|
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 >
|
QHash< int, QByteArray >
|
||||||
CStringListModel::roleNames() const
|
RegionsModel::roleNames() const
|
||||||
{
|
{
|
||||||
return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } };
|
return { { NameRole, "name" }, { KeyRole, "key" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const CStringPair*
|
QString
|
||||||
CStringListModel::item( int index ) const
|
RegionsModel::tr( const QString& region ) const
|
||||||
{
|
{
|
||||||
if ( ( index < 0 ) || ( index >= m_list.count() ) )
|
for ( const auto* p : m_private->m_regions )
|
||||||
{
|
{
|
||||||
return nullptr;
|
if ( p->key() == region )
|
||||||
|
{
|
||||||
|
return p->tr();
|
||||||
}
|
}
|
||||||
return m_list[ index ];
|
}
|
||||||
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ZonesModel::ZonesModel( QObject* parent )
|
||||||
|
: QAbstractListModel( parent )
|
||||||
|
, m_private( privateInstance() )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ZonesModel::~ZonesModel() {}
|
||||||
|
|
||||||
|
int
|
||||||
|
ZonesModel::rowCount( const QModelIndex& ) const
|
||||||
|
{
|
||||||
|
return m_private->m_zones.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() ];
|
||||||
|
switch ( role )
|
||||||
|
{
|
||||||
|
case NameRole:
|
||||||
|
return zone->tr();
|
||||||
|
case KeyRole:
|
||||||
|
return zone->key();
|
||||||
|
case RegionRole:
|
||||||
|
return zone->region();
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash< int, QByteArray >
|
||||||
|
ZonesModel::roleNames() const
|
||||||
|
{
|
||||||
|
return { { NameRole, "name" }, { KeyRole, "key" } };
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeZoneData*
|
||||||
|
ZonesModel::find( const QString& region, const QString& zone ) const
|
||||||
|
{
|
||||||
|
for ( const auto* p : m_private->m_zones )
|
||||||
|
{
|
||||||
|
if ( p->region() == region && p->zone() == zone )
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TimeZoneData*
|
||||||
|
ZonesModel::find( double latitude, double longitude ) const
|
||||||
|
{
|
||||||
|
/* 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*
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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() )
|
||||||
|
{
|
||||||
|
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 Locale
|
||||||
} // namespace CalamaresUtils
|
} // namespace CalamaresUtils
|
||||||
|
|
||||||
|
#include "utils/moc-warnings.h"
|
||||||
|
|
||||||
|
#include "TimeZone.moc"
|
||||||
|
@ -24,184 +24,198 @@
|
|||||||
|
|
||||||
#include "DllMacro.h"
|
#include "DllMacro.h"
|
||||||
|
|
||||||
#include "utils/Logger.h"
|
#include "locale/TranslatableString.h"
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QVariant>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace CalamaresUtils
|
namespace CalamaresUtils
|
||||||
{
|
{
|
||||||
namespace Locale
|
namespace Locale
|
||||||
{
|
{
|
||||||
|
class Private;
|
||||||
|
class RegionalZonesModel;
|
||||||
|
class ZonesModel;
|
||||||
|
|
||||||
/** @brief A pair of strings, one human-readable, one a key
|
class TimeZoneData : public QObject, TranslatableString
|
||||||
*
|
|
||||||
* 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<QString, QString> because there is API that needs
|
|
||||||
* C-style strings.
|
|
||||||
*/
|
|
||||||
class CStringPair : public QObject
|
|
||||||
{
|
{
|
||||||
|
friend class RegionalZonesModel;
|
||||||
|
friend class ZonesModel;
|
||||||
|
|
||||||
Q_OBJECT
|
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:
|
public:
|
||||||
/// @brief An empty pair
|
TimeZoneData( const QString& region,
|
||||||
CStringPair() {}
|
const QString& zone,
|
||||||
/// @brief Given an identifier, create the pair
|
const QString& country,
|
||||||
explicit CStringPair( const char* s1 );
|
double latitude,
|
||||||
CStringPair( CStringPair&& t );
|
double longitude );
|
||||||
CStringPair( const CStringPair& );
|
TimeZoneData( const TimeZoneData& ) = delete;
|
||||||
virtual ~CStringPair();
|
TimeZoneData( TimeZoneData&& ) = delete;
|
||||||
|
|
||||||
/// @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 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 region() const { return m_region; }
|
||||||
QString zone() const { return key(); }
|
QString zone() const { return key(); }
|
||||||
|
|
||||||
QString country() const { return m_country; }
|
QString country() const { return m_country; }
|
||||||
double latitude() const { return m_latitude; }
|
double latitude() const { return m_latitude; }
|
||||||
double longitude() const { return m_longitude; }
|
double longitude() const { return m_longitude; }
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
QString m_region;
|
QString m_region;
|
||||||
QString m_country;
|
QString m_country;
|
||||||
double m_latitude = 0.0, m_longitude = 0.0;
|
double m_latitude;
|
||||||
|
double m_longitude;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CStringListModel : public QAbstractListModel
|
|
||||||
|
/** @brief The list of timezone regions
|
||||||
|
*
|
||||||
|
* The regions are a short list of global areas (Africa, America, India ..)
|
||||||
|
* which contain zones.
|
||||||
|
*/
|
||||||
|
class DLLEXPORT RegionsModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged )
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @brief Create empty model
|
enum Roles
|
||||||
CStringListModel() {}
|
{
|
||||||
/// @brief Create model from list (non-owning)
|
NameRole = Qt::DisplayRole,
|
||||||
CStringListModel( CStringPairList );
|
KeyRole = Qt::UserRole // So that currentData() will get the key
|
||||||
|
};
|
||||||
|
|
||||||
|
RegionsModel( QObject* parent = nullptr );
|
||||||
|
virtual ~RegionsModel() override;
|
||||||
|
|
||||||
int rowCount( const QModelIndex& parent ) const override;
|
int rowCount( const QModelIndex& parent ) const override;
|
||||||
|
|
||||||
QVariant data( const QModelIndex& index, int role ) const override;
|
QVariant data( const QModelIndex& index, int role ) const override;
|
||||||
|
|
||||||
const CStringPair* item( int index ) const;
|
|
||||||
QHash< int, QByteArray > roleNames() const override;
|
QHash< int, QByteArray > roleNames() const override;
|
||||||
|
|
||||||
void setCurrentIndex( int index );
|
public Q_SLOTS:
|
||||||
int currentIndex() const;
|
/** @brief Provides a human-readable version of the region
|
||||||
|
*
|
||||||
void setList( CStringPairList );
|
* Returns @p region unchanged if there is no such region
|
||||||
|
* or no translation for the region's name.
|
||||||
inline int indexOf( const QString& key )
|
*/
|
||||||
{
|
QString tr( const QString& region ) const;
|
||||||
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:
|
private:
|
||||||
CStringPairList m_list;
|
Private* m_private;
|
||||||
int m_currentIndex = -1;
|
};
|
||||||
|
|
||||||
|
class DLLEXPORT ZonesModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles
|
||||||
|
{
|
||||||
|
NameRole = Qt::DisplayRole,
|
||||||
|
KeyRole = Qt::UserRole, // So that currentData() will get the key
|
||||||
|
RegionRole = 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;
|
||||||
|
|
||||||
|
/** @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:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* 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 ); }
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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:
|
signals:
|
||||||
void currentIndexChanged();
|
void regionChanged( const QString& );
|
||||||
|
|
||||||
|
private:
|
||||||
|
Private* m_private;
|
||||||
|
QString m_region;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Locale
|
} // namespace Locale
|
||||||
} // namespace CalamaresUtils
|
} // namespace CalamaresUtils
|
||||||
|
|
||||||
|
89
src/libcalamares/locale/TranslatableString.cpp
Normal file
89
src/libcalamares/locale/TranslatableString.cpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#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
|
68
src/libcalamares/locale/TranslatableString.h
Normal file
68
src/libcalamares/locale/TranslatableString.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||||
|
*
|
||||||
|
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef LOCALE_TRANSLATABLESTRING_H
|
||||||
|
#define LOCALE_TRANSLATABLESTRING_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
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<QString, QString> 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
|
@ -36,7 +36,9 @@ calamares_add_test(
|
|||||||
localetest
|
localetest
|
||||||
SOURCES
|
SOURCES
|
||||||
Tests.cpp
|
Tests.cpp
|
||||||
|
Config.cpp
|
||||||
LocaleConfiguration.cpp
|
LocaleConfiguration.cpp
|
||||||
|
SetTimezoneJob.cpp
|
||||||
timezonewidget/TimeZoneImage.cpp
|
timezonewidget/TimeZoneImage.cpp
|
||||||
DEFINITIONS
|
DEFINITIONS
|
||||||
SOURCE_DIR="${CMAKE_CURRENT_LIST_DIR}/images"
|
SOURCE_DIR="${CMAKE_CURRENT_LIST_DIR}/images"
|
||||||
|
@ -148,17 +148,50 @@ loadLocales( const QString& localeGenPath )
|
|||||||
return localeGenLines;
|
return localeGenLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline const CalamaresUtils::Locale::CStringPairList&
|
static bool
|
||||||
timezoneData()
|
updateGSLocation( Calamares::GlobalStorage* gs, const CalamaresUtils::Locale::TimeZoneData* location )
|
||||||
{
|
{
|
||||||
return CalamaresUtils::Locale::TZRegion::fromZoneTab();
|
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 )
|
Config::Config( QObject* parent )
|
||||||
: QObject( parent )
|
: QObject( parent )
|
||||||
, m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( ::timezoneData() ) )
|
, m_regionModel( std::make_unique< CalamaresUtils::Locale::RegionsModel >() )
|
||||||
, m_zonesModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >() )
|
, 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
|
// Slightly unusual: connect to our *own* signals. Wherever the language
|
||||||
// or the location is changed, these signals are emitted, so hook up to
|
// or the location is changed, these signals are emitted, so hook up to
|
||||||
@ -172,32 +205,21 @@ Config::Config( QObject* parent )
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
connect( this, &Config::currentLCCodeChanged, [&]() {
|
connect( this, &Config::currentLCCodeChanged, [&]() {
|
||||||
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
updateGSLocale( Calamares::JobQueue::instance()->globalStorage(), localeConfiguration() );
|
||||||
// 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 );
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
connect( this, &Config::currentLocationChanged, [&]() {
|
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 )
|
if ( locationChanged && m_adjustLiveTimezone )
|
||||||
{
|
{
|
||||||
QProcess::execute( "timedatectl", // depends on systemd
|
QProcess::execute( "timedatectl", // depends on systemd
|
||||||
{ "set-timezone", location->region() + '/' + location->zone() } );
|
{ "set-timezone", currentTimezoneCode() } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit currentTimezoneCodeChanged( currentTimezoneCode() );
|
||||||
|
emit currentTimezoneNameChanged( currentTimezoneName() );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); };
|
auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); };
|
||||||
@ -208,12 +230,6 @@ Config::Config( QObject* parent )
|
|||||||
|
|
||||||
Config::~Config() {}
|
Config::~Config() {}
|
||||||
|
|
||||||
const CalamaresUtils::Locale::CStringPairList&
|
|
||||||
Config::timezoneData() const
|
|
||||||
{
|
|
||||||
return ::timezoneData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Config::setCurrentLocation()
|
Config::setCurrentLocation()
|
||||||
{
|
{
|
||||||
@ -223,7 +239,8 @@ Config::setCurrentLocation()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::setCurrentLocation(const QString& regionzone)
|
void
|
||||||
|
Config::setCurrentLocation( const QString& regionzone )
|
||||||
{
|
{
|
||||||
auto r = CalamaresUtils::GeoIP::splitTZString( regionzone );
|
auto r = CalamaresUtils::GeoIP::splitTZString( regionzone );
|
||||||
if ( r.isValid() )
|
if ( r.isValid() )
|
||||||
@ -236,8 +253,7 @@ void
|
|||||||
Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
|
Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
|
||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
auto* region = timezoneData().find< TZRegion >( regionName );
|
auto* zone = m_zonesModel->find( regionName, zoneName );
|
||||||
auto* zone = region ? region->zones().find< TZZone >( zoneName ) : nullptr;
|
|
||||||
if ( zone )
|
if ( zone )
|
||||||
{
|
{
|
||||||
setCurrentLocation( zone );
|
setCurrentLocation( zone );
|
||||||
@ -250,7 +266,7 @@ Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location )
|
||||||
{
|
{
|
||||||
if ( location != m_currentLocation )
|
if ( location != m_currentLocation )
|
||||||
{
|
{
|
||||||
@ -277,6 +293,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
|||||||
emit currentLCStatusChanged( currentLCStatus() );
|
emit currentLCStatusChanged( currentLCStatus() );
|
||||||
}
|
}
|
||||||
emit currentLocationChanged( m_currentLocation );
|
emit currentLocationChanged( m_currentLocation );
|
||||||
|
// Other signals come from the LocationChanged signal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,9 +347,32 @@ Config::setLCLocaleExplicitly( const QString& locale )
|
|||||||
QString
|
QString
|
||||||
Config::currentLocationStatus() const
|
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() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
static inline QString
|
||||||
localeLabel( const QString& s )
|
localeLabel( const QString& s )
|
||||||
{
|
{
|
||||||
@ -380,7 +420,7 @@ getAdjustLiveTimezone( const QVariantMap& configurationMap, bool& adjustLiveTime
|
|||||||
adjustLiveTimezone = CalamaresUtils::getBool(
|
adjustLiveTimezone = CalamaresUtils::getBool(
|
||||||
configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() );
|
configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() );
|
||||||
#ifdef DEBUG_TIMEZONES
|
#ifdef DEBUG_TIMEZONES
|
||||||
if ( m_adjustLiveTimezone )
|
if ( adjustLiveTimezone )
|
||||||
{
|
{
|
||||||
cWarning() << "Turning off live-timezone adjustments because debugging is on.";
|
cWarning() << "Turning off live-timezone adjustments because debugging is on.";
|
||||||
adjustLiveTimezone = false;
|
adjustLiveTimezone = false;
|
||||||
@ -448,18 +488,20 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
|
|||||||
getStartingTimezone( configurationMap, m_startingTimezone );
|
getStartingTimezone( configurationMap, m_startingTimezone );
|
||||||
getGeoIP( configurationMap, m_geoip );
|
getGeoIP( configurationMap, m_geoip );
|
||||||
|
|
||||||
|
#ifndef BUILD_AS_TEST
|
||||||
if ( m_geoip && m_geoip->isValid() )
|
if ( m_geoip && m_geoip->isValid() )
|
||||||
{
|
{
|
||||||
connect(
|
connect(
|
||||||
Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP );
|
Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP );
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Calamares::JobList
|
Calamares::JobList
|
||||||
Config::createJobs()
|
Config::createJobs()
|
||||||
{
|
{
|
||||||
Calamares::JobList list;
|
Calamares::JobList list;
|
||||||
const CalamaresUtils::Locale::TZZone* location = currentLocation();
|
const auto* location = currentLocation();
|
||||||
|
|
||||||
if ( location )
|
if ( location )
|
||||||
{
|
{
|
||||||
@ -470,6 +512,15 @@ Config::createJobs()
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Config::finalizeGlobalStorage() const
|
||||||
|
{
|
||||||
|
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||||
|
updateGSLocale( gs, localeConfiguration() );
|
||||||
|
updateGSLocation( gs, currentLocation() );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Config::startGeoIP()
|
Config::startGeoIP()
|
||||||
{
|
{
|
||||||
|
@ -37,18 +37,25 @@ class Config : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY( const QStringList& supportedLocales READ supportedLocales CONSTANT FINAL )
|
Q_PROPERTY( const QStringList& supportedLocales READ supportedLocales CONSTANT FINAL )
|
||||||
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* zonesModel READ zonesModel CONSTANT FINAL )
|
Q_PROPERTY( CalamaresUtils::Locale::RegionsModel* regionModel READ regionModel CONSTANT FINAL )
|
||||||
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* 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
|
Q_PROPERTY(
|
||||||
NOTIFY currentLocationChanged )
|
CalamaresUtils::Locale::TimeZoneData* currentLocation READ currentLocation_c NOTIFY currentLocationChanged )
|
||||||
|
|
||||||
// Status are complete, human-readable, messages
|
// Status are complete, human-readable, messages
|
||||||
Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged )
|
Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged )
|
||||||
Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged )
|
Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged )
|
||||||
Q_PROPERTY( QString currentLCStatus READ currentLCStatus NOTIFY currentLCStatusChanged )
|
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"
|
// Code are internal identifiers, like "en_US.UTF-8"
|
||||||
Q_PROPERTY( QString currentLanguageCode READ currentLanguageCode WRITE setLanguageExplicitly NOTIFY currentLanguageCodeChanged )
|
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 )
|
Q_PROPERTY( QString currentLCCode READ currentLCCode WRITE setLCLocaleExplicitly NOTIFY currentLCCodeChanged )
|
||||||
|
|
||||||
// This is a long human-readable string with all three statuses
|
// This is a long human-readable string with all three statuses
|
||||||
@ -59,17 +66,9 @@ public:
|
|||||||
~Config();
|
~Config();
|
||||||
|
|
||||||
void setConfigurationMap( const QVariantMap& );
|
void setConfigurationMap( const QVariantMap& );
|
||||||
|
void finalizeGlobalStorage() const;
|
||||||
Calamares::JobList createJobs();
|
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.
|
/// locale configuration (LC_* and LANG) based solely on the current location.
|
||||||
LocaleConfiguration automaticLocaleConfiguration() const;
|
LocaleConfiguration automaticLocaleConfiguration() const;
|
||||||
/// locale configuration that takes explicit settings into account
|
/// locale configuration that takes explicit settings into account
|
||||||
@ -85,13 +84,27 @@ public:
|
|||||||
/// The human-readable summary of what the module will do
|
/// The human-readable summary of what the module will do
|
||||||
QString prettyStatus() const;
|
QString prettyStatus() const;
|
||||||
|
|
||||||
|
// A long list of locale codes (e.g. en_US.UTF-8)
|
||||||
const QStringList& supportedLocales() const { return m_localeGenLines; }
|
const QStringList& supportedLocales() const { return m_localeGenLines; }
|
||||||
CalamaresUtils::Locale::CStringListModel* regionModel() const { return m_regionModel.get(); }
|
// All the regions (Africa, America, ...)
|
||||||
CalamaresUtils::Locale::CStringListModel* zonesModel() const { return m_zonesModel.get(); }
|
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
|
/// Special case, set location from starting timezone if not already set
|
||||||
void setCurrentLocation();
|
void setCurrentLocation();
|
||||||
|
|
||||||
|
private:
|
||||||
|
CalamaresUtils::Locale::TimeZoneData* currentLocation_c() const
|
||||||
|
{
|
||||||
|
return const_cast< CalamaresUtils::Locale::TimeZoneData* >( m_currentLocation );
|
||||||
|
}
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
/// Set a language by user-choice, overriding future location changes
|
/// Set a language by user-choice, overriding future location changes
|
||||||
void setLanguageExplicitly( const QString& language );
|
void setLanguageExplicitly( const QString& language );
|
||||||
@ -111,38 +124,38 @@ public Q_SLOTS:
|
|||||||
* names a zone within that region.
|
* names a zone within that region.
|
||||||
*/
|
*/
|
||||||
void setCurrentLocation( const QString& region, const QString& zone );
|
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 currentLanguageCode() const { return localeConfiguration().language(); }
|
||||||
QString currentLCCode() const { return localeConfiguration().lc_numeric; }
|
QString currentLCCode() const { return localeConfiguration().lc_numeric; }
|
||||||
|
QString currentTimezoneName() const; // human-readable
|
||||||
|
QString currentTimezoneCode() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentLocationChanged( const CalamaresUtils::Locale::TZZone* location ) const;
|
void currentLocationChanged( const CalamaresUtils::Locale::TimeZoneData* location ) const;
|
||||||
void currentLocationStatusChanged( const QString& ) const;
|
void currentLocationStatusChanged( const QString& ) const;
|
||||||
void currentLanguageStatusChanged( const QString& ) const;
|
void currentLanguageStatusChanged( const QString& ) const;
|
||||||
void currentLCStatusChanged( const QString& ) const;
|
void currentLCStatusChanged( const QString& ) const;
|
||||||
void prettyStatusChanged( const QString& ) const;
|
void prettyStatusChanged( const QString& ) const;
|
||||||
void currentLanguageCodeChanged( const QString& ) const;
|
void currentLanguageCodeChanged( const QString& ) const;
|
||||||
void currentLCCodeChanged( const QString& ) const;
|
void currentLCCodeChanged( const QString& ) const;
|
||||||
|
void currentTimezoneCodeChanged( const QString& ) const;
|
||||||
|
void currentTimezoneNameChanged( const QString& ) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// A list of supported locale identifiers (e.g. "en_US.UTF-8")
|
/// A list of supported locale identifiers (e.g. "en_US.UTF-8")
|
||||||
QStringList m_localeGenLines;
|
QStringList m_localeGenLines;
|
||||||
|
|
||||||
/// The regions (America, Asia, Europe ..)
|
/// The regions (America, Asia, Europe ..)
|
||||||
std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel;
|
std::unique_ptr< CalamaresUtils::Locale::RegionsModel > m_regionModel;
|
||||||
/// The zones for the current region (e.g. America/New_York)
|
std::unique_ptr< CalamaresUtils::Locale::ZonesModel > m_zonesModel;
|
||||||
std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_zonesModel;
|
std::unique_ptr< CalamaresUtils::Locale::RegionalZonesModel > m_regionalZonesModel;
|
||||||
|
|
||||||
/// The location, points into the timezone data
|
const CalamaresUtils::Locale::TimeZoneData* m_currentLocation = nullptr;
|
||||||
const CalamaresUtils::Locale::TZZone* m_currentLocation = nullptr;
|
|
||||||
|
|
||||||
/** @brief Specific locale configurations
|
/** @brief Specific locale configurations
|
||||||
*
|
*
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||||
*
|
*
|
||||||
* Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
|
* SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac <teo@kde.org>
|
||||||
* Copyright 2017-2019, Adriaan de Groot <groot@kde.org>
|
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Calamares is free software: you can redistribute it and/or modify
|
* Calamares is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -42,7 +43,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent )
|
|||||||
QBoxLayout* mainLayout = new QVBoxLayout;
|
QBoxLayout* mainLayout = new QVBoxLayout;
|
||||||
|
|
||||||
QBoxLayout* tzwLayout = new QHBoxLayout;
|
QBoxLayout* tzwLayout = new QHBoxLayout;
|
||||||
m_tzWidget = new TimeZoneWidget( config->timezoneData(), this );
|
m_tzWidget = new TimeZoneWidget( m_config->zonesModel(), this );
|
||||||
tzwLayout->addStretch();
|
tzwLayout->addStretch();
|
||||||
tzwLayout->addWidget( m_tzWidget );
|
tzwLayout->addWidget( m_tzWidget );
|
||||||
tzwLayout->addStretch();
|
tzwLayout->addStretch();
|
||||||
@ -101,6 +102,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent )
|
|||||||
// Set up the location before connecting signals, to avoid a signal
|
// Set up the location before connecting signals, to avoid a signal
|
||||||
// storm as various parts interact.
|
// storm as various parts interact.
|
||||||
m_regionCombo->setModel( m_config->regionModel() );
|
m_regionCombo->setModel( m_config->regionModel() );
|
||||||
|
m_zoneCombo->setModel( m_config->regionalZonesModel() );
|
||||||
locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget
|
locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget
|
||||||
m_tzWidget->setCurrentLocation( m_config->currentLocation() );
|
m_tzWidget->setCurrentLocation( m_config->currentLocation() );
|
||||||
|
|
||||||
@ -111,7 +113,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent )
|
|||||||
connect( m_tzWidget,
|
connect( m_tzWidget,
|
||||||
&TimeZoneWidget::locationChanged,
|
&TimeZoneWidget::locationChanged,
|
||||||
config,
|
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_regionCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::regionChanged );
|
||||||
connect( m_zoneCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::zoneChanged );
|
connect( m_zoneCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::zoneChanged );
|
||||||
@ -151,35 +153,26 @@ LocalePage::regionChanged( int currentIndex )
|
|||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
|
|
||||||
Q_UNUSED( currentIndex )
|
QString selectedRegion = m_regionCombo->itemData( currentIndex ).toString();
|
||||||
QString selectedRegion = m_regionCombo->currentData().toString();
|
|
||||||
|
|
||||||
TZRegion* region = m_config->timezoneData().find< TZRegion >( selectedRegion );
|
|
||||||
if ( !region )
|
|
||||||
{
|
{
|
||||||
return;
|
cSignalBlocker z( m_zoneCombo );
|
||||||
|
m_config->regionalZonesModel()->setRegion( selectedRegion );
|
||||||
}
|
}
|
||||||
|
m_zoneCombo->currentIndexChanged( 0 );
|
||||||
{
|
|
||||||
cSignalBlocker b( m_zoneCombo );
|
|
||||||
m_zoneCombo->setModel( new CStringListModel( region->zones() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LocalePage::zoneChanged( int currentIndex )
|
LocalePage::zoneChanged( int currentIndex )
|
||||||
{
|
{
|
||||||
Q_UNUSED( currentIndex )
|
|
||||||
if ( !m_blockTzWidgetSet )
|
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
|
void
|
||||||
LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location )
|
LocalePage::locationChanged( const CalamaresUtils::Locale::TimeZoneData* location )
|
||||||
{
|
{
|
||||||
if ( !location )
|
if ( !location )
|
||||||
{
|
{
|
||||||
|
@ -53,7 +53,7 @@ private:
|
|||||||
|
|
||||||
void regionChanged( int currentIndex );
|
void regionChanged( int currentIndex );
|
||||||
void zoneChanged( int currentIndex );
|
void zoneChanged( int currentIndex );
|
||||||
void locationChanged( const CalamaresUtils::Locale::TZZone* location );
|
void locationChanged( const CalamaresUtils::Locale::TimeZoneData* location );
|
||||||
void changeLocale();
|
void changeLocale();
|
||||||
void changeFormats();
|
void changeFormats();
|
||||||
|
|
||||||
|
@ -138,6 +138,7 @@ LocaleViewStep::jobs() const
|
|||||||
void
|
void
|
||||||
LocaleViewStep::onActivate()
|
LocaleViewStep::onActivate()
|
||||||
{
|
{
|
||||||
|
m_config->setCurrentLocation(); // Finalize the location
|
||||||
if ( !m_actualWidget )
|
if ( !m_actualWidget )
|
||||||
{
|
{
|
||||||
setUpPage();
|
setUpPage();
|
||||||
@ -149,6 +150,7 @@ LocaleViewStep::onActivate()
|
|||||||
void
|
void
|
||||||
LocaleViewStep::onLeave()
|
LocaleViewStep::onLeave()
|
||||||
{
|
{
|
||||||
|
m_config->finalizeGlobalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,17 +16,40 @@
|
|||||||
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
|
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
#include "Tests.h"
|
|
||||||
#include "LocaleConfiguration.h"
|
#include "LocaleConfiguration.h"
|
||||||
#include "timezonewidget/TimeZoneImage.h"
|
#include "timezonewidget/TimeZoneImage.h"
|
||||||
|
|
||||||
#include "locale/TimeZone.h"
|
#include "locale/TimeZone.h"
|
||||||
|
#include "utils/Logger.h"
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Check the Config loading
|
||||||
|
void testConfigInitialization();
|
||||||
|
};
|
||||||
|
|
||||||
QTEST_MAIN( LocaleTests )
|
QTEST_MAIN( LocaleTests )
|
||||||
|
|
||||||
|
|
||||||
@ -115,24 +138,23 @@ LocaleTests::testTZImages()
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
const CStringPairList& regions = TZRegion::fromZoneTab();
|
const ZonesModel m;
|
||||||
|
|
||||||
int overlapcount = 0;
|
int overlapcount = 0;
|
||||||
for ( const auto* pr : regions )
|
for ( auto it = m.begin(); it; ++it )
|
||||||
{
|
{
|
||||||
const TZRegion* region = dynamic_cast< const TZRegion* >( pr );
|
QString region = m.data( m.index( it.index() ), ZonesModel::RegionRole ).toString();
|
||||||
QVERIFY( region );
|
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 );
|
QVERIFY( iterzone );
|
||||||
cDebug() << "Region" << region->region() << "zones #" << region->zones().count();
|
|
||||||
Logger::setupLogLevel( Logger::LOGERROR );
|
|
||||||
|
|
||||||
const auto zones = region->zones();
|
|
||||||
QVERIFY( zones.count() > 0 );
|
|
||||||
for ( const auto* pz : zones )
|
|
||||||
{
|
|
||||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
|
||||||
QVERIFY( zone );
|
QVERIFY( zone );
|
||||||
|
QCOMPARE( zone, iterzone );
|
||||||
|
QCOMPARE( zone->zone(), zoneName );
|
||||||
|
QCOMPARE( zone->region(), region );
|
||||||
|
|
||||||
int overlap = 0;
|
int overlap = 0;
|
||||||
auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() );
|
auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() );
|
||||||
@ -147,7 +169,6 @@ LocaleTests::testTZImages()
|
|||||||
overlapcount++;
|
overlapcount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QEXPECT_FAIL( "", "TZ Images not yet all fixed", Continue );
|
QEXPECT_FAIL( "", "TZ Images not yet all fixed", Continue );
|
||||||
QCOMPARE( overlapcount, 0 );
|
QCOMPARE( overlapcount, 0 );
|
||||||
@ -168,12 +189,17 @@ operator<( const QPoint& l, const QPoint& r )
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
listAll( const QPoint& p, const CalamaresUtils::Locale::CStringPairList& zones )
|
listAll( const QPoint& p, const CalamaresUtils::Locale::ZonesModel& zones )
|
||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
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() ) )
|
if ( p == TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ) )
|
||||||
{
|
{
|
||||||
cError() << Logger::SubEntry << zone->zone();
|
cError() << Logger::SubEntry << zone->zone();
|
||||||
@ -185,25 +211,15 @@ void
|
|||||||
LocaleTests::testTZLocations()
|
LocaleTests::testTZLocations()
|
||||||
{
|
{
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
const CStringPairList& regions = TZRegion::fromZoneTab();
|
ZonesModel zones;
|
||||||
|
|
||||||
|
QVERIFY( zones.rowCount( QModelIndex() ) > 100 );
|
||||||
|
|
||||||
int overlapcount = 0;
|
int overlapcount = 0;
|
||||||
for ( const auto* pr : regions )
|
|
||||||
{
|
|
||||||
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;
|
std::set< QPoint > occupied;
|
||||||
|
for ( auto it = zones.begin(); it; ++it )
|
||||||
const auto zones = region->zones();
|
|
||||||
QVERIFY( zones.count() > 0 );
|
|
||||||
for ( const auto* pz : zones )
|
|
||||||
{
|
{
|
||||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
const auto* zone = *it;
|
||||||
QVERIFY( zone );
|
QVERIFY( zone );
|
||||||
|
|
||||||
auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||||
@ -215,48 +231,17 @@ LocaleTests::testTZLocations()
|
|||||||
}
|
}
|
||||||
occupied.insert( pos );
|
occupied.insert( pos );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QEXPECT_FAIL( "", "TZ Images contain pixel-overlaps", Continue );
|
QEXPECT_FAIL( "", "TZ Images contain pixel-overlaps", Continue );
|
||||||
QCOMPARE( overlapcount, 0 );
|
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
|
void
|
||||||
LocaleTests::testSpecificLocations()
|
LocaleTests::testSpecificLocations()
|
||||||
{
|
{
|
||||||
const auto* gibraltar = findZone( "Gibraltar" );
|
CalamaresUtils::Locale::ZonesModel zones;
|
||||||
const auto* ceuta = findZone( "Ceuta" );
|
const auto* gibraltar = zones.find( "Europe", "Gibraltar" );
|
||||||
|
const auto* ceuta = zones.find( "Africa", "Ceuta" );
|
||||||
QVERIFY( gibraltar );
|
QVERIFY( gibraltar );
|
||||||
QVERIFY( ceuta );
|
QVERIFY( ceuta );
|
||||||
|
|
||||||
@ -268,3 +253,17 @@ LocaleTests::testSpecificLocations()
|
|||||||
QEXPECT_FAIL( "", "Gibraltar and Ceuta are really close", Continue );
|
QEXPECT_FAIL( "", "Gibraltar and Ceuta are really close", Continue );
|
||||||
QVERIFY( gpos.y() < cpos.y() ); // Gibraltar is north of Ceuta
|
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"
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
/* === This file is part of Calamares - <http://github.com/calamares> ===
|
|
||||||
*
|
|
||||||
* Copyright 2019-2020, Adriaan de Groot <groot@kde.org>
|
|
||||||
*
|
|
||||||
* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef TESTS_H
|
|
||||||
#define TESTS_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
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
|
|
@ -35,13 +35,13 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static QPoint
|
static QPoint
|
||||||
getLocationPosition( const CalamaresUtils::Locale::TZZone* l )
|
getLocationPosition( const CalamaresUtils::Locale::TimeZoneData* l )
|
||||||
{
|
{
|
||||||
return TimeZoneImageList::getLocationPosition( l->longitude(), l->latitude() );
|
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 )
|
: QWidget( parent )
|
||||||
, timeZoneImages( TimeZoneImageList::fromQRC() )
|
, timeZoneImages( TimeZoneImageList::fromQRC() )
|
||||||
, m_zonesData( zones )
|
, m_zonesData( zones )
|
||||||
@ -65,7 +65,7 @@ TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& z
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
TimeZoneWidget::setCurrentLocation( const TimeZoneData* location )
|
||||||
{
|
{
|
||||||
if ( location == m_currentLocation )
|
if ( location == m_currentLocation )
|
||||||
{
|
{
|
||||||
@ -190,21 +190,15 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event )
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set nearest location
|
// Set nearest location
|
||||||
int nX = 999999, mX = event->pos().x();
|
int nX = 999999, mX = event->pos().x();
|
||||||
int nY = 999999, mY = event->pos().y();
|
int nY = 999999, mY = event->pos().y();
|
||||||
|
|
||||||
using namespace CalamaresUtils::Locale;
|
using namespace CalamaresUtils::Locale;
|
||||||
const TZZone* closest = nullptr;
|
const TimeZoneData* closest = nullptr;
|
||||||
for ( const auto* region_p : m_zonesData )
|
for ( auto it = m_zonesData->begin(); it; ++it )
|
||||||
{
|
{
|
||||||
const auto* region = dynamic_cast< const TZRegion* >( region_p );
|
const auto* zone = *it;
|
||||||
if ( region )
|
|
||||||
{
|
|
||||||
for ( const auto* zone_p : region->zones() )
|
|
||||||
{
|
|
||||||
const auto* zone = dynamic_cast< const TZZone* >( zone_p );
|
|
||||||
if ( zone )
|
if ( zone )
|
||||||
{
|
{
|
||||||
QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||||
@ -217,8 +211,6 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( closest )
|
if ( closest )
|
||||||
{
|
{
|
||||||
|
@ -51,28 +51,28 @@ class TimeZoneWidget : public QWidget
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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:
|
public Q_SLOTS:
|
||||||
/** @brief Sets a location by pointer
|
/** @brief Sets a location by pointer
|
||||||
*
|
*
|
||||||
* Pointer should be within the same model as the widget uses.
|
* Pointer should be within the same model as the widget uses.
|
||||||
*/
|
*/
|
||||||
void setCurrentLocation( const TZZone* location );
|
void setCurrentLocation( const TimeZoneData* location );
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/** @brief The location has changed by mouse click */
|
/** @brief The location has changed by mouse click */
|
||||||
void locationChanged( const TZZone* location );
|
void locationChanged( const TimeZoneData* location );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QFont font;
|
QFont font;
|
||||||
QImage background, pin, currentZoneImage;
|
QImage background, pin, currentZoneImage;
|
||||||
TimeZoneImageList timeZoneImages;
|
TimeZoneImageList timeZoneImages;
|
||||||
|
|
||||||
const CalamaresUtils::Locale::CStringPairList& m_zonesData;
|
const CalamaresUtils::Locale::ZonesModel* m_zonesData;
|
||||||
const TZZone* m_currentLocation = nullptr; // Not owned by me
|
const TimeZoneData* m_currentLocation = nullptr; // Not owned by me
|
||||||
|
|
||||||
void paintEvent( QPaintEvent* event );
|
void paintEvent( QPaintEvent* event );
|
||||||
void mousePressEvent( QMouseEvent* event );
|
void mousePressEvent( QMouseEvent* event );
|
||||||
|
@ -79,9 +79,22 @@ LocaleQmlViewStep::jobs() const
|
|||||||
return m_config->createJobs();
|
return m_config->createJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LocaleQmlViewStep::onActivate()
|
||||||
|
{
|
||||||
|
m_config->setCurrentLocation(); // Finalize the location
|
||||||
|
QmlViewStep::onActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LocaleQmlViewStep::onLeave()
|
||||||
|
{
|
||||||
|
m_config->finalizeGlobalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
||||||
{
|
{
|
||||||
m_config->setConfigurationMap( configurationMap );
|
m_config->setConfigurationMap( configurationMap );
|
||||||
Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
|
QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,9 @@ public:
|
|||||||
bool isAtBeginning() const override;
|
bool isAtBeginning() const override;
|
||||||
bool isAtEnd() const override;
|
bool isAtEnd() const override;
|
||||||
|
|
||||||
|
virtual void onActivate() override;
|
||||||
|
virtual void onLeave() override;
|
||||||
|
|
||||||
Calamares::JobList jobs() const override;
|
Calamares::JobList jobs() const override;
|
||||||
|
|
||||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||||
|
@ -29,19 +29,19 @@ import QtPositioning 5.14
|
|||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
//Needs to come from .conf/geoip
|
// These are used by the map query to initially center the
|
||||||
property var configCity: "New York"
|
// map on the user's likely location. They are updated by
|
||||||
property var configCountry: "USA"
|
// getIp() which does a more accurate GeoIP lookup than
|
||||||
property var configTimezone: "America/New York"
|
// the default one in Calamares
|
||||||
property var geoipCity: "" //"Amsterdam"
|
property var cityName: ""
|
||||||
property var geoipCountry: "" //"Netherlands"
|
property var countryName: ""
|
||||||
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
|
|
||||||
|
|
||||||
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
|
var xhr = new XMLHttpRequest
|
||||||
|
|
||||||
xhr.onreadystatechange = function() {
|
xhr.onreadystatechange = function() {
|
||||||
@ -51,9 +51,10 @@ Column {
|
|||||||
var ct = responseJSON.city
|
var ct = responseJSON.city
|
||||||
var cy = responseJSON.country
|
var cy = responseJSON.country
|
||||||
|
|
||||||
tzText.text = "Timezone: " + tz
|
|
||||||
cityName = ct
|
cityName = ct
|
||||||
countryName = cy
|
countryName = cy
|
||||||
|
|
||||||
|
config.setCurrentLocation(tz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +64,25 @@ Column {
|
|||||||
xhr.send()
|
xhr.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTz() {
|
/* 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
|
||||||
|
* 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 xhr = new XMLHttpRequest
|
||||||
var latC = map.center.latitude
|
var latC = map.center.latitude
|
||||||
var lonC = map.center.longitude
|
var lonC = map.center.longitude
|
||||||
@ -73,16 +92,29 @@ Column {
|
|||||||
var responseJSON = JSON.parse(xhr.responseText)
|
var responseJSON = JSON.parse(xhr.responseText)
|
||||||
var tz2 = responseJSON.timezoneId
|
var tz2 = responseJSON.timezoneId
|
||||||
|
|
||||||
tzText.text = "Timezone: " + tz2
|
|
||||||
config.setCurrentLocation(tz2)
|
config.setCurrentLocation(tz2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Online lookup", latC, lonC)
|
||||||
// Needs to move to localeq.conf, each distribution will need their own account
|
// 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.open("GET", "http://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME")
|
||||||
xhr.send()
|
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 {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height / 1.28
|
height: parent.height / 1.28
|
||||||
@ -156,9 +188,8 @@ Column {
|
|||||||
map.center.latitude = coordinate.latitude
|
map.center.latitude = coordinate.latitude
|
||||||
map.center.longitude = coordinate.longitude
|
map.center.longitude = coordinate.longitude
|
||||||
|
|
||||||
getTz();
|
// Pick a TZ lookup method here (quick:offline, accurate:online)
|
||||||
|
getTzOffline();
|
||||||
console.log(coordinate.latitude, coordinate.longitude)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,13 +249,16 @@ Column {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: tzText
|
id: tzText
|
||||||
text: tzText.text
|
text: qsTr("Timezone: %1").arg(config.currentTimezoneName)
|
||||||
//text: qsTr("Timezone: %1").arg(timeZone)
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
anchors.centerIn: parent
|
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();
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,14 @@ Page {
|
|||||||
width: 800
|
width: 800
|
||||||
height: 550
|
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 {
|
Loader {
|
||||||
id: image
|
id: image
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
Loading…
Reference in New Issue
Block a user