[libcalamares] Rip out all the TZ models

- The models are overly complicated: **overall** there is just one
  list of timezones, and we need various views on that list.

Start over with an empty model of regions.
main
Adriaan de Groot 5 years ago
parent 05f3fbea05
commit fce05acf1e

@ -249,32 +249,6 @@ LocaleTests::testSimpleZones()
{
using namespace CalamaresUtils::Locale;
{
TZRegion r;
QVERIFY( r.tr().isEmpty() );
}
{
TZZone n;
QVERIFY( n.tr().isEmpty() );
}
{
TZZone r0( "xAmsterdam" );
QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) );
TZZone r1( r0 );
QCOMPARE( r0.tr(), QStringLiteral( "xAmsterdam" ) );
QCOMPARE( r1.tr(), QStringLiteral( "xAmsterdam" ) );
TZZone r2( std::move( r0 ) );
QCOMPARE( r2.tr(), QStringLiteral( "xAmsterdam" ) );
QCOMPARE( r0.tr(), QString() );
}
{
TZZone r0( nullptr );
QVERIFY( r0.tr().isEmpty() );
TZZone r1( r0 );
QVERIFY( r1.tr().isEmpty() );
TZZone r2( std::move( r0 ) );
QVERIFY( r2.tr().isEmpty() );
}
}
void
@ -282,18 +256,6 @@ LocaleTests::testComplexZones()
{
using namespace CalamaresUtils::Locale;
{
TZZone r0( "America/New_York" );
TZZone r1( "America/New York" );
QCOMPARE( r0.tr(), r1.tr() );
QCOMPARE( r0.tr(), QStringLiteral( "America/New York" ) );
}
{
TZZone r( "zxc,;*_vm" );
QVERIFY( !r.tr().isEmpty() );
QCOMPARE( r.tr(), QStringLiteral( "zxc,;* vm" ) ); // Only _ is special
}
}
QTEST_GUILESS_MAIN( LocaleTests )

@ -23,16 +23,14 @@
#include "TimeZone.h"
#include "utils/Logger.h"
#include "utils/String.h"
#include <QFile>
#include <QStringList>
#include <QTextStream>
#include <cstring>
static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab";
/** @brief Turns a string longitude or latitude notation into a double
*
* This handles strings like "+4230+00131" from zone.tab,
* which is degrees-and-minutes notation, and + means north or east.
*/
static double
getRightGeoLocation( QString str )
{
@ -61,28 +59,6 @@ getRightGeoLocation( QString str )
return sign * num;
}
namespace CalamaresUtils
{
namespace Locale
{
CStringPair::CStringPair( CStringPair&& t )
: m_human( nullptr )
, m_key()
{
// My pointers are initialized to nullptr
std::swap( m_human, t.m_human );
std::swap( m_key, t.m_key );
}
CStringPair::CStringPair( const CStringPair& t )
: m_human( t.m_human ? strdup( t.m_human ) : nullptr )
, m_key( t.m_key )
{
}
/** @brief Massage an identifier into a human-readable form
*
* Makes a copy of @p s, caller must free() it.
@ -110,204 +86,42 @@ munge( const char* s )
return t;
}
CStringPair::CStringPair( const char* s1 )
: m_human( s1 ? munge( s1 ) : nullptr )
, m_key( s1 ? QString( s1 ) : QString() )
{
}
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 = [&region]( const CStringPair* r ) { return r->key() == region; };
auto it = std::find_if( model.begin(), model.end(), keyMatch );
if ( it != model.end() )
{
thisRegion = dynamic_cast< TZRegion* >( *it );
}
else
{
thisRegion = new TZRegion( region.toUtf8().data() );
model.append( thisRegion );
}
QString countryCode = list.at( 0 ).trimmed();
if ( countryCode.size() != 2 )
{
continue;
}
timezoneParts.removeFirst();
thisRegion->m_zones.append(
new TZZone( region, timezoneParts.join( '/' ).toUtf8().constData(), countryCode, list.at( 1 ) ) );
}
auto sorter = []( const CStringPair* l, const CStringPair* r ) { return *l < *r; };
std::sort( model.begin(), model.end(), sorter );
for ( auto& it : model )
{
TZRegion* r = dynamic_cast< TZRegion* >( it );
if ( r )
{
std::sort( r->m_zones.begin(), r->m_zones.end(), sorter );
}
}
return model;
}
TZZone::TZZone( const QString& region, const char* zoneName, const QString& country, QString position )
: CStringPair( zoneName )
, m_region( region )
, m_country( country )
namespace CalamaresUtils
{
int cooSplitPos = position.indexOf( QRegExp( "[-+]" ), 1 );
if ( cooSplitPos > 0 )
{
m_latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
m_longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
}
}
QString
TZZone::tr() const
namespace Locale
{
// NOTE: context name must match what's used in zone-extractor.py
return QObject::tr( m_human, "tz_names" );
}
struct Private {
};
CStringListModel::CStringListModel( CStringPairList l )
: m_list( l )
static Private* privateInstance()
{
static Private* s_p = new Private;
return s_p;
}
void
CStringListModel::setList( CalamaresUtils::Locale::CStringPairList l )
RegionsModel::RegionsModel()
: QAbstractListModel()
, m_private( privateInstance() )
{
beginResetModel();
m_list = l;
endResetModel();
}
int
CStringListModel::rowCount( const QModelIndex& ) const
RegionsModel::~RegionsModel()
{
return m_list.count();
}
QVariant
CStringListModel::data( const QModelIndex& index, int role ) const
int RegionsModel::rowCount(const QModelIndex& parent) const
{
if ( ( role != Qt::DisplayRole ) && ( role != Qt::UserRole ) )
{
return QVariant();
}
if ( !index.isValid() )
{
return QVariant();
}
const auto* item = m_list.at( index.row() );
return item ? ( role == Qt::DisplayRole ? item->tr() : item->key() ) : QVariant();
return 0;
}
void
CStringListModel::setCurrentIndex( int index )
QVariant RegionsModel::data(const QModelIndex& index, int role) const
{
if ( ( index < 0 ) || ( index >= m_list.count() ) )
{
return;
}
m_currentIndex = index;
emit currentIndexChanged();
}
int
CStringListModel::currentIndex() const
{
return m_currentIndex;
return QVariant();
}
QHash< int, QByteArray >
CStringListModel::roleNames() const
{
return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } };
}
const CStringPair*
CStringListModel::item( int index ) const
{
if ( ( index < 0 ) || ( index >= m_list.count() ) )
{
return nullptr;
}
return m_list[ index ];
}
} // namespace Locale
} // namespace CalamaresUtils

@ -24,182 +24,36 @@
#include "DllMacro.h"
#include "utils/Logger.h"
#include <QAbstractListModel>
#include <QObject>
#include <QString>
#include <memory>
#include <QVariant>
namespace CalamaresUtils
{
namespace Locale
{
struct Private;
/** @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
{
Q_OBJECT
public:
/// @brief An empty pair
CStringPair() {}
/// @brief Given an identifier, create the pair
explicit CStringPair( const char* s1 );
CStringPair( CStringPair&& t );
CStringPair( const CStringPair& );
virtual ~CStringPair();
/// @brief Give the localized human-readable form
virtual QString tr() const = 0;
QString key() const { return m_key; }
bool operator<( const CStringPair& other ) const { return m_key < other.m_key; }
protected:
char* m_human = nullptr;
QString m_key;
};
class CStringPairList : public QList< CStringPair* >
{
public:
template < typename T >
T* find( const QString& key ) const
{
for ( auto* p : *this )
{
if ( p->key() == key )
{
return dynamic_cast< T* >( p );
}
}
return nullptr;
}
};
/** @brief Timezone regions (e.g. "America")
*
* A region has a key and a human-readable name, but also
* a collection of associated timezone zones (TZZone, below).
* This class is not usually constructed, but uses fromFile()
* to load a complete tree structure of timezones.
*/
class TZRegion : public CStringPair
{
Q_OBJECT
public:
using CStringPair::CStringPair;
virtual ~TZRegion() override;
TZRegion( const TZRegion& ) = delete;
QString tr() const override;
QString region() const { return key(); }
/** @brief Create list from a zone.tab-like file
*
* Returns a list of all the regions; each region has a list
* of zones within that region. Dyamically, the items in the
* returned list are TZRegions; their zones dynamically are
* TZZones even though all those lists have type CStringPairList.
*
* The list owns the regions, and the regions own their own list of zones.
* When getting rid of the list, remember to qDeleteAll() on it.
*/
static CStringPairList fromFile( const char* fileName );
/// @brief Calls fromFile with the standard zone.tab name
static const CStringPairList& fromZoneTab();
const CStringPairList& zones() const { return m_zones; }
private:
CStringPairList m_zones;
};
/** @brief Specific timezone zones (e.g. "New_York", "New York")
/** @brief The list of timezone regions
*
* 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.
* The regions are a short list of global areas (Africa, America, India ..)
* which contain zones.
*/
class TZZone : public CStringPair
class DLLEXPORT RegionsModel : public QAbstractListModel
{
Q_OBJECT
public:
using CStringPair::CStringPair;
QString tr() const override;
TZZone( const QString& region, const char* zoneName, const QString& country, QString position );
QString region() const { return m_region; }
QString zone() const { return key(); }
QString country() const { return m_country; }
double latitude() const { return m_latitude; }
double longitude() const { return m_longitude; }
protected:
QString m_region;
QString m_country;
double m_latitude = 0.0, m_longitude = 0.0;
};
class CStringListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged )
RegionsModel();
public:
/// @brief Create empty model
CStringListModel() {}
/// @brief Create model from list (non-owning)
CStringListModel( CStringPairList );
virtual ~RegionsModel() override;
static RegionsModel* instance();
int rowCount( const QModelIndex& parent ) const override;
QVariant data( const QModelIndex& index, int role ) const override;
const CStringPair* item( int index ) const;
QHash< int, QByteArray > roleNames() const override;
void setCurrentIndex( int index );
int currentIndex() const;
void setList( CStringPairList );
inline int indexOf( const QString& key )
{
const auto it = std::find_if(
m_list.constBegin(), m_list.constEnd(), [&]( const CalamaresUtils::Locale::CStringPair* item ) -> bool {
return item->key() == key;
} );
if ( it != m_list.constEnd() )
{
// distance() is usually a long long
return int( std::distance( m_list.constBegin(), it ) );
}
else
{
return -1;
}
}
private:
CStringPairList m_list;
int m_currentIndex = -1;
signals:
void currentIndexChanged();
Private *m_private;
};
} // namespace Locale

Loading…
Cancel
Save