/src/libtomahawk/playlist/PlaylistItemDelegate.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 882 lines · 665 code · 168 blank · 49 comment · 112 complexity · 117a01fbab0e49a23bdc31a4b721ba21 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2013-2014, Teo Mrnjavac <teo@kde.org>
  6. *
  7. * Tomahawk is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * Tomahawk is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include "PlaylistItemDelegate.h"
  21. #include "Query.h"
  22. #include "Result.h"
  23. #include "Artist.h"
  24. #include "Album.h"
  25. #include "Source.h"
  26. #include "SourceList.h"
  27. #include "PlayableModel.h"
  28. #include "PlayableItem.h"
  29. #include "PlayableProxyModel.h"
  30. #include "TrackView.h"
  31. #include "ViewHeader.h"
  32. #include "ViewManager.h"
  33. #include "widgets/DownloadButton.h"
  34. #include "audio/AudioEngine.h"
  35. #include "utils/ImageRegistry.h"
  36. #include "utils/PixmapDelegateFader.h"
  37. #include "utils/Closure.h"
  38. #include "utils/TomahawkStyle.h"
  39. #include "utils/TomahawkUtilsGui.h"
  40. #include "utils/Logger.h"
  41. #include <QAbstractTextDocumentLayout>
  42. #include <QApplication>
  43. #include <QDateTime>
  44. #include <QMouseEvent>
  45. #include <QPainter>
  46. #include <QToolTip>
  47. using namespace Tomahawk;
  48. PlaylistItemDelegate::PlaylistItemDelegate( TrackView* parent, PlayableProxyModel* proxy )
  49. : QStyledItemDelegate( (QObject*)parent )
  50. , m_view( parent )
  51. , m_model( proxy )
  52. {
  53. m_topOption = QTextOption( Qt::AlignTop );
  54. m_topOption.setWrapMode( QTextOption::NoWrap );
  55. m_bottomOption = QTextOption( Qt::AlignBottom );
  56. m_bottomOption.setWrapMode( QTextOption::NoWrap );
  57. m_centerOption = QTextOption( Qt::AlignVCenter );
  58. m_centerOption.setWrapMode( QTextOption::NoWrap );
  59. m_centerRightOption = QTextOption( Qt::AlignVCenter | Qt::AlignRight );
  60. m_centerRightOption.setWrapMode( QTextOption::NoWrap );
  61. m_demiBoldFont = parent->font();
  62. m_demiBoldFont.setPointSize( TomahawkUtils::defaultFontSize() + 1 );
  63. m_demiBoldFont.setWeight( QFont::DemiBold );
  64. m_normalFont = parent->font();
  65. m_normalFont.setPointSize( TomahawkUtils::defaultFontSize() + 1 );
  66. connect( this, SIGNAL( updateIndex( QModelIndex ) ), parent, SLOT( update( QModelIndex ) ) );
  67. connect( proxy, SIGNAL( modelReset() ), SLOT( modelChanged() ) );
  68. connect( parent, SIGNAL( modelChanged() ), SLOT( modelChanged() ) );
  69. }
  70. void
  71. PlaylistItemDelegate::updateRowSize( const QModelIndex& index )
  72. {
  73. emit sizeHintChanged( index );
  74. }
  75. QSize
  76. PlaylistItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
  77. {
  78. QSize size = QStyledItemDelegate::sizeHint( option, index );
  79. {
  80. if ( m_model->style() != PlayableProxyModel::SingleColumn )
  81. {
  82. int rowHeight = option.fontMetrics.height() * 1.6;
  83. size.setHeight( rowHeight );
  84. }
  85. }
  86. return size;
  87. }
  88. void
  89. PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItem* option, const QModelIndex& index, PlayableItem* item ) const
  90. {
  91. initStyleOption( option, index );
  92. TomahawkUtils::prepareStyleOption( option, index, item );
  93. }
  94. QWidget*
  95. PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  96. {
  97. PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
  98. Q_ASSERT( item );
  99. return DownloadButton::handleCreateEditor( parent, item->query(), m_view, index );
  100. }
  101. void
  102. PlaylistItemDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  103. {
  104. QStyledItemDelegate::updateEditorGeometry( editor, option, index );
  105. DownloadButton* comboBox = static_cast<DownloadButton*>(editor);
  106. comboBox->resize( option.rect.size() - QSize( 8, 0 ) );
  107. comboBox->move( option.rect.x() + 4, option.rect.y() );
  108. if ( m_downloadDropDownRects.contains( index ) )
  109. {
  110. editor->setGeometry( m_downloadDropDownRects.value( index ) );
  111. }
  112. if ( !comboBox->property( "shownPopup" ).toBool() )
  113. {
  114. comboBox->showPopup();
  115. comboBox->setProperty( "shownPopup", true );
  116. }
  117. }
  118. void
  119. PlaylistItemDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
  120. {
  121. }
  122. void
  123. PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  124. {
  125. const int style = index.data( PlayableProxyModel::StyleRole ).toInt();
  126. switch ( style )
  127. {
  128. case PlayableProxyModel::Collection:
  129. case PlayableProxyModel::Locker:
  130. case PlayableProxyModel::Detailed:
  131. paintDetailed( painter, option, index );
  132. break;
  133. }
  134. }
  135. void
  136. PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  137. {
  138. PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
  139. Q_ASSERT( item );
  140. QTextOption textOption( Qt::AlignVCenter | (Qt::Alignment)index.data( Qt::TextAlignmentRole ).toUInt() );
  141. textOption.setWrapMode( QTextOption::NoWrap );
  142. QStyleOptionViewItem opt = option;
  143. prepareStyleOption( &opt, index, item );
  144. opt.text.clear();
  145. qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter );
  146. if ( m_hoveringOver == index && !index.data().toString().isEmpty() &&
  147. ( index.column() == PlayableModel::Artist || index.column() == PlayableModel::Album || index.column() == PlayableModel::Track ) )
  148. {
  149. opt.rect.setWidth( opt.rect.width() - opt.rect.height() - 2 );
  150. const QRect arrowRect( opt.rect.x() + opt.rect.width(), opt.rect.y() + 1, opt.rect.height() - 2, opt.rect.height() - 2 );
  151. drawInfoButton( painter, arrowRect, index, 0.9 );
  152. }
  153. painter->save();
  154. /* if ( index.column() == PlayableModel::Score )
  155. {
  156. QColor barColor( 167, 183, 211 ); // This matches the sidebar (sourcetreeview.cpp:672)
  157. if ( opt.state & QStyle::State_Selected && !item->isPlaying() )
  158. painter->setPen( Qt::white );
  159. else
  160. painter->setPen( barColor );
  161. QRect r = opt.rect.adjusted( 3, 3, -6, -4 );
  162. painter->drawRect( r );
  163. QRect fillR = r;
  164. int fillerWidth = (int)( index.data().toFloat() * (float)fillR.width() );
  165. fillR.adjust( 0, 0, -( fillR.width() - fillerWidth ), 0 );
  166. if ( opt.state & QStyle::State_Selected && !item->isPlaying() )
  167. painter->setBrush( TomahawkUtils::Colors::NOW_PLAYING_ITEM.lighter() );
  168. else
  169. painter->setBrush( barColor );
  170. painter->drawRect( fillR );
  171. }
  172. else */
  173. if ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download )
  174. {
  175. DownloadButton::drawPrimitive( painter, opt.rect.adjusted( 4, 0, -4, 0 ), item->query(), hoveringOver() == index );
  176. }
  177. else if ( item->isPlaying() )
  178. {
  179. QRect r = opt.rect.adjusted( 3, 0, 0, 0 );
  180. // Paint Now Playing Speaker Icon
  181. if ( m_view->header()->visualIndex( index.column() ) == 0 )
  182. {
  183. const int pixMargin = 1;
  184. const int pixHeight = r.height() - pixMargin * 2;
  185. const QRect npr = r.adjusted( pixMargin, pixMargin, pixHeight - r.width() + pixMargin, -pixMargin );
  186. painter->drawPixmap( npr, TomahawkUtils::defaultPixmap( TomahawkUtils::NowPlayingSpeaker, TomahawkUtils::Original, npr.size() ) );
  187. r.adjust( pixHeight + 6, 0, 0, 0 );
  188. }
  189. painter->setPen( opt.palette.text().color() );
  190. const QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, r.width() - 3 );
  191. painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, textOption );
  192. }
  193. else
  194. {
  195. painter->setPen( opt.palette.text().color() );
  196. const QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, opt.rect.width() - 6 );
  197. painter->drawText( opt.rect.adjusted( 3, 1, -3, 0 ), text, textOption );
  198. }
  199. painter->restore();
  200. }
  201. QRect
  202. PlaylistItemDelegate::drawInfoButton( QPainter* painter, const QRect& rect, const QModelIndex& index, float height ) const
  203. {
  204. const int iconSize = rect.height() * height;
  205. QRect pixmapRect = QRect( ( rect.height() - iconSize ) / 2 + rect.left(), rect.center().y() - iconSize / 2, iconSize, iconSize );
  206. painter->drawPixmap( pixmapRect, TomahawkUtils::defaultPixmap( TomahawkUtils::InfoIcon, TomahawkUtils::Original, pixmapRect.size() ) );
  207. m_infoButtonRects[ index ] = pixmapRect;
  208. return rect.adjusted( rect.height(), 0, 0, 0 );
  209. }
  210. QRect
  211. PlaylistItemDelegate::drawCover( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const
  212. {
  213. QRect pixmapRect = rect;
  214. pixmapRect.setWidth( pixmapRect.height() );
  215. if ( !m_pixmaps.contains( index ) )
  216. {
  217. m_pixmaps.insert( index, QSharedPointer< Tomahawk::PixmapDelegateFader >( new Tomahawk::PixmapDelegateFader( item->query(), pixmapRect.size(), TomahawkUtils::RoundedCorners, false ) ) );
  218. _detail::Closure* closure = NewClosure( m_pixmaps[ index ], SIGNAL( repaintRequest() ), const_cast<PlaylistItemDelegate*>(this), SLOT( doUpdateIndex( const QPersistentModelIndex& ) ), QPersistentModelIndex( index ) );
  219. closure->setAutoDelete( false );
  220. }
  221. const QPixmap pixmap = m_pixmaps[ index ]->currentPixmap();
  222. painter->drawPixmap( pixmapRect, pixmap );
  223. return rect.adjusted( pixmapRect.width(), 0, 0, 0 );
  224. }
  225. QRect
  226. PlaylistItemDelegate::drawLoveBox( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const
  227. {
  228. const int avatarSize = rect.height() - 4 * 2;
  229. const int avatarMargin = 2;
  230. QList< Tomahawk::source_ptr > sources;
  231. foreach ( const Tomahawk::SocialAction& sa, item->query()->queryTrack()->socialActions( "Love", true, true ) )
  232. {
  233. sources << sa.source;
  234. }
  235. const int max = 5;
  236. const unsigned int count = qMin( sources.count(), max );
  237. QRect innerRect = rect.adjusted( rect.width() -
  238. ( avatarSize + avatarMargin ) * ( count + 1 ) -
  239. 4 * 4,
  240. 0, 0, 0 );
  241. if ( !sources.isEmpty() )
  242. drawRectForBox( painter, innerRect );
  243. QRect avatarsRect = innerRect.adjusted( 4, 4, -4, -4 );
  244. drawAvatarsForBox( painter, avatarsRect, avatarSize, avatarMargin, count, sources, index );
  245. TomahawkUtils::ImageType type = item->query()->queryTrack()->loved() ? TomahawkUtils::Loved : TomahawkUtils::NotLoved;
  246. QRect r = innerRect.adjusted( innerRect.width() - rect.height() + 4, 4, -4, -4 );
  247. painter->drawPixmap( r, TomahawkUtils::defaultPixmap( type, TomahawkUtils::Original, QSize( r.height(), r.height() ) ) );
  248. m_loveButtonRects[ index ] = r;
  249. return rect;
  250. }
  251. QRect
  252. PlaylistItemDelegate::drawGenericBox( QPainter* painter,
  253. const QStyleOptionViewItem& option,
  254. const QRect& rect, const QString& text,
  255. const QList< Tomahawk::source_ptr >& sources,
  256. const QModelIndex& index ) const
  257. {
  258. const int avatarSize = rect.height() - 4 * 2;
  259. const int avatarMargin = 2;
  260. const int max = 5;
  261. const unsigned int count = qMin( sources.count(), max );
  262. QTextDocument textDoc;
  263. textDoc.setHtml( QString( "<b>%1</b>" ).arg( text ) );
  264. textDoc.setDocumentMargin( 0 );
  265. textDoc.setDefaultFont( painter->font() );
  266. textDoc.setDefaultTextOption( m_bottomOption );
  267. QRect innerRect = rect.adjusted( rect.width() - ( avatarSize + avatarMargin ) * count - 4 * 4 -
  268. textDoc.idealWidth(),
  269. 0, 0, 0 );
  270. QRect textRect = innerRect.adjusted( 4, 4, - innerRect.width() + textDoc.idealWidth() + 2*4, -4 );
  271. drawRichText( painter, option, textRect, Qt::AlignVCenter|Qt::AlignRight, textDoc );
  272. if ( !sources.isEmpty() )
  273. drawRectForBox( painter, innerRect );
  274. QRect avatarsRect = innerRect.adjusted( textDoc.idealWidth() + 3*4, 4, -4, -4 );
  275. drawAvatarsForBox( painter, avatarsRect, avatarSize, avatarMargin, count, sources, index );
  276. return rect;
  277. }
  278. void
  279. PlaylistItemDelegate::drawRectForBox( QPainter* painter, const QRect& rect ) const
  280. {
  281. painter->save();
  282. painter->setRenderHint( QPainter::Antialiasing, true );
  283. painter->setBrush( Qt::transparent );
  284. QPen pen = painter->pen().color();
  285. pen.setWidthF( 0.2 );
  286. painter->setPen( pen );
  287. painter->drawRoundedRect( rect, 4, 4, Qt::RelativeSize );
  288. painter->restore();
  289. }
  290. void
  291. PlaylistItemDelegate::drawAvatarsForBox( QPainter* painter,
  292. const QRect& avatarsRect,
  293. int avatarSize,
  294. int avatarMargin,
  295. int count,
  296. const QList< Tomahawk::source_ptr >& sources,
  297. const QModelIndex& index ) const
  298. {
  299. painter->save();
  300. QHash< Tomahawk::source_ptr, QRect > rectsToSave;
  301. int i = 0;
  302. foreach ( const Tomahawk::source_ptr& s, sources )
  303. {
  304. if ( i >= count )
  305. break;
  306. QRect r = avatarsRect.adjusted( ( avatarSize + avatarMargin ) * i, 0, 0, 0 );
  307. r.setWidth( avatarSize + avatarMargin );
  308. QPixmap pixmap = s->avatar( TomahawkUtils::Original, QSize( avatarSize, avatarSize ), true );
  309. painter->drawPixmap( r.adjusted( avatarMargin / 2, 0, -( avatarMargin / 2 ), 0 ), pixmap );
  310. rectsToSave.insert( s, r );
  311. i++;
  312. }
  313. if ( !rectsToSave.isEmpty() )
  314. m_avatarBoxRects.insert( index, rectsToSave );
  315. painter->restore();
  316. }
  317. void
  318. PlaylistItemDelegate::drawRichText( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, int flags, QTextDocument& text ) const
  319. {
  320. Q_UNUSED( option );
  321. text.setPageSize( QSize( rect.width(), QWIDGETSIZE_MAX ) );
  322. QAbstractTextDocumentLayout* layout = text.documentLayout();
  323. const int height = qRound( layout->documentSize().height() );
  324. int y = rect.y();
  325. if ( flags & Qt::AlignBottom )
  326. y += ( rect.height() - height );
  327. else if ( flags & Qt::AlignVCenter )
  328. y += ( rect.height() - height ) / 2;
  329. QAbstractTextDocumentLayout::PaintContext context;
  330. context.palette.setColor( QPalette::Text, painter->pen().color() );
  331. painter->save();
  332. painter->translate( rect.x(), y );
  333. layout->draw( painter, context );
  334. painter->restore();
  335. }
  336. QRect
  337. PlaylistItemDelegate::drawSourceIcon( QPainter* painter, const QRect& rect, PlayableItem* item, float height ) const
  338. {
  339. const int sourceIconSize = rect.height() * height;
  340. QRect resultRect = rect.adjusted( 0, 0, -( sourceIconSize + 8 ), 0 );
  341. if ( item->query()->numResults( true ) == 0 )
  342. return resultRect;
  343. const QPixmap sourceIcon = item->query()->results().first()->sourceIcon( TomahawkUtils::RoundedCorners, QSize( sourceIconSize, sourceIconSize ) );
  344. if ( sourceIcon.isNull() )
  345. return resultRect;
  346. painter->setOpacity( 0.8 );
  347. painter->drawPixmap( QRect( rect.right() - sourceIconSize, rect.center().y() - sourceIconSize / 2, sourceIcon.width(), sourceIcon.height() ), sourceIcon );
  348. painter->setOpacity( 1.0 );
  349. return resultRect;
  350. }
  351. QRect
  352. PlaylistItemDelegate::drawSource( QPainter* painter, const QStyleOptionViewItem& /* option */, const QModelIndex& /* index */, const QRect& rect, PlayableItem* item ) const
  353. {
  354. painter->save();
  355. painter->setRenderHint( QPainter::TextAntialiasing );
  356. painter->setRenderHint( QPainter::SmoothPixmapTransform );
  357. QRect avatarRect = rect.adjusted( 22, rect.height() - 48, 0, -16 );
  358. QRect textRect = avatarRect.adjusted( avatarRect.height() + 24, 0, -32, 0 );
  359. avatarRect.setWidth( avatarRect.height() );
  360. QPixmap avatar = item->source()->avatar( TomahawkUtils::RoundedCorners, avatarRect.size(), true ) ;
  361. painter->drawPixmap( avatarRect, avatar );
  362. QTextOption to = QTextOption( Qt::AlignVCenter );
  363. to.setWrapMode( QTextOption::NoWrap );
  364. QFont f = painter->font();
  365. f.setPointSize( TomahawkUtils::defaultFontSize() + 2 );
  366. painter->setFont( f );
  367. painter->setOpacity( 0.8 );
  368. painter->setPen( QColor( "#000000" ) );
  369. painter->drawText( textRect, painter->fontMetrics().elidedText( item->source()->friendlyName(), Qt::ElideRight, textRect.width() ), to );
  370. painter->setOpacity( 0.15 );
  371. painter->setBrush( QColor( "#000000" ) );
  372. painter->drawRect( rect.adjusted( 0, rect.height() - 8, -32, -8 ) );
  373. painter->restore();
  374. return rect;
  375. }
  376. QRect
  377. PlaylistItemDelegate::drawTrack( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QRect& rect, PlayableItem* item ) const
  378. {
  379. const track_ptr track = item->query()->track();
  380. const bool hasOnlineResults = ( item->query()->numResults( true ) > 0 );
  381. painter->save();
  382. painter->setRenderHint( QPainter::TextAntialiasing );
  383. int rightMargin = 32;
  384. if ( !index.parent().isValid() )
  385. rightMargin = 0;
  386. if ( option.state & QStyle::State_Selected )
  387. {
  388. painter->setPen( TomahawkStyle::SELECTION_BACKGROUND );
  389. painter->setBrush( TomahawkStyle::SELECTION_BACKGROUND );
  390. painter->drawRect( rect.adjusted( 0, 4, -rightMargin, -4 ) );
  391. }
  392. painter->setPen( TomahawkStyle::SELECTION_FOREGROUND );
  393. painter->setFont( m_demiBoldFont );
  394. QRect r = rect.adjusted( 32, 6, -32 -rightMargin, -6 );
  395. const int margin = 8;
  396. const int numberWidth = painter->fontMetrics().width( "00" ) + 32;
  397. const int durationWidth = painter->fontMetrics().width( "00:00" ) + 32;
  398. int stateWidth = 0;
  399. QRect numberRect = QRect( r.x(), r.y(), numberWidth, r.height() );
  400. QRect extraRect = QRect( r.x() + r.width() - durationWidth, r.y(), durationWidth, r.height() );
  401. QRect stateRect = extraRect.adjusted( 0, 0, 0, 0 );
  402. if ( option.state & QStyle::State_Selected || hoveringOver() == index )
  403. {
  404. int h = extraRect.height() / 3;
  405. if ( track->loved() )
  406. {
  407. painter->save();
  408. painter->setOpacity( 0.5 );
  409. QRect r = stateRect.adjusted( -16, extraRect.height() / 2 - h / 2, 0, 0 );
  410. r.setHeight( h );
  411. r.setWidth( r.height() );
  412. painter->drawPixmap( r, ImageRegistry::instance()->pixmap( RESPATH "images/love.svg", r.size() ) );
  413. painter->restore();
  414. stateWidth += r.width() + 16;
  415. }
  416. }
  417. QRect downloadButtonRect = stateRect.adjusted( -stateWidth -144, 6, 0, -6 );
  418. downloadButtonRect.setWidth( 144 );
  419. stateWidth += downloadButtonRect.width() + 16;
  420. if ( DownloadButton::drawPrimitive( painter, downloadButtonRect, item->query(), m_hoveringOverDownloadButton == index ) )
  421. {
  422. m_downloadDropDownRects[ index ] = downloadButtonRect;
  423. }
  424. const int remWidth = r.width() - numberWidth - durationWidth;
  425. QRect titleRect = QRect( numberRect.x() + numberRect.width(), r.y(), (double)remWidth * 0.5, r.height() );
  426. QRect artistRect = QRect( titleRect.x() + titleRect.width(), r.y(), (double)remWidth * 0.5, r.height() );
  427. if ( stateWidth > 0 )
  428. {
  429. // Make sure we don't draw over the state icons
  430. artistRect.setWidth( artistRect.width() - stateWidth );
  431. }
  432. // draw title
  433. qreal opacityCo = 1.0;
  434. if ( !item->query()->playable() )
  435. opacityCo = 0.5;
  436. painter->setOpacity( 1.0 * opacityCo );
  437. QString text = painter->fontMetrics().elidedText( track->track(), Qt::ElideRight, titleRect.width() - margin );
  438. painter->drawText( titleRect, text, m_centerOption );
  439. // draw artist
  440. painter->setOpacity( 0.8 * opacityCo );
  441. painter->setFont( m_normalFont );
  442. text = painter->fontMetrics().elidedText( track->artist(), Qt::ElideRight, artistRect.width() - margin );
  443. painter->save();
  444. if ( m_hoveringOverArtist == index )
  445. {
  446. QFont f = painter->font();
  447. f.setUnderline( true );
  448. painter->setFont( f );
  449. }
  450. painter->drawText( artistRect, text, m_centerOption );
  451. m_artistNameRects[ index ] = painter->fontMetrics().boundingRect( artistRect, Qt::AlignLeft | Qt::AlignVCenter, text );
  452. painter->restore();
  453. // draw number or source icon
  454. if ( ( option.state & QStyle::State_Selected || hoveringOver() == index ) && item->query()->playable() )
  455. {
  456. const int iconHeight = numberRect.size().height() / 2;
  457. const QRect sourceIconRect( numberRect.x(), numberRect.y() + ( numberRect.size().height() - iconHeight ) / 2, iconHeight, iconHeight );
  458. painter->drawPixmap( sourceIconRect, item->query()->results().first()->sourceIcon( TomahawkUtils::Original, sourceIconRect.size() ) );
  459. }
  460. else
  461. {
  462. painter->setOpacity( 0.6 * opacityCo );
  463. QString number = QString::number( index.row() + 1 );
  464. if ( number.length() < 2 )
  465. number = "0" + number;
  466. painter->drawText( numberRect, number, m_centerOption );
  467. }
  468. if ( item->isPlaying() )
  469. {
  470. if ( m_nowPlaying != index )
  471. {
  472. connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( onPlaybackChange() ), Qt::UniqueConnection );
  473. connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackChange() ), Qt::UniqueConnection );
  474. connect( AudioEngine::instance(), SIGNAL( timerMilliSeconds( qint64 ) ), SLOT( onAudioEngineTick( qint64 ) ), Qt::UniqueConnection );
  475. m_nowPlaying = QPersistentModelIndex( index );
  476. }
  477. int h = extraRect.height() / 2;
  478. QRect playIconRect = extraRect.adjusted( extraRect.width() - h - 8, h / 2, -8, -h / 2 );
  479. playIconRect.setWidth( playIconRect.height() );
  480. painter->drawPixmap( playIconRect, ImageRegistry::instance()->pixmap( RESPATH "images/play.svg", playIconRect.size() ) );
  481. double duration = (double)AudioEngine::instance()->currentTrackTotalTime();
  482. if ( duration <= 0 )
  483. duration = item->query()->track()->duration() * 1000;
  484. if ( duration > 0 )
  485. {
  486. painter->save();
  487. painter->setPen( Qt::transparent );
  488. painter->setBrush( QColor( "#ff004c" ));
  489. QRect playBar = r.adjusted( 0, r.height() + 2, 0, 0 );
  490. playBar.setHeight( 2 );
  491. painter->setOpacity( 0.1 );
  492. painter->drawRect( playBar );
  493. playBar.setWidth( ( (double)AudioEngine::instance()->currentTime() / duration ) * (double)playBar.width() );
  494. painter->setOpacity( 1 );
  495. painter->drawRect( playBar );
  496. painter->restore();
  497. }
  498. }
  499. else if ( track->duration() > 0 )
  500. {
  501. painter->setOpacity( 0.5 * opacityCo );
  502. painter->drawText( extraRect, TomahawkUtils::timeToString( track->duration() ), m_centerRightOption );
  503. }
  504. painter->restore();
  505. return r;
  506. }
  507. bool
  508. PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
  509. {
  510. QStyledItemDelegate::editorEvent( event, model, option, index );
  511. if ( event->type() != QEvent::MouseButtonRelease &&
  512. event->type() != QEvent::MouseMove &&
  513. event->type() != QEvent::Leave )
  514. {
  515. return false;
  516. }
  517. bool hoveringArtist = false;
  518. bool hoveringInfo = false;
  519. bool hoveringLove = false;
  520. bool hoveringDownloadDropDown = false;
  521. Tomahawk::source_ptr hoveredAvatar;
  522. QRect hoveredAvatarRect;
  523. if ( m_infoButtonRects.contains( index ) )
  524. {
  525. const QRect infoRect = m_infoButtonRects[ index ];
  526. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  527. hoveringInfo = infoRect.contains( ev->pos() );
  528. }
  529. if ( m_artistNameRects.contains( index ) )
  530. {
  531. const QRect nameRect = m_artistNameRects[ index ];
  532. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  533. hoveringArtist = nameRect.contains( ev->pos() );
  534. }
  535. if ( m_loveButtonRects.contains( index ) )
  536. {
  537. const QRect loveRect = m_loveButtonRects[ index ];
  538. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  539. hoveringLove = loveRect.contains( ev->pos() );
  540. }
  541. if ( m_downloadDropDownRects.contains( index ) )
  542. {
  543. const QRect downloadDropDownRect = m_downloadDropDownRects[ index ];
  544. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  545. hoveringDownloadDropDown = downloadDropDownRect.contains( ev->pos() );
  546. }
  547. if ( m_avatarBoxRects.contains( index ) )
  548. {
  549. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  550. for ( QHash< Tomahawk::source_ptr, QRect >::const_iterator it = m_avatarBoxRects[ index ].constBegin();
  551. it != m_avatarBoxRects[ index ].constEnd(); ++it )
  552. {
  553. if ( it.value().contains( ev->pos() ) )
  554. {
  555. hoveredAvatar = it.key();
  556. hoveredAvatarRect = it.value();
  557. break;
  558. }
  559. }
  560. }
  561. if ( event->type() == QEvent::MouseMove )
  562. {
  563. if ( hoveringInfo || hoveringLove || hoveringArtist || hoveringDownloadDropDown )
  564. m_view->setCursor( Qt::PointingHandCursor );
  565. else
  566. m_view->setCursor( Qt::ArrowCursor );
  567. if ( !hoveredAvatar.isNull() )
  568. {
  569. QToolTip::showText( m_view->mapToGlobal( hoveredAvatarRect.bottomLeft() ),
  570. hoveredAvatar->friendlyName(),
  571. m_view,
  572. hoveredAvatarRect );
  573. }
  574. if ( hoveringArtist && m_hoveringOverArtist != index )
  575. {
  576. emit updateIndex( m_hoveringOverArtist );
  577. emit updateIndex( index );
  578. m_hoveringOverArtist = index;
  579. }
  580. if ( !hoveringArtist && m_hoveringOverArtist.isValid() )
  581. {
  582. emit updateIndex( m_hoveringOverArtist );
  583. m_hoveringOverArtist = QModelIndex();
  584. }
  585. if ( hoveringDownloadDropDown && m_hoveringOverDownloadButton != index )
  586. {
  587. QPersistentModelIndex ti = m_hoveringOverDownloadButton;
  588. m_hoveringOverDownloadButton = index;
  589. PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
  590. item->requestRepaint();
  591. emit updateIndex( m_hoveringOverDownloadButton );
  592. }
  593. if ( !hoveringDownloadDropDown && m_hoveringOverDownloadButton.isValid() )
  594. {
  595. QPersistentModelIndex ti = m_hoveringOverDownloadButton;
  596. m_hoveringOverDownloadButton = QModelIndex();
  597. PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
  598. item->requestRepaint();
  599. }
  600. if ( m_hoveringOver != index )
  601. {
  602. emit updateIndex( m_hoveringOver );
  603. emit updateIndex( index );
  604. m_hoveringOver = index;
  605. }
  606. // We return false here so the view can still decide to process/trigger things like D&D events
  607. return false;
  608. }
  609. // reset mouse cursor. we switch to a pointing hand cursor when hovering a button
  610. m_view->setCursor( Qt::ArrowCursor );
  611. if ( event->type() == QEvent::MouseButtonRelease )
  612. {
  613. PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( index ) );
  614. if ( !item )
  615. return false;
  616. if ( hoveringArtist )
  617. {
  618. ViewManager::instance()->show( item->query()->track()->artistPtr() );
  619. }
  620. else if ( hoveringLove )
  621. {
  622. item->query()->queryTrack()->setLoved( !item->query()->queryTrack()->loved() );
  623. }
  624. else if ( hoveringDownloadDropDown || ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download ) )
  625. {
  626. if ( DownloadButton::handleEditorEvent( event , m_view, m_model, index ) )
  627. return true;
  628. }
  629. else if ( hoveringInfo )
  630. {
  631. if ( m_model->style() == PlayableProxyModel::SingleColumn )
  632. {
  633. if ( item->query() )
  634. ViewManager::instance()->show( item->query()->track()->toQuery() );
  635. }
  636. else
  637. {
  638. switch ( index.column() )
  639. {
  640. case PlayableModel::Artist:
  641. {
  642. ViewManager::instance()->show( item->query()->track()->artistPtr() );
  643. break;
  644. }
  645. case PlayableModel::Album:
  646. {
  647. ViewManager::instance()->show( item->query()->track()->albumPtr() );
  648. break;
  649. }
  650. case PlayableModel::Track:
  651. {
  652. ViewManager::instance()->show( item->query()->track()->toQuery() );
  653. break;
  654. }
  655. default:
  656. break;
  657. }
  658. }
  659. }
  660. event->accept();
  661. return true;
  662. }
  663. return false;
  664. }
  665. void
  666. PlaylistItemDelegate::resetHoverIndex()
  667. {
  668. if ( !m_model || !m_hoveringOver.isValid() )
  669. return;
  670. QPersistentModelIndex idx = m_hoveringOver;
  671. m_hoveringOver = QModelIndex();
  672. m_hoveringOverArtist = QModelIndex();
  673. m_hoveringOverDownloadButton = QModelIndex();
  674. m_infoButtonRects.clear();
  675. m_loveButtonRects.clear();
  676. m_artistNameRects.clear();
  677. QModelIndex itemIdx = m_model->mapToSource( idx );
  678. if ( itemIdx.isValid() )
  679. {
  680. PlayableItem* item = m_model->sourceModel()->itemFromIndex( itemIdx );
  681. if ( item )
  682. item->requestRepaint();
  683. }
  684. emit updateIndex( idx );
  685. }
  686. void
  687. PlaylistItemDelegate::modelChanged()
  688. {
  689. m_pixmaps.clear();
  690. }
  691. void
  692. PlaylistItemDelegate::doUpdateIndex( const QPersistentModelIndex& index )
  693. {
  694. if ( index.isValid() )
  695. emit updateIndex( index );
  696. }
  697. void
  698. PlaylistItemDelegate::onAudioEngineTick( qint64 /* ms */ )
  699. {
  700. doUpdateIndex( m_nowPlaying );
  701. }
  702. void
  703. PlaylistItemDelegate::onPlaybackChange()
  704. {
  705. disconnect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( onPlaybackChange() ) );
  706. disconnect( AudioEngine::instance(), SIGNAL( stopped() ), this, SLOT( onPlaybackChange() ) );
  707. disconnect( AudioEngine::instance(), SIGNAL( timerMilliSeconds( qint64 ) ), this, SLOT( onAudioEngineTick( qint64 ) ) );
  708. doUpdateIndex( m_nowPlaying );
  709. m_nowPlaying = QModelIndex();
  710. }