Revert "transmission: update from 2.13 to 2.22"
[tomato.git] / release / src / router / transmission / qt / file-tree.cc
blob64715a6366166e7497556b297aeee3104d270c56
1 /*
2 * This file Copyright (C) Mnemosyne LLC
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10 * $Id: file-tree.cc 11405 2010-11-13 22:07:19Z charles $
13 #include <cassert>
14 #include <iostream>
16 #include <QApplication>
17 #include <QHeaderView>
18 #include <QPainter>
19 #include <QResizeEvent>
20 #include <QSortFilterProxyModel>
21 #include <QStringList>
23 #include <libtransmission/transmission.h> // priorities
25 #include "file-tree.h"
26 #include "formatter.h"
27 #include "hig.h"
28 #include "torrent.h" // FileList
29 #include "utils.h" // mime icons
31 enum
33 COL_NAME,
34 COL_PROGRESS,
35 COL_WANTED,
36 COL_PRIORITY,
37 NUM_COLUMNS
40 /****
41 *****
42 ****/
44 FileTreeItem :: ~FileTreeItem( )
46 assert( myChildren.isEmpty( ) );
48 if( myParent ) {
49 const int pos = myParent->myChildren.indexOf( this );
50 if( pos >= 0 )
51 myParent->myChildren.removeAt( pos );
52 else
53 assert( 0 && "failed to remove" );
57 void
58 FileTreeItem :: appendChild( FileTreeItem * child )
60 child->myParent = this;
61 myChildren.append( child );
64 FileTreeItem *
65 FileTreeItem :: child( const QString& filename )
67 foreach( FileTreeItem * c, myChildren )
68 if( c->name() == filename )
69 return c;
71 return 0;
74 int
75 FileTreeItem :: row( ) const
77 int i(0);
79 if( myParent )
80 i = myParent->myChildren.indexOf( const_cast<FileTreeItem*>(this) );
82 return i;
85 QVariant
86 FileTreeItem :: data( int column ) const
88 QVariant value;
90 switch( column ) {
91 case COL_NAME: value.setValue( fileSizeName( ) ); break;
92 case COL_PROGRESS: value.setValue( progress( ) ); break;
93 case COL_WANTED: value.setValue( isSubtreeWanted( ) ); break;
94 case COL_PRIORITY: value.setValue( priorityString( ) ); break;
97 return value;
100 void
101 FileTreeItem :: getSubtreeSize( uint64_t& have, uint64_t& total ) const
103 have += myHaveSize;
104 total += myTotalSize;
106 foreach( const FileTreeItem * i, myChildren )
107 i->getSubtreeSize( have, total );
110 double
111 FileTreeItem :: progress( ) const
113 double d(0);
114 uint64_t have(0), total(0);
115 getSubtreeSize( have, total );
116 if( total )
117 d = have / (double)total;
118 return d;
121 QString
122 FileTreeItem :: fileSizeName( ) const
124 uint64_t have(0), total(0);
125 QString str;
126 getSubtreeSize( have, total );
127 str = QString( name() + " (%1)" ).arg( Formatter::sizeToString( total ) );
128 return str;
131 bool
132 FileTreeItem :: update( int index, bool wanted, int priority, uint64_t totalSize, uint64_t haveSize, bool torrentChanged )
134 bool changed = false;
136 if( myIndex != index )
138 myIndex = index;
139 changed = true;
141 if( torrentChanged && myIsWanted != wanted )
143 myIsWanted = wanted;
144 changed = true;
146 if( torrentChanged && myPriority != priority )
148 myPriority = priority;
149 changed = true;
151 if( myTotalSize != totalSize )
153 myTotalSize = totalSize;
154 changed = true;
156 if( myHaveSize != haveSize )
158 myHaveSize = haveSize;
159 changed = true;
162 return changed;
165 QString
166 FileTreeItem :: priorityString( ) const
168 const int i( priority( ) );
169 if( i == LOW ) return tr( "Low" );
170 if( i == HIGH ) return tr( "High" );
171 if( i == NORMAL ) return tr( "Normal" );
172 return tr( "Mixed" );
176 FileTreeItem :: priority( ) const
178 int i( 0 );
180 if( myChildren.isEmpty( ) ) switch( myPriority ) {
181 case TR_PRI_LOW: i |= LOW; break;
182 case TR_PRI_HIGH: i |= HIGH; break;
183 default: i |= NORMAL; break;
186 foreach( const FileTreeItem * child, myChildren )
187 i |= child->priority( );
189 return i;
192 void
193 FileTreeItem :: setSubtreePriority( int i, QSet<int>& ids )
195 if( myPriority != i ) {
196 myPriority = i;
197 if( myIndex >= 0 )
198 ids.insert( myIndex );
201 foreach( FileTreeItem * child, myChildren )
202 child->setSubtreePriority( i, ids );
205 void
206 FileTreeItem :: twiddlePriority( QSet<int>& ids, int& p )
208 const int old( priority( ) );
210 if ( old & LOW ) p = TR_PRI_NORMAL;
211 else if( old & NORMAL ) p = TR_PRI_HIGH;
212 else p = TR_PRI_LOW;
214 setSubtreePriority( p, ids );
218 FileTreeItem :: isSubtreeWanted( ) const
220 if( myChildren.isEmpty( ) )
221 return myIsWanted ? Qt::Checked : Qt::Unchecked;
223 int wanted( -1 );
224 foreach( const FileTreeItem * child, myChildren ) {
225 const int childWanted = child->isSubtreeWanted( );
226 if( wanted == -1 )
227 wanted = childWanted;
228 if( wanted != childWanted )
229 wanted = Qt::PartiallyChecked;
230 if( wanted == Qt::PartiallyChecked )
231 return wanted;
234 return wanted;
237 void
238 FileTreeItem :: setSubtreeWanted( bool b, QSet<int>& ids )
240 if( myIsWanted != b ) {
241 myIsWanted = b;
242 if( myIndex >= 0 )
243 ids.insert( myIndex );
246 foreach( FileTreeItem * child, myChildren )
247 child->setSubtreeWanted( b, ids );
250 void
251 FileTreeItem :: twiddleWanted( QSet<int>& ids, bool& wanted )
253 wanted = isSubtreeWanted( ) != Qt::Checked;
254 setSubtreeWanted( wanted, ids );
257 /***
258 ****
259 ****
260 ***/
262 FileTreeModel :: FileTreeModel( QObject *parent ):
263 QAbstractItemModel(parent)
265 rootItem = new FileTreeItem( -1 );
268 FileTreeModel :: ~FileTreeModel( )
270 clear( );
272 delete rootItem;
275 QVariant
276 FileTreeModel :: data( const QModelIndex &index, int role ) const
278 QVariant value;
280 if( index.isValid() && role==Qt::DisplayRole )
282 FileTreeItem *item = static_cast<FileTreeItem*>(index.internalPointer());
283 value = item->data( index.column( ) );
286 return value;
289 Qt::ItemFlags
290 FileTreeModel :: flags( const QModelIndex& index ) const
292 int i( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
294 if( index.column( ) == COL_WANTED )
295 i |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate;
297 return (Qt::ItemFlags)i;
300 QVariant
301 FileTreeModel :: headerData( int column, Qt::Orientation orientation, int role ) const
303 QVariant data;
305 if( orientation==Qt::Horizontal && role==Qt::DisplayRole ) {
306 switch( column ) {
307 case COL_NAME: data.setValue( tr( "File" ) ); break;
308 case COL_PROGRESS: data.setValue( tr( "Progress" ) ); break;
309 case COL_WANTED: data.setValue( tr( "Download" ) ); break;
310 case COL_PRIORITY: data.setValue( tr( "Priority" ) ); break;
311 default: break;
315 return data;
318 QModelIndex
319 FileTreeModel :: index( int row, int column, const QModelIndex& parent ) const
321 QModelIndex i;
323 if( !hasIndex( row, column, parent ) )
325 std::cerr << " I don't have this index " << std::endl;
327 else
329 FileTreeItem * parentItem;
331 if( !parent.isValid( ) )
332 parentItem = rootItem;
333 else
334 parentItem = static_cast<FileTreeItem*>(parent.internalPointer());
336 FileTreeItem * childItem = parentItem->child( row );
338 if( childItem )
339 i = createIndex( row, column, childItem );
341 //std::cerr << "FileTreeModel::index(row("<<row<<"),col("<<column<<"),parent("<<qPrintable(parentItem->name())<<")) is returning " << qPrintable(childItem->name()) << ": internalPointer " << i.internalPointer() << " row " << i.row() << " col " << i.column() << std::endl;
344 return i;
347 QModelIndex
348 FileTreeModel :: parent( const QModelIndex& child ) const
350 return parent( child, 0 ); // QAbstractItemModel::parent() wants col 0
353 QModelIndex
354 FileTreeModel :: parent( const QModelIndex& child, int column ) const
356 if( !child.isValid( ) )
357 return QModelIndex( );
359 FileTreeItem * childItem = static_cast<FileTreeItem*>(child.internalPointer());
361 return indexOf( childItem->parent( ), column );
365 FileTreeModel :: rowCount( const QModelIndex& parent ) const
367 FileTreeItem * parentItem;
369 if( !parent.isValid( ) )
370 parentItem = rootItem;
371 else
372 parentItem = static_cast<FileTreeItem*>(parent.internalPointer());
374 return parentItem->childCount();
378 FileTreeModel :: columnCount( const QModelIndex &parent ) const
380 Q_UNUSED( parent );
382 return 4;
385 QModelIndex
386 FileTreeModel :: indexOf( FileTreeItem * item, int column ) const
388 if( !item || item==rootItem )
389 return QModelIndex( );
391 return createIndex( item->row( ), column, item );
394 void
395 FileTreeModel :: clearSubtree( const QModelIndex& top )
397 while( hasChildren( top ) )
398 clearSubtree( index( 0, 0, top ) );
400 delete static_cast<FileTreeItem*>(top.internalPointer());
403 void
404 FileTreeModel :: clear( )
406 clearSubtree( QModelIndex( ) );
408 reset( );
411 void
412 FileTreeModel :: addFile( int index,
413 const QString & filename,
414 bool wanted,
415 int priority,
416 uint64_t size,
417 uint64_t have,
418 QList<QModelIndex> & rowsAdded,
419 bool torrentChanged )
421 FileTreeItem * i( rootItem );
423 foreach( QString token, filename.split( "/" ) )
425 FileTreeItem * child( i->child( token ) );
426 if( !child )
428 QModelIndex parentIndex( indexOf( i, 0 ) );
429 const int n( i->childCount( ) );
430 beginInsertRows( parentIndex, n, n );
431 i->appendChild(( child = new FileTreeItem( -1, token )));
432 endInsertRows( );
433 rowsAdded.append( indexOf( child, 0 ) );
435 i = child;
438 if( i != rootItem )
439 if( i->update( index, wanted, priority, size, have, torrentChanged ) )
440 dataChanged( indexOf( i, 0 ), indexOf( i, NUM_COLUMNS-1 ) );
443 void
444 FileTreeModel :: parentsChanged( const QModelIndex& index, int column )
446 QModelIndex walk = index;
448 for( ;; ) {
449 walk = parent( walk, column );
450 if( !walk.isValid( ) )
451 break;
452 dataChanged( walk, walk );
456 void
457 FileTreeModel :: subtreeChanged( const QModelIndex& index, int column )
459 const int childCount = rowCount( index );
460 if( !childCount )
461 return;
463 // tell everyone that this tier changed
464 dataChanged( index.child(0,column), index.child(childCount-1,column) );
466 // walk the subtiers
467 for( int i=0; i<childCount; ++i )
468 subtreeChanged( index.child(i,column), column );
471 void
472 FileTreeModel :: clicked( const QModelIndex& index )
474 const int column( index.column( ) );
476 if( !index.isValid( ) )
477 return;
479 if( column == COL_WANTED )
481 FileTreeItem * item( static_cast<FileTreeItem*>(index.internalPointer()));
482 bool want;
483 QSet<int> fileIds;
484 item->twiddleWanted( fileIds, want );
485 emit wantedChanged( fileIds, want );
487 dataChanged( index, index );
488 parentsChanged( index, column );
489 subtreeChanged( index, column );
491 else if( column == COL_PRIORITY )
493 FileTreeItem * item( static_cast<FileTreeItem*>(index.internalPointer()));
494 int priority;
495 QSet<int>fileIds;
496 item->twiddlePriority( fileIds, priority );
497 emit priorityChanged( fileIds, priority );
499 dataChanged( index, index );
500 parentsChanged( index, column );
501 subtreeChanged( index, column );
505 /****
506 *****
507 ****/
509 QSize
510 FileTreeDelegate :: sizeHint( const QStyleOptionViewItem& item, const QModelIndex& index ) const
512 QSize size;
514 switch( index.column( ) )
516 case COL_NAME: {
517 const QFontMetrics fm( item.font );
518 const QString text = index.data().toString();
519 const int iconSize = QApplication::style()->pixelMetric( QStyle::PM_SmallIconSize );
520 size.rwidth() = HIG::PAD_SMALL + iconSize;
521 size.rheight() = std::max( iconSize, fm.height( ) );
522 break;
525 case COL_PROGRESS:
526 case COL_WANTED:
527 size = QSize( 20, 1 );
528 break;
530 default: {
531 const QFontMetrics fm( item.font );
532 const QString text = index.data().toString();
533 size = fm.size( 0, text );
534 break;
538 size.rheight() += 8; // make the spacing a little nicer
539 return size;
542 void
543 FileTreeDelegate :: paint( QPainter * painter,
544 const QStyleOptionViewItem & option,
545 const QModelIndex & index ) const
547 const int column( index.column( ) );
549 if( ( column != COL_PROGRESS ) && ( column != COL_WANTED ) && ( column != COL_NAME ) )
551 QItemDelegate::paint(painter, option, index);
552 return;
555 QStyle * style( QApplication :: style( ) );
556 if( option.state & QStyle::State_Selected )
557 painter->fillRect( option.rect, option.palette.highlight( ) );
558 painter->save();
559 if( option.state & QStyle::State_Selected )
560 painter->setBrush(option.palette.highlightedText());
562 if( column == COL_NAME )
564 // draw the file icon
565 static const int iconSize( style->pixelMetric( QStyle :: PM_SmallIconSize ) );
566 const QRect iconArea( option.rect.x(),
567 option.rect.y() + (option.rect.height()-iconSize)/2,
568 iconSize, iconSize );
569 QIcon icon;
570 if( index.model()->hasChildren( index ) )
571 icon = style->standardIcon( QStyle::StandardPixmap( QStyle::SP_DirOpenIcon ) );
572 else
574 QString name = index.data().toString();
575 icon = Utils :: guessMimeIcon( name.left( name.lastIndexOf( " (" ) ) );
577 icon.paint( painter, iconArea, Qt::AlignCenter, QIcon::Normal, QIcon::On );
579 // draw the name
580 QStyleOptionViewItem tmp( option );
581 tmp.rect.setWidth( option.rect.width( ) - iconArea.width( ) - HIG::PAD_SMALL );
582 tmp.rect.moveRight( option.rect.right( ) );
583 QItemDelegate::paint( painter, tmp, index );
585 else if( column == COL_PROGRESS )
587 QStyleOptionProgressBar p;
588 p.state = option.state | QStyle::State_Small;
589 p.direction = QApplication::layoutDirection();
590 p.rect = option.rect;
591 p.rect.setSize( QSize( option.rect.width()-2, option.rect.height()-8 ) );
592 p.rect.moveCenter( option.rect.center( ) );
593 p.fontMetrics = QApplication::fontMetrics();
594 p.minimum = 0;
595 p.maximum = 100;
596 p.textAlignment = Qt::AlignCenter;
597 p.textVisible = true;
598 p.progress = (int)(100.0*index.data().toDouble());
599 p.text = QString( ).sprintf( "%d%%", p.progress );
600 style->drawControl( QStyle::CE_ProgressBar, &p, painter );
602 else if( column == COL_WANTED )
604 QStyleOptionButton o;
605 o.state = option.state;
606 o.direction = QApplication::layoutDirection();
607 o.rect.setSize( QSize( 20, option.rect.height( ) ) );
608 o.rect.moveCenter( option.rect.center( ) );
609 o.fontMetrics = QApplication::fontMetrics();
610 switch( index.data().toInt() ) {
611 case Qt::Unchecked: o.state |= QStyle::State_Off; break;
612 case Qt::Checked: o.state |= QStyle::State_On; break;
613 default: o.state |= QStyle::State_NoChange;break;
615 style->drawControl( QStyle::CE_CheckBox, &o, painter );
618 painter->restore( );
621 /****
622 *****
623 *****
624 *****
625 ****/
627 FileTreeView :: FileTreeView( QWidget * parent ):
628 QTreeView( parent ),
629 myModel( this ),
630 myProxy( new QSortFilterProxyModel( ) ),
631 myDelegate( this )
633 setSortingEnabled( true );
634 setAlternatingRowColors( true );
635 setSelectionBehavior( QAbstractItemView::SelectRows );
636 setSelectionMode( QAbstractItemView::ExtendedSelection );
637 myProxy->setSourceModel( &myModel );
638 setModel( myProxy );
639 setItemDelegate( &myDelegate );
640 setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
641 sortByColumn( COL_NAME, Qt::AscendingOrder );
642 installEventFilter( this );
644 for( int i=0; i<NUM_COLUMNS; ++i )
645 header()->setResizeMode( i, QHeaderView::Fixed );
647 connect( this, SIGNAL(clicked(const QModelIndex&)),
648 this, SLOT(onClicked(const QModelIndex&)) );
650 connect( &myModel, SIGNAL(priorityChanged(const QSet<int>&, int)),
651 this, SIGNAL(priorityChanged(const QSet<int>&, int)));
653 connect( &myModel, SIGNAL(wantedChanged(const QSet<int>&, bool)),
654 this, SIGNAL(wantedChanged(const QSet<int>&, bool)));
657 FileTreeView :: ~FileTreeView( )
659 myProxy->deleteLater();
662 void
663 FileTreeView :: onClicked( const QModelIndex& proxyIndex )
665 const QModelIndex modelIndex = myProxy->mapToSource( proxyIndex );
666 myModel.clicked( modelIndex );
669 bool
670 FileTreeView :: eventFilter( QObject * o, QEvent * event )
672 if( o != this )
673 return false;
675 // this is kind of a hack to get the last three columns be the
676 // right size, and to have the filename column use whatever
677 // space is left over...
678 if( event->type() == QEvent::Resize )
680 QResizeEvent * r = dynamic_cast<QResizeEvent*>(event);
681 int left = r->size().width();
682 const QFontMetrics fontMetrics( font( ) );
683 for( int column=0; column<NUM_COLUMNS; ++column ) {
684 if( column == COL_NAME )
685 continue;
686 if( isColumnHidden( column ) )
687 continue;
688 const QString header = myModel.headerData( column, Qt::Horizontal ).toString( ) + " ";
689 const int width = fontMetrics.size( 0, header ).width( );
690 setColumnWidth( column, width );
691 left -= width;
693 left -= 20; // not sure why this is necessary. it works in different themes + font sizes though...
694 setColumnWidth( COL_NAME, std::max(left,0) );
695 return false;
698 // handle using the keyboard to toggle the
699 // wanted/unwanted state or the file priority
700 else if( event->type() == QEvent::KeyPress )
702 switch( dynamic_cast<QKeyEvent*>(event)->key() )
704 case Qt::Key_Space:
705 foreach( QModelIndex i, selectionModel()->selectedRows(COL_WANTED) )
706 clicked( i );
707 return false;
709 case Qt::Key_Enter:
710 case Qt::Key_Return:
711 foreach( QModelIndex i, selectionModel()->selectedRows(COL_PRIORITY) )
712 clicked( i );
713 return false;
717 return false;
720 void
721 FileTreeView :: update( const FileList& files )
723 update( files, true );
726 void
727 FileTreeView :: update( const FileList& files, bool torrentChanged )
729 foreach( const TrFile file, files ) {
730 QList<QModelIndex> added;
731 myModel.addFile( file.index, file.filename, file.wanted, file.priority, file.size, file.have, added, torrentChanged );
732 foreach( QModelIndex i, added )
733 expand( myProxy->mapFromSource( i ) );
737 void
738 FileTreeView :: clear( )
740 myModel.clear( );