1 /****************************************************************************************
2  * Copyright (c) 2012 Matěj Laitl <matej@laitl.cz>                                      *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #include "MatchedTracksPage.h"
18 
19 #include "App.h"
20 #include "core/meta/support/MetaConstants.h"
21 #include "core/support/Debug.h"
22 #include "statsyncing/TrackTuple.h"
23 #include "statsyncing/models/MatchedTracksModel.h"
24 #include "statsyncing/ui/TrackDelegate.h"
25 
26 #include <QEvent>
27 #include <QMenu>
28 #include <QPushButton>
29 #include <QSortFilterProxyModel>
30 #include <QStandardItemModel>
31 
32 // needed for QCombobox payloads:
33 Q_DECLARE_METATYPE( StatSyncing::ProviderPtr )
34 
35 namespace StatSyncing
36 {
37     class SortFilterProxyModel : public QSortFilterProxyModel
38     {
39         public:
SortFilterProxyModel(QObject * parent=nullptr)40             SortFilterProxyModel( QObject *parent = nullptr )
41                 : QSortFilterProxyModel( parent )
42                 , m_tupleFilter( -1 )
43             {
44                 // filer all columns, accept when at least one column matches:
45                 setFilterKeyColumn( -1 );
46             }
47 
48             /**
49              * Filter tuples based on their MatchedTracksModel::TupleFlag flag. Set to -1
50              * to accept tuples with any flags.
51              */
setTupleFilter(int filter)52             void setTupleFilter( int filter )
53             {
54                 m_tupleFilter = filter;
55                 invalidateFilter();
56                 sort( sortColumn(), sortOrder() ); // this doesn't happen automatically
57             }
58 
59         protected:
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const60             bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override
61             {
62                 if( source_parent.isValid() )
63                     return true; // we match all child items, we filter only root ones
64                 if( m_tupleFilter != -1 )
65                 {
66                     QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
67                     int flags = sourceModel()->data( index, MatchedTracksModel::TupleFlagsRole ).toInt();
68                     if( !(flags & m_tupleFilter) )
69                         return false;
70                 }
71                 return QSortFilterProxyModel::filterAcceptsRow( source_row, source_parent );
72             }
73 
lessThan(const QModelIndex & left,const QModelIndex & right) const74             bool lessThan( const QModelIndex &left, const QModelIndex &right ) const override
75             {
76                 if( left.parent().isValid() ) // we are comparing childs, special mode:
77                 {
78                     // take providers, e.g. reset column to 0
79                     QModelIndex l = sourceModel()->index( left.row(), 0, left.parent() );
80                     QModelIndex r = sourceModel()->index( right.row(), 0, right.parent() );
81                     QString leftProvider = sourceModel()->data( l, Qt::DisplayRole ).toString();
82                     QString rightProvider = sourceModel()->data( r, Qt::DisplayRole ).toString();
83 
84                     // make this sorting ignore the sort order, always sort ascendingly:
85                     if( sortOrder() == Qt::AscendingOrder )
86                         return leftProvider.localeAwareCompare( rightProvider ) < 0;
87                     else
88                         return leftProvider.localeAwareCompare( rightProvider ) > 0;
89                 }
90                 return QSortFilterProxyModel::lessThan( left, right );
91             }
92 
93         private:
94             int m_tupleFilter;
95     };
96 }
97 
98 using namespace StatSyncing;
99 
MatchedTracksPage(QWidget * parent,Qt::WindowFlags f)100 MatchedTracksPage::MatchedTracksPage( QWidget *parent, Qt::WindowFlags f )
101     : QWidget( parent, f )
102     , m_matchedTracksModel( 0 )
103 {
104     setupUi( this );
105     // this group box is only shown upon setTracksToScrobble() call
106     scrobblingGroupBox->hide();
107 
108     m_matchedProxyModel = new SortFilterProxyModel( this );
109     m_uniqueProxyModel = new QSortFilterProxyModel( this );
110     m_excludedProxyModel = new QSortFilterProxyModel( this );
111 
112 #define SETUP_MODEL( proxyModel, name, Name ) \
113     proxyModel->setSortLocaleAware( true ); \
114     proxyModel->setSortCaseSensitivity( Qt::CaseInsensitive ); \
115     proxyModel->setFilterCaseSensitivity( Qt::CaseInsensitive ); \
116     connect( proxyModel, &QSortFilterProxyModel::modelReset, this, &MatchedTracksPage::refresh##Name##StatusText ); \
117     connect( proxyModel, &QSortFilterProxyModel::rowsInserted, this, &MatchedTracksPage::refresh##Name##StatusText ); \
118     connect( proxyModel, &QSortFilterProxyModel::rowsRemoved, this, &MatchedTracksPage::refresh##Name##StatusText ); \
119     name##TreeView->setModel( m_##name##ProxyModel ); \
120     name##TreeView->setItemDelegate( new TrackDelegate( name##TreeView ) ); \
121     connect( name##FilterLine, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterFixedString ); \
122     name##TreeView->header()->setStretchLastSection( false ); \
123     name##TreeView->header()->setDefaultSectionSize( 80 );
124 
125     SETUP_MODEL( m_matchedProxyModel, matched, Matched )
126     SETUP_MODEL( m_uniqueProxyModel, unique, Unique )
127     SETUP_MODEL( m_excludedProxyModel, excluded, Excluded )
128 #undef SETUP_MODEL
129 
130     connect( uniqueFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
131              this, &MatchedTracksPage::changeUniqueTracksProvider );
132     connect( excludedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
133              this, &MatchedTracksPage::changeExcludedTracksProvider );
134 
135     QPushButton *configure = buttonBox->addButton( i18n( "Configure Synchronization..." ), QDialogButtonBox::ActionRole );
136     connect( configure, &QPushButton::clicked, this, &MatchedTracksPage::openConfiguration );
137 
138     QPushButton *back = buttonBox->addButton( i18n( "Back" ), QDialogButtonBox::ActionRole );
139 
140     QPushButton *synchronize = buttonBox->addButton( i18n( "Synchronize" ), QDialogButtonBox::AcceptRole );
141     synchronize->setIcon( QIcon( "document-save" ) );
142 
143     connect( back, &QAbstractButton::clicked, this, &MatchedTracksPage::back );
144     connect( buttonBox, &QDialogButtonBox::accepted, this, &MatchedTracksPage::accepted );
145     connect( buttonBox, &QDialogButtonBox::rejected, this, &MatchedTracksPage::rejected );
146 
147     tabWidget->setTabEnabled( 1, false );
148     tabWidget->setTabToolTip( 1, i18n( "There are no tracks unique to one of the sources "
149                                        "participating in the synchronization" ) );
150     tabWidget->setTabEnabled( 2, false );
151     tabWidget->setTabToolTip( 2, i18n( "There are no tracks excluded from "
152                                        "synchronization" ) );
153 
154     QMenu *menu = new QMenu( matchedExpandButton );
155     menu->addAction( i18n( "Expand Tracks With Conflicts" ), this, &MatchedTracksPage::expand )->setData(
156         MatchedTracksModel::HasConflict );
157     menu->addAction( i18n( "Expand Updated" ), this, &MatchedTracksPage::expand )->setData(
158         MatchedTracksModel::HasUpdate );
159     menu->addAction( i18n( "Expand All" ), this, &MatchedTracksPage::expand )->setData( 0 );
160     matchedExpandButton->setMenu( menu );
161 
162     menu = new QMenu( matchedCollapseButton );
163     menu->addAction( i18n( "Collapse Tracks Without Conflicts" ), this, &MatchedTracksPage::collapse )->setData(
164         MatchedTracksModel::HasConflict );
165     menu->addAction( i18n( "Collapse Not Updated" ), this, &MatchedTracksPage::collapse )->setData(
166         MatchedTracksModel::HasUpdate );
167     menu->addAction( i18n( "Collapse All" ), this, &MatchedTracksPage::collapse )->setData( 0 );
168     matchedCollapseButton->setMenu( menu );
169 }
170 
~MatchedTracksPage()171 MatchedTracksPage::~MatchedTracksPage()
172 {
173 }
174 
175 void
setProviders(const ProviderPtrList & providers)176 MatchedTracksPage::setProviders( const ProviderPtrList &providers )
177 {
178     // populate menu of the "Take Ratings From" button
179     QMenu *takeRatingsMenu = new QMenu( matchedRatingsButton );
180     foreach( const ProviderPtr &provider, providers )
181     {
182         QAction *action = takeRatingsMenu->addAction( provider->icon(), provider->prettyName(),
183                                                       this, &MatchedTracksPage::takeRatingsFrom );
184         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
185     }
186     takeRatingsMenu->addAction( i18n( "Reset All Ratings to Undecided" ), this, &MatchedTracksPage::takeRatingsFrom );
187     matchedRatingsButton->setMenu( takeRatingsMenu );
188     matchedRatingsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valRating ) ) );
189 
190     // populate menu of the "Labels" button
191     QMenu *labelsMenu = new QMenu( matchedLabelsButton );
192     foreach( const ProviderPtr &provider, providers )
193     {
194         QString text = i18nc( "%1 is collection name", "Include Labels from %1", provider->prettyName() );
195         QAction *action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::includeLabelsFrom );
196         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
197 
198         text = i18nc( "%1 is collection name", "Exclude Labels from %1", provider->prettyName() );
199         action = labelsMenu->addAction( provider->icon(), text, this, &MatchedTracksPage::excludeLabelsFrom );
200         action->setData( QVariant::fromValue<ProviderPtr>( provider ) );
201     }
202     labelsMenu->addAction( i18n( "Reset All Labels to Undecided (Don't Synchronize Them)" ),
203                            this, &MatchedTracksPage::excludeLabelsFrom );
204     matchedLabelsButton->setMenu( labelsMenu );
205     matchedLabelsButton->setIcon( QIcon::fromTheme( Meta::iconForField( Meta::valLabel ) ) );
206 }
207 
208 void
setMatchedTracksModel(MatchedTracksModel * model)209 MatchedTracksPage::setMatchedTracksModel( MatchedTracksModel *model )
210 {
211     m_matchedTracksModel = model;
212     Q_ASSERT( m_matchedTracksModel );
213     m_matchedProxyModel->setSourceModel( m_matchedTracksModel );
214 
215     setHeaderSizePoliciesFromModel( matchedTreeView->header(), m_matchedTracksModel );
216     m_matchedProxyModel->sort( 0, Qt::AscendingOrder );
217     // initially, expand tuples with conflicts:
218     expand( MatchedTracksModel::HasConflict );
219 
220     connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsAboutToBeRemoved,
221              this, &MatchedTracksPage::rememberExpandedState );
222     connect( m_matchedProxyModel, &StatSyncing::SortFilterProxyModel::rowsInserted,
223              this, &MatchedTracksPage::restoreExpandedState );
224 
225     // re-fill combo box and disable choices without tracks
226     bool hasConflict = m_matchedTracksModel->hasConflict();
227     matchedFilterCombo->clear();
228     matchedFilterCombo->addItem( i18n( "All Tracks" ), -1 );
229     matchedFilterCombo->addItem( i18n( "Updated Tracks" ), int( MatchedTracksModel::HasUpdate ) );
230     matchedFilterCombo->addItem( i18n( "Tracks With Conflicts" ), int( MatchedTracksModel::HasConflict ) );
231     QStandardItemModel *comboModel = dynamic_cast<QStandardItemModel *>( matchedFilterCombo->model() );
232     int bestIndex = 0;
233     if( comboModel )
234     {
235         bestIndex = 2;
236         if( !hasConflict )
237         {
238             comboModel->item( 2 )->setFlags( Qt::NoItemFlags );
239             matchedFilterCombo->setItemData( 2, i18n( "There are no tracks with conflicts" ),
240                                         Qt::ToolTipRole );
241             bestIndex = 1;
242             if( !m_matchedTracksModel->hasUpdate() )
243             {
244                 comboModel->item( 1 )->setFlags( Qt::NoItemFlags );
245                 matchedFilterCombo->setItemData( 1, i18n( "There are no tracks going to be "
246                                             "updated" ), Qt::ToolTipRole );
247                 bestIndex = 0; // no other possibility
248             }
249         }
250     }
251 
252     matchedFilterCombo->setCurrentIndex( bestIndex );
253     changeMatchedTracksFilter( bestIndex );
254     connect( matchedFilterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
255              this, &MatchedTracksPage::changeMatchedTracksFilter );
256 
257     matchedRatingsButton->setEnabled( hasConflict );
258     matchedLabelsButton->setEnabled( hasConflict );
259 }
260 
261 void
addUniqueTracksModel(ProviderPtr provider,QAbstractItemModel * model)262 MatchedTracksPage::addUniqueTracksModel( ProviderPtr provider, QAbstractItemModel *model )
263 {
264     bool first = m_uniqueTracksModels.isEmpty();
265     m_uniqueTracksModels.insert( provider, model );
266     uniqueFilterCombo->addItem( provider->icon(), provider->prettyName(),
267                                 QVariant::fromValue<ProviderPtr>( provider ) );
268 
269     if( first )
270     {
271         tabWidget->setTabEnabled( 1, true );
272         tabWidget->setTabToolTip( 1, i18n( "Tracks that are unique to their sources" ) );
273         setHeaderSizePoliciesFromModel( uniqueTreeView->header(), model );
274         uniqueFilterCombo->setCurrentIndex( 0 );
275         m_uniqueProxyModel->sort( 0, Qt::AscendingOrder );
276     }
277 }
278 
279 void
addExcludedTracksModel(ProviderPtr provider,QAbstractItemModel * model)280 MatchedTracksPage::addExcludedTracksModel( ProviderPtr provider, QAbstractItemModel *model )
281 {
282     bool first = m_excludedTracksModels.isEmpty();
283     m_excludedTracksModels.insert( provider, model );
284     excludedFilterCombo->addItem( provider->icon(), provider->prettyName(),
285                                   QVariant::fromValue<ProviderPtr>( provider ) );
286 
287     if( first )
288     {
289         tabWidget->setTabEnabled( 2, true );
290         tabWidget->setTabToolTip( 2, i18n( "Tracks that have been excluded from "
291                                            "synchronization due to ambiguity" ) );
292         setHeaderSizePoliciesFromModel( excludedTreeView->header(), model );
293         excludedFilterCombo->setCurrentIndex( 0 );
294         m_excludedProxyModel->sort( 0, Qt::AscendingOrder );
295     }
296 }
297 
298 void
setTracksToScrobble(const TrackList & tracksToScrobble,const QList<ScrobblingServicePtr> & services)299 MatchedTracksPage::setTracksToScrobble( const TrackList &tracksToScrobble,
300                                         const QList<ScrobblingServicePtr> &services )
301 {
302     int tracks = tracksToScrobble.count();
303     int plays = 0;
304     foreach( const TrackPtr &track, tracksToScrobble )
305     {
306         plays += track->recentPlayCount();
307     }
308     QStringList serviceNames;
309     foreach( const ScrobblingServicePtr &service, services )
310     {
311         serviceNames << "<b>" + service->prettyName() + "</b>";
312     }
313 
314     if( plays )
315     {
316         QString playsText = i18np( "<b>One</b> play", "<b>%1</b> plays", plays );
317         QString text = i18ncp( "%2 is the 'X plays message above'",
318                 "%2 of <b>one</b> track will be scrobbled to %3.",
319                 "%2 of <b>%1</b> tracks will be scrobbled to %3.", tracks, playsText,
320                 serviceNames.join( i18nc( "comma between list words", ", " ) ) );
321         scrobblingLabel->setText( text );
322         scrobblingGroupBox->show();
323     }
324     else
325         scrobblingGroupBox->hide();
326 }
327 
328 void
changeMatchedTracksFilter(int index)329 MatchedTracksPage::changeMatchedTracksFilter( int index )
330 {
331     int filter = matchedFilterCombo->itemData( index ).toInt();
332     m_matchedProxyModel->setTupleFilter( filter );
333 }
334 
335 void
changeUniqueTracksProvider(int index)336 MatchedTracksPage::changeUniqueTracksProvider( int index )
337 {
338     ProviderPtr provider = uniqueFilterCombo->itemData( index ).value<ProviderPtr>();
339     m_uniqueProxyModel->setSourceModel( m_uniqueTracksModels.value( provider ) );
340     // trigger re-sort, Qt doesn't do that automatically apparently
341     m_uniqueProxyModel->sort( m_uniqueProxyModel->sortColumn(), m_uniqueProxyModel->sortOrder() );
342 }
343 
344 void
changeExcludedTracksProvider(int index)345 MatchedTracksPage::changeExcludedTracksProvider( int index )
346 {
347     ProviderPtr provider = excludedFilterCombo->itemData( index ).value<ProviderPtr>();
348     m_excludedProxyModel->setSourceModel( m_excludedTracksModels.value( provider ) );
349     // trigger re-sort, Qt doesn't do that automatically apparently
350     m_excludedProxyModel->sort( m_excludedProxyModel->sortColumn(), m_excludedProxyModel->sortOrder() );
351 }
352 
353 void
refreshMatchedStatusText()354 MatchedTracksPage::refreshMatchedStatusText()
355 {
356     refreshStatusTextHelper( m_matchedProxyModel, matchedStatusBar );
357 }
358 
359 void
refreshUniqueStatusText()360 MatchedTracksPage::refreshUniqueStatusText()
361 {
362     refreshStatusTextHelper( m_uniqueProxyModel, uniqueStatusBar );
363 }
364 
365 void
refreshExcludedStatusText()366 MatchedTracksPage::refreshExcludedStatusText()
367 {
368     refreshStatusTextHelper( m_excludedProxyModel, excludedStatusBar );
369 }
370 
371 void
refreshStatusTextHelper(QSortFilterProxyModel * topModel,QLabel * label)372 MatchedTracksPage::refreshStatusTextHelper( QSortFilterProxyModel *topModel , QLabel *label )
373 {
374     int bottomModelRows = topModel->sourceModel() ?
375         topModel->sourceModel()->rowCount() : 0;
376     int topModelRows = topModel->rowCount();
377 
378     QString bottomText = i18np( "%1 track", "%1 tracks", bottomModelRows );
379     if( topModelRows == bottomModelRows )
380         label->setText( bottomText );
381     else
382     {
383         QString text = i18nc( "%2 is the above '%1 track(s)' message", "Showing %1 out "
384             "of %2", topModelRows, bottomText );
385         label->setText( text );
386     }
387 }
388 
389 void
rememberExpandedState(const QModelIndex & parent,int start,int end)390 MatchedTracksPage::rememberExpandedState( const QModelIndex &parent, int start, int end )
391 {
392     if( parent.isValid() )
393         return;
394     for( int topModelRow = start; topModelRow <= end; topModelRow++ )
395     {
396         QModelIndex topModelIndex = m_matchedProxyModel->index( topModelRow, 0 );
397         int bottomModelRow = m_matchedProxyModel->mapToSource( topModelIndex ).row();
398         if( matchedTreeView->isExpanded( topModelIndex ) )
399             m_expandedTuples.insert( bottomModelRow );
400         else
401             m_expandedTuples.remove( bottomModelRow );
402     }
403 }
404 
405 void
restoreExpandedState(const QModelIndex & parent,int start,int end)406 MatchedTracksPage::restoreExpandedState( const QModelIndex &parent, int start, int end )
407 {
408     if( parent.isValid() )
409         return;
410     for( int topModelRow = start; topModelRow <= end; topModelRow++ )
411     {
412         QModelIndex topIndex = m_matchedProxyModel->index( topModelRow, 0 );
413         int bottomModelRow = m_matchedProxyModel->mapToSource( topIndex ).row();
414         if( m_expandedTuples.contains( bottomModelRow ) )
415             matchedTreeView->expand( topIndex );
416     }
417 }
418 
419 void
takeRatingsFrom()420 MatchedTracksPage::takeRatingsFrom()
421 {
422     QAction *action = qobject_cast<QAction *>( sender() );
423     if( !action )
424     {
425         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
426         return;
427     }
428 
429     // provider may be null, it means "reset all ratings to undecided"
430     ProviderPtr provider = action->data().value<ProviderPtr>();
431     m_matchedTracksModel->takeRatingsFrom( provider );
432 }
433 
434 void
includeLabelsFrom()435 MatchedTracksPage::includeLabelsFrom()
436 {
437     QAction *action = qobject_cast<QAction *>( sender() );
438     if( !action )
439     {
440         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
441         return;
442     }
443 
444     ProviderPtr provider = action->data().value<ProviderPtr>();
445     if( provider ) // no sense with null provider
446         m_matchedTracksModel->includeLabelsFrom( provider );
447 }
448 
449 void
excludeLabelsFrom()450 MatchedTracksPage::excludeLabelsFrom()
451 {
452     QAction *action = qobject_cast<QAction *>( sender() );
453     if( !action )
454     {
455         warning() << __PRETTY_FUNCTION__ << "must only be called from QAction";
456         return;
457     }
458 
459     // provider may be null, it means "reset all labels to undecided"
460     ProviderPtr provider = action->data().value<ProviderPtr>();
461     m_matchedTracksModel->excludeLabelsFrom( provider );
462 }
463 
464 void
expand(int onlyWithTupleFlags)465 MatchedTracksPage::expand( int onlyWithTupleFlags )
466 {
467     if( onlyWithTupleFlags < 0 )
468     {
469         QAction *action = qobject_cast<QAction *>( sender() );
470         if( action )
471             onlyWithTupleFlags = action->data().toInt();
472         else
473             onlyWithTupleFlags = 0;
474     }
475 
476     for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
477     {
478         QModelIndex idx = m_matchedProxyModel->index( i, 0 );
479         if( matchedTreeView->isExpanded( idx ) )
480             continue;
481 
482         int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
483         if( ( flags & onlyWithTupleFlags ) == onlyWithTupleFlags )
484             matchedTreeView->expand( idx );
485     }
486 }
487 
488 void
collapse()489 MatchedTracksPage::collapse()
490 {
491     int excludingFlags;
492     QAction *action = qobject_cast<QAction *>( sender() );
493     if( action )
494         excludingFlags = action->data().toInt();
495     else
496         excludingFlags = 0;
497 
498     for( int i = 0; i < m_matchedProxyModel->rowCount(); i++ )
499     {
500         QModelIndex idx = m_matchedProxyModel->index( i, 0 );
501         if( !matchedTreeView->isExpanded( idx ) )
502             continue;
503 
504         int flags = idx.data( MatchedTracksModel::TupleFlagsRole ).toInt();
505         if( ( flags & excludingFlags ) == 0 )
506             matchedTreeView->collapse( idx );
507     }
508 }
509 
510 void
openConfiguration()511 MatchedTracksPage::openConfiguration()
512 {
513     App *app = pApp;
514     if( app )
515         app->slotConfigAmarok( QStringLiteral("MetadataConfig") );
516 }
517 
518 void
setHeaderSizePoliciesFromModel(QHeaderView * header,QAbstractItemModel * model)519 MatchedTracksPage::setHeaderSizePoliciesFromModel( QHeaderView *header, QAbstractItemModel *model )
520 {
521     for( int column = 0; column < model->columnCount(); column++ )
522     {
523         QVariant headerData = model->headerData( column, Qt::Horizontal,
524                                                  CommonModel::ResizeModeRole );
525         QHeaderView::ResizeMode mode = QHeaderView::ResizeMode( headerData.toInt() );
526         header->setSectionResizeMode( column, mode );
527     }
528 }
529