1/* 2 * This file Copyright (C) Mnemosyne LLC 3 * 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. 7 * 8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 9 * 10 * $Id: torrent-delegate.cc 13553 2012-10-07 17:51:56Z jordan $ 11 */ 12 13#include <iostream> 14 15#include <QApplication> 16#include <QFont> 17#include <QFontMetrics> 18#include <QIcon> 19#include <QModelIndex> 20#include <QPainter> 21#include <QPixmap> 22#include <QPixmapCache> 23#include <QStyleOptionProgressBarV2> 24 25#include "formatter.h" 26#include "torrent.h" 27#include "torrent-delegate.h" 28#include "torrent-model.h" 29 30enum 31{ 32 GUI_PAD = 6, 33 BAR_HEIGHT = 12 34}; 35 36QColor TorrentDelegate :: greenBrush; 37QColor TorrentDelegate :: blueBrush; 38QColor TorrentDelegate :: greenBack; 39QColor TorrentDelegate :: blueBack; 40 41TorrentDelegate :: TorrentDelegate( QObject * parent ): 42 QStyledItemDelegate( parent ), 43 myProgressBarStyle( new QStyleOptionProgressBarV2 ) 44{ 45 myProgressBarStyle->minimum = 0; 46 myProgressBarStyle->maximum = 1000; 47 48 greenBrush = QColor("forestgreen"); 49 greenBack = QColor("darkseagreen"); 50 51 blueBrush = QColor("steelblue"); 52 blueBack = QColor("lightgrey"); 53} 54 55TorrentDelegate :: ~TorrentDelegate( ) 56{ 57 delete myProgressBarStyle; 58} 59 60/*** 61**** 62***/ 63 64QSize 65TorrentDelegate :: margin( const QStyle& style ) const 66{ 67 Q_UNUSED( style ); 68 69 return QSize( 4, 4 ); 70} 71 72QString 73TorrentDelegate :: progressString( const Torrent& tor ) const 74{ 75 const bool isMagnet( !tor.hasMetadata( ) ); 76 const bool isDone( tor.isDone( ) ); 77 const bool isSeed( tor.isSeed( ) ); 78 const uint64_t haveTotal( tor.haveTotal( ) ); 79 QString str; 80 double seedRatio; 81 const bool hasSeedRatio( tor.getSeedRatio( seedRatio ) ); 82 83 if( isMagnet ) // magnet link with no metadata 84 { 85 /* %1 is the percentage of torrent metadata downloaded */ 86 str = tr( "Magnetized transfer - retrieving metadata (%1%)" ) 87 .arg( Formatter::percentToString( tor.metadataPercentDone() * 100.0 ) ); 88 } 89 else if( !isDone ) // downloading 90 { 91 /* %1 is how much we've got, 92 %2 is how much we'll have when done, 93 %3 is a percentage of the two */ 94 str = tr( "%1 of %2 (%3%)" ).arg( Formatter::sizeToString( haveTotal ) ) 95 .arg( Formatter::sizeToString( tor.sizeWhenDone( ) ) ) 96 .arg( Formatter::percentToString( tor.percentDone( ) * 100.0 ) ); 97 } 98 else if( !isSeed ) // partial seed 99 { 100 if( hasSeedRatio ) 101 { 102 /* %1 is how much we've got, 103 %2 is the torrent's total size, 104 %3 is a percentage of the two, 105 %4 is how much we've uploaded, 106 %5 is our upload-to-download ratio 107 %6 is the ratio we want to reach before we stop uploading */ 108 str = tr( "%1 of %2 (%3%), uploaded %4 (Ratio: %5 Goal: %6)" ) 109 .arg( Formatter::sizeToString( haveTotal ) ) 110 .arg( Formatter::sizeToString( tor.totalSize( ) ) ) 111 .arg( Formatter::percentToString( tor.percentComplete( ) * 100.0 ) ) 112 .arg( Formatter::sizeToString( tor.uploadedEver( ) ) ) 113 .arg( Formatter::ratioToString( tor.ratio( ) ) ) 114 .arg( Formatter::ratioToString( seedRatio ) ); 115 } 116 else 117 { 118 /* %1 is how much we've got, 119 %2 is the torrent's total size, 120 %3 is a percentage of the two, 121 %4 is how much we've uploaded, 122 %5 is our upload-to-download ratio */ 123 str = tr( "%1 of %2 (%3%), uploaded %4 (Ratio: %5)" ) 124 .arg( Formatter::sizeToString( haveTotal ) ) 125 .arg( Formatter::sizeToString( tor.totalSize( ) ) ) 126 .arg( Formatter::percentToString( tor.percentComplete( ) * 100.0 ) ) 127 .arg( Formatter::sizeToString( tor.uploadedEver( ) ) ) 128 .arg( Formatter::ratioToString( tor.ratio( ) ) ); 129 } 130 } 131 else // seeding 132 { 133 if( hasSeedRatio ) 134 { 135 /* %1 is the torrent's total size, 136 %2 is how much we've uploaded, 137 %3 is our upload-to-download ratio, 138 %4 is the ratio we want to reach before we stop uploading */ 139 str = tr( "%1, uploaded %2 (Ratio: %3 Goal: %4)" ) 140 .arg( Formatter::sizeToString( haveTotal ) ) 141 .arg( Formatter::sizeToString( tor.uploadedEver( ) ) ) 142 .arg( Formatter::ratioToString( tor.ratio( ) ) ) 143 .arg( Formatter::ratioToString( seedRatio ) ); 144 } 145 else /* seeding w/o a ratio */ 146 { 147 /* %1 is the torrent's total size, 148 %2 is how much we've uploaded, 149 %3 is our upload-to-download ratio */ 150 str = tr( "%1, uploaded %2 (Ratio: %3)" ) 151 .arg( Formatter::sizeToString( haveTotal ) ) 152 .arg( Formatter::sizeToString( tor.uploadedEver( ) ) ) 153 .arg( Formatter::ratioToString( tor.ratio( ) ) ); 154 } 155 } 156 157 /* add time when downloading */ 158 if( ( hasSeedRatio && tor.isSeeding( ) ) || tor.isDownloading( ) ) 159 { 160 str += tr( " - " ); 161 if( tor.hasETA( ) ) 162 str += tr( "%1 left" ).arg( Formatter::timeToString( tor.getETA( ) ) ); 163 else 164 str += tr( "Remaining time unknown" ); 165 } 166 167 return str; 168} 169 170QString 171TorrentDelegate :: shortTransferString( const Torrent& tor ) const 172{ 173 static const QChar upArrow( 0x2191 ); 174 static const QChar downArrow( 0x2193 ); 175 const bool haveMeta( tor.hasMetadata( ) ); 176 const bool haveDown( haveMeta && ((tor.webseedsWeAreDownloadingFrom()>0) || (tor.peersWeAreDownloadingFrom( )>0)) ); 177 const bool haveUp( haveMeta && tor.peersWeAreUploadingTo( ) > 0 ); 178 QString downStr, upStr, str; 179 180 if( haveDown ) 181 downStr = Formatter::speedToString( tor.downloadSpeed( ) ); 182 if( haveUp ) 183 upStr = Formatter::speedToString( tor.uploadSpeed( ) ); 184 185 if( haveDown && haveUp ) 186 str = tr( "%1 %2, %3 %4" ).arg(downArrow).arg(downStr).arg(upArrow).arg(upStr); 187 else if( haveDown ) 188 str = tr( "%1 %2" ).arg(downArrow).arg(downStr); 189 else if( haveUp ) 190 str = tr( "%1 %2" ).arg(upArrow).arg(upStr); 191 else if( tor.isStalled( ) ) 192 str = tr( "Stalled" ); 193 else if( tor.hasMetadata( ) ) 194 str = tr( "Idle" ); 195 196 return str; 197} 198 199QString 200TorrentDelegate :: shortStatusString( const Torrent& tor ) const 201{ 202 QString str; 203 204 switch( tor.getActivity( ) ) 205 { 206 case TR_STATUS_CHECK: 207 str = tr( "Verifying local data (%1% tested)" ).arg( Formatter::percentToString( tor.getVerifyProgress()*100.0 ) ); 208 break; 209 210 case TR_STATUS_DOWNLOAD: 211 case TR_STATUS_SEED: 212 if( !tor.isDownloading( ) ) 213 str = tr( "Ratio: %1, " ).arg( Formatter::ratioToString( tor.ratio( ) ) ); 214 str += shortTransferString( tor ); 215 break; 216 217 default: 218 str = tor.activityString( ); 219 break; 220 } 221 222 return str; 223} 224 225QString 226TorrentDelegate :: statusString( const Torrent& tor ) const 227{ 228 QString str; 229 230 if( tor.hasError( ) ) 231 { 232 str = tor.getError( ); 233 } 234 else switch( tor.getActivity( ) ) 235 { 236 case TR_STATUS_STOPPED: 237 case TR_STATUS_CHECK_WAIT: 238 case TR_STATUS_CHECK: 239 case TR_STATUS_DOWNLOAD_WAIT: 240 case TR_STATUS_SEED_WAIT: 241 str = shortStatusString( tor ); 242 break; 243 244 case TR_STATUS_DOWNLOAD: 245 if( !tor.hasMetadata() ) { 246 str = tr( "Downloading metadata from %n peer(s) (%1% done)", 0, tor.peersWeAreDownloadingFrom( ) ) 247 .arg( Formatter::percentToString( 100.0 * tor.metadataPercentDone( ) ) ); 248 } else { 249 /* it would be nicer for translation if this was all one string, but I don't see how to do multiple %n's in tr() */ 250 str = tr( "Downloading from %1 of %n connected peer(s)", 0, tor.connectedPeersAndWebseeds( ) ) 251 .arg( tor.peersWeAreDownloadingFrom( ) ); 252 if (tor.webseedsWeAreDownloadingFrom()) 253 str += tr(" and %n web seed(s)", "", tor.webseedsWeAreDownloadingFrom()); 254 } 255 break; 256 257 case TR_STATUS_SEED: 258 str = tr( "Seeding to %1 of %n connected peer(s)", 0, tor.connectedPeers( ) ) 259 .arg( tor.peersWeAreUploadingTo( ) ); 260 break; 261 262 default: 263 str = tr( "Error" ); 264 break; 265 } 266 267 if( tor.isReadyToTransfer( ) ) { 268 QString s = shortTransferString( tor ); 269 if( !s.isEmpty( ) ) 270 str += tr( " - " ) + s; 271 } 272 273 return str; 274} 275 276/*** 277**** 278***/ 279 280namespace 281{ 282 int MAX3( int a, int b, int c ) 283 { 284 const int ab( a > b ? a : b ); 285 return ab > c ? ab : c; 286 } 287} 288 289QSize 290TorrentDelegate :: sizeHint( const QStyleOptionViewItem& option, const Torrent& tor ) const 291{ 292 const QStyle* style( QApplication::style( ) ); 293 static const int iconSize( style->pixelMetric( QStyle::PM_MessageBoxIconSize ) ); 294 295 QFont nameFont( option.font ); 296 nameFont.setWeight( QFont::Bold ); 297 const QFontMetrics nameFM( nameFont ); 298 const QString nameStr( tor.name( ) ); 299 const int nameWidth = nameFM.width( nameStr ); 300 QFont statusFont( option.font ); 301 statusFont.setPointSize( int( option.font.pointSize( ) * 0.9 ) ); 302 const QFontMetrics statusFM( statusFont ); 303 const QString statusStr( statusString( tor ) ); 304 const int statusWidth = statusFM.width( statusStr ); 305 QFont progressFont( statusFont ); 306 const QFontMetrics progressFM( progressFont ); 307 const QString progressStr( progressString( tor ) ); 308 const int progressWidth = progressFM.width( progressStr ); 309 const QSize m( margin( *style ) ); 310 return QSize( m.width()*2 + iconSize + GUI_PAD + MAX3( nameWidth, statusWidth, progressWidth ), 311 //m.height()*3 + nameFM.lineSpacing() + statusFM.lineSpacing()*2 + progressFM.lineSpacing() ); 312 m.height()*3 + nameFM.lineSpacing() + statusFM.lineSpacing() + BAR_HEIGHT + progressFM.lineSpacing() ); 313} 314 315QSize 316TorrentDelegate :: sizeHint( const QStyleOptionViewItem & option, 317 const QModelIndex & index ) const 318{ 319 const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>() ); 320 return sizeHint( option, *tor ); 321} 322 323void 324TorrentDelegate :: paint( QPainter * painter, 325 const QStyleOptionViewItem & option, 326 const QModelIndex & index) const 327{ 328 const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>() ); 329 painter->save( ); 330 painter->setClipRect( option.rect ); 331 drawTorrent( painter, option, *tor ); 332 painter->restore( ); 333} 334 335void 336TorrentDelegate :: setProgressBarPercentDone( const QStyleOptionViewItem& option, const Torrent& tor ) const 337{ 338 double seedRatioLimit; 339 if (tor.isSeeding() && tor.getSeedRatio(seedRatioLimit)) 340 { 341 const double seedRateRatio = tor.ratio() / seedRatioLimit; 342 const int scaledProgress = seedRateRatio * (myProgressBarStyle->maximum - myProgressBarStyle->minimum); 343 myProgressBarStyle->progress = myProgressBarStyle->minimum + scaledProgress; 344 } 345 else 346 { 347 const bool isMagnet( !tor.hasMetadata( ) ); 348 myProgressBarStyle->direction = option.direction; 349 myProgressBarStyle->progress = int(myProgressBarStyle->minimum + (((isMagnet ? tor.metadataPercentDone() : tor.percentDone()) * (myProgressBarStyle->maximum - myProgressBarStyle->minimum)))); 350 } 351} 352 353void 354TorrentDelegate :: drawTorrent( QPainter * painter, const QStyleOptionViewItem& option, const Torrent& tor ) const 355{ 356 const QStyle * style( QApplication::style( ) ); 357 static const int iconSize( style->pixelMetric( QStyle::PM_LargeIconSize ) ); 358 QFont nameFont( option.font ); 359 nameFont.setWeight( QFont::Bold ); 360 const QFontMetrics nameFM( nameFont ); 361 const QString nameStr( tor.name( ) ); 362 const QSize nameSize( nameFM.size( 0, nameStr ) ); 363 QFont statusFont( option.font ); 364 statusFont.setPointSize( int( option.font.pointSize( ) * 0.9 ) ); 365 const QFontMetrics statusFM( statusFont ); 366 const QString statusStr( progressString( tor ) ); 367 QFont progressFont( statusFont ); 368 const QFontMetrics progressFM( progressFont ); 369 const QString progressStr( statusString( tor ) ); 370 const bool isPaused( tor.isPaused( ) ); 371 372 painter->save( ); 373 374 if (option.state & QStyle::State_Selected) { 375 QPalette::ColorGroup cg = option.state & QStyle::State_Enabled 376 ? QPalette::Normal : QPalette::Disabled; 377 if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) 378 cg = QPalette::Inactive; 379 380 painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight)); 381 } 382 383 QIcon::Mode im; 384 if( isPaused || !(option.state & QStyle::State_Enabled ) ) im = QIcon::Disabled; 385 else if( option.state & QStyle::State_Selected ) im = QIcon::Selected; 386 else im = QIcon::Normal; 387 388 QIcon::State qs; 389 if( isPaused ) qs = QIcon::Off; 390 else qs = QIcon::On; 391 392 QPalette::ColorGroup cg = QPalette::Normal; 393 if( isPaused || !(option.state & QStyle::State_Enabled ) ) cg = QPalette::Disabled; 394 if( cg == QPalette::Normal && !(option.state & QStyle::State_Active ) ) cg = QPalette::Inactive; 395 396 QPalette::ColorRole cr; 397 if( option.state & QStyle::State_Selected ) cr = QPalette::HighlightedText; 398 else cr = QPalette::Text; 399 400 QStyle::State progressBarState( option.state ); 401 if( isPaused ) progressBarState = QStyle::State_None; 402 progressBarState |= QStyle::State_Small; 403 404 // layout 405 const QSize m( margin( *style ) ); 406 QRect fillArea( option.rect ); 407 fillArea.adjust( m.width(), m.height(), -m.width(), -m.height() ); 408 QRect iconArea( fillArea.x( ), fillArea.y( ) + ( fillArea.height( ) - iconSize ) / 2, iconSize, iconSize ); 409 QRect nameArea( iconArea.x( ) + iconArea.width( ) + GUI_PAD, fillArea.y( ), 410 fillArea.width( ) - GUI_PAD - iconArea.width( ), nameSize.height( ) ); 411 QRect statusArea( nameArea ); 412 statusArea.moveTop( nameArea.y( ) + nameFM.lineSpacing( ) ); 413 statusArea.setHeight( nameSize.height( ) ); 414 QRect barArea( statusArea ); 415 barArea.setHeight( BAR_HEIGHT ); 416 barArea.moveTop( statusArea.y( ) + statusFM.lineSpacing( ) ); 417 QRect progArea( statusArea ); 418 progArea.moveTop( barArea.y( ) + barArea.height( ) ); 419 420 // render 421 if( tor.hasError( ) ) 422 painter->setPen( QColor( "red" ) ); 423 else 424 painter->setPen( option.palette.color( cg, cr ) ); 425 tor.getMimeTypeIcon().paint( painter, iconArea, Qt::AlignCenter, im, qs ); 426 painter->setFont( nameFont ); 427 painter->drawText( nameArea, 0, nameFM.elidedText( nameStr, Qt::ElideRight, nameArea.width( ) ) ); 428 painter->setFont( statusFont ); 429 painter->drawText( statusArea, 0, statusFM.elidedText( statusStr, Qt::ElideRight, statusArea.width( ) ) ); 430 painter->setFont( progressFont ); 431 painter->drawText( progArea, 0, progressFM.elidedText( progressStr, Qt::ElideRight, progArea.width( ) ) ); 432 myProgressBarStyle->rect = barArea; 433 if ( tor.isDownloading() ) { 434 myProgressBarStyle->palette.setBrush( QPalette::Highlight, blueBrush ); 435 myProgressBarStyle->palette.setColor( QPalette::Base, blueBack ); 436 myProgressBarStyle->palette.setColor( QPalette::Background, blueBack ); 437 } 438 else if ( tor.isSeeding() ) { 439 myProgressBarStyle->palette.setBrush( QPalette::Highlight, greenBrush ); 440 myProgressBarStyle->palette.setColor( QPalette::Base, greenBack ); 441 myProgressBarStyle->palette.setColor( QPalette::Background, greenBack ); 442 } 443 myProgressBarStyle->state = progressBarState; 444 setProgressBarPercentDone( option, tor ); 445 446 style->drawControl( QStyle::CE_ProgressBar, myProgressBarStyle, painter ); 447 448 painter->restore( ); 449} 450