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 }