/src/libtomahawk/ContextMenu.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 531 lines · 402 code · 102 blank · 27 comment · 82 complexity · e63a62e9e2fe72712b77a380b6d3fa4f 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 2013, Teo Mrnjavac <teo@kde.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 "ContextMenu.h"
  20. #include "audio/AudioEngine.h"
  21. #include "playlist/ContextView.h"
  22. #include "playlist/TrackView.h"
  23. #include "playlist/PlayableModel.h"
  24. #include "filemetadata/MetadataEditor.h"
  25. #include "utils/LinkGenerator.h"
  26. #include "ViewManager.h"
  27. #include "Query.h"
  28. #include "Result.h"
  29. #include "collection/Collection.h"
  30. #include "Source.h"
  31. #include "SourceList.h"
  32. #include "Artist.h"
  33. #include "Album.h"
  34. #include "utils/ImageRegistry.h"
  35. #include "utils/Logger.h"
  36. #include <QDesktopServices>
  37. #include <QFileInfo>
  38. using namespace Tomahawk;
  39. ContextMenu::ContextMenu( QWidget* parent )
  40. : QMenu( parent )
  41. , m_playlists_sigmap( 0 )
  42. , m_sources_sigmap( 0 )
  43. , m_loveAction( 0 )
  44. {
  45. setFont( TomahawkUtils::systemFont() );
  46. m_sigmap = new QSignalMapper( this );
  47. connect( m_sigmap, SIGNAL( mapped( int ) ), SLOT( onTriggered( int ) ) );
  48. clear();
  49. }
  50. ContextMenu::~ContextMenu()
  51. {
  52. }
  53. void
  54. ContextMenu::clear()
  55. {
  56. QMenu::clear();
  57. m_queries.clear();
  58. m_albums.clear();
  59. m_artists.clear();
  60. m_supportedActions = ActionPlay | ActionQueue | ActionPlaylist | ActionCopyLink | ActionLove | ActionStopAfter | ActionPage | ActionEditMetadata | ActionSend | ActionOpenFileManager;
  61. }
  62. unsigned int
  63. ContextMenu::itemCount() const
  64. {
  65. return m_queries.count() + m_artists.count() + m_albums.count();
  66. }
  67. void
  68. ContextMenu::addToPlaylist( int playlistIdx )
  69. {
  70. Tomahawk::playlist_ptr playlist = m_playlists.at( playlistIdx );
  71. playlist->addEntries( m_queries );
  72. }
  73. void
  74. ContextMenu::sendToSource( int sourceIdx )
  75. {
  76. const Tomahawk::source_ptr &src = m_sources.at( sourceIdx );
  77. foreach ( Tomahawk::query_ptr query, m_queries )
  78. {
  79. query->queryTrack()->share( src );
  80. }
  81. }
  82. bool
  83. playlistsLessThan( const Tomahawk::playlist_ptr& s1, const Tomahawk::playlist_ptr& s2 )
  84. {
  85. return s1->title().toLower() < s2->title().toLower();
  86. }
  87. bool
  88. sourcesLessThan( const Tomahawk::source_ptr& s1, const Tomahawk::source_ptr& s2 )
  89. {
  90. return s1->friendlyName().toLower() < s2->friendlyName().toLower();
  91. }
  92. void
  93. ContextMenu::setQueries( const QList<Tomahawk::query_ptr>& queries )
  94. {
  95. if ( queries.isEmpty() )
  96. return;
  97. QMenu::clear();
  98. m_queries.clear();
  99. m_queries << queries;
  100. if ( m_supportedActions & ActionPlay && itemCount() == 1 )
  101. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/play.svg" ), tr( "&Play" ) ), ActionPlay );
  102. if ( m_supportedActions & ActionDownload )
  103. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/downloads.svg" ), tr( "Download" ) ), ActionDownload );
  104. if ( m_supportedActions & ActionQueue )
  105. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/queue.svg" ), tr( "Add to &Queue" ) ), ActionQueue );
  106. if ( m_supportedActions & ActionPlaylist )
  107. {
  108. // Get the current list of all playlists.
  109. m_playlists = QList< Tomahawk::playlist_ptr >( SourceList::instance()->getLocal()->dbCollection()->playlists() );
  110. // Sort the playlist
  111. qSort( m_playlists.begin(), m_playlists.end(), playlistsLessThan );
  112. if ( m_playlists_sigmap != 0 )
  113. m_playlists_sigmap->deleteLater();
  114. m_playlists_sigmap = new QSignalMapper( this );
  115. // Build the menu listing all available playlists
  116. QMenu* playlistMenu = addMenu( ImageRegistry::instance()->icon( RESPATH "images/playlist-icon.svg" ), tr( "Add to &Playlist" ) );
  117. for ( int i = 0; i < m_playlists.length(); ++i )
  118. {
  119. QAction* action = new QAction( m_playlists.at( i )->title() , this );
  120. playlistMenu->addAction( action );
  121. m_playlists_sigmap->setMapping( action, i );
  122. connect( action, SIGNAL( triggered() ), m_playlists_sigmap, SLOT( map() ) );
  123. }
  124. connect( m_playlists_sigmap, SIGNAL( mapped( int ) ), this, SLOT( addToPlaylist( int ) ) );
  125. }
  126. if ( m_supportedActions & ActionSend ) //Send to someone's Inbox!
  127. {
  128. // Get the buddies list
  129. m_sources = SourceList::instance()->sources( true );
  130. qSort( m_sources.begin(), m_sources.end(), sourcesLessThan );
  131. if ( m_sources_sigmap != 0 )
  132. m_sources_sigmap->deleteLater();
  133. m_sources_sigmap = new QSignalMapper( this );
  134. QMenu* sourcesMenu = addMenu( ImageRegistry::instance()->icon( RESPATH "images/share.svg" ), tr( "Send to &Friend" ) );
  135. for ( int i = 0; i < m_sources.length(); ++i )
  136. {
  137. QAction* action = new QAction( m_sources.at( i )->friendlyName(), this );
  138. sourcesMenu->addAction( action );
  139. m_sources_sigmap->setMapping( action, i );
  140. connect( action, SIGNAL( triggered() ), m_sources_sigmap, SLOT( map() ) );
  141. }
  142. connect( m_sources_sigmap, SIGNAL( mapped( int ) ), this, SLOT( sendToSource( int ) ) );
  143. }
  144. if ( m_supportedActions & ActionStopAfter && itemCount() == 1 )
  145. {
  146. if ( AudioEngine::instance()->stopAfterTrack() == queries.first() )
  147. m_sigmap->setMapping( addAction( tr( "Continue Playback after this &Track" ) ), ActionStopAfter );
  148. else
  149. m_sigmap->setMapping( addAction( tr( "Stop Playback after this &Track" ) ), ActionStopAfter );
  150. }
  151. addSeparator();
  152. if ( m_supportedActions & ActionLove && itemCount() == 1 )
  153. {
  154. m_loveAction = addAction( tr( "&Love" ) );
  155. m_sigmap->setMapping( m_loveAction, ActionLove );
  156. connect( queries.first()->track().data(), SIGNAL( socialActionsLoaded() ), SLOT( onSocialActionsLoaded() ) );
  157. onSocialActionsLoaded();
  158. }
  159. addSeparator();
  160. if ( m_supportedActions & ActionPage && itemCount() == 1 )
  161. {
  162. // Ampersands need to be escaped as they indicate a keyboard shortcut
  163. const QString track = m_queries.first()->track()->track().replace( QString( "&" ), QString( "&&" ) );
  164. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/track-icon.svg" ),
  165. tr( "View Similar Tracks to \"%1\"" ).arg( track ) ), ActionTrackPage );
  166. if ( !m_queries.first()->track()->album().isEmpty() )
  167. {
  168. const QString album = m_queries.first()->track()->album().replace( QString( "&" ), QString( "&&" ) );
  169. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/album-icon.svg" ),
  170. tr( "Go to \"%1\"" ).arg( album ) ), ActionAlbumPage );
  171. }
  172. const QString artist = m_queries.first()->track()->artist().replace( QString( "&" ), QString( "&&" ) );
  173. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/artist-icon.svg" ),
  174. tr( "Go to \"%1\"" ).arg( artist ) ), ActionArtistPage );
  175. }
  176. addSeparator();
  177. if ( m_supportedActions & ActionCopyLink && itemCount() == 1 )
  178. {
  179. m_sigmap->setMapping( addAction( tr( "&Copy Track Link" ) ), ActionCopyLink );
  180. }
  181. if ( m_supportedActions & ActionOpenFileManager &&
  182. queries.length() == 1 &&
  183. queries.first()->numResults() &&
  184. queries.first()->results().first()->isLocal() )
  185. {
  186. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/folder.svg" ),
  187. tr( "Open Folder in File Manager..." ) ), ActionOpenFileManager );
  188. }
  189. if ( m_supportedActions & ActionEditMetadata && itemCount() == 1 )
  190. {
  191. m_sigmap->setMapping( addAction( tr( "Properties..." ) ), ActionEditMetadata );
  192. }
  193. addSeparator();
  194. if ( m_supportedActions & ActionMarkListened )
  195. {
  196. bool thereAreUnlistenedTracks = false;
  197. foreach ( const Tomahawk::query_ptr& query, m_queries )
  198. {
  199. if ( !query->queryTrack()->isListened() )
  200. {
  201. thereAreUnlistenedTracks = true;
  202. break;
  203. }
  204. }
  205. if ( thereAreUnlistenedTracks )
  206. m_sigmap->setMapping( addAction( tr( "Mark as &Listened" ) ), ActionMarkListened );
  207. }
  208. addSeparator();
  209. if ( m_supportedActions & ActionDelete )
  210. m_sigmap->setMapping( addAction( queries.count() > 1 ? tr( "&Remove Items" ) : tr( "&Remove Item" ) ), ActionDelete );
  211. foreach ( QAction* action, actions() )
  212. {
  213. connect( action, SIGNAL( triggered() ), m_sigmap, SLOT( map() ) );
  214. }
  215. }
  216. void
  217. ContextMenu::setQuery( const Tomahawk::query_ptr& query )
  218. {
  219. if ( query.isNull() )
  220. return;
  221. QList<query_ptr> queries;
  222. queries << query;
  223. setQueries( queries );
  224. }
  225. void
  226. ContextMenu::setAlbums( const QList<Tomahawk::album_ptr>& albums )
  227. {
  228. if ( albums.isEmpty() )
  229. return;
  230. QMenu::clear();
  231. m_albums.clear();
  232. m_albums << albums;
  233. if ( m_supportedActions & ActionQueue )
  234. m_sigmap->setMapping( addAction( tr( "Add to &Queue" ) ), ActionQueue );
  235. addSeparator();
  236. if ( m_supportedActions & ActionPage && itemCount() == 1 )
  237. {
  238. const QString album = m_albums.first()->name().replace( QString( "&" ), QString( "&&" ) );
  239. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/album-icon.svg" ),
  240. tr( "&Go to \"%1\"" ).arg( album ) ), ActionAlbumPage );
  241. const QString artist = m_albums.first()->artist()->name().replace( QString( "&" ), QString( "&&" ) );
  242. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/artist-icon.svg" ),
  243. tr( "Go to \"%1\"" ).arg( artist ) ), ActionArtistPage );
  244. }
  245. //m_sigmap->setMapping( addAction( tr( "&Add to Playlist" ) ), ActionAddToPlaylist );
  246. addSeparator();
  247. if ( m_supportedActions & ActionCopyLink && itemCount() == 1 )
  248. m_sigmap->setMapping( addAction( tr( "Copy Album &Link" ) ), ActionCopyLink );
  249. foreach ( QAction* action, actions() )
  250. {
  251. connect( action, SIGNAL( triggered() ), m_sigmap, SLOT( map() ) );
  252. }
  253. }
  254. void
  255. ContextMenu::setAlbum( const Tomahawk::album_ptr& album )
  256. {
  257. QList<album_ptr> albums;
  258. albums << album;
  259. setAlbums( albums );
  260. }
  261. void
  262. ContextMenu::setArtists( const QList<Tomahawk::artist_ptr>& artists )
  263. {
  264. if ( artists.isEmpty() )
  265. return;
  266. QMenu::clear();
  267. m_artists.clear();
  268. m_artists << artists;
  269. /* if ( m_supportedActions & ActionPlay && itemCount() == 1 )
  270. m_sigmap->setMapping( addAction( tr( "Show &Artist Page" ) ), ActionPlay );*/
  271. if ( m_supportedActions & ActionQueue )
  272. m_sigmap->setMapping( addAction( tr( "Add to &Queue" ) ), ActionQueue );
  273. addSeparator();
  274. if ( m_supportedActions & ActionPage && itemCount() == 1 )
  275. {
  276. const QString artist = m_artists.first()->name().replace( QString( "&" ), QString( "&&" ) );
  277. m_sigmap->setMapping( addAction( ImageRegistry::instance()->icon( RESPATH "images/artist-icon.svg" ),
  278. tr( "&Go to \"%1\"" ).arg( artist ) ), ActionArtistPage );
  279. }
  280. //m_sigmap->setMapping( addAction( tr( "&Add to Playlist" ) ), ActionAddToPlaylist );
  281. addSeparator();
  282. if ( m_supportedActions & ActionCopyLink && itemCount() == 1 )
  283. m_sigmap->setMapping( addAction( tr( "Copy Artist &Link" ) ), ActionCopyLink );
  284. foreach ( QAction* action, actions() )
  285. {
  286. connect( action, SIGNAL( triggered() ), m_sigmap, SLOT( map() ) );
  287. }
  288. }
  289. void
  290. ContextMenu::setArtist( const Tomahawk::artist_ptr& artist )
  291. {
  292. QList<artist_ptr> artists;
  293. artists << artist;
  294. setArtists( artists );
  295. }
  296. void
  297. ContextMenu::onTriggered( int action )
  298. {
  299. switch ( action )
  300. {
  301. case ActionQueue:
  302. addToQueue();
  303. break;
  304. case ActionCopyLink:
  305. copyLink();
  306. break;
  307. case ActionTrackPage:
  308. case ActionArtistPage:
  309. case ActionAlbumPage:
  310. openPage( (MenuActions)action );
  311. break;
  312. case ActionLove:
  313. m_queries.first()->track()->setLoved( !m_queries.first()->track()->loved() );
  314. break;
  315. case ActionStopAfter:
  316. if ( m_queries.first()->equals( AudioEngine::instance()->stopAfterTrack() ) )
  317. AudioEngine::instance()->setStopAfterTrack( query_ptr() );
  318. else
  319. AudioEngine::instance()->setStopAfterTrack( m_queries.first() );
  320. break;
  321. case ActionEditMetadata:
  322. {
  323. MetadataEditor* d = new MetadataEditor( m_queries.first(), m_interface, this );
  324. d->show();
  325. }
  326. break;
  327. case ActionOpenFileManager:
  328. {
  329. result_ptr result = m_queries.first()->results().first();
  330. QString path = QFileInfo( result->url() ).path();
  331. tLog() << Q_FUNC_INFO << "open directory" << path;
  332. QDesktopServices::openUrl( path );
  333. }
  334. break;
  335. default:
  336. emit triggered( action );
  337. }
  338. clear();
  339. }
  340. void
  341. ContextMenu::addToQueue()
  342. {
  343. foreach ( const query_ptr& query, m_queries )
  344. {
  345. ViewManager::instance()->queue()->view()->trackView()->model()->appendQuery( query );
  346. }
  347. foreach ( const artist_ptr& artist, m_artists )
  348. {
  349. ViewManager::instance()->queue()->view()->trackView()->model()->appendArtist( artist );
  350. }
  351. foreach ( const album_ptr& album, m_albums )
  352. {
  353. ViewManager::instance()->queue()->view()->trackView()->model()->appendAlbum( album );
  354. }
  355. }
  356. void
  357. ContextMenu::copyLink()
  358. {
  359. if ( !m_queries.isEmpty() )
  360. {
  361. Utils::LinkGenerator::instance()->copyOpenLink( m_queries.first() );
  362. }
  363. else if ( !m_albums.isEmpty() )
  364. {
  365. Utils::LinkGenerator::instance()->copyOpenLink( m_albums.first() );
  366. }
  367. else if ( !m_artists.isEmpty() )
  368. {
  369. Utils::LinkGenerator::instance()->copyOpenLink( m_artists.first() );
  370. }
  371. }
  372. void
  373. ContextMenu::openPage( MenuActions action )
  374. {
  375. if ( !m_queries.isEmpty() )
  376. {
  377. if ( action == ActionTrackPage )
  378. {
  379. ViewManager::instance()->show( m_queries.first() );
  380. }
  381. else
  382. {
  383. if ( action == ActionArtistPage )
  384. {
  385. ViewManager::instance()->show( m_queries.first()->track()->artistPtr() );
  386. }
  387. else if ( action == ActionAlbumPage )
  388. {
  389. ViewManager::instance()->show( m_queries.first()->track()->albumPtr() );
  390. }
  391. }
  392. }
  393. else if ( !m_albums.isEmpty() )
  394. {
  395. if ( action == ActionArtistPage )
  396. {
  397. ViewManager::instance()->show( m_albums.first()->artist() );
  398. }
  399. else
  400. {
  401. ViewManager::instance()->show( m_albums.first() );
  402. }
  403. }
  404. else if ( !m_artists.isEmpty() )
  405. {
  406. ViewManager::instance()->show( m_artists.first() );
  407. }
  408. }
  409. void
  410. ContextMenu::onSocialActionsLoaded()
  411. {
  412. if ( m_queries.isEmpty() || m_queries.first().isNull() )
  413. return;
  414. if ( m_loveAction && m_queries.first()->track()->loved() )
  415. {
  416. m_loveAction->setText( tr( "Un-&Love" ) );
  417. m_loveAction->setIcon( ImageRegistry::instance()->icon( RESPATH "images/not-loved.svg" ) );
  418. }
  419. else if ( m_loveAction )
  420. {
  421. m_loveAction->setText( tr( "&Love" ) );
  422. m_loveAction->setIcon( ImageRegistry::instance()->icon( RESPATH "images/loved.svg" ) );
  423. }
  424. }
  425. void
  426. ContextMenu::setPlaylistInterface( const Tomahawk::playlistinterface_ptr& plInterface )
  427. {
  428. m_interface = plInterface;
  429. }