File indexing completed on 2024-05-19 04:50:29

0001 /****************************************************************************************
0002  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "MatchedTracksPage.h"
0018 
0019 #include "App.h"
0020 #include "core/meta/support/MetaConstants.h"
0021 #include "core/support/Debug.h"
0022 #include "statsyncing/TrackTuple.h"
0023 #include "statsyncing/models/MatchedTracksModel.h"
0024 #include "statsyncing/ui/TrackDelegate.h"
0025 
0026 #include <QEvent>
0027 #include <QMenu>
0028 #include <QPushButton>
0029 #include <QSortFilterProxyModel>
0030 #include <QStandardItemModel>
0031 
0032 // needed for QCombobox payloads:
0033 Q_DECLARE_METATYPE( StatSyncing::ProviderPtr )
0034 
0035 namespace StatSyncing
0036 {
0037     class SortFilterProxyModel : public QSortFilterProxyModel
0038     {
0039         public:
0040             SortFilterProxyModel( QObject *parent = nullptr )
0041                 : QSortFilterProxyModel( parent )
0042                 , m_tupleFilter( -1 )
0043             {
0044                 // filer all columns, accept when at least one column matches:
0045                 setFilterKeyColumn( -1 );
0046             }
0047 
0048             /**
0049              * Filter tuples based on their MatchedTracksModel::TupleFlag flag. Set to -1
0050              * to accept tuples with any flags.
0051              */
0052             void setTupleFilter( int filter )
0053             {
0054                 m_tupleFilter = filter;
0055                 invalidateFilter();
0056                 sort( sortColumn(), sortOrder() ); // this doesn't happen automatically
0057             }
0058 
0059         protected:
0060             bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override
0061             {
0062                 if( source_parent.isValid() )
0063                     return true; // we match all child items, we filter only root ones
0064                 if( m_tupleFilter != -1 )
0065                 {
0066                     QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
0067                     int flags = sourceModel()->data( index, MatchedTracksModel::TupleFlagsRole ).toInt();
0068                     if( !(flags & m_tupleFilter) )
0069                         return false;
0070                 }
0071                 return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
0072             }
0073 
0074             bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override
0075             {
0076                 if( left.parent().isValid() ) // we are comparing childs, special mode:
0077                 {
0078                     // take providers, e.g. reset column to 0
0079                     QModelIndex l = sourceModel()->index( left.row(), 0, left.parent() );
0080                     QModelIndex r = sourceModel()->index( right.row(), 0, right.parent() );
0081                     QString leftProvider = sourceModel()->data( l, Qt::DisplayRole ).toString();
0082                     QString rightProvider = sourceModel()->data( r, Qt::DisplayRole ).toString();
0083 
0084                     // make this sorting ignore the sort order, always sort ascendingly:
0085                     if( sortOrder() == Qt::AscendingOrder )
0086                         return leftProvider.localeAwareCompare( rightProvider ) < 0;
0087                     else
0088                         return leftProvider.localeAwareCompare( rightProvider ) > 0;
0089                 }
0090                 return QSortFilterProxyModel::lessThan( left, right );
0091             }
0092 
0093         private:
0094             int m_tupleFilter;
0095     };
0096 }
0097 
0098 using namespace StatSyncing;
0099 
0100 MatchedTracksPage::MatchedTracksPage( QWidget *parent, Qt::WindowFlags f )
0101     : QWidget( parent, f )
0102     , m_matchedTracksModel( nullptr )
0103 {
0104     setupUi( this );
0105     // this group box is only shown upon setTracksToScrobble() call
0106     scrobblingGroupBox->hide();
0107 
0108     m_matchedProxyModel = new SortFilterProxyModel( this );
0109     m_uniqueProxyModel = new QSortFilterProxyModel( this );
0110     m_excludedProxyModel = new QSortFilterProxyModel( this );
0111 
0112 #define SETUP_MODEL( proxyModel, name, Name ) \
0113     proxyModel->setSortLocaleAware( true ); \
0114     proxyModel->setSortCaseSensitivity( Qt::CaseInsensitive ); \
0115     proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); \
0116     connect( proxyModel, &QSortFilterProxyModel::modelReset, this, &MatchedTracksPage::refresh##Name##StatusText ); \
0117     connect( proxyModel, &QSortFilterProxyModel::rowsInserted, this, &MatchedTracksPage::refresh##Name##StatusText ); \
0118     connect( proxyModel, &QSortFilterProxyModel::rowsRemoved, this, &MatchedTracksPage::refresh##Name##StatusText ); \
0119     name##TreeView->setModel( m_##name##ProxyModel ); \
0120     name##TreeView->setItemDelegate( new TrackDelegate( name##TreeView ) ); \
0121     connect( name##FilterLine, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterFixedString ); \
0122     name##TreeView->header()->setStretchLastSection( false ); \
0123     name##TreeView->header()->setDefaultSectionSize( 80 );
0124 
0125     SETUP_MODEL( m_matchedProxyModel, matched, Matched )
0126     SETUP_MODEL( m_uniqueProxyModel, unique, Unique )
0127     SETUP_MODEL( m_excludedProxyModel, excluded, Excluded )
0128 #undef SETUP_MODEL
0129 
0130     connect( uniqueFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0131              this, &MatchedTracksPage::changeUniqueTracksProvider );
0132     connect( excludedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0133              this, &MatchedTracksPage::changeExcludedTracksProvider );
0134 
0135     QPushButton *configure = buttonBox->addButton( i18n( "Configure Synchronization..." ), QDialogButtonBox::ActionRole );
0136     connect( configure, &QPushButton::clicked, this, &MatchedTracksPage::openConfiguration );
0137 
0138     QPushButton *back = buttonBox->addButton( i18n( "Back" ), QDialogButtonBox::ActionRole );
0139 
0140     QPushButton *synchronize = buttonBox->addButton( i18n( "Synchronize" ), QDialogButtonBox::AcceptRole );
0141     synchronize->setIcon( QIcon( "document-save" ) );
0142 
0143     connect( back, &QAbstractButton::clicked, this, &MatchedTracksPage::back );
0144     connect( buttonBox, &QDialogButtonBox::accepted, this, &MatchedTracksPage::accepted );
0145     connect( buttonBox, &QDialogButtonBox::rejected, this, &MatchedTracksPage::rejected );
0146 
0147     tabWidget->setTabEnabled( 1, false );
0148     tabWidget->setTabToolTip( 1, i18n( "There are no tracks unique to one of the sources "
0149                                        "participating in the synchronization" ) );
0150     tabWidget->setTabEnabled( 2, false );
0151     tabWidget->setTabToolTip( 2, i18n( "There are no tracks excluded from "
0152                                        "synchronization" ) );
0153 
0154     QMenu *menu = new QMenu( matchedExpandButton );
0155     menu->addAction( i18n( "Expand Tracks With Conflicts" ), this, &MatchedTracksPage::expand )->setData(
0156         MatchedTracksModel::HasConflict );
0157     menu->addAction( i18n( "Expand Updated" ), this, &MatchedTracksPage::expand )->setData(
0158         MatchedTracksModel::HasUpdate );
0159     menu->addAction( i18n( "Expand All" ), this, &MatchedTracksPage::expand )->setData( 0 );
0160     matchedExpandButton->setMenu( menu );
0161 
0162     menu = new QMenu( matchedCollapseButton );
0163     menu->addAction( i18n( "Collapse Tracks Without Conflicts" ), this, &MatchedTracksPage::collapse )->setData(
0164         MatchedTracksModel::HasConflict );
0165     menu->addAction( i18n( "Collapse Not Updated" ), this, &MatchedTracksPage::collapse )->setData(
0166         MatchedTracksModel::HasUpdate );
0167     menu->addAction( i18n( "Collapse All" ), this, &MatchedTracksPage::collapse )->setData( 0 );
0168     matchedCollapseButton->setMenu( menu );
0169 }
0170 
0171 MatchedTracksPage::~MatchedTracksPage()
0172 {
0173 }
0174 
0175 void
0176 MatchedTracksPage::setProviders( const ProviderPtrList &providers )
0177 {
0178     // populate menu of the "Take Ratings From" button
0179     QMenu *takeRatingsMenu = new QMenu( matchedRatingsButton );
0180     foreach( const ProviderPtr &provider, providers )
0181     {
0182         QAction *action = takeRatingsMenu->addAction( provider->icon(), provider->prettyName(),
0183                                                       this, &MatchedTracksPage::takeRatingsFrom );
0184         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
0185     }
0186     takeRatingsMenu->addAction( i18n( "Reset All Ratings to Undecided" ), this, &MatchedTracksPage::takeRatingsFrom );
0187     matchedRatingsButton->setMenu( takeRatingsMenu );
0188     matchedRatingsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valRating ) ) );
0189 
0190     // populate menu of the "Labels" button
0191     QMenu *labelsMenu = new QMenu( matchedLabelsButton );
0192     foreach( const ProviderPtr &provider, providers )
0193     {
0194         QString text = i18nc( "%1 is collection name", "Include Labels from %1", provider->prettyName() );
0195         QAction *action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::includeLabelsFrom );
0196         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
0197 
0198         text = i18nc( "%1 is collection name", "Exclude Labels from %1", provider->prettyName() );
0199         action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::excludeLabelsFrom );
0200         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
0201     }
0202     labelsMenu->addAction( i18n( "Reset All Labels to Undecided (Don't Synchronize Them)" ),
0203                            this, &MatchedTracksPage::excludeLabelsFrom );
0204     matchedLabelsButton->setMenu( labelsMenu );
0205     matchedLabelsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valLabel ) ) );
0206 }
0207 
0208 void
0209 MatchedTracksPage::setMatchedTracksModel( MatchedTracksModel *model )
0210 {
0211     m_matchedTracksModel = model;
0212     Q_ASSERT( m_matchedTracksModel );
0213     m_matchedProxyModel->setSourceModel( m_matchedTracksModel );
0214 
0215     setHeaderSizePoliciesFromModel( matchedTreeView->header(), m_matchedTracksModel );
0216     m_matchedProxyModel->sort( 0, Qt::AscendingOrder );
0217     // initially, expand tuples with conflicts:
0218     expand( MatchedTracksModel::HasConflict );
0219 
0220     connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsAboutToBeRemoved,
0221              this, &MatchedTracksPage::rememberExpandedState );
0222     connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsInserted,
0223              this, &MatchedTracksPage::restoreExpandedState );
0224 
0225     // re-fill combo box and disable choices without tracks
0226     bool hasConflict = m_matchedTracksModel->hasConflict();
0227     matchedFilterCombo->clear();
0228     matchedFilterCombo->addItem( i18n( "All Tracks" ), -1 );
0229     matchedFilterCombo->addItem( i18n( "Updated Tracks" ), int( MatchedTracksModel::HasUpdate ) );
0230     matchedFilterCombo->addItem( i18n( "Tracks With Conflicts" ), int( MatchedTracksModel::HasConflict ) );
0231     QStandardItemModel *comboModel = dynamic_cast<QStandardItemModel *>( matchedFilterCombo->model() );
0232     int bestIndex = 0;
0233     if( comboModel )
0234     {
0235         bestIndex = 2;
0236         if( !hasConflict )
0237         {
0238             comboModel->item( 2 )->setFlags( Qt::NoItemFlags );
0239             matchedFilterCombo->setItemData( 2, i18n( "There are no tracks with conflicts" ),
0240                                         Qt::ToolTipRole );
0241             bestIndex = 1;
0242             if( !m_matchedTracksModel->hasUpdate() )
0243             {
0244                 comboModel->item( 1 )->setFlags( Qt::NoItemFlags );
0245                 matchedFilterCombo->setItemData( 1, i18n( "There are no tracks going to be "
0246                                             "updated" ), Qt::ToolTipRole );
0247                 bestIndex = 0; // no other possibility
0248             }
0249         }
0250     }
0251 
0252     matchedFilterCombo->setCurrentIndex( bestIndex );
0253     changeMatchedTracksFilter( bestIndex );
0254     connect( matchedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0255              this, &MatchedTracksPage::changeMatchedTracksFilter );
0256 
0257     matchedRatingsButton->setEnabled( hasConflict );
0258     matchedLabelsButton->setEnabled( hasConflict );
0259 }
0260 
0261 void
0262 MatchedTracksPage::addUniqueTracksModel( ProviderPtr provider, QAbstractItemModel *model )
0263 {
0264     bool first = m_uniqueTracksModels.isEmpty();
0265     m_uniqueTracksModels.insert( provider, model );
0266     uniqueFilterCombo->addItem( provider->icon(), provider->prettyName(),
0267                                 QVariant::fromValue<ProviderPtr>( provider ) );
0268 
0269     if( first )
0270     {
0271         tabWidget->setTabEnabled( 1, true );
0272         tabWidget->setTabToolTip( 1, i18n( "Tracks that are unique to their sources" ) );
0273         setHeaderSizePoliciesFromModel( uniqueTreeView->header(), model );
0274         uniqueFilterCombo->setCurrentIndex( 0 );
0275         m_uniqueProxyModel->sort( 0, Qt::AscendingOrder );
0276     }
0277 }
0278 
0279 void
0280 MatchedTracksPage::addExcludedTracksModel( ProviderPtr provider, QAbstractItemModel *model )
0281 {
0282     bool first = m_excludedTracksModels.isEmpty();
0283     m_excludedTracksModels.insert( provider, model );
0284     excludedFilterCombo->addItem( provider->icon(), provider->prettyName(),
0285                                   QVariant::fromValue<ProviderPtr>( provider ) );
0286 
0287     if( first )
0288     {
0289         tabWidget->setTabEnabled( 2, true );
0290         tabWidget->setTabToolTip( 2, i18n( "Tracks that have been excluded from "
0291                                            "synchronization due to ambiguity" ) );
0292         setHeaderSizePoliciesFromModel( excludedTreeView->header(), model );
0293         excludedFilterCombo->setCurrentIndex( 0 );
0294         m_excludedProxyModel->sort( 0, Qt::AscendingOrder );
0295     }
0296 }
0297 
0298 void
0299 MatchedTracksPage::setTracksToScrobble( const TrackList &tracksToScrobble,
0300                                         const QList<ScrobblingServicePtr> &services )
0301 {
0302     int tracks = tracksToScrobble.count();
0303     int plays = 0;
0304     foreach( const TrackPtr &track, tracksToScrobble )
0305     {
0306         plays += track->recentPlayCount();
0307     }
0308     QStringList serviceNames;
0309     foreach( const ScrobblingServicePtr &service, services )
0310     {
0311         serviceNames << "<b>" + service->prettyName() + "</b>";
0312     }
0313 
0314     if( plays )
0315     {
0316         QString playsText = i18np( "<b>One</b> play", "<b>%1</b> plays", plays );
0317         QString text = i18ncp( "%2 is the 'X plays message above'",
0318                 "%2 of <b>one</b> track will be scrobbled to %3.",
0319                 "%2 of <b>%1</b> tracks will be scrobbled to %3.", tracks, playsText,
0320                 serviceNames.join( i18nc( "comma between list words", ", " ) ) );
0321         scrobblingLabel->setText( text );
0322         scrobblingGroupBox->show();
0323     }
0324     else
0325         scrobblingGroupBox->hide();
0326 }
0327 
0328 void
0329 MatchedTracksPage::changeMatchedTracksFilter( int index )
0330 {
0331     int filter = matchedFilterCombo->itemData( index ).toInt();
0332     m_matchedProxyModel->setTupleFilter( filter );
0333 }
0334 
0335 void
0336 MatchedTracksPage::changeUniqueTracksProvider( int index )
0337 {
0338     ProviderPtr provider = uniqueFilterCombo->itemData( index ).value<ProviderPtr>();
0339     m_uniqueProxyModel->setSourceModel( m_uniqueTracksModels.value( provider ) );
0340     // trigger re-sort, Qt doesn't do that automatically apparently
0341     m_uniqueProxyModel->sort( m_uniqueProxyModel->sortColumn(), m_uniqueProxyModel->sortOrder() );
0342 }
0343 
0344 void
0345 MatchedTracksPage::changeExcludedTracksProvider( int index )
0346 {
0347     ProviderPtr provider = excludedFilterCombo->itemData( index ).value<ProviderPtr>();
0348     m_excludedProxyModel->setSourceModel( m_excludedTracksModels.value( provider ) );
0349     // trigger re-sort, Qt doesn't do that automatically apparently
0350     m_excludedProxyModel->sort( m_excludedProxyModel->sortColumn(), m_excludedProxyModel->sortOrder() );
0351 }
0352 
0353 void
0354 MatchedTracksPage::refreshMatchedStatusText()
0355 {
0356     refreshStatusTextHelper( m_matchedProxyModel, matchedStatusBar );
0357 }
0358 
0359 void
0360 MatchedTracksPage::refreshUniqueStatusText()
0361 {
0362     refreshStatusTextHelper( m_uniqueProxyModel, uniqueStatusBar );
0363 }
0364 
0365 void
0366 MatchedTracksPage::refreshExcludedStatusText()
0367 {
0368     refreshStatusTextHelper( m_excludedProxyModel, excludedStatusBar );
0369 }
0370 
0371 void
0372 MatchedTracksPage::refreshStatusTextHelper( QSortFilterProxyModel *topModel , QLabel *label )
0373 {
0374     int bottomModelRows = topModel->sourceModel() ?
0375         topModel->sourceModel()->rowCount() : 0;
0376     int topModelRows = topModel->rowCount();
0377 
0378     QString bottomText = i18np( "%1 track", "%1 tracks", bottomModelRows );
0379     if( topModelRows == bottomModelRows )
0380         label->setText( bottomText );
0381     else
0382     {
0383         QString text = i18nc( "%2 is the above '%1 track(s)' message", "Showing %1 out "
0384             "of %2", topModelRows, bottomText );
0385         label->setText( text );
0386     }
0387 }
0388 
0389 void
0390 MatchedTracksPage::rememberExpandedState( const QModelIndex &parent, int start, int end )
0391 {
0392     if( parent.isValid() )
0393         return;
0394     for( int topModelRow = start; topModelRow <= end; topModelRow++ )
0395     {
0396         QModelIndex topModelIndex = m_matchedProxyModel->index( topModelRow, 0 );
0397         int bottomModelRow = m_matchedProxyModel->mapToSource( topModelIndex ).row();
0398         if( matchedTreeView->isExpanded( topModelIndex ) )
0399             m_expandedTuples.insert( bottomModelRow );
0400         else
0401             m_expandedTuples.remove( bottomModelRow );
0402     }
0403 }
0404 
0405 void
0406 MatchedTracksPage::restoreExpandedState( const QModelIndex &parent, int start, int end )
0407 {
0408     if( parent.isValid() )
0409         return;
0410     for( int topModelRow = start; topModelRow <= end; topModelRow++ )
0411     {
0412         QModelIndex topIndex = m_matchedProxyModel->index( topModelRow, 0 );
0413         int bottomModelRow = m_matchedProxyModel->mapToSource( topIndex ).row();
0414         if( m_expandedTuples.contains( bottomModelRow ) )
0415             matchedTreeView->expand( topIndex );
0416     }
0417 }
0418 
0419 void
0420 MatchedTracksPage::takeRatingsFrom()
0421 {
0422     QAction *action = qobject_cast<QAction *>( sender() );
0423     if( !action )
0424     {
0425         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
0426         return;
0427     }
0428 
0429     // provider may be null, it means "reset all ratings to undecided"
0430     ProviderPtr provider = action->data().value<ProviderPtr>();
0431     m_matchedTracksModel->takeRatingsFrom( provider );
0432 }
0433 
0434 void
0435 MatchedTracksPage::includeLabelsFrom()
0436 {
0437     QAction *action = qobject_cast<QAction *>( sender() );
0438     if( !action )
0439     {
0440         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
0441         return;
0442     }
0443 
0444     ProviderPtr provider = action->data().value<ProviderPtr>();
0445     if( provider ) // no sense with null provider
0446         m_matchedTracksModel->includeLabelsFrom( provider );
0447 }
0448 
0449 void
0450 MatchedTracksPage::excludeLabelsFrom()
0451 {
0452     QAction *action = qobject_cast<QAction *>( sender() );
0453     if( !action )
0454     {
0455         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
0456         return;
0457     }
0458 
0459     // provider may be null, it means "reset all labels to undecided"
0460     ProviderPtr provider = action->data().value<ProviderPtr>();
0461     m_matchedTracksModel->excludeLabelsFrom( provider );
0462 }
0463 
0464 void
0465 MatchedTracksPage::expand( int onlyWithTupleFlags )
0466 {
0467     if( onlyWithTupleFlags < 0 )
0468     {
0469         QAction *action = qobject_cast<QAction *>( sender() );
0470         if( action )
0471             onlyWithTupleFlags = action->data().toInt();
0472         else
0473             onlyWithTupleFlags = 0;
0474     }
0475 
0476     for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
0477     {
0478         QModelIndex idx = m_matchedProxyModel->index( i, 0 );
0479         if( matchedTreeView->isExpanded( idx ) )
0480             continue;
0481 
0482         int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
0483         if( ( flags & onlyWithTupleFlags ) == onlyWithTupleFlags )
0484             matchedTreeView->expand( idx );
0485     }
0486 }
0487 
0488 void
0489 MatchedTracksPage::collapse()
0490 {
0491     int excludingFlags;
0492     QAction *action = qobject_cast<QAction *>( sender() );
0493     if( action )
0494         excludingFlags = action->data().toInt();
0495     else
0496         excludingFlags = 0;
0497 
0498     for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
0499     {
0500         QModelIndex idx = m_matchedProxyModel->index( i, 0 );
0501         if( !matchedTreeView->isExpanded( idx ) )
0502             continue;
0503 
0504         int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
0505         if( ( flags & excludingFlags ) == 0 )
0506             matchedTreeView->collapse( idx );
0507     }
0508 }
0509 
0510 void
0511 MatchedTracksPage::openConfiguration()
0512 {
0513     App *app = pApp;
0514     if( app )
0515         app->slotConfigAmarok( QStringLiteral("MetadataConfig") );
0516 }
0517 
0518 void
0519 MatchedTracksPage::setHeaderSizePoliciesFromModel( QHeaderView *header, QAbstractItemModel *model )
0520 {
0521     for( int column = 0; column < model->columnCount(); column++ )
0522     {
0523         QVariant headerData = model->headerData( column, Qt::Horizontal,
0524                                                  CommonModel::ResizeModeRole );
0525         QHeaderView::ResizeMode mode = QHeaderView::ResizeMode( headerData.toInt() );
0526         header->setSectionResizeMode( column, mode );
0527     }
0528 }