source: calamares/trunk/fuentes/src/modules/locale/LocalePage.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 13 months ago

sync with github

File size: 18.1 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
4 *   Copyright 2017, Adriaan de Groot <groot@kde.org>
5 *
6 *   Calamares is free software: you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation, either version 3 of the License, or
9 *   (at your option) any later version.
10 *
11 *   Calamares is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "LocalePage.h"
21
22#include "timezonewidget/timezonewidget.h"
23#include "SetTimezoneJob.h"
24#include "utils/Logger.h"
25#include "utils/Retranslator.h"
26#include "GlobalStorage.h"
27#include "JobQueue.h"
28#include "LCLocaleDialog.h"
29#include "Settings.h"
30
31#include <QBoxLayout>
32#include <QComboBox>
33#include <QLabel>
34#include <QPushButton>
35#include <QProcess>
36
37
38LocalePage::LocalePage( QWidget* parent )
39    : QWidget( parent )
40    , m_blockTzWidgetSet( false )
41{
42    QBoxLayout* mainLayout = new QVBoxLayout;
43
44    QBoxLayout* tzwLayout = new QHBoxLayout;
45    mainLayout->addLayout( tzwLayout );
46    m_tzWidget = new TimeZoneWidget( this );
47    tzwLayout->addStretch();
48    tzwLayout->addWidget( m_tzWidget );
49    tzwLayout->addStretch();
50    setMinimumWidth( m_tzWidget->width() );
51
52    QBoxLayout* bottomLayout = new QHBoxLayout;
53    mainLayout->addLayout( bottomLayout );
54
55    m_regionLabel = new QLabel( this );
56    bottomLayout->addWidget( m_regionLabel );
57
58    m_regionCombo = new QComboBox( this );
59    bottomLayout->addWidget( m_regionCombo );
60    m_regionCombo->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
61    m_regionLabel->setBuddy( m_regionCombo );
62
63    bottomLayout->addSpacing( 20 );
64
65    m_zoneLabel = new QLabel( this );
66    bottomLayout->addWidget( m_zoneLabel );
67
68    m_zoneCombo = new QComboBox( this );
69    m_zoneCombo->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
70    bottomLayout->addWidget( m_zoneCombo );
71    m_zoneLabel->setBuddy( m_zoneCombo );
72
73    mainLayout->addStretch();
74
75    QBoxLayout* localeLayout = new QHBoxLayout;
76    m_localeLabel = new QLabel( this );
77    m_localeLabel->setWordWrap( true );
78    m_localeLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
79    localeLayout->addWidget( m_localeLabel );
80
81    m_localeChangeButton = new QPushButton( this );
82    m_localeChangeButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
83    localeLayout->addWidget( m_localeChangeButton );
84    mainLayout->addLayout( localeLayout );
85
86    QBoxLayout* formatsLayout = new QHBoxLayout;
87    m_formatsLabel = new QLabel( this );
88    m_formatsLabel->setWordWrap( true );
89    m_formatsLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
90    formatsLayout->addWidget( m_formatsLabel );
91
92    m_formatsChangeButton = new QPushButton( this );
93    m_formatsChangeButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
94    formatsLayout->addWidget( m_formatsChangeButton );
95    mainLayout->addLayout( formatsLayout );
96
97    setLayout( mainLayout );
98
99    connect( m_regionCombo,
100             static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ),
101             [this]( int currentIndex )
102    {
103        Q_UNUSED( currentIndex );
104        QHash< QString, QList< LocaleGlobal::Location > > regions = LocaleGlobal::getLocations();
105        if ( !regions.contains( m_regionCombo->currentData().toString() ) )
106            return;
107
108        m_zoneCombo->blockSignals( true );
109
110        m_zoneCombo->clear();
111
112        const QList< LocaleGlobal::Location > zones = regions.value( m_regionCombo->currentData().toString() );
113        for ( const LocaleGlobal::Location& zone : zones )
114        {
115            m_zoneCombo->addItem( LocaleGlobal::Location::pretty( zone.zone ), zone.zone );
116        }
117
118        m_zoneCombo->model()->sort( 0 );
119
120        m_zoneCombo->blockSignals( false );
121
122        m_zoneCombo->currentIndexChanged( m_zoneCombo->currentIndex() );
123    } );
124
125    connect( m_zoneCombo,
126             static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ),
127             [this]( int currentIndex )
128    {
129        Q_UNUSED( currentIndex )
130        if ( !m_blockTzWidgetSet )
131            m_tzWidget->setCurrentLocation( m_regionCombo->currentData().toString(),
132                                            m_zoneCombo->currentData().toString() );
133
134        updateGlobalStorage();
135    } );
136
137    connect( m_tzWidget, &TimeZoneWidget::locationChanged,
138             [this]( LocaleGlobal::Location location )
139    {
140        m_blockTzWidgetSet = true;
141
142        // Set region index
143        int index = m_regionCombo->findData( location.region );
144        if ( index < 0 )
145            return;
146
147        m_regionCombo->setCurrentIndex( index );
148
149        // Set zone index
150        index = m_zoneCombo->findData( location.zone );
151        if ( index < 0 )
152            return;
153
154        m_zoneCombo->setCurrentIndex( index );
155
156        m_blockTzWidgetSet = false;
157
158        updateGlobalStorage();
159    } );
160
161    connect( m_localeChangeButton, &QPushButton::clicked,
162             [this]
163    {
164        LCLocaleDialog* dlg =
165                new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ?
166                                        guessLocaleConfiguration().lang :
167                                        m_selectedLocaleConfiguration.lang,
168                                    m_localeGenLines,
169                                    this );
170        dlg->exec();
171        if ( dlg->result() == QDialog::Accepted &&
172             !dlg->selectedLCLocale().isEmpty() )
173        {
174            m_selectedLocaleConfiguration.lang = dlg->selectedLCLocale();
175            m_selectedLocaleConfiguration.explicit_lang = true;
176            this->updateLocaleLabels();
177        }
178
179        dlg->deleteLater();
180    } );
181
182    connect( m_formatsChangeButton, &QPushButton::clicked,
183             [this]
184    {
185        LCLocaleDialog* dlg =
186                new LCLocaleDialog( m_selectedLocaleConfiguration.isEmpty() ?
187                                        guessLocaleConfiguration().lc_numeric :
188                                        m_selectedLocaleConfiguration.lc_numeric,
189                                    m_localeGenLines,
190                                    this );
191        dlg->exec();
192        if ( dlg->result() == QDialog::Accepted &&
193             !dlg->selectedLCLocale().isEmpty() )
194        {
195            // TODO: improve the granularity of this setting.
196            m_selectedLocaleConfiguration.lc_numeric = dlg->selectedLCLocale();
197            m_selectedLocaleConfiguration.lc_time = dlg->selectedLCLocale();
198            m_selectedLocaleConfiguration.lc_monetary = dlg->selectedLCLocale();
199            m_selectedLocaleConfiguration.lc_paper = dlg->selectedLCLocale();
200            m_selectedLocaleConfiguration.lc_name = dlg->selectedLCLocale();
201            m_selectedLocaleConfiguration.lc_address = dlg->selectedLCLocale();
202            m_selectedLocaleConfiguration.lc_telephone = dlg->selectedLCLocale();
203            m_selectedLocaleConfiguration.lc_measurement = dlg->selectedLCLocale();
204            m_selectedLocaleConfiguration.lc_identification = dlg->selectedLCLocale();
205            m_selectedLocaleConfiguration.explicit_lc = true;
206
207            this->updateLocaleLabels();
208        }
209
210        dlg->deleteLater();
211
212    } );
213
214    CALAMARES_RETRANSLATE(
215        m_regionLabel->setText( tr( "Region:" ) );
216        m_zoneLabel->setText( tr( "Zone:" ) );
217
218        updateLocaleLabels();
219
220        m_localeChangeButton->setText( tr( "&Change..." ) );
221        m_formatsChangeButton->setText( tr( "&Change..." ) );
222    )
223}
224
225
226LocalePage::~LocalePage()
227{}
228
229
230void
231LocalePage::updateLocaleLabels()
232{
233    LocaleConfiguration lc = m_selectedLocaleConfiguration.isEmpty() ?
234                             guessLocaleConfiguration() :
235                             m_selectedLocaleConfiguration;
236    auto labels = prettyLocaleStatus( lc );
237    m_localeLabel->setText( labels.first );
238    m_formatsLabel->setText( labels.second );
239}
240
241
242void
243LocalePage::init( const QString& initialRegion,
244                  const QString& initialZone,
245                  const QString& localeGenPath )
246{
247    m_regionCombo->blockSignals( true );
248    m_zoneCombo->blockSignals( true );
249
250    // Setup locations
251    QHash< QString, QList< LocaleGlobal::Location > > regions = LocaleGlobal::getLocations();
252
253    QStringList keys = regions.keys();
254    keys.sort();
255
256    foreach ( const QString& key, keys )
257    {
258        m_regionCombo->addItem( LocaleGlobal::Location::pretty( key ), key );
259    }
260
261    m_regionCombo->blockSignals( false );
262    m_zoneCombo->blockSignals( false );
263
264    m_regionCombo->currentIndexChanged( m_regionCombo->currentIndex() );
265
266    // Default location
267    auto containsLocation = []( const QList< LocaleGlobal::Location >& locations,
268                                const QString& zone ) -> bool
269    {
270        for ( const LocaleGlobal::Location& location : locations )
271        {
272            if ( location.zone == zone )
273                return true;
274        }
275        return false;
276    };
277
278    if ( keys.contains( initialRegion ) &&
279         containsLocation( regions.value( initialRegion ), initialZone ) )
280    {
281        m_tzWidget->setCurrentLocation( initialRegion, initialZone );
282    }
283    else
284    {
285        m_tzWidget->setCurrentLocation( "America", "New_York" );
286    }
287    emit m_tzWidget->locationChanged( m_tzWidget->getCurrentLocation() );
288
289    // Some distros come with a meaningfully commented and easy to parse locale.gen,
290    // and others ship a separate file /usr/share/i18n/SUPPORTED with a clean list of
291    // supported locales. We first try that one, and if it doesn't exist, we fall back
292    // to parsing the lines from locale.gen
293    m_localeGenLines.clear();
294    QFile supported( "/usr/share/i18n/SUPPORTED" );
295    QByteArray ba;
296
297    if ( supported.exists() &&
298         supported.open( QIODevice::ReadOnly | QIODevice::Text ) )
299    {
300        ba = supported.readAll();
301        supported.close();
302
303        const auto lines = ba.split( '\n' );
304        for ( const QByteArray &line : lines )
305        {
306            m_localeGenLines.append( QString::fromLatin1( line.simplified() ) );
307        }
308    }
309    else
310    {
311        QFile localeGen( localeGenPath );
312        if ( localeGen.open( QIODevice::ReadOnly | QIODevice::Text ) )
313        {
314            ba = localeGen.readAll();
315            localeGen.close();
316        }
317        else
318        {
319            cDebug() << "Cannot open file" << localeGenPath
320                     << ". Assuming the supported languages are already built into "
321                        "the locale archive.";
322            QProcess localeA;
323            localeA.start( "locale", QStringList() << "-a" );
324            localeA.waitForFinished();
325            ba = localeA.readAllStandardOutput();
326        }
327        const auto lines = ba.split( '\n' );
328        for ( const QByteArray &line : lines )
329        {
330            if ( line.startsWith( "## " ) ||
331                 line.startsWith( "# " ) ||
332                 line.simplified() == "#" )
333                continue;
334
335            QString lineString = QString::fromLatin1( line.simplified() );
336            if ( lineString.startsWith( "#" ) )
337                lineString.remove( '#' );
338            lineString = lineString.simplified();
339
340            if ( lineString.isEmpty() )
341                continue;
342
343            m_localeGenLines.append( lineString );
344        }
345    }
346
347    if ( m_localeGenLines.isEmpty() )
348    {
349        cWarning() << "cannot acquire a list of available locales."
350                    << "The locale and localecfg modules will be broken as long as this "
351                    "system does not provide"
352                    << "\n\t  "
353                    << "* a well-formed"
354                    << supported.fileName()
355                    << "\n\tOR"
356                    << "* a well-formed"
357                    << (localeGenPath.isEmpty() ? QLatin1Literal("/etc/locale.gen") : localeGenPath)
358                    << "\n\tOR"
359                    << "* a complete pre-compiled locale-gen database which allows complete locale -a output.";
360        return; // something went wrong and there's nothing we can do about it.
361    }
362
363    // Assuming we have a list of supported locales, we usually only want UTF-8 ones
364    // because it's not 1995.
365    for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); )
366    {
367        if ( !it->contains( "UTF-8", Qt::CaseInsensitive ) &&
368             !it->contains( "utf8", Qt::CaseInsensitive ) )
369            it = m_localeGenLines.erase( it );
370        else
371            ++it;
372    }
373
374    // We strip " UTF-8" from "en_US.UTF-8 UTF-8" because it's redundant redundant.
375    for ( auto it = m_localeGenLines.begin(); it != m_localeGenLines.end(); ++it )
376    {
377        if ( it->endsWith( " UTF-8" ) )
378            it->chop( 6 );
379        *it = it->simplified();
380    }
381    updateGlobalStorage();
382}
383
384std::pair< QString, QString > LocalePage::prettyLocaleStatus( const LocaleConfiguration& lc ) const
385{
386    return std::make_pair< QString, QString >(
387        tr( "The system language will be set to %1." )
388            .arg( prettyLCLocale( lc.lang ) ),
389        tr( "The numbers and dates locale will be set to %1." )
390                            .arg( prettyLCLocale( lc.lc_numeric ) )
391                                             );
392}
393
394QString
395LocalePage::prettyStatus() const
396{
397    QString status;
398    status += tr( "Set timezone to %1/%2.<br/>" )
399              .arg( m_regionCombo->currentText() )
400              .arg( m_zoneCombo->currentText() );
401
402    LocaleConfiguration lc = m_selectedLocaleConfiguration.isEmpty() ?
403                guessLocaleConfiguration() :
404                m_selectedLocaleConfiguration;
405    auto labels = prettyLocaleStatus(lc);
406    status += labels.first + "<br/>";
407    status += labels.second + "<br/>";
408
409    return status;
410}
411
412
413QList< Calamares::job_ptr >
414LocalePage::createJobs()
415{
416    QList< Calamares::job_ptr > list;
417    LocaleGlobal::Location location = m_tzWidget->getCurrentLocation();
418
419    Calamares::Job* j = new SetTimezoneJob( location.region, location.zone );
420    list.append( Calamares::job_ptr( j ) );
421
422    return list;
423}
424
425
426QMap< QString, QString >
427LocalePage::localesMap()
428{
429    return m_selectedLocaleConfiguration.isEmpty() ?
430                guessLocaleConfiguration().toMap() :
431                m_selectedLocaleConfiguration.toMap();
432}
433
434
435void
436LocalePage::onActivate()
437{
438    m_regionCombo->setFocus();
439    if ( m_selectedLocaleConfiguration.isEmpty() ||
440         !m_selectedLocaleConfiguration.explicit_lang )
441    {
442        auto newLocale = guessLocaleConfiguration();
443        m_selectedLocaleConfiguration.lang = newLocale.lang;
444        updateLocaleLabels();
445    }
446}
447
448
449LocaleConfiguration
450LocalePage::guessLocaleConfiguration() const
451{
452    QLocale myLocale;   // User-selected language
453
454    // If we cannot say anything about available locales
455    if ( m_localeGenLines.isEmpty() )
456    {
457        cWarning() << "guessLocaleConfiguration can't guess from an empty list.";
458        return LocaleConfiguration::createDefault();
459    }
460
461    QString myLanguageLocale = myLocale.name();
462    if ( myLanguageLocale.isEmpty() )
463        return LocaleConfiguration::createDefault();
464
465    return LocaleConfiguration::fromLanguageAndLocation( myLanguageLocale,
466                                                         m_localeGenLines,
467                                                         m_tzWidget->getCurrentLocation().country );
468}
469
470
471QString
472LocalePage::prettyLCLocale( const QString& lcLocale ) const
473{
474    QString localeString = lcLocale;
475    if ( localeString.endsWith( " UTF-8" ) )
476        localeString.remove( " UTF-8" );
477
478    QLocale locale( localeString );
479    //: Language (Country)
480    return tr( "%1 (%2)" ).arg( QLocale::languageToString( locale.language() ) )
481                          .arg( QLocale::countryToString( locale.country() ) );
482}
483
484void
485LocalePage::updateGlobalStorage()
486{
487    LocaleGlobal::Location location = m_tzWidget->getCurrentLocation();
488    Calamares::JobQueue::instance()->globalStorage()
489            ->insert( "locationRegion", location.region );
490    Calamares::JobQueue::instance()->globalStorage()
491            ->insert( "locationZone", location.zone );
492
493    const QString bcp47 = m_selectedLocaleConfiguration.toBcp47();
494    Calamares::JobQueue::instance()->globalStorage()->insert( "locale", bcp47 );
495
496    // If we're in chroot mode (normal install mode), then we immediately set the
497    // timezone on the live system. When debugging timezones, don't bother.
498#ifndef DEBUG_TIMEZONES
499    if ( Calamares::Settings::instance()->doChroot() )
500    {
501        QProcess ::execute( "timedatectl",  // depends on systemd
502                            { "set-timezone",
503                              location.region + '/' + location.zone } );
504    }
505#endif
506
507    // Preserve those settings that have been made explicit.
508    auto newLocale = guessLocaleConfiguration();
509    if ( !m_selectedLocaleConfiguration.isEmpty() &&
510         m_selectedLocaleConfiguration.explicit_lang )
511        newLocale.lang = m_selectedLocaleConfiguration.lang;
512    if ( !m_selectedLocaleConfiguration.isEmpty() &&
513         m_selectedLocaleConfiguration.explicit_lc )
514    {
515        newLocale.lc_numeric = m_selectedLocaleConfiguration.lc_numeric;
516        newLocale.lc_time = m_selectedLocaleConfiguration.lc_time;
517        newLocale.lc_monetary = m_selectedLocaleConfiguration.lc_monetary;
518        newLocale.lc_paper = m_selectedLocaleConfiguration.lc_paper;
519        newLocale.lc_name = m_selectedLocaleConfiguration.lc_name;
520        newLocale.lc_address = m_selectedLocaleConfiguration.lc_address;
521        newLocale.lc_telephone = m_selectedLocaleConfiguration.lc_telephone;
522        newLocale.lc_measurement = m_selectedLocaleConfiguration.lc_measurement;
523        newLocale.lc_identification = m_selectedLocaleConfiguration.lc_identification;
524    }
525    newLocale.explicit_lang = m_selectedLocaleConfiguration.explicit_lang;
526    newLocale.explicit_lc = m_selectedLocaleConfiguration.explicit_lc;
527
528    m_selectedLocaleConfiguration = newLocale;
529    updateLocaleLabels();
530}
Note: See TracBrowser for help on using the repository browser.