diff --git a/src/calamares/CMakeLists.txt b/src/calamares/CMakeLists.txt index 92521f17a..259e3bf56 100644 --- a/src/calamares/CMakeLists.txt +++ b/src/calamares/CMakeLists.txt @@ -6,6 +6,7 @@ set( calamaresSources CalamaresWindow.cpp DebugWindow.cpp + VariantModel.cpp progresstree/ProgressTreeDelegate.cpp progresstree/ProgressTreeItem.cpp diff --git a/src/calamares/VariantModel.cpp b/src/calamares/VariantModel.cpp new file mode 100644 index 000000000..bdcf8f75c --- /dev/null +++ b/src/calamares/VariantModel.cpp @@ -0,0 +1,234 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, 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 . + */ + +#include "VariantModel.h" + +static void +overallLength( const QVariant& item, int& c, int parent, VariantModel::IndexVector* skiplist ) +{ + if ( skiplist ) + { + skiplist->append( parent ); + } + + parent = c++; + if ( item.canConvert< QVariantList >() ) + { + for ( const auto& subitem : item.toList() ) + { + overallLength( subitem, c, parent, skiplist ); + } + } + else if ( item.canConvert< QVariantMap >() ) + { + for ( const auto& subitem : item.toMap() ) + { + overallLength( subitem, c, parent, skiplist ); + } + } +} + +static quintptr +findNth( const VariantModel::IndexVector& skiplist, quintptr value, int n ) +{ + if ( n < 0 ) + { + return -1; + } + + int index = 0; + while ( ( n >= 0 ) && ( index < skiplist.count() ) ) + { + if ( skiplist[ index ] == value ) + { + if ( --n < 0 ) + { + return index; + } + } + index++; + } + return -1; +} + + +VariantModel::VariantModel( const QVariant* p ) + : m_p( p ) +{ + int x = 0; + overallLength( *p, x, -1, nullptr ); + m_rows.reserve( x ); + x = 0; + overallLength( *p, x, -1, &m_rows ); +} + +VariantModel::~VariantModel() {} + +int +VariantModel::columnCount( const QModelIndex& index ) const +{ + return 2; +} + +int +VariantModel::rowCount( const QModelIndex& index ) const +{ + quintptr p = index.isValid() ? index.internalId() : 0; + return m_rows.count( p ); +} + +QModelIndex +VariantModel::index( int row, int column, const QModelIndex& parent ) const +{ + quintptr p = 0; + + if ( parent.isValid() ) + { + if ( !( parent.internalId() >= m_rows.count() ) ) + { + p = parent.internalId(); + } + } + + return createIndex( row, column, findNth( m_rows, p, row ) ); +} + +QModelIndex +VariantModel::parent( const QModelIndex& index ) const +{ + if ( !index.isValid() || ( index.internalId() > m_rows.count() ) ) + { + return QModelIndex(); + } + + quintptr p = m_rows[ index.internalId() ]; + if ( p == 0 ) + { + return QModelIndex(); + } + + if ( p >= m_rows.count() ) + { + return QModelIndex(); + } + quintptr p_pid = m_rows[ p ]; + int row = 0; + for ( int i = 0; i < p; ++i ) + { + if ( m_rows[ i ] == p_pid ) + { + row++; + } + } + + return createIndex( row, index.column(), p ); +} + +QVariant +VariantModel::data( const QModelIndex& index, int role ) const +{ + if ( role != Qt::DisplayRole ) + { + return QVariant(); + } + + if ( !index.isValid() ) + { + return QVariant(); + } + + if ( ( index.column() < 0 ) || ( index.column() > 1 ) ) + { + return QVariant(); + } + + if ( index.internalId() >= m_rows.count() ) + { + return QVariant(); + } + + const QVariant thing = underlying( parent( index ) ); + + if ( !thing.isValid() ) + { + return QVariant(); + } + + if ( thing.canConvert< QVariantMap >() ) + { + QVariantMap the_map = thing.toMap(); + const auto key = the_map.keys().at( index.row() ); + if ( index.column() == 0 ) + { + return key; + } + else + { + return the_map[ key ]; + } + } + else if ( thing.canConvert< QVariantList >() ) + { + if ( index.column() == 0 ) + { + return index.row(); + } + else + { + QVariantList the_list = thing.toList(); + return the_list.at( index.row() ); + } + } + else + { + if ( index.column() == 0 ) + { + return QVariant(); + } + else + { + return thing; + } + } +} + +const QVariant +VariantModel::underlying( const QModelIndex& index ) const +{ + if ( !index.isValid() ) + { + return *m_p; + } + + const auto& thing = underlying( parent( index ) ); + if ( thing.canConvert< QVariantMap >() ) + { + const auto& the_map = thing.toMap(); + return the_map[ the_map.keys()[ index.row() ] ]; + } + else if ( thing.canConvert< QVariantList >() ) + { + return thing.toList()[ index.row() ]; + } + else + { + return thing; + } + + return QVariant(); +} diff --git a/src/calamares/VariantModel.h b/src/calamares/VariantModel.h new file mode 100644 index 000000000..9ea6f6737 --- /dev/null +++ b/src/calamares/VariantModel.h @@ -0,0 +1,74 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, 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 VARIANTMODEL_H +#define VARIANTMODEL_H + +#include +#include +#include + +/** @brief A model that operates directly on a QVariant + * + * A VariantModel operates directly on an underlying + * QVariant, treating QVariantMap and QVariantList as + * nodes with multiple children. In general, putting + * a QVariantMap into a QVariant and passing that into + * the model will get you a tree-like model of the + * VariantMap's data structure. + * + * Take care of object lifetimes and that the underlying + * QVariant does not change during use. + */ +class VariantModel : public QAbstractItemModel +{ +public: + /** @brief Auxiliary data + * + * The nodes of the tree are enumerated into a vector + * (of length equal to the number of nodes in the tree + 1) + * which are used to do index and parent calculations. + */ + using IndexVector = QVector< quintptr >; + + /** @brief Constructor + * + * The QVariant's lifetime is **not** affected by the model, + * so take care that the QVariant lives at least as long as + * the model). Also, don't change the QVariant underneath the model. + */ + VariantModel( const QVariant* p ); + + ~VariantModel() override; + + int columnCount( const QModelIndex& index ) const override; + int rowCount( const QModelIndex& index ) const override; + + QModelIndex index( int row, int column, const QModelIndex& parent ) const override; + QModelIndex parent( const QModelIndex& index ) const override; + QVariant data( const QModelIndex& index, int role ) const override; + +private: + const QVariant* const m_p; + IndexVector m_rows; + + /// @brief Implementation of walking an index through the variant-tree + const QVariant underlying( const QModelIndex& index ) const; +}; + +#endif