/src/sourcetree/sourcedelegate.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 852 lines · 672 code · 142 blank · 38 comment · 172 complexity · 4f8ca34a37d00a784da53056f3687fc9 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2011-2012, Leo Franchi <lfranchi@kde.org>
  5. * Copyright 2011, Michael Zanetti <mzanetti@kde.org>
  6. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  7. *
  8. * Tomahawk is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Tomahawk is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include "SourceDelegate.h"
  22. #include "items/SourceTreeItem.h"
  23. #include "items/SourceItem.h"
  24. #include "items/PlaylistItems.h"
  25. #include "items/CategoryItems.h"
  26. #include "items/TemporaryPageItem.h"
  27. #include "utils/TomahawkUtilsGui.h"
  28. #include "audio/AudioEngine.h"
  29. #include "AnimationHelper.h"
  30. #include "Source.h"
  31. #include "TomahawkSettings.h"
  32. #include "ActionCollection.h"
  33. #include "ViewManager.h"
  34. #include "ContextMenu.h"
  35. #include <QApplication>
  36. #include <QPainter>
  37. #include <QMouseEvent>
  38. #define TREEVIEW_INDENT_ADD 12
  39. SourceDelegate::SourceDelegate( QAbstractItemView* parent )
  40. : QStyledItemDelegate( parent )
  41. , m_parent( parent )
  42. , m_lastClicked( -1 )
  43. {
  44. m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack );
  45. m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum );
  46. m_dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist );
  47. m_dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems );
  48. m_dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 );
  49. m_dropTypeTextMap.insert( 0, tr( "Track" ) );
  50. m_dropTypeTextMap.insert( 1, tr( "Album" ) );
  51. m_dropTypeTextMap.insert( 2, tr( "Artist" ) );
  52. m_dropTypeTextMap.insert( 3, tr( "Local" ) );
  53. m_dropTypeTextMap.insert( 4, tr( "Top 10" ) );
  54. m_dropTypeImageMap.insert( 0, QPixmap( RESPATH "images/drop-song.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
  55. m_dropTypeImageMap.insert( 1, QPixmap( RESPATH "images/drop-album.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
  56. m_dropTypeImageMap.insert( 2, QPixmap( RESPATH "images/drop-all-songs.png" ).scaledToHeight( 32, Qt::SmoothTransformation ) );
  57. m_dropTypeImageMap.insert( 3, QPixmap( RESPATH "images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
  58. m_dropTypeImageMap.insert( 4, QPixmap( RESPATH "images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
  59. m_dropMimeData = new QMimeData();
  60. m_headphonesOff.load( RESPATH "images/headphones-off.png" );
  61. m_headphonesOn.load( RESPATH "images/headphones-sidebar.png" );
  62. m_realtimeLocked.load( RESPATH "images/closed-padlock.png" );
  63. m_realtimeUnlocked.load( RESPATH "images/open-padlock.png" );
  64. m_nowPlayingSpeaker.load( RESPATH "images/now-playing-speaker.png" );
  65. m_nowPlayingSpeakerDark.load( RESPATH "images/now-playing-speaker-dark.png" );
  66. m_collaborativeOn.load( RESPATH "images/green-dot.png" );
  67. }
  68. SourceDelegate::~SourceDelegate()
  69. {
  70. delete m_dropMimeData;
  71. }
  72. QSize
  73. SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
  74. {
  75. SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
  76. SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
  77. if ( type == SourcesModel::Collection )
  78. {
  79. return QSize( option.rect.width(), option.fontMetrics.height() * 3.0 );
  80. }
  81. else if ( type == SourcesModel::Divider )
  82. {
  83. return QSize( option.rect.width(), 6 );
  84. }
  85. else if ( type == SourcesModel::Group )
  86. {
  87. int groupSpacer = index.row() > 0 ? option.fontMetrics.height() * 0.6 : option.fontMetrics.height() * 0.2;
  88. return QSize( option.rect.width(), option.fontMetrics.height() + groupSpacer );
  89. }
  90. else if ( m_expandedMap.contains( index ) )
  91. {
  92. if ( !m_expandedMap.value( index )->initialized() )
  93. {
  94. int dropTypes = dropTypeCount( item );
  95. QSize originalSize = QSize( option.rect.width(), option.fontMetrics.height() * 1.4 );
  96. QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 38 + option.fontMetrics.height() * 1.4 );
  97. m_expandedMap.value( index )->initialize( originalSize, targetSize, 300 );
  98. m_expandedMap.value( index )->expand();
  99. }
  100. QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) );
  101. return m_expandedMap.value( index )->size();
  102. }
  103. else
  104. return QSize( option.rect.width(), option.fontMetrics.height() * 1.4 ); //QStyledItemDelegate::sizeHint( option, index ) );
  105. }
  106. void
  107. SourceDelegate::paintDecorations( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  108. {
  109. SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
  110. SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
  111. // Paint the speaker icon next to the currently-playing playlist
  112. const bool playable = ( type == SourcesModel::StaticPlaylist ||
  113. type == SourcesModel::AutomaticPlaylist ||
  114. type == SourcesModel::Station ||
  115. type == SourcesModel::TemporaryPage ||
  116. type == SourcesModel::LovedTracksPage ||
  117. type == SourcesModel::GenericPage );
  118. const bool playing = ( AudioEngine::instance()->isPlaying() || AudioEngine::instance()->isPaused() );
  119. if ( playable && playing && item->isBeingPlayed() )
  120. {
  121. int iconW = option.rect.height() - 4;
  122. if ( m_expandedMap.contains( index ) )
  123. {
  124. AnimationHelper* ah = m_expandedMap.value( index );
  125. if ( ah->initialized() )
  126. {
  127. iconW = ah->originalSize().height() - 4;
  128. }
  129. }
  130. QRect iconRect = QRect( 4, option.rect.y() + 2, iconW, iconW );
  131. QPixmap speaker = option.state & QStyle::State_Selected ? m_nowPlayingSpeaker : m_nowPlayingSpeakerDark;
  132. speaker = speaker.scaledToHeight( iconW, Qt::SmoothTransformation );
  133. painter->drawPixmap( iconRect, speaker );
  134. }
  135. }
  136. void
  137. SourceDelegate::paintCollection( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  138. {
  139. painter->save();
  140. QFont normal = option.font;
  141. QFont bold = option.font;
  142. bold.setBold( true );
  143. QFont figFont = bold;
  144. figFont.setFamily( "Arial Bold" );
  145. figFont.setWeight( QFont::Black );
  146. figFont.setPointSize( normal.pointSize() - 1 );
  147. SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
  148. SourceItem* colItem = qobject_cast< SourceItem* >( item );
  149. Q_ASSERT( colItem );
  150. bool status = !( !colItem || colItem->source().isNull() || !colItem->source()->isOnline() );
  151. QString tracks;
  152. QString name = index.data().toString();
  153. int figWidth = 0;
  154. if ( status && colItem && !colItem->source().isNull() )
  155. {
  156. tracks = QString::number( colItem->source()->trackCount() );
  157. figWidth = QFontMetrics( figFont ).width( tracks );
  158. name = colItem->source()->friendlyName();
  159. }
  160. QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 );
  161. QPixmap avatar = colItem->pixmap( iconRect.size() );
  162. painter->drawPixmap( iconRect, avatar );
  163. if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
  164. {
  165. painter->setPen( option.palette.color( QPalette::HighlightedText ) );
  166. }
  167. QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 28, 0 );
  168. if ( status || colItem->source().isNull() )
  169. painter->setFont( bold );
  170. QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() );
  171. painter->drawText( textRect, text );
  172. bool isPlaying = !( colItem->source()->currentTrack().isNull() );
  173. QString desc = colItem->source()->textStatus();
  174. if ( colItem->source().isNull() )
  175. desc = tr( "All available tracks" );
  176. painter->setFont( normal );
  177. textRect = option.rect.adjusted( iconRect.width() + 8, option.rect.height() / 2, -figWidth - 24, -6 );
  178. bool privacyOn = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::FullyPrivate;
  179. if ( !colItem->source().isNull() && colItem->source()->isLocal() && privacyOn )
  180. {
  181. QRect pmRect = textRect;
  182. pmRect.setRight( pmRect.left() + pmRect.height() );
  183. ActionCollection::instance()->getAction( "togglePrivacy" )->icon().paint( painter, pmRect );
  184. textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
  185. }
  186. if ( isPlaying || ( !colItem->source().isNull() && colItem->source()->isLocal() ) )
  187. {
  188. // Show a listen icon
  189. QPixmap listenAlongPixmap;
  190. QPixmap realtimeListeningAlongPixmap;
  191. if ( index.data( SourcesModel::LatchedOnRole ).toBool() )
  192. {
  193. // Currently listening along
  194. listenAlongPixmap = m_headphonesOn;
  195. if ( !colItem->source()->isLocal() )
  196. {
  197. realtimeListeningAlongPixmap =
  198. colItem->source()->playlistInterface()->latchMode() == Tomahawk::PlaylistModes::RealTime ?
  199. m_realtimeLocked : m_realtimeUnlocked;
  200. }
  201. }
  202. else if ( !colItem->source()->isLocal() )
  203. {
  204. listenAlongPixmap = m_headphonesOff;
  205. }
  206. if ( !listenAlongPixmap.isNull() )
  207. {
  208. QRect pmRect = textRect;
  209. pmRect.setRight( pmRect.left() + pmRect.height() );
  210. painter->drawPixmap( pmRect, listenAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
  211. textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
  212. m_headphoneRects[ index ] = pmRect;
  213. }
  214. else
  215. m_headphoneRects.remove( index );
  216. if ( !realtimeListeningAlongPixmap.isNull() )
  217. {
  218. QRect pmRect = textRect;
  219. pmRect.setRight( pmRect.left() + pmRect.height() );
  220. painter->drawPixmap( pmRect, realtimeListeningAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
  221. textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
  222. m_lockRects[ index ] = pmRect;
  223. }
  224. else
  225. m_lockRects.remove( index );
  226. }
  227. textRect.adjust( 0, 0, 0, 2 );
  228. text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() - 8 );
  229. QTextOption to( Qt::AlignVCenter );
  230. to.setWrapMode( QTextOption::NoWrap );
  231. painter->drawText( textRect, text, to );
  232. if ( colItem->source() && colItem->source()->currentTrack() )
  233. m_trackRects[ index ] = textRect;
  234. else
  235. m_trackRects.remove( index );
  236. if ( status )
  237. {
  238. painter->setRenderHint( QPainter::Antialiasing );
  239. QRect figRect = option.rect.adjusted( option.rect.width() - figWidth - 13, 0, -14, -option.rect.height() + option.fontMetrics.height() * 1.1 );
  240. int hd = ( option.rect.height() - figRect.height() ) / 2;
  241. figRect.adjust( 0, hd, 0, hd );
  242. painter->setFont( figFont );
  243. QColor figColor( 167, 183, 211 );
  244. painter->setPen( figColor );
  245. painter->setBrush( figColor );
  246. TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect );
  247. }
  248. painter->restore();
  249. }
  250. void
  251. SourceDelegate::paintCategory( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  252. {
  253. painter->save();
  254. QTextOption to( Qt::AlignVCenter );
  255. painter->setPen( option.palette.color( QPalette::Base ) );
  256. painter->setBrush( option.palette.color( QPalette::Base ) );
  257. painter->drawRect( option.rect );
  258. painter->setRenderHint( QPainter::Antialiasing );
  259. painter->setPen( Qt::white );
  260. painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
  261. painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
  262. painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
  263. if ( option.state & QStyle::State_MouseOver )
  264. {
  265. QString text = tr( "Show" );
  266. if ( option.state & QStyle::State_Open )
  267. text = tr( "Hide" );
  268. QFont font = option.font;
  269. font.setBold( true );
  270. painter->setFont( font );
  271. QTextOption to( Qt::AlignVCenter | Qt::AlignRight );
  272. // draw close icon
  273. painter->setPen( Qt::white );
  274. painter->drawText( option.rect.translated( -4, 1 ), text, to );
  275. painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
  276. painter->drawText( option.rect.translated( -4, 0 ), text, to );
  277. }
  278. painter->restore();
  279. }
  280. void
  281. SourceDelegate::paintGroup( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  282. {
  283. painter->save();
  284. QFont font = painter->font();
  285. font.setPointSize( option.font.pointSize() + 1 );
  286. font.setBold( true );
  287. painter->setFont( font );
  288. QTextOption to( Qt::AlignBottom );
  289. painter->setPen( option.palette.color( QPalette::Base ) );
  290. painter->setBrush( option.palette.color( QPalette::Base ) );
  291. painter->drawRect( option.rect );
  292. painter->setRenderHint( QPainter::Antialiasing );
  293. painter->setPen( Qt::white );
  294. painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
  295. painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
  296. painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
  297. if ( option.state & QStyle::State_MouseOver )
  298. {
  299. QString text = tr( "Show" );
  300. if ( option.state & QStyle::State_Open )
  301. text = tr( "Hide" );
  302. QFont font = option.font;
  303. font.setBold( true );
  304. painter->setFont( font );
  305. QTextOption to( Qt::AlignBottom | Qt::AlignRight );
  306. // draw close icon
  307. painter->setPen( Qt::white );
  308. painter->drawText( option.rect.translated( -4, 1 ), text, to );
  309. painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
  310. painter->drawText( option.rect.translated( -4, 0 ), text, to );
  311. }
  312. painter->restore();
  313. }
  314. void
  315. SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  316. {
  317. QStyleOptionViewItem o = option;
  318. QStyleOptionViewItemV4 o3 = option;
  319. painter->save();
  320. SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
  321. SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
  322. Q_ASSERT( item );
  323. if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled )
  324. {
  325. o.state = QStyle::State_Enabled;
  326. if ( ( option.state & QStyle::State_MouseOver ) == QStyle::State_MouseOver )
  327. {
  328. o.state |= QStyle::State_MouseOver;
  329. o3.state |= QStyle::State_MouseOver;
  330. }
  331. if ( ( option.state & QStyle::State_Open ) == QStyle::State_Open )
  332. {
  333. o.state |= QStyle::State_Open;
  334. }
  335. if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
  336. {
  337. if ( type != SourcesModel::Collection )
  338. o3.state |= QStyle::State_Selected;
  339. else
  340. o3.state &= ~QStyle::State_Selected;
  341. o.palette.setColor( QPalette::Base, QColor( 0, 0, 0, 0 ) );
  342. o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
  343. o3.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
  344. }
  345. }
  346. // shrink the indentations
  347. {
  348. int indentMult = 0;
  349. QModelIndex counter = index;
  350. while ( counter.parent().isValid() )
  351. {
  352. indentMult++;
  353. counter = counter.parent();
  354. }
  355. int indentDelta = o.rect.x() - m_parent->viewport()->x();
  356. o.rect.setX( o.rect.x() - indentDelta + indentMult * TREEVIEW_INDENT_ADD );
  357. o3.rect.setX( 0 );
  358. }
  359. if ( type != SourcesModel::Group && type != SourcesModel::Category && type != SourcesModel::Divider )
  360. QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter );
  361. if ( type == SourcesModel::Collection )
  362. {
  363. paintCollection( painter, o, index );
  364. }
  365. else if ( ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) &&
  366. m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 )
  367. {
  368. // Let Qt paint the original item. We add our stuff after it
  369. o.state &= ~QStyle::State_Selected;
  370. o.showDecorationSelected = false;
  371. o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() );
  372. QStyledItemDelegate::paint( painter, o, index );
  373. // Get whole rect for the menu
  374. QRect itemsRect = option.rect.adjusted( -option.rect.x(), m_expandedMap.value( index )->originalSize().height(), 0, 0 );
  375. QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() );
  376. bool cursorInRect = itemsRect.contains( cursorPos );
  377. // draw the background
  378. if ( m_gradient.finalStop() != itemsRect.bottomLeft() )
  379. {
  380. m_gradient = QLinearGradient( itemsRect.topLeft(), itemsRect.bottomLeft() );
  381. m_gradient.setColorAt( 0.0, Qt::white );
  382. m_gradient.setColorAt( 0.9, QColor( 0x88, 0x88, 0x88 ) );
  383. m_gradient.setColorAt( 1.0, QColor( 0x99, 0x99, 0x99 ) ); // dark grey
  384. }
  385. QPen pen = painter->pen();
  386. painter->setPen( QPen( Qt::NoPen ) );
  387. painter->setBrush( m_gradient );
  388. painter->drawRect( itemsRect );
  389. // calculate sizes for the icons
  390. int totalCount = dropTypeCount( item );
  391. int itemWidth = itemsRect.width() / totalCount;
  392. int iconSpacing = ( itemWidth - 32 ) / 2;
  393. // adjust to one single entry
  394. itemsRect.adjust( 0, 0, -itemsRect.width() + itemWidth, 0 );
  395. pen.setColor( Qt::white );
  396. painter->setPen( pen );
  397. QFont font = painter->font();
  398. font.setPointSize( option.font.pointSize() - 1 );
  399. painter->setFont( font );
  400. QFont fontBold = painter->font();
  401. fontBold.setBold( true );
  402. QRect textRect;
  403. QRect imageRect;
  404. SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData );
  405. int count = 0;
  406. for ( int i = 0; i < 5; ++i )
  407. {
  408. if ( !dropTypes.testFlag( m_dropTypeMap.value( i ) ) )
  409. continue;
  410. if ( count > 0 )
  411. itemsRect.adjust( itemWidth, 0, itemWidth, 0 );
  412. if ( itemsRect.contains( cursorPos ) | !cursorInRect )
  413. {
  414. painter->setFont( fontBold );
  415. m_hoveredDropType = m_dropTypeMap.value( i );
  416. cursorInRect = true;
  417. }
  418. else
  419. painter->setFont( font );
  420. int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2;
  421. textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 2, 0, 0 );
  422. painter->drawText( textRect, m_dropTypeTextMap.value( i ) );
  423. int maxHeight = itemsRect.height() - textRect.height() - 2;
  424. int verticalOffset = qMax( 0, maxHeight - 32 );
  425. if ( itemsRect.bottom() - textRect.height() - 2 > itemsRect.top() )
  426. {
  427. imageRect = itemsRect.adjusted( iconSpacing, verticalOffset, -iconSpacing, -textRect.height() - 2 );
  428. painter->drawPixmap( imageRect.x(), imageRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 32 - imageRect.height(), 32, imageRect.height() ) );
  429. }
  430. count++;
  431. }
  432. }
  433. else if ( type == SourcesModel::Group )
  434. {
  435. paintGroup( painter, o3, index );
  436. }
  437. else if ( type == SourcesModel::Category )
  438. {
  439. paintCategory( painter, o, index );
  440. }
  441. else if ( type == SourcesModel::Divider )
  442. {
  443. QRect middle = o.rect.adjusted( 0, 2, 0, -2 );
  444. painter->setRenderHint( QPainter::Antialiasing, false );
  445. QColor bgcolor = o3.palette.color( QPalette::Base );
  446. painter->setPen( bgcolor.darker( 120 ) );
  447. painter->drawLine( middle.topLeft(), middle.topRight() );
  448. painter->setPen( bgcolor.lighter( 120 ) );
  449. painter->drawLine( middle.bottomLeft(), middle.bottomRight() );
  450. }
  451. else
  452. {
  453. o.state &= ~QStyle::State_MouseOver;
  454. if ( !index.parent().parent().isValid() )
  455. o.rect.adjust( 7, 0, 0, 0 );
  456. if ( type == SourcesModel::TemporaryPage )
  457. {
  458. TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( item );
  459. Q_ASSERT( gpi );
  460. if ( gpi && o3.state & QStyle::State_MouseOver )
  461. {
  462. int padding = 3;
  463. m_iconHeight = ( o3.rect.height() - 2 * padding );
  464. o.rect.adjust( 0, 0, -( padding + m_iconHeight ), 0 );
  465. QStyledItemDelegate::paint( painter, o, index );
  466. // draw close icon
  467. QPixmap p( RESPATH "images/list-remove.png" );
  468. p = p.scaledToHeight( m_iconHeight, Qt::SmoothTransformation );
  469. QRect r( o3.rect.right() - padding - m_iconHeight, padding + o3.rect.y(), m_iconHeight, m_iconHeight );
  470. painter->drawPixmap( r, p );
  471. }
  472. else
  473. QStyledItemDelegate::paint( painter, o, index );
  474. }
  475. else if ( type == SourcesModel::StaticPlaylist )
  476. {
  477. QStyledItemDelegate::paint( painter, o, index );
  478. PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item );
  479. if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
  480. {
  481. const int padding = 2;
  482. const int imgWidth = o.rect.height() - 2*padding;
  483. const QPixmap icon = plItem->subscribedIcon().scaled( imgWidth, imgWidth, Qt::KeepAspectRatio, Qt::SmoothTransformation );
  484. const QRect subRect( o.rect.right() - padding - imgWidth, o.rect.top() + padding, imgWidth, imgWidth );
  485. painter->drawPixmap( subRect, icon );
  486. }
  487. if ( plItem->collaborative() )
  488. {
  489. const int imgWidth = m_collaborativeOn.size().width();
  490. const QRect subRect( o.rect.left(), o.rect.top(), imgWidth, imgWidth );
  491. painter->drawPixmap( subRect, m_collaborativeOn );
  492. }
  493. }
  494. else
  495. QStyledItemDelegate::paint( painter, o, index );
  496. }
  497. paintDecorations( painter, o3, index );
  498. painter->restore();
  499. }
  500. void
  501. SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  502. {
  503. if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist )
  504. editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) );
  505. else
  506. QStyledItemDelegate::updateEditorGeometry( editor, option, index );
  507. editor->setGeometry( editor->geometry().adjusted( 2 * TREEVIEW_INDENT_ADD, 0, 0, 0 ) );
  508. }
  509. bool
  510. SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
  511. {
  512. bool hoveringTrack = false;
  513. if ( m_trackRects.contains( index ) )
  514. {
  515. const QRect trackRect = m_trackRects[ index ];
  516. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  517. hoveringTrack = trackRect.contains( ev->pos() );
  518. }
  519. bool lockRectContainsClick = false, headphonesRectContainsClick = false;
  520. if ( m_headphoneRects.contains( index ) )
  521. {
  522. const QRect headphoneRect = m_headphoneRects[ index ];
  523. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  524. headphonesRectContainsClick = headphoneRect.contains( ev->pos() );
  525. }
  526. if ( m_lockRects.contains( index ) )
  527. {
  528. const QRect lockRect = m_lockRects[ index ];
  529. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  530. lockRectContainsClick = lockRect.contains( ev->pos() );
  531. }
  532. if ( event->type() == QEvent::MouseMove )
  533. {
  534. if ( hoveringTrack || lockRectContainsClick || headphonesRectContainsClick )
  535. m_parent->setCursor( Qt::PointingHandCursor );
  536. else
  537. m_parent->setCursor( Qt::ArrowCursor );
  538. }
  539. if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress )
  540. {
  541. SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
  542. if ( type == SourcesModel::TemporaryPage )
  543. {
  544. TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
  545. Q_ASSERT( gpi );
  546. QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  547. QStyleOptionViewItemV4 o = option;
  548. initStyleOption( &o, index );
  549. int padding = 3;
  550. QRect r ( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight );
  551. if ( r.contains( ev->pos() ) )
  552. {
  553. if ( event->type() == QEvent::MouseButtonRelease )
  554. {
  555. gpi->removeFromList();
  556. // Send a new mouse event to the view, since if the mouse is now over another item's delete area we want it to show up
  557. QMouseEvent* ev = new QMouseEvent( QEvent::MouseMove, m_parent->viewport()->mapFromGlobal( QCursor::pos() ), Qt::NoButton, Qt::NoButton, Qt::NoModifier );
  558. QApplication::postEvent( m_parent->viewport(), ev );
  559. }
  560. return true;
  561. }
  562. }
  563. else if ( type == SourcesModel::Collection )
  564. {
  565. SourceItem* colItem = qobject_cast< SourceItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
  566. Q_ASSERT( colItem );
  567. if ( hoveringTrack && colItem->source() && colItem->source()->currentTrack() )
  568. {
  569. const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
  570. if ( event->type() == QEvent::MouseButtonRelease && ev->button() == Qt::LeftButton )
  571. {
  572. ViewManager::instance()->show( colItem->source()->currentTrack() );
  573. return true;
  574. }
  575. else if ( event->type() == QEvent::MouseButtonPress && ev->button() == Qt::RightButton )
  576. {
  577. Tomahawk::ContextMenu* contextMenu = new Tomahawk::ContextMenu( m_parent );
  578. contextMenu->setQuery( colItem->source()->currentTrack() );
  579. contextMenu->exec( QCursor::pos() );
  580. return true;
  581. }
  582. }
  583. if ( !colItem->source().isNull() && !colItem->source()->currentTrack().isNull() && !colItem->source()->isLocal() )
  584. {
  585. if ( headphonesRectContainsClick || lockRectContainsClick )
  586. {
  587. if ( event->type() == QEvent::MouseButtonRelease )
  588. {
  589. if ( headphonesRectContainsClick )
  590. {
  591. if ( index.data( SourcesModel::LatchedOnRole ).toBool() )
  592. // unlatch
  593. emit latchOff( colItem->source() );
  594. else
  595. emit latchOn( colItem->source() );
  596. }
  597. else // it's in the lock rect
  598. emit toggleRealtimeLatch( colItem->source(), !index.data( SourcesModel::LatchedRealtimeRole ).toBool() );
  599. }
  600. return true;
  601. }
  602. }
  603. }
  604. else if ( event->type() == QEvent::MouseButtonRelease && type == SourcesModel::StaticPlaylist )
  605. {
  606. PlaylistItem* plItem = qobject_cast< PlaylistItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
  607. Q_ASSERT( plItem );
  608. QMouseEvent* mev = static_cast< QMouseEvent* >( event );
  609. if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
  610. {
  611. const int padding = 2;
  612. const int imgWidth = option.rect.height() - 2*padding;
  613. const QRect subRect( option.rect.right() - padding - imgWidth, option.rect.top() + padding, imgWidth, imgWidth );
  614. if ( subRect.contains( mev->pos() ) )
  615. {
  616. // Toggle playlist subscription
  617. plItem->setSubscribed( !plItem->subscribed() );
  618. }
  619. }
  620. }
  621. }
  622. // We emit our own clicked() signal instead of relying on QTreeView's, because that is fired whether or not a delegate accepts
  623. // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here we make sure we only
  624. // emit if we really want to
  625. if ( event->type() == QEvent::MouseButtonRelease )
  626. {
  627. if ( m_lastClicked == -1 )
  628. {
  629. m_lastClicked = QDateTime::currentMSecsSinceEpoch();
  630. emit clicked( index );
  631. }
  632. else
  633. {
  634. qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastClicked;
  635. if ( elapsed < QApplication::doubleClickInterval() )
  636. {
  637. m_lastClicked = -1;
  638. emit doubleClicked( index );
  639. } else
  640. {
  641. m_lastClicked = QDateTime::currentMSecsSinceEpoch();
  642. emit clicked( index );
  643. }
  644. }
  645. }
  646. return QStyledItemDelegate::editorEvent( event, model, option, index );
  647. }
  648. int
  649. SourceDelegate::dropTypeCount( SourceTreeItem* item ) const
  650. {
  651. int menuCount = 0;
  652. if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisTrack ) )
  653. menuCount++;
  654. if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisAlbum ) )
  655. menuCount++;
  656. if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeAllFromArtist ) )
  657. menuCount++;
  658. if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeLocalItems ) )
  659. menuCount++;
  660. if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeTop50 ) )
  661. menuCount++;
  662. return menuCount;
  663. }
  664. SourceTreeItem::DropType
  665. SourceDelegate::hoveredDropType() const
  666. {
  667. return m_hoveredDropType;
  668. }
  669. void
  670. SourceDelegate::hovered( const QModelIndex& index, const QMimeData* mimeData )
  671. {
  672. if ( !index.isValid() )
  673. {
  674. foreach ( AnimationHelper *helper, m_expandedMap )
  675. {
  676. helper->collapse();
  677. }
  678. return;
  679. }
  680. if ( !m_expandedMap.contains( index ) )
  681. {
  682. foreach ( AnimationHelper *helper, m_expandedMap )
  683. {
  684. helper->collapse();
  685. }
  686. m_newDropHoverIndex = index;
  687. m_dropMimeData->clear();
  688. foreach ( const QString &mimeDataFormat, mimeData->formats() )
  689. {
  690. m_dropMimeData->setData( mimeDataFormat, mimeData->data( mimeDataFormat ) );
  691. }
  692. m_expandedMap.insert( m_newDropHoverIndex, new AnimationHelper( m_newDropHoverIndex ) );
  693. connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) );
  694. }
  695. else
  696. qDebug() << "expandedMap already contains index" << index;
  697. }
  698. void
  699. SourceDelegate::dragLeaveEvent()
  700. {
  701. foreach ( AnimationHelper* helper, m_expandedMap )
  702. {
  703. helper->collapse( true );
  704. }
  705. }
  706. void
  707. SourceDelegate::animationFinished( const QModelIndex& index )
  708. {
  709. delete m_expandedMap.take( index );
  710. }