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/TimeZone.cpp
|
||||
locale/TranslatableConfiguration.cpp
|
||||
locale/TranslatableString.cpp
|
||||
|
||||
# Modules
|
||||
modulesystem/InstanceKey.cpp
|
||||
|
@ -47,7 +47,6 @@ splitTZString( const QString& tz )
|
||||
QStringList tzParts = timezoneString.split( '/', SplitSkipEmptyParts );
|
||||
if ( tzParts.size() >= 2 )
|
||||
{
|
||||
cDebug() << "GeoIP reporting" << timezoneString;
|
||||
QString region = tzParts.takeFirst();
|
||||
QString zone = tzParts.join( '/' );
|
||||
return RegionZonePair( region, zone );
|
||||
|
@ -47,8 +47,12 @@ private Q_SLOTS:
|
||||
void testInterlingue();
|
||||
|
||||
// TimeZone testing
|
||||
void testRegions();
|
||||
void testSimpleZones();
|
||||
void testComplexZones();
|
||||
void testTZLookup();
|
||||
void testTZIterator();
|
||||
void testLocationLookup();
|
||||
};
|
||||
|
||||
LocaleTests::LocaleTests() {}
|
||||
@ -244,58 +248,169 @@ LocaleTests::testTranslatableConfig2()
|
||||
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
|
||||
LocaleTests::testSimpleZones()
|
||||
{
|
||||
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;
|
||||
QVERIFY( r.tr().isEmpty() );
|
||||
}
|
||||
{
|
||||
TZZone n;
|
||||
QVERIFY( n.tr().isEmpty() );
|
||||
}
|
||||
{
|
||||
TZZone r0( "xAmsterdam" );
|
||||
QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) );
|
||||
TZZone r1( r0 );
|
||||
QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) );
|
||||
QCOMPARE( r1.tr(), QStringLiteral( "xAmsterdam" ) );
|
||||
TZZone r2( std::move( r0 ) );
|
||||
QCOMPARE( r2.tr(), QStringLiteral( "xAmsterdam" ) );
|
||||
QCOMPARE( r0.tr(), QString() );
|
||||
}
|
||||
{
|
||||
TZZone r0( nullptr );
|
||||
QVERIFY( r0.tr().isEmpty() );
|
||||
TZZone r1( r0 );
|
||||
QVERIFY( r1.tr().isEmpty() );
|
||||
TZZone r2( std::move( r0 ) );
|
||||
QVERIFY( r2.tr().isEmpty() );
|
||||
for ( const auto& s : names )
|
||||
{
|
||||
if ( s.startsWith( 'N' ) )
|
||||
{
|
||||
cDebug() << s;
|
||||
}
|
||||
}
|
||||
}
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "America" ) );
|
||||
QVERIFY( !names.contains( "New_York" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testComplexZones()
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
ZonesModel zones;
|
||||
RegionalZonesModel europe( &zones );
|
||||
|
||||
{
|
||||
TZZone r0( "America/New_York" );
|
||||
TZZone r1( "America/New York" );
|
||||
QStringList names;
|
||||
displayedNames( zones, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
|
||||
QCOMPARE( r0.tr(), r1.tr() );
|
||||
QCOMPARE( r0.tr(), QStringLiteral( "America/New York" ) );
|
||||
}
|
||||
{
|
||||
TZZone r( "zxc,;*_vm" );
|
||||
QVERIFY( !r.tr().isEmpty() );
|
||||
QCOMPARE( r.tr(), QStringLiteral( "zxc,;* vm" ) ); // Only _ is special
|
||||
}
|
||||
// No region set
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
|
||||
// Now filter
|
||||
europe.setRegion( "Europe" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( !names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( !names.contains( "Abidjan" ) );
|
||||
|
||||
europe.setRegion( "America" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "Prague" ) );
|
||||
QVERIFY( !names.contains( "Abidjan" ) );
|
||||
|
||||
europe.setRegion( "Africa" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( !names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
}
|
||||
|
||||
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 )
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
@ -22,17 +22,28 @@
|
||||
|
||||
#include "TimeZone.h"
|
||||
|
||||
#include "locale/TranslatableString.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/String.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <cstring>
|
||||
#include <QString>
|
||||
|
||||
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
|
||||
getRightGeoLocation( QString str )
|
||||
{
|
||||
@ -62,252 +73,381 @@ getRightGeoLocation( QString str )
|
||||
}
|
||||
|
||||
|
||||
namespace CalamaresUtils
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
|
||||
CStringPair::CStringPair( CStringPair&& t )
|
||||
: m_human( nullptr )
|
||||
, m_key()
|
||||
{
|
||||
// My pointers are initialized to nullptr
|
||||
std::swap( m_human, t.m_human );
|
||||
std::swap( m_key, t.m_key );
|
||||
}
|
||||
|
||||
CStringPair::CStringPair( const CStringPair& t )
|
||||
: m_human( t.m_human ? strdup( t.m_human ) : nullptr )
|
||||
, m_key( t.m_key )
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Massage an identifier into a human-readable form
|
||||
*
|
||||
* Makes a copy of @p s, caller must free() it.
|
||||
*/
|
||||
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
|
||||
TZRegion::tr() const
|
||||
{
|
||||
// NOTE: context name must match what's used in zone-extractor.py
|
||||
return QObject::tr( m_human, "tz_regions" );
|
||||
}
|
||||
|
||||
TZRegion::~TZRegion()
|
||||
{
|
||||
qDeleteAll( m_zones );
|
||||
}
|
||||
|
||||
const CStringPairList&
|
||||
TZRegion::fromZoneTab()
|
||||
{
|
||||
static CStringPairList zoneTab = TZRegion::fromFile( TZ_DATA_FILE );
|
||||
return zoneTab;
|
||||
}
|
||||
|
||||
CStringPairList
|
||||
TZRegion::fromFile( const char* fileName )
|
||||
{
|
||||
CStringPairList model;
|
||||
|
||||
QFile file( fileName );
|
||||
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
|
||||
{
|
||||
return model;
|
||||
}
|
||||
|
||||
TZRegion* thisRegion = nullptr;
|
||||
QTextStream in( &file );
|
||||
while ( !in.atEnd() )
|
||||
{
|
||||
QString line = in.readLine().trimmed().split( '#', SplitKeepEmptyParts ).first().trimmed();
|
||||
if ( line.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList list = line.split( QRegExp( "[\t ]" ), SplitSkipEmptyParts );
|
||||
if ( list.size() < 3 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList timezoneParts = list.at( 2 ).split( '/', SplitSkipEmptyParts );
|
||||
if ( timezoneParts.size() < 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString region = timezoneParts.first().trimmed();
|
||||
if ( region.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto keyMatch = [®ion]( const CStringPair* r ) { return r->key() == region; };
|
||||
auto it = std::find_if( model.begin(), model.end(), keyMatch );
|
||||
if ( it != model.end() )
|
||||
{
|
||||
thisRegion = dynamic_cast< TZRegion* >( *it );
|
||||
}
|
||||
else
|
||||
{
|
||||
thisRegion = new TZRegion( region.toUtf8().data() );
|
||||
model.append( thisRegion );
|
||||
}
|
||||
|
||||
QString countryCode = list.at( 0 ).trimmed();
|
||||
if ( countryCode.size() != 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
timezoneParts.removeFirst();
|
||||
thisRegion->m_zones.append(
|
||||
new TZZone( region, timezoneParts.join( '/' ).toUtf8().constData(), countryCode, list.at( 1 ) ) );
|
||||
}
|
||||
|
||||
auto sorter = []( const CStringPair* l, const CStringPair* r ) { return *l < *r; };
|
||||
std::sort( model.begin(), model.end(), sorter );
|
||||
for ( auto& it : model )
|
||||
{
|
||||
TZRegion* r = dynamic_cast< TZRegion* >( it );
|
||||
if ( r )
|
||||
{
|
||||
std::sort( r->m_zones.begin(), r->m_zones.end(), sorter );
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
TZZone::TZZone( const QString& region, const char* zoneName, const QString& country, QString position )
|
||||
: CStringPair( zoneName )
|
||||
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 )
|
||||
{
|
||||
int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 );
|
||||
if ( cooSplitPos > 0 )
|
||||
{
|
||||
m_latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
|
||||
m_longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
|
||||
}
|
||||
setObjectName( region + '/' + zone );
|
||||
}
|
||||
|
||||
QString
|
||||
TZZone::tr() const
|
||||
TimeZoneData::tr() const
|
||||
{
|
||||
// NOTE: context name must match what's used in zone-extractor.py
|
||||
return QObject::tr( m_human, "tz_names" );
|
||||
}
|
||||
|
||||
|
||||
CStringListModel::CStringListModel( CStringPairList l )
|
||||
: m_list( l )
|
||||
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
|
||||
return QObject::tr( m_human, "tz_regions" );
|
||||
}
|
||||
|
||||
static void
|
||||
loadTZData( RegionVector& regions, ZoneVector& zones )
|
||||
{
|
||||
QFile file( TZ_DATA_FILE );
|
||||
if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
|
||||
{
|
||||
QTextStream in( &file );
|
||||
while ( !in.atEnd() )
|
||||
{
|
||||
QString line = in.readLine().trimmed().split( '#', SplitKeepEmptyParts ).first().trimmed();
|
||||
if ( line.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList list = line.split( QRegExp( "[\t ]" ), SplitSkipEmptyParts );
|
||||
if ( list.size() < 3 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList timezoneParts = list.at( 2 ).split( '/', SplitSkipEmptyParts );
|
||||
if ( timezoneParts.size() < 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString region = timezoneParts.first().trimmed();
|
||||
if ( region.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString countryCode = list.at( 0 ).trimmed();
|
||||
if ( countryCode.size() != 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
timezoneParts.removeFirst();
|
||||
QString zone = timezoneParts.join( '/' );
|
||||
if ( zone.length() < 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString position = list.at( 1 );
|
||||
int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 );
|
||||
double latitude;
|
||||
double longitude;
|
||||
if ( cooSplitPos > 0 )
|
||||
{
|
||||
latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
|
||||
longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we have region, zone, country, lat and longitude
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Private : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
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 )
|
||||
: QAbstractListModel( parent )
|
||||
, m_private( privateInstance() )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
CStringListModel::setList( CalamaresUtils::Locale::CStringPairList l )
|
||||
{
|
||||
beginResetModel();
|
||||
m_list = l;
|
||||
endResetModel();
|
||||
}
|
||||
RegionsModel::~RegionsModel() {}
|
||||
|
||||
int
|
||||
CStringListModel::rowCount( const QModelIndex& ) const
|
||||
RegionsModel::rowCount( const QModelIndex& ) const
|
||||
{
|
||||
return m_list.count();
|
||||
return m_private->m_regions.count();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if ( !index.isValid() )
|
||||
const auto& region = m_private->m_regions[ index.row() ];
|
||||
if ( role == NameRole )
|
||||
{
|
||||
return QVariant();
|
||||
return region->tr();
|
||||
}
|
||||
|
||||
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() ) )
|
||||
if ( role == KeyRole )
|
||||
{
|
||||
return;
|
||||
return region->key();
|
||||
}
|
||||
|
||||
m_currentIndex = index;
|
||||
emit currentIndexChanged();
|
||||
}
|
||||
|
||||
int
|
||||
CStringListModel::currentIndex() const
|
||||
{
|
||||
return m_currentIndex;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash< int, QByteArray >
|
||||
CStringListModel::roleNames() const
|
||||
RegionsModel::roleNames() const
|
||||
{
|
||||
return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } };
|
||||
return { { NameRole, "name" }, { KeyRole, "key" } };
|
||||
}
|
||||
|
||||
const CStringPair*
|
||||
CStringListModel::item( int index ) const
|
||||
QString
|
||||
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 CalamaresUtils
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
||||
#include "TimeZone.moc"
|
||||
|
@ -24,184 +24,198 @@
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "locale/TranslatableString.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
|
||||
namespace CalamaresUtils
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
class Private;
|
||||
class RegionalZonesModel;
|
||||
class ZonesModel;
|
||||
|
||||
/** @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 there is API that needs
|
||||
* C-style strings.
|
||||
*/
|
||||
class CStringPair : public QObject
|
||||
class TimeZoneData : public QObject, TranslatableString
|
||||
{
|
||||
friend class RegionalZonesModel;
|
||||
friend class ZonesModel;
|
||||
|
||||
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:
|
||||
/// @brief An empty pair
|
||||
CStringPair() {}
|
||||
/// @brief Given an identifier, create the pair
|
||||
explicit CStringPair( const char* s1 );
|
||||
CStringPair( CStringPair&& t );
|
||||
CStringPair( const CStringPair& );
|
||||
virtual ~CStringPair();
|
||||
TimeZoneData( const QString& region,
|
||||
const QString& zone,
|
||||
const QString& country,
|
||||
double latitude,
|
||||
double longitude );
|
||||
TimeZoneData( const TimeZoneData& ) = delete;
|
||||
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 region() const { return key(); }
|
||||
|
||||
/** @brief Create list from a zone.tab-like file
|
||||
*
|
||||
* Returns a list of all the regions; each region has a list
|
||||
* of zones within that region. Dyamically, the items in the
|
||||
* returned list are TZRegions; their zones dynamically are
|
||||
* TZZones even though all those lists have type CStringPairList.
|
||||
*
|
||||
* The list owns the regions, and the regions own their own list of zones.
|
||||
* When getting rid of the list, remember to qDeleteAll() on it.
|
||||
*/
|
||||
static CStringPairList fromFile( const char* fileName );
|
||||
/// @brief Calls fromFile with the standard zone.tab name
|
||||
static const CStringPairList& fromZoneTab();
|
||||
|
||||
const CStringPairList& zones() const { return m_zones; }
|
||||
|
||||
private:
|
||||
CStringPairList m_zones;
|
||||
};
|
||||
|
||||
/** @brief Specific timezone zones (e.g. "New_York", "New York")
|
||||
*
|
||||
* A timezone zone lives in a region, and has some associated
|
||||
* data like the country (used to map likely languages) and latitude
|
||||
* and longitude information.
|
||||
*/
|
||||
class TZZone : public CStringPair
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using CStringPair::CStringPair;
|
||||
QString tr() const override;
|
||||
|
||||
TZZone( const QString& region, const char* zoneName, const QString& country, QString position );
|
||||
|
||||
QString region() const { return m_region; }
|
||||
QString zone() const { return key(); }
|
||||
|
||||
QString country() const { return m_country; }
|
||||
double latitude() const { return m_latitude; }
|
||||
double longitude() const { return m_longitude; }
|
||||
|
||||
protected:
|
||||
private:
|
||||
QString m_region;
|
||||
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_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged )
|
||||
|
||||
public:
|
||||
/// @brief Create empty model
|
||||
CStringListModel() {}
|
||||
/// @brief Create model from list (non-owning)
|
||||
CStringListModel( CStringPairList );
|
||||
enum Roles
|
||||
{
|
||||
NameRole = Qt::DisplayRole,
|
||||
KeyRole = Qt::UserRole // So that currentData() will get the key
|
||||
};
|
||||
|
||||
RegionsModel( QObject* parent = nullptr );
|
||||
virtual ~RegionsModel() override;
|
||||
|
||||
int rowCount( const QModelIndex& parent ) const override;
|
||||
|
||||
QVariant data( const QModelIndex& index, int role ) const override;
|
||||
|
||||
const CStringPair* item( int index ) const;
|
||||
QHash< int, QByteArray > roleNames() const override;
|
||||
|
||||
void setCurrentIndex( int index );
|
||||
int currentIndex() const;
|
||||
|
||||
void setList( CStringPairList );
|
||||
|
||||
inline int indexOf( const QString& key )
|
||||
{
|
||||
const auto it = std::find_if(
|
||||
m_list.constBegin(), m_list.constEnd(), [&]( const CalamaresUtils::Locale::CStringPair* item ) -> bool {
|
||||
return item->key() == key;
|
||||
} );
|
||||
|
||||
if ( it != m_list.constEnd() )
|
||||
{
|
||||
// distance() is usually a long long
|
||||
return int( std::distance( m_list.constBegin(), it ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Provides a human-readable version of the region
|
||||
*
|
||||
* Returns @p region unchanged if there is no such region
|
||||
* or no translation for the region's name.
|
||||
*/
|
||||
QString tr( const QString& region ) const;
|
||||
|
||||
private:
|
||||
CStringPairList m_list;
|
||||
int m_currentIndex = -1;
|
||||
Private* m_private;
|
||||
};
|
||||
|
||||
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:
|
||||
void currentIndexChanged();
|
||||
void regionChanged( const QString& );
|
||||
|
||||
private:
|
||||
Private* m_private;
|
||||
QString m_region;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Locale
|
||||
} // 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
|
||||
SOURCES
|
||||
Tests.cpp
|
||||
Config.cpp
|
||||
LocaleConfiguration.cpp
|
||||
SetTimezoneJob.cpp
|
||||
timezonewidget/TimeZoneImage.cpp
|
||||
DEFINITIONS
|
||||
SOURCE_DIR="${CMAKE_CURRENT_LIST_DIR}/images"
|
||||
|
@ -148,17 +148,50 @@ loadLocales( const QString& localeGenPath )
|
||||
return localeGenLines;
|
||||
}
|
||||
|
||||
static inline const CalamaresUtils::Locale::CStringPairList&
|
||||
timezoneData()
|
||||
static bool
|
||||
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 )
|
||||
: QObject( parent )
|
||||
, m_regionModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >( ::timezoneData() ) )
|
||||
, m_zonesModel( std::make_unique< CalamaresUtils::Locale::CStringListModel >() )
|
||||
, m_regionModel( std::make_unique< CalamaresUtils::Locale::RegionsModel >() )
|
||||
, m_zonesModel( std::make_unique< CalamaresUtils::Locale::ZonesModel >() )
|
||||
, m_regionalZonesModel( std::make_unique< CalamaresUtils::Locale::RegionalZonesModel >( m_zonesModel.get() ) )
|
||||
{
|
||||
// Slightly unusual: connect to our *own* signals. Wherever the language
|
||||
// or the location is changed, these signals are emitted, so hook up to
|
||||
@ -172,32 +205,21 @@ Config::Config( QObject* parent )
|
||||
} );
|
||||
|
||||
connect( this, &Config::currentLCCodeChanged, [&]() {
|
||||
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
// Update GS localeConf (the LC_ variables)
|
||||
auto map = localeConfiguration().toMap();
|
||||
QVariantMap vm;
|
||||
for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
|
||||
{
|
||||
vm.insert( it.key(), it.value() );
|
||||
}
|
||||
gs->insert( "localeConf", vm );
|
||||
updateGSLocale( Calamares::JobQueue::instance()->globalStorage(), localeConfiguration() );
|
||||
} );
|
||||
|
||||
connect( this, &Config::currentLocationChanged, [&]() {
|
||||
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
const bool locationChanged
|
||||
= updateGSLocation( Calamares::JobQueue::instance()->globalStorage(), currentLocation() );
|
||||
|
||||
// Update the GS region and zone (and possibly the live timezone)
|
||||
const auto* location = currentLocation();
|
||||
bool locationChanged = ( location->region() != gs->value( "locationRegion" ) )
|
||||
|| ( location->zone() != gs->value( "locationZone" ) );
|
||||
|
||||
gs->insert( "locationRegion", location->region() );
|
||||
gs->insert( "locationZone", location->zone() );
|
||||
if ( locationChanged && m_adjustLiveTimezone )
|
||||
{
|
||||
QProcess::execute( "timedatectl", // depends on systemd
|
||||
{ "set-timezone", location->region() + '/' + location->zone() } );
|
||||
{ "set-timezone", currentTimezoneCode() } );
|
||||
}
|
||||
|
||||
emit currentTimezoneCodeChanged( currentTimezoneCode() );
|
||||
emit currentTimezoneNameChanged( currentTimezoneName() );
|
||||
} );
|
||||
|
||||
auto prettyStatusNotify = [&]() { emit prettyStatusChanged( prettyStatus() ); };
|
||||
@ -208,12 +230,6 @@ Config::Config( QObject* parent )
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
const CalamaresUtils::Locale::CStringPairList&
|
||||
Config::timezoneData() const
|
||||
{
|
||||
return ::timezoneData();
|
||||
}
|
||||
|
||||
void
|
||||
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 );
|
||||
if ( r.isValid() )
|
||||
@ -236,8 +253,7 @@ void
|
||||
Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
auto* region = timezoneData().find< TZRegion >( regionName );
|
||||
auto* zone = region ? region->zones().find< TZZone >( zoneName ) : nullptr;
|
||||
auto* zone = m_zonesModel->find( regionName, zoneName );
|
||||
if ( zone )
|
||||
{
|
||||
setCurrentLocation( zone );
|
||||
@ -250,7 +266,7 @@ Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
|
||||
}
|
||||
|
||||
void
|
||||
Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
||||
Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location )
|
||||
{
|
||||
if ( location != m_currentLocation )
|
||||
{
|
||||
@ -277,6 +293,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
||||
emit currentLCStatusChanged( currentLCStatus() );
|
||||
}
|
||||
emit currentLocationChanged( m_currentLocation );
|
||||
// Other signals come from the LocationChanged signal
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,9 +347,32 @@ Config::setLCLocaleExplicitly( const QString& locale )
|
||||
QString
|
||||
Config::currentLocationStatus() const
|
||||
{
|
||||
return tr( "Set timezone to %1/%2." ).arg( m_currentLocation->region(), m_currentLocation->zone() );
|
||||
return tr( "Set timezone to %1/%2." )
|
||||
.arg( m_currentLocation ? m_currentLocation->region() : QString(),
|
||||
m_currentLocation ? m_currentLocation->zone() : QString() );
|
||||
}
|
||||
|
||||
QString
|
||||
Config::currentTimezoneCode() const
|
||||
{
|
||||
if ( m_currentLocation )
|
||||
{
|
||||
return m_currentLocation->region() + '/' + m_currentLocation->zone();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString
|
||||
Config::currentTimezoneName() const
|
||||
{
|
||||
if ( m_currentLocation )
|
||||
{
|
||||
return m_regionModel->tr( m_currentLocation->region() ) + '/' + m_currentLocation->tr();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
static inline QString
|
||||
localeLabel( const QString& s )
|
||||
{
|
||||
@ -380,7 +420,7 @@ getAdjustLiveTimezone( const QVariantMap& configurationMap, bool& adjustLiveTime
|
||||
adjustLiveTimezone = CalamaresUtils::getBool(
|
||||
configurationMap, "adjustLiveTimezone", Calamares::Settings::instance()->doChroot() );
|
||||
#ifdef DEBUG_TIMEZONES
|
||||
if ( m_adjustLiveTimezone )
|
||||
if ( adjustLiveTimezone )
|
||||
{
|
||||
cWarning() << "Turning off live-timezone adjustments because debugging is on.";
|
||||
adjustLiveTimezone = false;
|
||||
@ -448,18 +488,20 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
getStartingTimezone( configurationMap, m_startingTimezone );
|
||||
getGeoIP( configurationMap, m_geoip );
|
||||
|
||||
#ifndef BUILD_AS_TEST
|
||||
if ( m_geoip && m_geoip->isValid() )
|
||||
{
|
||||
connect(
|
||||
Calamares::ModuleManager::instance(), &Calamares::ModuleManager::modulesLoaded, this, &Config::startGeoIP );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Calamares::JobList
|
||||
Config::createJobs()
|
||||
{
|
||||
Calamares::JobList list;
|
||||
const CalamaresUtils::Locale::TZZone* location = currentLocation();
|
||||
const auto* location = currentLocation();
|
||||
|
||||
if ( location )
|
||||
{
|
||||
@ -470,6 +512,15 @@ Config::createJobs()
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
Config::finalizeGlobalStorage() const
|
||||
{
|
||||
auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
updateGSLocale( gs, localeConfiguration() );
|
||||
updateGSLocation( gs, currentLocation() );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Config::startGeoIP()
|
||||
{
|
||||
|
@ -37,18 +37,25 @@ class Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( const QStringList& supportedLocales READ supportedLocales CONSTANT FINAL )
|
||||
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* zonesModel READ zonesModel CONSTANT FINAL )
|
||||
Q_PROPERTY( CalamaresUtils::Locale::CStringListModel* regionModel READ regionModel CONSTANT FINAL )
|
||||
Q_PROPERTY( CalamaresUtils::Locale::RegionsModel* regionModel READ regionModel CONSTANT FINAL )
|
||||
Q_PROPERTY( CalamaresUtils::Locale::ZonesModel* zonesModel READ zonesModel CONSTANT FINAL )
|
||||
Q_PROPERTY( QAbstractItemModel* regionalZonesModel READ regionalZonesModel CONSTANT FINAL )
|
||||
|
||||
Q_PROPERTY( const CalamaresUtils::Locale::TZZone* currentLocation READ currentLocation WRITE setCurrentLocation
|
||||
NOTIFY currentLocationChanged )
|
||||
Q_PROPERTY(
|
||||
CalamaresUtils::Locale::TimeZoneData* currentLocation READ currentLocation_c NOTIFY currentLocationChanged )
|
||||
|
||||
// Status are complete, human-readable, messages
|
||||
Q_PROPERTY( QString currentLocationStatus READ currentLocationStatus NOTIFY currentLanguageStatusChanged )
|
||||
Q_PROPERTY( QString currentLanguageStatus READ currentLanguageStatus NOTIFY currentLanguageStatusChanged )
|
||||
Q_PROPERTY( QString currentLCStatus READ currentLCStatus NOTIFY currentLCStatusChanged )
|
||||
// Name are shorter human-readable names
|
||||
// .. main difference is that status is a full sentence, like "Timezone is America/New York"
|
||||
// while name is just "America/New York" (and the code, below, is "America/New_York")
|
||||
Q_PROPERTY( QString currentTimezoneName READ currentTimezoneName NOTIFY currentTimezoneNameChanged )
|
||||
// Code are internal identifiers, like "en_US.UTF-8"
|
||||
Q_PROPERTY( QString 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 )
|
||||
|
||||
// This is a long human-readable string with all three statuses
|
||||
@ -59,17 +66,9 @@ public:
|
||||
~Config();
|
||||
|
||||
void setConfigurationMap( const QVariantMap& );
|
||||
void finalizeGlobalStorage() const;
|
||||
Calamares::JobList createJobs();
|
||||
|
||||
// Underlying data for the models
|
||||
const CalamaresUtils::Locale::CStringPairList& timezoneData() const;
|
||||
|
||||
/** @brief The currently selected location (timezone)
|
||||
*
|
||||
* The location is a pointer into the date that timezoneData() returns.
|
||||
*/
|
||||
const CalamaresUtils::Locale::TZZone* currentLocation() const { return m_currentLocation; }
|
||||
|
||||
/// locale configuration (LC_* and LANG) based solely on the current location.
|
||||
LocaleConfiguration automaticLocaleConfiguration() const;
|
||||
/// locale configuration that takes explicit settings into account
|
||||
@ -85,13 +84,27 @@ public:
|
||||
/// The human-readable summary of what the module will do
|
||||
QString prettyStatus() const;
|
||||
|
||||
// A long list of locale codes (e.g. en_US.UTF-8)
|
||||
const QStringList& supportedLocales() const { return m_localeGenLines; }
|
||||
CalamaresUtils::Locale::CStringListModel* regionModel() const { return m_regionModel.get(); }
|
||||
CalamaresUtils::Locale::CStringListModel* zonesModel() const { return m_zonesModel.get(); }
|
||||
// All the regions (Africa, America, ...)
|
||||
CalamaresUtils::Locale::RegionsModel* regionModel() const { return m_regionModel.get(); }
|
||||
// All of the timezones in the world, according to zone.tab
|
||||
CalamaresUtils::Locale::ZonesModel* zonesModel() const { return m_zonesModel.get(); }
|
||||
// This model can be filtered by region
|
||||
CalamaresUtils::Locale::RegionalZonesModel* regionalZonesModel() const { return m_regionalZonesModel.get(); }
|
||||
|
||||
const CalamaresUtils::Locale::TimeZoneData* currentLocation() const { return m_currentLocation; }
|
||||
|
||||
|
||||
/// Special case, set location from starting timezone if not already set
|
||||
void setCurrentLocation();
|
||||
|
||||
private:
|
||||
CalamaresUtils::Locale::TimeZoneData* currentLocation_c() const
|
||||
{
|
||||
return const_cast< CalamaresUtils::Locale::TimeZoneData* >( m_currentLocation );
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
/// Set a language by user-choice, overriding future location changes
|
||||
void setLanguageExplicitly( const QString& language );
|
||||
@ -111,38 +124,38 @@ public Q_SLOTS:
|
||||
* names a zone within that region.
|
||||
*/
|
||||
void setCurrentLocation( const QString& region, const QString& zone );
|
||||
/** @brief Sets a location by pointer
|
||||
|
||||
/** @brief Sets a location by pointer to zone data.
|
||||
*
|
||||
* Pointer should be within the same model as the widget uses.
|
||||
* This can update the locale configuration -- the automatic one
|
||||
* follows the current location, and otherwise only explicitly-set
|
||||
* values will ignore changes to the location.
|
||||
*/
|
||||
void setCurrentLocation( const CalamaresUtils::Locale::TZZone* location );
|
||||
void setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* tz );
|
||||
|
||||
QString currentLanguageCode() const { return localeConfiguration().language(); }
|
||||
QString currentLCCode() const { return localeConfiguration().lc_numeric; }
|
||||
QString currentTimezoneName() const; // human-readable
|
||||
QString currentTimezoneCode() const;
|
||||
|
||||
signals:
|
||||
void currentLocationChanged( const CalamaresUtils::Locale::TZZone* location ) const;
|
||||
void currentLocationChanged( const CalamaresUtils::Locale::TimeZoneData* location ) const;
|
||||
void currentLocationStatusChanged( const QString& ) const;
|
||||
void currentLanguageStatusChanged( const QString& ) const;
|
||||
void currentLCStatusChanged( const QString& ) const;
|
||||
void prettyStatusChanged( const QString& ) const;
|
||||
void currentLanguageCodeChanged( const QString& ) const;
|
||||
void currentLCCodeChanged( const QString& ) const;
|
||||
void currentTimezoneCodeChanged( const QString& ) const;
|
||||
void currentTimezoneNameChanged( const QString& ) const;
|
||||
|
||||
private:
|
||||
/// A list of supported locale identifiers (e.g. "en_US.UTF-8")
|
||||
QStringList m_localeGenLines;
|
||||
|
||||
/// The regions (America, Asia, Europe ..)
|
||||
std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_regionModel;
|
||||
/// The zones for the current region (e.g. America/New_York)
|
||||
std::unique_ptr< CalamaresUtils::Locale::CStringListModel > m_zonesModel;
|
||||
std::unique_ptr< CalamaresUtils::Locale::RegionsModel > m_regionModel;
|
||||
std::unique_ptr< CalamaresUtils::Locale::ZonesModel > m_zonesModel;
|
||||
std::unique_ptr< CalamaresUtils::Locale::RegionalZonesModel > m_regionalZonesModel;
|
||||
|
||||
/// The location, points into the timezone data
|
||||
const CalamaresUtils::Locale::TZZone* m_currentLocation = nullptr;
|
||||
const CalamaresUtils::Locale::TimeZoneData* m_currentLocation = nullptr;
|
||||
|
||||
/** @brief Specific locale configurations
|
||||
*
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* === This file is part of Calamares - <https://github.com/calamares> ===
|
||||
*
|
||||
* Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
|
||||
* Copyright 2017-2019, Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac <teo@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
|
||||
* 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* tzwLayout = new QHBoxLayout;
|
||||
m_tzWidget = new TimeZoneWidget( config->timezoneData(), this );
|
||||
m_tzWidget = new TimeZoneWidget( m_config->zonesModel(), this );
|
||||
tzwLayout->addStretch();
|
||||
tzwLayout->addWidget( m_tzWidget );
|
||||
tzwLayout->addStretch();
|
||||
@ -101,6 +102,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent )
|
||||
// Set up the location before connecting signals, to avoid a signal
|
||||
// storm as various parts interact.
|
||||
m_regionCombo->setModel( m_config->regionModel() );
|
||||
m_zoneCombo->setModel( m_config->regionalZonesModel() );
|
||||
locationChanged( m_config->currentLocation() ); // doesn't inform TZ widget
|
||||
m_tzWidget->setCurrentLocation( m_config->currentLocation() );
|
||||
|
||||
@ -111,7 +113,7 @@ LocalePage::LocalePage( Config* config, QWidget* parent )
|
||||
connect( m_tzWidget,
|
||||
&TimeZoneWidget::locationChanged,
|
||||
config,
|
||||
QOverload< const CalamaresUtils::Locale::TZZone* >::of( &Config::setCurrentLocation ) );
|
||||
QOverload< const CalamaresUtils::Locale::TimeZoneData* >::of( &Config::setCurrentLocation ) );
|
||||
|
||||
connect( m_regionCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::regionChanged );
|
||||
connect( m_zoneCombo, QOverload< int >::of( &QComboBox::currentIndexChanged ), this, &LocalePage::zoneChanged );
|
||||
@ -151,35 +153,26 @@ LocalePage::regionChanged( int currentIndex )
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
|
||||
Q_UNUSED( currentIndex )
|
||||
QString selectedRegion = m_regionCombo->currentData().toString();
|
||||
|
||||
TZRegion* region = m_config->timezoneData().find< TZRegion >( selectedRegion );
|
||||
if ( !region )
|
||||
QString selectedRegion = m_regionCombo->itemData( currentIndex ).toString();
|
||||
{
|
||||
return;
|
||||
cSignalBlocker z( m_zoneCombo );
|
||||
m_config->regionalZonesModel()->setRegion( selectedRegion );
|
||||
}
|
||||
|
||||
{
|
||||
cSignalBlocker b( m_zoneCombo );
|
||||
m_zoneCombo->setModel( new CStringListModel( region->zones() ) );
|
||||
}
|
||||
|
||||
m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() );
|
||||
m_zoneCombo->currentIndexChanged( 0 );
|
||||
}
|
||||
|
||||
void
|
||||
LocalePage::zoneChanged( int currentIndex )
|
||||
{
|
||||
Q_UNUSED( currentIndex )
|
||||
if ( !m_blockTzWidgetSet )
|
||||
{
|
||||
m_config->setCurrentLocation( m_regionCombo->currentData().toString(), m_zoneCombo->currentData().toString() );
|
||||
m_config->setCurrentLocation( m_regionCombo->currentData().toString(),
|
||||
m_zoneCombo->itemData( currentIndex ).toString() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocalePage::locationChanged( const CalamaresUtils::Locale::TZZone* location )
|
||||
LocalePage::locationChanged( const CalamaresUtils::Locale::TimeZoneData* location )
|
||||
{
|
||||
if ( !location )
|
||||
{
|
||||
|
@ -53,7 +53,7 @@ private:
|
||||
|
||||
void regionChanged( int currentIndex );
|
||||
void zoneChanged( int currentIndex );
|
||||
void locationChanged( const CalamaresUtils::Locale::TZZone* location );
|
||||
void locationChanged( const CalamaresUtils::Locale::TimeZoneData* location );
|
||||
void changeLocale();
|
||||
void changeFormats();
|
||||
|
||||
|
@ -138,6 +138,7 @@ LocaleViewStep::jobs() const
|
||||
void
|
||||
LocaleViewStep::onActivate()
|
||||
{
|
||||
m_config->setCurrentLocation(); // Finalize the location
|
||||
if ( !m_actualWidget )
|
||||
{
|
||||
setUpPage();
|
||||
@ -149,6 +150,7 @@ LocaleViewStep::onActivate()
|
||||
void
|
||||
LocaleViewStep::onLeave()
|
||||
{
|
||||
m_config->finalizeGlobalStorage();
|
||||
}
|
||||
|
||||
|
||||
|
@ -16,17 +16,40 @@
|
||||
* along with Calamares. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "Tests.h"
|
||||
#include "Config.h"
|
||||
#include "LocaleConfiguration.h"
|
||||
#include "timezonewidget/TimeZoneImage.h"
|
||||
|
||||
#include "locale/TimeZone.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#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 )
|
||||
|
||||
|
||||
@ -115,37 +138,35 @@ LocaleTests::testTZImages()
|
||||
//
|
||||
//
|
||||
using namespace CalamaresUtils::Locale;
|
||||
const CStringPairList& regions = TZRegion::fromZoneTab();
|
||||
const ZonesModel m;
|
||||
|
||||
int overlapcount = 0;
|
||||
for ( const auto* pr : regions )
|
||||
for ( auto it = m.begin(); it; ++it )
|
||||
{
|
||||
const TZRegion* region = dynamic_cast< const TZRegion* >( pr );
|
||||
QVERIFY( region );
|
||||
QString region = m.data( m.index( it.index() ), ZonesModel::RegionRole ).toString();
|
||||
QString zoneName = m.data( m.index( it.index() ), ZonesModel::KeyRole ).toString();
|
||||
QVERIFY( !region.isEmpty() );
|
||||
QVERIFY( !zoneName.isEmpty() );
|
||||
const auto* zone = m.find( region, zoneName );
|
||||
const auto* iterzone = *it;
|
||||
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
cDebug() << "Region" << region->region() << "zones #" << region->zones().count();
|
||||
Logger::setupLogLevel( Logger::LOGERROR );
|
||||
QVERIFY( iterzone );
|
||||
QVERIFY( zone );
|
||||
QCOMPARE( zone, iterzone );
|
||||
QCOMPARE( zone->zone(), zoneName );
|
||||
QCOMPARE( zone->region(), region );
|
||||
|
||||
const auto zones = region->zones();
|
||||
QVERIFY( zones.count() > 0 );
|
||||
for ( const auto* pz : zones )
|
||||
int overlap = 0;
|
||||
auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
QVERIFY( images.index( pos, overlap ) >= 0 );
|
||||
QVERIFY( overlap > 0 ); // At least one image contains the spot
|
||||
if ( overlap > 1 )
|
||||
{
|
||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
||||
QVERIFY( zone );
|
||||
|
||||
int overlap = 0;
|
||||
auto pos = images.getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
QVERIFY( images.index( pos, overlap ) >= 0 );
|
||||
QVERIFY( overlap > 0 ); // At least one image contains the spot
|
||||
if ( overlap > 1 )
|
||||
{
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
cDebug() << Logger::SubEntry << "Zone" << zone->zone() << pos;
|
||||
(void)images.index( pos, overlap );
|
||||
Logger::setupLogLevel( Logger::LOGERROR );
|
||||
overlapcount++;
|
||||
}
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
cDebug() << Logger::SubEntry << "Zone" << zone->zone() << pos;
|
||||
(void)images.index( pos, overlap );
|
||||
Logger::setupLogLevel( Logger::LOGERROR );
|
||||
overlapcount++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,12 +189,17 @@ operator<( const QPoint& l, const QPoint& r )
|
||||
}
|
||||
|
||||
void
|
||||
listAll( const QPoint& p, const CalamaresUtils::Locale::CStringPairList& zones )
|
||||
listAll( const QPoint& p, const CalamaresUtils::Locale::ZonesModel& zones )
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
for ( const auto* pz : zones )
|
||||
for ( auto it = zones.begin(); it; ++it )
|
||||
{
|
||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
||||
const auto* zone = *it;
|
||||
if ( !zone )
|
||||
{
|
||||
cError() << Logger::SubEntry << "NULL zone";
|
||||
return;
|
||||
}
|
||||
if ( p == TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() ) )
|
||||
{
|
||||
cError() << Logger::SubEntry << zone->zone();
|
||||
@ -185,78 +211,37 @@ void
|
||||
LocaleTests::testTZLocations()
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
const CStringPairList& regions = TZRegion::fromZoneTab();
|
||||
ZonesModel zones;
|
||||
|
||||
QVERIFY( zones.rowCount( QModelIndex() ) > 100 );
|
||||
|
||||
int overlapcount = 0;
|
||||
for ( const auto* pr : regions )
|
||||
std::set< QPoint > occupied;
|
||||
for ( auto it = zones.begin(); it; ++it )
|
||||
{
|
||||
const TZRegion* region = dynamic_cast< const TZRegion* >( pr );
|
||||
QVERIFY( region );
|
||||
const auto* zone = *it;
|
||||
QVERIFY( zone );
|
||||
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
cDebug() << "Region" << region->region() << "zones #" << region->zones().count();
|
||||
Logger::setupLogLevel( Logger::LOGERROR );
|
||||
|
||||
std::set< QPoint > occupied;
|
||||
|
||||
const auto zones = region->zones();
|
||||
QVERIFY( zones.count() > 0 );
|
||||
for ( const auto* pz : zones )
|
||||
auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
if ( occupied.find( pos ) != occupied.end() )
|
||||
{
|
||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
||||
QVERIFY( zone );
|
||||
|
||||
auto pos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
if ( occupied.find( pos ) != occupied.end() )
|
||||
{
|
||||
cError() << "Zone" << zone->zone() << "occupies same spot as ..";
|
||||
listAll( pos, zones );
|
||||
overlapcount++;
|
||||
}
|
||||
occupied.insert( pos );
|
||||
cError() << "Zone" << zone->zone() << "occupies same spot as ..";
|
||||
listAll( pos, zones );
|
||||
overlapcount++;
|
||||
}
|
||||
occupied.insert( pos );
|
||||
}
|
||||
|
||||
QEXPECT_FAIL( "", "TZ Images contain pixel-overlaps", Continue );
|
||||
QCOMPARE( overlapcount, 0 );
|
||||
}
|
||||
|
||||
const CalamaresUtils::Locale::TZZone*
|
||||
findZone( const QString& name )
|
||||
{
|
||||
using namespace CalamaresUtils::Locale;
|
||||
const CStringPairList& regions = TZRegion::fromZoneTab();
|
||||
|
||||
for ( const auto* pr : regions )
|
||||
{
|
||||
const TZRegion* region = dynamic_cast< const TZRegion* >( pr );
|
||||
if ( !region )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto zones = region->zones();
|
||||
for ( const auto* pz : zones )
|
||||
{
|
||||
const TZZone* zone = dynamic_cast< const TZZone* >( pz );
|
||||
if ( !zone )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( zone->zone() == name )
|
||||
{
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testSpecificLocations()
|
||||
{
|
||||
const auto* gibraltar = findZone( "Gibraltar" );
|
||||
const auto* ceuta = findZone( "Ceuta" );
|
||||
CalamaresUtils::Locale::ZonesModel zones;
|
||||
const auto* gibraltar = zones.find( "Europe", "Gibraltar" );
|
||||
const auto* ceuta = zones.find( "Africa", "Ceuta" );
|
||||
QVERIFY( gibraltar );
|
||||
QVERIFY( ceuta );
|
||||
|
||||
@ -268,3 +253,17 @@ LocaleTests::testSpecificLocations()
|
||||
QEXPECT_FAIL( "", "Gibraltar and Ceuta are really close", Continue );
|
||||
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
|
||||
|
||||
static QPoint
|
||||
getLocationPosition( const CalamaresUtils::Locale::TZZone* l )
|
||||
getLocationPosition( const CalamaresUtils::Locale::TimeZoneData* l )
|
||||
{
|
||||
return TimeZoneImageList::getLocationPosition( l->longitude(), l->latitude() );
|
||||
}
|
||||
|
||||
|
||||
TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent )
|
||||
TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::ZonesModel* zones, QWidget* parent )
|
||||
: QWidget( parent )
|
||||
, timeZoneImages( TimeZoneImageList::fromQRC() )
|
||||
, m_zonesData( zones )
|
||||
@ -65,7 +65,7 @@ TimeZoneWidget::TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& z
|
||||
|
||||
|
||||
void
|
||||
TimeZoneWidget::setCurrentLocation( const CalamaresUtils::Locale::TZZone* location )
|
||||
TimeZoneWidget::setCurrentLocation( const TimeZoneData* location )
|
||||
{
|
||||
if ( location == m_currentLocation )
|
||||
{
|
||||
@ -190,32 +190,24 @@ TimeZoneWidget::mousePressEvent( QMouseEvent* event )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set nearest location
|
||||
int nX = 999999, mX = event->pos().x();
|
||||
int nY = 999999, mY = event->pos().y();
|
||||
|
||||
using namespace CalamaresUtils::Locale;
|
||||
const TZZone* closest = nullptr;
|
||||
for ( const auto* region_p : m_zonesData )
|
||||
const TimeZoneData* closest = nullptr;
|
||||
for ( auto it = m_zonesData->begin(); it; ++it )
|
||||
{
|
||||
const auto* region = dynamic_cast< const TZRegion* >( region_p );
|
||||
if ( region )
|
||||
const auto* zone = *it;
|
||||
if ( zone )
|
||||
{
|
||||
for ( const auto* zone_p : region->zones() )
|
||||
{
|
||||
const auto* zone = dynamic_cast< const TZZone* >( zone_p );
|
||||
if ( zone )
|
||||
{
|
||||
QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
QPoint locPos = TimeZoneImageList::getLocationPosition( zone->longitude(), zone->latitude() );
|
||||
|
||||
if ( ( abs( mX - locPos.x() ) + abs( mY - locPos.y() ) < abs( mX - nX ) + abs( mY - nY ) ) )
|
||||
{
|
||||
closest = zone;
|
||||
nX = locPos.x();
|
||||
nY = locPos.y();
|
||||
}
|
||||
}
|
||||
if ( ( abs( mX - locPos.x() ) + abs( mY - locPos.y() ) < abs( mX - nX ) + abs( mY - nY ) ) )
|
||||
{
|
||||
closest = zone;
|
||||
nX = locPos.x();
|
||||
nY = locPos.y();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,28 +51,28 @@ class TimeZoneWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using TZZone = CalamaresUtils::Locale::TZZone;
|
||||
using TimeZoneData = CalamaresUtils::Locale::TimeZoneData;
|
||||
|
||||
explicit TimeZoneWidget( const CalamaresUtils::Locale::CStringPairList& zones, QWidget* parent = nullptr );
|
||||
explicit TimeZoneWidget( const CalamaresUtils::Locale::ZonesModel* zones, QWidget* parent = nullptr );
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Sets a location by pointer
|
||||
*
|
||||
* Pointer should be within the same model as the widget uses.
|
||||
*/
|
||||
void setCurrentLocation( const TZZone* location );
|
||||
void setCurrentLocation( const TimeZoneData* location );
|
||||
|
||||
signals:
|
||||
/** @brief The location has changed by mouse click */
|
||||
void locationChanged( const TZZone* location );
|
||||
void locationChanged( const TimeZoneData* location );
|
||||
|
||||
private:
|
||||
QFont font;
|
||||
QImage background, pin, currentZoneImage;
|
||||
TimeZoneImageList timeZoneImages;
|
||||
|
||||
const CalamaresUtils::Locale::CStringPairList& m_zonesData;
|
||||
const TZZone* m_currentLocation = nullptr; // Not owned by me
|
||||
const CalamaresUtils::Locale::ZonesModel* m_zonesData;
|
||||
const TimeZoneData* m_currentLocation = nullptr; // Not owned by me
|
||||
|
||||
void paintEvent( QPaintEvent* event );
|
||||
void mousePressEvent( QMouseEvent* event );
|
||||
|
@ -79,9 +79,22 @@ LocaleQmlViewStep::jobs() const
|
||||
return m_config->createJobs();
|
||||
}
|
||||
|
||||
void
|
||||
LocaleQmlViewStep::onActivate()
|
||||
{
|
||||
m_config->setCurrentLocation(); // Finalize the location
|
||||
QmlViewStep::onActivate();
|
||||
}
|
||||
|
||||
void
|
||||
LocaleQmlViewStep::onLeave()
|
||||
{
|
||||
m_config->finalizeGlobalStorage();
|
||||
}
|
||||
|
||||
void
|
||||
LocaleQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
m_config->setConfigurationMap( configurationMap );
|
||||
Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
|
||||
QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ public:
|
||||
bool isAtBeginning() const override;
|
||||
bool isAtEnd() const override;
|
||||
|
||||
virtual void onActivate() override;
|
||||
virtual void onLeave() override;
|
||||
|
||||
Calamares::JobList jobs() const override;
|
||||
|
||||
void setConfigurationMap( const QVariantMap& configurationMap ) override;
|
||||
|
@ -29,19 +29,19 @@ import QtPositioning 5.14
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
//Needs to come from .conf/geoip
|
||||
property var configCity: "New York"
|
||||
property var configCountry: "USA"
|
||||
property var configTimezone: "America/New York"
|
||||
property var geoipCity: "" //"Amsterdam"
|
||||
property var geoipCountry: "" //"Netherlands"
|
||||
property var geoipTimezone: "" //"Europe/Amsterdam"
|
||||
// vars that will stay once connected
|
||||
property var cityName: (geoipCity != "") ? geoipCity : configCity
|
||||
property var countryName: (geoipCountry != "") ? geoipCountry : configCountry
|
||||
property var timeZone: (geoipTimezone != "") ? geoipTimezone : configTimezone
|
||||
// These are used by the map query to initially center the
|
||||
// map on the user's likely location. They are updated by
|
||||
// getIp() which does a more accurate GeoIP lookup than
|
||||
// the default one in Calamares
|
||||
property var cityName: ""
|
||||
property var countryName: ""
|
||||
|
||||
function getIp() {
|
||||
/* This is an extra GeoIP lookup, which will find better-accuracy
|
||||
* location data for the user's IP, and then sets the current timezone
|
||||
* and map location. Call it from Component.onCompleted so that
|
||||
* it happens "on time" before the page is shown.
|
||||
*/
|
||||
function getIpOnline() {
|
||||
var xhr = new XMLHttpRequest
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
@ -51,9 +51,10 @@ Column {
|
||||
var ct = responseJSON.city
|
||||
var cy = responseJSON.country
|
||||
|
||||
tzText.text = "Timezone: " + tz
|
||||
cityName = ct
|
||||
countryName = cy
|
||||
|
||||
config.setCurrentLocation(tz)
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +64,25 @@ Column {
|
||||
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 latC = map.center.latitude
|
||||
var lonC = map.center.longitude
|
||||
@ -73,16 +92,29 @@ Column {
|
||||
var responseJSON = JSON.parse(xhr.responseText)
|
||||
var tz2 = responseJSON.timezoneId
|
||||
|
||||
tzText.text = "Timezone: " + tz2
|
||||
config.setCurrentLocation(tz2)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Online lookup", latC, lonC)
|
||||
// Needs to move to localeq.conf, each distribution will need their own account
|
||||
xhr.open("GET", "http://api.geonames.org/timezoneJSON?lat=" + latC + "&lng=" + lonC + "&username=SOME_USERNAME")
|
||||
xhr.send()
|
||||
}
|
||||
|
||||
/* This is a quick TZ lookup method: it uses the existing
|
||||
* Calamares "closest TZ" code, which has lots of caveats.
|
||||
*
|
||||
* See below, in MouseArea, for calling the right method.
|
||||
*/
|
||||
function getTzOffline() {
|
||||
var latC = map.center.latitude
|
||||
var lonC = map.center.longitude
|
||||
var tz = config.zonesModel.lookup(latC, lonC)
|
||||
console.log("Offline lookup", latC, lonC)
|
||||
config.setCurrentLocation(tz.region, tz.zone)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height / 1.28
|
||||
@ -156,9 +188,8 @@ Column {
|
||||
map.center.latitude = coordinate.latitude
|
||||
map.center.longitude = coordinate.longitude
|
||||
|
||||
getTz();
|
||||
|
||||
console.log(coordinate.latitude, coordinate.longitude)
|
||||
// Pick a TZ lookup method here (quick:offline, accurate:online)
|
||||
getTzOffline();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,13 +249,16 @@ Column {
|
||||
|
||||
Text {
|
||||
id: tzText
|
||||
text: tzText.text
|
||||
//text: qsTr("Timezone: %1").arg(timeZone)
|
||||
text: qsTr("Timezone: %1").arg(config.currentTimezoneName)
|
||||
color: Kirigami.Theme.textColor
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
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
|
||||
height: 550
|
||||
|
||||
function onActivate() {
|
||||
/* If you want the map to follow Calamares's GeoIP
|
||||
* lookup or configuration, call the update function
|
||||
* here, and disable the one at onCompleted in Map.qml.
|
||||
*/
|
||||
if (Network.hasInternet) { image.item.getIpOffline() }
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: image
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
Loading…
Reference in New Issue
Block a user