/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 562 lines · 409 code · 116 blank · 37 comment · 72 complexity · 1c5070d0ae7e97f17f6cf7cdc9d4f529 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  4. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. *
  6. * Tomahawk is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Tomahawk is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "DynamicWidget.h"
  20. #include "DynamicControlList.h"
  21. #include "playlist/dynamic/DynamicModel.h"
  22. #include "playlist/PlayableProxyModel.h"
  23. #include "playlist/PlayableItem.h"
  24. #include "playlist/dynamic/GeneratorInterface.h"
  25. #include "playlist/dynamic/GeneratorFactory.h"
  26. #include "Pipeline.h"
  27. #include "Source.h"
  28. #include "audio/AudioEngine.h"
  29. #include "ReadOrWriteWidget.h"
  30. #include "CollapsibleControls.h"
  31. #include "DynamicControlWrapper.h"
  32. #include "ViewManager.h"
  33. #include "playlist/dynamic/DynamicView.h"
  34. #include "DynamicSetupWidget.h"
  35. #include "widgets/BasicHeader.h"
  36. #include "utils/AnimatedSpinner.h"
  37. #include "utils/TomahawkUtilsGui.h"
  38. #include "utils/Logger.h"
  39. #include "utils/DpiScaler.h"
  40. #include <QVBoxLayout>
  41. #include <QLabel>
  42. #include <QComboBox>
  43. #include <QPushButton>
  44. #include <QSpinBox>
  45. #include <QEvent>
  46. #include <QPainter>
  47. using namespace Tomahawk;
  48. DynamicWidget::DynamicWidget( const Tomahawk::dynplaylist_ptr& playlist, QWidget* parent )
  49. : QWidget( parent )
  50. , m_layout( new QVBoxLayout )
  51. , m_resolveOnNextLoad( false )
  52. , m_seqRevLaunched( 0 )
  53. , m_activePlaylist( false )
  54. , m_setup( 0 )
  55. , m_runningOnDemand( false )
  56. , m_controlsChanged( false )
  57. , m_steering( 0 )
  58. , m_controls( 0 )
  59. , m_view( 0 )
  60. , m_model()
  61. {
  62. m_header = new BasicHeader;
  63. m_layout->addWidget( m_header );
  64. m_controls = new CollapsibleControls( this );
  65. m_layout->addWidget( m_controls );
  66. setContentsMargins( 0, 0, 0, 1 ); // to align the bottom with the bottom of the sourcelist
  67. m_model = new DynamicModel( this );
  68. m_view = new DynamicView( this );
  69. m_view->setDynamicModel( m_model );
  70. m_view->setContentsMargins( 0, 0, 0, 0 );
  71. m_layout->addWidget( m_view, 1 );
  72. connect( m_model, SIGNAL( collapseFromTo( int, int ) ), m_view, SLOT( collapseEntries( int, int ) ) );
  73. connect( m_model, SIGNAL( trackGenerationFailure( QString ) ), this, SLOT( stationFailed( QString ) ) );
  74. m_loading = new AnimatedSpinner( m_view );
  75. connect( m_model, SIGNAL( tracksAdded() ), m_loading, SLOT( fadeOut() ) );
  76. m_setup = new DynamicSetupWidget( playlist, this );
  77. m_setup->fadeIn();
  78. connect( m_model, SIGNAL( tracksAdded() ), this, SLOT( tracksAdded() ) );
  79. loadDynamicPlaylist( playlist );
  80. m_layout->setContentsMargins( 0, 0, 0, 0 );
  81. m_layout->setMargin( 0 );
  82. m_layout->setSpacing( 0 );
  83. setLayout( m_layout );
  84. connect( m_setup, SIGNAL( generatePressed( int ) ), this, SLOT( generate( int ) ) );
  85. connect( m_setup, SIGNAL( typeChanged( QString ) ), this, SLOT( playlistTypeChanged( QString ) ) );
  86. layoutFloatingWidgets();
  87. connect( m_controls, SIGNAL( controlChanged( Tomahawk::dyncontrol_ptr ) ), this, SLOT( controlChanged( Tomahawk::dyncontrol_ptr ) ), Qt::QueuedConnection );
  88. connect( m_controls, SIGNAL( controlsChanged( bool ) ), this, SLOT( controlsChanged( bool ) ), Qt::QueuedConnection );
  89. connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( trackStarted() ) );
  90. connect( AudioEngine::instance(), SIGNAL( playlistChanged( Tomahawk::playlistinterface_ptr ) ), this, SLOT( playlistChanged( Tomahawk::playlistinterface_ptr ) ) );
  91. }
  92. DynamicWidget::~DynamicWidget()
  93. {
  94. }
  95. dynplaylist_ptr
  96. DynamicWidget::playlist()
  97. {
  98. return m_playlist;
  99. }
  100. void
  101. DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist )
  102. {
  103. // special case: if we have launched multiple setRevision calls, and the number of controls is different, it means that we're getting an intermediate setRevision
  104. // called after the user has already created more revisions. ignore in that case.
  105. if ( m_playlist.data() == playlist.data() && m_seqRevLaunched > 0
  106. && m_controls->controls().size() != playlist->generator()->controls().size() // different number of controls
  107. && qAbs( m_playlist->generator()->controls().size() - playlist->generator()->controls().size() ) < m_seqRevLaunched )
  108. { // difference in controls has to be less than how many revisions we launched
  109. return;
  110. }
  111. m_seqRevLaunched = 0;
  112. // if we're being told to load the same dynamic playlist over again, only do it if the controls have a different number
  113. if ( !m_playlist.isNull() && ( m_playlist.data() == playlist.data() ) // same playlist pointer
  114. && m_playlist->generator()->controls().size() == playlist->generator()->controls().size() )
  115. {
  116. // we can skip our work. just let the dynamiccontrollist show the difference
  117. m_controls->setControls( m_playlist, m_playlist->author()->isLocal() );
  118. m_playlist = playlist;
  119. if ( !m_runningOnDemand )
  120. m_model->loadPlaylist( m_playlist );
  121. else if ( !m_controlsChanged ) // if the controls changed, we already dealt with that and don't want to change station yet
  122. m_model->changeStation();
  123. m_controlsChanged = false;
  124. return;
  125. }
  126. if ( !m_playlist.isNull() )
  127. {
  128. disconnect( m_playlist->generator().data(), SIGNAL( generated( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksGenerated( QList<Tomahawk::query_ptr> ) ) );
  129. disconnect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), this, SLOT(onRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ) );
  130. disconnect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) );
  131. disconnect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDeleted() ) );
  132. disconnect( m_playlist.data(), SIGNAL( changed() ), this, SLOT( onChanged() ) );
  133. }
  134. m_playlist = playlist;
  135. m_view->setOnDemand( m_playlist->mode() == OnDemand );
  136. m_view->setReadOnly( !m_playlist->author()->isLocal() );
  137. m_model->loadPlaylist( m_playlist );
  138. m_controlsChanged = false;
  139. m_setup->setPlaylist( m_playlist );
  140. m_header->setCaption( m_playlist->title() );
  141. if ( !m_playlist->author()->isLocal() ) // hide controls, as we show the description in the summary
  142. m_layout->removeWidget( m_controls );
  143. else if ( m_layout->indexOf( m_controls ) == -1 )
  144. m_layout->insertWidget( 0, m_controls );
  145. connect( m_playlist->generator().data(), SIGNAL( generated( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksGenerated( QList<Tomahawk::query_ptr> ) ) );
  146. connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) );
  147. connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) );
  148. connect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDeleted() ) );
  149. connect( m_playlist.data(), SIGNAL( changed() ), this, SLOT( onChanged() ) );
  150. if ( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() )
  151. showPreview();
  152. if ( !m_playlist.isNull() )
  153. m_controls->setControls( m_playlist, m_playlist->author()->isLocal() );
  154. }
  155. void
  156. DynamicWidget::onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev )
  157. {
  158. Q_UNUSED( rev );
  159. tDebug( LOGVERBOSE ) << "DynamicWidget::onRevisionLoaded" << rev.revisionguid;
  160. if ( m_model->ignoreRevision( rev.revisionguid ) )
  161. {
  162. m_model->removeRevisionFromIgnore( rev.revisionguid );
  163. return;
  164. }
  165. loadDynamicPlaylist( m_playlist );
  166. if( m_resolveOnNextLoad || !m_playlist->author()->isLocal() )
  167. {
  168. m_playlist->resolve();
  169. m_resolveOnNextLoad = false;
  170. }
  171. }
  172. Tomahawk::playlistinterface_ptr
  173. DynamicWidget::playlistInterface() const
  174. {
  175. return m_view->proxyModel()->playlistInterface();
  176. }
  177. QSize
  178. DynamicWidget::sizeHint() const
  179. {
  180. // We want to take up as much room as the animated splitter containing us and the queue editor will allow. So we return a bogus huge sizehint
  181. // to avoid having to calculate it which is slow
  182. return QSize( 5000, 5000 );
  183. }
  184. void
  185. DynamicWidget::resizeEvent( QResizeEvent* )
  186. {
  187. layoutFloatingWidgets();
  188. }
  189. void
  190. DynamicWidget::layoutFloatingWidgets()
  191. {
  192. if ( !m_runningOnDemand )
  193. {
  194. int x = ( width() / 2 ) - ( m_setup->size().width() / 2 );
  195. int y = height() - m_setup->size().height() - 40; // padding
  196. m_setup->move( x, y );
  197. }
  198. else if( m_runningOnDemand && m_steering )
  199. {
  200. int x = ( width() / 2 ) - ( m_steering->size().width() / 2 );
  201. int y = height() - m_steering->size().height() - 40; // padding
  202. m_steering->move( x, y );
  203. }
  204. }
  205. void
  206. DynamicWidget::playlistChanged( Tomahawk::playlistinterface_ptr pl )
  207. {
  208. if ( pl == m_view->proxyModel()->playlistInterface() ) // same playlist
  209. m_activePlaylist = true;
  210. else
  211. {
  212. m_activePlaylist = false;
  213. // user started playing something somewhere else, so give it a rest
  214. if ( m_runningOnDemand )
  215. {
  216. stopStation( false );
  217. }
  218. }
  219. }
  220. void
  221. DynamicWidget::showEvent( QShowEvent* )
  222. {
  223. if ( !m_playlist.isNull() && !m_runningOnDemand )
  224. m_setup->fadeIn();
  225. }
  226. void
  227. DynamicWidget::generate( int num )
  228. {
  229. // get the items from the generator, and put them in the playlist
  230. m_view->setDynamicWorking( true );
  231. m_loading->fadeIn();
  232. m_playlist->generator()->generate( num );
  233. }
  234. void
  235. DynamicWidget::stationFailed( const QString& msg )
  236. {
  237. m_view->setDynamicWorking( false );
  238. m_view->showMessage( msg );
  239. m_loading->fadeOut();
  240. stopStation( false );
  241. }
  242. void
  243. DynamicWidget::trackStarted()
  244. {
  245. if ( m_activePlaylist && !m_playlist.isNull() &&
  246. m_playlist->mode() == OnDemand && !m_runningOnDemand )
  247. {
  248. startStation();
  249. }
  250. }
  251. void
  252. DynamicWidget::tracksAdded()
  253. {
  254. if ( m_playlist->mode() == OnDemand && m_runningOnDemand && m_setup->isVisible() )
  255. m_setup->fadeOut();
  256. }
  257. void
  258. DynamicWidget::stopStation( bool stopPlaying )
  259. {
  260. m_model->stopOnDemand( stopPlaying );
  261. m_runningOnDemand = false;
  262. // TODO until i add a qwidget interface
  263. QMetaObject::invokeMethod( m_steering, "fadeOut", Qt::DirectConnection );
  264. m_setup->fadeIn();
  265. }
  266. void
  267. DynamicWidget::startStation()
  268. {
  269. m_runningOnDemand = true;
  270. m_model->startOnDemand();
  271. m_setup->fadeOut();
  272. // show the steering controls
  273. if ( m_playlist->generator()->onDemandSteerable() )
  274. {
  275. // position it horizontally centered, above the botton.
  276. m_steering = m_playlist->generator()->steeringWidget();
  277. Q_ASSERT( m_steering );
  278. connect( m_steering, SIGNAL( steeringChanged() ), this, SLOT( steeringChanged() ) );
  279. int x = ( width() / 2 ) - ( m_steering->size().width() / 2 );
  280. int y = height() - m_steering->size().height() - 40; // padding
  281. m_steering->setParent( this );
  282. m_steering->move( x, y );
  283. // TODO until i add a qwidget interface
  284. QMetaObject::invokeMethod( m_steering, "fadeIn", Qt::DirectConnection );
  285. connect( m_steering, SIGNAL( resized() ), this, SLOT( layoutFloatingWidgets() ) );
  286. }
  287. }
  288. void
  289. DynamicWidget::playlistTypeChanged( QString )
  290. {
  291. // TODO
  292. }
  293. void
  294. DynamicWidget::tracksGenerated( const QList< query_ptr >& queries )
  295. {
  296. int limit = -1; // only limit the "preview" of a station
  297. if ( m_playlist->author()->isLocal() && m_playlist->mode() == Static )
  298. {
  299. m_resolveOnNextLoad = true;
  300. }
  301. else if ( m_playlist->mode() == OnDemand )
  302. {
  303. limit = 5;
  304. }
  305. if ( m_playlist->mode() != OnDemand )
  306. m_loading->fadeOut();
  307. m_model->tracksGenerated( queries, limit );
  308. }
  309. void
  310. DynamicWidget::controlsChanged( bool added )
  311. {
  312. // controlsChanged() is emitted when a control is added or removed
  313. // in the case of addition, it's blank by default... so to avoid an error
  314. // when playing a station just ignore it till we're ready and get a controlChanged()
  315. m_controlsChanged = true;
  316. if ( !m_playlist->author()->isLocal() )
  317. return;
  318. m_playlist->createNewRevision();
  319. m_seqRevLaunched++;
  320. if ( !added )
  321. showPreview();
  322. emit descriptionChanged( m_playlist->generator()->sentenceSummary() );
  323. }
  324. void
  325. DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control )
  326. {
  327. Q_UNUSED( control );
  328. if ( !m_playlist->author()->isLocal() )
  329. return;
  330. m_playlist->createNewRevision();
  331. m_seqRevLaunched++;
  332. showPreview();
  333. emit descriptionChanged( m_playlist->generator()->sentenceSummary() );
  334. }
  335. void
  336. DynamicWidget::steeringChanged()
  337. {
  338. // When steering changes, toss all the tracks that are upcoming, and re-fetch.
  339. // We have to find the currently playing item
  340. QModelIndex playing;
  341. for ( int i = 0; i < m_view->proxyModel()->rowCount( QModelIndex() ); ++i )
  342. {
  343. const QModelIndex cur = m_view->proxyModel()->index( i, 0, QModelIndex() );
  344. PlayableItem* item = m_view->proxyModel()->itemFromIndex( m_view->proxyModel()->mapToSource( cur ) );
  345. if ( item && item->isPlaying() )
  346. {
  347. playing = cur;
  348. break;
  349. }
  350. }
  351. if ( !playing.isValid() )
  352. return;
  353. const int upcoming = m_view->proxyModel()->rowCount( QModelIndex() ) - 1 - playing.row();
  354. tDebug() << "Removing tracks after current in station, found" << upcoming;
  355. QModelIndexList toRemove;
  356. for ( int i = playing.row() + 1; i < m_view->proxyModel()->rowCount( QModelIndex() ); i++ )
  357. {
  358. toRemove << m_view->proxyModel()->index( i, 0, QModelIndex() );
  359. }
  360. m_view->proxyModel()->removeIndexes( toRemove );
  361. m_playlist->generator()->fetchNext();
  362. }
  363. void
  364. DynamicWidget::showPreview()
  365. {
  366. if ( m_playlist->mode() == OnDemand && !m_runningOnDemand )
  367. {
  368. // if this is a not running station, preview matching tracks
  369. m_model->clear();
  370. generate( 20 ); // ask for more, we'll filter how many we actually want
  371. }
  372. }
  373. void
  374. DynamicWidget::generatorError( const QString& title, const QString& content )
  375. {
  376. m_view->setDynamicWorking( false );
  377. m_loading->fadeOut();
  378. if ( m_runningOnDemand )
  379. {
  380. stopStation( false );
  381. m_view->showMessage( tr( "Station ran out of tracks!\n\nTry tweaking the filters for a new set of songs to play." ) );
  382. }
  383. else
  384. m_view->showMessageTimeout( title, content );
  385. }
  386. void
  387. DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& /* pal */, QRect& r, qreal opacity )
  388. {
  389. p.setBackgroundMode( Qt::TransparentMode );
  390. p.setRenderHint( QPainter::Antialiasing );
  391. p.setOpacity( opacity );
  392. QColor c( 30, 30, 30 );
  393. QPen pen( c.darker(), .5 );
  394. p.setPen( pen );
  395. p.setBrush( c );
  396. p.drawRoundedRect( r, 10, 10 );
  397. p.setOpacity( opacity + .2 );
  398. p.setBrush( QBrush() );
  399. p.setPen( pen );
  400. p.drawRoundedRect( r, 10, 10 );
  401. }
  402. QString
  403. DynamicWidget::description() const
  404. {
  405. return m_model->description();
  406. }
  407. QString
  408. DynamicWidget::title() const
  409. {
  410. return m_model->title();
  411. }
  412. QPixmap
  413. DynamicWidget::pixmap() const
  414. {
  415. if ( m_playlist->mode() == OnDemand )
  416. return TomahawkUtils::defaultPixmap( TomahawkUtils::Station,
  417. TomahawkUtils::Original,
  418. TomahawkUtils::DpiScaler::scaled( this, 80, 80 ) );
  419. else if ( m_playlist->mode() == Static )
  420. return TomahawkUtils::defaultPixmap( TomahawkUtils::AutomaticPlaylist,
  421. TomahawkUtils::Original,
  422. TomahawkUtils::DpiScaler::scaled( this, 80, 80 ) );
  423. else
  424. return QPixmap();
  425. }
  426. bool
  427. DynamicWidget::jumpToCurrentTrack()
  428. {
  429. m_view->scrollTo( m_view->proxyModel()->currentIndex(), QAbstractItemView::PositionAtCenter );
  430. return true;
  431. }
  432. void
  433. DynamicWidget::onDeleted()
  434. {
  435. emit destroyed( widget() );
  436. }
  437. void
  438. DynamicWidget::onChanged()
  439. {
  440. if ( !m_playlist.isNull() &&
  441. ViewManager::instance()->currentPage() == this )
  442. {
  443. m_header->setCaption( m_playlist->title() );
  444. emit nameChanged( m_playlist->title() );
  445. }
  446. }