diff --git a/CHANGES b/CHANGES index 3a6ebd0f3..ee66ee152 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,9 @@ This release contains contributions from (alphabetically by first name): Calamares). The example QML that is compiled into Calamares has been improved. To use your own QML, put files `calamares-sidebar.qml` or `calamares-navigation.qml` into the branding directory. + - The sidebar and navigation can now be placed on any side of the + main window. This is probably only useful for QML-based UIs. + See `branding.desc` for details. ## Modules ## - The *welcomeq* module has been improved with better layout and diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index 365af30e9..b6694d1f4 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -45,12 +45,25 @@ windowPlacement: center # - "widget" or unset, use traditional sidebar (logo, items) # - "none", hide it entirely # - "qml", use calamares-sidebar.qml from branding folder +# In addition, you **may** specify a side, separated by a comma, +# from the kind. Valid sides are: +# - "left" (if not specified, uses this) +# - "right" +# - "top" +# - "bottom" +# For instance, "widget,right" is valid; so is "qml", which defaults +# to putting the sidebar on the left. Also valid is "qml,top". +# While "widget,top" is valid, the widgets code is **not** flexible +# and results will be terrible. sidebar: widget # Kind of navigation (button panel on the bottom). # - "widget" or unset, use traditional navigation # - "none", hide it entirely # - "qml", use calamares-navigation.qml from branding folder +# In addition, you **may** specify a side, separated by a comma, +# from the kind. The same sides are valid as for *sidebar*, +# except the default is *bottom*. navigation: widget # These are strings shown to the user in the user interface. diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index e9083c225..5ef97a6a3 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -338,17 +338,9 @@ CalamaresApplication::initViewSteps() m_mainwindow->show(); } - // ProgressTreeModel* m = new ProgressTreeModel( nullptr ); - // ProgressTreeView::instance()->setModel( m ); cDebug() << "STARTUP: Window now visible and ProgressTreeView populated"; - - const auto steps = Calamares::ViewManager::instance()->viewSteps(); - cDebug() << Logger::SubEntry << steps.count() << "view steps loaded."; - // Tell the first view that it's been shown. - if ( steps.count() > 0 ) - { - steps[ 0 ]->onActivate(); - } + cDebug() << Logger::SubEntry << Calamares::ViewManager::instance()->viewSteps().count() << "view steps loaded."; + Calamares::ViewManager::instance()->onInitComplete(); } void diff --git a/src/calamares/CalamaresWindow.cpp b/src/calamares/CalamaresWindow.cpp index 5d4565406..0e8ac1421 100644 --- a/src/calamares/CalamaresWindow.cpp +++ b/src/calamares/CalamaresWindow.cpp @@ -132,11 +132,10 @@ CalamaresWindow::getWidgetSidebar( int desiredWidth ) } QWidget* -CalamaresWindow::getQmlSidebar( int desiredWidth ) +CalamaresWindow::getQmlSidebar( int ) { CalamaresUtils::registerCalamaresModels(); QQuickWidget* w = new QQuickWidget( this ); - w->setFixedWidth( desiredWidth ); w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); w->setResizeMode( QQuickWidget::SizeRootObjectToView ); w->setSource( QUrl( @@ -217,7 +216,6 @@ CalamaresWindow::getQmlNavigation() { CalamaresUtils::registerCalamaresModels(); QQuickWidget* w = new QQuickWidget( this ); - w->setFixedHeight( 64 ); w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); w->setResizeMode( QQuickWidget::SizeRootObjectToView ); w->setSource( QUrl( @@ -251,6 +249,28 @@ flavoredWidget( Calamares::Branding::PanelFlavor flavor, NOTREACHED return nullptr; // All enum values handled above } +/** @brief Adds widgets to @p layout if they belong on this @p side + */ +static inline void +insertIf( QBoxLayout* layout, + Calamares::Branding::PanelSide side, + QWidget* first, + Calamares::Branding::PanelSide firstSide ) +{ + if ( first && side == firstSide ) + { + if ( ( side == Calamares::Branding::PanelSide::Left ) || ( side == Calamares::Branding::PanelSide::Right ) ) + { + first->setMinimumWidth( qMax( first->minimumWidth(), 64 ) ); + } + else + { + first->setMinimumHeight( qMax( first->minimumHeight(), 64 ) ); + } + layout->addWidget( first ); + } +} + CalamaresWindow::CalamaresWindow( QWidget* parent ) : QWidget( parent ) , m_debugWindow( nullptr ) @@ -273,6 +293,8 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) using CalamaresUtils::windowPreferredHeight; using CalamaresUtils::windowPreferredWidth; + using PanelSide = Calamares::Branding::PanelSide; + // Needs to match what's checked in DebugWindow this->setObjectName( "mainApp" ); @@ -292,21 +314,6 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) resize( w, h ); m_viewManager = Calamares::ViewManager::instance( this ); - - QBoxLayout* mainLayout = new QHBoxLayout; - setLayout( mainLayout ); - - QWidget* sideBox = flavoredWidget( - branding->sidebarFlavor(), - this, - &CalamaresWindow::getWidgetSidebar, - &CalamaresWindow::getQmlSidebar, - qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) ); - if ( sideBox ) - { - mainLayout->addWidget( sideBox ); - } - if ( branding->windowExpands() ) { connect( m_viewManager, &Calamares::ViewManager::enlarge, this, &CalamaresWindow::enlarge ); @@ -319,16 +326,35 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) // and requires an extra show() (at least with KWin/X11) which // is too annoying. Instead, leave it up to ignoring-the-quit- // event, which is also the ViewManager's responsibility. + + QBoxLayout* mainLayout = new QHBoxLayout; QBoxLayout* contentsLayout = new QVBoxLayout; - contentsLayout->addWidget( m_viewManager->centralWidget() ); + + setLayout( mainLayout ); + + QWidget* sideBox = flavoredWidget( + branding->sidebarFlavor(), + this, + &CalamaresWindow::getWidgetSidebar, + &CalamaresWindow::getQmlSidebar, + qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) ); QWidget* navigation = flavoredWidget( branding->navigationFlavor(), this, &CalamaresWindow::getWidgetNavigation, &CalamaresWindow::getQmlNavigation ); - if ( navigation ) - { - contentsLayout->addWidget( navigation ); - } + // Build up the contentsLayout (a VBox) top-to-bottom + // .. note that the bottom is mirrored wrt. the top + insertIf( contentsLayout, PanelSide::Top, sideBox, branding->sidebarSide() ); + insertIf( contentsLayout, PanelSide::Top, navigation, branding->navigationSide() ); + contentsLayout->addWidget( m_viewManager->centralWidget() ); + insertIf( contentsLayout, PanelSide::Bottom, navigation, branding->navigationSide() ); + insertIf( contentsLayout, PanelSide::Bottom, sideBox, branding->sidebarSide() ); + + // .. and then the mainLayout left-to-right + insertIf( mainLayout, PanelSide::Left, sideBox, branding->sidebarSide() ); + insertIf( mainLayout, PanelSide::Left, navigation, branding->navigationSide() ); mainLayout->addLayout( contentsLayout ); + insertIf( mainLayout, PanelSide::Right, navigation, branding->navigationSide() ); + insertIf( mainLayout, PanelSide::Right, sideBox, branding->sidebarSide() ); CalamaresUtils::unmarginLayout( mainLayout ); CalamaresUtils::unmarginLayout( contentsLayout ); diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index 25fab307e..ff7e43fb8 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -405,6 +405,79 @@ getString( const YAML::Node& doc, const char* key ) return QString(); } +static inline void +flavorAndSide( const YAML::Node& doc, const char* key, Branding::PanelFlavor& flavor, Branding::PanelSide& side ) +{ + using PanelFlavor = Branding::PanelFlavor; + using PanelSide = Branding::PanelSide; + + // *INDENT-OFF* + // clang-format off + static const NamedEnumTable< PanelFlavor > sidebarFlavorNames { + { QStringLiteral( "widget" ), PanelFlavor::Widget }, + { QStringLiteral( "none" ), PanelFlavor::None }, + { QStringLiteral( "hidden" ), PanelFlavor::None }, + { QStringLiteral( "qml" ), PanelFlavor::Qml } + }; + static const NamedEnumTable< PanelSide > panelSideNames { + { QStringLiteral( "left" ), PanelSide::Left }, + { QStringLiteral( "right" ), PanelSide::Right }, + { QStringLiteral( "top" ), PanelSide::Top }, + { QStringLiteral( "bottom" ), PanelSide::Bottom } + }; + // clang-format on + // *INDENT-ON* + + bool ok = false; + QString configValue = getString( doc, key ); + if ( configValue.isEmpty() ) + { + // Complain with the original values + cWarning() << "Branding setting for" << key << "is missing, using" << sidebarFlavorNames.find( flavor, ok ) + << panelSideNames.find( side, ok ); + return; + } + + QStringList parts = configValue.split( ',' ); + if ( parts.length() == 1 ) + { + PanelFlavor f = sidebarFlavorNames.find( configValue, ok ); + if ( ok ) + { + flavor = f; + } + else + { + // Complain with the original value + cWarning() << "Branding setting for" << key << "interpreted as" << sidebarFlavorNames.find( flavor, ok ) + << panelSideNames.find( side, ok ); + } + return; + } + + for ( const QString& spart : parts ) + { + bool isFlavor = false; + bool isSide = false; + PanelFlavor f = sidebarFlavorNames.find( spart, isFlavor ); + PanelSide s = panelSideNames.find( spart, isSide ); + if ( isFlavor ) + { + flavor = f; + } + else if ( isSide ) + { + side = s; + } + else + { + cWarning() << "Branding setting for" << key << "contains unknown" << spart << "interpreted as" + << sidebarFlavorNames.find( flavor, ok ) << panelSideNames.find( side, ok ); + return; + } + } +} + void Branding::initSimpleSettings( const YAML::Node& doc ) { @@ -419,12 +492,6 @@ Branding::initSimpleSettings( const YAML::Node& doc ) { QStringLiteral( "free" ), WindowPlacement::Free }, { QStringLiteral( "center" ), WindowPlacement::Center } }; - static const NamedEnumTable< PanelFlavor > sidebarFlavorNames { - { QStringLiteral( "widget" ), PanelFlavor::Widget }, - { QStringLiteral( "none" ), PanelFlavor::None }, - { QStringLiteral( "hidden" ), PanelFlavor::None }, - { QStringLiteral( "qml" ), PanelFlavor::Qml } - }; // clang-format on // *INDENT-ON* bool ok = false; @@ -443,18 +510,8 @@ Branding::initSimpleSettings( const YAML::Node& doc ) cWarning() << "Branding module-setting *windowPlacement* interpreted as" << placementNames.find( m_windowPlacement, ok ); } - m_sidebarFlavor = sidebarFlavorNames.find( getString( doc, "sidebar" ), ok ); - if ( !ok ) - { - cWarning() << "Branding module-setting *sidebar* interpreted as" - << sidebarFlavorNames.find( m_sidebarFlavor, ok ); - } - m_navigationFlavor = sidebarFlavorNames.find( getString( doc, "navigation" ), ok); - if ( !ok ) - { - cWarning() << "Branding module-setting *navigation* interpreted as" - << sidebarFlavorNames.find( m_navigationFlavor, ok ); - } + flavorAndSide( doc, "sidebar", m_sidebarFlavor, m_sidebarSide ); + flavorAndSide( doc, "navigation", m_navigationFlavor, m_navigationSide ); QString windowSize = getString( doc, "windowSize" ); if ( !windowSize.isEmpty() ) diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index b7ba637d6..023f1a511 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -123,7 +123,7 @@ public: Free }; Q_ENUM( WindowPlacement ) - ///@brief What kind of sidebar to use in the main window + ///@brief What kind of panel (sidebar, navigation) to use in the main window enum class PanelFlavor { None, @@ -131,6 +131,16 @@ public: Qml }; Q_ENUM( PanelFlavor ) + ///@brief Where to place a panel (sidebar, navigation) + enum class PanelSide + { + None, + Left, + Right, + Top, + Bottom + }; + Q_ENUM( PanelSide ) static Branding* instance(); @@ -201,6 +211,9 @@ public slots: QString styleString( StyleEntry styleEntry ) const; QString imagePath( ImageEntry imageEntry ) const; + PanelSide sidebarSide() const { return m_sidebarSide; } + PanelSide navigationSide() const { return m_navigationSide; } + private: static Branding* s_instance; @@ -231,6 +244,8 @@ private: PanelFlavor m_sidebarFlavor = PanelFlavor::Widget; PanelFlavor m_navigationFlavor = PanelFlavor::Widget; + PanelSide m_sidebarSide = PanelSide::Left; + PanelSide m_navigationSide = PanelSide::Bottom; }; template < typename U > diff --git a/src/libcalamaresui/ViewManager.cpp b/src/libcalamaresui/ViewManager.cpp index 9bfb31e43..43308d3d6 100644 --- a/src/libcalamaresui/ViewManager.cpp +++ b/src/libcalamaresui/ViewManager.cpp @@ -63,26 +63,9 @@ ViewManager::instance( QObject* parent ) return s_instance; } -/** @brief Get a button-sized icon. */ -static inline QPixmap -getButtonIcon( const QString& name ) -{ - return Calamares::Branding::instance()->image( name, QSize( 22, 22 ) ); -} - -static inline void -setButtonIcon( QPushButton* button, const QString& name ) -{ - auto icon = getButtonIcon( name ); - if ( button && !icon.isNull() ) - { - button->setIcon( icon ); - } -} - ViewManager::ViewManager( QObject* parent ) : QAbstractListModel( parent ) - , m_currentStep( 0 ) + , m_currentStep( -1 ) , m_widget( new QWidget() ) { Q_ASSERT( !s_instance ); @@ -136,18 +119,7 @@ ViewManager::insertViewStep( int before, ViewStep* step ) emit beginInsertRows( QModelIndex(), before, before ); m_steps.insert( before, step ); connect( step, &ViewStep::enlarge, this, &ViewManager::enlarge ); - // TODO: this can be a regular slot - connect( step, &ViewStep::nextStatusChanged, this, [this]( bool status ) { - ViewStep* vs = qobject_cast< ViewStep* >( sender() ); - if ( vs ) - { - if ( vs == m_steps.at( m_currentStep ) ) - { - m_nextEnabled = status; - emit nextEnabledChanged( m_nextEnabled ); - } - } - } ); + connect( step, &ViewStep::nextStatusChanged, this, &ViewManager::updateNextStatus ); if ( !step->widget() ) { @@ -255,6 +227,33 @@ ViewManager::onInitFailed( const QStringList& modules ) insertViewStep( 0, new BlankViewStep( title, description.arg( *Calamares::Branding::ProductName ), detailString ) ); } +void +ViewManager::onInitComplete() +{ + m_currentStep = 0; + + // Tell the first view that it's been shown. + if ( m_steps.count() > 0 ) + { + m_steps.first()->onActivate(); + } +} + +void +ViewManager::updateNextStatus( bool status ) +{ + ViewStep* vs = qobject_cast< ViewStep* >( sender() ); + if ( vs && currentStepValid() ) + { + if ( vs == m_steps.at( m_currentStep ) ) + { + m_nextEnabled = status; + emit nextEnabledChanged( m_nextEnabled ); + } + } +} + + ViewStepList ViewManager::viewSteps() const { @@ -265,7 +264,7 @@ ViewManager::viewSteps() const ViewStep* ViewManager::currentStep() const { - return m_steps.value( m_currentStep ); + return currentStepValid() ? m_steps.value( m_currentStep ) : nullptr; } @@ -289,14 +288,29 @@ stepIsExecute( const ViewStepList& steps, int index ) static inline bool isAtVeryEnd( const ViewStepList& steps, int index ) - { + // If we have an empty list, then there's no point right now + // in checking if we're at the end. + if ( steps.count() == 0 ) + { + return false; + } + // .. and if the index is invalid, ignore it too + if ( !( ( 0 <= index ) && ( index < steps.count() ) ) ) + { + return false; + } return ( index >= steps.count() ) || ( index == steps.count() - 1 && steps.last()->isAtEnd() ); } void ViewManager::next() { + if ( !currentStepValid() ) + { + return; + } + ViewStep* step = m_steps.at( m_currentStep ); bool executing = false; if ( step->isAtEnd() ) @@ -395,7 +409,7 @@ ViewManager::updateButtonLabels() // Going back is always simple UPDATE_BUTTON_PROPERTY( backLabel, tr( "&Back" ) ) - UPDATE_BUTTON_PROPERTY( backIcon, "go-back" ) + UPDATE_BUTTON_PROPERTY( backIcon, "go-previous" ) // Cancel button changes label at the end if ( isAtVeryEnd( m_steps, m_currentStep ) ) @@ -428,6 +442,11 @@ ViewManager::updateButtonLabels() void ViewManager::back() { + if ( !currentStepValid() ) + { + return; + } + ViewStep* step = m_steps.at( m_currentStep ); if ( step->isAtBeginning() && m_currentStep > 0 ) { diff --git a/src/libcalamaresui/ViewManager.h b/src/libcalamaresui/ViewManager.h index ad9376f1a..6c283cfbe 100644 --- a/src/libcalamaresui/ViewManager.h +++ b/src/libcalamaresui/ViewManager.h @@ -184,6 +184,15 @@ public Q_SLOTS: */ void onInitFailed( const QStringList& modules ); + /** @brief Tell the manager that initialization / loading is complete. + * + * Call this at least once, to tell the manager to activate the first page. + */ + void onInitComplete(); + + /// @brief Connected to ViewStep::nextStatusChanged for all steps + void updateNextStatus( bool enabled ); + signals: void currentStepChanged(); void enlarge( QSize enlarge ) const; // See ViewStep::enlarge() @@ -211,6 +220,8 @@ private: void updateButtonLabels(); void updateCancelEnabled( bool enabled ); + inline bool currentStepValid() const { return ( 0 <= m_currentStep ) && ( m_currentStep < m_steps.length() ); } + static ViewManager* s_instance; ViewStepList m_steps; diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp new file mode 100644 index 000000000..8b651d05d --- /dev/null +++ b/src/modules/keyboard/Config.cpp @@ -0,0 +1,539 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * Copyright 2020, Camilo Higuita * + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Config.h" + +#include "SetKeyboardLayoutJob.h" +#include "keyboardwidget/keyboardpreview.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Logger.h" +#include "utils/Retranslator.h" + +#include +#include +#include + +KeyboardModelsModel::KeyboardModelsModel( QObject* parent ) + : QAbstractListModel( parent ) +{ + detectModels(); +} + +void +KeyboardModelsModel::detectModels() +{ + beginResetModel(); + const auto models = KeyboardGlobal::getKeyboardModels(); + auto index = -1; + for ( const auto& key : models.keys() ) + { + index++; + m_list << QMap< QString, QString > { { "label", key }, { "key", models[ key ] } }; + if ( models[ key ] == "pc105" ) + { + this->setCurrentIndex( index ); + } + } + endResetModel(); +} + +void +KeyboardModelsModel::refresh() +{ + m_list.clear(); + setCurrentIndex( -1 ); + detectModels(); +} + +QVariant +KeyboardModelsModel::data( const QModelIndex& index, int role ) const +{ + if ( !index.isValid() ) + { + return QVariant(); + } + const auto item = m_list.at( index.row() ); + return role == Qt::DisplayRole ? item[ "label" ] : item[ "key" ]; +} + +int +KeyboardModelsModel::rowCount( const QModelIndex& ) const +{ + return m_list.count(); +} + +QHash< int, QByteArray > +KeyboardModelsModel::roleNames() const +{ + return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } }; +} + +int +KeyboardModelsModel::currentIndex() const +{ + return m_currentIndex; +} + +const QMap< QString, QString > +KeyboardModelsModel::item( const int& index ) const +{ + if ( index >= m_list.count() || index < 0 ) + { + return QMap< QString, QString >(); + } + + return m_list.at( index ); +} + +const QMap< QString, QString > +KeyboardVariantsModel::item( const int& index ) const +{ + if ( index >= m_list.count() || index < 0 ) + { + return QMap< QString, QString >(); + } + + return m_list.at( index ); +} + +void +KeyboardModelsModel::setCurrentIndex( const int& index ) +{ + if ( index >= m_list.count() || index < 0 ) + { + return; + } + + m_currentIndex = index; + emit currentIndexChanged( m_currentIndex ); +} + +KeyboardVariantsModel::KeyboardVariantsModel( QObject* parent ) + : QAbstractListModel( parent ) +{ +} + +int +KeyboardVariantsModel::currentIndex() const +{ + return m_currentIndex; +} + +void +KeyboardVariantsModel::setCurrentIndex( const int& index ) +{ + if ( index >= m_list.count() || index < 0 ) + { + return; + } + + m_currentIndex = index; + emit currentIndexChanged( m_currentIndex ); +} + +QVariant +KeyboardVariantsModel::data( const QModelIndex& index, int role ) const +{ + if ( !index.isValid() ) + { + return QVariant(); + } + const auto item = m_list.at( index.row() ); + return role == Qt::DisplayRole ? item[ "label" ] : item[ "key" ]; +} + +int +KeyboardVariantsModel::rowCount( const QModelIndex& ) const +{ + return m_list.count(); +} + +QHash< int, QByteArray > +KeyboardVariantsModel::roleNames() const +{ + return { { Qt::DisplayRole, "label" }, { Qt::UserRole, "key" } }; +} + +void +KeyboardVariantsModel::setVariants( QMap< QString, QString > variants ) +{ + m_list.clear(); + beginResetModel(); + for ( const auto& key : variants.keys() ) + { + const auto item = QMap< QString, QString > { { "label", key }, { "key", variants[ key ] } }; + m_list << item; + } + endResetModel(); +} + +/* Returns stringlist with suitable setxkbmap command-line arguments + * to set the given @p layout and @p variant. + */ +static inline QStringList +xkbmap_args( const QString& layout, const QString& variant ) +{ + QStringList r { "-layout", layout }; + if ( !variant.isEmpty() ) + { + r << "-variant" << variant; + } + return r; +} + +Config::Config( QObject* parent ) + : QObject( parent ) + , m_keyboardModelsModel( new KeyboardModelsModel( this ) ) + , m_keyboardLayoutsModel( new KeyboardLayoutModel( this ) ) + , m_keyboardVariantsModel( new KeyboardVariantsModel( this ) ) +{ + m_setxkbmapTimer.setSingleShot( true ); + + // Connect signals and slots + connect( m_keyboardModelsModel, &KeyboardModelsModel::currentIndexChanged, [&]( int index ) { + m_selectedModel = m_keyboardModelsModel->item( index ).value( "key", "pc105" ); + // Set Xorg keyboard model + QProcess::execute( "setxkbmap", QStringList { "-model", m_selectedModel } ); + emit prettyStatusChanged(); + } ); + + connect( m_keyboardLayoutsModel, &KeyboardLayoutModel::currentIndexChanged, [&]( int index ) { + m_selectedLayout = m_keyboardLayoutsModel->item( index ).first; + updateVariants( QPersistentModelIndex( m_keyboardLayoutsModel->index( index ) ) ); + emit prettyStatusChanged(); + } ); + + connect( m_keyboardVariantsModel, &KeyboardVariantsModel::currentIndexChanged, [&]( int index ) { + m_selectedVariant = m_keyboardVariantsModel->item( index )[ "key" ]; + + // Set Xorg keyboard layout + if ( m_setxkbmapTimer.isActive() ) + { + m_setxkbmapTimer.stop(); + m_setxkbmapTimer.disconnect( this ); + } + + connect( &m_setxkbmapTimer, &QTimer::timeout, this, [=] { + QProcess::execute( "setxkbmap", xkbmap_args( m_selectedLayout, m_selectedVariant ) ); + cDebug() << "xkbmap selection changed to: " << m_selectedLayout << '-' << m_selectedVariant; + m_setxkbmapTimer.disconnect( this ); + } ); + m_setxkbmapTimer.start( QApplication::keyboardInputInterval() ); + emit prettyStatusChanged(); + } ); +} + +KeyboardModelsModel* +Config::keyboardModels() const +{ + return m_keyboardModelsModel; +} + +KeyboardLayoutModel* +Config::keyboardLayouts() const +{ + return m_keyboardLayoutsModel; +} + +KeyboardVariantsModel* +Config::keyboardVariants() const +{ + return m_keyboardVariantsModel; +} + +static QPersistentModelIndex +findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) +{ + QPersistentModelIndex currentLayoutItem; + + for ( int i = 0; i < klm->rowCount(); ++i ) + { + QModelIndex idx = klm->index( i ); + if ( idx.isValid() && idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() == currentLayout ) + { + currentLayoutItem = idx; + } + } + + return currentLayoutItem; +} + +void +Config::init() +{ + //### Detect current keyboard layout and variant + QString currentLayout; + QString currentVariant; + QProcess process; + process.start( "setxkbmap", QStringList() << "-print" ); + + if ( process.waitForFinished() ) + { + const QStringList list = QString( process.readAll() ).split( "\n", QString::SkipEmptyParts ); + + for ( QString line : list ) + { + line = line.trimmed(); + if ( !line.startsWith( "xkb_symbols" ) ) + { + continue; + } + + line = line.remove( "}" ).remove( "{" ).remove( ";" ); + line = line.mid( line.indexOf( "\"" ) + 1 ); + + QStringList split = line.split( "+", QString::SkipEmptyParts ); + if ( split.size() >= 2 ) + { + currentLayout = split.at( 1 ); + + if ( currentLayout.contains( "(" ) ) + { + int parenthesisIndex = currentLayout.indexOf( "(" ); + currentVariant = currentLayout.mid( parenthesisIndex + 1 ).trimmed(); + currentVariant.chop( 1 ); + currentLayout = currentLayout.mid( 0, parenthesisIndex ).trimmed(); + } + + break; + } + } + } + + //### Layouts and Variants + QPersistentModelIndex currentLayoutItem = findLayout( m_keyboardLayoutsModel, currentLayout ); + if ( !currentLayoutItem.isValid() && ( ( currentLayout == "latin" ) || ( currentLayout == "pc" ) ) ) + { + currentLayout = "us"; + currentLayoutItem = findLayout( m_keyboardLayoutsModel, currentLayout ); + } + + // Set current layout and variant + if ( currentLayoutItem.isValid() ) + { + m_keyboardLayoutsModel->setCurrentIndex( currentLayoutItem.row() ); + updateVariants( currentLayoutItem, currentVariant ); + } + + // Default to the first available layout if none was set + // Do this after unblocking signals so we get the default variant handling. + if ( !currentLayoutItem.isValid() && m_keyboardLayoutsModel->rowCount() > 0 ) + { + m_keyboardLayoutsModel->setCurrentIndex( m_keyboardLayoutsModel->index( 0 ).row() ); + } +} + +QString +Config::prettyStatus() const +{ + QString status; + status += tr( "Set keyboard model to %1.
" ) + .arg( m_keyboardModelsModel->item( m_keyboardModelsModel->currentIndex() )[ "label" ] ); + + QString layout = m_keyboardLayoutsModel->item( m_keyboardLayoutsModel->currentIndex() ).second.description; + QString variant = m_keyboardVariantsModel->currentIndex() >= 0 + ? m_keyboardVariantsModel->item( m_keyboardVariantsModel->currentIndex() )[ "label" ] + : QString( "" ); + status += tr( "Set keyboard layout to %1/%2." ).arg( layout, variant ); + + return status; +} + +Calamares::JobList +Config::createJobs( const QString& xOrgConfFileName, const QString& convertedKeymapPath, bool writeEtcDefaultKeyboard ) +{ + QList< Calamares::job_ptr > list; + + Calamares::Job* j = new SetKeyboardLayoutJob( m_selectedModel, + m_selectedLayout, + m_selectedVariant, + xOrgConfFileName, + convertedKeymapPath, + writeEtcDefaultKeyboard ); + list.append( Calamares::job_ptr( j ) ); + + return list; +} + +void +Config::guessLayout( const QStringList& langParts ) +{ + bool foundCountryPart = false; + for ( auto countryPart = langParts.rbegin(); !foundCountryPart && countryPart != langParts.rend(); ++countryPart ) + { + cDebug() << Logger::SubEntry << "looking for locale part" << *countryPart; + for ( int i = 0; i < m_keyboardLayoutsModel->rowCount(); ++i ) + { + QModelIndex idx = m_keyboardLayoutsModel->index( i ); + QString name + = idx.isValid() ? idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() : QString(); + if ( idx.isValid() && ( name.compare( *countryPart, Qt::CaseInsensitive ) == 0 ) ) + { + cDebug() << Logger::SubEntry << "matched" << name; + m_keyboardLayoutsModel->setCurrentIndex( i ); + foundCountryPart = true; + break; + } + } + if ( foundCountryPart ) + { + ++countryPart; + if ( countryPart != langParts.rend() ) + { + cDebug() << "Next level:" << *countryPart; + for ( int variantnumber = 0; variantnumber < m_keyboardVariantsModel->rowCount(); ++variantnumber ) + { + if ( m_keyboardVariantsModel->item( variantnumber )[ "key" ].compare( *countryPart, + Qt::CaseInsensitive ) ) + { + m_keyboardVariantsModel->setCurrentIndex( variantnumber ); + cDebug() << Logger::SubEntry << "matched variant" + << m_keyboardVariantsModel->item( variantnumber )[ "key" ] << ' ' + << m_keyboardVariantsModel->item( variantnumber )[ "key" ]; + } + } + } + } + } +} + +void +Config::onActivate() +{ + /* Guessing a keyboard layout based on the locale means + * mapping between language identifiers in _ + * format to keyboard mappings, which are _ + * format; in addition, some countries have multiple languages, + * so fr_BE and nl_BE want different layouts (both Belgian) + * and sometimes the language-country name doesn't match the + * keyboard-country name at all (e.g. Ellas vs. Greek). + * + * This is a table of language-to-keyboard mappings. The + * language identifier is the key, while the value is + * a string that is used instead of the real language + * identifier in guessing -- so it should be something + * like _. + */ + static constexpr char arabic[] = "ara"; + static const auto specialCaseMap = QMap< std::string, std::string >( { + /* Most Arab countries map to Arabic keyboard (Default) */ + { "ar_AE", arabic }, + { "ar_BH", arabic }, + { "ar_DZ", arabic }, + { "ar_EG", arabic }, + { "ar_IN", arabic }, + { "ar_IQ", arabic }, + { "ar_JO", arabic }, + { "ar_KW", arabic }, + { "ar_LB", arabic }, + { "ar_LY", arabic }, + /* Not Morocco: use layout ma */ + { "ar_OM", arabic }, + { "ar_QA", arabic }, + { "ar_SA", arabic }, + { "ar_SD", arabic }, + { "ar_SS", arabic }, + /* Not Syria: use layout sy */ + { "ar_TN", arabic }, + { "ar_YE", arabic }, + { "ca_ES", "cat_ES" }, /* Catalan */ + { "as_ES", "ast_ES" }, /* Asturian */ + { "en_CA", "eng_CA" }, /* Canadian English */ + { "el_CY", "gr" }, /* Greek in Cyprus */ + { "el_GR", "gr" }, /* Greek in Greeze */ + { "ig_NG", "igbo_NG" }, /* Igbo in Nigeria */ + { "ha_NG", "hausa_NG" } /* Hausa */ + } ); + + // Try to preselect a layout, depending on language and locale + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QString lang = gs->value( "localeConf" ).toMap().value( "LANG" ).toString(); + + cDebug() << "Got locale language" << lang; + if ( !lang.isEmpty() ) + { + // Chop off .codeset and @modifier + int index = lang.indexOf( '.' ); + if ( index >= 0 ) + { + lang.truncate( index ); + } + index = lang.indexOf( '@' ); + if ( index >= 0 ) + { + lang.truncate( index ); + } + + lang.replace( '-', '_' ); // Normalize separators + } + if ( !lang.isEmpty() ) + { + std::string lang_s = lang.toStdString(); + if ( specialCaseMap.contains( lang_s ) ) + { + QString newLang = QString::fromStdString( specialCaseMap.value( lang_s ) ); + cDebug() << Logger::SubEntry << "special case language" << lang << "becomes" << newLang; + lang = newLang; + } + } + if ( !lang.isEmpty() ) + { + const auto langParts = lang.split( '_', QString::SkipEmptyParts ); + + // Note that this his string is not fit for display purposes! + // It doesn't come from QLocale::nativeCountryName. + QString country = QLocale::countryToString( QLocale( lang ).country() ); + cDebug() << Logger::SubEntry << "extracted country" << country << "::" << langParts; + + guessLayout( langParts ); + } +} + +void +Config::finalize() +{ + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( !m_selectedLayout.isEmpty() ) + { + gs->insert( "keyboardLayout", m_selectedLayout ); + gs->insert( "keyboardVariant", m_selectedVariant ); //empty means default variant + } + + //FIXME: also store keyboard model for something? +} + +void +Config::updateVariants( const QPersistentModelIndex& currentItem, QString currentVariant ) +{ + const auto variants = m_keyboardLayoutsModel->item( currentItem.row() ).second.variants; + m_keyboardVariantsModel->setVariants( variants ); + + auto index = -1; + for ( const auto& key : variants.keys() ) + { + index++; + if ( variants[ key ] == currentVariant ) + { + m_keyboardVariantsModel->setCurrentIndex( index ); + return; + } + } +} diff --git a/src/modules/keyboard/Config.h b/src/modules/keyboard/Config.h new file mode 100644 index 000000000..a05a5ab86 --- /dev/null +++ b/src/modules/keyboard/Config.h @@ -0,0 +1,133 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * Copyright 2020, Camilo Higuita + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef KEYBOARD_CONFIG_H +#define KEYBOARD_CONFIG_H + +#include "Job.h" +#include "KeyboardLayoutModel.h" + +#include +#include +#include +#include +#include + +class KeyboardModelsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY( int currentIndex WRITE setCurrentIndex READ currentIndex NOTIFY currentIndexChanged ) + +public: + explicit KeyboardModelsModel( QObject* parent = nullptr ); + int rowCount( const QModelIndex& = QModelIndex() ) const override; + QVariant data( const QModelIndex& index, int role ) const override; + + void setCurrentIndex( const int& index ); + int currentIndex() const; + const QMap< QString, QString > item( const int& index ) const; + +public slots: + void refresh(); + +protected: + QHash< int, QByteArray > roleNames() const override; + +private: + int m_currentIndex = -1; + QVector< QMap< QString, QString > > m_list; + void detectModels(); + +signals: + void currentIndexChanged( int index ); +}; + +class KeyboardVariantsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY( int currentIndex WRITE setCurrentIndex READ currentIndex NOTIFY currentIndexChanged ) + +public: + explicit KeyboardVariantsModel( QObject* parent = nullptr ); + void setVariants( QMap< QString, QString > variants ); + + int rowCount( const QModelIndex& = QModelIndex() ) const override; + QVariant data( const QModelIndex& index, int role ) const override; + + void setCurrentIndex( const int& index ); + int currentIndex() const; + + const QMap< QString, QString > item( const int& index ) const; + +protected: + QHash< int, QByteArray > roleNames() const override; + +private: + int m_currentIndex = -1; + QVector< QMap< QString, QString > > m_list; + +signals: + void currentIndexChanged( int index ); +}; + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY( KeyboardModelsModel* keyboardModelsModel READ keyboardModels CONSTANT FINAL ) + Q_PROPERTY( KeyboardLayoutModel* keyboardLayoutsModel READ keyboardLayouts CONSTANT FINAL ) + Q_PROPERTY( KeyboardVariantsModel* keyboardVariantsModel READ keyboardVariants CONSTANT FINAL ) + Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL ) + +public: + Config( QObject* parent = nullptr ); + + void init(); + + Calamares::JobList + createJobs( const QString& xOrgConfFileName, const QString& convertedKeymapPath, bool writeEtcDefaultKeyboard ); + QString prettyStatus() const; + + void onActivate(); + void finalize(); + +private: + void guessLayout( const QStringList& langParts ); + void updateVariants( const QPersistentModelIndex& currentItem, QString currentVariant = QString() ); + + KeyboardModelsModel* m_keyboardModelsModel; + KeyboardLayoutModel* m_keyboardLayoutsModel; + KeyboardVariantsModel* m_keyboardVariantsModel; + + QString m_selectedLayout; + QString m_selectedModel; + QString m_selectedVariant; + QTimer m_setxkbmapTimer; + +protected: + KeyboardModelsModel* keyboardModels() const; + KeyboardLayoutModel* keyboardLayouts() const; + KeyboardVariantsModel* keyboardVariants() const; + + +signals: + void prettyStatusChanged(); +}; + + +#endif diff --git a/src/modules/keyboard/KeyboardLayoutModel.cpp b/src/modules/keyboard/KeyboardLayoutModel.cpp index 0abd89ae2..7e24df570 100644 --- a/src/modules/keyboard/KeyboardLayoutModel.cpp +++ b/src/modules/keyboard/KeyboardLayoutModel.cpp @@ -28,7 +28,6 @@ KeyboardLayoutModel::KeyboardLayoutModel( QObject* parent ) init(); } - int KeyboardLayoutModel::rowCount( const QModelIndex& parent ) const { @@ -41,7 +40,9 @@ QVariant KeyboardLayoutModel::data( const QModelIndex& index, int role ) const { if ( !index.isValid() ) + { return QVariant(); + } switch ( role ) { @@ -56,19 +57,54 @@ KeyboardLayoutModel::data( const QModelIndex& index, int role ) const return QVariant(); } +const QPair< QString, KeyboardGlobal::KeyboardInfo > +KeyboardLayoutModel::item( const int& index ) const +{ + if ( index >= m_layouts.count() || index < 0 ) + { + return QPair< QString, KeyboardGlobal::KeyboardInfo >(); + } + + return m_layouts.at( index ); +} void KeyboardLayoutModel::init() { - KeyboardGlobal::LayoutsMap layouts = - KeyboardGlobal::getKeyboardLayouts(); - for ( KeyboardGlobal::LayoutsMap::const_iterator it = layouts.constBegin(); - it != layouts.constEnd(); ++it ) - m_layouts.append( qMakePair( it.key(), it.value() ) ); - - std::stable_sort( m_layouts.begin(), m_layouts.end(), []( const QPair< QString, KeyboardGlobal::KeyboardInfo >& a, - const QPair< QString, KeyboardGlobal::KeyboardInfo >& b ) + KeyboardGlobal::LayoutsMap layouts = KeyboardGlobal::getKeyboardLayouts(); + for ( KeyboardGlobal::LayoutsMap::const_iterator it = layouts.constBegin(); it != layouts.constEnd(); ++it ) { - return a.second.description < b.second.description; - } ); + m_layouts.append( qMakePair( it.key(), it.value() ) ); + } + + std::stable_sort( m_layouts.begin(), + m_layouts.end(), + []( const QPair< QString, KeyboardGlobal::KeyboardInfo >& a, + const QPair< QString, KeyboardGlobal::KeyboardInfo >& b ) { + return a.second.description < b.second.description; + } ); +} + +QHash< int, QByteArray > +KeyboardLayoutModel::roleNames() const +{ + return { { Qt::DisplayRole, "label" }, { KeyboardLayoutKeyRole, "key" }, { KeyboardVariantsRole, "variants" } }; +} + +void +KeyboardLayoutModel::setCurrentIndex( const int& index ) +{ + if ( index >= m_layouts.count() || index < 0 ) + { + return; + } + + m_currentIndex = index; + emit currentIndexChanged( m_currentIndex ); +} + +int +KeyboardLayoutModel::currentIndex() const +{ + return m_currentIndex; } diff --git a/src/modules/keyboard/KeyboardLayoutModel.h b/src/modules/keyboard/KeyboardLayoutModel.h index 27cb1d031..e0007fdbc 100644 --- a/src/modules/keyboard/KeyboardLayoutModel.h +++ b/src/modules/keyboard/KeyboardLayoutModel.h @@ -24,10 +24,12 @@ #include #include #include +#include class KeyboardLayoutModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY( int currentIndex WRITE setCurrentIndex READ currentIndex NOTIFY currentIndexChanged ) public: enum Roles : int @@ -42,10 +44,20 @@ public: QVariant data( const QModelIndex& index, int role ) const override; + void setCurrentIndex( const int& index ); + int currentIndex() const; + const QPair< QString, KeyboardGlobal::KeyboardInfo > item( const int& index ) const; + +protected: + QHash< int, QByteArray > roleNames() const override; + private: void init(); - + int m_currentIndex = -1; QList< QPair< QString, KeyboardGlobal::KeyboardInfo > > m_layouts; + +signals: + void currentIndexChanged( int index ); }; -#endif // KEYBOARDLAYOUTMODEL_H +#endif // KEYBOARDLAYOUTMODEL_H diff --git a/src/modules/keyboard/KeyboardPage.cpp b/src/modules/keyboard/KeyboardPage.cpp index 4509a5dbd..21e55d5d0 100644 --- a/src/modules/keyboard/KeyboardPage.cpp +++ b/src/modules/keyboard/KeyboardPage.cpp @@ -23,10 +23,10 @@ #include "KeyboardPage.h" -#include "ui_KeyboardPage.h" -#include "keyboardwidget/keyboardpreview.h" -#include "SetKeyboardLayoutJob.h" #include "KeyboardLayoutModel.h" +#include "SetKeyboardLayoutJob.h" +#include "keyboardwidget/keyboardpreview.h" +#include "ui_KeyboardPage.h" #include "GlobalStorage.h" #include "JobQueue.h" @@ -45,9 +45,7 @@ public: virtual ~LayoutItem(); }; -LayoutItem::~LayoutItem() -{ -} +LayoutItem::~LayoutItem() {} static QPersistentModelIndex findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) @@ -57,9 +55,10 @@ findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) for ( int i = 0; i < klm->rowCount(); ++i ) { QModelIndex idx = klm->index( i ); - if ( idx.isValid() && - idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() == currentLayout ) + if ( idx.isValid() && idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() == currentLayout ) + { currentLayoutItem = idx; + } } return currentLayoutItem; @@ -79,24 +78,19 @@ KeyboardPage::KeyboardPage( QWidget* parent ) m_setxkbmapTimer.setSingleShot( true ); // Connect signals and slots - connect( ui->listVariant, &QListWidget::currentItemChanged, - this, &KeyboardPage::onListVariantCurrentItemChanged ); + connect( ui->listVariant, &QListWidget::currentItemChanged, this, &KeyboardPage::onListVariantCurrentItemChanged ); - connect( ui->buttonRestore, &QPushButton::clicked, - [this] - { - ui->comboBoxModel->setCurrentIndex( m_defaultIndex ); - } ); + connect( + ui->buttonRestore, &QPushButton::clicked, [this] { ui->comboBoxModel->setCurrentIndex( m_defaultIndex ); } ); connect( ui->comboBoxModel, static_cast< void ( QComboBox::* )( const QString& ) >( &QComboBox::currentIndexChanged ), - [this]( const QString& text ) - { - QString model = m_models.value( text, "pc105" ); + [this]( const QString& text ) { + QString model = m_models.value( text, "pc105" ); - // Set Xorg keyboard model - QProcess::execute( "setxkbmap", QStringList{ "-model", model } ); - } ); + // Set Xorg keyboard model + QProcess::execute( "setxkbmap", QStringList { "-model", model } ); + } ); CALAMARES_RETRANSLATE( ui->retranslateUi( this ); ) } @@ -119,18 +113,17 @@ KeyboardPage::init() if ( process.waitForFinished() ) { - const QStringList list = QString( process.readAll() ) - .split( "\n", QString::SkipEmptyParts ); + const QStringList list = QString( process.readAll() ).split( "\n", QString::SkipEmptyParts ); for ( QString line : list ) { line = line.trimmed(); if ( !line.startsWith( "xkb_symbols" ) ) + { continue; + } - line = line.remove( "}" ) - .remove( "{" ) - .remove( ";" ); + line = line.remove( "}" ).remove( "{" ).remove( ";" ); line = line.mid( line.indexOf( "\"" ) + 1 ); QStringList split = line.split( "+", QString::SkipEmptyParts ); @@ -141,12 +134,9 @@ KeyboardPage::init() if ( currentLayout.contains( "(" ) ) { int parenthesisIndex = currentLayout.indexOf( "(" ); - currentVariant = currentLayout.mid( parenthesisIndex + 1 ) - .trimmed(); + currentVariant = currentLayout.mid( parenthesisIndex + 1 ).trimmed(); currentVariant.chop( 1 ); - currentLayout = currentLayout - .mid( 0, parenthesisIndex ) - .trimmed(); + currentLayout = currentLayout.mid( 0, parenthesisIndex ).trimmed(); } break; @@ -165,7 +155,9 @@ KeyboardPage::init() mi.next(); if ( mi.value() == "pc105" ) + { m_defaultIndex = ui->comboBoxModel->count(); + } ui->comboBoxModel->addItem( mi.key() ); } @@ -180,16 +172,16 @@ KeyboardPage::init() KeyboardLayoutModel* klm = new KeyboardLayoutModel( this ); ui->listLayout->setModel( klm ); - connect( ui->listLayout->selectionModel(), &QItemSelectionModel::currentChanged, - this, &KeyboardPage::onListLayoutCurrentItemChanged ); + connect( ui->listLayout->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + &KeyboardPage::onListLayoutCurrentItemChanged ); // Block signals ui->listLayout->blockSignals( true ); QPersistentModelIndex currentLayoutItem = findLayout( klm, currentLayout ); - if ( !currentLayoutItem.isValid() && ( - ( currentLayout == "latin" ) - || ( currentLayout == "pc" ) ) ) + if ( !currentLayoutItem.isValid() && ( ( currentLayout == "latin" ) || ( currentLayout == "pc" ) ) ) { currentLayout = "us"; currentLayoutItem = findLayout( klm, currentLayout ); @@ -208,7 +200,9 @@ KeyboardPage::init() // Default to the first available layout if none was set // Do this after unblocking signals so we get the default variant handling. if ( !currentLayoutItem.isValid() && klm->rowCount() > 0 ) + { ui->listLayout->setCurrentIndex( klm->index( 0 ) ); + } } @@ -217,11 +211,11 @@ KeyboardPage::prettyStatus() const { QString status; status += tr( "Set keyboard model to %1.
" ).arg( ui->comboBoxModel->currentText() ); - + QString layout = ui->listLayout->currentIndex().data().toString(); QString variant = ui->listVariant->currentItem() ? ui->listVariant->currentItem()->text() : QString( "" ); status += tr( "Set keyboard layout to %1/%2." ).arg( layout, variant ); - + return status; } @@ -232,15 +226,14 @@ KeyboardPage::createJobs( const QString& xOrgConfFileName, bool writeEtcDefaultKeyboard ) { QList< Calamares::job_ptr > list; - QString selectedModel = m_models.value( ui->comboBoxModel->currentText(), - "pc105" ); + QString selectedModel = m_models.value( ui->comboBoxModel->currentText(), "pc105" ); Calamares::Job* j = new SetKeyboardLayoutJob( selectedModel, - m_selectedLayout, - m_selectedVariant, - xOrgConfFileName, - convertedKeymapPath, - writeEtcDefaultKeyboard ); + m_selectedLayout, + m_selectedVariant, + xOrgConfFileName, + convertedKeymapPath, + writeEtcDefaultKeyboard ); list.append( Calamares::job_ptr( j ) ); return list; @@ -258,7 +251,8 @@ KeyboardPage::guessLayout( const QStringList& langParts ) for ( int i = 0; i < klm->rowCount(); ++i ) { QModelIndex idx = klm->index( i ); - QString name = idx.isValid() ? idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() : QString(); + QString name + = idx.isValid() ? idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() : QString(); if ( idx.isValid() && ( name.compare( *countryPart, Qt::CaseInsensitive ) == 0 ) ) { cDebug() << Logger::SubEntry << "matched" << name; @@ -273,13 +267,14 @@ KeyboardPage::guessLayout( const QStringList& langParts ) if ( countryPart != langParts.rend() ) { cDebug() << "Next level:" << *countryPart; - for (int variantnumber = 0; variantnumber < ui->listVariant->count(); ++variantnumber) + for ( int variantnumber = 0; variantnumber < ui->listVariant->count(); ++variantnumber ) { - LayoutItem *variantdata = dynamic_cast< LayoutItem* >( ui->listVariant->item( variantnumber ) ); - if ( variantdata && (variantdata->data.compare( *countryPart, Qt::CaseInsensitive ) == 0) ) + LayoutItem* variantdata = dynamic_cast< LayoutItem* >( ui->listVariant->item( variantnumber ) ); + if ( variantdata && ( variantdata->data.compare( *countryPart, Qt::CaseInsensitive ) == 0 ) ) { ui->listVariant->setCurrentItem( variantdata ); - cDebug() << Logger::SubEntry << "matched variant" << variantdata->data << ' ' << variantdata->text(); + cDebug() << Logger::SubEntry << "matched variant" << variantdata->data << ' ' + << variantdata->text(); } } } @@ -306,7 +301,7 @@ KeyboardPage::onActivate() * like _. */ static constexpr char arabic[] = "ara"; - static const auto specialCaseMap = QMap( { + static const auto specialCaseMap = QMap< std::string, std::string >( { /* Most Arab countries map to Arabic keyboard (Default) */ { "ar_AE", arabic }, { "ar_BH", arabic }, @@ -330,8 +325,8 @@ KeyboardPage::onActivate() { "ca_ES", "cat_ES" }, /* Catalan */ { "as_ES", "ast_ES" }, /* Asturian */ { "en_CA", "eng_CA" }, /* Canadian English */ - { "el_CY", "gr" }, /* Greek in Cyprus */ - { "el_GR", "gr" }, /* Greek in Greeze */ + { "el_CY", "gr" }, /* Greek in Cyprus */ + { "el_GR", "gr" }, /* Greek in Greeze */ { "ig_NG", "igbo_NG" }, /* Igbo in Nigeria */ { "ha_NG", "hausa_NG" } /* Hausa */ } ); @@ -348,10 +343,14 @@ KeyboardPage::onActivate() // Chop off .codeset and @modifier int index = lang.indexOf( '.' ); if ( index >= 0 ) + { lang.truncate( index ); + } index = lang.indexOf( '@' ); if ( index >= 0 ) + { lang.truncate( index ); + } lang.replace( '-', '_' ); // Normalize separators } @@ -386,7 +385,7 @@ KeyboardPage::finalize() if ( !m_selectedLayout.isEmpty() ) { gs->insert( "keyboardLayout", m_selectedLayout ); - gs->insert( "keyboardVariant", m_selectedVariant ); //empty means default variant + gs->insert( "keyboardVariant", m_selectedVariant ); //empty means default variant } //FIXME: also store keyboard model for something? @@ -394,15 +393,13 @@ KeyboardPage::finalize() void -KeyboardPage::updateVariants( const QPersistentModelIndex& currentItem, - QString currentVariant ) +KeyboardPage::updateVariants( const QPersistentModelIndex& currentItem, QString currentVariant ) { // Block signals ui->listVariant->blockSignals( true ); - QMap< QString, QString > variants = - currentItem.data( KeyboardLayoutModel::KeyboardVariantsRole ) - .value< QMap< QString, QString > >(); + QMap< QString, QString > variants + = currentItem.data( KeyboardLayoutModel::KeyboardVariantsRole ).value< QMap< QString, QString > >(); QMapIterator< QString, QString > li( variants ); LayoutItem* defaultItem = nullptr; @@ -420,7 +417,9 @@ KeyboardPage::updateVariants( const QPersistentModelIndex& currentItem, // currentVariant defaults to QString(). It is only non-empty during the // initial setup. if ( li.value() == currentVariant ) + { defaultItem = item; + } } // Unblock signals @@ -428,17 +427,20 @@ KeyboardPage::updateVariants( const QPersistentModelIndex& currentItem, // Set to default value if ( defaultItem ) + { ui->listVariant->setCurrentItem( defaultItem ); + } } void -KeyboardPage::onListLayoutCurrentItemChanged( const QModelIndex& current, - const QModelIndex& previous ) +KeyboardPage::onListLayoutCurrentItemChanged( const QModelIndex& current, const QModelIndex& previous ) { Q_UNUSED( previous ) if ( !current.isValid() ) + { return; + } updateVariants( QPersistentModelIndex( current ) ); } @@ -446,11 +448,14 @@ KeyboardPage::onListLayoutCurrentItemChanged( const QModelIndex& current, /* Returns stringlist with suitable setxkbmap command-line arguments * to set the given @p layout and @p variant. */ -static inline QStringList xkbmap_args( const QString& layout, const QString& variant ) +static inline QStringList +xkbmap_args( const QString& layout, const QString& variant ) { - QStringList r{ "-layout", layout }; + QStringList r { "-layout", layout }; if ( !variant.isEmpty() ) + { r << "-variant" << variant; + } return r; } @@ -463,7 +468,9 @@ KeyboardPage::onListVariantCurrentItemChanged( QListWidgetItem* current, QListWi LayoutItem* variantItem = dynamic_cast< LayoutItem* >( current ); if ( !layoutIndex.isValid() || !variantItem ) + { return; + } QString layout = layoutIndex.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString(); QString variant = variantItem->data; @@ -480,9 +487,7 @@ KeyboardPage::onListVariantCurrentItemChanged( QListWidgetItem* current, QListWi m_setxkbmapTimer.disconnect( this ); } - connect( &m_setxkbmapTimer, &QTimer::timeout, - this, [=] - { + connect( &m_setxkbmapTimer, &QTimer::timeout, this, [=] { QProcess::execute( "setxkbmap", xkbmap_args( layout, variant ) ); cDebug() << "xkbmap selection changed to: " << layout << '-' << variant; m_setxkbmapTimer.disconnect( this ); diff --git a/src/modules/keyboard/KeyboardPage.h b/src/modules/keyboard/KeyboardPage.h index dca8b869a..1ce21787c 100644 --- a/src/modules/keyboard/KeyboardPage.h +++ b/src/modules/keyboard/KeyboardPage.h @@ -48,24 +48,20 @@ public: QString prettyStatus() const; - Calamares::JobList createJobs( const QString& xOrgConfFileName, - const QString& convertedKeymapPath, - bool writeEtcDefaultKeyboard ); + Calamares::JobList + createJobs( const QString& xOrgConfFileName, const QString& convertedKeymapPath, bool writeEtcDefaultKeyboard ); void onActivate(); void finalize(); protected slots: - void onListLayoutCurrentItemChanged( const QModelIndex& current, - const QModelIndex& previous ); - void onListVariantCurrentItemChanged( QListWidgetItem* current, - QListWidgetItem* previous ); + void onListLayoutCurrentItemChanged( const QModelIndex& current, const QModelIndex& previous ); + void onListVariantCurrentItemChanged( QListWidgetItem* current, QListWidgetItem* previous ); private: /// Guess a layout based on the split-apart locale void guessLayout( const QStringList& langParts ); - void updateVariants( const QPersistentModelIndex& currentItem, - QString currentVariant = QString() ); + void updateVariants( const QPersistentModelIndex& currentItem, QString currentVariant = QString() ); Ui::Page_Keyboard* ui; KeyBoardPreview* m_keyboardPreview; @@ -77,4 +73,4 @@ private: QTimer m_setxkbmapTimer; }; -#endif // KEYBOARDPAGE_H +#endif // KEYBOARDPAGE_H diff --git a/src/modules/keyboard/KeyboardViewStep.cpp b/src/modules/keyboard/KeyboardViewStep.cpp index 472ffbe7e..e6e89eb1c 100644 --- a/src/modules/keyboard/KeyboardViewStep.cpp +++ b/src/modules/keyboard/KeyboardViewStep.cpp @@ -23,6 +23,8 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "utils/Variant.h" + CALAMARES_PLUGIN_FACTORY_DEFINITION( KeyboardViewStepFactory, registerPlugin< KeyboardViewStep >(); ) KeyboardViewStep::KeyboardViewStep( QObject* parent ) @@ -121,11 +123,13 @@ KeyboardViewStep::onLeave() void KeyboardViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { + using namespace CalamaresUtils; + if ( configurationMap.contains( "xOrgConfFileName" ) && configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String - && !configurationMap.value( "xOrgConfFileName" ).toString().isEmpty() ) + && !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) { - m_xOrgConfFileName = configurationMap.value( "xOrgConfFileName" ).toString(); + m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" ); } else { @@ -134,9 +138,9 @@ KeyboardViewStep::setConfigurationMap( const QVariantMap& configurationMap ) if ( configurationMap.contains( "convertedKeymapPath" ) && configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String - && !configurationMap.value( "convertedKeymapPath" ).toString().isEmpty() ) + && !getString( configurationMap, "convertedKeymapPath" ).isEmpty() ) { - m_convertedKeymapPath = configurationMap.value( "convertedKeymapPath" ).toString(); + m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); } else { @@ -146,7 +150,7 @@ KeyboardViewStep::setConfigurationMap( const QVariantMap& configurationMap ) if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) && configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) { - m_writeEtcDefaultKeyboard = configurationMap.value( "writeEtcDefaultKeyboard" ).toBool(); + m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); } else { diff --git a/src/modules/keyboard/SetKeyboardLayoutJob.cpp b/src/modules/keyboard/SetKeyboardLayoutJob.cpp index 79b23d7ca..5223e8fae 100644 --- a/src/modules/keyboard/SetKeyboardLayoutJob.cpp +++ b/src/modules/keyboard/SetKeyboardLayoutJob.cpp @@ -24,24 +24,24 @@ #include "SetKeyboardLayoutJob.h" -#include "JobQueue.h" #include "GlobalStorage.h" -#include "utils/Logger.h" +#include "JobQueue.h" #include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" #include -#include #include -#include +#include #include +#include SetKeyboardLayoutJob::SetKeyboardLayoutJob( const QString& model, - const QString& layout, - const QString& variant, - const QString& xOrgConfFileName, - const QString& convertedKeymapPath, - bool writeEtcDefaultKeyboard ) + const QString& layout, + const QString& variant, + const QString& xOrgConfFileName, + const QString& convertedKeymapPath, + bool writeEtcDefaultKeyboard ) : Calamares::Job() , m_model( model ) , m_layout( layout ) @@ -56,9 +56,7 @@ SetKeyboardLayoutJob::SetKeyboardLayoutJob( const QString& model, QString SetKeyboardLayoutJob::prettyName() const { - return tr( "Set keyboard model to %1, layout to %2-%3" ).arg( m_model ) - .arg( m_layout ) - .arg( m_variant ); + return tr( "Set keyboard model to %1, layout to %2-%3" ).arg( m_model ).arg( m_layout ).arg( m_variant ); } @@ -70,13 +68,14 @@ SetKeyboardLayoutJob::findConvertedKeymap( const QString& convertedKeymapPath ) // No search path supplied, assume the distribution does not provide // converted keymaps if ( convertedKeymapPath.isEmpty() ) + { return QString(); + } QDir convertedKeymapDir( convertedKeymapPath ); QString name = m_variant.isEmpty() ? m_layout : ( m_layout + '-' + m_variant ); - if ( convertedKeymapDir.exists( name + ".map" ) - || convertedKeymapDir.exists( name + ".map.gz" ) ) + if ( convertedKeymapDir.exists( name + ".map" ) || convertedKeymapDir.exists( name + ".map.gz" ) ) { cDebug() << Logger::SubEntry << "Found converted keymap" << name; return name; @@ -101,37 +100,53 @@ SetKeyboardLayoutJob::findLegacyKeymap() const { QString line = stream.readLine().trimmed(); if ( line.isEmpty() || line.startsWith( '#' ) ) + { continue; + } QStringList mapping = line.split( '\t', QString::SkipEmptyParts ); if ( mapping.size() < 5 ) + { continue; + } int matching = 0; // Determine how well matching this entry is // We assume here that we have one X11 layout. If the UI changes to // allow more than one layout, this should change too. - if ( m_layout == mapping[1] ) - // If we got an exact match, this is best + if ( m_layout == mapping[ 1 ] ) + // If we got an exact match, this is best + { matching = 10; + } // Look for an entry whose first layout matches ours - else if ( mapping[1].startsWith( m_layout + ',' ) ) + else if ( mapping[ 1 ].startsWith( m_layout + ',' ) ) + { matching = 5; + } if ( matching > 0 ) { - if ( m_model.isEmpty() || m_model == mapping[2] ) + if ( m_model.isEmpty() || m_model == mapping[ 2 ] ) + { matching++; + } - QString mappingVariant = mapping[3]; + QString mappingVariant = mapping[ 3 ]; if ( mappingVariant == "-" ) + { mappingVariant = QString(); + } else if ( mappingVariant.startsWith( ',' ) ) + { mappingVariant.remove( 1, 0 ); + } if ( m_variant == mappingVariant ) + { matching++; + } // We ignore mapping[4], the xkb options, for now. If we ever // allow setting options in the UI, we should match them here. @@ -140,13 +155,12 @@ SetKeyboardLayoutJob::findLegacyKeymap() const // The best matching entry so far, then let's save that if ( matching >= qMax( bestMatching, 1 ) ) { - cDebug() << Logger::SubEntry << "Found legacy keymap" << mapping[0] - << "with score" << matching; + cDebug() << Logger::SubEntry << "Found legacy keymap" << mapping[ 0 ] << "with score" << matching; if ( matching > bestMatching ) { bestMatching = matching; - name = mapping[0]; + name = mapping[ 0 ]; } } } @@ -156,16 +170,16 @@ SetKeyboardLayoutJob::findLegacyKeymap() const bool -SetKeyboardLayoutJob::writeVConsoleData( const QString& vconsoleConfPath, - const QString& convertedKeymapPath ) const +SetKeyboardLayoutJob::writeVConsoleData( const QString& vconsoleConfPath, const QString& convertedKeymapPath ) const { QString keymap = findConvertedKeymap( convertedKeymapPath ); if ( keymap.isEmpty() ) + { keymap = findLegacyKeymap(); + } if ( keymap.isEmpty() ) { - cDebug() << "Trying to use X11 layout" << m_layout - << "as the virtual console layout"; + cDebug() << "Trying to use X11 layout" << m_layout << "as the virtual console layout"; keymap = m_layout; } @@ -178,10 +192,14 @@ SetKeyboardLayoutJob::writeVConsoleData( const QString& vconsoleConfPath, file.open( QIODevice::ReadOnly | QIODevice::Text ); QTextStream stream( &file ); while ( !stream.atEnd() ) + { existingLines << stream.readLine(); + } file.close(); if ( stream.status() != QTextStream::Ok ) + { return false; + } } // Write out the existing lines and replace the KEYMAP= line @@ -196,11 +214,15 @@ SetKeyboardLayoutJob::writeVConsoleData( const QString& vconsoleConfPath, found = true; } else + { stream << existingLine << '\n'; + } } // Add a KEYMAP= line if there wasn't any if ( !found ) + { stream << "KEYMAP=" << keymap << '\n'; + } stream.flush(); file.close(); @@ -218,28 +240,33 @@ SetKeyboardLayoutJob::writeX11Data( const QString& keyboardConfPath ) const QTextStream stream( &file ); stream << "# Read and parsed by systemd-localed. It's probably wise not to edit this file\n" - "# manually too freely.\n" - "Section \"InputClass\"\n" - " Identifier \"system-keyboard\"\n" - " MatchIsKeyboard \"on\"\n"; + "# manually too freely.\n" + "Section \"InputClass\"\n" + " Identifier \"system-keyboard\"\n" + " MatchIsKeyboard \"on\"\n"; if ( !m_layout.isEmpty() ) + { stream << " Option \"XkbLayout\" \"" << m_layout << "\"\n"; + } if ( !m_model.isEmpty() ) + { stream << " Option \"XkbModel\" \"" << m_model << "\"\n"; + } if ( !m_variant.isEmpty() ) + { stream << " Option \"XkbVariant\" \"" << m_variant << "\"\n"; + } stream << "EndSection\n"; stream.flush(); file.close(); - cDebug() << "Written XkbLayout" << m_layout << - "; XkbModel" << m_model << - "; XkbVariant" << m_variant << "to X.org file" << keyboardConfPath; + cDebug() << "Written XkbLayout" << m_layout << "; XkbModel" << m_model << "; XkbVariant" << m_variant + << "to X.org file" << keyboardConfPath; return ( stream.status() == QTextStream::Ok ); } @@ -253,7 +280,7 @@ SetKeyboardLayoutJob::writeDefaultKeyboardData( const QString& defaultKeyboardPa QTextStream stream( &file ); stream << "# KEYBOARD CONFIGURATION FILE\n\n" - "# Consult the keyboard(5) manual page.\n\n"; + "# Consult the keyboard(5) manual page.\n\n"; stream << "XKBMODEL=\"" << m_model << "\"\n"; stream << "XKBLAYOUT=\"" << m_layout << "\"\n"; @@ -264,10 +291,8 @@ SetKeyboardLayoutJob::writeDefaultKeyboardData( const QString& defaultKeyboardPa file.close(); - cDebug() << "Written XKBMODEL" << m_model << - "; XKBLAYOUT" << m_layout << - "; XKBVARIANT" << m_variant << - "to /etc/default/keyboard file" << defaultKeyboardPath; + cDebug() << "Written XKBMODEL" << m_model << "; XKBLAYOUT" << m_layout << "; XKBVARIANT" << m_variant + << "to /etc/default/keyboard file" << defaultKeyboardPath; return ( stream.status() == QTextStream::Ok ); } @@ -292,28 +317,33 @@ SetKeyboardLayoutJob::exec() { keyboardConfPath = m_xOrgConfFileName; while ( keyboardConfPath.startsWith( '/' ) ) + { keyboardConfPath.remove( 0, 1 ); + } keyboardConfPath = destDir.absoluteFilePath( keyboardConfPath ); xorgConfDPath = QFileInfo( keyboardConfPath ).path(); } else { xorgConfDPath = destDir.absoluteFilePath( "etc/X11/xorg.conf.d" ); - keyboardConfPath = QDir( xorgConfDPath ) - .absoluteFilePath( m_xOrgConfFileName ); + keyboardConfPath = QDir( xorgConfDPath ).absoluteFilePath( m_xOrgConfFileName ); } destDir.mkpath( xorgConfDPath ); QString defaultKeyboardPath; if ( QDir( destDir.absoluteFilePath( "etc/default" ) ).exists() ) + { defaultKeyboardPath = destDir.absoluteFilePath( "etc/default/keyboard" ); + } // Get the path to the destination's path to the converted key mappings QString convertedKeymapPath = m_convertedKeymapPath; if ( !convertedKeymapPath.isEmpty() ) { while ( convertedKeymapPath.startsWith( '/' ) ) + { convertedKeymapPath.remove( 0, 1 ); + } convertedKeymapPath = destDir.absoluteFilePath( convertedKeymapPath ); } @@ -328,8 +358,9 @@ SetKeyboardLayoutJob::exec() if ( !defaultKeyboardPath.isEmpty() && m_writeEtcDefaultKeyboard ) { if ( !writeDefaultKeyboardData( defaultKeyboardPath ) ) - return Calamares::JobResult::error( tr( "Failed to write keyboard configuration to existing /etc/default directory." ), - tr( "Failed to write to %1" ).arg( keyboardConfPath ) ); + return Calamares::JobResult::error( + tr( "Failed to write keyboard configuration to existing /etc/default directory." ), + tr( "Failed to write to %1" ).arg( keyboardConfPath ) ); } return Calamares::JobResult::ok(); diff --git a/src/modules/keyboard/SetKeyboardLayoutJob.h b/src/modules/keyboard/SetKeyboardLayoutJob.h index 37ca709ef..599642b19 100644 --- a/src/modules/keyboard/SetKeyboardLayoutJob.h +++ b/src/modules/keyboard/SetKeyboardLayoutJob.h @@ -40,8 +40,7 @@ public: private: QString findConvertedKeymap( const QString& convertedKeymapPath ) const; QString findLegacyKeymap() const; - bool writeVConsoleData( const QString& vconsoleConfPath, - const QString& convertedKeymapPath ) const; + bool writeVConsoleData( const QString& vconsoleConfPath, const QString& convertedKeymapPath ) const; bool writeX11Data( const QString& keyboardConfPath ) const; bool writeDefaultKeyboardData( const QString& defaultKeyboardPath ) const; diff --git a/src/modules/keyboardq/CMakeLists.txt b/src/modules/keyboardq/CMakeLists.txt new file mode 100644 index 000000000..f5fd2b64b --- /dev/null +++ b/src/modules/keyboardq/CMakeLists.txt @@ -0,0 +1,19 @@ +set( _keyboard ${CMAKE_CURRENT_SOURCE_DIR}/../keyboard ) + +include_directories( ${_keyboard} ) + +calamares_add_plugin( keyboardq + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + KeyboardQmlViewStep.cpp + ${_keyboard}/Config.cpp + ${_keyboard}/KeyboardLayoutModel.cpp + ${_keyboard}/SetKeyboardLayoutJob.cpp + ${_keyboard}/keyboardwidget/keyboardglobal.cpp + RESOURCES + keyboardq.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + SHARED_LIB +) diff --git a/src/modules/keyboardq/KeyboardQmlViewStep.cpp b/src/modules/keyboardq/KeyboardQmlViewStep.cpp new file mode 100644 index 000000000..783349075 --- /dev/null +++ b/src/modules/keyboardq/KeyboardQmlViewStep.cpp @@ -0,0 +1,139 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2020, Camilo Higuita + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "KeyboardQmlViewStep.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Variant.h" + +CALAMARES_PLUGIN_FACTORY_DEFINITION( KeyboardQmlViewStepFactory, registerPlugin< KeyboardQmlViewStep >(); ) + +KeyboardQmlViewStep::KeyboardQmlViewStep( QObject* parent ) + : Calamares::QmlViewStep( parent ) + , m_config( new Config( this ) ) + , m_nextEnabled( false ) + , m_writeEtcDefaultKeyboard( true ) +{ + m_config->init(); + m_nextEnabled = true; + emit nextStatusChanged( m_nextEnabled ); +} + +QString +KeyboardQmlViewStep::prettyName() const +{ + return tr( "Keyboard" ); +} + +QString +KeyboardQmlViewStep::prettyStatus() const +{ + return m_prettyStatus; +} + +bool +KeyboardQmlViewStep::isNextEnabled() const +{ + return m_nextEnabled; +} + +bool +KeyboardQmlViewStep::isBackEnabled() const +{ + return true; +} + +bool +KeyboardQmlViewStep::isAtBeginning() const +{ + return true; +} + +bool +KeyboardQmlViewStep::isAtEnd() const +{ + return true; +} + +Calamares::JobList +KeyboardQmlViewStep::jobs() const +{ + return m_jobs; +} + +void +KeyboardQmlViewStep::onActivate() +{ + m_config->onActivate(); +} + +void +KeyboardQmlViewStep::onLeave() +{ + m_config->finalize(); + m_jobs = m_config->createJobs( m_xOrgConfFileName, m_convertedKeymapPath, m_writeEtcDefaultKeyboard ); + m_prettyStatus = m_config->prettyStatus(); +} + +QObject* +KeyboardQmlViewStep::getConfig() +{ + return m_config; +} + +void +KeyboardQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + using namespace CalamaresUtils; + + if ( configurationMap.contains( "xOrgConfFileName" ) + && configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String + && !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) + { + m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" ); + } + else + { + m_xOrgConfFileName = "00-keyboard.conf"; + } + + if ( configurationMap.contains( "convertedKeymapPath" ) + && configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String + && !getString( configurationMap, "convertedKeymapPath" ).isEmpty() ) + { + m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); + } + else + { + m_convertedKeymapPath = QString(); + } + + if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) + && configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) + { + m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); + } + else + { + m_writeEtcDefaultKeyboard = true; + } + + Calamares::QmlViewStep::setConfigurationMap( configurationMap ); +} diff --git a/src/modules/keyboardq/KeyboardQmlViewStep.h b/src/modules/keyboardq/KeyboardQmlViewStep.h new file mode 100644 index 000000000..22826f2cd --- /dev/null +++ b/src/modules/keyboardq/KeyboardQmlViewStep.h @@ -0,0 +1,71 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef KEYBOARDQMLVIEWSTEP_H +#define KEYBOARDQMLVIEWSTEP_H + +#include "Config.h" + +#include +#include +#include + +#include + +class KeyboardPage; + +class PLUGINDLLEXPORT KeyboardQmlViewStep : public Calamares::QmlViewStep +{ + Q_OBJECT + +public: + explicit KeyboardQmlViewStep( QObject* parent = nullptr ); + + QString prettyName() const override; + QString prettyStatus() const override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + Calamares::JobList jobs() const override; + + void onActivate() override; + void onLeave() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + QObject* getConfig() override; + +private: + Config* m_config; + bool m_nextEnabled; + QString m_prettyStatus; + + QString m_xOrgConfFileName; + QString m_convertedKeymapPath; + bool m_writeEtcDefaultKeyboard; + + Calamares::JobList m_jobs; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( KeyboardQmlViewStepFactory ) + +#endif // KEYBOARDQMLVIEWSTEP_H diff --git a/src/modules/keyboardq/keyboardq.conf b/src/modules/keyboardq/keyboardq.conf new file mode 100644 index 000000000..ee97c3939 --- /dev/null +++ b/src/modules/keyboardq/keyboardq.conf @@ -0,0 +1,16 @@ +# NOTE: you must have ckbcomp installed and runnable +# on the live system, for keyboard layout previews. +--- +# The name of the file to write X11 keyboard settings to +# The default value is the name used by upstream systemd-localed. +# Relative paths are assumed to be relative to /etc/X11/xorg.conf.d +xOrgConfFileName: "/etc/X11/xorg.conf.d/00-keyboard.conf" + +# The path to search for keymaps converted from X11 to kbd format +# Leave this empty if the setting does not make sense on your distribution. +convertedKeymapPath: "/lib/kbd/keymaps/xkb" + +# Write keymap configuration to /etc/default/keyboard, usually +# found on Debian-related systems. +# Defaults to true if nothing is set. +#writeEtcDefaultKeyboard: true diff --git a/src/modules/keyboardq/keyboardq.qml b/src/modules/keyboardq/keyboardq.qml new file mode 100644 index 000000000..a093fe31f --- /dev/null +++ b/src/modules/keyboardq/keyboardq.qml @@ -0,0 +1,204 @@ +import io.calamares.modules 1.0 as Modules +import io.calamares.ui 1.0 + +import QtQuick 2.10 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.7 as Kirigami + +ResponsiveBase +{ + id: control + Modules.Keyboard //locale handler + { + id: _keyboard + } + + title: stackView.currentItem.title + subtitle: stackView.currentItem.subtitle + + + stackView.initialItem: Item + { + id: _keyboardModelsComponet + + property string title: qsTr("Keyboard Model") + property string subtitle: qsTr("Pick your preferred keyboard model or use the default one based on the detected hardware") + + ListViewTemplate + { + id: _keyboardModelListView + + anchors.centerIn: parent + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + currentIndex: model.currentIndex + + header: ToolButton + { + icon.name: "view-refresh" + onClicked: model.refresh() + text: qsTr("Refresh") + } + footer: RowLayout + { + width: parent.width + z: 99999 + + Button + { + Layout.fillWidth: true + text: qsTr("Layouts") + icon.name: "go-previous" + onClicked: control.stackView.push(_keyboardLayoutsComponent) + } + } + + model: _keyboard.Config.keyboardModelsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _keyboardModelListView.model.currentIndex = index + control.stackView.push(_keyboardLayoutsComponent) + } + } + } + + } + + Component + { + id: _keyboardLayoutsComponent + + Item + { + property string title: qsTr("Keyboard Layout") + property string subtitle: _keyboard.Config.prettyStatus + + ListViewTemplate + { + id: _layoutsListView + + anchors.centerIn: parent + + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + + currentIndex: model.currentIndex + footer: RowLayout + { + width: parent.width + z: 99999 + + Button + { + Layout.fillWidth: true + icon.name: "go-previous" + text: qsTr("Models") + onClicked: control.stackView.pop() + } + + Button + { + Layout.fillWidth: true + icon.name: "go-next" + text: qsTr("Variants") + onClicked: control.stackView.push(_keyboardVariantsComponent) + } + } + + model: _keyboard.Config.keyboardLayoutsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _layoutsListView.model.currentIndex = index + _layoutsListView.positionViewAtIndex(index, ListView.Center) + control.stackView.push(_keyboardVariantsComponent) + } + } + } + } + + + } + + Component + { + id: _keyboardVariantsComponent + + Item + { + property string title: qsTr("Keyboard Layout") + property string subtitle: _keyboard.Config.prettyStatus + + ListViewTemplate + { + id: _variantsListView + + anchors.centerIn: parent + + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + + currentIndex: model.currentIndex + + footerPositioning: ListView.OverlayFooter + + footer: RowLayout + { + z: 99999 + width: parent.width + + Button + { + Layout.fillWidth: true + text: qsTr("Layouts") + icon.name: "go-previous" + onClicked: control.stackView.pop() + } + } + + model: _keyboard.Config.keyboardVariantsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _variantsListView.model.currentIndex = index + _variantsListView.positionViewAtIndex(index, ListView.Center) + } + } + } + } + + } + + + TextField + { + placeholderText: qsTr("Test your keyboard") + Layout.preferredHeight: 60 + Layout.maximumWidth: 500 + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + + background: Rectangle + { + color: Kirigami.Theme.backgroundColor + radius: 5 + opacity: 0.3 + } + } + + +} diff --git a/src/modules/keyboardq/keyboardq.qrc b/src/modules/keyboardq/keyboardq.qrc new file mode 100644 index 000000000..492f6e213 --- /dev/null +++ b/src/modules/keyboardq/keyboardq.qrc @@ -0,0 +1,7 @@ + + + ../keyboard/kbd-model-map + ../keyboard/images/restore.png + keyboardq.qml + + diff --git a/src/modules/localeq/CMakeLists.txt b/src/modules/localeq/CMakeLists.txt index b9ee645fc..458f44bfb 100644 --- a/src/modules/localeq/CMakeLists.txt +++ b/src/modules/localeq/CMakeLists.txt @@ -21,7 +21,7 @@ calamares_add_plugin( localeq ${_locale}/SetTimezoneJob.cpp ${_locale}/timezonewidget/localeglobal.cpp RESOURCES - ${_locale}/locale.qrc + localeq.qrc LINK_PRIVATE_LIBRARIES calamaresui Qt5::Network diff --git a/src/modules/localeq/LocaleQmlViewStep.cpp b/src/modules/localeq/LocaleQmlViewStep.cpp index 8376b4f1d..13fbeaacc 100644 --- a/src/modules/localeq/LocaleQmlViewStep.cpp +++ b/src/modules/localeq/LocaleQmlViewStep.cpp @@ -199,5 +199,4 @@ void LocaleQmlViewStep::setConfigurationMap(const QVariantMap& configurationMap) checkRequirements(); Calamares::QmlViewStep::setConfigurationMap( configurationMap ); // call parent implementation last - setContextProperty( "Localeq", m_config ); } diff --git a/src/modules/localeq/localeq.qml b/src/modules/localeq/localeq.qml index b186a0081..534c8952c 100644 --- a/src/modules/localeq/localeq.qml +++ b/src/modules/localeq/localeq.qml @@ -1,4 +1,4 @@ -import io.calamares.modules 1.0 as Modules +import io.calamares.core 1.0 import io.calamares.ui 1.0 import QtQuick 2.10 @@ -7,107 +7,33 @@ import QtQuick.Layouts 1.3 import org.kde.kirigami 2.7 as Kirigami import QtGraphicalEffects 1.0 -ResponsiveBase +RowLayout { - id: control - - Modules.Locale //locale handler - { - id: _locale - } - - title: stackView.currentItem.title - subtitle: stackView.currentItem.subtitle - message: stackView.currentItem.message - - stackView.initialItem: Item - { - id: _regionsListComponent - - property string title: qsTr("Region") - property string subtitle: qsTr("Pick your preferred region or use the default one based on your current location") - property string message: qsTr("Select your preferred zone within your location to continue with the installation") - - ListViewTemplate - { - id: _regionListView - anchors.centerIn: parent - implicitWidth: Math.min(parent.width, 500) - implicitHeight: Math.min(contentHeight, 500) - currentIndex: model.currentIndex - model: _locale.Config.regionModel - - delegate: ListItemDelegate - { - id: _delegate - label1.text: model.label - onClicked: - { - _regionListView.model.currentIndex = index - _stackView.push(_zonesListComponent) - } - } - - footer: RowLayout - { - width: parent.width - z: 99999 - Button - { - Layout.fillWidth: true - text: qsTr("Timezones") - icon.name: "go-previous" - onClicked: control.stackView.push(_zonesListComponent) - } - } - } - } - - - Component - { - id: _zonesListComponent - - Item - { - property string title: qsTr("Timezone") - property string subtitle: _locale.Config.prettyStatus - property string message: "" - ListViewTemplate - { - id: _zonesListView - anchors.centerIn: parent - implicitWidth: Math.min(parent.width, 500) - implicitHeight: Math.min(contentHeight, 500) - currentIndex: model.currentIndex - model: _locale.Config.zonesModel - - delegate: ListItemDelegate - { - id: _delegate - label1.text: model.label - onClicked: - { - _zonesListView.model.currentIndex = index - positionViewAtIndex(index, ListView.Center) - } - } - - footer: RowLayout - { - width: parent.width - z: 99999 - - Button - { - Layout.fillWidth: true - icon.name: "go-previous" - text: qsTr("Regions") - onClicked: control.stackView.pop() - } - } - } - } - } + Rectangle { + width: parent.width / 3 + Layout.fillWidth: true + ColumnLayout { + id: regions + Repeater { + model: config.regionModel + Text { + text: label + } + } + } + } + Rectangle { + width: parent.width / 3 + Layout.fillWidth: true + ColumnLayout { + id: zones + Repeater { + model: config.zonesModel + Text { + text: label + } + } + } + } } diff --git a/src/modules/localeq/localeq.qrc b/src/modules/localeq/localeq.qrc new file mode 100644 index 000000000..e2bf12636 --- /dev/null +++ b/src/modules/localeq/localeq.qrc @@ -0,0 +1,47 @@ + + + ../locale/images/bg.png + ../locale/images/pin.png + ../locale/images/timezone_0.0.png + ../locale/images/timezone_1.0.png + ../locale/images/timezone_2.0.png + ../locale/images/timezone_3.0.png + ../locale/images/timezone_3.5.png + ../locale/images/timezone_4.0.png + ../locale/images/timezone_4.5.png + ../locale/images/timezone_5.0.png + ../locale/images/timezone_5.5.png + ../locale/images/timezone_5.75.png + ../locale/images/timezone_6.0.png + ../locale/images/timezone_6.5.png + ../locale/images/timezone_7.0.png + ../locale/images/timezone_8.0.png + ../locale/images/timezone_9.0.png + ../locale/images/timezone_9.5.png + ../locale/images/timezone_10.0.png + ../locale/images/timezone_10.5.png + ../locale/images/timezone_11.0.png + ../locale/images/timezone_11.5.png + ../locale/images/timezone_12.0.png + ../locale/images/timezone_12.75.png + ../locale/images/timezone_13.0.png + ../locale/images/timezone_-1.0.png + ../locale/images/timezone_-2.0.png + ../locale/images/timezone_-3.0.png + ../locale/images/timezone_-3.5.png + ../locale/images/timezone_-4.0.png + ../locale/images/timezone_-4.5.png + ../locale/images/timezone_-5.0.png + ../locale/images/timezone_-5.5.png + ../locale/images/timezone_-6.0.png + ../locale/images/timezone_-7.0.png + ../locale/images/timezone_-8.0.png + ../locale/images/timezone_-9.0.png + ../locale/images/timezone_-9.5.png + ../locale/images/timezone_-10.0.png + ../locale/images/timezone_-11.0.png + + + localeq.qml + +